README.TXT

The \Mssdk\Samples\COM directory contains samples for the some
of the Platform SDK tools to demonstate using various Win32 APIs:

Windows 95 and Windows NT samples
* aniedit
* fontedit
* image
* imagedit
* mc
* porttool
* scrnsave
* spy
* tlist
* windiff
* zoomin

Samples that use Windows NT specific APIs
cacls
compact
dbmon
ddespy
dlgedit
netwatch
perfmon
pviewer
Remote
switcher
uconvert
walker
winat

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 directory. The build
environment can be installed from the readme.htm or by running
setup.exe on the root of the Platform SDK compact disc. The most
recent version of the build environment is also 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\COM
node, you'll need to install the COM and ActiveX components
from the Platform SDK compact disc or download and install them
from http://www.microsoft.com/msdn/sdk/platform.htm.


README.TXT

Creating Animated Cursors

The ANIEDIT tool can be used to create and edit animated cursors. The
program relies on the ImagEdit SDK tool for the actual bitmap manipulation.
ANIEDIT is a good source of information on the format of animated cursors.


ANICMD.C

/****************************************************************************\
*
*     MODULE: anicmd.c
*
*     PURPOSE: Processes WM_COMMANDs for the Animated Cursor Editor
*
*     Copyright 1993-1996 Microsoft Corp.
*
*
* History:
*   21-Apr-1993 JonPa   Wrote it.
*
\****************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include <search.h>
#include "anidefs.h"

static DWORD WINAPI ProcWaitThread( LPVOID lpv );
static BOOL CALLBACK ETWProc( HWND hwnd, LPARAM lParam );

BOOL gfEditFrame = FALSE;
TCHAR gszTempFile[MAX_PATH];

/****************************************************************************\
*
*     FUNCTION: void AniEndDialog( HDLG hdlg, int i )
*
*     PURPOSE:  Destroys Modless Dialogs
*
*
* History:
*   08-Sep-1995 JonPa   Created it
*
\****************************************************************************/
#define AniEndDialog( hdlg, i )         DestroyWindow( hdlg )

/****************************************************************************\
*
*     FUNCTION: void LoadAniFile(hWnd, hfCursor, szFileTitle, szFile)
*
*     PURPOSE:  Loads an ANI from the given handle, and sets up ganiAcon
*
*
* History:
*   31-May-1995 JonPa   Created it
*
\****************************************************************************/
void LoadAniFile(HWND hWnd, HANDLE hfCursor, LPTSTR szFileTitle, LPTSTR szFile)
{
    /* delete any existing ani file */
    NewAniCursor( hWnd );

    /* read in the file */
    if (!ReadAniFile( hWnd, hfCursor )) {
        FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP, TRUE,
                MSG_INVALIDCURSORFILE, szFileTitle );
    } else {
        /*
         * Put the filename in the title.
         */
        lstrcpy(ganiAcon.szFile, szFile);
        SetWindowFileTitle(hWnd, szFileTitle);
        SetDlgItemInt( hWnd, DLG_MAIN_RATE, ganiAcon.anih.jifRate, FALSE);
    }

    ResumePreview(hWnd, DLG_MAIN_PREVIEW);
}

/****************************************************************************\
*
*     FUNCTION: DoCommand(HWND, unsigned, WORD, LONG)
*
*     PURPOSE:  Processes commands for the main dialog box
*
*     MESSAGES:
*
*         WM_INITDIALOG - initialize dialog box
*         WM_COMMAND    - Input received
*
*     COMMENTS:
*
*
* History:
*   21-Apr-1993 JonPa   Created it
*
\****************************************************************************/
BOOL DoCommand( HWND hWnd, UINT wParam, LONG lParam )
{
    int cmd = LOWORD(wParam);

    switch(cmd){

    case MENU_FILE_NEW:
    case DLG_MAIN_BTNNEW:
        /* If dirty, then prompt for save */
        if(!CheckDirty(hWnd))
            break;

        /* free used memory and init structures and dlg */
        NewAniCursor(hWnd);
        ResumePreview(hWnd, DLG_MAIN_PREVIEW);
        break;

    case DLG_MAIN_BTNOPEN:
    case MENU_FILE_OPEN: {
        HANDLE hfCursor;
        TCHAR szFileTitle[MAX_PATH];
        TCHAR szFile[MAX_PATH];

        szFile[0] = TEXT('\0');

        /* check for dirty file */
        if(!CheckDirty(hWnd))
            break;

        /* Put up the open file dialog and get the open handle back */
        hfCursor = PromptAndOpenFile(hWnd, MAX_PATH, szFileTitle,
                 COUNTOF(ganiAcon.szFile), szFile, gpszAniFilter);

        if (hfCursor == INVALID_HANDLE_VALUE)
            break;

        LoadAniFile(hWnd, hfCursor, szFileTitle, szFile );

        break;
    }

    case DLG_MAIN_BTNSAVE:
    case MENU_FILE_SAVE:
    case MENU_FILE_SAVEAS:
        SaveFile(hWnd, cmd == MENU_FILE_SAVEAS);
        break;

    case MENU_FILE_INSERT: {
        TCHAR szFile[MAX_PATH];

        szFile[0] = TEXT('\0');

        /* Put up the open file dialog and get the open handle back */
        if (PromptForFile(hWnd, 0, NULL,
                 MAX_PATH, szFile, gpszCurFilter, gpszImport, FALSE)) {

            /*
             * If we got a file, open it and read the icon data, linking
             * it into the frame list and maintaining the steps as well.
             */
            ganiAcon.fDirty = TRUE;
            CreateFrameFromCursorFile(hWnd, szFile, FALSE);
        }
        break;
    }

    case MENU_FILE_EXPORT: {
#if 0
        TCHAR szFile[MAX_PATH];
        szFile[0] = TEXT('\0');

        /* Put up the open file dialog and get the open handle back */
        if (PromptForFile(hWnd, 0, NULL,
                 MAX_PATH, szFile, NULL, gpszExport, TRUE)) {

            /*
             * If we got a file, open it and read the icon data, linking
             * it into the frame list and maintaining the steps as well.
             */
            ????
        }
#else
        WRITEME(hWnd);
#endif
        break;
    }

    case MENU_FILE_EXIT:
        ExitCommand(hWnd);
        break;

    case DLG_MAIN_BTNCUT:
    case MENU_EDIT_CUT:
        ganiAcon.fDirty = TRUE;

        FALLTHRU(MENU_EDIT_COPY);

    case DLG_MAIN_BTNCOPY:
    case MENU_EDIT_COPY: {
        int *piSel;
        int cSel;

        cSel = GetSelStepCount(hWnd);

        if( cSel > 0 && (piSel = AllocMem(cSel * sizeof(int))) != NULL) {
            PCLPBRDDAT pcbd, pcbdNext, *ppcbd;

            int i;

            GetCurrentSel(hWnd, DLG_MAIN_FRAMELIST, piSel, cSel, &cSel);

            /* Clear clipboard */
            for( pcbd = gpbdClipBoard; pcbd != NULL; pcbd = pcbdNext ) {
                pcbdNext = pcbd->pcbdNext;

                DestroyClpBrdDat(pcbd);
            }

            /*
             * Get the steps and put them in the clipboard in the correct order
             */
            ppcbd = &gpbdClipBoard;

            for( i = 0; i < cSel; i++ ) {
                PSTEP ps;

                ps = GetStep(hWnd, piSel[i]);

                if( IsValidPS(ps) && (pcbd = NewClpBrdDat()) != NULL) {
                    CopyStep(&(pcbd->stp), ps);
                    *ppcbd = pcbd;
                    ppcbd = &(pcbd->pcbdNext);
                }
            }

            *ppcbd = NULL;

            /*
             * If this is a cut, then yank them out of the listbox
             */
            if (cmd == MENU_EDIT_CUT || cmd == DLG_MAIN_BTNCUT) {
                qsort( piSel, cSel, sizeof(piSel[0]), RevCompInts );
                for( i = 0; i < cSel; i++ ) {
                    SendDlgItemMessage(hWnd, DLG_MAIN_FRAMELIST,
                            LB_DELETESTRING, piSel[i], 0);
                }

                FreeMem(piSel);

                ClearStepSel(hWnd);
            }
        }

        break;
    }

    case DLG_MAIN_BTNPASTE:
    case MENU_EDIT_PASTE: {
        PCLPBRDDAT pcbd;
        int iSel, cSel;

        cSel = GetSelStepCount(hWnd);

        if (cSel > 1) {
            FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                    TRUE, MSG_LESSEQONEFRAME);
            break;
        }

        GetCurrentSel(hWnd, DLG_MAIN_FRAMELIST, &iSel, 1, &cSel);

        if (cSel == 0)
            iSel = GetStepCount(hWnd) - 1;

        cSel = iSel;
        ganiAcon.fDirty = TRUE;

        for( pcbd = gpbdClipBoard; pcbd != NULL; pcbd = pcbd->pcbdNext ) {
            PSTEP ps = NewStep();

            if (IsValidPS(ps)) {
                CopyStep(ps, &(pcbd->stp));

                SendDlgItemMessage(hWnd, DLG_MAIN_FRAMELIST, LB_INSERTSTRING,
                    ++cSel, (LPARAM)ps);
            } else {
                FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OKCANCEL |
                        MB_ICONEXCLAMATION, TRUE, MSG_PASTEERR );
            }
        }

        /* in this case, cSel is actually an index */
        iSel += 1;
        ClearStepSel(hWnd);
        SetStepSel(hWnd, iSel, cSel);

        break;
    }

    case DLG_MAIN_DELFRAME:
    case MENU_EDIT_CLEAR: {
        int *piSel;
        int cSteps = GetSelStepCount(hWnd);
        int i;

        if (cSteps <= 0)
            //BUGBUG - should we put a message box up here?
            break;

        ganiAcon.fDirty = TRUE;

        piSel = AllocMem(cSteps * sizeof(int));
        if (piSel == NULL)
            break;

        GetCurrentSel(hWnd, DLG_MAIN_FRAMELIST, piSel, cSteps, &cSteps);

        qsort( piSel, cSteps, sizeof(piSel[0]), RevCompInts );
        for( i = 0; i < cSteps; i++ ) {
            SendDlgItemMessage(hWnd, DLG_MAIN_FRAMELIST, LB_DELETESTRING,
                    piSel[i], 0);
        }

        FreeMem(piSel);
        ClearStepSel(hWnd);

        break;
    }

    case DLG_MAIN_DUPFRAME:
    case MENU_EDIT_DUP:
        /* copy */
        /* paste */
        ganiAcon.fDirty = TRUE;
        WRITEME(hWnd);
        break;

    case DLG_MAIN_EDITFRAME:
    case MENU_EDIT_EDITFRAME:
    case DLG_MAIN_ADDFRAME:
    case MENU_EDIT_ADDFRAME: {
        BOOL fEditFrame;

        ganiAcon.fDirty = TRUE;

        fEditFrame = (cmd == MENU_EDIT_EDITFRAME ||
                    cmd == DLG_MAIN_EDITFRAME);

        EditFrame(hWnd, fEditFrame);

        break;
    }

    case DLG_MAIN_STOP:
        PausePreview(hWnd, DLG_MAIN_PREVIEW);
        break;

    case DLG_MAIN_PLAY:
        ResumePreview(hWnd, DLG_MAIN_PREVIEW);
        break;

    case MENU_HELP_ABOUT:
        DialogBox(hInst, MAKEINTRESOURCE(DLG_ABOUT), hWnd, About);
        break;

    case MENU_HELP_CONTENTS:

        //WinHelp(hWnd, TEXT("RKTOOLS.HLP"), HELP_COMMAND, (DWORD)TEXT("JI(\"RKTOOLS.HLP\">\"main\",\"aniedit\")"));
        //WinHelp(hWnd, TEXT("RKTOOLS.HLP"), HELP_COMMAND, (DWORD)TEXT("JI(\"RKTOOLS.HLP\",\"aniedit\")"));
        WinHelp(hWnd, TEXT("RKTOOLS.HLP"), HELP_COMMAND, (DWORD)TEXT("JI(\"RKTOOLS.HLP>main\",\"aniedit\")"));
        break;

    case MENU_EDIT_OPTIONS:
        if (DialogBox(hInst, MAKEINTRESOURCE(DLG_OPTIONS), hWnd, OptionsProc)){
            InvalidateRect( GetDlgItem(hWnd, DLG_MAIN_PREVIEW), NULL, TRUE);
        }
        break;

    case DLG_MAIN_FRAMELIST: {
        HWND hwndLB = (HWND)lParam;

        switch(HIWORD(wParam)) {

            case LBN_SELCHANGE: {
                PSTEP ps;
                int cSel;
                LPTSTR pszText;

                cSel = SendMessage(hwndLB, LB_GETSELCOUNT, 0, 0);

                if (cSel > 1) {
                    int *piSel;
                    int i;

                    pszText = FmtSprintf( cSel == GetStepCount(hWnd) ?
                            MSG_ALLFRAMES : MSG_FRAMESSELECTED, cSel );
                    SetDlgItemText(hWnd, DLG_MAIN_FRAMEGRP, pszText);
                    FmtFree( pszText );

                    piSel = AllocMem( cSel * sizeof(int) );
                    if( piSel == NULL )
                        break;

                    GetCurrentSel(hWnd, DLG_MAIN_FRAMELIST, piSel, cSel, &cSel);
                    for( i = 0; i < cSel; i++ ) {
                        ps = GetStep(hWnd, piSel[i]);

                        if( IsValidPS(ps) ) {
                            if (i == 0) {
                                ganiAcon.anih.jifRate = ps->jif;
                            } else if(ganiAcon.anih.jifRate != ps->jif) {
                                break;
                            }
                        }
                    }

                    if (i < cSel) {
                        /* rates differ, wipe out jiffy edit control */
                        SetDlgItemText(hWnd, DLG_MAIN_RATE, "");
                    } else {
                        SetDlgItemInt(hWnd, DLG_MAIN_RATE,
                                ganiAcon.anih.jifRate, FALSE);
                    }

                    FreeMem(piSel);

                } else if (cSel == 1) {
                    int iLBSel;

                    GetCurrentSel(hWnd, DLG_MAIN_FRAMELIST, &iLBSel, 1, &cSel);

                    SetPreviewStep(hWnd, DLG_MAIN_PREVIEW, iLBSel);

                    pszText = FmtSprintf( MSG_FRAMEOFSEL, iLBSel + 1,
                            GetStepCount(hWnd) );
                    SetDlgItemText(hWnd, DLG_MAIN_FRAMEGRP, pszText);
                    FmtFree( pszText );

                    ps = (PSTEP)SendMessage(hwndLB, LB_GETITEMDATA, iLBSel, 0);

                    if (IsValidPS(ps)) {
                        SetDlgItemInt( hWnd, DLG_MAIN_RATE, ps->jif, FALSE);
                    }
                } else {
                    pszText = FmtSprintf( MSG_NOFRAMESSEL );
                    SetDlgItemText(hWnd, DLG_MAIN_FRAMEGRP, pszText);
                    FmtFree( pszText );
                    SetPreviewStep(hWnd, DLG_MAIN_PREVIEW, 0);
                }
                break;

            case LBN_DBLCLK:
                ganiAcon.fDirty = TRUE;
                EditFrame(hWnd, TRUE);
                break;
            }
        }
        break;

    }

    case DLG_MAIN_RATE: {
        static BOOL fEditCtlHasFocus = FALSE;
        static BOOL fEditCtlHasChanged = FALSE;
        int *piSel;
        int cSteps;
        int i;

        switch(HIWORD(wParam)) {
        case EN_SETFOCUS:
            fEditCtlHasFocus = TRUE;
            break;

        case EN_KILLFOCUS:
            fEditCtlHasFocus = FALSE;
            break;

        case EN_CHANGE:

            cSteps = GetSelStepCount(hWnd);

            if (fEditCtlHasFocus && cSteps >=1 &&
                    GetWindowTextLength(GetDlgItem(hWnd, DLG_MAIN_RATE)) > 0) {
                JIF jif;
                BOOL fOK;

                ganiAcon.fDirty = TRUE;
                piSel = AllocMem( cSteps * sizeof(int) );
                if (piSel == NULL) {
                    SetFocus((HWND)lParam);
                    break;
                }

                jif = GetDlgItemInt(hWnd, DLG_MAIN_RATE, &fOK, FALSE);

                if (jif == 0 || !fOK) {
                    FmtMessageBox(hWnd, MSG_LITERAL, gszWindowTitle,
                        MB_OK | MB_ICONEXCLAMATION, TRUE,
                        MSG_RATERANGE);
                    SetFocus((HWND)lParam);
                    break;
                }

                GetCurrentSel(hWnd, DLG_MAIN_FRAMELIST, piSel, cSteps,
                        &cSteps);

                for( i = 0; i < cSteps; i++ ) {
                    PSTEP ps = GetStep(hWnd, piSel[i]);

                    if (IsValidPS(ps)) {
                        ps->jif = jif;
                    }
                }
                InvalidateRect(GetDlgItem(hWnd,DLG_MAIN_FRAMELIST), NULL,TRUE);

                FreeMem( piSel );
            }
            break;
        }
        break;
    }

    default:
        return FALSE;
    }

    return TRUE;
}

/****************************************************************************\
*
*     FUNCTION: About(HWND, unsigned, WORD, LONG)
*
*     PURPOSE:  Processes messages for "About" dialog box
*
*     MESSAGES:
*
*         WM_INITDIALOG - initialize dialog box
*         WM_COMMAND    - Input received
*
*     COMMENTS:
*
*         No initialization is needed for this particular dialog box, but TRUE
*         must be returned to Windows.
*
*         Wait for user to click on "Ok" button, then close the dialog box.
*
\****************************************************************************/

BOOL APIENTRY About(
        HWND hDlg,                /* window handle of the dialog box */
        UINT message,             /* type of message                 */
        UINT wParam,              /* message-specific information    */
        LONG lParam)
{
    switch (message) {
        case WM_INITDIALOG:                /* message: initialize dialog box */
            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);        /* Exits the dialog box        */
                return (TRUE);
            }
            break;
    }
    return (FALSE);                           /* Didn't process a message    */
        UNREFERENCED_PARAMETER(lParam);
}

/****************************************************************************\
*
*     FUNCTION: OptionsProc(HWND, unsigned, WORD, LONG)
*
*     PURPOSE:  Processes messages for "Options" dialog box
*
*     MESSAGES:
*
*         WM_INITDIALOG - initialize dialog box
*         WM_COMMAND    - Input received
*
\****************************************************************************/

BOOL APIENTRY OptionsProc(
        HWND hDlg,                /* window handle of the dialog box */
        UINT message,             /* type of message                 */
        UINT wParam,              /* message-specific information    */
        LONG lParam)
{
    int i;
    int fRepaint = FALSE;

    switch (message) {
    case WM_INITDIALOG:                /* message: initialize dialog box */
        SendDlgItemMessage(hDlg, DLG_OPTIONS_EDITOR, EM_LIMITTEXT, MAX_PATH, 0);
        SetDlgItemText(hDlg, DLG_OPTIONS_EDITOR, gszCursorEditor);
        CheckRadioButton( hDlg,
                          DLG_OPTIONS_RADIO_DESKCOL,
                          DLG_OPTIONS_RADIO_WINCOL,
                          garadColor[giradColor].id );
        return (TRUE);

    case WM_COMMAND:                      /* message: received a command */
        switch(LOWORD(wParam)) {
        case IDOK:

            /*
             * Get the new desk color
             */
            for( i = 0; garadColor[i].id != 0; i++ ) {
                if( IsDlgButtonChecked(hDlg, garadColor[i].id) ) {
                    break;
                }
            }

            if (i != giradColor ) {
                /* new color, make new brush and repaint */
                if (ghbrPrevBackgnd != NULL)
                    DeleteObject(ghbrPrevBackgnd);

                ghbrPrevBackgnd =
                        CreateSolidBrush(GetSysColor(garadColor[i].idSys));
                giradColor = i;
                fRepaint = TRUE;
            }

            /*
             * Get new editor
             */
            GetDlgItemText(hDlg,DLG_OPTIONS_EDITOR,
                    gszCursorEditor,COUNTOF(gszCursorEditor));

            for( i = 0; i < COUNTOF(gszCursorEditor); i++ ) {

                if (gszCursorEditor[i] == TEXT('\0'))
                    break;

                if (gszCursorEditor[i] == TEXT('%') &&
                        ++i < COUNTOF(gszCursorEditor) &&
                        gszCursorEditor[i] == TEXT('1')) {
                    break;
                }
#if defined(DBCS) && !defined(UNICODE)
                if (IsDBCSLeadByte(gszCursorEditor[i])) {
                    i++;
                }
#endif
            }

            if (i >= COUNTOF(gszCursorEditor) ||
                gszCursorEditor[i] != TEXT('1')) {

                if (i >= (COUNTOF(gszCursorEditor) - 4)) {
                    i =  COUNTOF(gszCursorEditor) - 4;
                }

                lstrcpy(&gszCursorEditor[i], TEXT(" %1"));
            }

        case IDCANCEL:
            EndDialog(hDlg, fRepaint);        /* Exits the dialog box        */
            return (TRUE);

        default:
            break;
        }
        break;
    }
    return (FALSE);                           /* Didn't process a message    */
        UNREFERENCED_PARAMETER(lParam);
}

/****************************************************************************\
*
*     FUNCTION: ExitCommand(HWND)
*
*     PURPOSE:  Exit the program chekcing for dirty files etc.
*
*
\****************************************************************************/
VOID ExitCommand(HWND hWnd) {

    /* if file is dirty then prompt for save */
    if(CheckDirty(hWnd))
        AniEndDialog(hWnd, TRUE);
}

/****************************************************************************\
*
*     FUNCTION: CheckDirty(HWND)
*
*     PURPOSE:  check for dirty files and return TRUE if it is OK to continue.
*
*
\****************************************************************************/
BOOL CheckDirty(HWND hWnd) {
    int idRet;

    /* if file is dirty then prompt for save */
    if (ganiAcon.fDirty) {
        idRet = FmtMessageBox( hWnd, MSG_LITERAL, gszWindowTitle,
                MB_YESNOCANCEL | MB_ICONEXCLAMATION, TRUE, MSG_SAVEFILEQUEST,
                ganiAcon.szFile);

        switch( idRet ) {
        case IDYES:
            SaveFile(hWnd, FALSE);
            break;

        case IDNO:
            break;

        case IDCANCEL:
            return FALSE;
        }
    }

    return TRUE;
}

/****************************************************************************\
*
*     FUNCTION: HWND ExecProgram(  HWND hwndCaller, LPTSTR pszCmdLine )
*
*     PURPOSE:  Creates a process and returns the new processes main window
*
*     RETURNS: NULL if the process could not be created, otherwise the
*              processes main window handle.
*
*     SIDEEFFECT: This function will also start a thread that will block
*               on the process handle until the process terminates.  At that
*               time, the thread will post a message back to the calliers
*               window.
*
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
BOOL ExecProgram( HWND hwndCaller, LPTSTR pszCmdLine ) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    HWND hwnd;
    PTHDDATA pthd;
    DWORD tid;
    HANDLE hthrd;

    /*
     * Create the monitor thread (suspened)
     */
    pthd = AllocMem(sizeof(THDDATA));

    if (pthd == NULL)
        return FALSE;

    /* set thread data to be invalid incase we have to abort */
    pthd->hprocMonitor = NULL;
    pthd->hwndCaller = hwndCaller;

    if ((hthrd = CreateThread( NULL, 0, ProcWaitThread, pthd, CREATE_SUSPENDED,
            &tid )) == NULL) {
        /* could not create the monitor thread, return error */
        FreeMem(pthd);
        return FALSE;
    }

    /*
     * Create the process
     */
    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    si.wShowWindow = SW_SHOW;
    si.dwFlags = STARTF_USESHOWWINDOW;

    if (!CreateProcess( NULL, pszCmdLine, NULL, NULL, FALSE, 0, NULL, NULL,
            &si, &pi)) {
        ResumeThread(hthrd);    // make thread localfree the data and exit
        return FALSE;
    }

    DPRINT(("MT:Child IDs proc/thd: 0x%lx / 0x%lx\n", pi.dwProcessId, pi.dwThreadId));
    DPRINT(("MT:Child Hnd proc/thd: 0x%lx / 0x%lx\n", pi.hProcess, pi.hThread));

    /*
     * Wait for the main window to be created
     */
    if( WaitForInputIdle( pi.hProcess, CMS_WAIT_FOR_PROCESS ) != 0 ) {
        ResumeThread(hthrd);    // make thread localfree the data and exit
        return FALSE;
    }

    DPRINT(("MT:Child is idle\n"));

    /*
     * Enumerate the new processes main thread's windows and
     * return the main one.
     */
    hwnd = NULL;
    EnumThreadWindows( pi.dwThreadId, ETWProc, (LPARAM)&hwnd );

#if 0
    if (hwnd != NULL) {
        pthd->hprocMonitor = pi.hProcess;
        pthd->hwndMonitor = hwnd;

        SendMessage(hwndCaller, AIM_SETCHILDAPP, 0, hwnd);
    }
#else
    pthd->hprocMonitor = pi.hProcess;
    pthd->hwndMonitor = hwnd;

    if (pthd->hprocMonitor != NULL)
        SendMessage(hwndCaller, AIM_SETCHILDAPP, 0, (LPARAM)hwnd);
#endif

    ResumeThread(hthrd);
    CloseHandle(hthrd);
    CloseHandle(pi.hThread);
    return TRUE;
}

/****************************************************************************\
*
*     FUNCTION: BOOL CALLBACK ETWProc( HWND hwnd, LPARAM lParam )
*
*     PURPOSE:  Enumeration proc for ExecProgram.  It looks for the thread's
*               top level window.
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
BOOL CALLBACK ETWProc( HWND hwnd, LPARAM lParam ) {
    DWORD *pdw = (DWORD *)lParam;

    /*
     * If this window has no parent, then it is a toplevel
     * window for the thread.  Remember the last one we find since it
     * is probably the main window.
     */

    if (GetParent(hwnd) == NULL) {
        DPRINT(("MT:EnumThdWin found 0x%lx\n", (DWORD)hwnd));
        *pdw = (DWORD)hwnd;
    }

    return TRUE;
}

/****************************************************************************\
*
*     FUNCTION: DWORD ProcWaitThread( LPDWORD lpdw )
*
*     PURPOSE:  Thread to wait on a process and then post a message
*
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
DWORD WINAPI ProcWaitThread( LPVOID lpv ) {
    LPDWORD lpdw = lpv;
    PTHDDATA pthd = (PTHDDATA)lpdw;
    DWORD dwRet;

    if (pthd->hprocMonitor == NULL) {
        /* something went wrong, just exit now */
        DPRINT(("wt:Aborting\n"));
        FreeMem( lpdw );
        ExitThread(0);
    }

    DPRINT(("wt:Waiting\n"));
    dwRet = WaitForSingleObject( pthd->hprocMonitor, INFINITE );

    DPRINT(("wt:Send AIM_PROCESSTERM\n"));
    SendMessage(pthd->hwndCaller, AIM_PROCESSTERM, (dwRet == WAIT_OBJECT_0),
            (LPARAM)pthd->hwndMonitor);

    CloseHandle( pthd->hprocMonitor );
    FreeMem( lpdw );
    ExitThread(0);

    return 0;
}

/****************************************************************************\
*
*     FUNCTION: void NewAniCursor( HWND hwnd )
*
*     PURPOSE:  erase any used memory and init to a clean slate
*
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
void NewAniCursor( HWND hwnd ) {
    int i, cSteps;
    LPTSTR psz;

    PausePreview(hwnd, DLG_MAIN_PREVIEW);

    /* Step through the list box, deleting all the lb entryies and everything
     * that they point to (except the icons).
     */
    cSteps = GetStepCount(hwnd);

    if (cSteps != LB_ERR) {
        for( i = 0; i < cSteps; i++ ) {
            /*
             * Delete the top item of the list.  Note that once that item
             * (current index 0) is deleted, then the next item will move
             * up and become index 0.
             */
            SendDlgItemMessage( hwnd, DLG_MAIN_FRAMELIST, LB_DELETESTRING,0,0);
        }
    }

    /*
     * Step through the icon list deleting them.  We don't need to call
     * DestroyFrame since we are trashing the whole chain.
     */

#if 0
    pf = gpfrmFrames;
    gpfrmFrames = NULL;

    DON'T DO THIS!!! it will wipe out the clip board accidentally!

    for(; pf != NULL; pf = pfrmNext ) {
        pfrmNext = pf->pfrmNext;

        DestroyIcon( pf->hcur );
        FreeMem(pf);
    }
#endif

    /*
     * Init Ani header
     */
    ZeroMemory( &ganiAcon, sizeof(ganiAcon) );
    ganiAcon.anih.cbSizeof = sizeof(ganiAcon);
    ganiAcon.anih.cbSizeof = AF_ICON;
    ganiAcon.anih.jifRate = 10;

    SetDlgItemTextA(hwnd, DLG_MAIN_TITLE, ganiAcon.azTitle);
    SetDlgItemTextA(hwnd, DLG_MAIN_AUTHOR, ganiAcon.azCreator);

    SetDlgItemInt( hwnd, DLG_MAIN_RATE, ganiAcon.anih.jifRate, FALSE);

    PreviewCursor(hwnd, DLG_MAIN_PREVIEW);

    SetWindowFileTitle(hwnd, gpszUntitled );

    psz = FmtSprintf(MSG_NOFRAMESSEL);
    SetDlgItemText(hwnd, DLG_MAIN_FRAMEGRP, psz);
    FmtFree( psz );
}

/****************************************************************************\
*
*     FUNCTION: BOOL GetCurrentSel( HWND hwnd, int id, int * paiSel,
*                                                   int ciSel, int *pcSel );
*
*     PURPOSE:  Gets the selections and returns it's index
*
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
BOOL GetCurrentSel( HWND hwnd, int id, int * paiSel, int ciSel, int *pcSel ) {

#ifdef MULTISEL
    *pcSel = SendDlgItemMessage(hwnd,id, LB_GETSELITEMS, ciSel,(LPARAM)paiSel);

    if (*pcSel == LB_ERR) {
        *pcSel = 0;
    }
#else

    *paiSel = SendDlgItemMessage(hwnd, id, LB_GETCURSEL, 0, 0);
    *pcSel = 1;

    if (*paiSel == LB_ERR)
        *pcSel = 0;
#endif

return TRUE;
}

/****************************************************************************\
*
*     FUNCTION: VOID SetCurrentSel( HWND hwnd, int id, BOOL fExtend, int iSel);
*
*     PURPOSE:  Sets the selections and returns it's index
*
*
* History:
*   29-Apr-1993 JonPa   Created it
*
\****************************************************************************/
VOID SetCurrentSel( HWND hwnd, int id, BOOL fExtend, int iSel) {
#ifdef MULTISEL
    if (!fExtend) {
        SendDlgItemMessage(hwnd, id, LB_SETSEL, FALSE, -1);
    }

    SendDlgItemMessage(hwnd, id, LB_SETSEL, TRUE, iSel);
#else
    SendDlgItemMessage(hwnd, id, LB_SETCURSEL, iSel, 0);
#endif

    UpdateStepSel( hwnd );
}

/****************************************************************************\
*
*     FUNCTION: VOID EditFrame(HWND hwnd, int iSel);
*
*     PURPOSE:  Runs ImagEdit on the frame indexed by iSel
*
*
* History:
*   27-Apr-1993 JonPa
*
\****************************************************************************/
VOID EditFrame(HWND hWnd, BOOL fEditFrame) {
    LPTSTR pszCmdLine = NULL;
    int cchCmdLine;
    HANDLE hf;
    DWORD cb;
    PBYTE pbIcon;
    DWORD cbIcon;
    int iSel;
    int cSel;
    BOOL fExeced;

    /* create a temp .cur file name */
    if( !GetTempCursorFileName( gszTempFile ) ) {
        FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                TRUE, MSG_OUTOFRESOUCES );
        return;
    }

    cSel = GetSelStepCount(hWnd);

    if ( (fEditFrame && (cSel != 1)) || cSel > 1 ) {

        FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                TRUE, fEditFrame ? MSG_MUSTEQONEFAME : MSG_LESSEQONEFRAME);
        return;
    }

    /* cache the currently selected item (Singluar) */
    GetCurrentSel( hWnd, DLG_MAIN_FRAMELIST, &iSel, 1, &cSel );

    /*
     * If edit, then write the frame to the file and save checksum
     * otherwise write the blank cursor to the file.
     */

    hf = CreateFile( gszTempFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL, NULL );

    if (hf == INVALID_HANDLE_VALUE) {
        FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP, TRUE,
            MSG_CANTCREATEFILE, gszTempFile );
        return;
    }

    if (fEditFrame || cSel != 0) {
        PSTEP ps = GetStep(hWnd, iSel);

        if( !IsValidPS(ps) ) {
            FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                    TRUE, MSG_OUTOFRESOUCES );
            CloseHandle(hf);
            return;
        }

        pbIcon = ps->pfrmFrame->abIcon;
        cbIcon = ps->pfrmFrame->rtag.ckSize;

    } else {

        HRSRC hr = FindResource(hInst, MAKEINTRESOURCE(ID_BLANKCUR),
                MAKEINTRESOURCE(RCT_RAWDATA));

        if (hr == NULL || (pbIcon =LockResource(LoadResource(hInst, hr))) ==
                NULL) {
            FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                    TRUE, MSG_OUTOFRESOUCES );
            CloseHandle(hf);
            return;
        }

        cbIcon = SizeofResource(hInst, hr);
    }

    WriteFile(hf, pbIcon, cbIcon, &cb, NULL);

    CloseHandle(hf);

    /*
     * change .tmp to .cur
     */
    { TCHAR szOldName[MAX_PATH];

        cchCmdLine = lstrlen(gszTempFile);

        lstrcpy( szOldName, gszTempFile );
        lstrcpy( &gszTempFile[cchCmdLine - 3], gpszCUR );

        if(!MoveFile(szOldName, gszTempFile))
            lstrcpy( gszTempFile, szOldName );

        cchCmdLine = (cchCmdLine + lstrlen(gszCursorEditor) + 1 + 1) *
                sizeof(TCHAR);

        pszCmdLine = AllocMem(cchCmdLine);
    }

    if (pszCmdLine == NULL)
        return;

    {
        LPTSTR pszTempFile = gszTempFile;

        FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                gszCursorEditor, 0, 0, pszCmdLine, cchCmdLine, (va_list *)(DWORD)&pszTempFile);
    }

    /* spawn imagedit on the file */
    fExeced = ExecProgram( hWnd, pszCmdLine );
    DPRINT(("MT:Begin Defer to child\n"));

    FreeMem(pszCmdLine);

    if ( fExeced  ) {
        gfEditFrame = fEditFrame;
    } else {
        FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                    TRUE, MSG_NOIMAGEDIT, gszCursorEditor );
    }
}

/****************************************************************************\
*
*     FUNCTION: PSTEP NewStep( void );
*
*
*     PURPOSE:  Creates a new step and set's its pfrmFrame to NULL;
*
*
* History:
*   29-Apr-1993 JonPa   Created it
*
\****************************************************************************/
PSTEP NewStep( void ) {
    PSTEP ps;

    ps = AllocMem(sizeof(STEP));

    if (IsValidPS(ps))
        ps->pfrmFrame = NULL;

    return ps;
}

/****************************************************************************\
*
*     FUNCTION: VOID DestroyStep( PSTEP ps );
*
*
*     PURPOSE:  Deletes a step, and derefernces its frame, deleting it if
*               necessary.
*
* History:
*   29-Apr-1993 JonPa   Created it
*
\****************************************************************************/
VOID DestroyStep( PSTEP ps ) {
    LinkStepFrame(ps, NULL);

    FreeMem(ps);
}

/****************************************************************************\
*
*     FUNCTION: VOID CopyStep( PSTEP psDst, PSTEP psSrc );
*
*
*     PURPOSE:  Copyies a step, bumping the ref count of the frame if it
*               needs it.
*
* History:
*   07-May-1993 JonPa   Created it
*
\****************************************************************************/
VOID CopyStep( PSTEP psDst, PSTEP psSrc ) {
    *psDst = *psSrc;

    if( psDst->pfrmFrame != NULL ) {
        psDst->pfrmFrame->cRef += 1;
    }
}

/****************************************************************************\
*
*     FUNCTION: VOID LinkStepFrame( PSTEP ps, PFRAME pf );
*
*
*     PURPOSE:  Unlinks a step from its frame and then links the new
*               frame in its place.  If the old frame is an orphan, it
*               gets destroyed.
*
*
* History:
*   29-Apr-1993 JonPa   Created it
*
\****************************************************************************/
VOID LinkStepFrame(PSTEP ps, PFRAME pf ) {
    PFRAME pfOld = ps->pfrmFrame;

    if (pf != NULL)
        pf->cRef++;

    if (pfOld != NULL && --(pfOld->cRef) == 0)
        DestroyFrame(pfOld);

    ps->pfrmFrame = pf;
}

/****************************************************************************\
*
*     FUNCTION: VOID DestroyFrame( PFRAME pf );
*
*
*     PURPOSE:  Unlinks a frame from the list, deletes its hcur, and
*               Frees its memory.
*
*
* History:
*   28-Apr-1993 JonPa   Created it
*
\****************************************************************************/
VOID DestroyFrame( PFRAME pf ) {
    PFRAME pfList;

    if (pf == gpfrmFrames) {
        gpfrmFrames = pf->pfrmNext;
    } else {

        for( pfList = gpfrmFrames; pfList != NULL;
                pfList = pfList->pfrmNext ) {

            if (pfList->pfrmNext == pf) {
                break;
            }
        }

        if (pfList != NULL) {
            pfList->pfrmNext = pf->pfrmNext;
        }
    }

    DestroyIcon( pf->hcur );
    FreeMem(pf);
}

/****************************************************************************\
*
*     FUNCTION: PCLPBRDDAT NewClpBrdDat( void )
*
*
*     PURPOSE:  Creates a new clip board data struct
*
*
* History:
*   29-Apr-1993 JonPa   Created it
*
\****************************************************************************/
PCLPBRDDAT NewClpBrdDat( void ) {
    PCLPBRDDAT pcbd = AllocMem( sizeof(CLPBRDDAT) );

    if (pcbd != NULL)
        pcbd->stp.pfrmFrame = NULL;

    return pcbd;
}

/****************************************************************************\
*
*     FUNCTION: VOID DestroyClpBrdDat(PCLPBRDDAT pcbd)
*
*
*     PURPOSE:  Creates a new clip board data struct
*
*
* History:
*   29-Apr-1993 JonPa   Created it
*
\****************************************************************************/
VOID DestroyClpBrdDat(PCLPBRDDAT pcbd) {
    LinkStepFrame(&(pcbd->stp), NULL);

    FreeMem(pcbd);
}

/****************************************************************************\
*
*     FUNCTION: VOID SetWindowFileTitle(HWND hWnd, LPTSTR szFileTitle)
*
*
*     PURPOSE:  Sets the file title
*
*
* History:
*   30-Apr-1993 JonPa   Created it
*
\****************************************************************************/
VOID SetWindowFileTitle(HWND hWnd, LPTSTR szFileTitle) {

    /*
     * We use LocalAlloc here instead of AllocMem because we don't really
     * char if it fails
     */
    int cch = lstrlen( gszWindowTitle ) + lstrlen(szFileTitle);
    LPTSTR pszTitle = LocalAlloc(LPTR, (cch+4) * sizeof(TCHAR) );

    if (pszTitle != NULL) {
        wsprintf( pszTitle, "%s - %s", gszWindowTitle, szFileTitle );
        SetWindowText(hWnd, pszTitle);

        LocalFree(pszTitle);
    } else {
        SetWindowText(hWnd, gszWindowTitle);
    }
}

int __cdecl RevCompInts(const void *elm1, const void *elm2) {
    return *((int *)elm2) - *((int *)elm1);
}

/****************************************************************************\
*
*     FUNCTION: ClearStepSel
*
*
*     PURPOSE:  Clears all selections from the frame list
*
*
* History:
*   02-Jul-1993 JonPa   Created it
*
\****************************************************************************/
VOID ClearStepSel( HWND hWnd )  {
    int cItems = GetStepCount(hWnd);

    if (cItems != 0) {
        SendDlgItemMessage(hWnd, DLG_MAIN_FRAMELIST,
                LB_SELITEMRANGE, (WPARAM)FALSE, MAKELPARAM(0, cItems - 1));
    }

    UpdateStepSel(hWnd);
}


ANICUR.H

/****************************** Module Header ******************************\
* Module Name: ANICUR.H
*
* This header contains all structures and constants needed for manipulating
* Animated cursor files.
*
* History:
*   28-Apr-1993 JonPa   Copied from Windows NT
*
\***************************************************************************/

#include <mmsystem.h>

// RIFF chunk header.

typedef struct _RTAG {
    FOURCC ckID;
    DWORD ckSize;
} RTAG, *PRTAG;

// Valid TAG types.

// 'ANI ' - simple ANImation file

#define FOURCC_ACON  mmioFOURCC('A', 'C', 'O', 'N')

// 'anih' - ANImation Header
// Contains an ANIHEADER structure.

#define FOURCC_anih mmioFOURCC('a', 'n', 'i', 'h')

// 'rate' - RATE table (array of jiffies)
// Contains an array of JIFs.  Each JIF specifies how long the corresponding
// animation frame is to be displayed before advancing to the next frame.
// If the AF_SEQUENCE flag is set then the count of JIFs == anih.cSteps,
// otherwise the count == anih.cFrames.

#define FOURCC_rate mmioFOURCC('r', 'a', 't', 'e')

// 'seq ' - SEQuence table (array of frame index values)
// Countains an array of DWORD frame indices.  anih.cSteps specifies how
// many.

#define FOURCC_seq  mmioFOURCC('s', 'e', 'q', ' ')

// 'fram' - list type for the icon list that follows

#define FOURCC_fram mmioFOURCC('f', 'r', 'a', 'm')

// 'icon' - Windows ICON format image (replaces MPTR)

#define FOURCC_icon mmioFOURCC('i', 'c', 'o', 'n')

// Standard tags (but for some reason not defined in MMSYSTEM.H)

#define FOURCC_INFO mmioFOURCC('I', 'N', 'F', 'O')      // INFO list
#define FOURCC_IART mmioFOURCC('I', 'A', 'R', 'T')      // Artist
#define FOURCC_INAM mmioFOURCC('I', 'N', 'A', 'M')      // Name/Title

typedef DWORD JIF, *PJIF;

typedef struct _ANIHEADER {     /* anih */
    DWORD cbSizeof;
    DWORD cFrames;
    DWORD cSteps;
    DWORD cx, cy;
    DWORD cBitCount, cPlanes;
    JIF   jifRate;
    DWORD fl;
} ANIHEADER, *PANIHEADER;

/* If the AF_ICON flag is specified the fields cx, cy, cBitCount, and
 * cPlanes are all unused.  Each frame will be of type ICON and will
 * contain its own dimensional information.
 */

#define AF_ICON     0x0001L     /* Windows format icon/cursor animation */
#define AF_SEQUENCE 0x0002L     /* Animation is sequenced */


ANIDEFS.H

/*
 * Common includes
 *
 */
#include "anicur.h"
#include "anidlgs.h"
#include "animsg.h"

/*
 * Constants
 *
 */
#define CMS_WAIT_FOR_PROCESS    (60 * 1000) // one minute

#define AIM_PROCESSTERM         (WM_USER + 0x0FFF)
#define AIM_SETCHILDAPP         (AIM_PROCESSTERM + 1)

#define CCH_TITLE    80
#define CCH_CREATOR  80
#define CCH_JIF      10         /* Numbers are small even in German! */

#define CTRL_C  (TEXT(('C' - 'A') + 1))
#define CTRL_V  (TEXT(('V' - 'A') + 1))
#define CTRL_X  (TEXT(('X' - 'A') + 1))

#define TITL_ERROR  (DWORD)(-1)
#define szPREVIEW   TEXT("AniEditPreviewClass")
#define szBNTBAR    TEXT("AniEditBtnBarClass")

#define PM_NEWCURSOR            (WM_USER + 0x4a50)
#define PM_PAUSEANIMATION       (PM_NEWCURSOR + 1)
#define PM_UNPAUSEANIMATION     (PM_NEWCURSOR + 2)
#define PM_SETSTEP              (PM_NEWCURSOR + 3)

#define ID_PREVIEWTIMER         (0x0050)

#define MULTISEL        1       /* listbox is mutliple selction */

/*
 * Structure defintions
 *
 */
typedef struct _FRAME *PFRAME;

typedef struct _FRAME {
    int         cRef;           /* refrence count */
    int         iFrame;
    PFRAME      pfrmNext;
    DWORD       dwCheckSum;     /* checksum of the icon data.  Used to
                                 * compare new imported icons against existing
                                 * ones to see if we can sequence them.
                                 */
    HICON       hcur;           /* icon representation of raw data below */
    WORD        xHotSpot;
    WORD        yHotSpot;
    RTAG        rtag;           /* icon chunk as it appears in file */
    BYTE        abIcon[1];
} FRAME;

typedef struct _STEP *PSTEP;

typedef struct _STEP {
    JIF     jif;
    PFRAME  pfrmFrame;
} STEP;

typedef struct _THDDATA {
    HWND    hwndCaller;
    HWND    hwndMonitor;
    HANDLE  hprocMonitor;
} THDDATA, *PTHDDATA;

typedef struct _ANICUR {
    ANIHEADER anih;
    BOOL      fDirty;
    CHAR      azTitle[CCH_TITLE];
    CHAR      azCreator[CCH_CREATOR];
    TCHAR     szFile[MAX_PATH];
} ANICUR;

typedef struct _CLPBRDDAT *PCLPBRDDAT;

typedef struct _CLPBRDDAT {
    PCLPBRDDAT pcbdNext;
    STEP    stp;
} CLPBRDDAT;

//
// Structure that contains data used within a preview window.  This
// data is unique for each preview window, and is used to optimize
// the painting.
//
typedef struct
{
    HDC hdcMem;
    HBITMAP hbmMem;
    HBITMAP hbmOld;
    HICON   hcur;
    DWORD   iFrame;
    WORD    xHot;
    WORD    yHot;
} PREVIEWDATA, *PPREVIEWDATA;

typedef struct {
    int id;
    int idSys;
} RADIOCOLOR;

/*
 * Header of the resource file in the new format
 */
#pragma pack(2)
typedef struct tagNEWHEADER {
    WORD reserved;
    WORD rt;
    WORD cResources;
} NEWHEADER, *LPNEWHEADER;

typedef struct tagICONDIR {
    BYTE Width;            /* 16, 32, 64 */
    BYTE Height;           /* 16, 32, 64 */
    BYTE ColorCount;       /* 2, 8, 16 */
    BYTE reserved;
} ICONDIR;

typedef struct tagCURSORDIR {
    WORD Width;
    WORD Height;
} CURSORDIR;

typedef struct tagRESDIR {
    union {
        ICONDIR Icon;
        CURSORDIR Cursor;
    } ResInfo;

    WORD Planes;
    WORD BitCount;
    DWORD BytesInRes;
    WORD idIcon;
} RESDIR;
typedef RESDIR *LPRESDIR;

typedef struct _ICONFILERESDIR {    // ird
    BYTE bWidth;
    BYTE bHeight;
    BYTE bColorCount;
    BYTE bReserved;
    WORD xHotspot;
    WORD yHotspot;
    DWORD dwDIBSize;
    DWORD dwDIBOffset;
} ICONFILERESDIR;

typedef struct tagCURSORRESOURCE {
    WORD xHotspot;
    WORD yHotspot;
    BITMAPINFOHEADER bih;
} CURSORRESOURCE, *PCURSORRESOURCE;
#pragma pack()

/*
 * Globals
 *
 */
extern HANDLE hInst;
extern HWND ghwndMain;
extern HWND ghwndLB;
extern int gcyCursor, gcxCursor;
extern HBRUSH ghbrHighlight, ghbrWindow, ghbrPrevBackgnd;
extern COLORREF gcrHighlightText;
extern ANICUR ganiAcon;
extern TCHAR gszTempFile[];
extern BOOL gfEditFrame;
extern TCHAR gszWindowTitle[];
extern TCHAR gszDots[];
extern PFRAME   gpfrmFrames;
extern PCLPBRDDAT gpbdClipBoard;
extern TCHAR gszCursorEditor[MAX_PATH];
extern int giradColor;
RADIOCOLOR garadColor[];

/*
 * String table pointers
 */
extern LPTSTR gpszAniFilter;
extern LPTSTR gpszImagEdit;
extern LPTSTR gpszCUR;
extern LPTSTR gpszANI;
extern LPTSTR gpszUnknownError;
extern LPTSTR gpszCurFilter;
extern LPTSTR gpszUntitled;
extern LPTSTR gpszImport;

/*
 * Macros
 *
 */
#if 0
#   include <stdio.h>
#   define DPRINT(p)   if(1){ printf p; fflush(stdout); } else
#else
#   define DPRINT(p)
#endif

#define WRITEME(hwnd)       \
            MessageBox(hwnd, "This function is not yet implemented",    \
            NULL, MB_OK | MB_ICONSTOP)

#define FALLTHRU(label)

#define COUNTOF(sz)     (sizeof(sz) / sizeof((sz)[0]))

#define FreeMem(pv)     LocalFree((HLOCAL)pv)

#define IsValidPS(ps)   (((ps) != (PSTEP)LB_ERR) && ((ps) != NULL))

#define PADUP(cb)       (((cb) + 1) & ~1)
#define RET_CLOSE_IF_ERR( func, hf )  \
        if( !func ) {CloseHandle(hf); return FALSE;} else

#define GetStep(hwnd, iSel) (PSTEP)SendDlgItemMessage(hwnd, \
        DLG_MAIN_FRAMELIST, LB_GETITEMDATA, iSel, 0)

#define GetStepCount(hwnd) SendDlgItemMessage(hwnd, \
        DLG_MAIN_FRAMELIST, LB_GETCOUNT, 0, 0)

#define GetSelStepCount(hwnd) SendDlgItemMessage(hwnd, \
        DLG_MAIN_FRAMELIST, LB_GETSELCOUNT, 0, 0)

#define ResumePreview(hwnd, id) \
    SendDlgItemMessage(hwnd, id, PM_UNPAUSEANIMATION, 0, 0)

#define PausePreview(hwnd, id) \
    SendDlgItemMessage(hwnd, id, PM_PAUSEANIMATION, 0, 0)

#define SetPreviewStep(hwnd, id, iStep) \
    SendDlgItemMessage(hwnd, id, PM_SETSTEP, iStep, 0)

#define PreviewCursor(hwnd, id) \
    SendDlgItemMessage(hwnd, id, PM_NEWCURSOR, 0, 0)

#define FmtFree( psz ) if((psz) != gszDots) LocalFree(psz); else

#define UpdateStepSel( hWnd )                                    \
    SendMessage(hWnd, WM_COMMAND, MAKEWPARAM( DLG_MAIN_FRAMELIST, \
            LBN_SELCHANGE), (LPARAM)(GetDlgItem(hWnd,             \
            DLG_MAIN_FRAMELIST)))

#define SetStepSel(hWnd, iMin, iMax)    (               \
    SendDlgItemMessage(hWnd, DLG_MAIN_FRAMELIST,        \
            LB_SELITEMRANGE, TRUE, MAKELPARAM((iMin), (iMax))), \
    UpdateStepSel(hWnd)                                 \
)

/*
 * Function Prototypes
 *
 */

/*
 * In aniedit.c
 */
BOOL InitApplication(HANDLE);
BOOL InitInstance(HANDLE, int);
INT  APIENTRY GetHeightFromPoints( int pts);
BOOL APIENTRY MainWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam);
void DrawCursorListItem( DRAWITEMSTRUCT *pdis );
int FmtMessageBox( HWND hwnd, DWORD dwTitleID, LPTSTR pszTitleStr,
    UINT fuStyle, BOOL fSound, DWORD dwTextID, ... );
LPTSTR FmtSprintf( DWORD id, ... );
PVOID AllocMem( DWORD cb );
LRESULT CALLBACK PreviewWndProc( HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam );
VOID NextFrame( HWND hwnd, BOOL fRun );
VOID SetWindowFileTitle(HWND hWnd, LPTSTR szFileTitle);
void AniAddFontModule(HINSTANCE hInst);
VOID ReadRegistry( VOID );
VOID WriteRegistry( VOID );
LRESULT CALLBACK BtnBarWndProc( HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam);
#ifdef DBCS
BOOL LoadResourceStr( VOID );
#endif

/*
 * In anicmd.c
 */
void LoadAniFile(HWND hWnd, HANDLE hfCursor, LPTSTR szFileTitle, LPTSTR szFile);
BOOL DoCommand( HWND hWnd, UINT wParam, LONG lParam );
VOID ExitCommand(HWND hWnd);
BOOL CheckDirty(HWND hWnd);
void NewAniCursor( HWND hwnd );
BOOL ExecProgram( HWND hwndCaller, LPTSTR pszCmdLine );
BOOL GetCurrentSel( HWND hwnd, int id, int *paiSel, int ciSel, int *pcSel );
VOID SetCurrentSel( HWND hwnd, int id, BOOL fExtend, int iSel);
VOID EditFrame(HWND hWnd, BOOL fEditFrame);
PSTEP NewStep( void );
VOID DestroyStep( PSTEP ps );
VOID CopyStep( PSTEP psDst, PSTEP psSrc );
VOID LinkStepFrame(PSTEP ps, PFRAME pf );
VOID DestroyFrame( PFRAME pf );
PCLPBRDDAT NewClpBrdDat( void );
VOID DestroyClpBrdDat(PCLPBRDDAT pcbd);
int __cdecl RevCompInts(const void *elm1, const void *elm2);
BOOL APIENTRY About(HWND, UINT, UINT, LONG);
BOOL APIENTRY OptionsProc( HWND hDlg, UINT message, UINT wParam, LONG lParam);
void ClearStepSel( HWND hWnd );

/*
 * In anifile.c
 */
BOOL CreateFrameFromCursorFile(HWND hwnd,  LPTSTR pszFile, BOOL fEdit);
HANDLE PromptAndOpenFile( HWND hwnd, DWORD  cchFileTitle, LPTSTR pszFileTitle,
    DWORD cchFileName, LPTSTR pszFileName, LPTSTR pszFilter);
BOOL PromptForFile( HWND hwnd, DWORD  cchFileTitle, LPTSTR pszFileTitle,
    DWORD  cchFileName, LPTSTR pszFile, LPTSTR pszFilter, LPTSTR pszDlgTitle,
    BOOL fSave );
BOOL ReadAniFile( HWND hwnd, HANDLE hf );
PFRAME ReadIconFromFile(HWND hwnd, HANDLE hf, DWORD ckSize);
BOOL ReadTag( HANDLE hf, PRTAG ptag);
BOOL ReadChunk( HANDLE hf, PRTAG ptag, PVOID pv);
BOOL ReadChunkN( HANDLE hf, PRTAG ptag, PVOID pv, DWORD cbMax);
BOOL SkipChunk( HANDLE hf, PRTAG ptag);
DWORD CalcCheckSum( PBYTE pb, DWORD cb );
HICON ConvertDataToIcon( PFRAME pf, WORD *pxHotSave, WORD *pyHotSave );
BOOL GetTempCursorFileName( LPTSTR pszName );
BOOL SaveAniFile( HWND hwnd, HANDLE hf );
BOOL WriteTag(HANDLE hf, PRTAG prtag);
BOOL WriteType(HANDLE hf, FOURCC ckID );
BOOL WriteTagData(HANDLE hf, PRTAG prtag, VOID *pvData );
VOID SaveFile(HWND hwnd, BOOL fPrompt);


ANIDLGS.H

#define MENU_FILE_NEW               0x0100
#define MENU_FILE_OPEN              0x0101
#define MENU_FILE_SAVEAS            0x0102
#define MENU_FILE_SAVE              0x0103
#define MENU_FILE_INSERT            0x0104
#define MENU_FILE_EXPORT            0x0105
#define MENU_FILE_EXIT              0x0106
#define MENU_EDIT_CUT               0x0110
#define MENU_EDIT_COPY              0x0111
#define MENU_EDIT_PASTE             0x0112
#define MENU_EDIT_CLEAR             0x0113
#define MENU_EDIT_DUP               0x0114
#define MENU_EDIT_EDITFRAME         0x0115
#define MENU_EDIT_ADDFRAME          0x0116
#define MENU_EDIT_OPTIONS           0x0117
#define MENU_HELP_ABOUT             0x0120
#define MENU_HELP_CONTENTS          0x0121

#define RCT_RAWDATA                 0x0200
#define ID_BLANKCUR                 0x0201

#define FONT_BUTTON                 0x0300

#define DLG_MAIN                    0x0400
#define DLG_MAIN_DELFRAME           0x0401
#define DLG_MAIN_DUPFRAME           0x0402
#define DLG_MAIN_EDITFRAME          0x0403
#define DLG_MAIN_ADDFRAME           0x0404
#define DLG_MAIN_FRAMELIST          0x0405
#define DLG_MAIN_RATE               0x0406
#define DLG_MAIN_FRAMEGRP           0x0407
#define DLG_MAIN_TITLE              0x0408
#define DLG_MAIN_AUTHOR             0x0409
#define DLG_MAIN_PREVIEW            0x040a
#define DLG_MAIN_RATESLOW           0x040b
#define DLG_MAIN_BOGUSICON          0x040c
#define DLG_MAIN_SINGLESTEP         0x040d
#define DLG_MAIN_RATEFAST           0x040e
#define DLG_MAIN_STOP               0x040f
#define DLG_MAIN_PLAY               0x0410
#define DLG_MAIN_RATESPIN           0x0411
#define DLG_MAIN_BTNNEW             0x0412
#define DLG_MAIN_BTNSAVE            0x0414
#define DLG_MAIN_BTNCUT             0x0415
#define DLG_MAIN_BTNCOPY            0x0416
#define DLG_MAIN_BTNPASTE           0x0417
#define DLG_MAIN_BTNOPEN            0x0418
#define DLG_MAIN_FRAMETXT           0x0413
#define DLG_ABOUT                   0x0500
#define DLG_ABOUT_PICTURE           0x0502
#define ANI_ICON                    0x0501
#define DLG_OPTIONS                 0x0600
#define DLG_OPTIONS_EDITOR          0x0602
#define DLG_OPTIONS_BROWSE          0x0604
#define DLG_OPTIONS_RADIO_DESKCOL   0x0605
#define DLG_OPTIONS_RADIO_WINCOL    0x0606
#define DLG_MAIN_BTNBAR             0x041a

#ifdef DBCS  // string id
#define IDS_ANI_FILTER1             500
#define IDS_ANI_FILTER2             501
#define IDS_ANI_FILTER3             502
#define IDS_ANI_FILTER4             503
#define IDS_CUR_FILTER1             504
#define IDS_CUR_FILTER2             505
#define IDS_CUR_FILTER3             506
#define IDS_CUR_FILTER4             507
#define IDS_CUR_FILTER5             508
#define IDS_CUR_FILTER6             509
#define IDS_UNKNOWN_ERROR           510
#define IDS_UNTITLED                511
#define IDS_IMPORT                  512
#endif


ANIEDIT.C

/****************************************************************************\
*
*     PROGRAM: AniEdit.c
*
*     PURPOSE: Animated Cursor Editor for Windows NT
*
*     Copyright 1993-1996 Microsoft Corp.
*
*
* History:
*   21-Apr-1993 JonPa   Wrote it.
*
\****************************************************************************/

#include <windows.h>
#include "anidefs.h"

HANDLE hInst;
HWND ghwndMain;
HWND ghwndRateScroll = NULL;

ANICUR ganiAcon;

int gcyCursor, gcxCursor;

HBRUSH  ghbrPrevBackgnd, ghbrWindow, ghbrHighlight;
COLORREF gcrHighlightText;
HICON   ghIcon;

TCHAR   gszModPath[MAX_PATH];     /* name for above font module */
TCHAR   gszWindowTitle[MAX_PATH] = TEXT("AniEdit");
TCHAR   gszDots[] = TEXT("...");
PFRAME  gpfrmFrames = NULL;
PCLPBRDDAT gpbdClipBoard = NULL;
TCHAR   gszCursorEditor[MAX_PATH];

//HACCEL haccel;
int giradColor = 0; /* Default to desktop color */
RADIOCOLOR garadColor[] = {
        {DLG_OPTIONS_RADIO_DESKCOL, COLOR_BACKGROUND},
        {DLG_OPTIONS_RADIO_WINCOL,  COLOR_WINDOW},
        {0, 0}
};

#if DLG_OPTIONS_RADIO_DESKCOL == 0 || DLG_OPTIONS_RADIO_WINCOL == 0
#   error("Dialog IDs must not equal zero!")
#endif

/*
 * Registry Strings
 * (Since the registry is not localized, these don't have to be read in
 *  from the strings RC)
 */
TCHAR gszAppKey[] = "Software\\Microsoft\\AniEdit";
TCHAR gszKeyCurEditor[] = "Editor";
TCHAR gszKeyPrevColor[] = "Preview Color";

/****************************************************************************
*
*     FUNCTION: ParseCmdLine
*
*     PURPOSE: Returns a pointer to the first arg of the command line
*
* History:
*   31-May-1995 JonPa   Created it
*
\****************************************************************************/
TCHAR gszParseToken[MAX_PATH];

LPTSTR ParseCmdLine( int iToken ) {
    LPTSTR szFile;
    LPTSTR szCmdLine;
    BOOL fSkipBlank;
    BOOL fInQuote;

    fSkipBlank = TRUE;      // skip leading blanks
    fInQuote = FALSE;
    szFile = gszParseToken;
    iToken++;

    for(szCmdLine = GetCommandLine(); *szCmdLine != TEXT('\0') && iToken != 0;
            szCmdLine++ ) {

        switch (*szCmdLine) {
        case TEXT('"'):
            fInQuote = !fInQuote;
            break;

        case TEXT(' '):
        case TEXT('\t'):
            if (fInQuote) {
                *szFile++ = *szCmdLine;
            } else if (!fSkipBlank) {
                iToken--;
                *szFile++ = TEXT('\0');
                szFile = gszParseToken;
                fSkipBlank = TRUE;
            }
            break;

        default:
            *szFile++ = *szCmdLine;
            fSkipBlank = FALSE;
            break;

        }
    }

    if (*szCmdLine == TEXT('\0') && !fSkipBlank ) {
        iToken--;
        *szFile++ = TEXT('\0');
    }

    if (iToken == 0 )
        return gszParseToken;
    else
        return NULL;
}

/****************************************************************************
*
*     FUNCTION: WinMain(HANDLE, HANDLE, 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.
*
* History:
*   21-Apr-1993 JonPa   Created it
*
\****************************************************************************/
int APIENTRY WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
    )
{

    MSG msg;                                 /* message                      */

    UNREFERENCED_PARAMETER( lpCmdLine );

    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);

    ghIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ANI_ICON));

    {   HACCEL hAccel;
        HWND hwndDlg;
        MSG msg;

        hAccel = LoadAccelerators(hInstance, "AniEditMenu");

        hwndDlg = CreateDialogParam( hInstance, MAKEINTRESOURCE(DLG_MAIN), GetDesktopWindow(),
            MainWndProc, (LPARAM)ParseCmdLine(1) );

        while (GetMessage(&msg, NULL, 0L, 0L))
        {
            if (!TranslateAccelerator(hwndDlg, hAccel, &msg))
            {
                if (!IsDialogMessage(hwndDlg, &msg))
                {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
        }
    }

    /* Write user profile */
    WriteRegistry();

    //BUGBUG - unregister preview class

    if (gszModPath[0] != TEXT('\0')) {
        RemoveFontResource(gszModPath);
        PostMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
    }

    DeleteObject(ghbrPrevBackgnd);

    /* Return the value from PostQuitMessage */
    return (msg.wParam);
}

/****************************************************************************
*
*     FUNCTION: InitApplication(HANDLE)
*
*     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(HANDLE hInstance)       /* current instance             */
{
    WNDCLASS cls;

    /*
     * Register a new window class to handle the cursor preview.
     */
    cls.style = 0;
    cls.lpfnWndProc = PreviewWndProc;
    cls.cbClsExtra = 0;
    cls.cbWndExtra = 0;
    cls.hInstance = hInstance;
    cls.hIcon = NULL;
    cls.hCursor = NULL;
    cls.hbrBackground = NULL;
    cls.lpszMenuName = NULL;
    cls.lpszClassName = szPREVIEW;
    RegisterClass(&cls);

    AniAddFontModule(hInstance);

    return TRUE;
}

void AniAddFontModule(HINSTANCE hInst) {

    if (GetModuleFileName(hInst, gszModPath, MAX_PATH))
        AddFontResource(gszModPath);
    else
        gszModPath[0] = TEXT('\0');
}

/****************************************************************************
*
*     FUNCTION:  InitInstance(HANDLE, 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(
    HANDLE          hInstance,
    int             nCmdShow)
{

    /* Save the instance handle in static variable, which will be used in  */
    /* many subsequence calls from this application to Windows.            */

    hInst = hInstance;

    gcyCursor = GetSystemMetrics(SM_CYCURSOR);
    gcxCursor = GetSystemMetrics(SM_CXCURSOR);

#ifdef DBCS
    /* Load resource strings */
    if (!LoadResourceStr())
        return FALSE;
#endif

    /* Load user profile */
    ReadRegistry();

#if 0
    /* Load the accel table */
    if (!(haccel = LoadAccelerators(hInstance, "AniEditAccel")))
        return FALSE;
#endif

    return (TRUE);               /* Returns the value from PostQuitMessage */

}

/* Copied from winfile:
 */
INT  APIENTRY GetHeightFromPoints( int pts)
{
    HDC hdc;
    INT height;

    hdc = GetDC (NULL);
    height = MulDiv(-pts, GetDeviceCaps (hdc, LOGPIXELSY), 72);
    ReleaseDC (NULL, hdc);

    return height;
}

/****************************************************************************
*
*     FUNCTION: MainWndProc(HWND, unsigned, WORD, LONG)
*
*     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
*         aniedit.rc file and turn control over to the About() function.  When
*         it returns, free the intance address.
*
* History:
*   21-Apr-1993 JonPa   Created it
*
\****************************************************************************/

BOOL APIENTRY MainWndProc(
        HWND hWnd,                /* window handle                   */
        UINT message,             /* type of message                 */
        UINT wParam,              /* additional information          */
        LONG lParam)              /* additional information          */
{
    static HWND     hwndChildApp = NULL;
    static HBRUSH   hbrBtnBar;
    static HFONT    hfontButton;

    switch (message) {
    case WM_INITDIALOG:
        ghwndMain = hWnd;

        gcrHighlightText = GetSysColor(COLOR_HIGHLIGHTTEXT);
        ghbrHighlight = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
        ghbrWindow = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
        hbrBtnBar = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));

        hfontButton = CreateFont (GetHeightFromPoints(8), 0, 0, 0, 400,
                                0, 0, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
                                CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
                                DEFAULT_PITCH | FF_SWISS,
                                TEXT("AniEdit Button"));

        SendDlgItemMessage (hWnd, DLG_MAIN_BTNNEW, WM_SETFONT, (WPARAM)hfontButton, 0L);
        SendDlgItemMessage (hWnd, DLG_MAIN_BTNOPEN, WM_SETFONT, (WPARAM)hfontButton, 0L);
        SendDlgItemMessage (hWnd, DLG_MAIN_BTNSAVE, WM_SETFONT, (WPARAM)hfontButton, 0L);

        SendDlgItemMessage (hWnd, DLG_MAIN_BTNCUT, WM_SETFONT, (WPARAM)hfontButton, 0L);
        SendDlgItemMessage (hWnd, DLG_MAIN_BTNCOPY, WM_SETFONT, (WPARAM)hfontButton, 0L);
        SendDlgItemMessage (hWnd, DLG_MAIN_BTNPASTE, WM_SETFONT, (WPARAM)hfontButton, 0L);

        SendDlgItemMessage (hWnd, DLG_MAIN_DELFRAME, WM_SETFONT, (WPARAM)hfontButton, 0L);

        SendDlgItemMessage (hWnd, DLG_MAIN_ADDFRAME, WM_SETFONT, (WPARAM)hfontButton, 0L);
        SendDlgItemMessage (hWnd, DLG_MAIN_EDITFRAME, WM_SETFONT, (WPARAM)hfontButton, 0L);

        SendDlgItemMessage (hWnd, DLG_MAIN_PLAY, WM_SETFONT, (WPARAM)hfontButton, 0L);
        SendDlgItemMessage (hWnd, DLG_MAIN_STOP, WM_SETFONT, (WPARAM)hfontButton, 0L);

#ifndef DBCS
        // ANIBTN.FNT file has been changed. This font doesn't include
        // capital letters. So we shouldn't set the font to this control.
        SendDlgItemMessage (hWnd, DLG_MAIN_FRAMETXT, WM_SETFONT, (WPARAM)hfontButton, 0L);
#endif

        GetWindowText(hWnd, gszWindowTitle, COUNTOF(gszWindowTitle));

        /* cache scroll bar window handle */
        ghwndRateScroll = GetDlgItem(hWnd, DLG_MAIN_RATESPIN);

        /* limit title and author string lengths */

        SendDlgItemMessage(hWnd, DLG_MAIN_TITLE, EM_LIMITTEXT,
                COUNTOF(ganiAcon.azTitle), 0);

        SendDlgItemMessage(hWnd, DLG_MAIN_AUTHOR, EM_LIMITTEXT,
                COUNTOF(ganiAcon.azCreator), 0);

        NewAniCursor(hWnd);

        if (lParam != (LPARAM)NULL) {
            HANDLE hf;
            LPTSTR pszFileName = (LPTSTR)lParam;

            hf = CreateFile(pszFileName, GENERIC_READ,
                    0, NULL,
                    OPEN_EXISTING,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);

            if (hf == INVALID_HANDLE_VALUE) {
                // User may have left the file type off, add it on, and try
                // again.
                //
                // HACK ALERT!  -- This code assumes that pszFileName points
                // to a buffer that is MAX_PATH in length.
                //
                int cchName;
                cchName = lstrlen( pszFileName );
                if (cchName + 4 < MAX_PATH) {
                    lstrcat( pszFileName, TEXT(".Ani") );

                    hf = CreateFile(pszFileName, GENERIC_READ,
                            0, NULL,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);

                    if (hf == INVALID_HANDLE_VALUE) {
                        FmtMessageBox( hWnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                            TRUE, MSG_CANTOPENFILE, pszFileName );
                        return (TRUE);
                    }
                }
            }

            LoadAniFile(hWnd, hf, pszFileName, pszFileName);
        }
        return (TRUE);

    case WM_COMMAND:           /* message: command from application menu */
        return DoCommand( hWnd, wParam, lParam );

    case AIM_SETCHILDAPP:
        /*
         * A child app has just been started.  Remeber its hwnd and defer
         * all activation to it.
         */
        DPRINT(("MT:Child HWND = 0x%lx\n", lParam));
        hwndChildApp = (HWND)lParam;

        if (hwndChildApp == NULL)
            EnableWindow(hWnd, FALSE);

        break;

    case AIM_PROCESSTERM:
        /*
         * The copy of ImagEdit that we spanwed off has just ended.
         * Time to read in the cursor file and put it back into the list.
         */

        /* "enable" our window */
        DPRINT(("MT:got AIM_PROCESSTERM\n"));

        hwndChildApp = NULL;

        EnableWindow(hWnd, TRUE);

        SetForegroundWindow(hWnd);

        /* call CreateFrameFromCursorFile to reload the modified cursor */
        if(CreateFrameFromCursorFile(hWnd, gszTempFile,  gfEditFrame))
            DeleteFile(gszTempFile);

        break;

    case WM_ACTIVATE:
        /*
         * Convert WM_ACTIVATE to WM_NCACTIVATE
         */
        switch (LOWORD(wParam)) {

        case WA_CLICKACTIVE:
            /*
             * Simulate disabled window's beep
             */
            if (IsWindow( hwndChildApp ))
                MessageBeep(MB_OK);

            wParam = TRUE;
            break;

        case WA_ACTIVE:
            wParam = TRUE;
            break;

        default:
            wParam = FALSE;
            break;
        }

        FALLTHRU(WM_NCACTIVATE);

    case WM_NCACTIVATE:
    case WM_ACTIVATEAPP:
        DPRINT(("MT:got Activate (%04x) %c %08x\n", message, wParam ? 'T' : 'F', lParam));

        if (wParam == TRUE && IsWindow( hwndChildApp )) {
            /*
             * We have a 'modal' child app upp, defer the activation to it.
             */
            DPRINT(("MT:Defering Now\n"));
            return SetForegroundWindow(hwndChildApp);
        }

        /*
         * Let DefWndProc process this message
         */
        return FALSE;

    case WM_MEASUREITEM:
        ((MEASUREITEMSTRUCT *)lParam)->itemHeight = gcyCursor + 2;
        break;

    case WM_DRAWITEM:
        DrawCursorListItem((DRAWITEMSTRUCT *)lParam);
        break;

    case WM_DELETEITEM: {
        PSTEP ps;

        if (wParam != DLG_MAIN_FRAMELIST)
            return FALSE;

        ps = (PSTEP)((LPDELETEITEMSTRUCT)lParam)->itemData;

        if (IsValidPS(ps)) {
            DestroyStep(ps);
        }
        break;
    }

    case WM_VSCROLL:
        if( (HWND)lParam == ghwndRateScroll ) {
            LONG iDelta;

            switch( LOWORD(wParam) ) {
            case SB_LINEUP:
            case SB_PAGEUP:
                iDelta = 1;
                break;

            case SB_LINEDOWN:
            case SB_PAGEDOWN:
                iDelta = -1;
                break;

            default:
                iDelta = 0;
            }

            if (iDelta != 0) {
                BOOL fOK;
                JIF jifRate = GetDlgItemInt(hWnd, DLG_MAIN_RATE, &fOK, FALSE);

                if( fOK ) {
                    if ((jifRate += iDelta) != 0) {
                        int *piSel, cSel;

                        SetDlgItemInt(hWnd, DLG_MAIN_RATE, jifRate, FALSE);

                        cSel = GetSelStepCount(hWnd);

                        if (cSel > 0 && (piSel = AllocMem(cSel * sizeof(int))) !=
                                NULL) {
                            int i;

                            ganiAcon.fDirty = TRUE;
                            GetCurrentSel(hWnd, DLG_MAIN_FRAMELIST, piSel, cSel,
                                    &cSel);

                            for( i = 0; i < cSel; i++ ) {
                                PSTEP ps = GetStep(hWnd, piSel[i]);
                                if (IsValidPS(ps)) {
                                    ps->jif = jifRate;
                                }
                            }

                            InvalidateRect(GetDlgItem(hWnd, DLG_MAIN_FRAMELIST),
                                    NULL, TRUE);
                            FreeMem(piSel);
                        }
                    }
                } else {
                    int *piSel, cSel, i;
                    JIF jifMin, jifTmp;

                    cSel = GetSelStepCount(hWnd);

                    if (cSel > 0 && (piSel = AllocMem(cSel * sizeof(int))) !=
                            NULL) {

                        ganiAcon.fDirty = TRUE;
                        GetCurrentSel(hWnd, DLG_MAIN_FRAMELIST, piSel, cSel,
                                &cSel);

                        jifMin = MAXLONG;

                        for( i = 0; i < cSel; i++ ) {
                            PSTEP ps = GetStep(hWnd, piSel[i]);
                            if (IsValidPS(ps)) {
                                jifMin = min(jifMin, ps->jif);
                            }
                        }

                        for( i = 0; i < cSel; i++ ) {
                            PSTEP ps = GetStep(hWnd, piSel[i]);
                            if (IsValidPS(ps)) {

                                jifTmp = ps->jif;

                                if (iDelta == 1) {
                                    ps->jif += (ps->jif / jifMin);
                                } else {
                                    ps->jif -= (ps->jif / jifMin);
                                }

                                /* check for over/under-flow */
                                if (ps->jif == 0) {
                                    ps->jif = jifTmp;
                                }
                            }
                        }

                        InvalidateRect(GetDlgItem(hWnd, DLG_MAIN_FRAMELIST),
                                NULL, TRUE);
                        FreeMem(piSel);
                    }
                }
            }
        }
        break;

    case WM_SYSCOMMAND:
        if (wParam == SC_CLOSE) {
            ExitCommand(hWnd);
        } else {
            return FALSE;
        }

        break;

    case WM_SYSCOLORCHANGE:
        DeleteObject(ghbrPrevBackgnd);
        DeleteObject(ghbrWindow);
        DeleteObject(ghbrHighlight);
        DeleteObject(hbrBtnBar);

        ghbrHighlight = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
        gcrHighlightText = GetSysColor(COLOR_HIGHLIGHTTEXT);
        ghbrWindow = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
        ghbrPrevBackgnd = CreateSolidBrush(GetSysColor(
                garadColor[giradColor].idSys));

        hbrBtnBar = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
        break;

    case WM_ERASEBKGND:
        if (IsIconic(hWnd)) {
            RECT rc;
            HBRUSH hbr =  CreateSolidBrush(GetSysColor(COLOR_BACKGROUND));

            GetClientRect(hWnd, &rc);
            FillRect((HDC)wParam, &rc, hbr);
            DeleteObject(hbr);
            break;

        } else {
            RECT rc;
            // Fix this to use a real tool bar
            HBRUSH hbr =  CreateSolidBrush(GetSysColor(COLOR_3DFACE));

            GetClientRect(hWnd, &rc);
            FillRect((HDC)wParam, &rc, hbr);
            DeleteObject(hbr);

            GetWindowRect(GetDlgItem(hWnd, DLG_MAIN_BTNBAR), &rc);
            ScreenToClient(hWnd, (LPPOINT)&(rc.left));
            ScreenToClient(hWnd, (LPPOINT)&(rc.right));
            FillRect((HDC)wParam, &rc, hbrBtnBar);
        }
        break;

    case WM_PAINT:
        if (IsIconic(hWnd)) {
            HDC hdc;
            PAINTSTRUCT ps;

            hdc = BeginPaint(hWnd, &ps);
            DrawIcon(hdc, 0, 0, ghIcon);

            EndPaint(hWnd, &ps);
            break;
        } else
            return FALSE;

    case WM_DESTROY:
#ifdef DBCS
        GlobalFree(gpszAniFilter);
        GlobalFree(gpszCurFilter);
        GlobalFree(gpszUnknownError);
        GlobalFree(gpszUntitled);
        GlobalFree(gpszImport);
#endif
        DeleteObject(ghbrPrevBackgnd);
        DeleteObject(ghbrWindow);
        DeleteObject(ghbrHighlight);
        DeleteObject(hbrBtnBar);
        DeleteObject(hfontButton );
        PostQuitMessage(0);
        return FALSE;

#if 0
    case WM_CHAR:
        switch( (TCHAR)wParam ) {
        case CTRL_X:
            PostMessage(hWnd, WM_COMMAND, MENU_EDIT_CUT, 0L);
            break;

        case CTRL_C:
            PostMessage(hWnd, WM_COMMAND, MENU_EDIT_COPY, 0L);
            break;

        case CTRL_V:
            PostMessage(hWnd, WM_COMMAND, MENU_EDIT_PASTE, 0L);
            break;
        }

        // fall through to DefDlgProc
#endif

    default:
        return FALSE;
    }

    return TRUE;
}

/***************************************************************************\
*
* DrawCursorListItem
*
*
* History:
* 22-Dec-1991 DarrinM       Created in the Cursors cpa.
* 22-Apr-1993 JonPa         copied into this app and tweeked it.
\***************************************************************************/

void DrawCursorListItem(
    DRAWITEMSTRUCT *pdis)
{
    COLORREF crPrev;
    static LONG cxAvgChar = 0;
    TEXTMETRIC tm;
    PSTEP ps;
    TCHAR szJif[CCH_JIF];

    /*
     * If item == -1 and we are getting the focus, then draw an empty
     * focus rect.
     */
    if (pdis->itemAction == ODA_FOCUS && pdis->itemID == (UINT)-1) {
        FillRect(pdis->hDC, &pdis->rcItem, ghbrWindow);
        if (pdis->itemState & ODS_FOCUS) {
            RECT rc;

            CopyRect( &rc, &pdis->rcItem );
            InflateRect( &rc, -2, -2 );
            DrawFocusRect(pdis->hDC, &rc);
        }
        return;
    }

    /* find the average char width for this listbox and cache it */
    if (cxAvgChar == 0) {
        if (GetTextMetrics( pdis->hDC, &tm)) {
            cxAvgChar = tm.tmAveCharWidth;
        }
    }

    if (!(ps = (PSTEP)(pdis->itemData)))
        return;

    SetBkMode(pdis->hDC, TRANSPARENT);

    if (pdis->itemState & ODS_SELECTED) {
        FillRect(pdis->hDC, &pdis->rcItem, ghbrHighlight);
        crPrev = SetTextColor(pdis->hDC, gcrHighlightText);
    } else {
        FillRect(pdis->hDC, &pdis->rcItem, ghbrWindow);
    }

    /* Draw the frame */
    DrawIcon(pdis->hDC, pdis->rcItem.left + 2, pdis->rcItem.top + 1,
            ps->pfrmFrame->hcur);

    pdis->rcItem.left += gcxCursor + 2 + ((cxAvgChar != 0) ? cxAvgChar : 8);

    /* write the rate text */
    wsprintf( szJif, "%d", (int)ps->jif );

    DrawText(pdis->hDC, szJif, strlen(szJif), &pdis->rcItem,
            DT_SINGLELINE | DT_LEFT | DT_VCENTER);

    if (pdis->itemState & ODS_SELECTED) {
        SetTextColor(pdis->hDC, crPrev);
    }

    if (pdis->itemState & ODS_FOCUS) {
        RECT rc;

        CopyRect( &rc, &pdis->rcItem );
        InflateRect( &rc, -1, -2 );
        OffsetRect( &rc, -1, 0 );
        DrawFocusRect(pdis->hDC, &rc);
    }
}

/***************************************************************************\
*
*     FUNCTION: FmtMessageBox( HWND hwnd, DWORD dwTitleID, UINT fuStyle,
*                   BOOL fSound, DWORD dwTextID, ... );
*
*     PURPOSE:  Formats messages with FormatMessage and then displays them
*               in a message box
*
*
*
*
* History:
* 22-Apr-1993 JonPa         Created it.
\***************************************************************************/
int FmtMessageBox( HWND hwnd, DWORD dwTitleID, LPTSTR pszTitleStr,
    UINT fuStyle, BOOL fSound, DWORD dwTextID, ... ) {
    LPTSTR pszMsg;
    LPTSTR pszTitle;
    int idRet;

    va_list marker;

    va_start( marker, dwTextID );

    if(!FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_MAX_WIDTH_MASK, hInst,
            dwTextID, 0, (LPTSTR)&pszMsg, 1, &marker))
        pszMsg = gpszUnknownError;

    va_end( marker );

    GetLastError();

    pszTitle = NULL;

    if (dwTitleID == (DWORD)-1 ||
            FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_MAX_WIDTH_MASK |
                FORMAT_MESSAGE_ARGUMENT_ARRAY,
                hInst, dwTitleID, 0, (LPTSTR)&pszTitle, 1, (va_list *)(DWORD)&pszTitleStr)) {
    }

    GetLastError();

    if (fSound) {
        MessageBeep( fuStyle & (MB_ICONASTERISK | MB_ICONEXCLAMATION |
                MB_ICONHAND | MB_ICONQUESTION | MB_OK) );
    }

    idRet = MessageBox(hwnd, pszMsg, pszTitle, fuStyle);

    if (pszTitle != NULL)
        LocalFree( pszTitle );

    if (pszMsg != gpszUnknownError)
        LocalFree( pszMsg );

    return idRet;
}

/***************************************************************************\
*
*     FUNCTION: FmtSprintf( DWORD id, ... );
*
*     PURPOSE:  sprintf but it gets the pattern string from the message rc.
*
* History:
* 03-May-1993 JonPa         Created it.
\***************************************************************************/
LPTSTR FmtSprintf( DWORD id, ... ) {
    LPTSTR pszMsg;
    va_list marker;

    va_start( marker, id );

    if(!FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_MAX_WIDTH_MASK, hInst,
            id, 0, (LPTSTR)&pszMsg, 1, &marker)) {
        GetLastError();
        pszMsg = gszDots;
    }
    va_end( marker );

    return pszMsg;
}

/***************************************************************************\
*
*     FUNCTION: PVOID AllocMem( DWORD cb );
*
*     PURPOSE:  allocates memory, checking for errors
*
* History:
*   22-Apr-1993 JonPa   Wrote it.
\***************************************************************************/
PVOID AllocMem( DWORD cb ) {
    PVOID pv = (PVOID)LocalAlloc(LPTR, cb);

    if (pv == NULL) {
        FmtMessageBox( ghwndMain, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                TRUE, MSG_OUTOFMEM );
    }

    return pv;
}

/***************************************************************************\
* PreviewWndProc
*
*
* History:
* 08-07-92 DarrinM      Created in CURSORS.CPL.
* 24-Apr-1993 JonPa     Copied here and tweeked.
\***************************************************************************/

LRESULT CALLBACK
PreviewWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    HDC hdc;
    RECT rc;
    PAINTSTRUCT ps;
    PPREVIEWDATA ppd;
    static int cxBM, cyBM;
    static int cxCenter, cyCenter;

    switch (msg) {
    case WM_CREATE:
        if (!(ppd = (PPREVIEWDATA)LocalAlloc(LPTR, sizeof(PREVIEWDATA))))
            return -1;

        SetWindowLong(hwnd, GWL_USERDATA, (LONG)ppd);

        /*
         * Create a temp DC and bitmap to be used for buffering the
         * preview rendering.
         */
        cxCenter = gcxCursor;
        cyCenter = gcyCursor;

        cxBM = cxCenter * 2;
        cyBM = cyCenter * 2;

        hdc = GetDC(hwnd);
        ppd->hdcMem = CreateCompatibleDC(hdc);
        ppd->hbmMem = CreateCompatibleBitmap(hdc, cxBM, cyBM);
        ppd->hbmOld = SelectObject(ppd->hdcMem, ppd->hbmMem);
        ppd->iFrame = 0;
        ppd->hcur = NULL;
        ppd->xHot = ppd->yHot = 0;
        ReleaseDC(hwnd, hdc);
        break;

    case WM_SIZE:
        ppd = (PPREVIEWDATA)GetWindowLong(hwnd, GWL_USERDATA);

        SelectObject(ppd->hdcMem, ppd->hbmOld);
        DeleteObject(ppd->hbmMem);

        cxBM = LOWORD(lParam);
        cyBM = HIWORD(lParam);
        cxCenter = cxBM / 2;
        cyCenter = cyBM / 2;

        hdc = GetDC(hwnd);
        ppd->hbmMem = CreateCompatibleBitmap(hdc, cxBM, cyBM);
        ppd->hbmOld = SelectObject(ppd->hdcMem, ppd->hbmMem);
        ReleaseDC(hwnd, hdc);
        break;

    case WM_DESTROY:
        ppd = (PPREVIEWDATA)GetWindowLong(hwnd, GWL_USERDATA);
        SelectObject(ppd->hdcMem, ppd->hbmOld);
        DeleteObject(ppd->hbmMem);
        DeleteDC(ppd->hdcMem);
        LocalFree(ppd);
        break;

    case PM_PAUSEANIMATION:
        KillTimer(hwnd, ID_PREVIEWTIMER);
        break;

    case PM_UNPAUSEANIMATION:
        NextFrame(hwnd, TRUE);
        break;

    case PM_NEWCURSOR:
        wParam = 0;
        FALLTHRU(PM_SETSTEP);

    case PM_SETSTEP: {
        BOOL fRun = KillTimer(hwnd, ID_PREVIEWTIMER);

        ppd = (PPREVIEWDATA)GetWindowLong(hwnd, GWL_USERDATA);

        ppd->iFrame = wParam;

        NextFrame(hwnd, fRun);
        InvalidateRect(hwnd, NULL, FALSE);
        break;
    }

    case WM_TIMER:
        if (wParam != ID_PREVIEWTIMER)
            break;

        NextFrame(hwnd, TRUE);
        break;

    case WM_PAINT:
        BeginPaint(hwnd, &ps);

        ppd = (PPREVIEWDATA)GetWindowLong(hwnd, GWL_USERDATA);

        if (ppd->hcur != NULL)
        {
            rc.left = rc.top = 0;
            rc.right = cxBM;
            rc.bottom = cyBM;
            FillRect(ppd->hdcMem, &rc, ghbrPrevBackgnd);

            DrawIcon(ppd->hdcMem, cxCenter - ppd->xHot, cyCenter - ppd->yHot,
                    ppd->hcur);

            BitBlt(ps.hdc, 0, 0, cxBM, cyBM, ppd->hdcMem, 0, 0, SRCCOPY);
        }
        else
        {
            FillRect(ps.hdc, &ps.rcPaint, ghbrPrevBackgnd);
        }

        EndPaint(hwnd, &ps);
        break;

    case WM_ERASEBKGND:
        break;

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

    return 0;
}

/*****************************************************************************\
* NextFrame
*
* Sets up for the next frame in the preview window.
*
* Arguments:
*   HWND hwnd - Dialog window handle.
*
\*****************************************************************************/

VOID
NextFrame(
    HWND hwnd, BOOL fRun
    )
{
    PPREVIEWDATA ppd;
    HWND hwndLB;
    DWORD cFrame;
    PSTEP ps;

    ppd = (PPREVIEWDATA)GetWindowLong(hwnd, GWL_USERDATA);

    //
    // Be sure there is a cursor specified.  If not, or it is
    // not an animated cursor, we are done.
    //
    hwndLB = GetDlgItem(GetParent(hwnd), DLG_MAIN_FRAMELIST);
    cFrame = SendMessage(hwndLB, LB_GETCOUNT, 0, 0);
    if (cFrame == LB_ERR || cFrame == 0) {
        ppd->hcur = NULL;
        InvalidateRect(hwnd, NULL, FALSE);
        return;
    }

    if (ppd->iFrame >= cFrame)
        ppd->iFrame = 0;

    /*
     * Find how long this frame should be displayed (i.e. get jifRate)
     */
    ps = (PSTEP)SendMessage(hwndLB, LB_GETITEMDATA, ppd->iFrame, 0);

    if (IsValidPS(ps)) {
        ppd->xHot = ps->pfrmFrame->xHotSpot;
        ppd->yHot = ps->pfrmFrame->yHotSpot;

        ppd->hcur = ps->pfrmFrame->hcur;

        if (fRun)
            SetTimer(hwnd, ID_PREVIEWTIMER, ps->jif * 16, NULL);

        ppd->iFrame += 1;
    } else {
        ppd->hcur = NULL;
    }

    /*
     * Redraw this frame of the cursor.
     */
    InvalidateRect(hwnd, NULL, FALSE);
}

/*****************************************************************************\
* ReadRegistry
*
* Opens (creates if necessary) the registry key for preferences and then
* reads the last saved values.
*
*   03-Jul-1993 JonPa   Copied from Spy, but changed greatly
*
\*****************************************************************************/

VOID ReadRegistry( VOID ) {
    DWORD dw;
    DWORD cbData;
    HKEY hkey;

    if (RegOpenKeyEx(HKEY_CURRENT_USER, gszAppKey, 0, KEY_QUERY_VALUE, &hkey)){

        lstrcpy( gszCursorEditor, gpszImagEdit );

    } else {

        cbData = sizeof(gszCursorEditor);
        if (RegQueryValueEx(hkey, gszKeyCurEditor, NULL, NULL,
            gszCursorEditor, &cbData) != ERROR_SUCCESS) {

            lstrcpy( gszCursorEditor, gpszImagEdit );

        }

        cbData = sizeof(dw);
        if (RegQueryValueEx(hkey, gszKeyPrevColor, NULL, NULL, (LPBYTE)&dw,
                &cbData) == ERROR_SUCCESS) {

            giradColor = (int)dw;
        }

        RegCloseKey(hkey);
    }

    ghbrPrevBackgnd = CreateSolidBrush(GetSysColor(garadColor[giradColor].idSys));
}

/*****************************************************************************\
* WriteRegistry
*
* Writes out preference data to the registry when the app exits, then
* closes the registry key.
*
*   03-Jul-1993 JonPa   Copied from Spy, but changed greatly
\*****************************************************************************/

VOID WriteRegistry( VOID ) {
    HKEY hkey;
    DWORD dw;

    if (RegCreateKeyEx(HKEY_CURRENT_USER, gszAppKey, 0, NULL,
                REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hkey, &dw))
        return;

    RegSetValueEx(hkey, gszKeyCurEditor, 0, REG_SZ, gszCursorEditor,
            lstrlen(gszCursorEditor)+1);

    dw = giradColor;
    RegSetValueEx(hkey, gszKeyPrevColor, 0, REG_DWORD, (LPBYTE)&dw, sizeof(dw));

    RegCloseKey(hkey);

}

#ifdef DBCS
/*****************************************************************************\
* LoadResourceStr
*
* Load resource string.
*
\*****************************************************************************/

BOOL LoadResourceStr( VOID ) {
    static TCHAR lpszTmp[MAX_PATH];
    static TCHAR lpszBuf[MAX_PATH];
    UINT  wID;
    INT   nLen, nOffset;

    /* Load Ani Filter */
    for(wID = IDS_ANI_FILTER1, nOffset = 0; wID <= IDS_ANI_FILTER4; wID++) {
        nLen = LoadString(hInst,wID,lpszTmp,sizeof(lpszTmp));
        lstrcpy(&lpszBuf[nOffset],lpszTmp);
        nOffset += nLen + 1;
    }
    nLen += nOffset + 1;
    if((gpszAniFilter = (LPTSTR)GlobalAlloc(GMEM_FIXED,nLen)) == NULL)
        return FALSE;
    CopyMemory(gpszAniFilter,lpszBuf,nLen);

    /* Load Cur Filter */
    for(wID = IDS_CUR_FILTER1, nOffset = 0; wID <= IDS_CUR_FILTER6; wID++) {
        nLen = LoadString(hInst,wID,lpszTmp,sizeof(lpszTmp));
        lstrcpy(&lpszBuf[nOffset],lpszTmp);
        nOffset += nLen + 1;
    }
    nLen += nOffset + 1;
    if((gpszCurFilter = (LPTSTR)GlobalAlloc(GMEM_FIXED,nLen)) == NULL)
        return FALSE;
    CopyMemory(gpszCurFilter,lpszBuf,nLen);

    /* Load Other strings */
    nLen = LoadString(hInst,IDS_UNKNOWN_ERROR,lpszBuf,sizeof(lpszBuf));
    if((gpszUnknownError = (LPTSTR)GlobalAlloc(
                        GMEM_FIXED,nLen+1)) == NULL)
        return FALSE;
    lstrcpy(gpszUnknownError,lpszBuf);
    nLen = LoadString(hInst,IDS_UNTITLED,lpszBuf,sizeof(lpszBuf));
    if((gpszUntitled = (LPTSTR)GlobalAlloc(
                        GMEM_FIXED,nLen+1)) == NULL)
        return FALSE;
    lstrcpy(gpszUntitled,lpszBuf);
    nLen = LoadString(hInst,IDS_IMPORT,lpszBuf,sizeof(lpszBuf));
    if((gpszImport = (LPTSTR)GlobalAlloc(
                        GMEM_FIXED,nLen+1)) == NULL)
        return FALSE;
    lstrcpy(gpszImport,lpszBuf);

    return TRUE;
}
#endif


ANIFILE.C

/****************************************************************************\
*
*     MODULE: anifile.c
*
*     PURPOSE: Processes files for the Animated Cursor Editor
*
*     Copyright 1993-1996 Microsoft Corp.
*
*
* History:
*   21-Apr-1993 JonPa   Wrote it.
*
\****************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include "anidefs.h"

/****************************************************************************\
*
*     FUNCTION: BOOL CreateFrameFromCursorFile( LPSTR pszFile )
*
*     PURPOSE:  Opens a cursor file, reads the icon info out of it,
*               and creates a frame and step for that icon, then links
*               everything together and updates the listbox.
*
*     NOTES:    This function accesses the global flag gfEditFrame.
*               If this bool is TRUE, then the currently selected frame
*               in the listbox is overwritten.  If it is false, then
*               a new frame is created and inserted after the currently
*               selected frame (or at the end if no selection).
*
* History:
*   21-Apr-1993 JonPa   Created it
*
\****************************************************************************/
BOOL CreateFrameFromCursorFile(HWND hwnd,  LPTSTR pszFile, BOOL fEdit) {
    PFRAME pf;
    HANDLE hf;
    PSTEP psOld, psNew;
    DWORD ckSize;
    int iSel;
    int cSel;

    cSel = GetSelStepCount(hwnd);

    if ( (fEdit && (cSel != 1)) || cSel > 1) {
        FmtMessageBox( hwnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                TRUE, fEdit ? MSG_MUSTEQONEFAME : MSG_LESSEQONEFRAME);
        return FALSE;
    }

    /* get currently selected frame */
    GetCurrentSel(hwnd, DLG_MAIN_FRAMELIST, &iSel, 1, &cSel );

    if (cSel == 0)
        psOld = NULL;
    else
        psOld = GetStep(hwnd, iSel);

    /*
     * If not editing, create a new step
     */
    if (!fEdit || !IsValidPS(psOld)) {
        psNew = NewStep();

        if (psNew == NULL) {
            return FALSE;
        }
    } else {
        psNew = NULL;
    }

    hf = CreateFile(pszFile, GENERIC_READ,
             0, NULL,
             OPEN_EXISTING,
             FILE_ATTRIBUTE_NORMAL,
             NULL);

    if (hf == INVALID_HANDLE_VALUE)
        return FALSE;

    ckSize = GetFileSize(hf, NULL);

    /* get the frame out of the file */
    pf = ReadIconFromFile(hwnd, hf, ckSize);
    CloseHandle(hf);

    if (pf == NULL) {
        if (psNew != NULL)
            FreeMem(psNew);
        return FALSE;
    }

    if (psNew != NULL) {

        if (IsValidPS(psOld)) {
            psNew->jif = psOld->jif;
            iSel += 1;
        } else {
            psNew->jif = ganiAcon.anih.jifRate;
            iSel = SendDlgItemMessage(hwnd, DLG_MAIN_FRAMELIST, LB_GETCOUNT,
                    0, 0);
        }

        LinkStepFrame(psNew, pf);

        SendDlgItemMessage(hwnd, DLG_MAIN_FRAMELIST, LB_INSERTSTRING, iSel,
            (LPARAM)psNew);

        SetCurrentSel(hwnd, DLG_MAIN_FRAMELIST, FALSE, iSel);

    } else {
        HWND hwndLB = GetDlgItem(hwnd, DLG_MAIN_FRAMELIST);

        /*
         * Delete the old frame and point the step to the new one.
         */
        LinkStepFrame(psOld, pf);

        InvalidateRect(hwndLB, NULL, TRUE);
    }

    return TRUE;
}

/****************************************************************************\
*
*     FUNCTION: HICON ConvertDataToIcon( PFRAME pf )
*
*     PURPOSE:
*
*
*
*
* History:
*   23-Apr-1993 JonPa   copied from Win NT USERs ReadIconGuts
*
\****************************************************************************/
HICON ConvertDataToIcon( PFRAME pf, WORD *pxHotSave, WORD *pyHotSave )
{
    NEWHEADER *pnh;
    NEWHEADER *pnhBase;
    RESDIR *prd;
    int offMatch;
    ICONFILERESDIR *pird;
    PCURSORRESOURCE pcres;
    BOOL fIcon;
    HICON hicon;
    WORD x, y;
    LPBYTE pbBits;

    pnhBase = (NEWHEADER *)pf->abIcon;

    /*
     * Construct a fake array of RESDIR entries using the info at the head
     * of the file.  Store the data offset in the idIcon WORD so it can be
     * returned by RtlGetIdFromDirectory.
     */
    pnh = (NEWHEADER *)LocalAlloc(LMEM_FIXED, sizeof(NEWHEADER) +
            (pnhBase->cResources * sizeof(RESDIR)));
    if (pnh == NULL)
        return NULL;

    *pnh = *pnhBase;
    prd = (RESDIR *)(pnh + 1);
    pird = (ICONFILERESDIR *)(pnhBase + 1);

    /* prime pird for first line of loop */
    pird--;

    for (offMatch = 0; offMatch < (int)pnh->cResources; offMatch++, prd++) {

        /*
         * Get the next resource directory from the icon file.
         */

        ++pird;

        /*
         * Convert from the icon editor's resource directory format
         * to the post-RC.EXE format LookupIconIdFromDirectory expects.
         */
        if (pnh->rt == 1) {     // ICON
            prd->ResInfo.Icon.Width = pird->bWidth;
            prd->ResInfo.Icon.Height = pird->bHeight;
            prd->ResInfo.Icon.ColorCount = pird->bColorCount;
            prd->ResInfo.Icon.reserved = 0;
        } else {                // CURSOR
            prd->ResInfo.Cursor.Width = pird->bWidth;
            prd->ResInfo.Cursor.Height = pird->bHeight;
        }
        prd->Planes = 0;                // Hopefully nobody uses this
        prd->BitCount = 0;              //        "        "
        prd->BytesInRes = pird->dwDIBSize;
        prd->idIcon = (WORD)pird->dwDIBOffset;
    }

    /*
     * NOTE: nh.rt is NOT an RT_ type value.  For instance, nh.rt == 1 for
     * an icon file where as 1 == RT_CURSOR, not RT_ICON.
     */

    fIcon = (pnhBase->rt == 1);
    offMatch = LookupIconIdFromDirectory((PBYTE)pnh, fIcon);

    LocalFree(pnh);

    if (fIcon) {
        pcres = (PCURSORRESOURCE)&(pf->abIcon[offMatch]);
        *pxHotSave = gcxCursor / 2;
        *pyHotSave = gcyCursor / 2;
    } else {

        offMatch -= (sizeof(pcres->xHotspot) + sizeof(pcres->yHotspot));

        for(; pird->dwDIBOffset != (WORD)offMatch &&
                pird != (ICONFILERESDIR *)(pnhBase + 1); pird--);

        pcres = (PCURSORRESOURCE)&(pf->abIcon[offMatch]);

        x = pcres->xHotspot;
        y = pcres->yHotspot;
        *pxHotSave = pcres->xHotspot = pird->xHotspot;
        *pyHotSave = pcres->yHotspot = pird->yHotspot;
    }

    // Buffer must be aligned
    pbBits = LocalAlloc(LMEM_FIXED, pf->rtag.ckSize - offMatch );
    if (pbBits) {
        CopyMemory( pbBits, pcres, pf->rtag.ckSize - offMatch );

        hicon = CreateIconFromResource( pbBits,
                pf->rtag.ckSize - offMatch, fIcon, 0x00030000);

        LocalFree( pbBits );
    } else
        hicon = NULL;

    if(!fIcon) {
        pcres->xHotspot = x;
        pcres->yHotspot = y;
    }

    return hicon;
}

/****************************************************************************\
*
*     FUNCTION: PFRAME ReadIconFromFile(HWND hwnd, HANDLE hf, DWORD ckSize)
*
*     PURPOSE:  Reads the icon info out of a file,
*               and creates a frame for that icon.
*
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
PFRAME ReadIconFromFile(HWND hwnd, HANDLE hf, DWORD ckSize) {
    PFRAME pf = AllocMem( sizeof( FRAME ) + ckSize );
    DWORD cbRead;
    PFRAME pfList;

    if (pf != NULL) {
        pf->cRef = 0;

        if (ReadFile(hf, pf->abIcon, ckSize, &cbRead, NULL) &&
                cbRead == ckSize) {
            /* got the data, now set up the rest of the frame and link it in */
            pf->dwCheckSum = CalcCheckSum( pf->abIcon, ckSize );
            pf->rtag.ckID = FOURCC_icon;
            pf->rtag.ckSize = ckSize;

            /* Check if this fram is already in the list */
            for (pfList = gpfrmFrames; pfList != NULL;
                    pfList = pfList->pfrmNext ) {
                if (pf->dwCheckSum == pfList->dwCheckSum &&
                        pf->rtag.ckSize == pfList->rtag.ckSize &&
                        memcmp( pf->abIcon, pfList->abIcon, ckSize ) == 0) {
                    /*
                     * These frames are the same, coalesce them into a
                     * sequence.
                     */
                    FreeMem(pf);
                    pf = pfList;
                    break;
                }
            }

            if (pfList == NULL) {
                /*
                 * Did not find a dup, create an icon for this frame
                 */
                pf->hcur = ConvertDataToIcon( pf, &(pf->xHotSpot),
                        &(pf->yHotSpot) );

                pf->pfrmNext = gpfrmFrames;
                gpfrmFrames = pf;
            }

        } else {
            /* File Error */
            FreeMem(pf);
            pf = NULL;
        }

    }

    return pf;
}

/****************************************************************************\
*
*     FUNCTION: HANDLE PromptAndOpenFile( )
*
*     PURPOSE:  Pust up the standard open dialog and then opens the file
*
*
*
*
* History:
*   21-Apr-1993 JonPa   Created it
*
\****************************************************************************/
HANDLE PromptAndOpenFile(
    HWND hwnd,
    DWORD  cchFileTitle,
    LPTSTR pszFileTitle,
    DWORD  cchFileName,
    LPTSTR pszFileName,
    LPTSTR pszFilter
    )
{
    HANDLE hf = INVALID_HANDLE_VALUE;

    if (PromptForFile( hwnd, cchFileTitle, pszFileTitle, cchFileName,
                pszFileName, pszFilter, NULL, FALSE )) {

        /* Open the file. */

        hf = CreateFile(pszFileName, GENERIC_READ,
                0, NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);

        if (hf == INVALID_HANDLE_VALUE) {
            FmtMessageBox( hwnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP, TRUE,
                MSG_CANTOPENFILE, pszFileName );
        }
    }

    return hf;
}

/****************************************************************************\
*
*     FUNCTION: HANDLE PromptForFile( )
*
*     PURPOSE:  Pust up the standard open dialog
*
*
*
*
* History:
*   28-Apr-1993 JonPa   Created it from PromptAndOpenFile
*
\****************************************************************************/
BOOL PromptForFile(
    HWND hwnd,
    DWORD  cchFileTitle,
    LPTSTR pszFileTitle,
    DWORD  cchFileName,
    LPTSTR pszFile,
    LPTSTR pszFilter,
    LPTSTR pszDlgTitle,
    BOOL fSave
    )
{
    OPENFILENAME ofn;

    ZeroMemory(&ofn, sizeof(ofn));

    /* Set the members of the OPENFILENAME structure. */

    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;

    ofn.lpstrFilter = pszFilter;
    ofn.nFilterIndex = 0;

    ofn.lpstrFile = pszFile;
    ofn.nMaxFile = cchFileName;

    ofn.lpstrFileTitle = pszFileTitle;
    ofn.nMaxFileTitle = cchFileTitle;

    ofn.lpstrTitle = pszDlgTitle;

    ofn.lpstrDefExt = gpszANI;

    if (fSave) {
        ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
    } else {
        ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    }

    /* Display the SaveAs or Open dialog box. */

    return fSave ? GetSaveFileName(&ofn) : GetOpenFileName(&ofn);

}

/****************************************************************************\
*
*     FUNCTION: BOOL ReadAniFile( HWND hwnd, HANDLE hf ) {
*
*     PURPOSE:
&
*   Loads an animatied cursor from a RIFF file.  The RIFF file format for
*   animated cursors looks like this:
*
*   RIFF( 'ACON'
*       LIST( 'INFO'
*           INAM( <name> )
*           IART( <artist> )
*       )
*       anih( <anihdr> )
*       [rate( <rateinfo> )  ]
*       ['seq '( <seq_info> )]
*       LIST( 'fram' icon( <icon_file> ) )
*   )
*
*
* History:
*   02-Oct-1991 DarrinM     Created. (in Win32 user)
*   17-Mar-1993 JonPa       Rewrote to use RIFF format instead of RAD
*   21-Apr-1993 JonPa       Copied it to anifile.c and tweeked it.
*
\****************************************************************************/
BOOL ReadAniFile( HWND hwnd, HANDLE hf ) {

    RTAG tag;
    DWORD cbRead;
    BOOL fSuccess = FALSE;
    JIF *pjifRate = NULL;
    DWORD *pseq = NULL;
    PFRAME *ppfram = NULL;
    int iFrame = 0;
    int i;

    if (!ReadTag(hf, &tag))
        goto laiFileErr;

    /*
     * Make sure it's a RIFF ANI file
     */
    if (tag.ckID != FOURCC_RIFF)
        goto laiFileErr;

    /* read the chunk type */
    if(!ReadFile(hf, &tag.ckID, sizeof(tag.ckID), &cbRead, NULL) ||
            cbRead < sizeof(tag.ckID)) {
        goto laiFileErr;
    }

    if (tag.ckID != FOURCC_ACON)
        goto laiFileErr;

    /* look for 'anih', 'rate', 'seq ', and 'icon' chunks */
    while( ReadTag(hf, &tag)) {

        switch( tag.ckID ) {
        case FOURCC_anih:
            if (!ReadChunk(hf, &tag, &ganiAcon.anih))
                goto laiFileErr;

            if (!(ganiAcon.anih.fl & AF_ICON) || (ganiAcon.anih.cFrames == 0))
                goto laiFileErr;

            /*
             * Allocate space for the ANIHEADER, and a seq and
             * rate table (in case we run into one later).
             */

            pjifRate = AllocMem( ganiAcon.anih.cSteps * sizeof(JIF) +
                    ganiAcon.anih.cSteps * sizeof(DWORD) +
                    ganiAcon.anih.cSteps * sizeof(PFRAME));

            if (pjifRate == NULL)
                goto laiFileErr;

            pseq = (DWORD *)(pjifRate + ganiAcon.anih.cSteps);
            ppfram = (PFRAME *)(pseq + ganiAcon.anih.cSteps);

            for( i = 0; i < (int)ganiAcon.anih.cSteps; i++ ) {
                pjifRate[i] = ganiAcon.anih.jifRate;
                pseq[i] = i;
                ppfram[i] = NULL;
            }
            break;

        case FOURCC_rate:
            /*
             * If we find a rate chunk, read it into its preallocated
             * space.
             */
            if(!ReadChunk(hf, &tag, (PBYTE)pjifRate))
                goto laiFileErr;
            break;

        case FOURCC_seq:
            /*
             * If we find a seq chunk, read it into its preallocated
             * space.
             */
            if(!ReadChunk(hf, &tag, (PBYTE)pseq))
                goto laiFileErr;
            break;

        case FOURCC_LIST: {
            DWORD cbChunk = PADUP(tag.ckSize);

            /*
             * See if this list is the 'fram' list of icon chunks
             */
            if(!ReadFile(hf, &tag.ckID, sizeof(tag.ckID), &cbRead, NULL) ||
                    cbRead < sizeof(tag.ckID)) {
                goto laiFileErr;
            }

            cbChunk -= cbRead;

            if (tag.ckID == FOURCC_fram) {

                while(cbChunk >= sizeof(tag)) {
                    if (!ReadTag(hf, &tag))
                        goto laiFileErr;

                    cbChunk -= sizeof(tag);

                    if(tag.ckID == FOURCC_icon) {
                        PFRAME pfrm;

                        /*
                         * Ok, load the icon/cursor bits,
                         */
                        pfrm = ReadIconFromFile(hwnd, hf, tag.ckSize);

                        if (pfrm == NULL) {
                            goto laiFileErr;
                        }

                        for( i = 0; i < (int)ganiAcon.anih.cSteps; i++ ) {
                            if (pseq[i] == (DWORD)iFrame) {
                                ppfram[i] = pfrm;
                            }
                        }

                        iFrame++;

                    } else {
                        /*
                         * Unknown chunk in fram list, just ignore it
                         */
                        SkipChunk(hf, &tag);
                    }

                    cbChunk -= PADUP(tag.ckSize);
                }
            } else if (tag.ckID == FOURCC_INFO) {
                /* now look for INAM and IART chunks */

                while( cbChunk >= sizeof(tag) ) {

                    if (!ReadTag(hf, &tag))
                        goto laiFileErr;

                    cbChunk -= sizeof(tag);

                    switch( tag.ckID ) {
                    case FOURCC_INAM:
                        if (cbChunk < tag.ckSize ||
                                !ReadChunkN(hf, &tag, ganiAcon.azTitle,
                                                sizeof(ganiAcon.azTitle)))
                            goto laiFileErr;

                        cbChunk -= PADUP(tag.ckSize);
                        break;

                    case FOURCC_IART:
                        if (cbChunk < tag.ckSize ||
                                !ReadChunkN(hf, &tag, ganiAcon.azCreator,
                                        sizeof(ganiAcon.azCreator)))
                            goto laiFileErr;

                        cbChunk -= PADUP(tag.ckSize);
                        break;

                    default:
                        if (!SkipChunk( hf, &tag ))
                            goto laiFileErr;

                        cbChunk -= PADUP(tag.ckSize);
                        break;
                    }
                }

            } else {
                /*
                 * Not the fram list or the INFO list.  Skip
                 * the rest of this chunk.  (Don't forget that we have
                 * already skipped one dword!)
                 */
                tag.ckSize = cbChunk;
                SkipChunk(hf, &tag);
                break;
            }

            break;
        }

        default:
            /*
             * We're not interested in this chunk, skip it.
             */
            if(!SkipChunk(hf, &tag))
                goto laiFileErr;
            break;

        }

    }

    /*
     * Update the frame count incase we coalesced some frames while reading
     * in the file.
     */
    ganiAcon.anih.cFrames = iFrame;

    /*
     * Now build up the listbox
     */

    for( i = 0; i < (int)ganiAcon.anih.cSteps; i++ ) {
        PSTEP ps;

        ps = NewStep();
        if (ps == NULL)
            goto laiFileErr;

        ps->jif = pjifRate[i];
        LinkStepFrame(ps, ppfram[i]);

        SendDlgItemMessage(hwnd, DLG_MAIN_FRAMELIST, LB_INSERTSTRING, i,
                (LPARAM)ps);
    }

    SetDlgItemText(hwnd, DLG_MAIN_TITLE, ganiAcon.azTitle);
    SetDlgItemText(hwnd, DLG_MAIN_AUTHOR, ganiAcon.azCreator);

    SendDlgItemMessage(hwnd, DLG_MAIN_PREVIEW, PM_NEWCURSOR, 0, 0);
    fSuccess = TRUE;

laiFileErr:

    if (pjifRate != NULL)
        FreeMem(pjifRate);

    if (!fSuccess)
        NewAniCursor(hwnd);

    CloseHandle(hf);

    return fSuccess;
}

/***************************************************************************\
* DWORD CalcCheckSum( PBYTE pb );
*
*
* History:
*
* 23-Apr-1993 JonPa     Created.
\***************************************************************************/
DWORD CalcCheckSum( PBYTE pb, DWORD cb ) {
    DWORD dw = 0;

    while(cb--)
        dw += (DWORD)*pb++;

    return dw;
}

/***************************************************************************\
* ReadTag, ReadChunk, SkipChunk
*
* Some handy functions for reading RIFF files.
*
* History:
* 10-02-91 DarrinM      Created.
* 03-25-93 Jonpa        Changed to use RIFF format instead of ASDF
* 23-Apr-1993 JonPa     Copied from Win NT USER.
\***************************************************************************/
BOOL ReadTag(
    HANDLE hf,
    PRTAG ptag)
{
    DWORD cbActual;

    ptag->ckID = ptag->ckSize = 0L;

    if (!ReadFile(hf, ptag, sizeof(RTAG), &cbActual, NULL) ||
            (cbActual != sizeof(RTAG)))
        return FALSE;

    /* no need to align file pointer since RTAG is already word aligned */
    return TRUE;
}

BOOL ReadChunk(
    HANDLE hf,
    PRTAG ptag,
    PVOID pv)
{
    DWORD cbActual;

    if (!ReadFile(hf, pv, ptag->ckSize, &cbActual, NULL) ||
            (cbActual != ptag->ckSize))
        return FALSE;

    /* WORD align file pointer */
    if( ptag->ckSize & 1 )
        SetFilePointer(hf, 1, NULL, FILE_CURRENT);

    return TRUE;
}

BOOL ReadChunkN(
    HANDLE hf,
    PRTAG ptag,
    PVOID pv,
    DWORD cbMax)
{
    DWORD cbActual;
    DWORD cbRead = min( cbMax, ptag->ckSize );

    if (!ReadFile(hf, pv, ptag->ckSize, &cbActual, NULL) ||
            (cbActual != cbRead))
        return FALSE;

    /* WORD align file pointer */

    cbRead = ptag->ckSize - cbActual;

    if( ptag->ckSize & 1 )
        cbRead++;

    return SetFilePointer(hf, cbRead, NULL, FILE_CURRENT) != 0xFFFFFFFF;
}

BOOL SkipChunk(
    HANDLE hf,
    PRTAG ptag)
{
    /* Round ptag->ckSize up to nearest word boundary to maintain alignment */
    return SetFilePointer(hf, PADUP(ptag->ckSize), NULL, FILE_CURRENT) !=
            0xFFFFFFFFL;
}

/****************************************************************************\
*
*     FUNCTION: VOID GetTempCursorFileName( szFileName );
*
*     PURPOSE:  Create a temporary .cur filename
*
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
BOOL GetTempCursorFileName( LPTSTR pszName ) {
    TCHAR szPath[MAX_PATH];

    if( GetTempPath( MAX_PATH, szPath ) >= MAX_PATH )
        lstrcpy( pszName, TEXT(".") );

    return GetTempFileName(szPath, TEXT("ae"), 0, pszName) != 0;
}

/****************************************************************************\
*
*     FUNCTION: BOOL SaveAniFile( HWND hwnd, HANDLE hf )
*
*     PURPOSE:
&
*   Saves an animatied cursor to a RIFF file.  The RIFF file format for
*   animated cursors looks like this:
*
*   RIFF( 'ACON'
*       [LIST( 'INFO'
*           [INAM( <name> )]
*           [IART( <artist> )]
*       )]
*       anih( <anihdr> )
*       [rate( <rateinfo> )  ]
*       ['seq '( <seq_info> )]
*       LIST( 'fram' icon( <icon_file> ) )
*   )
*
*
* History:
*   29-Apr-1993 JonPa   Created it.
*
\****************************************************************************/
BOOL SaveAniFile( HWND hwnd, HANDLE hf ) {
    int cSteps, i;
    int cFrames;
    PFRAME pf;
    DWORD cbFile, cbFram, cbINFO, cbTitle, cbAuthor;
    BOOL fRate, fSeq;
    RTAG rtag;
    PJIF pjif;
    DWORD *pseq;
    PFRAME *pfrm;

    fRate = fSeq = FALSE;
    cbINFO = cbFram = cbFile = cbTitle = cbAuthor = 0;

    PausePreview(hwnd, DLG_MAIN_PREVIEW);

    cSteps = GetStepCount(hwnd);

    if( cSteps == LB_ERR ) {
        FmtMessageBox( ghwndMain, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                TRUE, MSG_OUTOFRESOUCES );
        return FALSE;
    }

    cFrames = 0;
    for( pf = gpfrmFrames; pf != NULL; pf = pf->pfrmNext ) {
        pf->iFrame = -1;
        cFrames++;
    }

    ganiAcon.anih.cSteps = cSteps;

    pjif = AllocMem( (sizeof(JIF) + sizeof(DWORD) + sizeof(PFRAME)) * cSteps );

    if(pjif == NULL)
        return FALSE;

    pseq = (DWORD *)&pjif[cSteps];
    pfrm = (PFRAME *)&pseq[cSteps];

    cFrames = 0;

    for( i = 0; i < cSteps; i++ ) {
        PSTEP ps;

        ps = GetStep(hwnd, i);

        if( IsValidPS(ps) ) {

            if (ps->pfrmFrame->iFrame == -1) {

                cbFram += sizeof(RTAG);
                cbFram += PADUP(ps->pfrmFrame->rtag.ckSize);

                ps->pfrmFrame->iFrame = cFrames;
                pfrm[cFrames++] = ps->pfrmFrame;

            } else
                fSeq = TRUE;

            pseq[i] = ps->pfrmFrame->iFrame;

            if ((pjif[i] = ps->jif) != ganiAcon.anih.jifRate) {
                fRate = TRUE;
            }
        }
    }

    ganiAcon.anih.cbSizeof = sizeof(ganiAcon.anih);
    ganiAcon.anih.cFrames = cFrames;
    ganiAcon.anih.fl = AF_ICON | (fSeq ? AF_SEQUENCE : 0);

    cbTitle = GetDlgItemTextA(hwnd, DLG_MAIN_TITLE, ganiAcon.azTitle,
                            COUNTOF(ganiAcon.azTitle));

    cbAuthor = GetDlgItemTextA(hwnd, DLG_MAIN_AUTHOR, ganiAcon.azCreator,
                            COUNTOF(ganiAcon.azCreator));

    /*
     * At this point, cbFram == the size required by all the frames,
     * add in the rate, seq, anih, and INFO list sizes as well as
     * all the other overhead.
     */

    cbFram += sizeof(FOURCC);     //fram type

    cbFile = cbFram;

    cbFile += sizeof(FOURCC) +    //ACON type
                    sizeof(RTAG) +      //anih tag
                        PADUP(sizeof(ANIHEADER)) +
                    sizeof(RTAG);       //LIST tag (for fram list)

    if( cbTitle || cbAuthor) {
        /*
         * Remember, azCreator, and azTitle are ANSI strings!
         */
        if( cbTitle ) {
            cbTitle += 1; //add in ASCIIZ terminator
            cbINFO +=   sizeof(RTAG) +     //INAM tag
                        PADUP( cbTitle * sizeof(char));
        }

        if (cbAuthor) {
            cbAuthor += 1; //add in ASCIIZ terminator
            cbINFO +=   sizeof(RTAG) +     //IART tag
                        PADUP(cbAuthor * sizeof(char));
        }

        cbINFO +=  sizeof(FOURCC);      //INFO type

        cbFile +=   sizeof(RTAG) +      //LIST tag
                    cbINFO;
    }

    if (fSeq) {
        cbFile += sizeof(RTAG) +    //seq tag
                    PADUP(cSteps * sizeof(DWORD));
    }

    if (fRate) {
        cbFile += sizeof(RTAG) +    //rate tag
                    PADUP(cSteps * sizeof(JIF));
    }

    /*
     * Now we have all the structures built in memory, it's time to
     * write them out in RIFF ACON format!
     */
    rtag.ckID = FOURCC_RIFF;
    rtag.ckSize = cbFile;

    RET_CLOSE_IF_ERR( WriteTag(hf, &rtag), hf );

    RET_CLOSE_IF_ERR( WriteType(hf, FOURCC_ACON), hf );

    if( cbTitle || cbAuthor) {
        rtag.ckID = FOURCC_LIST;
        rtag.ckSize = cbINFO;

        RET_CLOSE_IF_ERR( WriteTag(hf, &rtag), hf );

        RET_CLOSE_IF_ERR( WriteType(hf, FOURCC_INFO), hf );

        if (cbTitle) {
            rtag.ckID = FOURCC_INAM;
            rtag.ckSize = cbTitle;
            RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, ganiAcon.azTitle), hf );
        }

        if (cbAuthor) {
            rtag.ckID = FOURCC_IART;
            rtag.ckSize = cbAuthor;
            RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, ganiAcon.azCreator), hf );
        }
    }

    /* write anih */
    rtag.ckID = FOURCC_anih;
    rtag.ckSize = sizeof(ganiAcon.anih);

    RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, &(ganiAcon.anih)), hf );

    /* if rate then write it */
    if (fRate) {
        rtag.ckID = FOURCC_rate;
        rtag.ckSize = cSteps * sizeof(JIF);

        RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, pjif), hf );
    }

    /* if seq, then write it */
    if (fSeq) {
        rtag.ckID = FOURCC_seq;
        rtag.ckSize = cSteps * sizeof(DWORD);

        RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, pseq), hf );
    }

    /* write the fram list */
    rtag.ckID = FOURCC_LIST;
    rtag.ckSize = cbFram;

    RET_CLOSE_IF_ERR( WriteTag(hf, &rtag), hf );
    RET_CLOSE_IF_ERR( WriteType(hf, FOURCC_fram), hf );

    for( i = 0; i < cFrames; i++ ) {
        RET_CLOSE_IF_ERR( WriteTagData(hf, &(pfrm[i]->rtag), pfrm[i]->abIcon),
                hf);
    }

    /* Close the file */
    CloseHandle(hf);

    return TRUE;
}

/***************************************************************************\
* WriteTag, WriteType, WriteTagData
*
* Some handy functions for writing RIFF files.
*
* History:
*   30-Apr-1993 JonPa   Created them.
\***************************************************************************/
BOOL WriteTag(HANDLE hf, PRTAG prtag) {
    DWORD cbWritten;

    return (WriteFile(hf, prtag, sizeof(RTAG), &cbWritten, NULL) &&
            cbWritten == sizeof(RTAG));
}

BOOL WriteType(HANDLE hf, FOURCC ckID ) {
    DWORD cbWritten;

    return (WriteFile(hf, &ckID, sizeof(FOURCC), &cbWritten, NULL) &&

cbWritten == sizeof(FOURCC));
}

BOOL WriteTagData(HANDLE hf, PRTAG prtag, VOID *pvData ) {
    DWORD cbWritten;
    DWORD cbWrite = PADUP(prtag->ckSize);

    return  WriteTag(hf, prtag) && WriteFile(hf, pvData, cbWrite,
            &cbWritten, NULL) && cbWritten == cbWrite;
}

/***************************************************************************\
* VOID SaveFile(HWND hwnd, BOOL fPrompt)
*
* Conditionally Prompt the user for a name and then save the file
*
* History:
*   04-May-1993 JonPa   It
\***************************************************************************/
VOID SaveFile(HWND hwnd, BOOL fPrompt) {
    TCHAR szFileTitle[MAX_PATH];
    HANDLE hf;

    szFileTitle[0] = TEXT('\0');

    if (fPrompt || ganiAcon.szFile[0] == TEXT('\0')) {
tryagain:
        if (!PromptForFile(hwnd, COUNTOF(szFileTitle), szFileTitle,
                COUNTOF(ganiAcon.szFile), ganiAcon.szFile, gpszAniFilter,
                NULL, TRUE)) {
            return;
        }
    }

    hf = CreateFile( ganiAcon.szFile, GENERIC_WRITE, 0, NULL,
            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hf == INVALID_HANDLE_VALUE) {
        FmtMessageBox(hwnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP, TRUE,
                MSG_CANTCREATEFILE, ganiAcon.szFile);

        goto tryagain;
    }

    if( !SaveAniFile(hwnd, hf) ) {
        FmtMessageBox(hwnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP, TRUE,
                MSG_FILEWRITEERR, ganiAcon.szFile);
        return;
    }

    if (szFileTitle[0] != TEXT('\0'))
        SetWindowFileTitle(hwnd, szFileTitle);

    ganiAcon.fDirty = FALSE;
}


ANISTR.C

#include <windows.h>

//BUGBUG - eventually load these out of the resource file

LPTSTR gpszImagEdit    = "imagedit %1";
#ifdef DBCS // Load string dynamically
LPTSTR gpszAniFilter;
#else
LPTSTR gpszAniFilter   = "Animated Cursor\0*.ani\0All Files\0*.*\0";
#endif
LPTSTR gpszCUR         = "cur";
LPTSTR gpszANI         = "ani";
#ifdef DBCS // Load string dynamically
LPTSTR gpszUnknownError;
LPTSTR gpszCurFilter;
LPTSTR gpszUntitled;
LPTSTR gpszImport;
#else
LPTSTR gpszUnknownError = "Unknown Error";
LPTSTR gpszCurFilter   = "Cursor files\0*.cur\0Icon files\0*.ico\0All Files\0*.*\0";
LPTSTR gpszUntitled    = "(Untitled)";
LPTSTR gpszImport      = "Import Frame";
#endif


README.TXT

Font Editor

The FONTEDIT tool allows you to modify an existing bitmap font. You can
change any or all of the header information, as well as graphically edit
the bitmap image of the glyphs. This sample can be useful for learning
about the binary font format.


FONTCHAR.C

#include "windows.h"
#include <windowsx.h>
#include "fontedit.h"
#include "fcntl.h"
#include <stdio.h>

/****************************************************************************/
/*              Shared Variables                                            */
/****************************************************************************/

POINT SnapPointToGrid(POINT Pt);
LONG  APIENTRY FontEditWndProc(HWND, WORD, WPARAM, LONG);
BOOL  APIENTRY AboutDlg(
HWND   hDlg,
WORD   message,
WPARAM wParam,
LPARAM lParam
);
BOOL   NewFile;                         /* flag indicating that NEW menu
                                           item was selected */
extern DLGPROC lpHeaderProc;            /* Pointer to Dialog Box Procedure */
extern DLGPROC lpReSizeProc;            /* Pointer to Dialog Box Procedure */
extern DLGPROC lpWidthProc;             /* Pointer to Dialog Box Procedure */

extern FontHeaderType font;             /* Structure of Font File Header */
extern CHAR matBox [wBoxLim] [kBoxLim]; /* array to hold Box */
extern HCURSOR hCross;                  /* handle to "+" shaped cursor(displayed
                                           when in ROW or COLUMN menus */
extern BOOL fReadOnly;
extern HANDLE hInst;                    /* Module Handle */
extern HBRUSH hbrBackGround;
extern HWND hFont;
extern HWND hBox;
extern RECT rectWin;                    /* Client Rectangle */
extern BOOL fLoaded;                    /* Set if a file loaded */
extern BOOL fChanged;                   /* Anything has changed */
extern BOOL fEdited;                    /* This character changed */
extern DWORD kBox;                       /* height of character */
extern DWORD wBox;                       /* Width of character */
extern DWORD kStuff;                     /* Width of Show Header */
extern INT  swH;                        /* Position in Show Window 0-100 */
extern BYTE iChar;                      /* Character being edited */
extern BYTE jChar;                      /* Last Char. of edit block */
extern CHAR szNewFile[];                /* Name of New File */
extern CHAR szFontFile[];               /* Name of Font File */
extern CHAR szFontFileFull[];               /* Name of Font File */
extern CHAR szFileNameTemp[];
extern CHAR *szFileNameSave;
extern INT cSysHeight;

extern CHAR *vrgsz[];                    /* total number of strings */
extern OFSTRUCT ofstrFile;
extern CHAR szExt[];                    /* default extension */
extern CHAR szAppName[];
extern CHAR szSCC[];
extern HCURSOR hOldCursor;              /* handle to old arrow shaped cursor */
/****************************************************************************/
/*                      Local Variables                                     */
/****************************************************************************/

HBRUSH hbrWhite;
HBRUSH hbrBlack;
HBRUSH hbrGray;
HBRUSH hbrDkGray;
HBRUSH hNullBrush;
HPEN hWhitePen;
DWORD colors[3] = {WHITENESS, BLACKNESS, PATCOPY};

CHAR matBackup [wBoxLim] [kBoxLim];     /* Backup for UNDO */
DWORD wBoxBackup;

LONG scale = 7;/* height/width of squares in box */
WORD cursor = 0;/* Add/Del cursor */

BOOL fAll = TRUE;/* Redraw all if TRUE */
POINT ptBox = {10, 5};/* where edit box is */

RECTrectRubber;/* Rubber banding rectangle */
HDChDst;/* Rubber banding dc */

POINT ptA;/* Start of draw/rectangle */
POINT ptB;/* End of rectangle */
POINT ptC;/* Current square */
CHAR colorA;/* Color at/under point A */
DWORD newWidth;/* Width set in WIDER option */
BOOL FillingRect = FALSE;
BOOL fRubberBanding = FALSE;/* flag indicating if rubberbanding in
                                   progress for row/column add/delete */
BOOL fStartRubberBand = FALSE;  /* flag indicating that rubberbanding
                                   can start */
BOOL fCaptured = FALSE;/* set if mouse is caputred */
BOOL fJustZapped = FALSE;/* Set on row/col add/delete */
RECT FontRect;/* rectangle bounding font pattern */

/****************************************************************************/
/*                      Local Functions                                     */
/****************************************************************************/

VOID ClearFill(DWORD col, DWORD row, WORD mode);
VOID FontEditCommand(HWND hBox, WORD id);
VOID BoxRestore(VOID);
VOID CharRectDimensions(LPRECT Rect);
VOID BoxPaint(VOID);
VOID DrawBox(HDC, DWORD, DWORD, DWORD, DWORD, INT, DWORD);
VOID PASCAL DrawRubberBand(HDC hDst, LPRECT lpRect, DWORD rop);
VOID FontEditPaint(HWND hBox, HDC hDC);
BOOL CheckSave(VOID);
VOID DupCol(DWORD col, DWORD row);
VOID DupRow(DWORD col, DWORD row);
VOID ZapCol(DWORD col, DWORD row);
VOID ZapRow(DWORD col, DWORD row);
VOID AddDel(DWORD col, DWORD row, WORD mode);
VOID MouseInBox(HWND hBox, WORD message, POINT ptMouse);
VOID BoxBackup(VOID);
VOID ReadRect(VOID);

/*****************************************************************************/
VOID
BoxPaint(
VOID
)      /* Our call to FontEditPaint */
{
HDC hDC;

hDC = GetDC(hBox);
FontEditPaint(hBox,  hDC);
ReleaseDC(hBox, hDC);
if (fRubberBanding)
DrawRubberBand(hDst, &rectRubber, R2_XORPEN);
}

/******************************************************************************
 * FontEditPaint(hBox, hDC)
 *
 * purpose: calculates coordinates for text in main window and repaints edit
 *          box,small character boxes and text
 *
 * params:  HWND hBox : handle to main window
 *          HDC hDC   I handle to display context
 * returns: none
 *
 * side effects:  alters matBox (global 2-d array with ready pixel info. on
 *                currently displayed box)
 *****************************************************************************/
VOID
FontEditPaint(
HWND hBox,
HDC hDC
)
{
CHAR szTemp[20];
DWORD len, yText, xText;

if (!fLoaded)               /* Must load font first */
return;

/* Here the application paints its window. */
if (fAll) {               /* Draw box setting */
GetClientRect(hBox, (LPRECT)&rectWin);
scale = (rectWin.bottom-rectWin.top-kStuff-20) / (kBox+1);
scale = min(scale, (min(320, rectWin.right - rectWin.left) -
90) / ((int)wBox + 1));
scale = max(scale, 4);
xText = ptBox.x + scale * wBox + 16;

SelectObject(hDC, hbrDkGray);
Rectangle(hDC,
    ptBox.x - 2,
    ptBox.y - 2,
    ptBox.x + 3 + wBox * scale,
    ptBox.y + 5 + kBox * scale);
SelectObject(hDC, hbrGray);

Rectangle(hDC,          /* Surround for font displays */
    xText,
    ptBox.y - 2,
    xText + wBox + 8,
    ptBox.y + 3 + kBox * 2 + font.ExtLeading);

/* Now put up the text */
yText = 14 + 2 * kBox + font.ExtLeading;
len = (DWORD) sprintf(szTemp, vszCHAR, iChar);
TextOut(hDC, xText, yText, (LPSTR)szTemp, len);
len = (DWORD) sprintf(szTemp, vszWIDTH, wBox);
TextOut(hDC, xText, yText + cSysHeight, (LPSTR)szTemp, len);
len = (DWORD) sprintf(szTemp, vszHEIGHT, kBox);
TextOut(hDC, xText, yText + cSysHeight + cSysHeight,
(LPSTR)szTemp, len);
}

/* Draw Character Box */
DrawBox(hDC, ptBox.x, ptBox.y, wBox, kBox, scale, 1);

/* Draw small character */
xText = ptBox.x + scale * wBox + 16;
DrawBox(hDC,
    xText + 4,
    ptBox.y,
    wBox,
    kBox,
    1, 0);

/* Draw another small character to show leading */
DrawBox(hDC,
    xText + 4,
    ptBox.y + kBox + font.ExtLeading,
    wBox,
    kBox,
    1, 0);
fAll = TRUE;
}

/******************************************************************************
 * DrawBox(hDC, xChar, yChar, wChar, kChar, scale, htSep)
 *
 * purpose: draws the edit box for the character being edited and colors the
 *          grid squares according to the pixels set for the character.
 *
 * params:  HDC hDC         : handle to display context
 *          DWORD xChar      : x-location of char box.
 *          DWORD yChar      : y-location of char box
 *          DWORD wChar      : width of char box
 *          DWORD kChar      : height of char
 *          INT   wScale     : Scale of the squares.
 *          DWORD htSep      : height of square separators
 *
 * returns: none
 *
 * side effects:  alters matBox (global 2-d array with ready pixel info. on
 *                currently displayed box)
 *****************************************************************************/
VOID
DrawBox(
HDC hDC,
DWORD xChar,                             /* x-location of char. */
DWORD yChar,                             /* y-location of char. */
DWORD wChar,                             /* width of char. */
DWORD kChar,                             /* height of char */
INT   wScale, /* scale of the squares. */
DWORD htSep                              /* hgt of square separators */
)
/* draw a character of separate squares of height 'scale' with sep. 'htSep' */
{
DWORD i, j, sep;

if (fAll) {               /* redraw them all */
    for (j = 0; j < kChar; j++) {
sep = (j >= font.Ascent) ? htSep : 0;
for (i = 0; i < wChar; i++) {
    if (wScale == 1)
SetPixel(hDC, xChar + i, yChar + j,
matBox[i][j] == TRUE ? BLACK : WHITE);
    else
PatBlt(hDC,
xChar + wScale * i,
yChar + wScale * j + sep,
wScale - htSep,
wScale - htSep,
colors[matBox[i][j] == TRUE ? 1 : 0]);
}
    }
}
else {/* redraw one just flipped */
    if (wScale == 1)
SetPixel(hDC,
xChar + ptC.x,
yChar + ptC.y,
matBox[ptC.x][ptC.y] == TRUE ? BLACK : WHITE);
    else {
sep = (((DWORD) ptC.y >= font.Ascent) ? htSep : 0L);
SelectObject(hDC, hbrGray);
PatBlt(hDC,
xChar + wScale * ptC.x,
yChar + wScale * ptC.y + sep,
wScale - htSep,
wScale - htSep,
colors[matBox[ptC.x][ptC.y]]);
    }
}

}

/******************************************************************************
 * FontEditCommand(hBox, id)
 *
 * purpose: interprets menu id and calls appropriate function to do the task
 *
 * params:  HWND hBox : handle to main window
 *          WORD id   : menu command id
 * returns: none
 *
 * side effects: plenty
 *
 *****************************************************************************/
VOID
FontEditCommand(
HWND hBox,
WORD id
)
{
CHAR * szError;                 /* String for error messages */
LONG w;
DWORD y, i, j;
BOOL fRepaint = FALSE;
HMENU hMenu;
DLGPROC lpprocAboutDlg;
MSG message;

szError = "";               /* No Errors yet */

switch (id) {
    case FONT_EXIT:
if (!CheckSave())    /* See if any files need saving */
break;
/* Window's being destroyed. */
if (fLoaded)         /* 4/8/87 Linsh added */
DeleteGlobalBitmap(); /* Get rid of memory DC */
PostQuitMessage(0);  /* Cause application to be terminated */
break;

    case FONT_ABOUT:
lpprocAboutDlg = (DLGPROC)AboutDlg;
DialogBox (hInst, vszABOUT, hBox, lpprocAboutDlg);
FreeProcInstance (lpprocAboutDlg);
break;

    case FONT_LOAD:             /*  Check File Name  */
    case FONT_NEW :
if (!CheckSave())       /*  See if current font needs saving */
return;
/* to prevent scrambling of Show window chars, Bring back Show
        ** window to parent window's client area before invoking the dialog */

if (CommDlgOpen(hBox,&ofstrFile,szNewFile,szExt,szFontFile,id)
== FALSE) {

InvalidateRect(hFont, (LPRECT)NULL, FALSE);
UpdateWindow(hFont);
return;
}
/* else drop thru */

    case FONT_START:/*  Here if file name passed as argument */
InvalidateRect(hFont, (LPRECT)NULL, FALSE);
UpdateWindow(hFont);

szError = FontLoad (szNewFile, &ofstrFile);

/* Hack : needed to remove umwanted WM_MOUSEMOVE messages from the
         * queue.
         * Apparently, Windows needs to reposition the mouse after a dialog
         * is ended with a mouse double-click (releases mouse capture?) for
         * which a couple of WM_MOUSEMOVEs may get sent to parent app.
         * These mess with the edit box below the dialog if they happen to
         * overlap.
         */
PeekMessage((LPMSG) &message, hBox, WM_MOUSEMOVE, WM_MOUSEMOVE,
    PM_REMOVE);

if (fLoaded)    /* If loaded then do a few things */ {
    jChar = iChar = 65;                 /* Show an A */
    if ((BYTE)iChar > (BYTE)font.LastChar)
jChar = iChar = font.FirstChar; /* .. if we can */
    swH = 15;                   /* Good bet to make A visible */
    fEdited = fChanged = FALSE;
    ResizeShow();               /* Set Box to proper size */
    ScrollFont();               /* Set thumb */
    CharToBox(iChar);
}
FontRename(szError);
SetFocus(hBox);
return;

    case FONT_SAVE:
if (!NewFile) {
    if (fLoaded && fChanged) {
lstrcpy((LPSTR)szNewFile, (LPSTR)szFontFileFull);
BoxToChar(iChar);           /* Just in case */
szError = FontSave (szNewFile, &ofstrFile);
FontRename(szError);        /* Rename or Print Error */
return;
    }
    else
return;
}
/* else file has been opened by selecting NEW... on menu.
         * Fall thro' and bring up SaveAs dialog minus default
         * filename in edit window */

    case FONT_SAVEAS:
BoxToChar(iChar);               /* Just in case */

if (CommDlgSaveAs (hInst, hBox, &ofstrFile, szNewFile, szExt,
szFontFile) == TRUE) {

    szError = FontSave (szNewFile, &ofstrFile);
    FontRename (szError);          /* Rename or Print Error */
}

/* to prevent scrambling of Show window chars,
           repaint show window after dialog is brought down */
InvalidateRect (hFont, (LPRECT)NULL, TRUE);
UpdateWindow (hFont);
return;

    case FONT_HEADER:
/* to prevent scrambling of Show window chars,
 * repaint show window after dialog is invoked */
DialogBox(hInst, (LPSTR)vszDHeader, hBox, lpHeaderProc);
InvalidateRect(hFont, (LPRECT)NULL, TRUE);
UpdateWindow(hFont);
return;

    case FONT_RESIZE:
/* to prevent scrambling of Show window chars,
   repaint show window after dialog is brought down */
if (DialogBox(hInst, (LPSTR)vszDResize, hBox, lpReSizeProc)) {
    /* BoxToChar(iChar);*/ /* save current before resizing */
    ResizeShow();       /* New Font Display Size */
    CharToBox(iChar);               /* New Box display */
}
InvalidateRect(hFont, (LPRECT)NULL, TRUE);
UpdateWindow(hFont);
return;

    case FONT_COPY:                     /* Copy to Clipboard */
BoxToChar(iChar);               /* Just in case */
ToClipboard(iChar, wBox, kBox);
break;

    case FONT_PASTE:            /* Paste in Character form Clipboard */
BoxBackup();            /* In case we change our minds */
ptA.x = ptA.y = 0;
wBox = ClipboardToBox(ptA, wBox, kBox, TRUE);
fRepaint = TRUE;
break;

    case WIDER_LEFT:
    case WIDER_RIGHT:
    case WIDER_BOTH:
    case NARROWER_LEFT:
    case NARROWER_RIGHT:
    case NARROWER_BOTH:
    case WIDTH:
w = newWidth = wBox;
if (font.Family & 1)            /* Variable width or else */ {
    switch (id) {
case WIDER_BOTH:
    w++;
case WIDER_LEFT:
case WIDER_RIGHT:
    w++;
    break;
case NARROWER_BOTH:
    w--;
case NARROWER_LEFT:
case NARROWER_RIGHT:
    w--;
    break;
case WIDTH:
    if (DialogBox(hInst,
(LPSTR)vszDWidth, hBox, lpWidthProc))
w = newWidth;
    break;
    }

    if (w < 0 || w >= wBoxLim) {
MessageBox(hBox,
(LPSTR)vszEdLimits0To64,
(LPSTR)szAppName,
MB_OK | MB_ICONASTERISK);
break;                  /* Out of range! quit */
    }
    if (w > (LONG) font.MaxWidth) {
if (IDOK == MessageBox(hBox,
    (LPSTR)vszMaxWidthIncrease,
    (LPSTR)szAppName,
    MB_OKCANCEL | MB_ICONQUESTION))
    font.MaxWidth = (WORD)w;
else
    break;
    }
    BoxBackup();                /* In case we change our minds */
    wBox = (WORD)w;             /* Reset width */
    fRepaint = TRUE;            /* Signal redraw */
    switch (id) {
    case WIDER_LEFT:
DupCol(0, kBoxLim - 1);
for (y = 0; y < kBoxLim; y++)
    matBox[0][y] = FALSE;       /* Clear left column */
break;
    case WIDER_BOTH:            /* Shift character one right */
DupCol(0, kBoxLim - 1);
for (y = 0; y < kBoxLim; y++)
    matBox[wBox -1][y] = FALSE; /* Clear right column */
for (y = 0; y < kBoxLim; y++)
    matBox[0][y] = FALSE;       /* Clear left column */
break;
    case NARROWER_LEFT:
    case NARROWER_BOTH:/* Shift character one left */
if (wBox) {/* .. unless width is already 0 */
    for (j = 0; j <= kBox - 1; j++)
for (i = 0; i <= wBox - 1; i++)
    matBox[i][j] = matBox[i + 1][j];
    break;
}
    }
}
else {
    MessageBox(hBox,
    (LPSTR)vszCannotChangeWidth,
    (LPSTR)szAppName,
    MB_OK | MB_ICONASTERISK);
}
break;

    case ROW_ADD:
    case ROW_DEL:
    case COL_ADD:
    case COL_DEL:
/* set cursor to "+" shaped cursor */
SetCapture (hBox); /* so that cursor doesn't get restored
      before we are done */
hOldCursor = SetCursor (LoadCursor (NULL, IDC_CROSS));
fCaptured = TRUE;
cursor = id;
break;

    case BOX_CLEAR:
    case BOX_FILL:
    case BOX_INV:
    case BOX_HATCH:
    case BOX_LEFTRIGHT:
    case BOX_TOPBOTTOM:
    case BOX_COPY:
    case BOX_PASTE:
/* Get one o' da funky cursors */
SetCapture(hBox);
hOldCursor = SetCursor(LoadCursor(hInst, MAKEINTRESOURCE(id)));
fStartRubberBand = TRUE;
CharRectDimensions((LPRECT)&FontRect);
cursor = id;
break;

    case BOX_REFRESH:           /* Go get old version of character */
BoxBackup();            /* In case we change our minds */
CharToBox(iChar);
hMenu = GetMenu(hBox);
EnableMenuItem(hMenu, BOX_UNDO, MF_ENABLED);    /* Can Unrefresh! */
break;
    case BOX_UNDO:
BoxRestore();
hMenu = GetMenu(hBox);
EnableMenuItem(hMenu, BOX_REFRESH, MF_ENABLED);
fRepaint = TRUE;
break;
}
if (fRepaint) {
fEdited = fChanged = TRUE;
InvalidateRect(hBox, (LPRECT)NULL, TRUE);
}
}

VOID
CharRectDimensions(
LPRECT Rect
)
/* returns the dimensions of the edit box */
{
Rect->top    =  ptBox.y;
Rect->bottom =  ptBox.y + (kBox) * (scale);
Rect->left   =  ptBox.x;
Rect->right  =  ptBox.x + (wBox) * (scale);
}

/******************************************************************************
 * BOOL  APIENTRY WidthProc(hDial, message, wParam, lParam)
 *
 * purpose: dialog function for Width menu function
 *
 * params:  same as for all dialog fns.
 *
 * side effects: changes Box width variable
 *
 *****************************************************************************/
BOOL  APIENTRY
WidthProc(
HWND   hDial,
WORD   message,
WPARAM wParam,
LPARAM lParam
)
{
INT i;
BOOL fOk;

UNREFERENCED_PARAMETER(lParam);

switch (message) {
default:
return FALSE;
case WM_INITDIALOG:
SetDlgItemInt(hDial, BOX_WIDTH, newWidth, FALSE);
break;

case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
fChanged = TRUE;
i = GetDlgItemInt(hDial, BOX_WIDTH, (LPBOOL)&fOk, FALSE);
if (fOk && i < wBoxLim)
newWidth = i;
else
ErrorBox(hDial, vszWidthOutOfBounds);

case IDCANCEL:
EndDialog(hDial, LOWORD(wParam) != IDCANCEL);
break;

default:
break;
}
}
return TRUE;
}

/******************************************************************************
 * BOOL CheckSave()
 *
 * purpose: checks if font is dirty and prompts user to save font. If yes,
 *          edit box is saved and file save function is called.
 *
 * params:  none
 *
 * returns: TRUE : font has been changed
 *          FALSE: font untouched
 *
 * side effects: file dirty flag is reset.
 *
 *****************************************************************************/
BOOL
CheckSave(
VOID
)
{
CHAR * szError;                 /* String for error messages */
CHAR szMessage[MAX_STR_LEN+MAX_FNAME_LEN];

/* Check if anything changed */
if (fLoaded && fChanged && (!fReadOnly)) {
lstrcpy((LPSTR)szNewFile, (LPSTR)szFontFileFull);
DlgMergeStrings(szSCC, szNewFile, szMessage);
switch (MessageBox(hFont,
    (LPSTR)szMessage,
    (LPSTR)szAppName,
    MB_YESNOCANCEL | MB_ICONQUESTION)) {
case IDYES:
BoxToChar(iChar);            /* Just in case */
szError = FontSave (szNewFile, &ofstrFile);
FontRename(szError);            /* Rename or Print Error */
case IDNO:
return TRUE;
case IDCANCEL:
return FALSE;
}
}
return TRUE;
}

/******************************************************************************
 * MouseInBox(hBox, message, ptMouse)
 *
 * purpose: do edit operation depending on currently active menu command
 *
 * params: HWND hBox       : handle to main window
 *         WORD message    : Message retrieved by main window's window fn.
 *         POINT ptMouse   : current mouse coordinates
 * returns: none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box). Also assigns values to ptA and ptB
 *****************************************************************************/
VOID
MouseInBox(
HWND hBox,
WORD message,
POINT ptMouse
)
{
POINT pt;
BOOL fRepaint = FALSE;
short nct = 0 ;

pt = SnapPointToGrid(ptMouse);

if (pt.x >= 0L && pt.y >= 0L &&
    ((DWORD)pt.x) < wBox && ((DWORD)pt.y) < kBox) {
fEdited = fChanged = TRUE;
ptC.x = pt.x;
ptC.y = pt.y;/* Current square */
if (message == WM_LBUTTONDOWN)
BoxBackup();/* Set up for UNDO */
switch (cursor) {
case BOX_COPY:
case BOX_PASTE:
case BOX_CLEAR:
case BOX_FILL:
case BOX_INV:
case BOX_HATCH:
case BOX_LEFTRIGHT:
case BOX_TOPBOTTOM:
ptA.x = pt.x;
ptA.y = pt.y;/* save anchor point */
/* save color under marker */
colorA = matBox[pt.x][pt.y];
fAll = FALSE;

fRepaint = TRUE;
break;
default:
AddDel(pt.x, pt.y, cursor);
fRepaint = TRUE;
cursor = FALSE;
break;
case FALSE:
switch (message) {
case WM_LBUTTONDOWN:/*invert */
nct =1 ;
colorA = (matBox[pt.x][pt.y] ^= TRUE);
break;

case WM_LBUTTONUP:
break;

case WM_MOUSEMOVE:
matBox[pt.x][pt.y] = colorA;    /* paint */
break;
}
/*
if( nct != 0 )
{
*/
fRepaint = TRUE;
fAll = FALSE;               /* Limited redraw */
/*
}
*/
break;
}
if (fRepaint) {
BoxPaint();
return;
}
}
cursor = FALSE;
}

/******************************************************************************
 * ReadRect(ptMouse)
 *
 * purpose:      defines the rectangular region in edit box to be filled by
 *               fill menu command  by fixing top left (ptA) and bottom right
 *               (ptB) coordinates of rect.
 *
 * params:
 *
 * assumes:that rectRubber is normalized (eg, left < right, botton > top)
 *
 * returns:    none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box). Also assigns values to ptA and ptB
 *****************************************************************************/
VOID
ReadRect(
)
{

ptA.x = (rectRubber.left-(ptBox.x+1))   / scale;
ptA.y = (rectRubber.top-(ptBox.y+1))    / scale;
ptB.x = (rectRubber.right-(ptBox.x-2))  / scale - 1;
ptB.y = (rectRubber.bottom-(ptBox.y-2)) / scale - 1;

if (((DWORD)ptB.x) > wBox - 1)
    ptB.x = wBox - 1;
if (((DWORD)ptB.y) > kBox - 1)
    ptB.y = kBox - 1;

if (ptB.x >= 0 && ptB.y >= 0) {
    ClearFill((DWORD)ptB.x, (DWORD)ptB.y, cursor);
    BoxPaint();
}
cursor = FALSE;
}

/******************************************************************************
 * ClearFill(col, row, mode)
 *
 * purpose:  fill the specified rectangular region in edit box with fill type
 *           indicated by mode.Top left corner of rect is global(ptA)
 *
 * params:   DWORD row : row (of bottom right corner of rect(ptB.x))
 *           DWORD col : column (of bottom right corner of rect(ptB.y))
 *           WORD mode: action to be performed
 *
 * returns:  none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box)
 *****************************************************************************/
VOID
ClearFill(
DWORD col,
DWORD row,
WORD mode
)
{
DWORD i, x, y;
CHAR z;

if (col < (DWORD)ptA.x) /* if points are reversed */ {
i = col;
col = ptA.x;
ptA.x = i;
}          /* flip them */
if (row < (DWORD) ptA.y) {
i = row;
row = ptA.y;
ptA.y = i;
}          /* flip them */

if (mode == BOX_LEFTRIGHT) {
for (x = ptA.x; x <= (DWORD)((ptA.x + col) / 2); x++)
for (y = ptA.y; y <= row; y++) {
z = matBox[x][y];
matBox[x][y] = matBox[ptA.x + col - x][y];
matBox[ptA.x + col - x][y] = z;
}
return;
}

if (mode == BOX_TOPBOTTOM) {
for (y = ptA.y; y <= ((DWORD)(ptA.y + row) / 2); y++)
for (x = ptA.x; x <= col; x++) {
z = matBox[x][y];
matBox[x][y] = matBox[x][ptA.y + row - y];
matBox[x][ptA.y + row - y] = z;
}
return;
}

if (mode == BOX_COPY)
BoxToClipboard(ptA, col - ptA.x + 1, row - ptA.y + 1);

if (mode == BOX_PASTE)
ClipboardToBox(ptA, col - ptA.x + 1, row - ptA.y + 1, FALSE);

for (x = ptA.x; x <= col; x++)
for (y = ptA.y; y <= row; y++) {
switch (mode) {
case BOX_CLEAR:
case BOX_FILL:
matBox[x][y] = (CHAR)(mode == BOX_FILL);
break;
case BOX_INV:
matBox[x][y] ^= (CHAR)TRUE;
break;
case BOX_HATCH:
matBox[x][y] = (CHAR)((x+y)%2 ? TRUE : FALSE);
break;
}
}
}

/******************************************************************************
 * AddDel(col, row, mode)
 *
 * purpose:  Add/Delete row/col as per mode
 *
 * params:   DWORD row : row
 *           DWORD col : column
 *           WORD mode: action to be performed
 *
 * returns:  none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box)
 *****************************************************************************/
VOID
AddDel(
DWORD col,
DWORD row,
WORD mode
)
{
switch (mode) {
case ROW_ADD:
DupRow(wBox, row);
break;

case ROW_DEL:
ZapRow(wBox, row);
break;

case COL_ADD:
DupCol(col, kBox);
break;

case COL_DEL:
ZapCol(col, kBox);
break;
}
/* restore arrow cursor */
SetCursor (hOldCursor);
ReleaseCapture();
fCaptured = FALSE;
fJustZapped = TRUE;
}

/******************************************************************************
 * ZapCol(col, row)
 *
 * purpose: delete given column in edit box. Shift cols to right given col
 *          right. Rightmost column gets duplicated.
 *
 * params : DWORD col : column
 *          DWORD row : row
 * returns: none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box)
 *****************************************************************************/
VOID
ZapCol(
DWORD col,
DWORD row
)
{
DWORD x, y;
for (y = 0; y <= row; y++)
for (x = col; x < wBox - 1; x++)
matBox[x][y] = matBox[x + 1][y];
for (y = 0; y <= row; y++)
matBox[x][y] = matBox[x - 1][y];
}

/******************************************************************************
 * ZapRow(col, row)
 *
 * purpose: delete given row in edit box. Shift rows below given row up. Lowest
 *          row gets duplicated
 *
 * params:  DWORD col : column
 *          DWORD row : row
 * returns: none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box)
 *****************************************************************************/
VOID
ZapRow(
DWORD col,
DWORD row
)
{
DWORD x, y;
for (x = 0; x <= col; x++)
for (y = row; y < kBox - 1; y++)
matBox[x][y] = matBox[x][y + 1];
for (x = 0; x <= col; x++)
matBox[x][y] = matBox[x][y - 1];
}

/******************************************************************************
 * DupCol(col, row)
 *
 * purpose: duplicate given column in edit box. Shift cols to right of given
 *          col right
 *
 * params:  DWORD col : column
 *          DWORD row : row
 * returns: none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on

*               currently displayed box)
 *****************************************************************************/
VOID
DupCol(
DWORD col,
DWORD row
)
{
DWORD x, y;
for (x = wBox - 1; x > col; x--)
for (y = 0; y <= row; y++)
matBox[x][y] = matBox[x - 1][y];
}

/******************************************************************************
 * DupRow(col, row)
 *
 * purpose: duplicate given row in edit box. Shift rows below given row down.
 *
 * params:  DWORD col : column
 *          DWORD row : row
 * returns: none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box)
 *****************************************************************************/
VOID
DupRow(
DWORD col,
DWORD row
)
{
DWORD x, y;
for (x = 0; x <= col; x++)
for (y = kBox - 1; y > row; y--)
matBox[x][y] = matBox[x][y - 1];
}

/******************************************************************************
 * ClearBox(col, row, bb)
 *
 * purpose: reset all pixels in edit box (make box white)
 *
 * params : none
 *
 * returns: none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box)
 *
 *****************************************************************************/
VOID
ClearBox(
VOID
)                  /* Clear edit box */
{
DWORD x, y;
for (x = 0; x < wBoxLim; x++)
for (y = 0; y < kBoxLim; y++)
matBox[x][y] = FALSE;
}

/******************************************************************************
 * BoxBackup()
 *
 * purpose:   makes a backup of pix. info. of currently displayed edit box
 *
 * params:    none
 *
 * returns:   none
 *
 * side effects: alters matBackup (local 2-d array with backup pixel info.
 *               on edit box )
 *****************************************************************************/
VOID
BoxBackup(
VOID
)
{
DWORD x, y;
HMENU hMenu;

hMenu = GetMenu(hBox);
EnableMenuItem(hMenu, BOX_UNDO, MF_ENABLED);
EnableMenuItem(hMenu, BOX_REFRESH, MF_ENABLED);
for (x = 0; x < wBoxLim; x++)
for (y = 0; y < kBoxLim; y++)
matBackup[x][y] = matBox[x][y];
wBoxBackup = wBox;
}

/******************************************************************************
 * BoxRestore()
 *
 * purpose : Current edit box and backup box exchange places
 *
 * params  : none
 *
 * returns : none
 *
 * side effects: alters matBox (global 2-d array with ready pixel info. on
 *               currently displayed box
 *
 *****************************************************************************/
VOID
BoxRestore(
VOID
)            /* Box and Backup exchange places */
{
DWORD x, y, temp;
CHAR z;

for (x = 0; x < wBoxLim; x++)
for (y = 0; y < kBoxLim; y++) {
z = matBackup[x][y];
matBackup[x][y] = matBox[x][y];
matBox[x][y] = z;
}
temp = wBox;
wBox = wBoxBackup;
wBoxBackup = temp;
}

/******************************************************************************
 * POINT SnapPointToGrid (Pt)
 *
 * purpose : Intended only for the Fill menu command where rubberbanding rect.
 *           needs to be aligned on the grid lines. Snap the current mouse
 *           coordinates to nearest grid intersection.
 *
 * params  : POINT Pt : current point mouse is over
 *
 * returns : POINT    : number of nearest square
 *
 * side effects:      : current mouse coordinate(global variable) altered to
 *                      return value
 *
 *****************************************************************************/
POINT
SnapPointToGrid(
POINTPt
)
{

Pt.x = (Pt.x - ptBox.x) / scale;
if (Pt.y > (scale * (font.Ascent - 1)))
Pt.y = Pt.y - 2;/* Allow for break in box */
Pt.y = (Pt.y - ptBox.y) / scale;
return (Pt);
}

/******************************************************************************
 * VOID PASCAL EndRubberBandingRect()
 *
 * purpose: Stops rubberbanding rect for Fill menu command and cleans up
 *
 * params : HANDLE hDst : handle to dest. DC
 *
 * side effects: none
 *
 *****************************************************************************/
VOID PASCAL
EndRubberBandingRect(
HDC hDst  /* handle to dest. DC */
)
{
fRubberBanding = FALSE;      /* reset "in-progress" flag */

ReleaseDC(hBox, hDst);
ReleaseCapture();
SetCursor(hOldCursor);
}

/******************************************************************************
 * HDC PASCAL InitialiseRubberBandingRect(hBox)
 *
 * purpose: Sets up rubberbanding rect for Fill menu command.
 *
 * params : HANDLE hDst : handle to box DC
 *
 * returns :handle to destination display context
 *
 * side effects: alters few global flags for rubberbanding
 *
 *****************************************************************************/
HDC PASCAL
InitialiseRubberBandingRect(
HWND hBox  /* handle to  DC  of box */
)
{
HDC hDst;

fRubberBanding = TRUE;       /* set "in-progress" flag */
fStartRubberBand = FALSE;    /* reset "start-proceedings" flag */
SetCapture(hBox);            /* send all msgs to current window */

hDst = GetDC (hBox);

/* select pen and fill mode for rectangle*/
hWhitePen  = SelectObject(hDst, GetStockObject (WHITE_PEN));
hNullBrush = SelectObject(hDst, GetStockObject (NULL_BRUSH));
SetROP2(hDst, R2_XORPEN);

return(hDst);
}

/******************************************************************************
 * VOID PASCAL DrawRubberBand()
 *
 * purpose: Draw rubberbanding rect for Fill menu command.
 *
 * params : HANDLE hDst : handle to dest. DC
 *
 * side effects: alters few global flags for rubberbanding
 *
 *****************************************************************************/
VOID PASCAL
DrawRubberBand(
HDC   hDst,                        /*  handle to dest. DC */
LPRECTlpRect,
DWORDrop
)
{
#ifdef DBG
charbuf[256];
        SIZE        Size;
        static LONG cxPrev;
        INT         nLeftRect;
#endif

SetROP2(hDst, rop);
Rectangle(hDst, lpRect->left,  lpRect->top,
lpRect->right, lpRect->bottom);

#ifdef DBG
sprintf(buf, "left=%d, top=%d, right=%d, bottom=%d",
lpRect->left,  lpRect->top, lpRect->right, lpRect->bottom);
    GetTextExtentPoint32(hDst, buf, lstrlen(buf), &Size);
    nLeftRect = ptBox.x+scale*wBox+16 + Size.cx;
    if(nLeftRect < cxPrev) {
        RECT rc;
        rc.left   = nLeftRect;
        rc.top    = 14+2*kBox+font.ExtLeading+3*cSysHeight;
        rc.right  = cxPrev;
        rc.bottom = 14+2*kBox+font.ExtLeading+3*cSysHeight + Size.cy;
    FillRect(hDst, &rc, hbrBackGround);
    }
    cxPrev = nLeftRect;
TextOut(hDst, ptBox.x+scale*wBox+16,
      14+2*kBox+font.ExtLeading+3*cSysHeight,
      buf, strlen(buf));
#endif
}

/******************************************************************************
 * long  APIENTRY FontEditWndProc(hBox, message, wParam, lParam)
 *
 * purpose: Master controller for Fontedit's all-encompassing main window
 *
 * params : same as for all window functions
 *
 * side effects: countless
 *
 *****************************************************************************/
LONG  APIENTRY
FontEditWndProc(
HWND   hBox,
WORD   message,
WPARAM wParam,
LPARAM lParam
)
{
PAINTSTRUCTps;
HMENUhMenu;
WORDmf;
POINTpt;
RECTBoxRect;

switch (message) {
case WM_CLOSE:
if (!CheckSave())    /* See if any files need saving */
break;
/* Window's being destroyed. */
if (fLoaded)         /* 4/8/87 Linsh added */
DeleteGlobalBitmap(); /* Get rid of memory DC */
DestroyWindow(hFont);
DestroyWindow(hBox);
break;

case WM_DESTROY:
PostQuitMessage(0);  /* Cause application to be terminated */
break;

case WM_QUERYENDSESSION:
if (CheckSave())             /* See if any files need saving */
return TRUE;
break;

case WM_ENDSESSION:
if (fLoaded)
DeleteGlobalBitmap();      /* Get rid of memory DC */
break;

case WM_SIZE:
/* Window's size is changing.  lParam contains the width
        ** and height, in the low and high words, respectively.
        ** wParam contains SIZENORMAL for "normal" size changes,
        ** SIZEICONIC when the window is being made iconic, and
        ** SIZEFULLSCREEN when the window is being made full screen. */
switch (wParam) {
case SIZEFULLSCREEN:
case SIZENORMAL:
ResizeShow();
if (kStuff != GetkStuff())/* Did it change ? */
ResizeShow();           /* Yes resize again */
break;
}
break;

case WM_MOVE: /* Tell popup to move with us. */
if (!IsIconic(hBox))
ResizeShow();
break;

case WM_PAINT:
/* Time for the window to draw itself. */
BeginPaint(hBox, (LPPAINTSTRUCT)&ps);
FontEditPaint(hBox,  ps.hdc);
EndPaint(hBox, (LPPAINTSTRUCT)&ps);
break;

case WM_COMMAND:
/* A menu item has been selected, or a control is notifying
 * its parent.  wParam is the menu item value (for menus),
 * or control ID (for controls).  For controls, the low word
 * of lParam has the window handle of the control, and the hi
 * word has the notification code.  For menus, lParam contains
 * 0L. */
FontEditCommand(hBox, LOWORD(wParam));
break;

/* Data interchange request. */
case WM_CUT:
case WM_COPY:
case WM_PASTE:
case WM_CLEAR:
case WM_UNDO:
case WM_RENDERFORMAT:
case WM_RENDERALLFORMATS:
case WM_DESTROYCLIPBOARD:
case WM_DRAWCLIPBOARD:
break;
case WM_INITMENU:
hMenu = GetMenu(hBox);  /* Gray menu if no clipboard bitmap */
mf = (WORD)(IsClipboardFormatAvailable(CF_BITMAP) ? MF_ENABLED :
MF_GRAYED);
EnableMenuItem(hMenu, BOX_PASTE, mf);
EnableMenuItem(hMenu, FONT_PASTE, mf);
break;

/* For each of following mouse window messages, wParam contains
** bits indicating whether or not various virtual keys are down,
** and lParam is a POINT containing the mouse coordinates.   The
** keydown bits of wParam are:  MK_LBUTTON (set if Left Button is
** down); MK_RBUTTON (set if Right Button is down); MK_SHIFT (set
** if Shift Key is down); MK_ALTERNATE (set if Alt Key is down);
** and MK_CONTROL (set if Control Key is down). */

case WM_LBUTTONDOWN:
((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);

if (fStartRubberBand) {
/* a green signal to rubberband a rectangle for the
 * Fill menu command rectangle now has null dimensions.
 * Snap the current mouse point to nearest grid
 * intersection thus defining upper left corner of
 * rectangle */

if (PtInRect((LPRECT)&FontRect, pt)) {
pt = SnapPointToGrid(pt);
rectRubber.top    =  pt.y   *scale+ptBox.y+1;
rectRubber.bottom = (pt.y+1)*scale+ptBox.y-2;
rectRubber.left   =  pt.x   *scale+ptBox.x+1;
rectRubber.right  = (pt.x+1)*scale+ptBox.x-2;

hDst = InitialiseRubberBandingRect(hBox);
DrawRubberBand(hDst, &rectRubber, R2_XORPEN);
}
else {
fStartRubberBand = fRubberBanding = FALSE;
ReleaseCapture();
}
}
/* do operation depending upon current active command,
 * but not if we just added/deleted a row/column. */
if (!fJustZapped) {
if (fStartRubberBand) {
pt.x *= scale;
pt.y *= scale;
MouseInBox(hBox, message, pt);
}
else {
((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
MouseInBox(hBox, message, pt);
}
}

break;

case WM_LBUTTONUP:               /* Get other corner of rectangle */
fJustZapped = FALSE;
if (fRubberBanding) {
/* if rubberbanding for the Fill menu command,
 * terminate proceedings and clean up */
DrawRubberBand(hDst, &rectRubber, R2_NOT);
EndRubberBandingRect(hDst);
if (cursor) {
ReadRect();
}
}
if (fCaptured ) {
/* if cursor is + shaped, restore it to default */
ReleaseCapture();
SetCursor (hOldCursor);
}
break;

case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
break;

case WM_MOUSEMOVE:                      /* If mouse is down */

((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);

if ((fRubberBanding) && (wParam & MK_LBUTTON)) {
/* if any of Fill menu commands is active
** (AND the mouse key depressed) draw a rubberband
** a rectangle with the mouse movements */

/* get current square number */
pt = SnapPointToGrid(pt);

/* calculate grid for new square */
BoxRect.top    =  pt.y   *scale+ptBox.y+1;
BoxRect.bottom = (pt.y+1)*scale+ptBox.y-2;
BoxRect.left   =  pt.x   *scale+ptBox.x+1;
BoxRect.right  = (pt.x+1)*scale+ptBox.x-2;

/* erase old mark */
DrawRubberBand(hDst, &rectRubber, R2_NOT);

/* limit rubber band to box */
if (BoxRect.right > (LONG) (scale * wBox + ptBox.x))
BoxRect.right = scale * wBox + ptBox.x;
if (BoxRect.bottom > (LONG) (scale * kBox + ptBox.y))
BoxRect.bottom = scale * kBox + ptBox.y;
if (BoxRect.top < 0)
BoxRect.top = 1;
if (BoxRect.left < 0)
BoxRect.left = 1;

if (ptA.x == pt.x) {
rectRubber.right  = BoxRect.right;
rectRubber.left   = BoxRect.left;
}
if (ptA.y == pt.y) {
rectRubber.bottom = BoxRect.bottom;
rectRubber.top    = BoxRect.top;
}

/* almost an IntersectRect */
if (ptA.x >= pt.x)
rectRubber.left   = BoxRect.left;
else
rectRubber.right  = BoxRect.right;

if (ptA.y >= pt.y)
rectRubber.top    = BoxRect.top;
else
rectRubber.bottom = BoxRect.bottom;

/* Draw new mark */
DrawRubberBand(hDst, &rectRubber, R2_XORPEN);
}
else {
/* if not "Fill"ing(AND mouse key depressed,
 * paint with the mouse movements */
if ((wParam & MK_LBUTTON) && cursor == FALSE &&
    fJustZapped == FALSE)
MouseInBox(hBox, message, pt);
}
break;

case WM_LBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
break;

default:

/* Everything else comes here.  This call MUST exist
        ** in your window proc.  */

return(DefWindowProc(hBox, message, wParam, lParam));
break;
}

/* A window proc should always return something */
return(0L);
}

/***************************** Public  Function ****************************\
*
* BOOL  APIENTRY AboutDlg(hDlg, message, wParam, lParam)
* HWND     hDlg;
* WORD message;
* WPARAM wParam;
* LPARAM lParam;
*
*
* Effects: none.
*
\***************************************************************************/
BOOL  APIENTRY
AboutDlg(
HWND   hDlg,
WORD   message,
WPARAM wParam,
LPARAM lParam
)
{
UNREFERENCED_PARAMETER(lParam);

switch (message) {
case WM_INITDIALOG:
break;

case WM_COMMAND:
EndDialog(hDlg, LOWORD(wParam));
/* idok or idcancel */
break;

default:
return FALSE;
break;
}
return(TRUE);
}


FONTCVT.C

/*++

Copyright 1993 - 1998 Microsoft Corporation

Module Name:

    fontcvt.c

Abstract:

    Font Editor specific routines for interface to the type conversion
    functions in Typecvt.c.

--*/

#include "windows.h"
#include <windowsx.h>
#include "typecvt.h"
#include "fontedit.h"

extern LONG lSizeOfOldFontHeader;       /* Old packed font header size. */
extern LONG lSizeOfOldFontHeader30;     /* Old 3.0 packed font header size. */
extern LONG lSizeOfOldGlyph20;/* Old packed glyph 2.0 structure. */
extern LONG lSizeOfOldGlyph30;/* Old packed glyph 3.0 structure. */

//
// The following is a definition of the current 2.0 font header structure.
// This is the same as the old structure except that it will now be stored
// DWORD aligned in memory.
//
// First, we give it the size of the element type (as it was under Win 3.0).
// Second, we give it the actual size of the element.  These will only differ
//  if the element is some sort of array.
//
// Note that it is important that this array accurately reflect any changes
// in the actual structure.  When CalculateStructOffsets is called it will
// return an error if the structure does not match in size, but it cannot
// detect swapped element errors.
//

#define pfh2T   ((FontHeaderType *)NULL)

SDI     rgsdiFontHeader [] =
{
{ sizeof (WORD),  sizeof (pfh2T->Version)},
{ sizeof (DWORD), sizeof (pfh2T->Size)},
{ sizeof (CHAR),  sizeof (pfh2T->Copyright)},
{ sizeof (WORD),  sizeof (pfh2T->Type)},
{ sizeof (WORD),  sizeof (pfh2T->Points)},
{ sizeof (WORD),  sizeof (pfh2T->VertRes)},
{ sizeof (WORD),  sizeof (pfh2T->HorizRes)},
{ sizeof (WORD),  sizeof (pfh2T->Ascent)},
{ sizeof (WORD),  sizeof (pfh2T->IntLeading)},
{ sizeof (WORD),  sizeof (pfh2T->ExtLeading)},
{ sizeof (BYTE),  sizeof (pfh2T->Italic)},
{ sizeof (BYTE),  sizeof (pfh2T->Underline)},
{ sizeof (BYTE),  sizeof (pfh2T->StrikeOut)},
{ sizeof (WORD),  sizeof (pfh2T->Weight)},
{ sizeof (BYTE),  sizeof (pfh2T->CharSet)},
{ sizeof (WORD),  sizeof (pfh2T->PixWidth)},
{ sizeof (WORD),  sizeof (pfh2T->PixHeight)},
{ sizeof (BYTE),  sizeof (pfh2T->Family)},
{ sizeof (WORD),  sizeof (pfh2T->AvgWidth)},
{ sizeof (WORD),  sizeof (pfh2T->MaxWidth)},
{ sizeof (BYTE),  sizeof (pfh2T->FirstChar)},
{ sizeof (BYTE),  sizeof (pfh2T->LastChar)},
{ sizeof (BYTE),  sizeof (pfh2T->DefaultChar)},
{ sizeof (BYTE),  sizeof (pfh2T->BreakChar)},
{ sizeof (WORD),  sizeof (pfh2T->WidthBytes)},
{ sizeof (DWORD), sizeof (pfh2T->Device)},
{ sizeof (DWORD), sizeof (pfh2T->Face)},
{ sizeof (DWORD), sizeof (pfh2T->BitsPointer)},
{ sizeof (DWORD), sizeof (pfh2T->BitsOffset)},
{ 0, 0 }
};

//
// The following is the current font 3.0 header.  This structure has not
// changed at all.  Note that it will contain filler between elements to
// maintain DWORD alignment.
//

#define pfh3T   ((FontHeader30 *)NULL)

SDI     rgsdiFontHeader30 [] =
{
{ sizeof (WORD), sizeof (pfh3T->fsVersion)},
{ sizeof (DWORD),  sizeof (pfh3T->fsSize)},
{ sizeof (CHAR),  sizeof (pfh3T->fsCopyright)},
{ sizeof (WORD), sizeof (pfh3T->fsType)},
{ sizeof (WORD), sizeof (pfh3T->fsPoints)},
{ sizeof (WORD), sizeof (pfh3T->fsVertRes)},
{ sizeof (WORD), sizeof (pfh3T->fsHorizRes)},
{ sizeof (WORD), sizeof (pfh3T->fsAscent)},
{ sizeof (WORD), sizeof (pfh3T->fsInternalLeading)},
{ sizeof (WORD), sizeof (pfh3T->fsExternalLeading)},
{ sizeof (BYTE),  sizeof (pfh3T->fsItalic)},
{ sizeof (BYTE),  sizeof (pfh3T->fsUnderline)},
{ sizeof (BYTE),  sizeof (pfh3T->fsStrikeOut)},
{ sizeof (WORD), sizeof (pfh3T->fsWeight)},
{ sizeof (BYTE),  sizeof (pfh3T->fsCharSet)},
{ sizeof (WORD), sizeof (pfh3T->fsPixWidth)},
{ sizeof (WORD), sizeof (pfh3T->fsPixHeight)},
{ sizeof (BYTE),  sizeof (pfh3T->fsPitchAndFamily)},
{ sizeof (WORD), sizeof (pfh3T->fsAvgWidth)},
{ sizeof (WORD), sizeof (pfh3T->fsMaxWidth)},
{ sizeof (BYTE),  sizeof (pfh3T->fsFirstChar)},
{ sizeof (BYTE),  sizeof (pfh3T->fsLastChar)},
{ sizeof (BYTE),  sizeof (pfh3T->fsDefaultChar)},
{ sizeof (BYTE),  sizeof (pfh3T->fsBreakChar)},
{ sizeof (WORD), sizeof (pfh3T->fsWidthBytes)},
{ sizeof (DWORD),  sizeof (pfh3T->fsDevice)},
{ sizeof (DWORD),  sizeof (pfh3T->fsFace)},
{ sizeof (DWORD),  sizeof (pfh3T->fsBitsPointer)},
{ sizeof (DWORD),  sizeof (pfh3T->fsBitsOffset)},
{ sizeof (CHAR),  sizeof (pfh3T->fsDBfiller)},
{ sizeof (DWORD),  sizeof (pfh3T->fsFlags)},
{ sizeof (WORD), sizeof (pfh3T->fsAspace)},
{ sizeof (WORD), sizeof (pfh3T->fsBspace)},
{ sizeof (WORD), sizeof (pfh3T->fsCspace)},
{ sizeof (DWORD),  sizeof (pfh3T->fsColorPointer)},
{ sizeof (DWORD),  sizeof (pfh3T->fsReserved)},
{ sizeof (CHAR),  sizeof (pfh3T->fsCharOffset)},
{ 0, 0 }
};

//
// The following definition is for the GlyphInfo20 structure.  These
// structures are read off of disk and then converted into memory.
//

#define pgi2T   ((GLYPHINFO_20 *)NULL)

SDI     rgsdiGlyph20 [] =
{
{ sizeof (SHORT),   sizeof (pgi2T->GIwidth)},
{ sizeof (SHORT),   sizeof (pgi2T->GIoffset)},
{ 0, 0 }
};

//
// The following definition is for the GlyphInfo30 structure.  These
// structures are read off of disk and then converted into memory.
//

#define pgi3T   ((GLYPHINFO_30 *)NULL)

SDI     rgsdiGlyph30 [] =
{
{ sizeof (SHORT),  sizeof (pgi3T->GIwidth)},
{ sizeof (LONG), sizeof (pgi3T->GIoffset)},
{ 0, 0 }
};

BOOL
fConvStructInit ()

/*++

Routine Description:

    This function performs all the necessary initialization on the font
    editor's structure definition info strucrures (SDI) and type conversion
    structures (TCI).  This routine should be called before either
    FontStructFromBuffer or BufferFromFontStruct are called.
    It is also important that this function be called before the global
    variable ulOldFontHeaderSize is accessed.

Arguments:

    None.  The function accesses the global definition structures and sets
    the variable ulOldFontHeaderSize to the size of the packed font header
    structure.

Return Value:

    BOOL - TRUE = the function successfully initialized all internal
            strucutres.
           FALSE = there was an error in one of the structure definitions.
            This is usually caused by element changes which are not reflected
            in the structure definition.  The program should perform an
            assertion failure if this is the case.
    Also, the global variable ulOldFontHeaderSize is set to the size of the
        packed font header structure.

--*/

{
    //
    // Calculate all of the element offsets for the old font header structure.
    // The function will return the size of the structure in packed format.
    //
    lSizeOfOldFontHeader = lCalculateStructOffsets (
             rgsdiFontHeader,           // Give it the struct definition.
             CVT_ALIGN_PACKED,          // Alignment type is packed on disk.
 sizeof (FontHeaderType)
            );

    //
    // If CalculateStructOffsets returns -1 then there was a problem with
    // structure definition.  The only types of error it can find is if the
    // structure size it is passed does not match the one it calculates.
    //
    if (lSizeOfOldFontHeader == -1) {
        return (FALSE);
    }

    //
    // Calculate The size of the old font 3.0 structure.
    // The function will return the size of the structure in packed format.
    //

    lSizeOfOldFontHeader30 = lCalculateStructOffsets (
             rgsdiFontHeader30,         // Give it the new font header struct.
             CVT_ALIGN_PACKED,          // Alignment type is packed on disk.
 sizeof (FontHeader30)
            );

    //
    // Check if the routine detected and error in the structure definition.
    //
    if (lSizeOfOldFontHeader30 == -1) {
        return (FALSE);
    }

    //
    // Now do the same operations on the glyph information structures.
    //

    lSizeOfOldGlyph20 = lCalculateStructOffsets (
rgsdiGlyph20,
CVT_ALIGN_PACKED,
sizeof (GLYPHINFO_20)
);

    if (lSizeOfOldGlyph20 == -1) {
        return (FALSE);
    }

    lSizeOfOldGlyph30 = lCalculateStructOffsets (
rgsdiGlyph30,
CVT_ALIGN_PACKED,
sizeof (GLYPHINFO_30)
);

    if (lSizeOfOldGlyph30 == -1) {
        return (FALSE);
    }

    return (TRUE);      // No error found.
}

VOID
vFontStructFromBuffer (
             PBYTE           pjSourceBuff,
             FontHeaderType  *pfhDestFHStruct
        )

/*++

Routine Description:

    This function provides an interface to the font editor for the strutcture
    type conversion routines.
    It essentially just calls the typecvt routines but hides the details from
    the font editor.

Arguments:

    pjSourceBuff    - This is a pointer to the source buffer with the old
                      packed font header information.  This buffer is from a
                      memory mapped disk file.

    pfhDestFHStruct - This is a pointer to the destination FontHeaderType
                      structure.  This structure is of the new DWORD aligned
                      type.

Return Value:

    None.

--*/

{
    vPerformConversion (
 rgsdiFontHeader,// Give it the structure description.
             pjSourceBuff,// Give it our source file buffer.
 CVT_ALIGN_PACKED,// File is packed alignment.
 CVT_FILE_ENDIAN_DEFAULT,// Use file endian type (little).
             (PBYTE)pfhDestFHStruct,// Give it our destination buffer.
 CVT_ALIGN_SYSTEM,// Use the current system aligment.
 CVT_ENDIAN_SYSTEM// Use the current system endian.
            );
}

VOID
vBufferFromFontStruct (
             FontHeaderType  *pfhSrcFHStruct,
             PBYTE           pjDestBuff
        )

/*++

Routine Description:

    This function provides an interface to the font editor for the strutcture
    type conversion routines.
    It converts a source structure of type FontHeaderType to a destination
    buffer which can then be written out to disk.
    It essentially just calls the typecvt routines but hides the details from
    the font editor.

Arguments:

    pfhSrcFHStruct  - This is a pointer to the source FontHeaderType structure.
                      This structure is of the new DWORD aligned type.

    pjDestBuff  -     This is a pointer to the destination buffer which will
                      hold the old packed font header structure.
                      This buffer is from a memory mapped disk file.

Return Value:

    None.

--*/

{
    vPerformConversion (
 rgsdiFontHeader,// Give it the structure description.
             (PBYTE)pfhSrcFHStruct,// Give it our destination buffer.
 CVT_ALIGN_SYSTEM,// Use the current system aligment.
 CVT_ENDIAN_SYSTEM,// Use the current system endian.
             pjDestBuff,// Give it our source file buffer.
 CVT_ALIGN_PACKED,// File is packed alignment.
 CVT_FILE_ENDIAN_DEFAULT// Use file endian type (little).
            );
}

VOID
vBufferFromFont30Struct (
             FontHeader30    *pfh3SrcFH3Struct,
             PBYTE           pjDestBuff
        )

/*++

Routine Description:

    This function provides an interface to the font editor for the strutcture
    type conversion routines.
    It converts a source structure of type FontHeader30 to a destination
    buffer which can then be written out to disk.
    It essentially just calls the typecvt routines but hides the details from
    the font editor.

Arguments:

    pfh3SrcFH3Struct- This is a pointer to the source FontHeader30 structure.
                      This structure is of the new DWORD aligned type.

    pjDestBuff  -     This is a pointer to the destination buffer which will
                      hold the old packed font header structure.
                      This buffer is from a memory mapped disk file.

Return Value:

    None.

--*/

{
    vPerformConversion (
 rgsdiFontHeader30,// Give it the structure description.
             (PBYTE)pfh3SrcFH3Struct,// Give it our destination buffer.
 CVT_ALIGN_SYSTEM,// Use the current system aligment.
 CVT_ENDIAN_SYSTEM,// Use the current system endian.
             pjDestBuff,// Give it our source file buffer.
 CVT_ALIGN_PACKED,// File is packed alignment.
 CVT_FILE_ENDIAN_DEFAULT// Use file endian type (little).
            );
}

VOID
vGlyphInfo20FromBuffer (
             PBYTE           pjSourceBuff,
             GLYPHINFO_20   *pgi2DestGI2Struct
        )

/*++

Routine Description:

    This function provides an interface to the font editor for the strutcture
    type conversion routines.
    It essentially just calls the typecvt routines but hides the details from
    the font editor.

Arguments:

    pjSourceBuff    - This is a pointer to the source buffer with the old
                      packed font header information.  This buffer is from a
                      memory mapped disk file.

    pgi2DestGI2Struct - This is a pointer to the destination GLYPHINFO_20
                      structure.  This structure is of the new DWORD aligned
                      type.

Return Value:

    None.

--*/

{
    vPerformConversion (
 rgsdiGlyph20,// Give it the structure description.
             pjSourceBuff,// Give it our source file buffer.
 CVT_ALIGN_PACKED,// File is packed alignment.
 CVT_FILE_ENDIAN_DEFAULT,// Use file endian type (little).
             (PBYTE)pgi2DestGI2Struct,// Give it our destination buffer.
 CVT_ALIGN_SYSTEM,// Use the current system aligment.
 CVT_ENDIAN_SYSTEM// Use the current system endian.
            );
}

VOID
vGlyphInfo30FromBuffer (
             PBYTE           pjSourceBuff,
             GLYPHINFO_30   *pgi3DestGI3Struct
        )

/*++

Routine Description:

    This function provides an interface to the font editor for the strutcture
    type conversion routines.
    It essentially just calls the typecvt routines but hides the details from
    the font editor.

Arguments:

    pjSourceBuff    - This is a pointer to the source buffer with the old
                      packed font header information.  This buffer is from a
                      memory mapped disk file.

    pgi3DestGI3Struct - This is a pointer to the destination GLYPHINFO_30
                      structure.  This structure is of the new DWORD aligned
                      type.

Return Value:

    None.

--*/

{
    vPerformConversion (
 rgsdiGlyph30,// Give it the structure description.
             pjSourceBuff,// Give it our source file buffer.
 CVT_ALIGN_PACKED,// File is packed alignment.
 CVT_FILE_ENDIAN_DEFAULT,// Use file endian type (little).
             (PBYTE)pgi3DestGI3Struct,// Give it our destination buffer.
 CVT_ALIGN_SYSTEM,// Use the current system aligment.
 CVT_ENDIAN_SYSTEM// Use the current system endian.
            );
}

VOID
vBufferFromGlyphInfo20 (
             GLYPHINFO_20   *pgi2SrcGI2Struct,
             PBYTE           pjDestBuff
        )

/*++

Routine Description:

    This function provides an interface to the font editor for the strutcture
    type conversion routines.
    It converts a source structure of type GLYPHINFO_20 to a destination
    buffer which can then be written out to disk.
    It essentially just calls the typecvt routines but hides the details from
    the font editor.

Arguments:

    pgi2SrcGI2Struct- This is a pointer to the source GLYPHINFO_20 structure.
                      This structure is of the new DWORD aligned type.

    pjDestBuff  -     This is a pointer to the destination buffer which will
                      hold the old packed font header structure.
                      This buffer is from a memory mapped disk file.

Return Value:

    None.

--*/

{
    vPerformConversion (
 rgsdiGlyph20,// Give it the structure description.
             (PBYTE)pgi2SrcGI2Struct,// Give it our destination buffer.
 CVT_ALIGN_SYSTEM,// Use the current system aligment.
 CVT_ENDIAN_SYSTEM,// Use the current system endian.
             pjDestBuff,// Give it our source file buffer.
 CVT_ALIGN_PACKED,// File is packed alignment.
 CVT_FILE_ENDIAN_DEFAULT// Use file endian type (little).
            );
}

VOID
vBufferFromGlyphInfo30 (
             GLYPHINFO_30   *pgi3SrcGI3Struct,
             PBYTE           pjDestBuff
        )

/*++

Routine Description:

    This function provides an interface to the font editor for the strutcture
    type conversion routines.
    It converts a source structure of type GLYPHINFO_30 to a destination
    buffer which can then be written out to disk.
    It essentially just calls the typecvt routines but hides the details from
    the font editor.

Arguments:

    pgi3SrcGI3Struct- This is a pointer to the source GLYPHINFO_30 structure.
                      This structure is of the new DWORD aligned type.

    pjDestBuff  -     This is a pointer to the destination buffer which will
                      hold the old packed font header structure.
                      This buffer is from a memory mapped disk file.

Return Value:

    None.

--*/

{
    vPerformConversion (
 rgsdiGlyph30,// Give it the structure description.
             (PBYTE)pgi3SrcGI3Struct,// Give it our destination buffer.
 CVT_ALIGN_SYSTEM,// Use the current system aligment.
 CVT_ENDIAN_SYSTEM,// Use the current system endian.
             pjDestBuff,// Give it our source file buffer.
 CVT_ALIGN_PACKED,// File is packed alignment.
 CVT_FILE_ENDIAN_DEFAULT// Use file endian type (little).
            );
}


FONTDLG.C

/*++

Copyright 1993 - 1998 Microsoft Corporation

Module Name:

    fontdlg.c

Abstract:

    Font Editor interface to the common dialog Open File and Save File
functions.  Also, this routine displays and controls the font format
save dialog.

--*/

#include "windows.h"
#include <windowsx.h>
#include "fontedit.h"
#include "commdlg.h"

/* message box strings loaded in sample.c from the stringtable */
extern CHAR szIFN[], szFNF[], szREF[], szSCC[], szEOF[], szECF[];

extern CHAR    szAppName [];
extern CHAR    szExt [];
extern CHAR    szExtDesc [];

extern CHAR    szNEWFONT [];
extern CHAR    szFRO [];

extern INT iFontFormat;             /* Set to the id of current font format */
extern BOOL fReadOnly;

CHAR szDlgMsg [MAX_STR_LEN+MAX_FNAME_LEN];

externCHAR szFilter[];

//
// Local Function Prototypes.
//

BOOL
DlgCheckFormat (
HANDLE hInstance,       // app module instance handle
HWND   hWndParent       // window handle of parent window
);

BOOL APIENTRY
DlgFnCheckFormat (
HWND   hDlg,
WORD   message,
WPARAM wParam,
LPARAM lParam
);

//
// Functions.
//

BOOL
CommDlgOpen (
HWND   hWndParent,      /* window handle of parent window */
OFSTRUCT *pofsReOpenInfo,/* ptr to current file OFSTRUCT (->cBytes=0 if no
 * cur. file)*/
CHAR  *pszNewNameIn,    /* ptr to array which will get new file's name
 * (no path) */
CHAR  *pszExtIn,        /* ptr to current default extension */
CHAR  *pszFileNameOnly,    /* ptr to application name */
BOOL   fOpenType
)
{
OPENFILENAMEofTempOF;
HFILEhFile;
        CHAR            szBuf[_MAX_PATH];

ofTempOF.lStructSize =sizeof(OPENFILENAME);
ofTempOF.hwndOwner =hWndParent;
ofTempOF.lpstrFilter =szFilter;
ofTempOF.lpstrCustomFilter =(LPSTR)NULL;
ofTempOF.nMaxCustFilter =0L;
ofTempOF.nFilterIndex =0L;
ofTempOF.lpstrFile =pszNewNameIn;
ofTempOF.nMaxFile =MAX_FNAME_LEN;
ofTempOF.lpstrFileTitle =pszFileNameOnly;
ofTempOF.nMaxFileTitle =MAX_FNAME_LEN;
ofTempOF.lpstrInitialDir =(LPSTR)NULL;
ofTempOF.lpstrTitle =(LPSTR)NULL;
ofTempOF.Flags =OFN_SHOWHELP;
ofTempOF.nFileOffset =0;
ofTempOF.nFileExtension =0;
ofTempOF.lpstrDefExt =pszExtIn;

if (fOpenType == FONT_NEW)
{
if (MessageBox (hWndParent, (LPSTR)szNEWFONT, (LPSTR)szAppName,
MB_OKCANCEL | MB_ICONEXCLAMATION | MB_APPLMODAL) == IDCANCEL)
{
return (FALSE);
}
}

        lstrcpy(szBuf,pszNewNameIn);
if (GetOpenFileName (&ofTempOF) == FALSE)
{
        lstrcpy(pszNewNameIn,szBuf);
return (FALSE);
}

CharUpper (pszNewNameIn);

fReadOnly = FALSE;

hFile = (HFILE)OpenFile (pszNewNameIn, pofsReOpenInfo, OF_READWRITE);

if (hFile == (HFILE) -1) {

hFile = (HFILE)OpenFile (pszNewNameIn, pofsReOpenInfo, OF_READ);

if (hFile == (HFILE) -1) {

DlgMergeStrings (szFNF, pszNewNameIn, szDlgMsg);

MessageBox (hWndParent, szDlgMsg, szAppName,
MB_OK | MB_ICONASTERISK | MB_APPLMODAL);

return (FALSE);

} else if (fOpenType != FONT_NEW) {

BOOLfResult;

DlgMergeStrings (szFRO, pszNewNameIn, szDlgMsg);

/* File Is Read Only */
fResult = MessageBox (hWndParent, szDlgMsg, szAppName,
MB_OKCANCEL | MB_ICONEXCLAMATION | MB_APPLMODAL);

/* Give them the chance to cancel right now. */
if (fResult == IDCANCEL) {

_lclose((HFILE)hFile);
return (FALSE);
}

fReadOnly = TRUE;
}
}

_lclose((HFILE)hFile);

return (TRUE);
}

BOOL
CommDlgSaveAs(
HANDLE hInstance,
HWND   hWndParent,      /* window handle of parent window */
OFSTRUCT *pofsReOpenInfo,/* ptr to current file OFSTRUCT (->cBytes=0 if no
 * cur. file)*/
CHAR  *pszNewNameIn,    /* ptr to array which will get new file's name
 * (no path) */
CHAR  *pszExtIn,        /* ptr to current default extension */
CHAR  *pszFileNameOnly  /* ptr to file name */
)
{
OPENFILENAMEofTempOF;
HFILEhFile;
        CHAR szBuf[_MAX_PATH];

ofTempOF.lStructSize =sizeof(OPENFILENAME);
ofTempOF.hwndOwner =hWndParent;
ofTempOF.lpstrFilter =szFilter;
ofTempOF.lpstrCustomFilter =(LPSTR)NULL;
ofTempOF.nMaxCustFilter =0L;
ofTempOF.nFilterIndex =0L;
ofTempOF.lpstrFile =pszNewNameIn;
ofTempOF.nMaxFile =MAX_FNAME_LEN;
ofTempOF.lpstrFileTitle =pszFileNameOnly;
ofTempOF.nMaxFileTitle =MAX_FNAME_LEN;
ofTempOF.lpstrInitialDir =(LPSTR)NULL;
ofTempOF.lpstrTitle =(LPSTR)NULL;
ofTempOF.Flags =OFN_SHOWHELP;
ofTempOF.nFileOffset =0;
ofTempOF.nFileExtension =0;
ofTempOF.lpstrDefExt =pszExtIn;

if (DlgCheckFormat (hInstance, hWndParent) == FALSE)
{
return (FALSE);
}

        // save lpstrFile. Because if GetSaveFileName returns 0(i.e. select
        // [Cancel] in dialog, pointer to lpstrFile will be lost.
        lstrcpy(szBuf,pszNewNameIn);

if (GetSaveFileName (&ofTempOF) == FALSE)
{
            lstrcpy(pszNewNameIn,szBuf);
return (FALSE);
}

CharUpper (pszNewNameIn);

hFile = (HFILE)OpenFile (pszNewNameIn, pofsReOpenInfo, OF_EXIST);

if (hFile >= (HFILE) 0) /* already exists */
{
_lclose((HFILE)hFile);

DlgMergeStrings (szREF, pszNewNameIn, szDlgMsg);

if (MessageBox (hWndParent, (LPSTR)szDlgMsg, (LPSTR)pszNewNameIn,
MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION | MB_APPLMODAL)
== IDNO)
{
return (FALSE);
}

hFile = (HFILE)OpenFile (pszNewNameIn, pofsReOpenInfo, OF_WRITE);

if (hFile == (HFILE) -1)
{
DlgMergeStrings(szEOF, pszNewNameIn, szDlgMsg);

MessageBox(hWndParent, (LPSTR)szDlgMsg, (LPSTR)pszNewNameIn,
MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL);

return (FALSE);
}

_lclose((HFILE)hFile);

return (TRUE);
}

hFile = (HFILE)OpenFile (pszNewNameIn, pofsReOpenInfo, OF_CREATE);

if (hFile == (HFILE) -1)
{
DlgMergeStrings(szECF, pszNewNameIn, szDlgMsg);

MessageBox(hWndParent, (LPSTR)szDlgMsg, (LPSTR)pszNewNameIn,
MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL);

return (FALSE);
}

_lclose((HFILE)hFile);

    return (TRUE);

} /* end dlgsaveas */

/*=============================================================================
 DLGMERGESTRINGS scans string1 for merge spec (%%). If found, insert string2 at
 that point, and then append remainder of string1.  Result in string3.
==============================================================================*/
BOOL
DlgMergeStrings(
CHAR   *szSrc,
CHAR   *szMerge,
CHAR   *szDst
)
{
    CHAR *pchSrc;
    CHAR *pchDst;

    pchSrc = szSrc;
    pchDst = szDst;

    /* Find merge spec if there is one. */
    while (!((*pchSrc == '%') && (*(pchSrc+1) == '%')))  {
        *pchDst++ = *pchSrc;

        /* If we reach end of string before merge spec, just return. */
        if (!*pchSrc++)
            return FALSE;

    }
    /* If merge spec found, insert sz2 there. (check for null merge string */
    if (szMerge) {
        while (*szMerge)
            *pchDst++ = *szMerge++;

    }

    /* Jump over merge spec */
    pchSrc++; pchSrc++;

    /* Now append rest of Src String */
    while (*pchDst++ = *pchSrc++);
    return TRUE;

} /* end dlgmergestrings */

BOOL
DlgCheckFormat (
HANDLE hInstance,       // app module instance handle
HWND   hWndParent       // window handle of parent window
)
{
    //FARPROC  lpProc;
    BOOL     fResult;

    fResult = DialogBox (hInstance, (LPSTR)MAKEINTRESOURCE (IDD_FORMAT),
//hWndParent, (WNDPROC)(lpProc = DlgFnCheckFormat));
hWndParent, (WNDPROC)DlgFnCheckFormat);

    //FreeProcInstance(lpProc);

return (fResult);

} /* end dlgcheckformat */

BOOL APIENTRY
DlgFnCheckFormat (
HWND   hDlg,
WORD   message,
WPARAM wParam,
LPARAM lParam
)
{

switch (message)
{

case WM_INITDIALOG:
CheckRadioButton (hDlg, ID_FORMAT2, ID_FORMAT3, iFontFormat);
break;

case WM_COMMAND:

switch (LOWORD(wParam))
{
case IDOK:

EndDialog (hDlg, TRUE);
break;

case IDCANCEL:
EndDialog (hDlg, FALSE);
break;

case ID_FORMAT2:
case ID_FORMAT3:
CheckRadioButton(hDlg, ID_FORMAT2,ID_FORMAT3,
iFontFormat = LOWORD(wParam));
break;

default:

return FALSE;

} /* end switch wparam */
break;

default:

return FALSE;

} /* end switch message */

return TRUE;

} /* end dlgsaveasdlg */


FONTEDIT.C

#define  NOVIRTUALKEYCODES

#include "windows.h"
#include <windowsx.h>
#include "fontedit.h"
#include "fcntl.h"
#include <stdlib.h>   // Causes redefinition warnings.
#include <string.h>

/****************************************************************************/
/*              Shared Variables (Home base)                                */
/****************************************************************************/

FARPROC lpHeaderProc;                  /* Pointer to Dialog Box Procedure */
FARPROC lpReSizeProc;                  /* Pointer to Dialog Box Procedure */
FARPROC lpWidthProc;                   /* Pointer to Dialog Box Procedure */

FontHeaderType font;                    /* Structure of Font File Header */
LONG lSizeOfOldFontHeader = 0;                  /* Old packed header size.              */
LONG lSizeOfOldFontHeader30 = 0;                /* Old 3.0 packed header size.  */
LONG lSizeOfOldGlyph20 = 0;             /* Old packed 2.0 glyph info size. */
LONG lSizeOfOldGlyph30 = 0;                     /* Old packed 3.0 glyph info size. */
extern CHAR szFontFile[];               /* Name of Font File */
extern CHAR szFontFileFull[];               /* Name of Font File */
extern CHAR szNewFile[];                /* Temporary Name */
extern BOOL NewFile;                    /* flag indicating if file was opened
                                           by selecting NEW menu item */
HCURSOR hCross;                         /* handle to a "+" shaped cursor */
HANDLE hInst;                           /* Handle to Window Instance */
HANDLE hgPrev;
INT  swH = 0;                           /* Position in Show Window 0-100 */
BYTE iChar = 65;                        /* Character being edited */
BYTE jChar = 65;                        /* Last Char. of edit block */
CHAR szFaceName[szNamesMax] = {""};     /* Face Name of Font */
DWORD offsets[257];                     /* Offsets Table */
CHAR *vrgsz[CSTRINGS];                  /* string table */

BOOL fChanged = FALSE;                  /* Note if we did anything */
BOOL fLoaded = FALSE;                   /* Set if a font is loaded */
BOOL fEdited = FALSE;                   /* Character was edited */
INT  iFontFormat;                       /* Set to id of current font format */
INT  iFontFormatPrev;                   /* Set to id of prev. font format */
BOOL fReadOnly;

HWND hBox = NULL;                       /* Handle to Edit Window */
HWND hFont = NULL;                      /* Handle to Show window */
HDC hMemDC = NULL;                      /* Handle to Memory Display Context */
HBITMAP hBitmap = NULL;                 /* Handle to our work bit map */

CHAR matBox [wBoxLim] [kBoxLim];        /* array to hold Box */
WORD wBox = 10;                 /* Width of Character(s) */
WORD kBox = 16;                 /* Height of Characters */
DWORD kStuff;                   /* Height of font window caption etc. */

OFSTRUCT ofstrFile;             /* holds info of file - filled in by dlgopen/save */
//WORD     fp = NULL;             /* global fp of current file - " " " " " " */

/*** string arrays for using stringtable ***/
CHAR    szAppName[MAX_STR_LEN];
CHAR    szIFN[MAX_STR_LEN];         /* illegal filename */
CHAR    szFNF[MAX_STR_LEN];         /* file not found   */
CHAR    szREF[MAX_STR_LEN];         /* replace existing file */
CHAR    szSCC[MAX_STR_LEN];         /* save current changes */
CHAR    szEOF[MAX_STR_LEN];         /* error opening file */
CHAR    szECF[MAX_STR_LEN];         /* error creating file */
CHAR    szFRO[MAX_STR_LEN];         /* error creating file */
CHAR    szExt[MAX_STR_LEN];         /* .FNT */
CHAR    szExtDesc[MAX_STR_LEN];     /* Font Files(.FNT) */
CHAR    szNEWFONT[MAX_STR_LEN];     /* Font Files(.FNT) */
CHAR    szFilter[MAX_STR_LEN];     /* Font Files(.FNT) */

/* size of system font */
INT     cSysWidth;
INT     cSysHeight;

/****************************************************************************/
/*              Local Variables                                             */
/****************************************************************************/

BYTE FindMouse();               /* Find where mouse is */

HBRUSH hbrWhite;
HBRUSH hbrBlack;
HBRUSH hbrGray;
HBRUSH hbrDkGray;
HBRUSH hbrBackGround;

BYTE downChar;                          /* Where mouse was pressed */
BYTE lastChar;                          /* Where mouse was last */
WORD wBoxOld;                           /* So we can catch any changes */
RECT rectWin;                           /* Client Rectangle */
LONG origin;                           /* Position in Show Window pixels */
BOOL fFirstShow = TRUE;                /* first time Show Window displayed? */

/****************************************************************************/
/*              Local Functions                                             */
/****************************************************************************/

VOID InvertFont(HDC, BYTE, BYTE);
BOOL FontEditInit(HANDLE);
VOID InitSizes(VOID);

/*****************************************************************************
 * FontShowHorzScroll(hFont, code, posNew)
 *
 * purpose: intepret scroll message for tiny window and call scroll funtion
 *          accordingly
 *
 * params: HWND hFont : handle to tiny window
 *         int code   : scroll message
 *         int posNew : thumb position
 * returns: none
 *
 * side effects: scroll position variable altered
 *
 ****************************************************************************/
VOID
FontShowHorzScroll(
    HWND hFont,
    INT code,
    INT posNew
        )
{
    WORD   wChar;
    RECT   rect;

    /* Get dimensions */
    GetClientRect(hFont, (LPRECT)&rect);

    if (font.AvgWidth == 0 ) { // for Japan
        font.AvgWidth = 1 ;
        }
    /* Make a fair guess as to how many characters are in the window */
    /* Convert sorta to hex too. */
    wChar=(WORD)(11*(rect.right-rect.left)/(font.AvgWidth*16));

    switch (code)
        {
        case SB_LINEUP:     /* line left */
            swH -= 1;
            break;
        case SB_LINEDOWN:   /* line right */
            swH += 1;
            break;
        case SB_PAGEUP:     /* page left */
            swH -= wChar;
            break;
        case SB_PAGEDOWN:   /* page right */
            swH += wChar;
            break;
        case SB_THUMBPOSITION:
            swH = posNew;
            break;
        case SB_THUMBTRACK:
            return;
    }
    ScrollFont();
}

/*****************************************************************************
 * FontShowPaint(hDC)
 *
 * purpose: repaint the little window at the bottom
 *
 * params:  HDC hDC : handle to display context.
 *
 * returns: none
 *
 * side effects: some scroll globals altered.
 *
 ****************************************************************************/

VOID
FontShowPaint(
        HDC hDC
        )
{
        DWORD range, nBits;
        INT nx;

    nx=GetSystemMetrics(SM_CXBORDER);
    /* 320 --> 300 */
    nBits = min(300, rectWin.right - rectWin.left-nx-nx) - 24;
        /* Window width (pixels) */
    /* 320 --> 300 */
    range = max(0, (font.WidthBytes << 3) - min(300, nBits));
    origin = (swH * range) / 100;               /* Global variable */
    BitBlt(hDC, 12, 2, nBits, font.PixHeight,
           hMemDC, origin, 0, NOTSRCCOPY);
    /* Now highlight the current value of iChar */
    InvertFont(hDC, iChar, jChar);
}

/*****************************************************************************
 * MouseInFont(ptMouse)
 *
 * purpose: Select new char in tiny Show window by inverting current char
 *          and un-inverting last one
 *
 * params:  POINT ptMouse  : mouse coordinates
 *
 * returns: none
 *
 * side effects: pixels inverted(uninverted) in bitmap.
 *
 ****************************************************************************/
VOID
MouseInFont(
        POINT ptMouse                    /*  .. to get new iChar */
        )
{
        HDC hDC;

    BoxToChar(iChar);           /* Replace edited character in Box if changed */
    hDC = GetDC(hFont);
    /* UnInvert present inverted region */
    InvertFont(hDC, iChar, jChar);
    /* Find where mouse is */
    lastChar = downChar = iChar = jChar = FindMouse(ptMouse);
    /* Invert Character touched */
    InvertFont(hDC, iChar, jChar);
    ReleaseDC(hFont, hDC);
}

/*****************************************************************************
 *
 * purpose:
 *
 * params:
 *
 * returns:
 *
 * side effects:
 *
 ****************************************************************************/
VOID
MouseMoveFont(
        POINT ptMouse
        )
        /* Mouse raised in font */
        /*  .. to get new jChar */
{
        UNREFERENCED_PARAMETER(ptMouse);
    return;     /********* NOT YET IN USE !! ********/

#if 0
        BYTE newChar;
        HDC hDC;

    newChar = FindMouse(ptMouse);               /* Find where mouse is */
    if (newChar == lastChar)            /* Did we move ? */
        return;                         /* No: return */
    lastChar = newChar;
    hDC = GetDC(hFont);
    if (newChar > jChar)
        {
        InvertFont(hDC, jChar + 1, newChar);    /* extend jChar */
        jChar = newChar;
        }
    else if (newChar < iChar)           /* iChar & jChar switch */
        {
        InvertFont(hDC, newChar, iChar - 1);
        iChar = newChar;
        }
    else
        {
        if (newChar >= downChar)        /* width reduced on right */
            {
            InvertFont(hDC, newChar + 1, jChar);
            jChar = newChar;
            }
        if (newChar <= downChar)        /* width reduced on left */
            {
            InvertFont(hDC, iChar, newChar - 1);
            iChar = newChar;
            }
        }
    ReleaseDC(hFont, hDC);
#endif
}

/*****************************************************************************
 * InvertFont(hDC, iChar, jChar)
 *
 * purpose: inverts color on selected chars (iChar thro' jChar) in the Show
 *          Window
 *
 * params:  HDC hDC    : handle to display context
 *          char iChar : start char
 *          char jChar : end char
 *
 * returns: none
 *
 * side effects: changes pixel values in bitmap
 *
 ****************************************************************************/
VOID
InvertFont(
        HDC hDC,
        BYTE iChar,
        BYTE jChar
        )
{
    PatBlt(hDC,                         /* Use Blt to Invert it */
        12 + offsets[iChar] - origin,   /* X: Position */
        2,                              /* Y: Allow for top band */
        offsets[jChar + 1] - offsets[iChar],    /* dx: Compute width */
        font.PixHeight,                 /* dy: Look up the Height */
        DSTINVERT);                     /* Z: Tell it to invert it */
}

/*****************************************************************************
 * MouseOutFont(ptMouse)
 *
 * purpose: brings selected char to edit box and sets up tiny window for
 *          repainting
 *
 * params:  POINT ptMouse : mouse location
 *
 * returns: none
 *
 * side effects: changes matBox(2-d array containing ready pixel info. on
 *               char being edited)
 *
 ****************************************************************************/
VOID
MouseOutFont(
        POINT ptMouse
        )
        /* Mouse raised in font */
        /*  .. to get new jChar */
{
    MouseMoveFont(ptMouse);     /* Check on iChar and jChar */
    CharToBox(iChar);
    InvalidateRect(hFont, (LPRECT)NULL, TRUE);  /* Cause repaint */
}

/*****************************************************************************
 * BYTE FindMouse(ptMouse)
 *
 * purpose: Locate number of character(in tiny window at bottom) under mouse
 *
 * params:  POINT ptMouse : mouse location
 *
 * returns: number of character
 *
 * side effects: alters scroll position variable
 *
 ****************************************************************************/

BYTE
FindMouse(
        POINT ptMouse
        )
{
        int  iChar;
        LONG   x;

    x = ptMouse.x + origin - 12;        /* Horizontal position of mouse */
    if (x < 0)
        iChar = font.FirstChar;
    else
        {
        iChar = x / font.AvgWidth + font.FirstChar;
        /* Right if Fixed Pitch -- Best Guess if variable width */
        if (!font.PixWidth)                     /* Scan for new iChar */
            {
            if (iChar > (int)font.LastChar)
                   iChar = (int)font.LastChar;  /* Don't overshoot */

            while (iChar < (int)font.LastChar && offsets[iChar] < (DWORD)x)
                iChar++;
            while (iChar > (int)font.FirstChar && offsets[iChar] > (DWORD)x)
                iChar--;
            }
        }

    /* Bug fix: prevent nil character from showing if mouse is pressed way
       over on right side of font scroll with narrow fixed fonts. */
    if (iChar > (int)font.LastChar)
        iChar = (int)font.LastChar;  /* Don't overshoot */

    if (iChar < (int)font.FirstChar)
        iChar = (int)font.FirstChar;  /* Don't undershoot */

    if (ptMouse.x <= 12L)
        {
        swH -= 1;
        ScrollFont();
        }
    if (ptMouse.x > 308L)
        {
        swH += 1;
        ScrollFont();
        }
    return (BYTE)iChar;
}

/*****************************************************************************
 * ScrollFont()
 *
 * purpose: scrolls tiny window at bottom
 *
 * params:  none
 *
 * returns: none
 *
 * side effects: alters scroll position variable
 *
 ****************************************************************************/
VOID
ScrollFont(
        VOID
        )
{
        HDC hDC;

    swH = max(0, swH);
    swH = min(100, swH);        /* maintain 0 - 100 range */
    SetScrollPos(hFont, SB_HORZ, swH, TRUE);     /* Move thumb */
    hDC = GetDC(hFont);
    FontShowPaint(hDC);
    ReleaseDC(hFont, hDC);
}

/*****************************************************************************
 * BoxToChar(iChar)
 *
 * purpose: sets pixels in bitmap according to matBox(2-d array containing
 *          ready pixel info. on char being edited)
 *
 * params:  BYTE iChar : index of char in bitmap offset array(offsets)
 *
 * returns: none
 *
 * side effects: changes font bitmap
 *
 ****************************************************************************/
VOID
BoxToChar(
        BYTE iChar
        )
{
        DWORD x, y, offset;

    if (!fEdited)
        return;

    if (wBox != wBoxOld)                /* I.e if width has changed */
        CharWidth(iChar, wBox);         /* .. go fix it */

    offset = offsets[iChar];
    for (x = 0; x < wBox; x++)
        {
        for (y = 0; y < kBox; y++)
            SetPixel(hMemDC, offset + x, y,
                matBox[x] [y] == TRUE ? WHITE : BLACK);
        }
    fEdited = FALSE;
}

/*****************************************************************************
 * CharToBox(iChar)
 *
 * purpose: assigns matBox(2-d array containing ready pixel info. on char being
 *          edited) according to pixels in portion of bitmap corresp. to char.
 *
 * params:  BYTE iChar : index of char in bitmap offset array(offsets)
 *
 * returns: none
 *
 * side effects: changes matBox
 *
 ****************************************************************************/
VOID
CharToBox(
        BYTE iChar
        )
{
        DWORD x, y, offset;
        HMENU hMenu;

    ClearBox();
    offset = offsets[iChar];
    wBox = wBoxOld = (WORD) (offsets[iChar + 1] - offset); /* Edit Box width */
    kBox = font.PixHeight;                              /* Edit Box Height */
    for (x = 0; x < wBox; x++)
        for (y = 0; y < kBox; y++)
            matBox[x][y] = (BYTE) (GetPixel(hMemDC, offset + x, y) ? 1 : 0);
    InvalidateRect(hBox, (LPRECT)NULL, TRUE);
    fEdited = FALSE;            /* Not Changed Yet */
    hMenu = GetMenu(hBox);
    EnableMenuItem(hMenu, BOX_UNDO, MF_GRAYED);
    EnableMenuItem(hMenu, BOX_REFRESH, MF_GRAYED);
}

/*****************************************************************************/
/*              WinMain  and  Friends                                        */
/*****************************************************************************/

/* Procedure called every time a new instance of the application
** is created */

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, INT cmdShow)
{
        MSG    msg;
        WORD   message;
        INT    nx;
        HANDLE hAccel;

    hInst = hInstance;
    hgPrev=hPrevInstance;

        if (!FontEditInit( hInstance ))
            return FALSE;

    LoadString(hInstance, IDS_APPNAME, szAppName, MAX_STR_LEN);
    LoadString(hInstance, IDS_IFN, szIFN, MAX_STR_LEN); /* illegal filename */
    LoadString(hInstance, IDS_FNF, szFNF, MAX_STR_LEN); /* file not found   */
    LoadString(hInstance, IDS_REF, szREF, MAX_STR_LEN);
                        /* replace existing file */
    LoadString(hInstance, IDS_SCC, szSCC, MAX_STR_LEN);
                        /* save current changes */
    LoadString(hInstance, IDS_EOF, szEOF, MAX_STR_LEN); /* error opening file */
    LoadString(hInstance, IDS_ECF, szECF, MAX_STR_LEN);
        // File is read only.
    LoadString(hInstance, IDS_FRO, szFRO, MAX_STR_LEN);
                        /* error creating file */
    LoadString(hInstance, IDS_EXT, szExt, MAX_STR_LEN); /* default file ext. */

        // File extension description for common dialog.
    LoadString(hInstance, IDS_EXTDESC, szExtDesc, MAX_STR_LEN);
        // Message for new font warning box.
    LoadString(hInstance, IDS_NEW_FONT, szNEWFONT, MAX_STR_LEN);
    memset(szFilter, 0, MAX_STR_LEN);
    LoadString(hInstance, IDS_EXTDESC, szFilter, MAX_STR_LEN);
    LoadString(hInstance, IDS_STARDOTFNT, szFilter+strlen(szFilter)+1,
                                MAX_STR_LEN-(strlen(szFilter)+1));

    ClearBox();

        /* Create a window instance of class "FontEdit" */

    hBox = CreateWindow((LPSTR) vszFontEdit,
                      (LPSTR)szAppName,
                      WS_TILEDWINDOW,
                      56,34,
                      GetSystemMetrics(SM_CXFULLSCREEN)/2,
                      GetSystemMetrics(SM_CYFULLSCREEN)/2,
                      (HWND)NULL,        /* no parent */
                      (HMENU)NULL,       /* use class menu */
                      (HANDLE)hInstance, /* handle to window instance */
                      (LPSTR)NULL        /* no params to pass on */
                      );

    ShowWindow(hBox, cmdShow);
    UpdateWindow(hBox);
    InitSizes();     /* 11/21/86 - linsh - get system char width & height */

    GetWindowRect(hBox, (LPRECT)&rectWin);
    nx=GetSystemMetrics(SM_CXBORDER);
    hFont = CreateWindow((LPSTR) vszFontShow,
                       (LPSTR) "",
                       WS_BORDER|WS_HSCROLL|WS_CAPTION,
                       rectWin.left,
                       rectWin.bottom-56,
                       min(300, rectWin.right - rectWin.left-nx-nx),
                       50 - GetSystemMetrics(SM_CYBORDER),
                       (HWND)hBox,
                       (HMENU)NULL,
                       (HANDLE)hInstance,
                       (LPSTR)NULL
                       );

    /* Get address of Dialog Box procedure */
    lpHeaderProc = MakeProcInstance((FARPROC)HeaderProc, hInstance);
    lpReSizeProc = MakeProcInstance((FARPROC)ReSizeProc, hInstance);
    lpWidthProc = MakeProcInstance((FARPROC)WidthProc, hInstance);

    /* Start it loading if it's not iconic */
    if (!IsIconic(hBox))
        {
        if (lpszCmdLine[0])     /* If we have a font name use it */
            {
            BOOL   fDot;
            INT   i;

            /* Copy the specified file and make it upper case */
            lstrcpy((LPSTR)szFontFile, CharUpper((LPSTR)lpszCmdLine));

            fDot = FALSE;
            nx=lstrlen((LPSTR)szFontFile);

            for (i = 0; i < nx; i++)
                {
                if (szFontFile[i] == '.')       /* Add .FNT if none */
                    fDot = TRUE;

                if (szFontFile[i] == ' ')
                    szFontFile[i]=0;
                }

            if (!fDot)
                lstrcat((LPSTR)szFontFile, (LPSTR)vszDotFNT);

            lstrcpy((LPSTR)szNewFile, (LPSTR)szFontFile);

            /* Do this thing that someone forgot.  THe dialog open does it
               and affects the Save As initialization. */
            (HFILE)OpenFile((LPSTR)szNewFile, &ofstrFile, OF_READ);

            message = FONT_START;
            }
        else            /* Start by doing a regular file load */
            message = FONT_LOAD;

        SetFocus(hBox);
        PostMessage(hBox, WM_COMMAND, message, (LONG)0);
        }

    if (!fLoaded)
        ShowWindow(hFont, SW_HIDE);

    hAccel=LoadAccelerators(hInstance, "FE");

    /* Quit message will terminate application */
    while (GetMessage((LPMSG)&msg, NULL, 0, 0))
        {
        if (!TranslateAccelerator (hBox, hAccel, &msg))
            {
            TranslateMessage((LPMSG)&msg);
            DispatchMessage((LPMSG)&msg);
            }
        }

    return msg.wParam;
}

/*****************************************************************************
 * int FontEditInit( hInstance )
 *
 * purpose: Initialises a whole lot of stuff
 *
 * params:  HANDLE hInstance : handle to instance of application
 *
 * returns:     TRUE : if everything goes off well
 *             FALSE : error in initialisation
 *
 * side effects: a whole lot
 *
 ****************************************************************************/

/* Procedure called when the application is loaded */
BOOL
FontEditInit(
        HANDLE hInstance
        )
{
    INT cch;          /* all variables used to load srings from .RC file */
    INT cchRemaining;
    CHAR *pch;
    HANDLE hStrings;
    INT i;
    CHAR ColorStr[CCHCOLORSTRING]; /* background color inf. fronm win.ini */
    BYTE bR, bG, bB;  /* the three primary color values ret. from win.ini */
    // CHAR cDummy;      /* dummy char to hold the " " char */
    PWNDCLASS   pFontEditClass;         /* Edit Box window class */
    PWNDCLASS   pFontShowClass;         /* Show Font window class */

        //
        // Initialize the conversion routines to read in the old font structure
        // off of disk.
        //

        if (fConvStructInit () == FALSE) {

                return FALSE;
        }

    /* load strings from resource file */
    if (!(pch = (CHAR *)(hStrings =
                LocalAlloc (LPTR, cchRemaining = CCHSTRINGSMAX))))
          return FALSE;        /* unable to alloc. Try increasing initial
                                  heapsize in .DEf file */
    for (i=0; i< CSTRINGS; i++){
        cch = 1+LoadString (hInstance, (WORD) i, (LPTSTR)pch, cchRemaining);
        if (cch < 2){
            return FALSE;
                }
        vrgsz[i] = pch;
        pch += cch;
        if ((cchRemaining -= cch ) <= 0)
            return FALSE;
    }

    /* Allocate class structure in local heap */
    pFontEditClass = (PWNDCLASS)LocalAlloc( LPTR, sizeof(WNDCLASS) );
    if (pFontEditClass == NULL)
        return FALSE;

    /* set up some default brushes */
    hbrWhite = GetStockObject( WHITE_BRUSH );
    hbrBlack = GetStockObject( BLACK_BRUSH );
    hbrGray  = GetStockObject( GRAY_BRUSH );
    hbrDkGray  = GetStockObject( DKGRAY_BRUSH );

    /* get App. Workspace color from win.ini - LR */
    if (GetProfileString(vszcolors, vszAppWorkspace, "",ColorStr,
                                                       CCHCOLORSTRING)){
        bR = (BYTE)atoi((const char *)strtok (ColorStr, " "));
        bG = (BYTE)atoi((const char *)strtok (NULL, " "));
        bB = (BYTE)atoi((const char *)strtok (NULL, "\n"));
        hbrBackGround = CreateSolidBrush(RGB(bR, bG, bB));
    }
    else  /* set to lt. blue background */
        hbrBackGround = CreateSolidBrush((LONG)0x00FF8000);

    /* get necessary resources */
    pFontEditClass->hCursor  = LoadCursor( NULL, IDC_ARROW);
    pFontEditClass->hIcon    = LoadIcon( hInstance,(LPSTR)vszFontEdit);
    pFontEditClass->lpszMenuName = (LPSTR)vszFontEdit;
    pFontEditClass->lpszClassName = (LPSTR)vszFontEdit;
    pFontEditClass->hbrBackground = hbrBackGround;
    pFontEditClass->hInstance     = hInstance;

    pFontEditClass->style = CS_VREDRAW | CS_HREDRAW;

    /* Register our Window Proc */
    pFontEditClass->lpfnWndProc = (WNDPROC)FontEditWndProc;

    /* register this new class with WINDOWS */
    if (!hgPrev)
        if (!RegisterClass( (LPWNDCLASS)pFontEditClass ) )
            return FALSE;   /* Initialization failed */

    /* Now repeat the performance for the Show window */

    pFontShowClass = pFontEditClass;
    /* get necessary resources */
    pFontShowClass->hCursor  = LoadCursor(NULL,IDC_ARROW);
    pFontShowClass->hIcon    = (HICON)NULL;
    pFontShowClass->lpszMenuName = NULL;
    pFontShowClass->lpszClassName = (LPSTR)vszFontShow;
    pFontShowClass->hbrBackground = hbrGray;
    pFontShowClass->hInstance     = hInstance;
    pFontShowClass->style = CS_VREDRAW | CS_HREDRAW;

    /* Register our Window Proc */
    pFontShowClass->lpfnWndProc = (WNDPROC)FontShowWndProc;

    /* register this new class with WINDOWS */
    if (!hgPrev)
        if (!RegisterClass( (LPWNDCLASS)pFontShowClass ) )
            return FALSE;   /* Initialization failed */

    LocalFree( (HANDLE)pFontShowClass );

    return TRUE;    /* Initialization succeeded */
}

/*****************************************************************************
 * InitSizes()
 *
 * purpose: gets system char width and height
 *
 * params:  none
 *
 * returns: none
 *
 * side effects: sets width and height globals
 *
 ****************************************************************************/
VOID
InitSizes(
        VOID
        )
{
    HDC hDC;
    TEXTMETRIC tm;

    hDC = GetDC(hFont);
    GetTextMetrics(hDC, (LPTEXTMETRIC) &tm);
    cSysHeight = tm.tmHeight + tm.tmExternalLeading;;
    cSysWidth  = tm.tmAveCharWidth;
    ReleaseDC(hFont, hDC);
}

/*****************************************************************************
 * long  APIENTRY FontShowWndProc(hFont, message, wParam, lParam)
 *
 * purpose: Window function for tiny window at the bottom (showing font chars)
 *          Independently processes paint, scroll, keyboard and mouse
 *          messages for the window
 *
 * params:  as for window functions
 *
 * side effects: lots
 *
 ****************************************************************************/

/* Procedures which make up the window class. */
LONG  APIENTRY
FontShowWndProc(
        HWND   hFont,
        WORD   message,
        WPARAM wParam,
        LPARAM lParam
        )
{
        PAINTSTRUCT  ps;
        // RECT         rectWin;
        POINT        pt;

        ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);

    switch (message)
    {

        case WM_SIZE :
            switch (wParam){
                /* if main window is iconised, hide the child window */
                case SIZEICONIC :
                    ShowWindow(hFont, SW_HIDE);
                    break;
                default:
                    return(DefWindowProc(hFont, message, wParam, lParam));
                    break;
            }
            break;

        case WM_PAINT:
            /* Time for the window to draw itself. */
            BeginPaint(hFont, (LPPAINTSTRUCT)&ps);
            FontShowPaint(ps.hdc);
            EndPaint(hFont, (LPPAINTSTRUCT)&ps);
            break;

        /* For each of following mouse window messages, wParam contains
        ** bits indicating whether or not various virtual keys are down,
        ** and lParam is a POINT containing the mouse coordinates.   The
        ** keydown bits of wParam are:  MK_LBUTTON (set if Left Button is
        ** down); MK_RBUTTON (set if Right Button is down); MK_SHIFT (set
        ** if Shift Key is down); MK_ALTERNATE (set if Alt Key is down);
        ** and MK_CONTROL (set if Control Key is down). */

        case WM_LBUTTONDOWN:
            MouseInFont(pt);              /* .. set iChar */
            break;
        case WM_LBUTTONUP:
            MouseOutFont(pt);     /* .. set jChar */
            break;
        case WM_RBUTTONDOWN:
        case WM_RBUTTONUP:
        case WM_MOUSEMOVE:                      /* If mouse is down */
            if (wParam & MK_LBUTTON)
                MouseMoveFont(pt);        /* .. set jChar */

break;
        case WM_LBUTTONDBLCLK:
        case WM_RBUTTONDBLCLK:
            break;

        case WM_HSCROLL:
            /* wParam contains the scroll code.
            ** For the thumb movement codes, the low
            ** word of lParam contain the new scroll position.
            ** Possible values for wParam are: SB_LINEUP, SB_LINEDOWN,
            ** SB_PAGEUP, SB_PAGEDOWN, SB_THUMBPOSITION, SB_THUMBTRACK */
            /* Horizontal scroll bar input.  Parameters same as for
            ** WM_HSCROLL.  UP and DOWN should be interpreted as LEFT
            ** and RIGHT, respectively. */
            FontShowHorzScroll(hFont, LOWORD(wParam),
                                HIWORD(wParam));
            break;
        case WM_CLOSE:
            break;   /* don't allow this window to be closed before
                        bigger one */

        default:

            /* Everything else comes here.  This call MUST exist
            ** in your window proc.  */

            return(DefWindowProc(hFont, message, wParam, lParam));
            break;

    }

    /* A window proc should always return something */
    return(0L);
}

VOID
FontRename(
        CHAR * szError
        )
{
    CHAR *szTitle[MAX_STR_LEN+MAX_FNAME_LEN];

    if (szError[0])
        ErrorBox(hBox, szError);
    else
        {
        lstrcpy((LPSTR)szFontFileFull, (LPSTR)szNewFile);
        lstrcpy((LPSTR)szTitle, (LPSTR)szAppName);
        lstrcat((LPSTR)szTitle, (LPSTR)vszBlankDashBlank);
        lstrcat((LPSTR)szTitle, (LPSTR)szFontFile);
        SetWindowText(hBox, (LPSTR)szTitle);
        }
}

/*****************************************************************************
 * ResizeShow()
 *
 * purpose:   resize tiny window at bottom (showing font chars) in proportion
 *            to large window
 *
 * params:    none
 *
 * returns:   none
 *
 * side effects:
 *
 ****************************************************************************/

VOID
ResizeShow(
        VOID
        )
{
        INT    height;
        INT    nx;

    /* size message for hbox goes thru before hfont created. 06-Jul-1987. */
    if (!hFont)
        return;

    GetWindowRect(hBox, (LPRECT)&rectWin);
    kStuff = GetkStuff();               /* For check on second pass */

#if 0
    height = font.PixHeight + kStuff + 20;  /* 4 Height we want box */
#else
    height = font.PixHeight + GetSystemMetrics(SM_CYHSCROLL)
                            + GetSystemMetrics(SM_CYCAPTION)
                            + 4*GetSystemMetrics(SM_CYBORDER)/*20*/;     /* 4*/
#endif

     nx=GetSystemMetrics(SM_CXBORDER);
     MoveWindow(hFont,
                rectWin.left +nx+4,
                rectWin.bottom - (height + GetSystemMetrics(SM_CYBORDER)+2),
                min(300, rectWin.right - rectWin.left-nx-nx),
                height,
                TRUE);
    if (!IsWindowVisible(hFont) && fLoaded)
        ShowWindow(hFont, SW_SHOW);
}

DWORD
GetkStuff(
        VOID
        )     /* Get size of menu, scrollbar etc. */
{
        RECT rect, rectWin;

    /* size message for hbox goes thru before hfont created. 06-Jul-1987. */
    if (!hFont)
        return 0;

    GetClientRect(hFont, (LPRECT)&rect);     /* How much do WE get? */
    GetWindowRect(hFont, (LPRECT)&rectWin);
    return (DWORD) (rectWin.bottom - rectWin.top) - (rect.bottom - rect.top);
}


FONTEDIT.H

#include <stdlib.h>

#define   WIDER_LEFT 1
#define   WIDER_RIGHT 2
#define   WIDER_BOTH 3
#define   NARROWER_LEFT 4
#define   NARROWER_RIGHT 5
#define   NARROWER_BOTH 6
#define   WIDTH 7
#define   BOX_WIDTH 8

#define   BOX_REFRESH 10                /* Restore char to original form */
#define   BOX_CLEAR 11
#define   BOX_FILL 12
#define   BOX_INV 13
#define   BOX_HATCH 14
#define   BOX_UNDO 15                   /* Undo last change */
#define   BOX_LEFTRIGHT 16              /* Flip left to right */
#define   BOX_TOPBOTTOM 17              /* Flip top over bottom */
#define   BOX_COPY 18
#define   BOX_PASTE 19

#define   ROW_ADD 21
#define   ROW_DEL 22

#define   COL_ADD 31
#define   COL_DEL 32

#define   ID_EDIT 34
#define   ID_PATH 35
#define   ID_LISTBOX 36
#define   ID_FACE_NAME 37
#define   ID_FONT_NAME 38
#define   ID_COPYRIGHT 39

#define   FONT_START 40                 /* Generated if win fontedit arg. */

#define   FONT_LOAD 41                  /* Codes for Font Window */
#define   FONT_SAVE 42
#define   FONT_HEADER 43
#define   FONT_COPY 44
#define   FONT_PASTE 45                 /* Paste in without rescaling */
#define   FONT_ABOUT 46                 /* Display the about box */
#define   FONT_RESIZE 47                /* Go to Resize Dialog Box */
#define   FONT_NEW 95                   /* Menu id: create new font */

#define   ID_FIRST_CHAR 48              /* First character in the font */
#define   ID_LAST_CHAR 49               /* Last character in the font */

#define   ID_POINTS 50                  /* Nominal Point Size */
#define   ID_VERT_RES 51                /* Nominal Vertical Resolution */
#define   ID_HORIZ_RES 52               /* Nominal Horizontal Resolution */
#define   ID_ASCENT 53                  /* Height of Ascent */
#define   ID_EXT_LEADING 54             /* Height of External Leading */
#define   ID_INT_LEADING 55             /* Height of Internal Leading */

#define   FONT_EXIT 56                  /* exit the font editor */

#define   ID_ITALIC 60                  /* Flag for Italic Fonts */
#define   ID_UNDERLINE 61               /* Flag for Underlined Fonts */
#define   ID_STRIKEOUT 62               /* Flag for Struckout Fonts */
#define   ID_ANSI 63                    /* 0 = ANSI, 255 = other */
#define   ID_OEM 64
#define   ID_CHAR_SET 65
#define   ID_DEFAULT_CHAR 66            /* Default for undefined Chars. */
#define   ID_BREAK_CHAR 67
#define   ID_SYMBOL 68

#define   ID_UNKNOWN 70
#define   ID_ROMAN 71
#define   ID_SWISS 72                   /* Pitch Families */
#define   ID_MODERN 73
#define   ID_SCRIPT 74
#define   ID_DECORATIVE 75

#define   ID_PIX_HEIGHT 80              /* Height of Characters */
#define   ID_WIDTH 81                   /* Fixed or Maximum Width */
#define   ID_WIDTH_TEXT 82              /* Caption for above */
#define   ID_AVERAGE 83                 /* Average Character Width */
#define   ID_FIXED 84                   /* Fixed Width Font */
#define   ID_VARIABLE 85                /* Variable width font */

#define   ID_THIN 86
#define   ID_EXTRALIGHT 87
#define   ID_LIGHT 88
#define   ID_NORMAL 89
#define   ID_MEDIUM 90
#define   ID_SEMIBOLD 91
#define   ID_BOLD 92
#define   ID_EXTRABOLD 93
#define   ID_HEAVY 94
#define   ID_BOXOPEN 96                /* dialog box frame id for File Open */
#define   FONT_SAVEAS 97

#define   ID_FORMAT2 102                /* Windows 2.0 Font Format */
#define   ID_FORMAT3 103                /* Windows 3.0 Font Format */

/* string IDS for strings in resource file - LR */
#define IDS_NOMPTSIZENOTOK          0
#define IDS_NOMVERTRESNOTOK         1
#define IDS_NOMHORRESNOTOK          2
#define IDS_ASCENTTOOBIG            3
#define IDS_ASCENTNOTOK             4
#define IDS_EXTLEADNOTOK            5
#define IDS_INTLEADTOOBIG           6
#define IDS_INTLEADNOTOK            7
#define IDS_CHARSETOUTOFBOUNDS      8
#define IDS_DEFCHAROUTSIDEFONT      9
#define IDS_DEFCHARNOTOK           10
#define IDS_BREAKCHAROUTSIDEFONT   11
#define IDS_BREAKCHARNOTOK         12
#define IDS_UNKNOWNFACE            13
#define IDS_NOVARTOFIXCHANGE       14
#define IDS_TOOBIGFOR20            15
#define IDS_HEIGHTOUTOFBOUNDS      16
#define IDS_WIDTHOUTOFBOUNDS       17
#define IDS_CHAR1MORETHANDCHAR     18
#define IDS_CHAR1NOTOK             19
#define IDS_LASTCHARTOOSMALL       20
#define IDS_LASTCHARNOTOK          21
#define IDS_CANNOTOPENCLIP         22
#define IDS_COPYINGTOCLIP          23
#define IDS_NOTENOUGHMEM           24
#define IDS_ALLOCATINGSPACE        25
#define IDS_FONTEDIT               26
#define IDS_FONTSHOW               27
#define IDS_DOTFNT                 28
#define IDS_BLANKDASHBLANK         29
#define IDS_ERROROPENINGFILE       30
#define IDS_ERRORREADINGHDR        31
#define IDS_UNKNOWNFORMAT          32
#define IDS_ERRORREADINGBODY       33
#define IDS_FILETOOLARGE           34
#define IDS_ERRORWRITINGHDR        35
#define IDS_ERRORWRITINGOFFSETS    36
#define IDS_ERRORWRITINGBODY       37
#define IDS_STARDOTFNT             38
#define IDS_UNKNOWN                39
#define IDS_MAXWIDTH               40
#define IDS_CHARPIXELWIDTH         41
#define IDS_ABOUT                  42
#define IDS_DHEADER                43
#define IDS_DRESIZE                44
#define IDS_DWIDTH                 45
#define IDS_EDLIMITS0TO64          46
#define IDS_MAXWIDTHINCREASE       47
#define IDS_CANNOTCHANGEWIDTH      48
#define IDS_WARNING                49
#define IDS_INCORRECTPIXWIDTH      50
#define IDS_MAXWIDTHOUTOFBOUNDS    51
#define IDS_AVGWIDTHOUTOFBOUNDS    52
#define IDS_WIDTHBYTESNOTOK        53
#define IDS_BITSOFFSETNOTOK        54
#define IDS_TABLEWIDTHSBAD         55
#define IDS_TABLEOFFSETSBAD        56
#define IDS_COLOR                  57
#define IDS_APPWORKSPACE           58
#define IDS_FILEREADONLY           59

#define     IDS_APPNAME            60
#define     IDS_IFN                61
#define     IDS_FNF                62
#define     IDS_REF                63
#define     IDS_SCC                64
#define     IDS_EOF                65
#define     IDS_ECF                66
#define     IDS_FRO                67
#define     IDS_EXT                68
#define     IDS_EXTDESC            69
#define     IDS_NEW_FONT           70
#define     IDS_ERRORCLIP          71
#define     IDS_CHAR72
#define     IDS_WIDTH73
#define     IDS_HEIGHT74

#define CSTRINGS                   75   /* total number of strings in
                                           resource file */
#define CCHSTRINGSMAX              4096 /* total length of all strings
                                           loaded must be <= this */
#define CCHCOLORSTRING             15   /* size of App Workspace color inf.
                                           string loaded from win.ini */
#define ID_SHIFTJIS                76

/*****************************************************************************/
/*              Typedef's etc.                                               */
/*****************************************************************************/

// typedef unsigned char CHAR;  /* ... since we use these to index */

/* font file header (Adaptation Guide section 6.4) */
typedef struct {
    WORD        Version;              /* Always 17985 for the Nonce */
    DWORD       Size;                 /* Size of whole file */
    CHAR        Copyright[60];
    WORD        Type;                 /* Raster Font if Type & 1 == 0 */
    WORD        Points;               /* Nominal Point size */
    WORD        VertRes;              /* Nominal Vertical resolution */
    WORD        HorizRes;             /* Nominal Horizontal resolution */
    WORD        Ascent;               /* Height of Ascent */
    WORD        IntLeading;           /* Internal (Microsoft) Leading */
    WORD        ExtLeading;           /* External (Microsoft) Leading */
    BYTE        Italic;               /* Italic font if set */
    BYTE        Underline;            /* Etc. */
    BYTE        StrikeOut;            /* Etc. */
    WORD        Weight;               /* Weight: 200 = regular */
    BYTE        CharSet;              /* ANSI=0. other=255 */
    WORD        PixWidth;             /* Fixed width. 0 ==> Variable */
    WORD        PixHeight;            /* Fixed Height */
    BYTE        Family;               /* Pitch and Family */
    WORD        AvgWidth;             /* Width of character 'X' */
    WORD        MaxWidth;             /* Maximum width */
    BYTE        FirstChar;            /* First character defined in font */
    BYTE        LastChar;             /* Last character defined in font */
    BYTE        DefaultChar;          /* Sub. for out of range chars. */
    BYTE        BreakChar;            /* Word Break Character */
    WORD        WidthBytes;           /* No.Bytes/row of Bitmap */
    DWORD       Device;               /* Pointer to Device Name string */
    DWORD       Face;                   /* Pointer to Face Name String */
    DWORD       BitsPointer;            /* Pointer to Bit Map */
    DWORD       BitsOffset;             /* Offset to Bit Map */
} FontHeaderType;           /* Above pointers all rel. to start of file */

typedef struct {
    WORD        fsVersion;
    DWORD       fsSize;
    CHAR        fsCopyright[60];
    WORD        fsType;               /* Type field for the font              */
    WORD        fsPoints;             /* Point size of font                   */
    WORD        fsVertRes;            /* Vertical digitization                */
    WORD        fsHorizRes;           /* Horizontal digitization              */
    WORD        fsAscent;             /* Baseline offset from char cell top   */
    WORD        fsInternalLeading;    /* Internal leading included in font    */
    WORD        fsExternalLeading;    /* Prefered extra space between lines   */
    BYTE        fsItalic;             /* Flag specifying if italic            */
    BYTE        fsUnderline;          /* Flag specifying if underlined        */
    BYTE        fsStrikeOut;          /* Flag specifying if struck out        */
    WORD        fsWeight;             /* Weight of font                       */
    BYTE        fsCharSet;            /* Character set of font                */
    WORD        fsPixWidth;           /* Width field for the font             */
    WORD        fsPixHeight;          /* Height field for the font            */
    BYTE        fsPitchAndFamily;     /* Flag specifying pitch and family     */
    WORD        fsAvgWidth;           /* Average character width              */
    WORD        fsMaxWidth;           /* Maximum character width              */
    BYTE        fsFirstChar;          /* First character in the font          */
    BYTE        fsLastChar;           /* Last character in the font           */
    BYTE        fsDefaultChar;        /* Default character for out of range   */
    BYTE        fsBreakChar;          /* Character to define wordbreaks       */
    WORD        fsWidthBytes;         /* Number of bytes in each row          */
    DWORD       fsDevice;             /* Offset to device name                */
    DWORD       fsFace;               /* Offset to face name                  */
    DWORD       fsBitsPointer;        /* Bits pointer                         */
    DWORD       fsBitsOffset;         /* Offset to the begining of the bitmap */
BYTEfsDBfiller;  /* Word alignment for the offset table  */

    DWORD       fsFlags;              /* Bit flags                            */
    WORD        fsAspace;             /* Global A space, if any               */
    WORD        fsBspace;             /* Global B space, if any               */
    WORD        fsCspace;             /* Global C space, if any               */
    DWORD       fsColorPointer;       /* offset to color table, if any        */
    DWORD       fsReserved[4];        /*                                      */
    BYTE        fsCharOffset;         /* Area for storing the char. offsets   */

} FontHeader30;

#define   CCHEXTRA             31     /* no. of extra bytes in the 3.0 header */

//
// These are the old glyph info structures.  Since they are not DWORD aligned
// they must be converted to new structure types.
//

typedef struct{
     SHORT GIwidth;
     SHORT GIoffset;
} GLYPHINFO_20;

typedef struct{
     SHORT GIwidth;
     LONG GIoffset;
} GLYPHINFO_30;

#define ClipBoard TRUE

LONG  APIENTRY FontEditWndProc(HWND, WORD, WPARAM, LONG);
LONG  APIENTRY FontShowWndProc(HWND, WORD, WPARAM, LONG);
BOOL  APIENTRY
HeaderProc(
HWND   hDial,
WORD   message,
WPARAM wParam,
LPARAM lParam
);
BOOL APIENTRY
ReSizeProc(
HWND   hDial,
WORD   message,
WPARAM wParam,
LPARAM lParam
);
BOOL  APIENTRY
WidthProc(
HWND   hDial,
WORD   message,
WPARAM wParam,
LPARAM lParam
);

CHAR * FontLoad(CHAR *, OFSTRUCT *);
CHAR * FontSave(CHAR *, OFSTRUCT *);
BOOL ResizeBody();              /* Change Height of Font. MAde return type BOOL istead of VOID - LR */
BOOL ResizeWidths();            /* Change Widths (Fixed Width Fonts).MAde return type BOOL istead of VOID - LR  */
BOOL SpreadWidths(DWORD);            /* Change Widths (Var. Width Fonts). MAde return type BOOL istead of VOID - LR  */
VOID DeleteGlobalBitmap();
VOID ScrollFont();
VOID ExciseChars();             /* Delete part of a font */
BOOL
CharWidth(
    BYTE iChar,                             /* Character to change */
    DWORD wBox                               /* New width */
    );
BOOL NewLastChar(DWORD);             /* MAde return type BOOL istead of VOID - LR */
BOOL NewFirstChar(DWORD);            /* MAde return type BOOL istead of VOID - LR */
#define kBoxLim 65  /* was 49 */

#define wBoxLim 65
#define szNamesMax 32
#define BLACK (LONG)0
#define WHITE (LONG)0x00FFFFFF          /* For SetPixel rgbColor */
#define FILENAMEMAX _MAX_PATH   /* Maximum allowed length of file names */

/*********
 the following added 11-Sep-1986 to use dlgsave.c and dlgopen.c
**********/
#define   IDD_OPEN 1    /* dialog box ids */
#define   IDD_SAVE 2
#define   IDD_FORMAT 3

#define     MAX_STR_LEN     128
#define     MAX_FNAME_LEN   128
#define     CBEXTMAX        6   /* Number of bytes in "\*.ext" */

#define NOSAVE  0   /* return flags for DlgFnSave() */
#define NEWSAVE 1
#define OLDSAVE 2

#define NOOPEN  0   /* return flags for DlgFnOpen() */
#define NEWOPEN 1
#define OLDOPEN 2

#define WORD_LIMIT    65534    /* max. number of bytes that can be read(written) by */
#define SEGMENT_SIZE  65536    /* segment length in bytes */

/* abbreviations for accessing resource strings - LR */

#define vszNomPtSizeNotOk       vrgsz[IDS_NOMPTSIZENOTOK      ]
#define vszNomVertResNotOk      vrgsz[IDS_NOMVERTRESNOTOK     ]
#define vszNomHorResNotOk       vrgsz[IDS_NOMHORRESNOTOK      ]
#define vszAscentTooBig         vrgsz[IDS_ASCENTTOOBIG        ]
#define vszAscentNotOk          vrgsz[IDS_ASCENTNOTOK         ]
#define vszExtLeadNotOk         vrgsz[IDS_EXTLEADNOTOK        ]
#define vszIntLeadTooBig        vrgsz[IDS_INTLEADTOOBIG       ]
#define vszIntLeadNotOk         vrgsz[IDS_INTLEADNOTOK        ]
#define vszCharSetOutOfBounds   vrgsz[IDS_CHARSETOUTOFBOUNDS  ]
#define vszDefCharOutsideFont   vrgsz[IDS_DEFCHAROUTSIDEFONT  ]
#define vszDefCharNotOk         vrgsz[IDS_DEFCHARNOTOK        ]
#define vszBreakCharOutsideFont vrgsz[IDS_BREAKCHAROUTSIDEFONT]
#define vszBreakCharNotOk       vrgsz[IDS_BREAKCHARNOTOK      ]
#define vszUnknownFace          vrgsz[IDS_UNKNOWNFACE         ]
#define vszNoVarToFixChange     vrgsz[IDS_NOVARTOFIXCHANGE    ]
#define vszTooBigFor20          vrgsz[IDS_TOOBIGFOR20         ]
#define vszHeightOutOfBounds    vrgsz[IDS_HEIGHTOUTOFBOUNDS   ]
#define vszWidthOutOfBounds     vrgsz[IDS_WIDTHOUTOFBOUNDS    ]
#define vszChar1MoreThanDChar   vrgsz[IDS_CHAR1MORETHANDCHAR  ]
#define vszChar1NotOk           vrgsz[IDS_CHAR1NOTOK          ]
#define vszLastCharTooSmall     vrgsz[IDS_LASTCHARTOOSMALL    ]
#define vszLastCharNotOk        vrgsz[IDS_LASTCHARNOTOK       ]
#define vszCannotOpenClip       vrgsz[IDS_CANNOTOPENCLIP      ]
#define vszCopyingToClip        vrgsz[IDS_COPYINGTOCLIP       ]
#define vszNotEnoughMem         vrgsz[IDS_NOTENOUGHMEM        ]
#define vszAllocatingSpace      vrgsz[IDS_ALLOCATINGSPACE     ]
#define vszFontEdit             vrgsz[IDS_FONTEDIT            ]
#define vszFontShow             vrgsz[IDS_FONTSHOW            ]
#define vszDotFNT               vrgsz[IDS_DOTFNT              ]
#define vszBlankDashBlank       vrgsz[IDS_BLANKDASHBLANK      ]
#define vszErrorOpeningFile     vrgsz[IDS_ERROROPENINGFILE    ]
#define vszErrorReadingHdr      vrgsz[IDS_ERRORREADINGHDR     ]
#define vszUnknownFormat        vrgsz[IDS_UNKNOWNFORMAT       ]
#define vszErrorReadingBody     vrgsz[IDS_ERRORREADINGBODY    ]
#define vszFileTooLarge         vrgsz[IDS_FILETOOLARGE        ]
#define vszErrorWritingHdr      vrgsz[IDS_ERRORWRITINGHDR     ]
#define vszErrorWritingOffsets  vrgsz[IDS_ERRORWRITINGOFFSETS ]
#define vszErrorWritingBody     vrgsz[IDS_ERRORWRITINGBODY    ]
#define vszStarDotFNT           vrgsz[IDS_STARDOTFNT          ]
#define vszUnknown              vrgsz[IDS_UNKNOWN             ]
#define vszMaxWidth             vrgsz[IDS_MAXWIDTH            ]
#define vszCharPixelWidth       vrgsz[IDS_CHARPIXELWIDTH      ]
#define vszABOUT                vrgsz[IDS_ABOUT               ]
#define vszDHeader              vrgsz[IDS_DHEADER             ]
#define vszDResize              vrgsz[IDS_DRESIZE             ]
#define vszDWidth               vrgsz[IDS_DWIDTH              ]
#define vszEdLimits0To64        vrgsz[IDS_EDLIMITS0TO64       ]
#define vszMaxWidthIncrease     vrgsz[IDS_MAXWIDTHINCREASE    ]
#define vszCannotChangeWidth    vrgsz[IDS_CANNOTCHANGEWIDTH   ]
#define vszWarning              vrgsz[IDS_WARNING             ]
#define vszIncorrectPixWidth    vrgsz[IDS_INCORRECTPIXWIDTH   ]
#define vszMaxWidthOutOfBounds  vrgsz[IDS_MAXWIDTHOUTOFBOUNDS ]
#define vszAvgWidthOutOfBounds  vrgsz[IDS_AVGWIDTHOUTOFBOUNDS ]
#define vszWidthBytesNotOk      vrgsz[IDS_WIDTHBYTESNOTOK     ]
#define vszBitsOffsetNotOk      vrgsz[IDS_BITSOFFSETNOTOK     ]
#define vszTableWidthsBad       vrgsz[IDS_TABLEWIDTHSBAD      ]
#define vszTableOffsetsBad      vrgsz[IDS_TABLEOFFSETSBAD     ]
#define vszcolors               vrgsz[IDS_COLOR               ]
#define vszAppWorkspace         vrgsz[IDS_APPWORKSPACE        ]
#define vszFileReadOnly         vrgsz[IDS_FILEREADONLY        ]
#define vszErrorClip            vrgsz[IDS_ERRORCLIP           ]
#define vszCHARvrgsz[IDS_CHAR]
#define vszWIDTHvrgsz[IDS_WIDTH]
#define vszHEIGHTvrgsz[IDS_HEIGHT]

/*------ fontedit.c -------*/
extern VOID ResizeShow(VOID);
extern VOID ScrollFont(VOID);
extern VOID CharToBox(BYTE);
extern VOID FontRename(CHAR *);
extern VOID BoxToChar(BYTE);
extern DWORD GetkStuff(VOID);

/*------ fontload.c -------*/
extern VOID ToClipboard(BYTE, DWORD, DWORD);
extern VOID BoxToClipboard(POINT, DWORD, DWORD);
extern DWORD ClipboardToBox(POINT, DWORD, DWORD, BOOL);

/*------ fontdlg.c -------*/
BOOL
CommDlgOpen (
HWND   hWndParent,      /* window handle of parent window */
OFSTRUCT *pOfstrIn,     /* ptr to current file OFSTRUCT (->cBytes=0 if no
 * cur. file)*/
CHAR  *pszNewNameIn,    /* ptr to array which will get new file's name */
CHAR  *pszExtIn,        /* ptr to current default extension */
CHAR  *pszAppNameIn,    /* ptr to application name */
BOOL   fOpenType
);

BOOL
CommDlgSaveAs(
HANDLE hInstance,
HWND   hWndParent,      /* window handle of parent window */
OFSTRUCT *pOfstrIn,     /* ptr to current file OFSTRUCT (->cBytes=0 if no
 * cur. file)*/
CHAR  *pszNewNameIn,    /* ptr to array which will get new file's name
 * (no path) */
CHAR  *pszExtIn,        /* ptr to current default extension */
CHAR  *pszAppNameIn     /* ptr to application name */
);

BOOL
DlgMergeStrings(
CHAR   *szSrc,
CHAR   *szMerge,
CHAR   *szDst
);

/*------ fonthead.c -------*/
extern VOID ErrorBox(HWND, CHAR *);

/*------ fontchar.c -------*/
extern VOID ClearBox(VOID);

/*------ fontcvt.c --------*/

BOOL
fConvStructInit ();

VOID
vFontStructFromBuffer (
             PBYTE           pjSourceBuff,
            FontHeaderType  *pfhDestFHStruct
        );

VOID
vBufferFromFontStruct (
             FontHeaderType  *pfhSourceFHStruct,
            PBYTE           pjDestBuff
        );

VOID
vBufferFromFont30Struct (
             FontHeader30    *pfh3SourceFH3Struct,
            PBYTE           pjDestBuff
        );

VOID
vGlyphInfo20FromBuffer (
             PBYTE           pjSourceBuff,
            GLYPHINFO_20   *pgi2DestGI2Struct
        );

VOID
vGlyphInfo30FromBuffer (
             PBYTE           pjSourceBuff,
            GLYPHINFO_30   *pgi3DestGI3Struct
        );

VOID
vBufferFromGlyphInfo20 (
             GLYPHINFO_20    *pgi2SrcGI2Struct,
            PBYTE           pjDestBuff
        );

VOID
vBufferFromGlyphInfo30 (
             GLYPHINFO_30    *pgi3SrcGI3Struct,
            PBYTE           pjDestBuff
        );

// dword alligned size of DIB scan in bytes
#define CJ_DIB_SCAN(cx)  ((((cx) + 31) & ~31) >> 3)


FONTHEAD.C

#include "windows.h"
#include <windowsx.h>
#include "fontedit.h"
#include "fcntl.h"
#include "stdio.h"

#define ATTRDIRLIST 0x4010      /* Include directories and drives in listbox */

/****************************************************************************/
/*              Shared Variables                                            */
/****************************************************************************/
extern LONG lSizeOfOldFontHeader;/* Old font header type. */
extern FontHeaderType font;             /* Structure of Font File Header */
extern FontHeaderType fontBuffer;       /* temp structure of font file header */
CHAR szFontFileFull[FILENAMEMAX] = {"*.FNT"}; /* Name of Font File */
CHAR szFontFile[FILENAMEMAX] = {"*.FNT"}; /* Name of Font File */
CHAR szNewFile[FILENAMEMAX] = {"*.FNT"};  /* Name of New File */
extern CHAR szFaceName[];               /* Face Name of Font */
extern BOOL NewFile;                    /* flag indicating if file was opened
                                           by selecting NEW on menu */
extern CHAR *vrgsz[CSTRINGS];           /* string table */
extern BOOL fChanged;                   /* Note if we did anything */
extern INT swH;
extern BYTE iChar, jChar;

extern HWND hFont;                      /* Handle to Show window */
extern HWND hBox;                       /* Handle to Edit Window */
extern HDC hMemDC;                      /* Handle to Memory Display Context */

extern CHAR szAppName[];
extern INT iFontFormat;             /* format of font currently being edited */
extern DWORD cTable;                /* offset table size */
/****************************************************************************/
/*              Local Variables                                             */
/****************************************************************************/
WORD newCharSet;                  /* Temporary value of font.CharSet */
WORD newFamily;                   /* Temporary value of font.Family << 4 */
BOOL newFV;     /* Temporay value of Fixed/Variable flag */
WORD newItalic, newUnderline, newStrikeOut;
WORD newWeight;         /* Temporary value of font.Weight */
BOOL fMsgBoxUp; /* Bug Fix Hack explained in ReSizeProc */
/****************************************************************************/
/*              Local Functions                                             */
/****************************************************************************/

VOID SetCharSet(HWND hDial);
VOID SetFamily(HWND hDial);
VOID SetFixed(HWND hDial, BOOL fFV);
VOID SetWeight(HWND hDial);

/****************************************************************************
 * BOOL  APIENTRY HeaderProc(hDial, message, wParam, lParam)
 *
 * purpose : Dialog function which verifies and accepts inputs which may
 *           alter font's header information (font attributes)
 *
 * params  : Same as for all dialog functions
 *
 * side effects: may alter font attributes
 *
 ***************************************************************************/
BOOL  APIENTRY
HeaderProc(
HWND   hDial,
WORD   message,
WPARAM wParam,
LPARAM lParam
)
{
FontHeaderType FAR * lpFont;
BOOL fOk;               /* for GetDlgItemInt errors! */
UINT i;

UNREFERENCED_PARAMETER(lParam);

    lpFont = (FontHeaderType FAR *)&font;
    switch (message)
        {
    default:
        return FALSE;
    case WM_INITDIALOG:
        newCharSet = font.CharSet;
        SetCharSet(hDial);      /* Set OEM/ANSI buttons */

        newFamily = (WORD) (font.Family >> 4);   /* Working value */
        SetFamily(hDial);               /* Set Font Family */

        CheckDlgButton(hDial, ID_ITALIC, newItalic = font.Italic);
        CheckDlgButton(hDial, ID_UNDERLINE, newUnderline = font.Underline);
        CheckDlgButton(hDial, ID_STRIKEOUT, newStrikeOut = font.StrikeOut);

        SetDlgItemInt(hDial, ID_POINTS, lpFont->Points, FALSE);
        SetDlgItemInt(hDial, ID_VERT_RES, lpFont->VertRes, FALSE);
        SetDlgItemInt(hDial, ID_HORIZ_RES, lpFont->HorizRes, FALSE);
        SetDlgItemInt(hDial, ID_ASCENT, lpFont->Ascent, FALSE);
        SetDlgItemInt(hDial, ID_EXT_LEADING, lpFont->ExtLeading, FALSE);
        SetDlgItemInt(hDial, ID_INT_LEADING, lpFont->IntLeading, FALSE);
        SetDlgItemInt(hDial, ID_DEFAULT_CHAR,lpFont->DefaultChar , FALSE);
        SetDlgItemInt(hDial, ID_BREAK_CHAR, lpFont->BreakChar, FALSE);

        SetDlgItemText(hDial, ID_COPYRIGHT, lpFont->Copyright);
        SetDlgItemText(hDial, ID_FACE_NAME, (LPSTR)szFaceName);
        SetDlgItemText(hDial, ID_FONT_NAME, (LPSTR)szFontFile);
        break;
    case WM_COMMAND:
        switch (LOWORD(wParam))
            {
        case ID_ANSI:
            newCharSet = ANSI_CHARSET;
            SetCharSet(hDial);          /* Set OEM/ANSI buttons */
            break;
        case ID_SYMBOL:
            newCharSet = SYMBOL_CHARSET;
            SetCharSet(hDial);          /* Set OEM/ANSI buttons */
            break;
        case ID_OEM:
            newCharSet = OEM_CHARSET;
            SetCharSet(hDial);          /* Set OEM/ANSI buttons */
            break;
        case ID_SHIFTJIS:
            newCharSet = SHIFTJIS_CHARSET;
            SetCharSet(hDial);/* Set OEM/ANSI buttons */
            break;
        case ID_UNKNOWN:
            newFamily = 0;
            SetFamily(hDial);
            break;
        case ID_ROMAN:
            newFamily = 1;
            SetFamily(hDial);
            break;
        case ID_SWISS:
            newFamily = 2;
            SetFamily(hDial);
            break;
        case ID_MODERN:
            newFamily = 3;
            SetFamily(hDial);
            break;
        case ID_SCRIPT:
            newFamily = 4;
            SetFamily(hDial);
            break;
        case ID_DECORATIVE:
            newFamily = 5;
            SetFamily(hDial);
            break;
        case ID_ITALIC:
            newItalic = (WORD) !newItalic;
            CheckDlgButton(hDial, ID_ITALIC, newItalic);
            break;
        case ID_UNDERLINE:
            newUnderline = (WORD) !newUnderline;
            CheckDlgButton(hDial, ID_UNDERLINE, newUnderline);
            break;
        case ID_STRIKEOUT:
            newStrikeOut = (WORD) !newStrikeOut;
            CheckDlgButton(hDial, ID_STRIKEOUT, newStrikeOut);
            break;
        case ID_CHAR_SET  :
            i = GetDlgItemInt(hDial, ID_CHAR_SET, (LPBOOL)&fOk, FALSE);
            if (fOk && (i < 256))
                {
                font.CharSet = (BYTE) (newCharSet = (WORD) i);
                CheckDlgButton(hDial, ID_ANSI, (WORD) (i == ANSI_CHARSET));
                CheckDlgButton(hDial, ID_SYMBOL, (WORD) (i == SYMBOL_CHARSET));
                CheckDlgButton(hDial, ID_OEM, (WORD) (i == OEM_CHARSET));
                CheckDlgButton(hDial, ID_SHIFTJIS,i == SHIFTJIS_CHARSET);
                }
            break;

        case IDOK:
            font.CharSet = (BYTE) newCharSet;
            font.Family = (BYTE) ((newFamily << 4) | (font.Family & 1));
            font.Italic = (BYTE) newItalic;
            font.Underline = (BYTE) newUnderline;
            font.StrikeOut = (BYTE) newStrikeOut;
            fChanged = TRUE;

            /* nominal point size */
            i = GetDlgItemInt(hDial, ID_POINTS, (LPBOOL)&fOk, FALSE);
            if (fOk)
                lpFont->Points = (WORD) i;
            else{
                ErrorBox(hDial, vszNomPtSizeNotOk);
                SetFocus(GetDlgItem(hDial, ID_POINTS));
                break;
                }
            /* nominal vertical resolution */
            i = GetDlgItemInt(hDial, ID_VERT_RES, (LPBOOL)&fOk, FALSE);
            if (fOk)
                lpFont->VertRes = (WORD) i;
            else{
                ErrorBox(hDial, vszNomVertResNotOk);
                SetFocus(GetDlgItem(hDial, ID_VERT_RES));
                break;
                }
            /* nominal horizontal resolution */
            i = GetDlgItemInt(hDial, ID_HORIZ_RES, (LPBOOL)&fOk, FALSE);
            if (fOk)
                lpFont->HorizRes = (WORD) i;
            else{
                ErrorBox(hDial, vszNomHorResNotOk);
                SetFocus(GetDlgItem(hDial, ID_HORIZ_RES));
                break;
                }
            /* font ascent */
            i = GetDlgItemInt(hDial, ID_ASCENT, (LPBOOL)&fOk, FALSE);
            if (fOk)
                {
                if (i <= font.PixHeight)
                    lpFont->Ascent = (WORD) i;
                else{
                    ErrorBox(hDial, vszAscentTooBig);
                    SetFocus(GetDlgItem(hDial, ID_ASCENT));
                    break;
                    }
                }
            else{
                ErrorBox(hDial, vszAscentNotOk);
                SetFocus(GetDlgItem(hDial, ID_ASCENT));
                break;
                }
            /* font external leading */
            i = GetDlgItemInt(hDial, ID_EXT_LEADING, (LPBOOL)&fOk, FALSE);
            if (fOk)
                lpFont->ExtLeading = (WORD) i;
            else{
                ErrorBox(hDial, vszExtLeadNotOk);
                SetFocus(GetDlgItem(hDial, ID_EXT_LEADING));
                break;
                }
            /* font internal leading */
            i = GetDlgItemInt(hDial, ID_INT_LEADING, (LPBOOL)&fOk, FALSE);
            if (fOk)
                {
                if (i <= font.Ascent)
                    lpFont->IntLeading = (WORD) i;
                else{
                    ErrorBox(hDial, vszIntLeadTooBig);
                    SetFocus(GetDlgItem(hDial, ID_INT_LEADING));
                    break;
                    }
                }
            else{
                ErrorBox(hDial, vszIntLeadNotOk);
                SetFocus(GetDlgItem(hDial, ID_INT_LEADING));
                break;
                }
            /* font character set */
            i = GetDlgItemInt(hDial, ID_CHAR_SET, (LPBOOL)&fOk, FALSE);
            if (fOk && i < 256)
                {
                font.CharSet = (BYTE) i;
                SetCharSet(hDial);
                }
            else{
                ErrorBox(hDial, vszCharSetOutOfBounds);
                SetFocus(GetDlgItem(hDial, ID_CHAR_SET));
                break;
                }
            /* font default char number */
            i = GetDlgItemInt(hDial, ID_DEFAULT_CHAR, (LPBOOL)&fOk, FALSE);

            if (fOk)
                {
#ifdef JAPAN // this makes no sense so it will remain langauge specific
                if (i <= (UINT)font.LastChar - (UINT)font.FirstChar)
#else
                if ((i >= font.FirstChar) && (i <= font.LastChar))
#endif
                    lpFont->DefaultChar = (BYTE) i;
                else{
                    ErrorBox(hDial, vszDefCharOutsideFont);
                    SetFocus(GetDlgItem(hDial, ID_DEFAULT_CHAR));
                    break;
                    }
                }
            else{
                ErrorBox(hDial, vszDefCharNotOk);
                SetFocus(GetDlgItem(hDial, ID_DEFAULT_CHAR));
                break;
                }
            /* break char number */
            i = GetDlgItemInt(hDial, ID_BREAK_CHAR, (LPBOOL)&fOk, FALSE);
            if (fOk)
                {
                if ((BYTE)i <= (font.LastChar - font.FirstChar))
                    lpFont->BreakChar = (BYTE) i;
                else{
                    ErrorBox(hDial, vszBreakCharOutsideFont);
                    SetFocus(GetDlgItem(hDial, ID_BREAK_CHAR));
                    break;
                    }
                }
            else{
                ErrorBox(hDial, vszBreakCharNotOk);
                SetFocus(GetDlgItem(hDial, ID_BREAK_CHAR));
                break;
                }
            /* facename string */
            GetDlgItemText(hDial, ID_COPYRIGHT, lpFont->Copyright, 60);
            GetDlgItemText(hDial, ID_FACE_NAME, (LPSTR)szFaceName, szNamesMax);
            if (!lstrlen((LPSTR)szFaceName))
                {
                lstrcpy((LPSTR)szFaceName, (LPSTR)vszUnknown);
                ErrorBox(hDial, vszUnknownFace);
                }
            /* fall thru to enddialog */

        case IDCANCEL:
            EndDialog(hDial, wParam != IDCANCEL);
            break;

        default:
            break;
            }
        }
    return TRUE;
}

/****************************************************************************
 * char * VerifyHeaderContents()
 *
 * purpose: Checks if the Header information of the file just read makes
 *          sense. If not, returns an error message string to FontLoad
 *
 * params : none
 *
 * returns :char *szError : ptr to error string if error occurs
 *                          NULL otherwise
 *
 * side effects: none
 *
 ***************************************************************************/
CHAR * PASCAL
VerifyHeaderContents(
VOID
)
{
     if (fontBuffer.Points > 999)
        return vszNomPtSizeNotOk;
     if (fontBuffer.VertRes > 999)
        return vszNomVertResNotOk;
     if (fontBuffer.HorizRes > 999)
        return vszNomHorResNotOk;
     if (fontBuffer.Ascent > fontBuffer.PixHeight)
        return vszAscentTooBig;
     if (fontBuffer.Ascent > 999)
        return vszAscentNotOk;
     if (fontBuffer.ExtLeading > 999)
        return vszExtLeadNotOk;
     if (fontBuffer.IntLeading > fontBuffer.Ascent)
        return vszIntLeadTooBig;
     if (fontBuffer.IntLeading > 999)
        return vszIntLeadNotOk;
     if (fontBuffer.LastChar > 255)
        return vszCharSetOutOfBounds;
     if (fontBuffer.DefaultChar > fontBuffer.FirstChar + (fontBuffer.LastChar - fontBuffer.FirstChar))
#if 0
          fontBuffer.DefaultChar = 0;
#else
        return vszDefCharOutsideFont;
#endif
     if (fontBuffer.DefaultChar > 255)
        return vszDefCharNotOk;
     if (fontBuffer.BreakChar > (fontBuffer.LastChar - fontBuffer.FirstChar))
        return vszBreakCharOutsideFont;
     if (fontBuffer.BreakChar > 255)
        return vszBreakCharNotOk;
     if (fontBuffer.PixHeight > 64)
        return vszHeightOutOfBounds;
     if (fontBuffer.MaxWidth > 64)
        return vszMaxWidthOutOfBounds;
     if (fontBuffer.AvgWidth > 64)
        return vszAvgWidthOutOfBounds;
     if (iFontFormat == ID_FORMAT2)
        if (fontBuffer.BitsOffset > (DWORD)SEGMENT_SIZE)
            return vszBitsOffsetNotOk;
     return NULL;
}

/****************************************************************************
 * BOOL PASCAL Format20FileTooBig(iHeight, iWidth)
 *
 * purpose: Checks if the 2.0 font file size is within the 64k
 *          limit imposed by the 2.0 format offset table.( if file over
 *          size limit is saved  in 2.0 format, loss of information will
 *          result)
 *
 * params:  WORD iHeight : current height of font in pixels
 *          WORD iWidth  : current width of font in pixels
 *
 * returns: TRUE  : file too large
 *          FALSE : ok to save
 *
 * side effects: none
 *
 ***************************************************************************/
BOOL PASCAL
Format20FileTooBig(
WORD iHeight,
WORD iWidth
)
{
    if ((DWORD)lSizeOfOldFontHeader + (DWORD)cTable
         + (font.LastChar - font.FirstChar +1)
         * (((DWORD)iHeight * (DWORD)iWidth ) >> 3) >= WORD_LIMIT)
             return TRUE;
    return FALSE;
}

/****************************************************************************
 * BOOL  APIENTRY ReSizeProc(hDial, message, wParam, lParam)
 *
 * purpose : dialog fn. which verifies and accepts font resize (stretch,
 *           shrink...) input and calls the appropriate routine to perform
 *           the function. Also alters font weight attributes (bold, light,
 *           extra light...)
 *
 * params  : same as for all dialog functions
 *
 * side effects: alters header information regarding font dimensions and font
 *               weight
 *
 ***************************************************************************/
BOOL  APIENTRY
ReSizeProc(
HWND   hDial,
WORD   message,
WPARAM wParam,
LPARAM lParam
)
{
FontHeaderType FAR * lpFont;
BOOL fOk;               /* for GetDlgItemInt errors! */
UINT i;
HMENU hMenu;
static BOOL fFV; /* temporary fixed/var width flag */

UNREFERENCED_PARAMETER(lParam);

    lpFont = (FontHeaderType FAR *)&font;
    switch (message)
        {
    case WM_INITDIALOG:
        fMsgBoxUp = FALSE;
        newWeight = (WORD) font.Weight;
        newFV = (BOOL) (font.Family & 1);
        fFV = newFV;
        SetFixed(hDial, fFV);   /* Set Fixed or Variable width */
        SetDlgItemInt(hDial, ID_PIX_HEIGHT, lpFont->PixHeight, FALSE);
        SetDlgItemInt(hDial, ID_FIRST_CHAR, lpFont->FirstChar, FALSE);
        if (!fFV)
            SetDlgItemInt(hDial, ID_WIDTH, lpFont->AvgWidth, FALSE);
        else
            SetDlgItemInt(hDial, ID_WIDTH, lpFont->MaxWidth, FALSE);
        SetDlgItemInt(hDial, ID_AVERAGE, lpFont->AvgWidth, FALSE);
        SetDlgItemInt(hDial, ID_LAST_CHAR, lpFont->LastChar, FALSE);
        SetWeight(hDial);
        SetFixed(hDial, fFV);
        break;

    case WM_COMMAND:
        switch (LOWORD(wParam))
            {
        case ID_THIN:
            newWeight = FW_THIN;
            SetWeight(hDial);
            break;
        case ID_EXTRALIGHT:
            newWeight = FW_EXTRALIGHT;
            SetWeight(hDial);
            break;
        case ID_LIGHT:
            newWeight = FW_LIGHT;
            SetWeight(hDial);
            break;
        case ID_NORMAL:
            newWeight = FW_NORMAL;
            SetWeight(hDial);
            break;
        case ID_MEDIUM:
            newWeight = FW_MEDIUM;
            SetWeight(hDial);
            break;
        case ID_BOLD:
            newWeight = FW_BOLD;
            SetWeight(hDial);
            break;
        case ID_SEMIBOLD:
            newWeight = FW_SEMIBOLD;
            SetWeight(hDial);
            break;
        case ID_EXTRABOLD:
            newWeight = FW_EXTRABOLD;
            SetWeight(hDial);
            break;
        case ID_HEAVY:
            newWeight = FW_HEAVY;
            SetWeight(hDial);
            break;

        case ID_FIXED:
            fFV = 0;
            SetFixed(hDial, fFV);
            break;
        case ID_VARIABLE:
            fFV = 1;
            SetFixed(hDial, fFV);
            break;

        case IDOK:
            fChanged = TRUE;
            font.Weight = newWeight;
            /* give warning if user attempts to change from variable to
               fixed width */
            if ((fFV == 0) && (newFV != 0))
                {
                fMsgBoxUp = TRUE;
                ErrorBox(hDial, vszNoVarToFixChange);
                fMsgBoxUp = FALSE;
                SetFocus(GetDlgItem(hDial, ID_VARIABLE));
                break;
                }
            else
                newFV = fFV;

            if (newFV && !(font.Family & 1))
                {
                font.PixWidth = 0;      /* All we need to do to make this */
                font.Family |= 1;       /* .. font variable width */
                }
            /* change height command */
            i = GetDlgItemInt(hDial, ID_PIX_HEIGHT, (LPBOOL)&fOk, FALSE);
            if (fOk && i && i < kBoxLim && i>0 )
                {
                    if (i != font.PixHeight){        /* Same size ? */
                        /* No: Resize body height */
                        if (!ResizeBody(font.WidthBytes, i)){
                            SetFocus(hDial);
                            break;
                        }
                    }
                }
            else{
                ErrorBox(hDial, vszHeightOutOfBounds);
                SetFocus(GetDlgItem(hDial, ID_PIX_HEIGHT));
                break;
                }

            /* change width command */
            i = GetDlgItemInt(hDial, ID_WIDTH, (LPBOOL)&fOk, FALSE);
            if (fOk && i < wBoxLim && i > 0){
                int kki;
                kki = (newFV) ? font.MaxWidth : font.AvgWidth;
                if (i != (UINT)kki){
                    if (newFV){
                        if (!SpreadWidths(i)){
                            SetFocus(hDial);
                            break;  /* new variable widths */
                        }
                    }
                    else{
                        if (!ResizeWidths(i)){
                            SetFocus(hDial);
                            break;  /* new fixed widths */
                        }
                    }
                }
            }
            else{
                ErrorBox(hDial, vszWidthOutOfBounds);
                SetFocus(GetDlgItem(hDial, ID_WIDTH));
                break;
                }

            /* new first char command */
            i = GetDlgItemInt(hDial, ID_FIRST_CHAR, (LPBOOL)&fOk, FALSE);
            if (fOk)
                {
                if ((i <= font.LastChar) && (i <= font.DefaultChar))
                    {
                    if (i != font.FirstChar)
                        {
                        if (!NewFirstChar(i))
                            break;
                        iChar = jChar = (CHAR) i;
                        swH = 0;
                        ScrollFont();           /* Show front end */
                        }
                    }
                else{
                    ErrorBox(hDial, vszChar1MoreThanDChar);
                    SetFocus(GetDlgItem(hDial, ID_FIRST_CHAR));
                    break;
                    }
                }
            else{
                ErrorBox(hDial, vszChar1NotOk);
                SetFocus(GetDlgItem(hDial, ID_FIRST_CHAR));
                break;
                }

            /* new last char command */
            i = GetDlgItemInt(hDial, ID_LAST_CHAR, (LPBOOL)&fOk, FALSE);
            if (fOk && i < 256)
                {
                if ((i >= font.FirstChar) &&
(i >= font.DefaultChar))
                    {
                    if (i != font.LastChar)
                        {
                        if (!NewLastChar(i))
                            break;
                        iChar = jChar = (CHAR) i;
                        swH = 100;
                        ScrollFont();           /* Show back end */
                        }
                    }
                else{
                    ErrorBox(hDial, vszLastCharTooSmall);
                    SetFocus(GetDlgItem(hDial, ID_LAST_CHAR));
                    break;
                    }
                }
            else{
                ErrorBox(hDial, vszLastCharNotOk);
                SetFocus(GetDlgItem(hDial, ID_LAST_CHAR));
                break;
                }

            /* this is moved from SetFixed, since it should only be done
             * if idok pressed. */
            if (newFV)
                {  /* Enable "width" if variable-width font */
                hMenu = GetMenu(hBox);
                EnableMenuItem(hMenu, 4, MF_BYPOSITION | MF_ENABLED);
                DrawMenuBar(hBox);
                }

            /* fall thru to enddialog...*/

        case IDCANCEL:
            EndDialog(hDial, wParam != IDCANCEL);
            break;

        default:
            return FALSE;
            break;
            }  /* end switch wParam */

    default:
        return FALSE;
        } /* end switch message */

    return TRUE;
}

VOID
ErrorBox(
HWND hWndparent,
CHAR * szMessage
)
/* Show Message Box */
{
    MessageBox(hWndparent, (LPSTR)szMessage, (LPSTR)szAppName,
                  MB_OK | MB_ICONASTERISK | MB_APPLMODAL);
}

/****************************************************************************
 * SetCharSet(hDial)
 *
 * purpose : Set ANSI/OEM dialog button and set the number in edit window
 *
 * param   : HWND hDial : handle to dialog box window
 *
 * returns : none
 *
 ***************************************************************************/
VOID
SetCharSet(
HWND hDial
)
{
    CheckDlgButton(hDial, ID_ANSI, (WORD) (newCharSet == ANSI_CHARSET));
    CheckDlgButton(hDial, ID_SYMBOL, (WORD) (newCharSet == SYMBOL_CHARSET));
    CheckDlgButton(hDial, ID_OEM, (WORD) (newCharSet == OEM_CHARSET));
    CheckDlgButton(hDial, ID_SHIFTJIS, newCharSet == SHIFTJIS_CHARSET);
    SetDlgItemInt(hDial,  ID_CHAR_SET, newCharSet, FALSE);
}

/****************************************************************************
 * SetFixed(hDial)
 *
 * purpose : Set fixed/variable dialog button
 *
 * params  : HWND hDial : handle to dialog box window
 *           BOOL fFV   : variable or fixed
 *
 * returns : none
 *
 ***************************************************************************/
VOID
SetFixed(
HWND hDial,
BOOL fFV
)
{
    CheckDlgButton(hDial, ID_FIXED, (WORD)(fFV == 0));
    CheckDlgButton(hDial, ID_VARIABLE, (WORD)(fFV == 1));
    if (newFV)
        SetDlgItemText(hDial, ID_WIDTH_TEXT, (LPSTR)vszMaxWidth);
    else
        SetDlgItemText(hDial, ID_WIDTH_TEXT, (LPSTR)vszCharPixelWidth);
}

/****************************************************************************
 * SetWeight(hDial)
 *
 * purpose : Set font weight dialog button
 *
 * params  : HWND hDial : handle to dialog box window
 *
 * returns : none
 *
 ****************************************************************************/
VOID
SetWeight(
HWND hDial
)
{
    CheckDlgButton(hDial, ID_THIN, (WORD)(newWeight == FW_THIN));
    CheckDlgButton(hDial, ID_EXTRALIGHT, (WORD)(newWeight == FW_EXTRALIGHT));
    CheckDlgButton(hDial, ID_LIGHT, (WORD)(newWeight == FW_LIGHT));
    CheckDlgButton(hDial, ID_NORMAL, (WORD)(newWeight == FW_NORMAL));
    CheckDlgButton(hDial, ID_MEDIUM, (WORD)(newWeight == FW_MEDIUM));
    CheckDlgButton(hDial, ID_SEMIBOLD, (WORD)(newWeight == FW_SEMIBOLD));
    CheckDlgButton(hDial, ID_BOLD, (WORD)(newWeight == FW_BOLD));
    CheckDlgButton(hDial, ID_EXTRABOLD, (WORD)(newWeight == FW_EXTRABOLD));
    CheckDlgButton(hDial, ID_HEAVY, (WORD)(newWeight == FW_HEAVY));
}

/****************************************************************************
 * SetFamily(hDial)
 *
 * purpose : Set font family dialog button
 *
 * params  : HWND hDial : handle to dialog box window
 *
 * returns : none
 *
 ***************************************************************************/
VOID
SetFamily(
HWND hDial
)
{
    CheckDlgButton(hDial, ID_UNKNOWN, (WORD) (newFamily == 0));
    CheckDlgButton(hDial, ID_ROMAN, (WORD)(newFamily == 1));
    CheckDlgButton(hDial, ID_SWISS, (WORD)(newFamily == 2));
    CheckDlgButton(hDial, ID_MODERN, (WORD)(newFamily == 3));
    CheckDlgButton(hDial, ID_SCRIPT, (WORD)(newFamily == 4));
    CheckDlgButton(hDial, ID_DECORATIVE, (WORD) (newFamily == 5));
}


FONTLOAD.C

#include "windows.h"
#include <windowsx.h>
#include "fontedit.h"
#include "fcntl.h"
#include "memory.h"
#include "stdio.h"
#include "commdlg.h"

/****************************************************************************/
/*              Shared Variables                                            */
/****************************************************************************/
extern CHAR *PASCAL VerifyHeaderContents();/* checks integrity of header */

extern FontHeaderType font;             /* Structure of Font File Header */
extern LONG lSizeOfOldGlyph20;          /* Old packed 2.0 glyph info size. */
extern LONG lSizeOfOldGlyph30;     /* Old packed 3.0 glyph info size. */
extern LONG lSizeOfOldFontHeader;       /* Old packed font header size. */
extern LONG lSizeOfOldFontHeader30;     /* Old 3.0 packed font header size. */
extern CHAR szFaceName[];               /* Face Name of Font */
extern DWORD offsets[];                 /* Offsets Table */

extern BOOL fReadOnly;
extern BOOL fChanged;                   /* Note if we did anything */
extern BOOL fLoaded;                    /* Set if a font is loaded */
extern INT iFontFormatPrev;             /* Set to the id of prev.font format */
extern INT iFontFormat;             /* Set to the id of current font format */

extern HWND hFont;                      /* Handle to Show window */
extern HWND hBox;                       /* Handle to Edit Window */
extern HDC hMemDC;                      /* Handle to Memory Display Context */
extern HBITMAP hBitmap;                 /* Handle to our work bit map */
extern CHAR matBox [wBoxLim] [kBoxLim]; /* array to hold Box */

extern CHAR *vrgsz[];                   /* string table */
extern OFSTRUCT ofstrFile;
extern BOOL NewFile;                    /* flag indicating if file was opened
                                           by selecting NEW on menu */
FontHeaderType fontBuffer;          /* temporary buffer of Font File Header */
WORD cHeader, cTable;
HFILE nNewFile;                          /* NT file handle */

/****************************************************************************/
/*              Local Variables                                             */
/****************************************************************************/

DWORD Proport(DWORD, DWORD, DWORD, DWORD);  /* Reproportions a value */

CHAR  *lpFontBody = NULL;  /* Pointer to Font Body */
CHAR  *lpWork = NULL;      /* Pointer to Work Area */
HDC hNewMemDC;
HDC hBoxMemDC;
HBITMAP hNewBitmap;
HBITMAP hBoxBitmap;
HCURSOR hOldCursor = NULL;      /* Holds Arrow while we show Hourglass */
HCURSOR hHourGlass = NULL;
DWORD oldMode;                   /* For StretchBltMode */
BYTE  *lp1,  *lp2;    /* Pointers to bitmaps in format N */

FontHeader30  font30;

#define ALPHA_CNT        26
#define TOTAL_WEIGHTS  1000
#define FSF_FIXED             0x0001
#define FSF_PROPORTIONAL      0x0002

SHORT widthweights[ALPHA_CNT + 1] =
        {
                 64,        /* a
                 14,        /* b     */
                 27,        /* c     */
                 35,        /* d     */
                100,        /* e     */
                 20,        /* f     */
                 14,        /* g     */
                 42,        /* h     */
                 63,        /* i     */
                 3 ,        /* j     */
                 6 ,        /* k     */
                 35,        /* l     */
                 20,        /* m     */
                 56,        /* n     */
                 56,        /* o     */
                 17,        /* p     */
                 4 ,        /* q     */
                 49,        /* r     */
                 56,        /* s     */
                 71,        /* t     */
                 31,        /* u     */
                 10,        /* v     */
                 18,        /* w     */
                 3 ,        /* x     */
                 18,        /* y     */
                 2 ,        /* z     */
                166,        /* space, must be last, to use with the following
                               code */
        };

/****************************************************************************/
/*              Local Functions                                             */
/****************************************************************************/

VOID NewAverage(VOID);
BOOL GetNewMap(DWORD, DWORD);
VOID UseNewMap(VOID);
VOID ShrinkFont(DWORD, DWORD);

/****************************************************************************
 * WORD ConvertToBitmapFormat(width, phase, height)
 *
 * purpose  : Takes a part of the font file and converts it to a string of
 *            bytes for a single scan of bitmap for a character.
 *
 * params   : WORD width : width of the character in pixels(bits)
 *            WORD phase : current deviation from byte alignment (pixels)
 *            WORD height: height of character(pixels)
 *
 * returns  : WORD phase : new deviation from byte alignment (pixels)
 *
 * side effects : modifies pointers lp1 and lp2 (pointing to work space and
 *                font body respectively)
 *
 ****************************************************************************/
DWORD PASCAL
ConvertToBitmapFormat(
     DWORD width,     /* width of the character in pixels(bits) */
     DWORD phase,     /* current deviation from byte alignment (pixels) */
     DWORD height     /* height of character(pixels) */
     )
{
      INT w;
      WORD j;

      /* in the font file format characters are stored consecutively in
         column-major ordering (columns of char 1 followed by columns of char
         2 ... etc.). lp2 points to start of columns of current character.
         lp1 points to start of row of bitmap for character. */

      for (w = width; w > 0; w -= 8){
          if (phase == 0){
          /*  easy case */
              *lp1++ = *lp2;

              if (w < 8)
                  phase = w;
          }
          else{

              --lp1;
              j = (WORD)*lp1;
              j <<= 8;
              j |= (((WORD)*lp2) << (8 - phase));
              *lp1++ = (BYTE)(j >> 8);
              *lp1++ = (BYTE)j;
              if (w < 8){
                  phase += w;
                  if (phase <= 8)
                      lp1--;          /* back up pointer */
                  phase &= 7;
              }
          }
          lp2 += height;          /* move to next column */
      }
      return phase;
}

/****************************************************************************
 * char * VerifyTableContents()
 *
 * purpose  : scan the offsets table of file just read and check if
 *            width and offsets lie within limits
 *
 * params    : none
 *
 * returns   : char * szError : ptr to Error message if table not OK
 *                              NULL otherwise
 *
 * side effects : none
 *
 ****************************************************************************/
CHAR * PASCAL
VerifyTableContents(
    VOID
    )
{
 PBYTE  pjGlyphData;
     GLYPHINFO_20 gi2T20;        /* temp ptr to a 2.0 style table*/
     GLYPHINFO_30 gi3T30;        /* temp ptr to a 2.0 style table*/
     INT i;

     /* separate loops written for the 2.0 and 3.0 font processing because
        a single loop would involve too many checks (of font type) within
        the loop and slow down processing  */
     if (iFontFormat == ID_FORMAT2){
         pjGlyphData = (lpFontBody +1);

/* view table as 2.0 table */

         /* check that each and every width and offset lie within limits */

         for (i=0; i < (fontBuffer.LastChar - fontBuffer.FirstChar + 1); i++) {

  vGlyphInfo20FromBuffer (pjGlyphData, &gi2T20);

              if (gi2T20.GIwidth > 64)
                  return vszTableWidthsBad;

              if ((gi2T20.GIoffset > (UINT)WORD_LIMIT) ||
                  (gi2T20.GIoffset < 0) )
                  return vszTableOffsetsBad;

  pjGlyphData += lSizeOfOldGlyph20;
         }
     }
     else{

         pjGlyphData = lpFontBody;

            /* view table as 3.0 table */
         /* check that each and every width and offset lie within limits */

         for (i=0; i< (fontBuffer.LastChar - fontBuffer.FirstChar +1); i++) {

  vGlyphInfo30FromBuffer (pjGlyphData, &gi3T30);

              if (gi3T30.GIwidth > 64)
                  return vszTableWidthsBad;

              if (gi3T30.GIoffset < (DWORD)0)
                  return vszTableOffsetsBad;

  pjGlyphData += lSizeOfOldGlyph30;
         }
     }
     return NULL;
}

/****************************************************************************
 * char * FontLoad()
 *
 * purpose: reads in the specified font file and creates a work bitmap for
 *          the editor. Also creates a local bitmap-offset table for easy
 *          access to the characters in the bitmap
 *
 * params : pszFileName - File Name to open.
 *
 * returns: ptr to NULL string if Open goes off OK
 *          ptr to error message string otherwise
 *
 * side effects: lots
 *
 ****************************************************************************/
CHAR *
FontLoad(
    CHAR *pszFileName,
OFSTRUCT*pofsReOpenInfo
    )
{

    CHAR  *lpFontBodySav = NULL;  /* local pointer to font body */
    DWORD len, fontlen;               /* length of font body (bytes)*/
UINT i;
    INT mf;                      /* menu function ID */
    DWORD iWorkLen;                   /* length of work area (bytes) */
    HDC hDC;
    CHAR * pszError;                   /* error msg if header is messed up */

    DWORD row, height, width,phase;
    DWORD offset;
    GLYPHINFO_20 gi2GlyphTable20;    /* Pointer into 2.0 style offsets table */
    GLYPHINFO_30 gi3GlyphTable30;    /* Pointer into 3.0 style offsets table */
PBYTE pjGlyphData; /* Pointer to Glyph information buffer. */

    BYTE  cDummy [CCHEXTRA];         /* dummy buffer for unneeded 3.0 header
                                    * info
                                    */

    /* Put up an hourglass ... this may take a while */
    if (!hHourGlass)
        hHourGlass = LoadCursor(NULL, IDC_WAIT);        /* Get Hourglass */
    hOldCursor = SetCursor(hHourGlass);         /* Show hourglass */

    /* open file for read. */
nNewFile = (HFILE)OpenFile (pszFileName, pofsReOpenInfo, OF_READ);

if (nNewFile < 0) {

return vszErrorOpeningFile;
}

//- ReadFile:  Here is where we need to make some adjustments in order to
//- read the file in to a new DWORD aligned format.
{
BYTE    jBuffer [200];

/* Read the header */
if (_lread ((HFILE) nNewFile, (LPSTR)&jBuffer, lSizeOfOldFontHeader) !=
(UINT)lSizeOfOldFontHeader) {

_lclose((HFILE)nNewFile);
return vszErrorReadingHdr;
}

//
// Call conversion routine to give us a properly aligned buffer.
//

vFontStructFromBuffer (
 (PBYTE)&jBuffer,
 &fontBuffer
);

}

    /* Check the version number -- make sure it's a font */
    switch (fontBuffer.Version)
        {
        case 0x200:
                iFontFormat = ID_FORMAT2;
                break;
        case 0x300:        /* added 1/19/88 */
                iFontFormat = ID_FORMAT3;
                /* read in the extra fields into a dummy buffer ... they don't
                 * mean anything to us  at this stage.
                 */
                if ((_lread((HFILE)nNewFile, (LPSTR)cDummy, CCHEXTRA))
                            != CCHEXTRA){
                    _lclose((HFILE)nNewFile);
                    return vszErrorReadingHdr;
                }

                break;
        default:                /* Anything else -- toughies */
                _lclose((HFILE)nNewFile);
                return vszUnknownFormat;
        }

    /* check if contents of font header are sensible. If not,
       give an error message and quit */

    if ((pszError = VerifyHeaderContents()) != NULL) {

_lclose((HFILE)nNewFile);
        return pszError;
}

    /* Ready to load -- Check if we will be overwriting */
    if (fLoaded)
{
        DeleteGlobalBitmap();
        fLoaded = FALSE;
}

    /* Allocate space for font body and Face Name and read them in */
    len= (fontBuffer.Size - lSizeOfOldFontHeader);  /* Compute size */

    if ((lpFontBody = (LPSTR)GlobalAlloc(GMEM_ZEROINIT, (LONG)len)) == 0) {

_lclose((HFILE)nNewFile);
        return vszNotEnoughMem;                       /* Get Handle to space */
}

    if (iFontFormat == ID_FORMAT3)
        fontlen = len - CCHEXTRA;
    else
        fontlen = len;

    lpFontBodySav = lpFontBody;                    /* save ptr to font body */
    while (fontlen >= (DWORD)SEGMENT_SIZE) {

        /*  file read method if font file size is 64k bytes or greater.
            file is read in in chunks of 65536 (SEGMENT_SIZE), taking care to
            position buffer ptr at 64k boundary each time */

        /* First read in the maximum number of bytes _lread can read (65534) */
        if ((_lread((HFILE)nNewFile, lpFontBodySav, WORD_LIMIT)) == WORD_LIMIT) {

            lpFontBodySav += WORD_LIMIT; /* buffer ptr moved up by 64k bytes */
            fontlen -= WORD_LIMIT;     /* fontlen = no of bytes left to read */

} else {

GlobalFree (lpFontBody);
_lclose((HFILE)nNewFile);
            return vszErrorReadingBody;
}

        /* read in an additional two bytes to reach end of segment */
        if ((_lread((HFILE)nNewFile, lpFontBodySav, 2)) == 2) {

            lpFontBodySav += 2; /* buffer ptr moved up by 2 bytes */
            fontlen -= 2;       /* fontlen = no of bytes left to read */

        } else {

GlobalFree (lpFontBody);
_lclose((HFILE)nNewFile);
            return vszErrorReadingBody;
        }
}

    /* read the partially filled segment  */
    if ((_lread((HFILE)nNewFile, lpFontBodySav, (DWORD)fontlen)) != (UINT) fontlen) {

        GlobalFree (lpFontBody);
        _lclose((HFILE)nNewFile);
        return vszErrorReadingBody;
}

    /* Close the file */
    _lclose((HFILE)nNewFile);

    /* check if the offset table entries are within allowable limits.
       If not give an error message, clean up and quit */
    if ((pszError = VerifyTableContents()) != NULL) {

        GlobalFree (lpFontBody);
        return pszError;
}

    /* now that everything has been checked, move buffer to font */
    font = fontBuffer;

    /* Make local copies of FaceName and Device Name */
    if (font.Face){

        lstrcpy((LPSTR)szFaceName, lpFontBody + font.Face -
                                         (iFontFormat == ID_FORMAT2 ?
                                                    lSizeOfOldFontHeader   :
                                                    lSizeOfOldFontHeader30-1));
    } else {

        lstrcpy((LPSTR)szFaceName, (LPSTR)"");
}

    for (i = 0; i <= 256; i++)      /* Zero offsets Table for below */
        offsets[i] = 0;

    /* compute work space needed if a 3.0 file. This has to be done since
       3.0 files are compressed versions of 2.0 files and may need a
       work bitmap bigger than the actual font body size */

    if (iFontFormat == ID_FORMAT3) {

        cTable = (WORD) (6 * (font.LastChar - font.FirstChar + 2));
        iWorkLen = 0;

        pjGlyphData = lpFontBody;

        /* work bitmap size = sum of sizes of all characters in font */

        for (i = font.FirstChar; i <= font.LastChar; i++) {

 vGlyphInfo30FromBuffer (pjGlyphData, &gi3GlyphTable30);

             iWorkLen += ((gi3GlyphTable30.GIwidth +7) >> 3) * font.PixHeight;

 pjGlyphData += lSizeOfOldGlyph30;
}

    } else { /* 2.0 file */

        cTable = (WORD)(4 * (font.LastChar - font.FirstChar + 2));
           /* compute table length */
        iWorkLen = len;  /* work space for a 2.0 file is the same as the
                             length of the font body */
    }

//- Add some extra space for dword alignment.
iWorkLen += (font.PixHeight * sizeof (DWORD));
      /* Get work space */

    if ((lpWork = (LPSTR)GlobalAlloc (GMEM_ZEROINIT, (LONG)iWorkLen)) == 0) {

        GlobalFree (lpFontBody);
        return vszNotEnoughMem;
    }

lp1 = lpWork;

    height = (DWORD) font.PixHeight;
    offset = 0;
    /* put the font file into bitmap format */
    if (iFontFormat == ID_FORMAT2){         /* table in 2.0 format */
        for (row = 0; row < height; row++){

             /* view table as a 2.0 style table */
             pjGlyphData = lpFontBody + 1;

             phase = 0;

             for (i = 0; i < (UINT)(font.LastChar - font.FirstChar + 1); i++) {

vGlyphInfo20FromBuffer (pjGlyphData, &gi2GlyphTable20);

                width = (DWORD) gi2GlyphTable20.GIwidth;

                /* size of each table element = 4bytes */
                lp2 = lpFontBody + (gi2GlyphTable20.GIoffset -
lSizeOfOldFontHeader) + row;

pjGlyphData += lSizeOfOldGlyph20;

                /* offset ends up as the sum of the widths */
                if (row == 0)              /* Once is enough */
offsets[i + font.FirstChar + 1] = offset += width;

                /* create a single scan of bitmap for character */
                phase = ConvertToBitmapFormat (width, phase, height);
             }
             if ((lp1 - lpWork) & 1)
                 *lp1++ = 0;             /* Round lp1 up to Word Boundary */
#ifdef DWORDROUND
             if ((lp1 - lpWork) & 2) {
                 *lp1++ = 0;             /* Round lp1 up to DWord Boundary */
                 *lp1++ = 0;             /* Round lp1 up to DWord Boundary */
 }
#endif
             //if (((offset + 7) >> 3) & 1)
                 //*lp1++ = 0;             /* Round lp1 up to Word Boundary */
             //if (((offset + 7) >> 3) & 2) {
                 //*lp1++ = 0;             /* Round lp1 up to DWord Boundary */
                 //*lp1++ = 0;             /* Round lp1 up to DWord Boundary */
 //}
        }
    }
     /* separate loops written for the 2.0 and 3.0 font processing because
        a single loop would involve too many checks (of font type) within
        the loop and slow down processing  */
    else {                                 /* table in 3.0 format */

        for (row = 0; row < height; row++){

             phase = 0;
             /* view table as a 3.0 style table */
             pjGlyphData = lpFontBody;

             for (i = 0; i < (UINT)(font.LastChar - font.FirstChar + 1); i++) {

vGlyphInfo30FromBuffer (pjGlyphData, &gi3GlyphTable30);

                width = gi3GlyphTable30.GIwidth;

                  /* size of each table element = 6bytes */
                lp2 = lpFontBody + (gi3GlyphTable30.GIoffset -
lSizeOfOldFontHeader30 +1) + row;

pjGlyphData += lSizeOfOldGlyph30;

                 /* offset ends up as the sum of the widths */
                if (row == 0)              /* Once is enough */
                    offsets[i + font.FirstChar + 1] = offset += width;

                 /* create a single scan of bitmap for character */
                phase = ConvertToBitmapFormat (width, phase, height);
             }
             if ((lp1 - lpWork) & 1)
                 *lp1++ = 0;             /* Round lp1 up to Word Boundary */
#ifdef DWORDROUND
             if ((lp1 - lpWork) & 2) {
                 *lp1++ = 0;             /* Round lp1 up to DWord Boundary */
                 *lp1++ = 0;             /* Round lp1 up to DWord Boundary */
 }
#endif
        }
    }
             // FontLoad(): width byte is DWORD align
    font.WidthBytes = (WORD) CJ_DIB_SCAN(offset);
       /* fixup NEWFON error */

    GlobalFree(lpFontBody);
    lpFontBody = lpWork;          /* So that below we free the other buffer */
    /* Create a WINDOWS bitmap to move the font definition bits into */

    hDC = GetDC (hFont);                         /* DC to be compatible with */
    hBitmap = CreateBitmap(
                    (INT)font.WidthBytes << 3, /* Width of font in pixels */
                    (INT)font.PixHeight,
                    1, 1, (LPBYTE)NULL);
    hMemDC = CreateCompatibleDC(hDC);           /* Create a DC */
    SelectObject(hMemDC, hBitmap);              /* Relate the two of them */
    ReleaseDC(hFont, hDC);                      /* Done with font DC */

    /* Move the bits in */
    SetBitmapBits(hBitmap,
          (DWORD)font.WidthBytes * (DWORD)font.PixHeight,(CHAR  *)lpWork);

    /* Free up the space we loaded the file into */
    GlobalFree(lpFontBody);
    fLoaded = TRUE;
    {
        HMENU hMenu;

        hMenu = GetMenu(hBox);  /* Gray menu if no clipboard bitmap */
        mf = (font.Family & 1) ? MF_ENABLED : MF_GRAYED;
        EnableMenuItem(hMenu, FONT_SAVE, MF_ENABLED);
        EnableMenuItem(hMenu, FONT_SAVEAS, MF_ENABLED);
        EnableMenuItem(hMenu, 1, MF_BYPOSITION | MF_ENABLED);
        EnableMenuItem(hMenu, 2, MF_BYPOSITION | MF_ENABLED);
        EnableMenuItem(hMenu, 3, MF_BYPOSITION | MF_ENABLED);
        EnableMenuItem(hMenu, 4, MF_BYPOSITION | mf);
        EnableMenuItem(hMenu, 5, MF_BYPOSITION | MF_ENABLED);
        EnableMenuItem(hMenu, 6, MF_BYPOSITION | MF_ENABLED);
        DrawMenuBar(hBox);
    }
    SetCursor(hOldCursor);              /* Restore regular cursor */
    return "";
}

/**************************************
 * compares nBytes bytes of s1 and s2 *
 **************************************/
BOOL
ByteCompare (
    CHAR  *s1,
    CHAR  *s2,
    DWORD nBytes
    )
{
    for ( ; nBytes > 0; nBytes--)
        if (*s1++ != *s2++)
            return FALSE;
    return TRUE;
}

/***********************************
 * copies nBytes bytes of s2 to s1 *
 ***********************************/
BOOL
ByteCopy(
    CHAR  *s1,
    CHAR  *s2,
    LONG  nBytes
    )
{
    for ( ; nBytes > 0; nBytes--)
        *s1++ = *s2++;
    return TRUE;
}

/****************************************************************************
 * VOID ConvertToFileFormat(width, phase, height)
 *
 * purpose  : Takes a part of the bitmap (corresponding to a single character)
 *            and converts it to a string of bytes in the font file format
 *
 * params   : WORD width :  width of the character in pixels(bits)
 *            WORD phase :  current deviation from byte alignment (pixels)
 *            WORD height:  height of character(pixels)
 *
 * returns:   WORD phase :  new deviation from byte alignment (pixels)
 *
 * side effects : modifies pointers lp1 and lp2 (pointing to font body and work
 *                space respectively)
 *
 ****************************************************************************/
DWORD PASCAL
ConvertToFileFormat(
     DWORD width,
     DWORD phase,
     DWORD height
     )
{
      INT w;

      for (w = width; w > 0; w -= 8){  /* for each byte of font */
           if (phase == 0){             /* easy case */

               BYTE b;

               b = *lp1++;
               if (w < 8){

                   phase = (DWORD) w;
                   b >>= 8 - w;    /* Clear left side bits */
                   b <<= 8 - w;
               }
               *lp2 = b;
           }
           else{

               DWORD j;

               lp1--;          /* Re-read byte prevously read */
               //j = (DWORD) ((BYTE)*lp1++ << 8) | ((BYTE)*lp1++);
               j = (DWORD)((BYTE)*lp1++ << 8);
               j |= (DWORD) ((BYTE)*lp1++);
               if (w < 8){

                   j >>= 16 - phase - (w & 7);  /* shove it right */
                   j <<= 8 - (w & 7); /* Left justify in low byte */
                   phase += (DWORD) w;
                   if (phase <=  8)
                       lp1--;         /* back up pointer */
                   phase &= 7;
               }
               else
                   j >>= 8 - phase;
               *lp2 = (BYTE)j;
           }
           lp2 += height;          /* move to next column */
       }
       return phase;
}

/****************************************************************************
 * char * FontSave()
 *
 * purpose: saves the work bitmap in the required font file format (2.0 or
 *          3.0 and cleans up
 *
 * params : none
 *
 * returns: ptr to a NULL string if Save goes off OK
 *          ptr to error message string otherwise
 *
 * side effects: lots
 *
 ****************************************************************************/

CHAR *
FontSave(
CHAR *pszFileName,
OFSTRUCT*pofsReOpenInfo
    )
{
    DWORD bytecount;       /* number of bytes returned by lwrite */
    DWORD size;            /* total size of font */

    WORD height, row;
    DWORD i, fontlen;
    DWORD cBody, cFont,cFontsav, cFace;
    CHAR *lpFont;          /* ponter to font body */
    CHAR * sz;
    DWORD iMax;
    WORD widthsav;
PBYTEpjGlyphData;
PBYTEpjGlyphSave;
    GLYPHINFO_20 gi2GlyphTable20;    /* 2.0 style Glyph data struct */
    GLYPHINFO_30 gi3GlyphTable30;    /* 3.0 style Glyph data struct */

    NewAverage(); /* force ave width to be recomputed 8/17/87 BobM */

    /* reset file pointer */
nNewFile = (HFILE)OpenFile (pszFileName, pofsReOpenInfo, OF_WRITE | OF_REOPEN);

if (nNewFile < (HFILE) 0) {

return vszErrorOpeningFile;
}

    /* Put up an houglass ... this may take a while */
    if (!hHourGlass)
        hHourGlass = LoadCursor (NULL, IDC_WAIT);        /* Get Hourglass */
    hOldCursor = SetCursor (hHourGlass);         /* Show hourglass */

    height = font.PixHeight;
    cBody = (DWORD)height * (DWORD)font.WidthBytes;

    /* Recompute file size and update header */
    if (iFontFormat == ID_FORMAT2)
       cHeader = (WORD)(lSizeOfOldFontHeader +1);
    else
       cHeader = (WORD)(lSizeOfOldFontHeader30 - 1);

    /* if of 2.0 type, check if size will exceed 64kbytes. If yes, saving
       in 2.0 format will result in loss of information (because offset table
       of 2.0 file is composed of 16bit words). Warn user and ask if file can
       be saved as a 3.0 file */

    if (iFontFormat == ID_FORMAT2)
    {
if (((DWORD)cHeader + (DWORD)(lSizeOfOldGlyph20 * (font.LastChar -
font.FirstChar+2)) + (DWORD)cBody) >= WORD_LIMIT)
               if (MessageBox(hBox, vszTooBigFor20, vszWarning,
                              IDOK | IDCANCEL |IDNO) == IDYES)
                    iFontFormat = ID_FORMAT3;
}

if (iFontFormat == ID_FORMAT2) {

/* allocate space for a 2.0 style offsets table */
cTable = (WORD)(lSizeOfOldGlyph20 *
(font.LastChar - font.FirstChar + 2));

} else {

/* allocate space for a 3.0 style offsets table */
cTable = (WORD)(lSizeOfOldGlyph30 *
(font.LastChar - font.FirstChar + 2));
}

pjGlyphData = (LPSTR)GlobalAlloc (GMEM_ZEROINIT, (DWORD)cTable);

if (pjGlyphData == 0) {

_lclose((HFILE)nNewFile);
return vszNotEnoughMem;
}

pjGlyphSave = pjGlyphData;

    size = cHeader + cTable;
    iMax = font.LastChar - font.FirstChar + 1;

    // Recreate maximum character width when the font is proportional
    // since the WIDTH.. command may grow maxwidth but may not reduce.
    if (font.Family & 1)
font.MaxWidth = 0;

    /* create offsets table of font file */
    for (i = 0; i <= iMax; i++){

         DWORD width, charSize;

         width = offsets[i + font.FirstChar + 1] - offsets[i + font.FirstChar];

         if (i == iMax) {
             width = 8;      /* Sentinal blank */
 }

         if (iFontFormat == ID_FORMAT2){

            gi2GlyphTable20.GIwidth  = (SHORT)width;
            gi2GlyphTable20.GIoffset = (SHORT)size;

vBufferFromGlyphInfo20 (&gi2GlyphTable20, pjGlyphData);

pjGlyphData += lSizeOfOldGlyph20;

         } else {

            gi3GlyphTable30.GIwidth  = (SHORT)width;
            gi3GlyphTable30.GIoffset = (INT)size;

vBufferFromGlyphInfo30 (&gi3GlyphTable30, pjGlyphData);

pjGlyphData += lSizeOfOldGlyph30;
         }
        // update max width
        if(font.Family & 1 ) {
            if(width > font.MaxWidth )
                font.MaxWidth = (WORD)width ;
        }
        if(i == 0x81 && font.CharSet == SHIFTJIS_CHARSET) {
            if(width * 2 > font.MaxWidth) {
                font.MaxWidth = (WORD)width * 2 ;
            }
        }

         charSize = height * ((width + 7) >> 3);  /* size in bytes */

         if ((size + charSize) < size){           /* Overflow? */

             GlobalFree (pjGlyphData);
             _lclose((HFILE)nNewFile);
             return vszFileTooLarge;

         }
         size += charSize;
    }

    /* Update stuff in the header */

    font.Face = (DWORD)size;
    cFace = (WORD)lstrlen (szFaceName) + 1;   /* Allow for \0 */

size += cFace;
    font.Size = (DWORD)size;                                /* new file size */
    font.BitsOffset = (DWORD)(cHeader + cTable);
    font.Device = (DWORD)NULL;                   /* Device Name must be NULL */
    cFontsav = size - cHeader - cTable;
    cFont =cFontsav;

/* alloc extra byte for lp1 in case it needs it */
    if (!(lpFontBody = (LPSTR)GlobalAlloc (GMEM_ZEROINIT, (LONG)cBody +
height * 4))) {

_lclose((HFILE)nNewFile);
        return vszNotEnoughMem;
}

    GetBitmapBits (hBitmap, (DWORD)cBody, lpFontBody);

    /* save current WidthBytes */
    widthsav = font.WidthBytes;

    /*  MD - reset WidthBytes from computed width */
    font.WidthBytes = (WORD) (cFont - cFace)/ height;

    /* Allocate a block to put bitmap into */
    if ((lpFont = lpWork = GlobalAlloc (GMEM_ZEROINIT,(LONG)cFont)) == 0)
{
        GlobalFree (lpFontBody);
_lclose((HFILE)nNewFile);
        return vszNotEnoughMem;
}

    lp1 = lpFontBody;

    /* convert bitmap to file format */
    if (iFontFormat == ID_FORMAT2){   /* offsets table in 2.0 format */
        INT nChars;
        nChars =  font.LastChar - font.FirstChar +1;

        for (row = 0; row < height; row++){

            DWORD phase;

            phase = 0;

            pjGlyphData = pjGlyphSave;

            for (i = 0; i < (DWORD) nChars; i++) {

                INT width;

vGlyphInfo20FromBuffer (pjGlyphData, &gi2GlyphTable20);

                width = gi2GlyphTable20.GIwidth;

                lp2 = (BYTE  *)(lpWork + (gi2GlyphTable20.GIoffset -
cHeader - cTable + row));

pjGlyphData += lSizeOfOldGlyph20;

                phase = ConvertToFileFormat (width, phase, height);
            }
            if ((lp1 - lpWork) & 1)
                 lp1++;             /* Round lp1 up to Word Boundary */
#ifdef DWORDROUND
            if ((lp1 - lpWork) & 2) {
                 lp1++;             /* Round lp1 up to DWord Boundary */
                 lp1++;             /* Round lp1 up to DWord Boundary */
}
#endif
            //if(((offsets[font.LastChar + 1] + 7) >> 3) & 1)
                //lp1++;          /* Round lp1 up to Word Boundary */
        }
    }
     /* separate loops written for the 2.0 and 3.0 font processing because
        a single loop would involve too many checks (of font type) within
        the loop and slow down processing  */
    else{  /* table in 3.0 format */

        INT nChars;
        nChars =  font.LastChar - font.FirstChar +1;
        for (row = 0; row < height; row++){

            DWORD phase;

            phase = 0;
pjGlyphData = pjGlyphSave;

            for (i = 0; i < (DWORD) nChars; i++) {

                INT width;

vGlyphInfo30FromBuffer (pjGlyphData, &gi3GlyphTable30);

                width = gi3GlyphTable30.GIwidth;

                lp2 = (BYTE  *)(lpWork + (gi3GlyphTable30.GIoffset -
cHeader - cTable + row));

pjGlyphData += lSizeOfOldGlyph30;

                phase = ConvertToFileFormat (width, phase, height);
            }
            if ((lp1 - lpWork) & 1)
                 lp1++;             /* Round lp1 up to Word Boundary */
#ifdef DWORDROUND
            if ((lp1 - lpWork) & 2) {
                 lp1++;             /* Round lp1 up to DWord Boundary */
                 lp1++;             /* Round lp1 up to DWord Boundary */
}
#endif
            //if(((offsets[font.LastChar + 1] + 7) >> 3) & 1)
                //lp1++;          /* Round lp1 up to Word Boundary */
        }
    }

/* Restore start of data. */
pjGlyphData = pjGlyphSave;

    lp2 -= height - 1;              /* Back up to start of character */
    for (i = 0; i < height; i++)
         *lp2++ = 0;             /* Fill in guaranteed blank character */

    font.Version = 0x200;

    /******  code for compaction in 3.0 fmt borrowed from CMPCTFON ******/
    if (iFontFormat== ID_FORMAT3){
        DWORD iDefBitmapSize = 0;     /* size of default char */
        DWORD cch = 0;                /* count of number of default char */
#if 0
        GLYPHINFO_30  *cur_char;  /* current element of offset table */
        GLYPHINFO_30  *move_char;
            /* element from which moving is to be done */
        LONG iDefBitmapOffset;        /* offset of default char */
        INT iDefBitmapWidth;          /* width of default char */
        static LONG iEndOfBitmaps;    /* address of end of font body */
        INT iFirstC;                  /* first char in font */
        INT iLastC;                   /* last char in font */
        INT i,j;
        WORD width;
        CHAR  *rgbFont;         /* pointer to font body */
        DWORD Offset;

        GDI seems to understand compressed 2.0 external formats but not
        compressed 3.0 formats, and this is causing it to crash loading a 3.0
        format compressed font. Since compression does not affect the
        validity of the font, This code is disabled temporarily till
        this problem is verified. Again, the font is perfectly usable in this
        form, though a trifle bigger than desired -  LR 20/26/90

Note that this will now not work after the conversion to NT.
the glyph table needs to accessed as above.  t-davema 8/20/91

        iFirstC = font.FirstChar & 0x0ff;
        iLastC  = font.LastChar & 0x0ff;

        iEndOfBitmaps = (LONG)font.BitsOffset+((LONG)font.WidthBytes *
                (LONG)font.PixHeight);
        rgbFont = (CHAR  *)lpFont;   /* start of font body */
        cur_char =(GLYPHINFO_30  *) lpTable; /* start of offset table */

        /* calculate some parameters for the default char */
        iDefBitmapOffset = cur_char[font.DefaultChar].GIoffset;
        iDefBitmapWidth  = cur_char[font.DefaultChar].GIwidth;
        iDefBitmapSize   = (DWORD)cur_char[font.DefaultChar+1].GIoffset
                                           - (DWORD)iDefBitmapOffset;

        /* scan the font body via the offsets table. If a default char
           is recognised, move all the bytes to it's right left by the
           size of the default char.Make all offset table entries of default
           char point to one image of the char (the earliest occuring image) */
        for (i = iFirstC; i <= iLastC; ++i) {

            /* important: Check for limiting conditions (in case break char
               was modified?)*/
            if (cur_char->GIoffset == iDefBitmapOffset){
                cur_char++;
                continue;
            }

            Offset = cur_char->GIoffset -cHeader -cTable;
            /* proceed to compare images only if widths are equal */
            if ((cur_char->GIwidth  == iDefBitmapWidth) &&
                (ByteCompare (&rgbFont [Offset],
                &rgbFont[iDefBitmapOffset-cHeader-cTable],
                                (DWORD)iDefBitmapSize) == TRUE)){

                /* set offset to earliest occurence of the default char */
                if (cur_char->GIoffset <  iDefBitmapOffset){
                    iDefBitmapOffset = cur_char->GIoffset;
                }
                else    {
                    if (i != iLastC){
                        /* move bytes to right of default char left by the
                           size of the char */
                        ByteCopy (&rgbFont [ Offset],
                                  &rgbFont [ Offset+ iDefBitmapSize],
                                  (LONG)(iEndOfBitmaps -
                                  ((cur_char + 1)->GIoffset)));

                        /* correct the offset table entries */
                        move_char = cur_char + 1;
                        for (j=i; j < iLastC; ++j){
                             move_char->GIoffset -= (LONG)iDefBitmapSize;
                             move_char++;
                        }
                    }
                    iEndOfBitmaps -= iDefBitmapSize;
                    /* move End-of-font to the left */
                    cur_char->GIoffset = iDefBitmapOffset;
                    /* point offset of cuurent char to default char */
                    cch++;
                }
            }
            cur_char++;
        }
#endif
        /* recalculate some font attributes */
        lp2   -= (cch * iDefBitmapSize + height);
        for (i = 0; i < height; i++)
            *lp2++ = 0;             /* Fill in guaranteed blank character */

        cFont -= cch * iDefBitmapSize;
        font.WidthBytes = (WORD) (cFont - cFace)/ height;
        font.Size = (DWORD) (cFont + cHeader + cTable);
        font.Face = font.Size - cFace ;
        font.Version = 0x300;

        /* copy info into 3.0 (new) format header and set the additional
           fields */
        ByteCopy ((LPSTR)&font30, (LPSTR)&font, (DWORD)sizeof (font));
        font30.fsFlags        = 0;
        font30.fsFlags = (font.Family & 1 ? FSF_PROPORTIONAL : FSF_FIXED);
        font30.fsAspace       = 0;
        font30.fsBspace       = 0;
        font30.fsCspace       = 0;
        font30.fsColorPointer = 0L;
        for (i = 0; i < 4 ; i++)
            font30.fsReserved[i] = 0L;
    }

    /* Add the FaceName, if any, to the end of the bitmap */
    lstrcpy((LPSTR)lp2, (LPSTR)szFaceName);

/*
 * Again we need to do some tricky things to get the header output
 * correct.  We want to run it through the conversion backwards until
 * we get the packed structure.
 */
{
BYTE jOutputBuffer [sizeof (font30)];

if (iFontFormat == ID_FORMAT2) {

vBufferFromFontStruct (&font, (PBYTE)&jOutputBuffer);

} else {

vBufferFromFont30Struct (&font30, (PBYTE)&jOutputBuffer);
}

bytecount = _lwrite((HFILE)nNewFile, (PBYTE)&jOutputBuffer, (DWORD)cHeader);
}

    /* Write out Header Information */
    if (bytecount == cHeader) {

            /* Write out OffsetsTable */
         if (cTable == 0 || (_lwrite((HFILE)nNewFile, pjGlyphData, (DWORD)cTable)
== (UINT)cTable)){
              /* Write out Body */
              fontlen = cFont;
              while (fontlen >= SEGMENT_SIZE){

                 /* file write method if font size is 64k or greater
                 file is written in chunks of 65536 bytes (SEGMENT_SIZE)-lr */

                 /* First write as many bytes as _lwrite will allow(65534) */
                  if (_lwrite((HFILE)nNewFile, (CHAR  *)lpFont, WORD_LIMIT) ==
                        WORD_LIMIT){
                      fontlen -= WORD_LIMIT;
                            /* fontlen = no of bytes left to write*/
                      lpFont+= WORD_LIMIT; /* buffer ptr moved up by 64k */
                  }
                  else
                      sz = vszErrorWritingBody;

                  /* write the two bytes remaining in the segment */
                  if (_lwrite((HFILE)nNewFile, (CHAR  *)lpFont, 2) == 2){
                      fontlen -= 2;  /* fontlen = no of bytes left to write*/
                      lpFont+= 2;    /* buffer ptr moved up by 2 */
                  }
                  else
                      sz = vszErrorWritingBody;
              }
              /* segment only partially filled. Write the remaining bytes */
              if (_lwrite((HFILE)nNewFile, (CHAR  *)lpFont, (DWORD)fontlen) ==
                    (UINT) fontlen){
                   fChanged = FALSE;
                   sz= "";
              }
              else
                  sz = vszErrorWritingBody;
         }
         else
             sz = vszErrorWritingOffsets;
    }
    else
         sz = vszErrorWritingHdr;

    /* hack: Restore saved value of widthbytes, eliminating a variety
       of minor scrolling problems that follow after a File/Save */
    font.WidthBytes = widthsav;

    _lclose((HFILE)nNewFile);

/* Tidy up */

    GlobalFree(lpFontBody);
    GlobalFree(pjGlyphData);
    GlobalFree(lpWork);

    SetCursor(hOldCursor);              /* Restore regular cursor */

    return sz;
}

/****************************************************************************
 * BOOL ResizeWidths(wChar)
 *
 * params : WORD wChar : new width of a single character
 *
 * purpose: resize work bitmap according to new character width by stretching/
 *          compressing all characters as neccesary
 *
 * returns: none
 *
 * side effects: Work bitmap changes.Some header info (regarding font dimensions
 *               altered as well
 ****************************************************************************/

BOOL
ResizeWidths(
    DWORD wChar
    )
{
    DWORD width;
    DWORD offset, i;

    /* Create a new bitmap to move the font definition bits into */
    width = (font.LastChar - font.FirstChar + 1) * wChar; /* In pixels */
             // ResizeWidths(): width byte is DWORD align
    width = CJ_DIB_SCAN(width);
    if (!GetNewMap(width, font.PixHeight))
        return (FALSE);

    /* Move the bits in */
    offset = 0;
    oldMode = SetStretchBltMode(hNewMemDC, COLORONCOLOR);
    for (i = font.FirstChar; i <= font.LastChar; i++)
        {

        StretchBlt(hNewMemDC, offset, 0,
                wChar, font.PixHeight,                  /* New character */
                hMemDC, offsets[i], 0,
                font.PixWidth, font.PixHeight,          /* Old character */
                SRCCOPY);
        offsets[i] = offset;
        offset += wChar;
        }
    SetStretchBltMode(hNewMemDC, oldMode);
    offsets[font.LastChar + 1] = offset;

    UseNewMap();                /* Switch Pointers and release the space */

    font.HorizRes = (WORD) Proport(font.HorizRes, wChar, font.PixWidth, 999);
    font.WidthBytes = (WORD) width;            /* Misc. ajustments */
    font.PixWidth = font.AvgWidth = (font.MaxWidth = (WORD) wChar);
    font.MaxWidth = (WORD) wChar * 2;// set MaxWidth as DBCS width.

    return (TRUE);
}

/****************************************************************************
 * BOOL SpreadWidths(wChar)
 *
 * purpose: spread/compress work bitmap (with variable width characters)
 *          in proportion with the maximum character width
 *
 * params : WORD wChar : new width of a character
 *
 * returns: none
 *
 * side effects: Work bitmap changes.Some header info (regarding font dimensions
 *               altered as well
 ****************************************************************************/

BOOL
SpreadWidths(
    DWORD wChar
    )
{
    DWORD offset, i;
    DWORD width, oldWidth, newWidths[257];

    /* Create a new bitmap to move the font definition bits into */
    width = 0;          /* Compute the new width */
    for (i = (DWORD) font.FirstChar; i <= (DWORD) font.LastChar; i++)
        {
        oldWidth = offsets[i + 1] - offsets[i];
        /* Compute new width to nearest whole number */
        newWidths[i] = Proport(oldWidth, wChar, font.MaxWidth, wBoxLim - 1);
        width += newWidths[i];
        }
             // SpreadWidths(): byte width is DWORD align
    width = CJ_DIB_SCAN(width);
    if (!GetNewMap(width, font.PixHeight))
        return(FALSE);

    /* Move the bits in */
    offset = 0;
    oldMode = SetStretchBltMode(hNewMemDC, COLORONCOLOR);
    for (i = font.FirstChar; i <= font.LastChar; i++)
        {
        oldWidth = offsets[i + 1] - offsets[i];
        StretchBlt(hNewMemDC, offset, 0,
                newWidths[i], font.PixHeight,           /* New character */
                hMemDC, offsets[i], 0,
                oldWidth, font.PixHeight,               /* Old character */
                SRCCOPY);
        offsets[i] = offset;
        offset += newWidths[i];
        }
    SetStretchBltMode(hNewMemDC, oldMode);
    offsets[font.LastChar + 1] = offset;

    UseNewMap();                /* Switch Pointers and release the space */
    font.HorizRes = (WORD) Proport(font.HorizRes, wChar, font.MaxWidth, 999);
    font.WidthBytes = (WORD) width;            /* Misc. ajustments */
    font.MaxWidth = (WORD) wChar;
    NewAverage();                       /* Compute new average width */

    return (TRUE);
}

/****************************************************************************
 * NewAverage()
 *
 * purpose: recalculate average width of a character in font
 *
 * params : none
 *
 * returns: none
 *
 * side effects: alters the average width parameter in font header
 *
 ****************************************************************************/
VOID
NewAverage(
    VOID
    )
{
#ifdef FOO
    WORD i, totalwidth;
    /* 12/23/85 -- use weighted avg of lower case letters (mikecr) */

    /* width of the space */
    totalwidth = (offsets[' ' + 1] - offsets[' ']) * widthweights[ALPHA_CNT];

    for (i = 0; i < ALPHA_CNT; i++)
            totalwidth += (offsets['a' + i + 1] - offsets['a' + i]) *
            widthweights[i];

    font.AvgWidth = totalwidth / TOTAL_WEIGHTS;

    /* round up if necessary */
    if (totalwidth % TOTAL_WEIGHTS >= (TOTAL_WEIGHTS >> 1))
            font.AvgWidth++;

#endif

    /* lets do a simple average here */
    font.AvgWidth = (WORD) (((offsets[font.LastChar+1] -
            offsets[font.FirstChar]) + (font.LastChar - font.FirstChar)/2) /
            (font.LastChar - font.FirstChar + 1));

    if (font.AvgWidth == 0) {
       font.AvgWidth++;
    }
    if (font.AvgWidth < 1) {
        font.AvgWidth = 1;
    }
}

/****************************************************************************
 * BOOL ResizeBody(width, height)
 *
 * purpose: adjust work bitmap according to new specified dimensions
 *
 * params : WORD width  : new width of font in pixels
 *          WORD height : new height of font in pixels
 *
 * returns: none
 *
 * side effects: Work bitmap changes.Some header info (regarding font dimensions
 *               altered as well
 ****************************************************************************/

BOOL
ResizeBody(
    DWORD width,
    DWORD height
    )
{

    /* Create a new bitmap to move the font definition bits into */
    if (!GetNewMap(width, height))
        return(FALSE);

    /* Move the bits in */
    oldMode = SetStretchBltMode(hNewMemDC, COLORONCOLOR);
    StretchBlt(hNewMemDC, 0, 0,
            width << 3, height,         /* New Char. */
            hMemDC, 0, 0,
            width << 3, font.PixHeight, /* Old Char. */
            SRCCOPY);
    SetStretchBltMode(hNewMemDC, oldMode);

    UseNewMap();                /* Switch Pointers and release the space */

    font.ExtLeading = (WORD) Proport(font.ExtLeading, height, font.PixHeight, 999);
    font.IntLeading = (WORD) Proport(font.IntLeading, height, font.PixHeight, 999);
    font.Ascent = (WORD) Proport(font.Ascent, height, font.PixHeight, 32);
    font.VertRes = (WORD) Proport(font.VertRes, height, font.PixHeight, 999);
    font.Points = (WORD) Proport(font.Points, height, font.PixHeight, 999);
    font.PixHeight = (WORD) height;            /* Fix misc. header values */
    font.WidthBytes = (WORD) width;

    return (TRUE);
}

/****************************************************************************
 * BOOL NewFirstChar(first)
 *
 * purpose: redefines first character in font and resizes work bitmap
 *          accordingly
 *
 * params : WORD first : new first character to be defined
 *
 * returns: none
 *
 * side effects: Work bitmap changes.Some header info (regarding font dimensions
 *               altered as well
 ****************************************************************************/

BOOL
NewFirstChar(
    DWORD first
    )
{
    DWORD  width, wDefault;
    DWORD offset, i;
    INT dw;

    if (first > font.FirstChar)         /* Smaller? */
        {
        ShrinkFont(first, font.LastChar);
        font.FirstChar = (BYTE) first;
        /*return(FALSE);*/
        return(TRUE);
        }

    /* If not smaller we must pad with the default character */
    wDefault = offsets[font.DefaultChar + 1] - offsets[font.DefaultChar];
    dw = wDefault * (font.FirstChar - first);           /* Extra width */
    width = offsets[font.LastChar + 1] + dw;    /* New width (pixels) */
             // NewFirstChar(): byte width is DWORD align
    width = CJ_DIB_SCAN(width);
    if (!GetNewMap(width, font.PixHeight))
        return(FALSE);           /* New work area */

    /* Move it in in two parts */
    /* First move in default characters */
    offset = 0;
    for (i = first; i < font.FirstChar; i++)
        {
        BitBlt(hNewMemDC, offset, 0,
                wDefault, font.PixHeight,
                hMemDC, offsets[font.DefaultChar], 0,
                SRCCOPY);
        offsets[i] = offset;
        offset += wDefault;
        }
    /* Now move in the rest */
    BitBlt(hNewMemDC, offset, 0,
            offsets[font.LastChar + 1], font.PixHeight,
            hMemDC, 0, 0,
            SRCCOPY);

    UseNewMap();                /* Switch Pointers and release the space */

    /* Now fix up offsets table */
    for (i = font.FirstChar; i <= (DWORD)(font.LastChar + 1); i++)
        offsets[i] = offsets[i] + dw;           /* Shift the rest right */
    font.WidthBytes = (WORD) width;
    font.FirstChar = (BYTE) first;

    return (TRUE);
}

/****************************************************************************
 * ShrinkFont(first, last)
 *
 * purpose:  redefine the first and last charcter in the font and shrink
 *           work bitmap accordingly
 *
 * params :  WORD first : new first character to be defined
 *           WORD last  : new last character  "  "   "
 *
 * returns:  none
 *
 * side effects: Work bitmap changes.Some header info (regarding font dimensions
 *               altered as well
 ****************************************************************************/

VOID
ShrinkFont(
    DWORD first,
    DWORD last
    )
{
    DWORD width, widthPixels;
    DWORD i;
    INT dw;

    dw = offsets[first] - offsets[font.FirstChar];      /* left shift if any */
    widthPixels = offsets[last + 1] - offsets[first];   /* Width in pixels */
             // ShrinkFont(): byte width is DWORD align
    width = CJ_DIB_SCAN(widthPixels);
    if (!GetNewMap(width, font.PixHeight))
        return;           /* New work area.*/

    /* Now move the font into the reduced space */

    BitBlt(hNewMemDC, 0, 0,
            widthPixels, font.PixHeight,
            hMemDC, offsets[first], 0,
            SRCCOPY);

    UseNewMap();                /* Switch Pointers and release the space */

    if (dw)                             /* Ajust offsets */
        {
        for (i = first; i <= last + 1; i++)
            offsets[i] -= dw;
        }

    font.WidthBytes = (WORD) width;

}

/****************************************************************************
 * BOOL NewLastChar(last)
 *
 * purpose:  redefines the last character in the font
 *
 * params :  WORD last : number of character to be made the last character
 *
 * returns:  none
 *
 * side effects: Work bitmap changes.Some header info (regarding font dimensions
 *               altered as well
 *
 ****************************************************************************/

BOOL
NewLastChar(
    DWORD last
    )
{
    DWORD  width, wDefault;
    DWORD offset, i;
    INT dw;

    if (last < font.LastChar)           /* Smaller? */
        {
        ShrinkFont(font.FirstChar, last);
        font.LastChar = (BYTE) last;
        return(FALSE);
        }

    /* If not smaller we must pad with the default character */
    wDefault = offsets[font.DefaultChar + 1] - offsets[font.DefaultChar];
    dw = wDefault * (last - font.LastChar);             /* Extra width */
    offset = offsets[font.LastChar + 1];        /* Current end */
    width = offset + dw;                        /* New width (pixels) */
             // NewLastChar(): byte width is DWORD align
    width = CJ_DIB_SCAN(width);
    if (!GetNewMap(width, font.PixHeight))
        return(FALSE);           /* New work area */

    /* Move it in in two parts */
    /* First move in the existing font */
    BitBlt(hNewMemDC, 0, 0,
            offset, font.PixHeight,
            hMemDC, 0, 0,
            SRCCOPY);
    /* Then move in default characters */
    for (i = font.LastChar + 1; i <= last;)
        {
        BitBlt(hNewMemDC, offset, 0,
                wDefault, font.PixHeight,
                hMemDC, offsets[font.DefaultChar], 0,
                SRCCOPY);
        offset += wDefault;
        offsets[++i] = offset;
        }

    UseNewMap();                /* Switch Pointers and release the space */

    font.WidthBytes = (WORD) width;
    font.LastChar = (BYTE) last;

    return (TRUE);
}

/****************************************************************************
 * BOOL CharWidth(iChar, wBox)
 *
 * purpose: resizes selected char according to new dimensions. (only for
 *          variable pitch)
 *
 * params : BYTE iChar : character to resize
 *          WORD wBox  : new width of char in pixels
 *
 * returns: none
 *
 * side effects: work bitmap pixel values and header info(regarding font
 *               dimensions) altered
 *
 ****************************************************************************/

BOOL
CharWidth(
    BYTE iChar,                             /* Character to change */
    DWORD wBox                               /* New width */
    )
{
    DWORD  width, nChars;
    DWORD  w1, w2, i;
    INT dw;

    nChars = font.LastChar - font.FirstChar + 1;        /* Character count */
    dw = wBox - (offsets[iChar + 1] - offsets[iChar]);  /* Width change */
    width = offsets[font.LastChar + 1] + dw;            /* New width (pixels) */
             // CharWidth(): byte width is DWORD align
    width = CJ_DIB_SCAN(width);
    if (!GetNewMap(width, font.PixHeight))
        return(FALSE);                   /* New work area */

    /* Move it in in two parts */
    /* First move up to and including iChar */
    w1 = offsets[iChar + 1];            /* Width (in pixels) to move */
    BitBlt(hNewMemDC, 0, 0,
            w1 + dw, font.PixHeight,
            hMemDC, 0, 0,
            SRCCOPY);
    /* Now move in the rest */
    if (iChar < (BYTE) font.LastChar)        /* Part to right of elision */
        {
        w2 = offsets[font.LastChar + 1] - offsets[iChar + 1];
        BitBlt(hNewMemDC, offsets[iChar] + wBox, 0,
                w2, font.PixHeight,
                hMemDC, offsets[iChar + 1], 0,
                SRCCOPY);
        }

    UseNewMap();                /* Switch Pointers and release the space */

    /* Now fix up offsets table */
    for (i = iChar + 1;                         /* Where changes start */
        i <= (DWORD)(font.LastChar + 1); i++)            /* Ajust offsets */
        offsets[i] = offsets[i] + dw;           /*  .. by adding dw */
    font.WidthBytes = (WORD) width;
    NewAverage();

    return (TRUE);
}

/****************************************************************************
 * BoxToClipboard(ptA, width, height)
 *
 * purpose: write char (or part of it) to clipboard
 *
 * params : POINT ptA    : upper left coordinate
 *          DWORD width  : width of char in pixels
 *          DWORD height : height of char in pixels
 * returns: none
 *
 * side effects: none
 *
 ****************************************************************************/
VOID
BoxToClipboard(
    POINT ptA,                               /* Upper left point */
    DWORD width,
    DWORD height                      /* Size */
    )
{
    HDC hDC;
    DWORD x, y;

    hDC = GetDC(hFont);                         /* DC to be compatible with */
    hNewBitmap = CreateBitmap(
                    width, height,
                    1, 1, (LPBYTE)NULL);
    hNewMemDC = CreateCompatibleDC(hDC);                /* Create a DC */
    SelectObject(hNewMemDC, hNewBitmap);                /* Relate them */
    ReleaseDC(hFont, hDC);                      /* Done with font DC */

    for (x = 0; x < width; x++)
        for (y = 0; y < height; y++)
            SetPixel(hNewMemDC, x, y, matBox[x + ptA.x][y + ptA.y] == TRUE ?
            BLACK : WHITE);

    /* Now wake up Clipboard and empty it */
    if (!OpenClipboard(hFont))
        ErrorBox(hBox, vszCannotOpenClip); // , vszCopyingToClip);
    else        /* Ok: We got the Clipboard */
        {
        EmptyClipboard();
        SetClipboardData(CF_BITMAP, hNewBitmap);        /* Tell Clipboard */
        }

    /* Tidy things up */
    CloseClipboard();
    DeleteDC(hNewMemDC);
}

/****************************************************************************
 * WORD ClipboardToBox(ptA, width, height, fFit)
 *
 * purpose: copies char (or part of char ) from clipboard to work bitmap
 *          stretching it if need be
 *
 * params : PIONT ptA    : upper left coordinate
 *          DWORD width  : width of char in pixels

*          DWORD height : height of char in pixels
 *          BOOL fFit   : flag to indicate if default width is to be used
 * returns: none
 *
 * side effects: pixel values of bitmap may change for char
 *
 ****************************************************************************/

DWORD
ClipboardToBox(
    POINT ptA,                               /* Upper left point */
    DWORD width,
    DWORD height,                     /* Size */
    BOOL fFit                               /* Use default width if TRUE */
    )
{
    BITMAP bitmap;
    HDC hDC;
    DWORD x, y;
    HANDLE hT;

    if (!OpenClipboard(hFont)) {
        ErrorBox(hBox, vszCannotOpenClip);
        return 0;
    }
    hNewBitmap = GetClipboardData(CF_BITMAP);

    /* Check if we got something like a character */
    if (GetObject(hNewBitmap, sizeof(BITMAP), (LPSTR)&bitmap) != sizeof(BITMAP))
        {   /* What did we get */
        ErrorBox(hBox, vszErrorClip);
        CloseClipboard();
        return 0;
    }

    if (fFit && ((WORD)bitmap.bmWidth <= font.MaxWidth))
        width = bitmap.bmWidth;

    hDC = GetDC(hFont);                         /* DC to be compatible with */
    hBoxBitmap = CreateBitmap(
                    width, height,
                    1, 1, (LPBYTE)NULL);
    hBoxMemDC = CreateCompatibleDC(hDC);                /* Create a DC */
    hT = SelectObject(hBoxMemDC, hBoxBitmap);           /* Relate them */
    if (hT == NULL || hDC == NULL || hBoxBitmap == NULL || hBoxMemDC == NULL) {
        ErrorBox(hBox, vszErrorClip);
CloseClipboard();
DeleteDC(hBoxMemDC);
DeleteObject(hBoxBitmap);
        return 0;
    }

    /* Get a DC to relate to the Bitmap we just got */
    hNewMemDC = CreateCompatibleDC(hDC);                /* Create a DC */
    hT = SelectObject(hNewMemDC, hNewBitmap);           /* Relate them */
    ReleaseDC(hFont, hDC);                      /* Done with font DC */
    if (hT == NULL || hNewMemDC == NULL) {
        ErrorBox(hBox, vszErrorClip);
CloseClipboard();
DeleteDC(hNewMemDC);
DeleteDC(hBoxMemDC);
DeleteObject(hBoxBitmap);
        return 0;
    }

    /* Now StretchBlt whatever was on the clipboard into the character */
    oldMode = SetStretchBltMode(hBoxMemDC, COLORONCOLOR);
    fFit = StretchBlt(hBoxMemDC, 0, 0, width, height,
                hNewMemDC, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
    if (!fFit || !oldMode) {
        ErrorBox(hBox, vszErrorClip);
CloseClipboard();
DeleteDC(hNewMemDC);
DeleteDC(hBoxMemDC);
DeleteObject(hBoxBitmap);
        return 0;
    }
    (void)SetStretchBltMode(hBoxMemDC, oldMode);
    for (x = 0; x < width; x++)
        for (y = 0; y < height; y++)
            matBox[x + ptA.x] [y + ptA.y] = (CHAR)(GetPixel(hBoxMemDC, x, y) ?
                0 : 1);
    /* Tidy things up */
    DeleteDC(hNewMemDC);
    DeleteDC(hBoxMemDC);
    DeleteObject(hBoxBitmap);
    CloseClipboard();
    return(width);
}

/****************************************************************************
 * ToClipboard(iChar, width, height)
 *
 * purpose: write char in edit box to clipboard
 *
 * params : BYTE iChar  : number of char to be copied to clipboard
 *          DWORD width  : width of char in pixels
 *          DWORD height : height of char in pixels
 *
 * returns: none
 *
 * side effects: none
 *
 ****************************************************************************/

VOID
ToClipboard(
    BYTE iChar,
    DWORD width,             /* Here in Pixels */
    DWORD height             /* Also in Pixels */
    )
{
    HDC hDC;

    hDC = GetDC(hFont);                         /* DC to be compatible with */
    hNewBitmap = CreateBitmap(
                    width,                      /* Width of font in pixels */
                    height,
                    1, 1, (LPBYTE)NULL);
    hNewMemDC = CreateCompatibleDC(hDC);                /* Create a DC */
    SelectObject(hNewMemDC, hNewBitmap);                /* Relate them */
    ReleaseDC(hFont, hDC);                      /* Done with font DC */

    BitBlt(hNewMemDC, 0, 0, width, height,      /* Move Character in */
            hMemDC, offsets[iChar], 0, NOTSRCCOPY);

    /* Now wake up Clipboard and empty it */
    if (!OpenClipboard(hFont))
        ErrorBox(hBox, vszCannotOpenClip); // , vszCopyingToClip);
    else        /* Ok: We got the Clipboard */
        {
        EmptyClipboard();
        SetClipboardData(CF_BITMAP, hNewBitmap);        /* Tell Clipboard */
        }

    /* Tidy things up */
    CloseClipboard();
    DeleteDC(hNewMemDC);
}

/****************************************************************************
 * GetNewMap(width, height)
 *
 * purpose: create new bitmap of the given width and height.
 *
 * params : WORD width  : width of bitmap in pixels
 *          WORD height : height of bitmap in pixels
 *
 * returns: TRUE if successful, FALSE otherwise
 *
 * side effects: Handle and DC values of new DC assigned
 *
 ****************************************************************************/

BOOL
GetNewMap(
    DWORD width,
    DWORD height              /* New size */
    )
{
    HDC hDC;

    if (height==0) /* Check if something stupid is happening */
        height=font.PixHeight; /* Fix it */

    /* Put up an houglass ... this may take a while */
    if (!hHourGlass)
        hHourGlass = LoadCursor(NULL, IDC_WAIT);        /* Get Hourglass */
    hOldCursor = SetCursor(hHourGlass);         /* Show hourglass */

    /* Create a new bitmap to move the font definition bits into */
    hDC = GetDC(hFont);                         /* DC to be compatible with */
    hNewBitmap = CreateBitmap(
                    width << 3,                 /* Width of font in pixels */
                    height,
                    1, 1, (LPBYTE)NULL);
    if (!hNewBitmap)
        {
        ErrorBox(hBox, vszNotEnoughMem); // , vszAllocatingSpace);
        ReleaseDC(hFont, hDC);    /* bug# 2380 */
        return FALSE;
        }

    hNewMemDC = CreateCompatibleDC(hDC);                /* Create a DC */
    SelectObject(hNewMemDC, hNewBitmap);                /* Relate them */
    ReleaseDC(hFont, hDC);                      /* Done with font DC */
    PatBlt(hNewMemDC, 0, 0, width << 3, height, BLACKNESS);     /* Clear it */
    return TRUE;
}

/****************************************************************************
 * UseNewMap()
 *
 * params : none
 *
 * purpose: discard old bitmap and replace it with new one
 *
 * returns: none
 *
 * side effects: Handle to old bitmap and handle to old bitmap DC replaced
 *               by those of new bitmap respectively
 *
 ****************************************************************************/

VOID
UseNewMap(
    VOID
    )
{
    DeleteDC(hMemDC);
    DeleteObject(hBitmap);              /* Release old space */
    hBitmap = hNewBitmap;
    hMemDC = hNewMemDC;              /* Release old space */
    SetCursor(hOldCursor);              /* Restore regular cursor */
}

VOID
DeleteGlobalBitmap(
    VOID
    )
{
    if (hMemDC)
        DeleteDC(hMemDC);
    if (hBitmap)
        DeleteObject(hBitmap);
}

DWORD
Proport(
    DWORD value,
    DWORD top,
    DWORD bottom,
    DWORD limit
    )
    /* Reproportion a value by the ratio top/bottom to the nearest integer
     * and make sure we are still in range */
{
    return min(limit, (DWORD)((1 + 2 * value * top) / (2 * bottom)));
}


FCNTL.H

/*
 * fcntl.h
 *
 * defines file control options used by the open system call.
 *
 * Copyright (C) Microsoft Corporation, 1993-1996
 */

#define O_RDONLY        0x0000
#define O_WRONLY        0x0001
#define O_RDWR          0x0002
#define O_APPEND        0x0008  /* writes done at eof */

#define O_CREAT         0x0100  /* create and open file */
#define O_TRUNC         0x0200  /* open with truncation */
#define O_EXCL          0x0400  /* exclusive open */

/* O_TEXT files have <cr><lf> sequences translated to <lf> on read()'s,
** and <lf> sequences translated to <cr><lf> on write()'s
*/

#define O_TEXT          0x4000  /* file mode is text (translated) */
#define O_BINARY        0x8000  /* file mode is binary (untranslated) */


TYPECVT.C

/*++

Copyright 1993 - 1998 Microsoft Corporation

Module Name:

    typecvt.c

Abstract:

    Routines for generically converting between structure formats without
    regard to alignment boundries.

--*/

#include "windows.h"
#include <windowsx.h>
#include "typecvt.h"

// We want to assert errors for now.
#define ASSERT_ERRORS

#ifdef ASSERT_ERRORS
int sprintf ();
#endif

//
// For lack of any place better to put a general description of the type
// conversion functions, The following will attempt to explain what is
// needed set up for, and use, the functions.
//
// The main purpose of these functions is to convert non-aligned structures
// to aligned structures and back again.
//
// In order to convert bewteen two structures it is first necessary to define
// what each one looks like.  This is done most easily by statically allocating
// and array of StructDefineInfo or SDI structures.  Here is an example:
//
//      The two structures
//      struct                          struct
//      {                               {
//          CHAR    chElem1;                LONG    rglElem2 [10];
//          WORD    rgwElem2 [5];           ULONG   ulElem1;
//      } src;                          } dest;
//      could be described by.
//
//      SDI  rgsdiSrcStructDef [] = {
//          { sizeof (CHAR), sizeof (src.chElem1) },
//          { sizeof (WORD), sizeof (src.rgwElem2) },
//          { 0, 0 } };
//
//      SDI  rgsdiDestStructDef [] = {
//          { sizeof (LONG), sizeof (dest.rglElem2) },
//          { sizeof (ULONG), sizeof (dest.ulElem1) },
//          { 0, 0 } };
//
// Once both structures have been defined the conversion between the structures
// in different alignments and endian types can be performed with
// PerformConversion ().
//
// For an example of how this is implemented see Fontcvt.c
//

//
// An advantage of the conversions routines is that they are a portable
// method of handling machines with Big Endian integer storage.  See header
// file typecvt.h for complete description of big and little endian.
//
// The folowing variables are used to select the endian type of the source
// and the destination structures.  The default is Little Endian.
// Also, these are not meant to be accessed outside of this module.
// Routines have been defined to access these.
//

INT vfFileEndianType = CVT_FILE_ENDIAN_DEFAULT;
INT vfSysEndianType  = CVT_ENDIAN_UNKNOWN;// Set to unknown.

LONG
lCalculateStructOffsets (
      PSDI    rgsdiStructDefine,
      INT     fAlignmentType,
   INTcSizeOfStruct
    )

/*++

Routine Description:

    This function is used to calculate the offsets of each element in the
    Struct Define Info array.  These values are stored back int the SDI array
    which was passed to us.  The offsets are based on the alignment type
    sent to this routine.  It will also return the sizeof that structure as
    it would be with the alignment or -1 if there is an error.

Arguments:

    rgsdiStruceDefine   - Definition of this structure as an array of SDI
                          structures.  Note, see beginning of this file for
                          a complete description of how to define this
                          structure.

    fAlignmentType      - This is an optional parameter which can be used to
                          request the count of bytes for a particular alignment
  CVT_ALIGN_PACKED - Return the sizeof the structure
if the alignment were packed.
  CVT_ALIGN_WORD   - Return the sizeof the structure
if the alignment were WORD aligned.
  CVT_ALIGN_DWORD  - Return the sizeof the structure
if the alignment were DWORD aligned.
  Zero or any other value, return 0 on success.

cSizeOfStruct    - This value is used only for error detection.
  It should be the size of the structure you are
  converting in the current system alignment.
  (ex. sizeof (FontHeaderType)).  The function will
  compare this to it's calculate offset and return
  an error if they differ.
  Note that if this value is 0, no error checking will
  be performed.

Return Value:

    Postitive Value = The total size of the structure we are looking at.
            This should be the same as if we did a sizeof (struct) in the
            proper alignment.
    -1 = There was an error with the definition of the structure.
            Here is how errors are detected.  Along with determining all of
            the offsets in the specified alignment type, it will also do it
            in CVT_ALIGN_SYSTEM.  If the system alignment does not match the
            initial offsets, an error is reported.

--*/

{
    LONG   oPackedOffset = 0;     // Counter for packed offset.
    LONG   oWordOffset = 0;     // Counter for WORD aligned offset.
    LONG   oDWordOffset = 0;      // Counter for DWORD aligned offset.

//
// Make sure that the system endian type has been computed.
// We will probably need this for calls to PerformConversion.
//

if (vfSysEndianType == CVT_ENDIAN_UNKNOWN) {

vfSysEndianType = fDetermineSysEndianType ();
}

    //
    // Keep looping while there are defined elements.
    //

    while (rgsdiStructDefine->cTypeSize) {

        //
        // Make sure we are aligned to either the type boundry or the
        // alignment count, whichever is smaller.
        //

if (rgsdiStructDefine->cTypeSize >= 2) {

oWordOffset += (oWordOffset & 1);// Make sure both are on
oDWordOffset += (oDWordOffset & 1);// a WORD boundry.
}

if (rgsdiStructDefine->cTypeSize >= 4) {

// Add 2 to the DWORD alignment if it is not on a DWORD boundry.
// Note that the previous check would already have WORD aligned it.

oDWordOffset += (oDWordOffset & 2);

}

        //
        // Store the offset to that element within the structure itsself.
        //

        rgsdiStructDefine->oPackedAlign = oPackedOffset;
        rgsdiStructDefine->oWordAlign = oWordOffset;
        rgsdiStructDefine->oDWordAlign = oDWordOffset;

        //
        // Increase by actual size of the element.
        //

        oPackedOffset += rgsdiStructDefine->cActualSize;
        oWordOffset += rgsdiStructDefine->cActualSize;
        oDWordOffset += rgsdiStructDefine->cActualSize;

        rgsdiStructDefine++;
    }

    //
    // Be sure that the structure fits within alignement.
// Add word and DWORD padding accordingly.
    //

oWordOffset  += (oWordOffset & 1);
oDWordOffset += (oDWordOffset & 1);
oDWordOffset += (oDWordOffset & 2);

    //
    // Check to see if we successfully reached the end of the structure array.
// We assume that they have sent us a sizeof(struct) in system alignment.
    // If not then return an error, -1.  Otherwise, everything is fine so
    // return the sizeof the structure we just got.
// Note we only do it if they have given us something to compare.
    //

if (cSizeOfStruct) {

switch (CVT_ALIGN_SYSTEM) {

case CVT_ALIGN_PACKED:
if (cSizeOfStruct != oPackedOffset) {
return (-1);
}
break;

case CVT_ALIGN_WORD:
if (cSizeOfStruct != oWordOffset) {
return (-1);
}
break;

case CVT_ALIGN_DWORD:
if (cSizeOfStruct != oDWordOffset) {
return (-1);
}
break;
}
}

//
// Check and see what type of stucture they want us to return to
// them.  Generally on NT it will be the packed value they want.
//

switch (fAlignmentType) {

case CVT_ALIGN_PACKED:
return (oPackedOffset);

case CVT_ALIGN_WORD:
return (oWordOffset);
break;

case CVT_ALIGN_DWORD:
return (oDWordOffset);
break;

default:
return (0);
}
}

VOID
vPerformConversion (
     PSDI    rgsdiStructDefine,
     PBYTE   pjSrcBuffer,
 INTfSrcAlignment,
 INTfSrcEndianType,
     PBYTE   pjDestBuffer,
 INTfDestAlignment,
 INTfDestEndianType
    )

/*++

Routine Description:

WARNING: CalcStructureOffsets must have previously been called with
rgsdiStructDesc as it's argument.

    This function is the heart of the generic conversion system.  It steps
    through the (PSDI) structure definition array and converts each element
from the source buffer to the destination buffer.  To do this it uses the
precomputed offset information stored in the structure definition.
Also, if the source endian type is different from the destination, it
will swap all data in the transfer.

Arguments:

    rgsdiStruceDefine   - Definition of this structure as an array of SDI
                          structures.  Note, see beginning of this file for
                          a complete description of how to define this
                          structure.

    pjSrcBuffer         - This is a pointer to the source strucure in memory.
                          This structure does not need to be aligned.

    fSrcAlignment       - This is the alignment of the source structure.
  The permissible values of this field are:
CVT_ALIGN_PACKED - structure is packed.
CVT_ALIGN_WORD   - structure is WORD aligned.
CVT_ALIGN_DWORD  - structure is DWORD aligned.
CVT_ALIGN_SYSTEM - use system default alignment.

    fSrcEndianType      - This is the byte ordering of the source data.
  The permissible values of this field are:
CVT_LITTLE_ENDIAN - data is little endian format.
CVT_BIG_ENDIAN    - data is big endian format.
CVT_ENDIAN_FILE   - use current file endian type.
CVT_ENDIAN_SYSTEM - use current system endian type.

    pjDestBuffer        - This is a pointer to the destination structure.
                          This structure does not need to be aligned.

    fDestAlignment      - This is the alignment of the source structure.
  The permissible values of this field: Same as above.

    fDestEndianType     - This is the byte ordering of the source data.
  The permissible values of this field: Same as above.

Return Value:

    None.  Any detectable errors would have been found in the initialization
    of the SDI structure.

--*/

{
register PSDI rgsdiStrDef = rgsdiStructDefine;
INT  *poSrcAlign;
INT  *poDestAlign;
PBYTE  pjSrc;
PBYTE  pjDest;

//
// Get the alignment pointers set to the proper offset into the
// structure.  Here is the general trick we use here.  First we set the
// offset pointer to the start of the structure array.  We then find out
// what offset into that structure will point us to the alignment type
// we want.  After that, since we have an array we just add the sizeof
// the SDI struct to the pointer and it will give us the next offset.
//

poSrcAlign = (INT *)rgsdiStrDef;

switch (fSrcAlignment) {

case CVT_ALIGN_PACKED:
((CHAR *)poSrcAlign) += ((CHAR *)&rgsdiStrDef->oPackedAlign -
(CHAR *)rgsdiStrDef);
break;

case CVT_ALIGN_WORD:
((CHAR *)poSrcAlign) += ((CHAR *)&rgsdiStrDef->oWordAlign -
(CHAR *)rgsdiStrDef);
break;

case CVT_ALIGN_DWORD:
((CHAR *)poSrcAlign) += ((CHAR *)&rgsdiStrDef->oDWordAlign -
(CHAR *)rgsdiStrDef);
break;
}

//
// Same with dest.
//

poDestAlign = (INT *)rgsdiStrDef;

switch (fDestAlignment) {

case CVT_ALIGN_PACKED:
((CHAR *)poDestAlign) += ((CHAR *)&rgsdiStrDef->oPackedAlign -
(CHAR *)rgsdiStrDef);
break;

case CVT_ALIGN_WORD:
((CHAR *)poDestAlign) += ((CHAR *)&rgsdiStrDef->oWordAlign -
(CHAR *)rgsdiStrDef);
break;

case CVT_ALIGN_DWORD:
((CHAR *)poDestAlign) += ((CHAR *)&rgsdiStrDef->oDWordAlign -
(CHAR *)rgsdiStrDef);
break;
}

if (fSrcEndianType == fDestEndianType) {

INTcTotalCount;

//
// Keep converting while there exists elements with a valid convert
// structure.
//

while (rgsdiStrDef->cTypeSize) {

//
// Store the actual size we need to transfer.  Also, store the
// pointers to the offsets within the current structures.
//

cTotalCount = rgsdiStrDef->cActualSize;
pjSrc = pjSrcBuffer + *poSrcAlign;
pjDest = pjDestBuffer + *poDestAlign;

//
// MemCpy the bytes, probably faster to do it inline.
//

while (cTotalCount--) {

*pjDest++ = *pjSrc++;
}

//
// Now we step our three pointers to the next structure in
// the array.
//

((CHAR *)poSrcAlign) += sizeof (SDI);
((CHAR *)poDestAlign) += sizeof (SDI);
rgsdiStrDef++;
}

} else {

INTcArrayCount;
INTcElemSize;
INTwT;

//
// Keep converting while there exists elements with a valid convert
// structure.
//

while (rgsdiStrDef->cTypeSize) {

cArrayCount = rgsdiStrDef->cActualSize / rgsdiStrDef->cTypeSize;

//
// Store the actual size we need to transfer.  Also, store the
// pointers to the offsets within the current structures.
//

cElemSize = rgsdiStrDef->cActualSize;
pjSrc = pjSrcBuffer + *poSrcAlign;
pjDest = pjDestBuffer + *poDestAlign;

//
// Handle each array element seperately.
//

while (cArrayCount--) {

for (wT = 1; wT <= cElemSize; wT++) {

//
// Copy in reverse order.
//

*pjDest++ = *(pjSrc + (cElemSize - wT));
}

pjSrc += cElemSize;
}

//
// Now we step our three pointers to the next structure in
// the array.
//

((CHAR *)poSrcAlign) += sizeof (SDI);
((CHAR *)poDestAlign) += sizeof (SDI);
rgsdiStrDef++;
}
}
}

VOID
vSetFileEndianType (
    BOOL     fNewEndianType
    )

/*++

Routine Description:

    This routine is used to set the endian type of the disk file.  Normally
the define CVT_FILE

Arguments:

    fNewEndianType   - Endian format type to set the source to.
                       The only valid types are CVT_LITTLE_ENDIAN and
                       CVT_BIG_ENDIAN.
                       Portable calls to this function should always
                       use either CVT_ENDIAN_SYSTEM or CVT_ENDIAN_FILE.

Return Value:

    None.

--*/

{
    // Set the endian type of the source data.
    vfFileEndianType = fNewEndianType;
}

INT
fDetermineSysEndianType (
VOID
    )

/*++

Routine Description:

    This function is used to determine how the current system stores its
integers in memory.

Arguments:

None.

Return Value:

CVT_LITTLE_ENDIAN   - The system stores integers in little endian
  format.  (this is 80x86 default).
CVT_BIG_ENDIAN  - The system stores integers in big endian format.

--*/

{
INTnCheckInteger;
CHARrgchTestBytes [sizeof (INT)];

//
// Clear the test bytes to zero.
//

*((INT *)rgchTestBytes) = 0;

//
// Set first to some value.
//

rgchTestBytes [0] = (CHAR) 0xFF;

//
// Map it to an integer.
//

nCheckInteger = *((INT *)rgchTestBytes);

//
// See if value was stored in low order of integer.
// If so then system is little endian.
//

if (nCheckInteger == 0xFF) {

return (CVT_LITTLE_ENDIAN);
} else {

return (CVT_LITTLE_ENDIAN);
}
}

VOID
vCharToShort (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source CHAR to the destination SHORT.  Note that sign extension
    is performed.  The destination buffer does not need to be WORD aligned.
    This is generally used for transfering file mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a CHAR.
    pjDest      - Returns the copied SHORT at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source CHAR and use it as an argument for destination SHORT.

    vDestBuffFromSHORT (
             (SHORT)*pjSrc,
             pjDest
            );
}

VOID
vCharToUShort (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source CHAR to the destination USHORT.  Note that sign
    extension is not perfomed.  The destination buffer does not need to be
    WORD aligned. This is generally used for transfering file mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a CHAR.
    pjDest      - Returns the copied SHORT at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source CHAR and use it as an argument for destination USHORT.

    vDestBuffFromUSHORT (
             (USHORT)*pjSrc,
             pjDest
            );
}

VOID
vCharToLong (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source CHAR to the destination LONG.  Note that sign extension
    is performed.  The destination buffer does not need to be DWORD aligned.
    This is generally used for transfering file mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a CHAR.
    pjDest      - Returns the copied LONG at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source CHAR and use it as an argument for destination LONG.

    vDestBuffFromLONG (
             (LONG)*pjSrc,
             pjDest
            );
}

VOID
vCharToULong (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source CHAR to the destination ULONG.  Note that sign extension
    is not performed.  The destination buffer does not need to be DWORD
    aligned. This is generally used for transfering file mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a CHAR.
    pjDest      - Returns the copied ULONG at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source CHAR and use it as an argument for destination ULONG.

    vDestBuffFromULONG (
             (ULONG)*pjSrc,
             pjDest
            );
}

VOID
vShortToShort (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source SHORT to the destination SHORT.  Neither buffers need
    to be WORD aligned.  This is generally used for transfering file mapped
    data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a SHORT.
    pjDest      - Returns the copied SHORT at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source SHORT and use it as an argument for destination SHORT.

    vDestBuffFromSHORT (
             sSHORTFromSrcBuff (pjSrc),
             pjDest
            );
}

VOID
vShortToLong (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source SHORT to the destination LONG.  Note that sign extension
    is perfomed.  Neither buffers need to be WORD or DWORD aligned.
    This is generally used for transfering file mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a SHORT.
    pjDest      - Returns the copied LONG at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source SHORT and use it as an argument for destination LONG.

    vDestBuffFromLONG (
             (LONG)sSHORTFromSrcBuff (pjSrc),
             pjDest
            );
}

VOID
vShortToULong (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source SHORT to the destination ULONG.  Note that sign extension
    is perfomed.  Neither buffers need to be WORD or DWORD aligned.
    This is generally used for transfering file mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a SHORT.
    pjDest      - Returns the copied ULONG at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source SHORT and use it as an argument for destination ULONG.

    vDestBuffFromULONG (
             (LONG)sSHORTFromSrcBuff (pjSrc),
             pjDest
            );
}

VOID
vLongToLong (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source LONG to the destination LONG.  Neither buffers need to
    be WORD or DWORD aligned. This is generally used for transfering file
    mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a LONG.
    pjDest      - Returns the copied LONG at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source LONG and use it as an argument for destination LONG.

    vDestBuffFromLONG (
             (LONG)lLONGFromSrcBuff (pjSrc),
             pjDest
            );
}

VOID
vLongToShort (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source LONG to the destination SHORT.  Neither buffers need to
    be WORD or DWORD aligned. This is generally used for transfering file
    mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a LONG.
    pjDest      - Returns the copied SHORT at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source LONG and use it as an argument for destination SHORT.

    vDestBuffFromSHORT (
             (SHORT)lLONGFromSrcBuff (pjSrc),
             pjDest
            );
}

VOID
vLongToChar (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source LONG to the destination CHAR.  Neither buffers need to
    be WORD or DWORD aligned. This is generally used for transfering file
    mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a LONG.
    pjDest      - Returns the copied CHAR at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source LONG and use it as an argument for destination CHAR.

*pjDest = (CHAR)lLONGFromSrcBuff (pjSrc);
}

VOID
vShortToChar (
             PBYTE   pjSrc,
             PBYTE   pjDest
    )

/*++

Routine Description:

    Copies the source SHORT to the destination CHAR.  Neither buffers need to
    be WORD or DWORD aligned. This is generally used for transfering file
    mapped data.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a SHORT.
    pjDest      - Returns the copied CHAR at the location pointed to.

Return Value:

    None.

--*/

{
    // Get the source SHORT and use it as an argument for destination CHAR.

*pjDest = (CHAR)sSHORTFromSrcBuff (pjSrc);
}

SHORT
sSHORTFromSrcBuff (
         PBYTE   pjSrc
    )

/*++

Routine Description:

    Copies two byte short from buffer into a signed short.  The buffer does
    not need to be WORD aligned.  This is generally used for copying from
    mapped disk files.  If CVT_BIG_ENDIAN_SUPPORT is defined then the
    fSrcEndianType is checked before copy.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a SHORT.

Return Value:

    Signed short which is obtained from the buffer.

--*/

{
    SHORT   sStorage;

#ifdef CVT_BIG_ENDIAN_SUPPORT
    if (fSrcEndianType == CVT_LITTLE_ENDIAN) {
#endif
        sStorage = (SHORT)          // Source is little endian, shift
                (                   // the high bytes high.
                 (pjSrc [1] << 8) |
                 (pjSrc [0])
                );

#ifdef CVT_BIG_ENDIAN_SUPPORT
    } else {
        sStorage = (SHORT)          // Source is big endian, shift
                (                   // the high bytes low.
                 (pjSrc [0] << 8) |
                 (pjSrc [1])
                );
    }

#endif

    return (sStorage);
}

USHORT
usUSHORTFromSrcBuff (
         PBYTE   pjSrc
    )

/*++

Routine Description:

    Copies two byte short from buffer into an unsigned short.  The buffer does
    not need to be WORD aligned.  This is generally used for copying from
    mapped disk files.  If CVT_BIG_ENDIAN_SUPPORT is defined then the
    fSrcEndianType is checked before copy.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a USHORT.

Return Value:

    Unsigned short which is obtained from the buffer.

--*/

{
    USHORT  usStorage;

#ifdef CVT_BIG_ENDIAN_SUPPORT
    if (fSrcEndianType == CVT_LITTLE_ENDIAN) {
#endif
        usStorage = (USHORT)            // Source is little endian, shift
                (                       // the high bytes high.
                 (pjSrc [1] << 8) |
                 (pjSrc [0])
                );

#ifdef CVT_BIG_ENDIAN_SUPPORT
    } else {
        usStorage = (USHORT)            // Source is big endian, shift
                (                       // the high bytes low.
                 (pjSrc [0] << 8) |
                 (pjSrc [1])
                );
    }

#endif

    return (usStorage);
}

LONG
lLONGFromSrcBuff (
         PBYTE   pjSrc
    )

/*++

Routine Description:

    Copies four byte long from buffer into a signed long.  The buffer does
    not need to be DWORD aligned.  This is generally used for copying from
    mapped disk files.  If CVT_BIG_ENDIAN_SUPPORT is defined then the
    fSrcEndianType is checked before copy.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a LONG.

Return Value:

    Signed long which is obtained from the buffer.

--*/

{
    LONG    lStorage;

#ifdef CVT_BIG_ENDIAN_SUPPORT
    if (fSrcEndianType == CVT_LITTLE_ENDIAN) {
#endif
        lStorage = (LONG)               // Source is little endian, shift
                ((pjSrc [3] << 24) |    // the high bytes high.
                 (pjSrc [2] << 16) |
                 (pjSrc [1] << 8) |
                 (pjSrc [0])
                );

#ifdef CVT_BIG_ENDIAN_SUPPORT
    } else {
        lStorage = (LONG)               // Source is big endian, shift the
                ((pjSrc [0] << 24) |    // high bytes low.
                 (pjSrc [1] << 16) |
                 (pjSrc [2] << 8) |
                 (pjSrc [3])
                );
    }

#endif

    return (lStorage);
}

ULONG
ulULONGFromSrcBuff (
         PBYTE   pjSrc
    )

/*++

Routine Description:

    Copies four byte long from buffer into an unsigned long.  The buffer does
    not need to be DWORD aligned.  This is generally used for copying from
    mapped disk files.  If CVT_BIG_ENDIAN_SUPPORT is defined then the
    fSrcEndianType is checked before copy.

Arguments:

    pjSrc       - Supplies pointer to the source buffer containing a ULONG.

Return Value:

    Unsigned long which is obtained from the buffer.

--*/

{
    ULONG   ulStorage;

#ifdef CVT_BIG_ENDIAN_SUPPORT
    if (fSrcEndianType == CVT_LITTLE_ENDIAN) {
#endif
        ulStorage = (ULONG)             // Source is little endian, shift
                ((pjSrc [3] << 24) |    // the high bytes high.
                 (pjSrc [2] << 16) |
                 (pjSrc [1] << 8) |
                 (pjSrc [0])
                );

#ifdef CVT_BIG_ENDIAN_SUPPORT
    } else {
        ulStorage = (ULONG)             // Source is big endian, shift the
                ((pjSrc [0] << 24) |    // high bytes low.
                 (pjSrc [1] << 16) |
                 (pjSrc [2] << 8) |
                 (pjSrc [3])
                );
    }

#endif

    return (ulStorage);
}

VOID
vDestBuffFromSHORT (
         SHORT   sSource,
         PBYTE   pjDest
    )

/*++

Routine Description:

    Copies two byte signed short into a destination buffer.  The buffer does
    not need to be WORD aligned.  This is generally used for copying to

mapped disk files.  If CVT_BIG_ENDIAN_SUPPORT is defined then the
    fDestEndianType is checked before copy.

Arguments:

    sSource     - Signed short to convert to buffer.
    pjDest      - Supplies pointer to destination buffer for the USHORT.

Return Value:

    None.

--*/

{
#ifdef CVT_BIG_ENDIAN_SUPPORT
    if (fSrcEndianType == CVT_LITTLE_ENDIAN) {
#endif
        pjDest [0] = ((sSource) & 0xFF);
        pjDest [1] = ((sSource >> 8)  & 0xFF);

#ifdef CVT_BIG_ENDIAN_SUPPORT
    } else {
        pjDest [1] = ((sSource) & 0xFF);
        pjDest [0] = ((sSource >> 8)  & 0xFF);
    }

#endif
}

VOID
vDestBuffFromUSHORT (
         USHORT  usSource,
         PBYTE   pjDest
    )

/*++

Routine Description:

    Copies two byte unsigned short into a destination buffer.  The buffer
    does not need to be WORD aligned.  This is generally used for copying to
    mapped disk files.  If CVT_BIG_ENDIAN_SUPPORT is defined then the
    fDestEndianType is checked before copy.

Arguments:

    usSource    - Unsigned short to convert to buffer.
    pjDest      - Supplies pointer to destination buffer for the USHORT.

Return Value:

    None.

--*/

{
#ifdef CVT_BIG_ENDIAN_SUPPORT
    if (fSrcEndianType == CVT_LITTLE_ENDIAN) {
#endif
        pjDest [0] = ((usSource) & 0xFF);
        pjDest [1] = ((usSource >> 8)  & 0xFF);

#ifdef CVT_BIG_ENDIAN_SUPPORT
    } else {
        pjDest [1] = ((usSource) & 0xFF);
        pjDest [0] = ((usSource >> 8)  & 0xFF);
    }

#endif
}

VOID
vDestBuffFromLONG (
         LONG    lSource,
         PBYTE   pjDest
    )

/*++

Routine Description:

    Copies four byte long into a destination buffer.  The buffer does not
    need to be DWORD aligned.  This is generally used for copying to
    mapped disk files.  If CVT_BIG_ENDIAN_SUPPORT is defined then the
    fDestEndianType is checked before copy.

Arguments:

    lSource     - Signed long to convert to buffer.
    pjDest      - Supplies pointer to destination buffer for the LONG.

Return Value:

    None.

--*/

{
#ifdef CVT_BIG_ENDIAN_SUPPORT
    if (fSrcEndianType == CVT_LITTLE_ENDIAN) {
#endif
        pjDest [0] = ((lSource) & 0xFF);
        pjDest [1] = ((lSource >> 8)  & 0xFF);
        pjDest [2] = ((lSource >> 16) & 0xFF);
        pjDest [3] = ((lSource >> 24) & 0xFF);

#ifdef CVT_BIG_ENDIAN_SUPPORT
    } else {
        pjDest [3] = ((lSource) & 0xFF);
        pjDest [2] = ((lSource >> 8)  & 0xFF);
        pjDest [1] = ((lSource >> 16) & 0xFF);
        pjDest [0] = ((lSource >> 24) & 0xFF);
    }

#endif
}

VOID
vDestBuffFromULONG (
         ULONG   ulSource,
         PBYTE   pjDest
    )

/*++

Routine Description:

    Copies four byte long into a destination buffer.  The buffer does not
    need to be DWORD aligned.  This is generally used for copying to
    mapped disk files.  If CVT_BIG_ENDIAN_SUPPORT is defined then the
    fDestEndianType is checked before copy.

Arguments:

    ulSource    - Unsigned long to convert to buffer.
    pjDest      - Supplies pointer to destination buffer for the ULONG.

Return Value:

    None.

--*/

{
#ifdef CVT_BIG_ENDIAN_SUPPORT
    if (fSrcEndianType == CVT_LITTLE_ENDIAN) {
#endif
        pjDest [0] = (BYTE)((ulSource) & (ULONG)0xFF);
        pjDest [1] = (BYTE)((ulSource >> 8)  & (ULONG)0xFF);
        pjDest [2] = (BYTE)((ulSource >> 16) & (ULONG)0xFF);
        pjDest [3] = (BYTE)((ulSource >> 24) & (ULONG)0xFF);

#ifdef CVT_BIG_ENDIAN_SUPPORT
    } else {
        pjDest [3] = (BYTE)((ulSource) & (ULONG)0xFF);
        pjDest [2] = (BYTE)((ulSource >> 8)  & (ULONG)0xFF);
        pjDest [1] = (BYTE)((ulSource >> 16) & (ULONG)0xFF);
        pjDest [0] = (BYTE)((ulSource >> 24) & (ULONG)0xFF);
    }

#endif
}


TYPECVT.H

/*++

Copyright 1993 - 1998 Microsoft Corporation

Module Name:

    typecvt.h

Abstract:

    Header file for typecvt.c.  Contains any defines, structures and prototypes
    which are needed by programs to call the typecvt routines.

--*/

// Make sure we are not already defined.

#ifndef _TYPECVT_

#define _TYPECVT_

//
// The following preprocessor directives are used to control possible
// differences between the way integers are stored on the machine and
// how they are stored in the file.  Normally Intel and MIPS chips will
// all be little endian so these should not be necessray.  Some time in
// the future we may port to a machine which is only big endian.
// If this is the case CVT_BIG_ENDIAN_SUPPORT should be defined.
//

//
// The following defines are used to set the type of source and destination
// structures.  For those of us who are confused by little endian and big
// endian formats, here is a breif recap.
//
// Little Endian:  (This is used on Intel chips.  The MIPS chip is switchable
//      but for NT is will run in little endian format.)
//    This is where the high order bytes of a short or long are stored higher
//    in memory.  For example the number 0x80402010 is stored as follows.
//      Address:        Value:
//          00            10
//          01            20
//          02            40
//          03            80
//    This looks backwards when memory is dumped in order: 10 20 40 80
//
// Big Endian:  (This is not currently used on any NT systems but hey, this
//      is supposed to be portable!!)
//    This is where the high order bytes of a short or long are stored lower
//    in memory.  For example the number 0x80402010 is stored as follows.
//      Address:        Value:
//          00            80
//          01            40
//          02            20
//          03            10
//    This looks correct when memory is dumped in order: 80 40 20 10
//

#define   CVT_ENDIAN_UNKNOWN  0   // Endian type is unknown. (do not use).
#define   CVT_LITTLE_ENDIAN   1   // Format is little endian.
#define   CVT_BIG_ENDIAN      2   // Format is big endian.

//
// Define the endian type of the file.  CVT_FILE_ENDIAN_DEFAULT defines how
// most files are stored on disk.  The default is in little endian since most
// Microsoft standards are set based on the Intel chip.
//

#define         CVT_FILE_ENDIAN_DEFAULT        CVT_LITTLE_ENDIAN

//
// The following variables are used to make "changeable defines."   They
// allow the caller to specify a constant which changes from system to
// system.
//

extern INT vfFileEndianType;
extern INT vfSysEndianType;

#define   CVT_ENDIAN_FILE     vfFileEndianType
#define   CVT_ENDIAN_SYSTEM   vfSysEndianType

//
// Fake structure used to determine system alignment type.
//

struct tagAlignmentCheck
{
charchElem1;// Note that this structure will be different
long  lElem2;// sizes based on the system alignment scheme.
// The different values follow.
};

//
// Note that the following defines must correspond to the size of the
// preceeding structure in different packing schemes.
//

#define   CVT_ALIGN_PACKED      5   // Packed = 1-byte boundry ...
#define   CVT_ALIGN_WORD        6   // WORD = 2-byte boundry ...
#define   CVT_ALIGN_DWORD       8   // DWORD = 4-byte boundry ...

//
// The following will correspond to one of the above alignment methods and
// will then reflect the system that this is compiled under.
//

#define   CVT_ALIGN_SYSTEM      sizeof(struct tagAlignmentCheck)

//
// The next two structures are the heart of the conversion process.  The
// goal here is to describe two structures individually.  Each element should
// be defined in a SDI structure.  An array of these structures will make up
// the complete definition.
//

typedef struct tagStructDefineInfo
{
    INT     cTypeSize;          // Size of type. (ex. sizeof (int)).
    INT     cActualSize;        // Actual size (ex. sizeof (cTypeSize)).
    INT     oPackedAlign;      // Offset of element in PACKED alginment.
    INT     oWordAlign;        // Offset of element in WORD alginment.
    INT     oDWordAlign;       // Offset of element in DWORD alginment.
} SDI, * PSDI;

//
// Prototypes for base functions which user can call to perfom the conversion.
//

LONG
lCalculateStructOffsets (
     PSDI    rgsdiStructDefine,
     INT     fAlignmentType,
  INT             cSizeOfStruct
    );

VOID
vPerformConversion (
     PSDI    rgsdiStructDefine,
     PBYTE   pjSrcBuffer,
 INTfSrcAlignment,
 INTfSrcEndianType,
    PBYTE   pjDestBuffer,
 INTfDestAlignment,
 INTfDestEndianType
    );

VOID
vSetFileEndianType (
    BOOL     fNewEndianType
    );

INT
fDetermineSysEndianType (
VOID
    );

//
// Prototypes for convertion functions available to external programs.
// These functions are never actually used by
//

VOID
vCharToShort (
          PBYTE  pjSrc,
         PBYTE  pjDest
         );
VOID
vCharToUShort (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vCharToLong (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vCharToULong (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vShortToShort (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vShortToLong (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vShortToULong (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vLongToLong (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vLongToShort (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vLongToChar (
          PBYTE  pjSource,
         PBYTE  pjDest
         );
VOID
vShortToChar (
          PBYTE  pjSource,
         PBYTE  pjDest
         );

//
// The following functions are the ones called by the utility functions.
// They could also be used in some other situations so I will make them
// public.
//

SHORT
sSHORTFromSrcBuff (
         PBYTE   pjSrc
    );
USHORT
usUSHORTFromSrcBuff (
         PBYTE   pjSrc
    );
LONG
lLONGFromSrcBuff (
         PBYTE   pjSrc
    );
ULONG
ulULONGFromSrcBuff (
         PBYTE   pjSrc
    );

VOID
vDestBuffFromSHORT (
         SHORT   sSource,
        PBYTE   pjDest
    );
VOID
vDestBuffFromUSHORT (
         USHORT  usSource,
        PBYTE   pjDest
    );
VOID
vDestBuffFromLONG (
         LONG    lSource,
        PBYTE   pjDest
    );
VOID
vDestBuffFromULONG (
         ULONG   ulSource,
        PBYTE   pjDest
    );

#endif  // _TYPECVT_


README.TXT

Dr. Watson

The DRWATSON tool is an application error debugger. It traps application
errors and logs diagnostic information.

The information obtained and logged by DRWATSON is the information needed
by technical support groups to diagnose application errors. The DRWTSN32.LOG
log file is created in the form of an electronic text file that can be
delivered to support personnel by whatever method they prefer.

To start DRWATSON when no error has occurred, type DRWTSN32 at the command
prompt or select the file from the File Manager or Windows Explorer.


CONTROLS.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    controls.c

Abstract:
    This file implements the sun-classing and message processing of
    the controls on the main ui dialog.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"
#include "resource.h"

typedef struct _tagCONTROLINFO {
    struct _tagCONTROLINFO   *next;
    HWND                     hwnd;
    WNDPROC                  wndProc;
} CONTROLINFO, *PCONTROLINFO;

PCONTROLINFO   ciHead    = NULL;
PCONTROLINFO   ciTail    = NULL;
PCONTROLINFO   ciFocus   = NULL;
PCONTROLINFO   ciDefault = NULL;

void
SetFocusToCurrentControl( void )

/*++

Routine Description:

    Sets the focus  to the current control.

Arguments:

    None.

Return Value:

    None.

--*/

{
    if (ciFocus != NULL) {
        SetFocus( ciFocus->hwnd );
        SendMessage( ciFocus->hwnd, BM_SETSTATE, 0, 0 );
    }
}

LRESULT
ControlWndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )

/*++

Routine Description:

    Processes focus messages and ensures that when the focus changes
    from one button to another that the old button looses the focus
    and the "default" state.

Arguments:

    Standard WNDPROC entry.

Return Value:

    LRESULT - Depending on input message and processing options.

--*/

{
    PCONTROLINFO ci = ciHead;

    while (ci->hwnd != hwnd) {
        ci = ci->next;
        if (ci == NULL) {
            return FALSE;
        }
    }

    switch(message) {
        case WM_SETFOCUS:
            ciFocus = ci;
            break;

        case BM_SETSTYLE:
            if (wParam == BS_DEFPUSHBUTTON) {
                ciDefault = ci;
            }
            break;

        case BM_SETSTATE:
            if ((GetWindowLong( hwnd, GWL_STYLE ) & 0xff) < BS_CHECKBOX) {
                //
                // change the button that had the focus
                //
                SendMessage( ciDefault->hwnd,
                             BM_SETSTYLE,
                             ( WPARAM ) BS_PUSHBUTTON,
                             ( LPARAM ) TRUE
                           );
                UpdateWindow( ciDefault->hwnd );

                //
                // change the button that is getting the focus
                //
                SendMessage( hwnd,
                             BM_SETSTYLE,
                             ( WPARAM ) BS_DEFPUSHBUTTON,
                             ( LPARAM ) TRUE
                           );
                SetFocus( hwnd );
                UpdateWindow( hwnd );
            }
            break;
    }

    return CallWindowProc( ci->wndProc, hwnd, message, wParam, lParam );
}

BOOL CALLBACK
EnumChildProc( HWND hwnd, LPARAM lParam )

/*++

Routine Description:

    Subclass a controls in DrWatson's main window.

Arguments:

    hwnd    - Supplies the window handle for the main window.
    lParam  - non used

Return Value:

    BOOL    - Returns TRUE if each of the buttons in the ButtonHelpTable is
              subclassed.

--*/

{
    PCONTROLINFO ci;

    //
    // add the control to the linked list
    //
    ci = (PCONTROLINFO) malloc( sizeof(CONTROLINFO) );
    if (ci == NULL) {
        return FALSE;
    }
    memset( ci, 0, sizeof(CONTROLINFO) );

    if (ciHead == NULL) {
        ciHead = ciTail = ci;
    }
    else {
        ciTail->next = ci;
        ciTail = ci;
    }

    //
    // save the HWND
    //
    ci->hwnd = hwnd;

    //
    // change the WNDPROC and save the address of the old one
    //
    ci->wndProc = (WNDPROC) SetWindowLong( hwnd,
                                           GWL_WNDPROC,
                                           (LONG)ControlWndProc
                                         );

    if (GetWindowLong( hwnd, GWL_STYLE ) & BS_DEFPUSHBUTTON) {
        ciDefault = ci;
    }

    return TRUE;
}

BOOL
SubclassControls( HWND hwnd )

/*++

Routine Description:

    Subclass the controls in DrWatson's main window.

Arguments:

    hWnd    - Supplies the window handle for the main window.

Return Value:

    BOOL    - Returns TRUE if each of the buttons in the ButtonHelpTable is
              subclassed.

--*/

{
    EnumChildWindows( hwnd, EnumChildProc, 0 );

    return TRUE;
}


DEBUG.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    debug.c

Abstract:

    This file implements the debug module for drwatson.  This module
    processes all debug events and generates the postmortem dump.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>

#include "drwatson.h"
#include "cv.h"
#include "cvfmt.h"
#include "proto.h"
#include "messages.h"
#include "resource.h"

typedef struct tagSYSINFO {
    char    szUserName[MAX_PATH];
    char    szMachineName[MAX_PATH];
} SYSINFO, *PSYSINFO;

#define DBG_EXCEPTION_HANDLED           ((DWORD)0x00010001L)
#define STATUS_POSSIBLE_DEADLOCK        ((DWORD)0xC0000194L)
#define STATUS_VDM_EVENT                STATUS_SEGMENT_NOTIFICATION

PTHREADCONTEXT
AllocTctx(
    PDEBUGPACKET dp
    );

void
PostMortemDump(
    PDEBUGPACKET dp,
    LPEXCEPTION_DEBUG_INFO ed
    );

void
AttachToActiveProcess (
    PDEBUGPACKET dp
    );

void
ProcessCreateProcess(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    );

void
ProcessCreateThread(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    );

void
ProcessExitThread(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    );

void
ProcessLoadDll(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    );

void
LogSystemInformation(
    PDEBUGPACKET dp
    );

DWORD
SysInfoThread(
    PSYSINFO si
    );

void
LogDisassembly(
    PDEBUGPACKET dp,
    PCRASHES pCrash
    );

void
LogStackWalk(
    PDEBUGPACKET dp
    );

void
LogStackDump(
    PDEBUGPACKET dp
    );

char *
GetExceptionText(
    DWORD dwExceptionCode
    );

LPSTR
ExpandPath(
    LPSTR lpPath
    );

void
SetFaultingContext(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    );

void
SkipOverBreakpoint(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    );

DWORD
DispatchDebugEventThread(
    PDEBUGPACKET dp
    )

/*++

Routine Description:

    This is the entry point for DRWTSN32

Arguments:

    None.

Return Value:

    None.

--*/

{
    DEBUG_EVENT   de;
    DWORD         rc = 0;
    char          szLogFileName[1024];
    char          buf[1024];
    LPSTR         p;
    DWORD         ContinueStatus;

    if (dp->dwPidToDebug == 0) {
        rc = 1;
        goto exit;
    }

    SetErrorMode( SEM_FAILCRITICALERRORS |
                  SEM_NOGPFAULTERRORBOX  |
                  SEM_NOOPENFILEERRORBOX   );

    AttachToActiveProcess( dp );

    p = ExpandPath( dp->options.szLogPath );
    if (p) {
        strcpy( szLogFileName, p );
        free( p );
    } else {
        strcpy( szLogFileName, dp->options.szLogPath );
    }
    MakeLogFileName( szLogFileName );
    OpenLogFile( szLogFileName,
                 dp->options.fAppendToLogFile,
                 dp->options.fVisual
               );

    while (TRUE) {
        if (!WaitForDebugEvent( &de, 10000 )) {
            rc = GetLastError();
            goto exit;
        }
        ContinueStatus = DBG_CONTINUE;

        switch (de.dwDebugEventCode) {
            case EXCEPTION_DEBUG_EVENT:
                if (de.u.Exception.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT) {
                    if (de.u.Exception.dwFirstChance) {
                        SkipOverBreakpoint(dp, &de);
                        ContinueStatus = DBG_EXCEPTION_HANDLED;
                        //
                        // The aedebug event will be signalled AFTER this
                        // thread exits, so that it will disappear before
                        // the dump snapshot is taken.
                        //
                        break;
                    }
                }
                if (dp->options.fVisual) {
                    //
                    // this notification is necessary because the shell must know when
                    // the debugee has been attached.  if it doesn't know and the user is
                    // allowed to terminate drwatson then the system may intervene with
                    // a popup.
                    //
                    SendMessage( dp->hwnd, WM_ATTACHCOMPLETE, 0, 0 );
                    wsprintf( buf,
                              LoadRcString( IDS_AE_TEXT ),
                              GetExceptionText(de.u.Exception.ExceptionRecord.ExceptionCode),
                              de.u.Exception.ExceptionRecord.ExceptionCode,
                              de.u.Exception.ExceptionRecord.ExceptionAddress );
                    SendMessage( dp->hwnd, WM_EXCEPTIONINFO, 0, (LPARAM) buf );
                }
                SetFaultingContext(dp, &de);
                PostMortemDump( dp, &de.u.Exception );
                if (dp->options.fCrash) {
                    CreateDumpFile( dp, &de.u.Exception );
                }
                ContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
                break;

            case CREATE_THREAD_DEBUG_EVENT:
                ProcessCreateThread( dp, &de );
                break;

            case CREATE_PROCESS_DEBUG_EVENT:
                ProcessModuleLoad( dp, &de );
                de.u.CreateThread.hThread = de.u.CreateProcessInfo.hThread;
                ProcessCreateThread( dp, &de );
                break;

            case EXIT_THREAD_DEBUG_EVENT:
                ProcessExitThread( dp, &de );
                if ( dp->hEventToSignal ) {
                    SetEvent(dp->hEventToSignal);
                    dp->hEventToSignal = 0L;
                }
                break;

            case EXIT_PROCESS_DEBUG_EVENT:
                goto exit;
                break;

            case LOAD_DLL_DEBUG_EVENT:
                ProcessModuleLoad( dp, &de );
                break;

            case UNLOAD_DLL_DEBUG_EVENT:
                break;

            case OUTPUT_DEBUG_STRING_EVENT:
                break;

            case RIP_EVENT:
                break;

            default:
                lprintf( MSG_INVALID_DEBUG_EVENT, de.dwDebugEventCode );
                break;
        }
        ContinueDebugEvent( de.dwProcessId, de.dwThreadId, ContinueStatus );
    }

exit:
    CloseLogFile();

    if (dp->options.fVisual) {
        SendMessage( dp->hwnd, WM_DUMPCOMPLETE, 0, 0 );
    }

    return 0;
}

PTHREADCONTEXT
AllocTctx(
    PDEBUGPACKET dp
    )
{
    PTHREADCONTEXT ptctx;

    ptctx = (PTHREADCONTEXT) malloc( sizeof(THREADCONTEXT) );
    if (ptctx == NULL) {
        if (dp->options.fVisual) {
            FatalError( LoadRcString(IDS_MEMORY) );
        }
        else {
            ExitProcess( 1 );
        }
    }
    memset( ptctx, 0, sizeof(THREADCONTEXT) );

    return ptctx;
}

void
ProcessCreateThread(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    )
{
    dp->tctx              = AllocTctx( dp );
    dp->tctx->hThread     = de->u.CreateThread.hThread;
    dp->tctx->dwThreadId  = de->dwThreadId;

    InsertTailList(&dp->ThreadList, &dp->tctx->ThreadList);

    return;
}

void
ProcessExitThread(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    )
{
    PTHREADCONTEXT ptctx;
    PLIST_ENTRY List = dp->ThreadList.Flink;

    while (List != &dp->ThreadList) {
        ptctx = CONTAINING_RECORD(List, THREADCONTEXT, ThreadList);
        if (ptctx->dwThreadId == de->dwThreadId) {
            RemoveEntryList(List);
            free(ptctx);
            break;
        }
        List = List->Flink;
    }
}

void
PostMortemDump(
    PDEBUGPACKET dp,
    LPEXCEPTION_DEBUG_INFO ed
    )
{
    IMAGEHLP_MODULE   mi;
    char              dbuf[1024];
    char              szDate[20];
    char              szTime[20];
    CRASHES           crash;
    DWORD             dwThreadId;
    HANDLE            hThread;
    PLIST_ENTRY        List;

    GetLocalTime( &crash.time );
    crash.dwExceptionCode = ed->ExceptionRecord.ExceptionCode;
    crash.dwAddress = (DWORD)ed->ExceptionRecord.ExceptionAddress;
    strcpy( crash.szAppName, szApp );

    lprintf( MSG_APP_EXCEPTION );
    wsprintf( dbuf, "%d", dp->dwPidToDebug );
    lprintf( MSG_APP_EXEP_NAME, crash.szAppName, dbuf );
    wsprintf( szDate, "%d/%d/%d", crash.time.wMonth,
                                  crash.time.wDay,
                                  crash.time.wYear );
    wsprintf( szTime, "%d:%d:%d.%d", crash.time.wHour,
                                     crash.time.wMinute,
                                     crash.time.wSecond,
                                     crash.time.wMilliseconds );
    lprintf( MSG_APP_EXEP_WHEN, szDate, szTime );
    wsprintf( dbuf, "%08lx", ed->ExceptionRecord.ExceptionCode );
    lprintf( MSG_EXCEPTION_NUMBER, dbuf );

    lprintfs( "(%s)\r\n\r\n",
              GetExceptionText(ed->ExceptionRecord.ExceptionCode) );

    LogSystemInformation( dp );

    LogTaskList();

    if (SymGetModuleInfo( dp->hProcess, 0, &mi )) {
        do {
            lprintfs( "(%08x - %08x) %s\r\n",
                      (DWORD)mi.BaseOfImage,
                      (DWORD)mi.BaseOfImage + mi.ImageSize,
                      mi.LoadedImageName
                    );
        } while( SymGetModuleInfo( dp->hProcess, (DWORD)-1, &mi ));
        lprintfs( "\r\n" );
    }

    List = dp->ThreadList.Flink;
    while (List != &dp->ThreadList) {

        dp->tctx = CONTAINING_RECORD(List, THREADCONTEXT, ThreadList);
        List = List->Flink;

        GetContextForThread( dp );

        if (dp->tctx->fFaultingContext || dp->options.fDumpAllThreads) {
            wsprintf( dbuf, "%x", dp->tctx->dwThreadId );
            lprintf( MSG_STATE_DUMP, dbuf );
            OutputAllRegs( dp, TRUE );
            LogDisassembly( dp, &crash );
            LogStackWalk( dp );
            LogStackDump( dp );
        }
    }

    if (dp->options.fDumpSymbols) {
        DumpSymbols( dp );
    }

    ElSaveCrash( &crash, dp->options.dwMaxCrashes );

    hThread = CreateThread( NULL,
                            16000,
                            (LPTHREAD_START_ROUTINE)TerminationThread,
                            dp,
                            0,
                            (LPDWORD)&dwThreadId
                          );

    WaitForSingleObject( hThread, 30000 );

    CloseHandle( hThread );

    return;
}

void
LogStackDump(
    PDEBUGPACKET dp
    )
{
    DWORD   i;
    DWORD   j;
    BYTE    stack[1024];

    memset( stack, 0, sizeof(stack) );
    if (!DoMemoryRead( dp,
                       (LPVOID)dp->tctx->stack,
                       (LPVOID)stack,
                       sizeof(stack),
                       (LPDWORD)&i )) {
        return;
    }

    lprintf( MSG_STACK_DUMP_HEADER );

    for( i = 0; i < 20; i++ ) {
        j = i * 16;
        lprintfs( "%08x  %02x %02x %02x %02x %02x %02x %02x %02x - %02x %02x %02x %02x %02x %02x %02x %02x  %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\r\n",
                  j + dp->tctx->stack,
                  stack[ j +  0 ],
                  stack[ j +  1 ],
                  stack[ j +  2 ],
                  stack[ j +  3 ],
                  stack[ j +  4 ],
                  stack[ j +  5 ],
                  stack[ j +  6 ],
                  stack[ j +  7 ],
                  stack[ j +  8 ],
                  stack[ j +  9 ],
                  stack[ j + 10 ],
                  stack[ j + 11 ],
                  stack[ j + 12 ],
                  stack[ j + 13 ],
                  stack[ j + 14 ],
                  stack[ j + 15 ],
                  isprint( stack[ j +  0 ]) ? stack[ j +  0 ] : '.',
                  isprint( stack[ j +  1 ]) ? stack[ j +  1 ] : '.',
                  isprint( stack[ j +  2 ]) ? stack[ j +  2 ] : '.',
                  isprint( stack[ j +  3 ]) ? stack[ j +  3 ] : '.',
                  isprint( stack[ j +  4 ]) ? stack[ j +  4 ] : '.',
                  isprint( stack[ j +  5 ]) ? stack[ j +  5 ] : '.',
                  isprint( stack[ j +  6 ]) ? stack[ j +  6 ] : '.',
                  isprint( stack[ j +  7 ]) ? stack[ j +  7 ] : '.',
                  isprint( stack[ j +  8 ]) ? stack[ j +  8 ] : '.',
                  isprint( stack[ j +  9 ]) ? stack[ j +  9 ] : '.',
                  isprint( stack[ j + 10 ]) ? stack[ j + 10 ] : '.',
                  isprint( stack[ j + 11 ]) ? stack[ j + 11 ] : '.',
                  isprint( stack[ j + 12 ]) ? stack[ j + 12 ] : '.',
                  isprint( stack[ j + 13 ]) ? stack[ j + 13 ] : '.',
                  isprint( stack[ j + 14 ]) ? stack[ j + 14 ] : '.',
                  isprint( stack[ j + 15 ]) ? stack[ j + 15 ] : '.'
                );
    }

    lprintfs( "\r\n" );
    return;
}

void
LogStackWalk(
    PDEBUGPACKET dp
    )
{
    #define SAVE_EBP(f)        f.Reserved[0]
    #define TRAP_TSS(f)        f.Reserved[1]
    #define TRAP_EDITED(f)     f.Reserved[1]
    #define SAVE_TRAP(f)       f.Reserved[2]

    DWORD             dwDisplacement = 0;
    DWORD             frames = 0;
    LPSTR             szSymName;
    IMAGEHLP_MODULE   mi;
    DWORD             i;
    DWORD             machType;
    CONTEXT           Context;
    STACKFRAME        stk;

    Context = dp->tctx->context;
    ZeroMemory( &stk, sizeof(stk) );

    stk.AddrPC.Offset       = dp->tctx->pc;
    stk.AddrPC.Mode         = AddrModeFlat;

#if defined(_M_IX86)
    machType = IMAGE_FILE_MACHINE_I386;
    stk.AddrStack.Offset    = dp->tctx->stack;
    stk.AddrStack.Mode      = AddrModeFlat;
    stk.AddrFrame.Offset    = dp->tctx->frame;
    stk.AddrFrame.Mode      = AddrModeFlat;
#elif defined(_M_MRX000)
    machType = IMAGE_FILE_MACHINE_R4000;
#elif defined(_M_ALPHA)
    machType = IMAGE_FILE_MACHINE_ALPHA;
#elif defined(_M_PPC)
    machType = IMAGE_FILE_MACHINE_POWERPC;
#else
#error( "unknown target machine" );
#endif

    lprintf( MSG_STACKTRACE );

    for (i=0; i<100; i++) {
        if (!StackWalk( machType,
                        (HANDLE)dp,
                        NULL,
                        &stk,
                        &Context,
                        SwReadProcessMemory,
                        SwFunctionTableAccess,
                        SwGetModuleBase,
                        SwTranslateAddress )) {
            break;
        }
        if (SymGetSymFromAddr( dp->hProcess, stk.AddrPC.Offset, &dwDisplacement, sym )) {
            szSymName = sym->Name;
        }
        else {
            szSymName = "<nosymbols>";
        }
        lprintfs( "%08x %08x %08x %08x %08x %08x ",
                  stk.AddrFrame.Offset,
                  stk.AddrReturn.Offset,
                  stk.Params[0],
                  stk.Params[1],
                  stk.Params[2],
                  stk.Params[3]
                );

        if (SymGetModuleInfo( dp->hProcess, stk.AddrPC.Offset, &mi )) {
            lprintfs( "%s!", mi.ModuleName );
        }

        lprintfs( "%s ", szSymName );

        if (sym && (sym->Flags & SYMF_OMAP_GENERATED || sym->Flags & SYMF_OMAP_MODIFIED)) {
            lprintfs( "[omap] " );
        }

        if (stk.FuncTableEntry && machType == IMAGE_FILE_MACHINE_I386) {
            PFPO_DATA pFpoData = (PFPO_DATA)stk.FuncTableEntry;
            switch (pFpoData->cbFrame) {
                case FRAME_FPO:
                    if (pFpoData->fHasSEH) {
                        lprintfs( "(FPO: [SEH])" );
                    } else {
                        lprintfs( " (FPO:" );
                        if (pFpoData->fUseBP) {
                            lprintfs( " [EBP 0x%08x]", SAVE_EBP(stk) );
                        }
                        lprintfs(" [%d,%d,%d])", pFpoData->cdwParams,
                                                 pFpoData->cdwLocals,
                                                 pFpoData->cbRegs);
                    }
                    break;
                case FRAME_NONFPO:
                    lprintfs( "(FPO: Non-FPO [%d,%d,%d])",
                                 pFpoData->cdwParams,
                                 pFpoData->cdwLocals,
                                 pFpoData->cbRegs);
                    break;

                case FRAME_TRAP:
                case FRAME_TSS:
                default:
                    lprintfs( "(UNKNOWN FPO TYPE)" );
                    break;
            }
        }

        lprintfs( "\r\n" );

    }

    lprintfs( "\r\n" );
    return;
}

void
LogDisassembly(
    PDEBUGPACKET dp,
    PCRASHES pCrash
    )
{
    DWORD             dwFuncAddr;
    DWORD             dwFuncSize;
    DWORD             dwDisplacement = 0;
    char              *szSymName;
    DWORD             offset;
    int               i;
    int               j;
    char              dbuf[1024];
    BOOL              fFaultingInst;
    int               dwStartDis;
    int               dwEndDis;

    if (SymGetSymFromAddr( dp->hProcess, dp->tctx->pc, &dwDisplacement, sym )) {
        dwFuncAddr = sym->Address;
        dwFuncSize = sym->Size;
        if (dwFuncSize < 1) {
            dwFuncSize = 100;
        }
        szSymName = sym->Name;
    }
    else {
        dwFuncAddr = dp->tctx->pc - 50;
        dwFuncSize = 100;
        szSymName = "<nosymbols>";
    }

    if (dp->tctx->fFaultingContext) {
        strcpy( pCrash->szFunction, szSymName );
    }

    lprintf( MSG_FUNCTION, szSymName );

tryagain:
    //
    // count the number of instructions in the function
    // also, save the instruction number of context's pc
    //
    for (i=0,offset=dwFuncAddr,j=-1; offset<dwFuncAddr+dwFuncSize; i++) {
        if (offset == dp->tctx->pc) {
            j = i;
        }
        if (!disasm( dp, &offset, dbuf, TRUE )) {
            break;
        }
    }

    if (j == -1) {
        //
        // we didn't find a match for the current pc
        // this because we don't have symbols for the current pc and
        // therefore had to just backup and start disassembling.  we try
        // to recover by adding 1 to the func addr and do it again.
        // eventually we will hit the pc and we will be a-ok.
        //
        dwFuncAddr++;
        goto tryagain;
    }

    //
    // print the disassemled instructions.  only print the number
    // of instructions before and after the current pc that the
    // user specified in the registry options.
    //
    dwStartDis = max(0,j - (int)dp->options.dwInstructions);
    dwEndDis = j+(int)dp->options.dwInstructions;
    fFaultingInst = FALSE;
    for (i=0,offset=dwFuncAddr; offset<dwFuncAddr+dwFuncSize; i++) {
        if (offset == dp->tctx->pc) {
            fFaultingInst = TRUE;
        }
        if (!disasm( dp, &offset, dbuf, TRUE )) {
            break;
        }
        if (i >= dwStartDis) {
            if (fFaultingInst && dp->tctx->fFaultingContext) {
                fFaultingInst = FALSE;
                lprintf( MSG_FAULT );
            }
            else {
                lprintfs( "        " );
            }
            lprintfs( "%s\r\n", dbuf );
        }
        if (i > dwEndDis) {
            break;
        }
    }

    lprintfs( "\r\n" );
    return;
}

void
AttachToActiveProcess (
    PDEBUGPACKET dp
    )
{
    HANDLE              Token;
    PTOKEN_PRIVILEGES   NewPrivileges;
    BYTE                OldPriv[1024];
    PBYTE               pbOldPriv;
    ULONG               cbNeeded;
    BOOLEAN             fRc;
    LUID                LuidPrivilege;

    //
    // Make sure we have access to adjust and to get the old token privileges
    //
    if (!OpenProcessToken( GetCurrentProcess(),
                           TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
                           &Token)) {
        if (dp->options.fVisual) {
            FatalError( LoadRcString(IDS_DEBUGPRIV) );
        }
        else {
            ExitProcess( 1 );
        }
    }

    cbNeeded = 0;

    //
    // Initialize the privilege adjustment structure
    //

    LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &LuidPrivilege );

    NewPrivileges =
        (PTOKEN_PRIVILEGES)LocalAlloc(LMEM_ZEROINIT,
                                      sizeof(TOKEN_PRIVILEGES) +
                                          (1 - ANYSIZE_ARRAY) *
                                          sizeof(LUID_AND_ATTRIBUTES));
    if (NewPrivileges == NULL) {
        if (dp->options.fVisual) {
            FatalError( LoadRcString(IDS_DEBUGPRIV) );
        }
        else {
            ExitProcess( 1 );
        }
    }

    NewPrivileges->PrivilegeCount = 1;
    NewPrivileges->Privileges[0].Luid = LuidPrivilege;
    NewPrivileges->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    //
    // Enable the privilege
    //

    pbOldPriv = OldPriv;
    fRc = AdjustTokenPrivileges( Token,
                                 FALSE,
                                 NewPrivileges,
                                 1024,
                                 (PTOKEN_PRIVILEGES)pbOldPriv,
                                 &cbNeeded );

    if (!fRc) {

        //
        // If the stack was too small to hold the privileges
        // then allocate off the heap
        //
        if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {

            pbOldPriv = LocalAlloc(LMEM_FIXED, cbNeeded);
            if (pbOldPriv == NULL) {
                if (dp->options.fVisual) {
                    FatalError( LoadRcString(IDS_DEBUGPRIV) );
                }
                else {
                    ExitProcess( 1 );
                }
            }

            fRc = AdjustTokenPrivileges( Token,
                                         FALSE,
                                         NewPrivileges,
                                         cbNeeded,
                                         (PTOKEN_PRIVILEGES)pbOldPriv,
                                         &cbNeeded );
        }
    }

    if (!DebugActiveProcess( dp->dwPidToDebug )) {
        if (dp->options.fVisual) {
            FatalError( LoadRcString(IDS_ATTACHFAIL) );
        }
        else {
            ExitProcess( 1 );
        }
    }

    return;
}

void
LogSystemInformation(
    PDEBUGPACKET dp
    )
{
    char          buf[1024];
    SYSTEM_INFO   si;
    DWORD         ver;
    SYSINFO       mySi;
    DWORD         dwThreadId;
    HANDLE        hThread;

    lprintf( MSG_SYSINFO_HEADER );
    hThread = CreateThread( NULL,
                            16000,
                            (LPTHREAD_START_ROUTINE)SysInfoThread,
                            &mySi,
                            0,
                            (LPDWORD)&dwThreadId
                          );
    Sleep( 0 );
    if (WaitForSingleObject( hThread, 30000 ) == WAIT_TIMEOUT) {
        Assert(TerminateThread( hThread, 0 ) == TRUE);
    }
    CloseHandle( hThread );
    lprintf( MSG_SYSINFO_COMPUTER, mySi.szMachineName );
    lprintf( MSG_SYSINFO_USER, mySi.szUserName );
    GetSystemInfo( &si );
    wsprintf( buf, "%d", si.dwNumberOfProcessors );
    lprintf( MSG_SYSINFO_NUM_PROC, buf );
    RegLogProcessorType();
    ver = GetVersion();
    wsprintf( buf, "%d.%d", LOBYTE(LOWORD(ver)), HIBYTE(LOWORD(ver)) );
    lprintf( MSG_SYSINFO_WINVER, buf );
    RegLogCurrentVersion();
    lprintfs( "\r\n" );
}

DWORD
SysInfoThread(
    PSYSINFO si
    )
{
    DWORD len;

    strcpy( si->szMachineName, LoadRcString( IDS_UNKNOWN_MACHINE ) );
    strcpy( si->szUserName,    LoadRcString( IDS_UNKNOWN_USER ) );
    len = sizeof(si->szMachineName);
    GetComputerName( si->szMachineName, &len );
    len = sizeof(si->szUserName);
    GetUserName( si->szUserName, &len );

    return 0;
}

DWORD
TerminationThread(
    PDEBUGPACKET dp
    )
{
    HANDLE hProcess;

    hProcess = OpenProcess( PROCESS_TERMINATE, FALSE, dp->dwPidToDebug );
    if (hProcess != NULL) {
        TerminateProcess( hProcess, 0 );
        CloseHandle( hProcess );
    }

    return 0;
}

char *
GetExceptionText(
    DWORD dwExceptionCode
    )
{
    static char buf[80];
    DWORD dwFormatId = 0;

    memset( buf, 0, sizeof(buf) );

    switch (dwExceptionCode) {
        case STATUS_SINGLE_STEP:
            dwFormatId = MSG_SINGLE_STEP_EXCEPTION;
            break;

        case DBG_CONTROL_C:
            dwFormatId = MSG_CONTROLC_EXCEPTION;
            break;

        case DBG_CONTROL_BREAK:
            dwFormatId = MSG_CONTROL_BRK_EXCEPTION;
            break;

        case STATUS_ACCESS_VIOLATION:
            dwFormatId = MSG_ACCESS_VIOLATION_EXCEPTION;
            break;

        case STATUS_STACK_OVERFLOW:
            dwFormatId = MSG_STACK_OVERFLOW_EXCEPTION;
            break;

        case STATUS_INTEGER_DIVIDE_BY_ZERO:
            dwFormatId = MSG_INTEGER_DIVIDE_BY_ZERO_EXCEPTION;
            break;

        case STATUS_PRIVILEGED_INSTRUCTION:
            dwFormatId = MSG_PRIVILEGED_INSTRUCTION_EXCEPTION;
            break;

        case STATUS_ILLEGAL_INSTRUCTION:
            dwFormatId = MSG_ILLEGAL_INSTRUCTION_EXCEPTION;
            break;

        case STATUS_IN_PAGE_ERROR:
            dwFormatId = MSG_IN_PAGE_IO_EXCEPTION;
            break;

        case STATUS_DATATYPE_MISALIGNMENT:
            dwFormatId = MSG_DATATYPE_EXCEPTION;
            break;

        case STATUS_POSSIBLE_DEADLOCK:
            dwFormatId = MSG_DEADLOCK_EXCEPTION;
            break;

        case STATUS_VDM_EVENT:
            dwFormatId = MSG_VDM_EXCEPTION;
            break;

        case STATUS_BREAKPOINT:
            dwFormatId = MSG_BREAKPOINT_EXCEPTION;
            break;

        default:
            lprintfs( "\r\n" );
            break;
    }

    FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                   NULL,
                   dwFormatId,
                   0, // GetUserDefaultLangID(),
                   buf,
                   sizeof(buf),
                   NULL
                 );

    return buf;
}

BOOL
DoMemoryRead(
    PDEBUGPACKET  dp,
    LPVOID        addr,
    LPVOID        buf,
    DWORD         size,
    LPDWORD       lpcb
    )
{
    return ReadProcessMemory( dp->hProcess, addr, buf, size, lpcb );
}

LPSTR
ExpandPath(
    LPSTR lpPath
    )
{
    DWORD   len;
    LPSTR   p;

    len = ExpandEnvironmentStrings( lpPath, NULL, 0 );
    if (!len) {
        return NULL;
    }

    len += 1;
    p = malloc( len );
    if (!p) {
        return NULL;
    }

    len = ExpandEnvironmentStrings( lpPath, p, len );
    if (!len) {
        free( p );
        return NULL;
    }

    return p;
}

void
SetFaultingContext(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    )
{
    PTHREADCONTEXT ptctx;
    PLIST_ENTRY List = dp->ThreadList.Flink;

    dp->DebugEvent = *de;

    while (List != &dp->ThreadList) {
        ptctx = CONTAINING_RECORD(List, THREADCONTEXT, ThreadList);
        List = List->Flink;
        if (ptctx->dwThreadId == de->dwThreadId) {
            ptctx->fFaultingContext = TRUE;
            break;
        }
    }
}

PTHREADCONTEXT
DebugEventToThreadContext(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    )
{
    PTHREADCONTEXT ptctx;
    PLIST_ENTRY List = dp->ThreadList.Flink;

    while (List != &dp->ThreadList) {
        ptctx = CONTAINING_RECORD(List, THREADCONTEXT, ThreadList);
        List = List->Flink;
        if (ptctx->dwThreadId == de->dwThreadId) {
            return ptctx;
        }
    }
    return NULL;
}

void
SkipOverBreakpoint(
    PDEBUGPACKET dp,
    LPDEBUG_EVENT de
    )
{
    PTHREADCONTEXT ptctx;
    CONTEXT Context;

    ptctx = DebugEventToThreadContext(dp, de);
    Context.ContextFlags = CONTEXT_FULL;
    GetThreadContext(ptctx->hThread, &Context);
    PC(&Context) = (DWORD) de->u.Exception.ExceptionRecord.ExceptionAddress + BP_SIZE;
    SetThreadContext(ptctx->hThread, &Context);
}


DRWTSN32.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    drwtsn32.c

Abstract:

    This file implements the user interface for DRWTSN32.  this includes
    both dialogs: the ui for the control of the options & the popup
    ui for application errors.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"
#include "resource.h"

#define MAX_SYMNAME_SIZE  1024
CHAR symBuffer[sizeof(IMAGEHLP_SYMBOL)+MAX_SYMNAME_SIZE];
PIMAGEHLP_SYMBOL sym = (PIMAGEHLP_SYMBOL) symBuffer;

int _CRTAPI1
main( int argc, char *argv[] )

/*++

Routine Description:

    This is the entry point for DRWTSN32

Arguments:

    argc           - argument count
    argv           - array of arguments

Return Value:

    always zero.

--*/

{
    DWORD   dwPidToDebug = 0;
    HANDLE  hEventToSignal = 0;
    BOOLEAN rc;

    sym->SizeOfStruct  = sizeof(IMAGEHLP_SYMBOL);
    sym->MaxNameLength = MAX_SYMNAME_SIZE;

    rc = GetCommandLineArgs( &dwPidToDebug, &hEventToSignal );

    if (dwPidToDebug > 0) {
        NotifyWinMain();
    }
    else
    if (!rc) {
        DrWatsonWinMain();
    }

    return 0;
}


DUMP.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    dump.c

Abstract:

    This file implements the crash dump code.

Author:

    Wesley Witt (wesw) 27-Jan-1995

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <crash.h>

#include "drwatson.h"
#include "proto.h"
#include "messages.h"
#include "resource.h"

#define MEM_SIZE (64*1024)

//
// these are here only so that we can link
// with crashlib.  they are only referenced
// when reading a kernel mode crash dump
//
DWORD KiProcessors;
DWORD KiPcrBaseAddress;

//
// private data structure use for communcating
// crash dump data to the callback function
//
typedef struct _CRASH_DUMP_INFO {
    PDEBUGPACKET                dp;
    EXCEPTION_DEBUG_INFO        *ExceptionInfo;
    DWORD                       MemoryCount;
    DWORD                       Address;
    PUCHAR                      MemoryData;
    MEMORY_BASIC_INFORMATION    mbi;
    BOOL                        MbiOffset;
    ULONG                       MbiRemaining;
    PTHREADCONTEXT              ptctx;
    IMAGEHLP_MODULE             mi;
    PCRASH_MODULE               CrashModule;
} CRASH_DUMP_INFO, *PCRASH_DUMP_INFO;

LPSTR
ExpandPath(
    LPSTR lpPath
    );

DWORD
GetTeb(
    HANDLE hTread
    );

BOOL
CrashDumpCallback(
    DWORD               DataType,
    PVOID               *DumpData,
    LPDWORD             DumpDataLength,
    PCRASH_DUMP_INFO    CrashdumpInfo
    )

/*++

Routine Description:

    This function is the callback used by crashlib.
    Its purpose is to provide data to DmpCreateUserDump()
    for writting to the crashdump file.

Arguments:

    DataType        - requested data type
    DumpData        - pointer to a pointer to the data
    DumpDataLength  - pointer to the data length
    CrashdumpInfo   - DrWatson private data

Return Value:

    TRUE    - continue calling back for the requested data type
    FALSE   - stop calling back and go on to the next data type

--*/

{
    DWORD   cb;

    switch( DataType ) {
        case DMP_DEBUG_EVENT:
            *DumpData = &CrashdumpInfo->dp->DebugEvent;
            *DumpDataLength = sizeof(DEBUG_EVENT);
            break;

        case DMP_THREAD_STATE:
            {
                static CRASH_THREAD CrashThread;
                PTHREADCONTEXT  ptctx;
                PLIST_ENTRY     ListEntry;

                *DumpData = &CrashThread;

                if (CrashdumpInfo->ptctx == NULL) {
                    ListEntry = CrashdumpInfo->dp->ThreadList.Flink;
                } else {
                    ListEntry = CrashdumpInfo->ptctx->ThreadList.Flink;
                }

                if (ListEntry == &CrashdumpInfo->dp->ThreadList) {
                    CrashdumpInfo->ptctx = NULL;
                    return FALSE;
                }

                ptctx =
                CrashdumpInfo->ptctx = CONTAINING_RECORD(ListEntry, THREADCONTEXT, ThreadList);

                ZeroMemory(&CrashThread, sizeof(CrashThread));

                CrashThread.ThreadId = ptctx->dwThreadId;
                CrashThread.SuspendCount = SuspendThread(ptctx->hThread);
                if (CrashThread.SuspendCount != (DWORD)-1) {
                    ResumeThread(ptctx->hThread);
                }
                CrashThread.PriorityClass = GetPriorityClass(CrashdumpInfo->dp->hProcess);
                CrashThread.Priority = GetThreadPriority(ptctx->hThread);
                CrashThread.Teb = GetTeb(ptctx->hThread);

                *DumpDataLength = sizeof(CRASH_THREAD);
            }
            break;

        case DMP_MEMORY_BASIC_INFORMATION:
            while( TRUE ) {
                CrashdumpInfo->Address += CrashdumpInfo->mbi.RegionSize;
                if (!VirtualQueryEx(
                        CrashdumpInfo->dp->hProcess,
                        (LPVOID)CrashdumpInfo->Address,
                        &CrashdumpInfo->mbi,
                        sizeof(MEMORY_BASIC_INFORMATION) )) {
                    return FALSE;
                }
                if ((CrashdumpInfo->mbi.AllocationProtect & PAGE_GUARD) ||
                    (CrashdumpInfo->mbi.AllocationProtect & PAGE_NOACCESS)) {
                    continue;
                }
                if ((CrashdumpInfo->mbi.State & MEM_FREE) ||
                    (CrashdumpInfo->mbi.State & MEM_RESERVE)) {
                    continue;
                }
                break;
            }
            *DumpData = &CrashdumpInfo->mbi;
            *DumpDataLength = sizeof(MEMORY_BASIC_INFORMATION);
            break;

        case DMP_THREAD_CONTEXT:
            {
                PLIST_ENTRY     ListEntry;

                if (CrashdumpInfo->ptctx == NULL) {
                    ListEntry = CrashdumpInfo->dp->ThreadList.Flink;
                } else {
                    ListEntry = CrashdumpInfo->ptctx->ThreadList.Flink;
                }

                if (ListEntry == &CrashdumpInfo->dp->ThreadList) {
                    CrashdumpInfo->ptctx = NULL;
                    return FALSE;
                }

                CrashdumpInfo->ptctx = CONTAINING_RECORD(ListEntry, THREADCONTEXT, ThreadList);

                *DumpData = &CrashdumpInfo->ptctx->context;
                *DumpDataLength = sizeof(CONTEXT);
            }
            break;

        case DMP_MODULE:
            if (CrashdumpInfo->mi.BaseOfImage == 0) {
                return FALSE;
            }
            CrashdumpInfo->CrashModule->BaseOfImage = CrashdumpInfo->mi.BaseOfImage;
            CrashdumpInfo->CrashModule->SizeOfImage = CrashdumpInfo->mi.ImageSize;
            CrashdumpInfo->CrashModule->ImageNameLength = strlen(CrashdumpInfo->mi.ImageName) + 1;
            strcpy( CrashdumpInfo->CrashModule->ImageName, CrashdumpInfo->mi.ImageName );
            *DumpData = CrashdumpInfo->CrashModule;
            *DumpDataLength = sizeof(CRASH_MODULE) + CrashdumpInfo->CrashModule->ImageNameLength;
            if (!SymGetModuleInfo( CrashdumpInfo->dp->hProcess, (DWORD)-1, &CrashdumpInfo->mi )) {
                CrashdumpInfo->mi.BaseOfImage = 0;
            }
            break;

        case DMP_MEMORY_DATA:
            if (!CrashdumpInfo->MemoryCount) {
                CrashdumpInfo->Address = 0;
                CrashdumpInfo->MbiOffset = 0;
                CrashdumpInfo->MbiRemaining = 0;
                ZeroMemory( &CrashdumpInfo->mbi, sizeof(MEMORY_BASIC_INFORMATION) );
                CrashdumpInfo->MemoryData = VirtualAlloc(
                    NULL,
                    MEM_SIZE,
                    MEM_COMMIT,
                    PAGE_READWRITE
                    );
            }
            if (!CrashdumpInfo->MbiRemaining) {
                while( TRUE ) {
                    CrashdumpInfo->Address += CrashdumpInfo->mbi.RegionSize;
                    if (!VirtualQueryEx(
                            CrashdumpInfo->dp->hProcess,
                            (LPVOID)CrashdumpInfo->Address,
                            &CrashdumpInfo->mbi,
                            sizeof(MEMORY_BASIC_INFORMATION) )) {
                        if (CrashdumpInfo->MemoryData) {
                            VirtualFree( CrashdumpInfo->MemoryData, MEM_SIZE, MEM_RELEASE );
                        }
                        return FALSE;
                    }
                    if ((CrashdumpInfo->mbi.Protect & PAGE_GUARD) ||
                        (CrashdumpInfo->mbi.Protect & PAGE_NOACCESS)) {
                        continue;
                    }
                    if ((CrashdumpInfo->mbi.State & MEM_FREE) ||
                        (CrashdumpInfo->mbi.State & MEM_RESERVE)) {
                        continue;
                    }
                    CrashdumpInfo->MbiOffset = 0;
                    CrashdumpInfo->MbiRemaining = CrashdumpInfo->mbi.RegionSize;
                    CrashdumpInfo->MemoryCount += 1;
                    break;
                }
            }
            *DumpDataLength = min( CrashdumpInfo->MbiRemaining, MEM_SIZE );
            CrashdumpInfo->MbiRemaining -= *DumpDataLength;
            ReadProcessMemory(
                CrashdumpInfo->dp->hProcess,
                (PUCHAR)((ULONG)CrashdumpInfo->mbi.BaseAddress + (ULONG)CrashdumpInfo->MbiOffset),
                CrashdumpInfo->MemoryData,
                *DumpDataLength,
                &cb
                );
            *DumpData = CrashdumpInfo->MemoryData;
            CrashdumpInfo->MbiOffset += *DumpDataLength;
            break;
    }

    return TRUE;
}

BOOL
CreateDumpFile(
    PDEBUGPACKET            dp,
    LPEXCEPTION_DEBUG_INFO  ed
    )

/*++

Routine Description:

    This function creates a crash dump file.

Arguments:

    dp              - debug packet for current process

    ed              - exception data

Return Value:

    TRUE    - Crash dump was created
    FALSE   - Crash dump was NOT created

--*/

{
    CRASH_DUMP_INFO     CrashdumpInfo;
    LPSTR               p;

    ZeroMemory( &CrashdumpInfo, sizeof(CRASH_DUMP_INFO) );

    CrashdumpInfo.dp = dp;
    CrashdumpInfo.ExceptionInfo = ed;
    CrashdumpInfo.ptctx = NULL;
    //
    // Get first entry in module list
    //
    SymGetModuleInfo( dp->hProcess, (DWORD)0, &CrashdumpInfo.mi );
    CrashdumpInfo.CrashModule = LocalAlloc( LPTR, 4096 );

    p = ExpandPath( dp->options.szCrashDump );
    if (!p) {
        return FALSE;
    }

    DmpCreateUserDump( p, CrashDumpCallback, &CrashdumpInfo );

    free( p );

    LocalFree( CrashdumpInfo.CrashModule );

    return TRUE;
}


ERROR.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    error.c

Abstract:

    This file implements the error handeling functions for the
    entire DRWTSN32 application.  This includes error popups,
    debug prints, and assertions.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"
#include "resource.h"
#include "messages.h"

void
FatalError(char *format, ...)

/*++

Routine Description:

    This function is called when there is nothing else to do, hence
    the name FatalError.  It puts up a popup and then terminates.

Arguments:

    Same as printf.

Return Value:

    None.

--*/

{
    char      vbuf[1024];
    char      buf[1024];
    char      szErrorCode[10];
    DWORD     dwCount;
    DWORD     dwArgs[4];
    va_list   arg_ptr;

    va_start(arg_ptr, format);
    _vsnprintf(vbuf, sizeof(vbuf), format, arg_ptr);
    wsprintf( szErrorCode, "%d", GetLastError() );
    dwArgs[0] = (DWORD)vbuf;
    dwArgs[1] = (DWORD)szErrorCode;
    dwCount = FormatMessage(
                FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                NULL,
                MSG_FATAL_ERROR,
                0, // GetUserDefaultLangID(),
                buf,
                sizeof(buf),
                (va_list*)dwArgs
                );
    Assert( dwCount != 0 );
    MessageBox( NULL,
                buf,
                LoadRcString( IDS_FATAL_ERROR ),
                MB_SETFOREGROUND | MB_OK
              );
    ExitProcess( 0 );
}

void
NonFatalError(char *format, ...)

/*++

Routine Description:

    This function is used to generate a popup with some kind of
    warning message inside.

Arguments:

    Same as printf.

Return Value:

    None.

--*/

{
    char      vbuf[1024];
    char      buf[1024];
    char      szErrorCode[10];
    DWORD     dwCount;
    DWORD     dwArgs[4];
    va_list   arg_ptr;

    va_start(arg_ptr, format);
    _vsnprintf(vbuf, sizeof(vbuf), format, arg_ptr);
    wsprintf( szErrorCode, "%d", GetLastError() );
    dwArgs[0] = (DWORD)vbuf;
    dwArgs[1] = (DWORD)szErrorCode;
    dwCount = FormatMessage(
                FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                NULL,
                MSG_FATAL_ERROR,
                0, // GetUserDefaultLangID(),
                buf,
                sizeof(buf),
                (va_list*)dwArgs
                );
    Assert( dwCount != 0 );
    MessageBox( NULL,
                buf,
                LoadRcString( IDS_NONFATAL_ERROR ),
                MB_SETFOREGROUND | MB_OK
              );
}

void
dprintf(char *format, ...)

/*++

Routine Description:

    This function is a var-args version of OutputDebugString.

Arguments:

    Same as printf.

Return Value:

    None.

--*/

{
    char    buf[1024];

    va_list arg_ptr;
    va_start(arg_ptr, format);
    _vsnprintf(buf, sizeof(buf), format, arg_ptr);
    OutputDebugString( buf );
    return;
}

BOOL CALLBACK
AssertDialogProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)

/*++

Routine Description:

    This is the dialog procedure for the assert dialog box.  Normally
    an assertion box is simply a message box but in this case a Help
    button is desired so a dialog box is used.

Arguments:

    hDlg       - window handle to the dialog box
    message    - message number
    wParam     - first message parameter
    lParam     - second message parameter

Return Value:

    TRUE       - did not process the message
    FALSE      - did process the message

--*/

{
    char     *p;
    HICON    hIcon;
    char     szHelpFileName[MAX_PATH];

    switch (message) {
        case WM_INITDIALOG:
            //
            // lParam comes in as a pointer to a buffer containing
            // 2 null terminated strings
            //

            //
            // get the assertion text
            //
            p = (char *) lParam;
            SetDlgItemText( hDlg, ID_ASSERT_TEXT, p );

            //
            // get the app name and use it as the title
            //
            p += (strlen(p)+1);
            SetWindowText( hDlg, p );

            //
            // set the icon
            //
            hIcon = LoadIcon( NULL, IDI_HAND );
            SendMessage( GetDlgItem( hDlg, ID_ASSERT_ICON ), STM_SETICON, (WPARAM) hIcon, 0 );
            break;

        case WM_COMMAND:
            switch (wParam) {
                case IDABORT:
                    //
                    // end the dialog and say why
                    //
                    EndDialog( hDlg, IDABORT );
                    break;

                case IDRETRY:
                    //
                    // end the dialog and say why
                    //
                    EndDialog( hDlg, IDRETRY );
                    break;

                case IDIGNORE:
                    //
                    // end the dialog and say why
                    //
                    EndDialog( hDlg, IDIGNORE );
                    break;

                case ID_HELP:
                    //
                    // get the help file name
                    //
                    GetHelpFileName( szHelpFileName, sizeof(szHelpFileName) );

                    //
                    // call winhelp
                    //
                    WinHelp( hDlg, szHelpFileName, HELP_FINDER, IDH_ASSERT );
                    break;
            }
            break;
    }

    return FALSE;
}

void
AssertError(
    char   *Expression,
    char   *File,
    DWORD  LineNumber
    )

/*++

Routine Description:

    Display an assertion failure message box which gives the user a choice
    as to whether the process should be aborted, the assertion ignored or
    a break exception generated.

Arguments:

    Expression  - Supplies a string representation of the failed assertion.
    File        - Supplies a pointer to the file name where the assertion
                  failed.
    LineNumber  - Supplies the line number in the file where the assertion
                  failed.

Return Value:

    None.

--*/

{
    int        Response;
    char       ModuleBuffer[ MAX_PATH ];
    DWORD      Length;
    char       Buffer[ 4096 ];
    DWORD      Args[ ] = {
        ( DWORD ) Expression,
        ( DWORD ) GetLastError( ),
        ( DWORD ) File,
        ( DWORD ) LineNumber
    };

    //
    // Format the assertion string that describes the failure.
    //
    FormatMessage(
                  FORMAT_MESSAGE_ARGUMENT_ARRAY
                | FORMAT_MESSAGE_FROM_STRING
                & ~FORMAT_MESSAGE_FROM_HMODULE,
                ( LPVOID ) "Assertion Failed : %1!s! (%2!d!)\nin file %3!hs! at line %4!d!\n",
                0,
                0, // GetUserDefaultLangID(),
                Buffer,
                sizeof( Buffer ),
                (va_list*)Args
                );

    //
    // Get the asserting module's file name.
    //
    Length = GetModuleFileName(
                    NULL,
                    ModuleBuffer,
                    sizeof( ModuleBuffer )
                    );

    //
    // put it at the end of the buffer
    //
    strcpy( &Buffer[strlen(Buffer)+1], ModuleBuffer );

    Response = DialogBoxParam( GetModuleHandle( NULL ),
                            MAKEINTRESOURCE( ASSERTDIALOG ),
                            0,
                            AssertDialogProc,
                            (LPARAM) Buffer
                          );

    switch( Response ) {
        case IDABORT:
            //
            // Terminate the process.
            //
            ExitProcess( (UINT) -1 );
            break;

        case IDIGNORE:
            //
            // Ignore the failed assertion.
            //
            break;

        case IDRETRY:
            //
            // Break into a debugger.
            //
            DebugBreak( );
            break;

        default:
            //
            // Break into a debugger because of a catastrophic failure.
            //
            DebugBreak( );
            break;
    }
}


EVENTLOG.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    ui.c

Abstract:

    This file contains all functions that access the application event log.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"
#include "messages.h"

char * AddString( char *p, char *s );
char * AddNumber( char *p, char *f, DWORD dwNumber );
char * GetDWORD( PDWORD pdwData, char *p );
char * GetWORD( PWORD pwData, char *p );
char * GetString( char *s, char *p, DWORD size );

BOOL
ElClearAllEvents( void )
{
    HANDLE           hEventLog;
    char             szAppName[MAX_PATH];

    GetAppName( szAppName, sizeof(szAppName) );
    hEventLog = OpenEventLog( NULL, szAppName );
    Assert( hEventLog != NULL );
    ClearEventLog( hEventLog, NULL );
    CloseEventLog( hEventLog );
    RegSetNumCrashes(0);

    return TRUE;
}

BOOL
ElEnumCrashes( PCRASHINFO crashInfo, CRASHESENUMPROC lpEnumFunc )
{
    char             *p;
    HANDLE           hEventLog;
    char             *szEvBuf;
    EVENTLOGRECORD   *pevlr;
    DWORD            dwRead;
    DWORD            dwNeeded;
    DWORD            dwBufSize = 4096;
    BOOL             rc;
    BOOL             ec;
    char             szAppName[MAX_PATH];

    GetAppName( szAppName, sizeof(szAppName) );
    hEventLog = OpenEventLog( NULL, szAppName );
    if (hEventLog == NULL) {
        return FALSE;
    }

    szEvBuf = (char *) malloc( dwBufSize );
    if (szEvBuf == NULL) {
        return FALSE;
    }

    while (TRUE) {
try_again:
        rc = ReadEventLog(hEventLog,
                        EVENTLOG_FORWARDS_READ | EVENTLOG_SEQUENTIAL_READ,
                        0,
                        (EVENTLOGRECORD *) szEvBuf,
                        dwBufSize,
                        &dwRead,
                        &dwNeeded);

        if (!rc) {
            ec = GetLastError();
            if (ec != ERROR_INSUFFICIENT_BUFFER) {
                goto exit;
            }

            free( szEvBuf );

            dwBufSize = dwNeeded + 1024;
            szEvBuf = (char *) malloc( dwBufSize );
            if (szEvBuf == NULL) {
                return FALSE;
            }

            goto try_again;
        }

        if (dwRead == 0) {
            break;
        }

        GetAppName( szAppName, sizeof(szAppName) );
        p = szEvBuf;

        do {

            pevlr = (EVENTLOGRECORD *) p;

            p += pevlr->StringOffset;

            p = GetString( crashInfo->crash.szAppName,           p, sizeof(crashInfo->crash.szAppName) );
            p = GetWORD  ( &crashInfo->crash.time.wMonth,        p );
            p = GetWORD  ( &crashInfo->crash.time.wDay,          p );
            p = GetWORD  ( &crashInfo->crash.time.wYear,         p );
            p = GetWORD  ( &crashInfo->crash.time.wHour,         p );
            p = GetWORD  ( &crashInfo->crash.time.wMinute,       p );
            p = GetWORD  ( &crashInfo->crash.time.wSecond,       p );
            p = GetWORD  ( &crashInfo->crash.time.wMilliseconds, p );
            p = GetDWORD ( &crashInfo->crash.dwExceptionCode,    p );
            p = GetDWORD ( &crashInfo->crash.dwAddress,          p );
            p = GetString( crashInfo->crash.szFunction,          p, sizeof(crashInfo->crash.szFunction) );

            p = (char *) ((DWORD)pevlr + sizeof(EVENTLOGRECORD));

            if (strcmp( p, szAppName) == 0) {
                crashInfo->dwCrashDataSize = pevlr->DataLength;
                crashInfo->pCrashData = (char *) ((DWORD)pevlr + pevlr->DataOffset);

                if (!lpEnumFunc( crashInfo )) {
                    goto exit;
                }
            }

            //
            // update the pointer & read count
            //
            dwRead -= pevlr->Length;
            p = (char *) ((DWORD)pevlr + pevlr->Length);

        } while ( dwRead > 0 );
    }

exit:
    free( szEvBuf );
    CloseEventLog( hEventLog );
    return TRUE;
}

BOOL
ElSaveCrash( PCRASHES crash, DWORD dwMaxCrashes )
{
    char    szStrings[4096];
    LPSTR   p = szStrings;
    HANDLE  hEventSrc;
    LPSTR   pp[20];
    char    *pLogFileData;
    DWORD   dwLogFileDataSize;
    char    szAppName[MAX_PATH];

    if (dwMaxCrashes > 0) {
        if (RegGetNumCrashes() >= dwMaxCrashes) {
            return FALSE;
        }
    }

    RegSetNumCrashes( RegGetNumCrashes()+1 );

    p = AddString( pp[0]  = p,         crash->szAppName           );
    p = AddNumber( pp[1]  = p, "%2d",  crash->time.wMonth         );
    p = AddNumber( pp[2]  = p, "%2d",  crash->time.wDay           );
    p = AddNumber( pp[3]  = p, "%4d",  crash->time.wYear          );
    p = AddNumber( pp[4]  = p, "%2d",  crash->time.wHour          );
    p = AddNumber( pp[5]  = p, "%2d",  crash->time.wMinute        );
    p = AddNumber( pp[6]  = p, "%2d",  crash->time.wSecond        );
    p = AddNumber( pp[7]  = p, "%3d",  crash->time.wMilliseconds  );
    p = AddNumber( pp[8]  = p, "%08x", crash->dwExceptionCode     );
    p = AddNumber( pp[9]  = p, "%08x", crash->dwAddress           );
    p = AddString( pp[10] = p,         crash->szFunction          );

    GetAppName( szAppName, sizeof(szAppName) );

    hEventSrc = RegisterEventSource( NULL, szAppName );

    if (hEventSrc == NULL) {
        return FALSE;
    }

    pLogFileData = GetLogFileData( &dwLogFileDataSize );

    ReportEvent( hEventSrc,
                 EVENTLOG_INFORMATION_TYPE,
                 0,
                 MSG_CRASH,
                 NULL,
                 11,
                 dwLogFileDataSize,
                 pp,
                 pLogFileData
               );

    DeregisterEventSource( hEventSrc );

    free( pLogFileData );

    return TRUE;
}

char *
AddString( char *p, char *s )
{
    strcpy( p, s );
    p += (strlen(s) + 1);
    return p;
}

char *
AddNumber( char *p, char *f, DWORD dwNumber )
{
    char buf[20];
    wsprintf( buf, f, dwNumber );
    return AddString( p, buf );
}

char *
GetString( char *s, char *p, DWORD size )
{
    strncpy( s, p, size );
    return p + strlen(p) + 1;
}

char *
GetDWORD( PDWORD pdwData, char *p )
{
    sscanf( p, "%x", pdwData );
    return p + strlen(p) + 1;
}

char *
GetWORD( PWORD pwData, char *p )
{
    *pwData = atoi( p );
    return p + strlen(p) + 1;
}


GETTEB.C

/*++

Copyright 1996 - 1998 Microsoft Corporation

Module Name:

    getteb.c

Abstract:

    stub

--*/

#include <windef.h>

DWORD
GetTeb(
    HANDLE hThread
    )
{
    return 0;
}


LOG.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    log.c

Abstract:

    This file implements the access to the postmortem log file.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <commdlg.h>
#include <direct.h>

#include "drwatson.h"
#include "proto.h"
#include "messages.h"
#include "resource.h"

//
// global variables for this module
//
static HANDLE  hFile = NULL;
static HANDLE  hLogProtect = NULL;
static DWORD   dwStartingPos = 0;

void
lprintf(DWORD dwFormatId, ...)

/*++

Routine Description:

    This is function is a printf style function for printing messages
    in a message file.

Arguments:

    dwFormatId    - format id in the message file
    ...           - var args

Return Value:

    None.

--*/

{
    char        buf[1024];
    DWORD       dwCount;
    va_list     args;

    va_start( args, dwFormatId );

    dwCount = FormatMessage(
                FORMAT_MESSAGE_FROM_HMODULE,
                NULL,
                dwFormatId,
                0, // GetUserDefaultLangID(),
                buf,
                sizeof(buf),
                &args
                );

    Assert( dwCount != 0 );

    WriteFile( hFile, buf, dwCount, &dwCount, NULL );

    return;
}

void
lprintfs(char *format, ...)

/*++

Routine Description:

    This is function is a printf replacement that writes the output to
    the DrWatson log file.

Arguments:

    format        - print format
    ...           - var args

Return Value:

    None.

--*/

{
    char    buf[1024];
    DWORD   cb;

    va_list arg_ptr;
    va_start(arg_ptr, format);
    cb = _vsnprintf(buf, sizeof(buf), format, arg_ptr);
    Assert( hFile != NULL );
    WriteFile( hFile, buf, cb, &cb, NULL );
    return;
}

void
OpenLogFile( char *szFileName, BOOL fAppend, BOOL fVisual )

/*++

Routine Description:

    Opens the DrWatson logfile for reading & writting.

Arguments:

    szFileName    - logfile name
    fAppend       - append the new data to the end of the file or
                    create a new file
    fVisual       - visual notification

Return Value:

    None.

--*/

{
    char   szName[1024];

    GetAppName( szName, sizeof(szName) );
    strcat( szName, "LogProtect" );

    hLogProtect = OpenSemaphore( SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, FALSE, szName);
    if (hLogProtect == NULL) {
        hLogProtect = CreateSemaphore( NULL, 0, 1, szName );
        Assert( hLogProtect != NULL );
    }
    else {
        WaitForSingleObject( hLogProtect, INFINITE );
    }

openagain:
    hFile = CreateFile( szFileName,
                        GENERIC_WRITE | GENERIC_READ,
                        FILE_SHARE_READ,
                        NULL,
                        fAppend ? OPEN_EXISTING : CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL
                      );

    if (fAppend) {
        if (hFile == INVALID_HANDLE_VALUE) {
            //
            // file does not exist, so lets create a new file
            //
            hFile = CreateFile( szFileName,
                                GENERIC_WRITE | GENERIC_READ,
                                FILE_SHARE_READ,
                                NULL,
                                CREATE_NEW,
                                FILE_ATTRIBUTE_NORMAL,
                                NULL
                              );
            if (hFile == INVALID_HANDLE_VALUE) {
                if (fVisual) {
                    NonFatalError( LoadRcString(IDS_INVALID_LOGFILE) );
                    _getcwd( szFileName, MAX_PATH );
                    if (!BrowseForDirectory( szFileName )) {
                        FatalError( LoadRcString(IDS_CANT_OPEN_LOGFILE) );
                    }
                    MakeLogFileName( szFileName );
                    goto openagain;
                }
                else {
                    ExitProcess( 1 );
                }
            }

            //
            // write the file banner
            //
            lprintfs( "\r\n" );
            lprintf( MSG_BANNER );
            lprintfs( "\r\n" );
        }

        SetFilePointer( hFile, 0, 0, FILE_END );
    }
    else {
        //
        // write the file banner
        //
        lprintfs( "\r\n" );
        lprintf( MSG_BANNER );
        lprintfs( "\r\n" );
    }

    Assert( hFile != INVALID_HANDLE_VALUE );

    dwStartingPos = SetFilePointer( hFile, 0, NULL, FILE_CURRENT );

    return;
}

void
CloseLogFile( void )

/*++

Routine Description:

    Closes the DrWatson logfile & releases the semaphore that
    protects it.

Arguments:

    None.

Return Value:

    None.

--*/

{
    CloseHandle( hFile );
    ReleaseSemaphore( hLogProtect, 1, NULL );
    CloseHandle( hLogProtect );
}

char *
GetLogFileData( PDWORD pdwLogFileDataSize )

/*++

Routine Description:

    Reads in all of the logfile data that has been written since it was
    opened.  The data is placed into a buffer allocated by this function.
    The caller is responsible for freeing the memory.

Arguments:

    pdwLogFileDataSize     -  pointer to a dword that contains the size
                              in bytes of the data that is read.

Return Value:

    Valid character pointer to the logfile data

    NULL - could not read the data.

--*/

{
    DWORD   dwCurrPos;
    char    *p;
    DWORD   size;

    dwCurrPos = SetFilePointer( hFile, 0, NULL, FILE_CURRENT );

    *pdwLogFileDataSize = 0;
    size = dwCurrPos - dwStartingPos;

    p = (char *) malloc( size );
    if (p == NULL) {
        return NULL;
    }

    SetFilePointer( hFile, dwStartingPos, NULL, FILE_BEGIN );

    if (!ReadFile( hFile, p, size, &size, NULL )) {
        free( p );
        p = NULL;
        size = 0;
    }

    SetFilePointer( hFile, dwCurrPos, NULL, FILE_BEGIN );

    *pdwLogFileDataSize = size;

    return p;
}

void
MakeLogFileName( char *szName )

/*++

Routine Description:

    Concatinates the base logfile name on to the string passed in.

Arguments:

    szName                 -  buffer for the logfile name.

Return Value:

    None.

--*/

{
    strcat( szName, "\\drwtsn32.log" );
}


MODULE.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    module.c

Abstract:

    This file implements the module load debug events.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"
#include "messages.h"

//
// defines for symbol (.dbg) file searching
//
#define SYMBOL_PATH             "_NT_SYMBOL_PATH"
#define ALTERNATE_SYMBOL_PATH   "_NT_ALT_SYMBOL_PATH"
char szApp[MAX_PATH];

//
// local prototypes
//
LPSTR GetSymbolSearchPath( void );

BOOL
ProcessModuleLoad ( PDEBUGPACKET dp, LPDEBUG_EVENT de )

/*++

Routine Description:

    Process all module load debug events, create process & dll load.
    The purpose is to allocate a MODULEINFO structure, fill in the
    necessary values, and load the symbol table.

Arguments:

    dp      - pointer to a debug packet
    de      - pointer to a debug event structure

Return Value:

    TRUE    - everything worked
    FALSE   - we're hosed

--*/

{
    HANDLE      hFile;
    DWORD       dwBaseOfImage;
    LPSTR       SymbolPath;

    if (de->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
        hFile = de->u.CreateProcessInfo.hFile;
        dwBaseOfImage = (DWORD)de->u.CreateProcessInfo.lpBaseOfImage;
        dp->hProcess = de->u.CreateProcessInfo.hProcess;
        dp->dwProcessId = de->dwProcessId;
        SymInitialize( dp->hProcess, NULL, FALSE );
        SymbolPath = GetSymbolSearchPath();
        SymSetSearchPath( dp->hProcess, SymbolPath );
        free( SymbolPath );
    } else if (de->dwDebugEventCode == LOAD_DLL_DEBUG_EVENT) {
        hFile = de->u.LoadDll.hFile;
        dwBaseOfImage = (DWORD)de->u.LoadDll.lpBaseOfDll;
    }

    if ((hFile == NULL) || (hFile == INVALID_HANDLE_VALUE)) {
        return FALSE;
    }

    if (!SymLoadModule( dp->hProcess, hFile, NULL, NULL, dwBaseOfImage, 0 )) {
        return FALSE;
    } else {
        if (de->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
            IMAGEHLP_MODULE   mi;
            if (SymGetModuleInfo( dp->hProcess, dwBaseOfImage, &mi )) {
                strcpy( szApp, mi.ImageName );
            }
        }
    }

    return TRUE;
}

LPSTR
GetSymbolSearchPath( void )

/*++

Routine Description:

    Gets the search path to be used for locating a .DBG file.

Arguments:

    None.

Return Value:

    pointer to the path string

--*/

{
    LPSTR   lpSymPathEnv      = NULL;
    LPSTR   lpAltSymPathEnv   = NULL;
    LPSTR   lpSystemRootEnv   = NULL;
    LPSTR   SymbolSearchPath  = NULL;
    DWORD   cbSymPath         = 0;

    cbSymPath = 16;
    if (lpSymPathEnv = getenv(SYMBOL_PATH)) {
        cbSymPath += strlen(lpSymPathEnv) + 1;
    }
    if (lpAltSymPathEnv = getenv(ALTERNATE_SYMBOL_PATH)) {
        cbSymPath += strlen(lpAltSymPathEnv) + 1;
    }
    if (lpSystemRootEnv = getenv("SystemRoot")) {
        cbSymPath += strlen(lpSystemRootEnv) + 1;
    }

    SymbolSearchPath = calloc(cbSymPath,1);

    if (lpAltSymPathEnv) {
        strcat(SymbolSearchPath,lpAltSymPathEnv);
        strcat(SymbolSearchPath,";");
    }
    if (lpSymPathEnv) {
        strcat(SymbolSearchPath,lpSymPathEnv);
        strcat(SymbolSearchPath,";");
    }
    if (lpSystemRootEnv) {
        strcat(SymbolSearchPath,lpSystemRootEnv);
        strcat(SymbolSearchPath,";");
    }
    return SymbolSearchPath;
}


NOTIFY.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    browse.c

Abstract:
    This file implements the functions that make use of the common
    file open dialogs for browsing for files/directories.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <direct.h>

#include "drwatson.h"
#include "proto.h"
#include "resource.h"
#include "messages.h"

//
// defines
//
#define DEFAULT_WAIT_TIME   (1000 * 60 * 5)     // wait for 5 minutes

//
// static global variables
//
static HANDLE         hThreadDebug;
static PDEBUGPACKET   dp;

LRESULT NotifyWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK UsageDialogProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

void
NotifyWinMain ( void )

/*++

Routine Description:

    This is the entry point for DRWTSN32

Arguments:

    None.

Return Value:

    None.

--*/

{
    MSG            msg;
    WNDCLASS       wndclass;
    DWORD          dwThreadId;
    HINSTANCE      hInst;

    dp = (PDEBUGPACKET) malloc( sizeof(DEBUGPACKET) );
    memset( dp, 0, sizeof(DEBUGPACKET) );
    GetCommandLineArgs( &dp->dwPidToDebug, &dp->hEventToSignal );

    InitializeListHead(&dp->ThreadList);

    RegInitialize( &dp->options );

    if (dp->options.fVisual) {
        hInst                   = GetModuleHandle( NULL );
        wndclass.style          = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc    = NotifyWndProc;
        wndclass.cbClsExtra     = 0;
        wndclass.cbWndExtra     = DLGWINDOWEXTRA;
        wndclass.hInstance      = hInst;
        wndclass.hIcon          = LoadIcon( hInst, MAKEINTRESOURCE(APPICON) );
        wndclass.hCursor        = LoadCursor( NULL, IDC_ARROW );
        wndclass.hbrBackground  = (HBRUSH) (COLOR_3DFACE + 1);
        wndclass.lpszMenuName   = NULL;
        wndclass.lpszClassName  = "NotifyDialog";
        RegisterClass( &wndclass );

        dp->hwnd = CreateDialog( hInst,
                                 MAKEINTRESOURCE( NOTIFYDIALOG ),
                                 0,
                                 NotifyWndProc );
    }

    hThreadDebug = CreateThread( NULL,
                            16000,
                            (LPTHREAD_START_ROUTINE)DispatchDebugEventThread,
                            dp,
                            0,
                            (LPDWORD)&dwThreadId
                          );

    if (dp->options.fSound) {
        if ((waveOutGetNumDevs() == 0) || (!strlen(dp->options.szWaveFile))) {
            MessageBeep( MB_ICONHAND );
            MessageBeep( MB_ICONHAND );
        }
        else {
            PlaySound( dp->options.szWaveFile, NULL, SND_FILENAME );
        }
    }

    if (dp->options.fVisual) {
        ShowWindow( dp->hwnd, SW_SHOWNORMAL );
        while (GetMessage (&msg, NULL, 0, 0)) {
            if (!IsDialogMessage( dp->hwnd, &msg )) {
                TranslateMessage (&msg) ;
                DispatchMessage (&msg) ;
            }
        }
    }
    else {
        WaitForSingleObject( hThreadDebug, INFINITE );
    }

    CloseHandle( hThreadDebug );

    return;
}

LRESULT
NotifyWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

/*++

Routine Description:

    Window procedure for the DRWTSN32.EXE popup.  This is the popup
    that is displayed when an application error occurs.

Arguments:

    hwnd       - window handle to the dialog box
    message    - message number
    wParam     - first message parameter
    lParam     - second message parameter

Return Value:

    TRUE       - did not process the message
    FALSE      - did process the message

--*/

{
    DWORD          dwThreadId;
    DWORD          dwSize;
    HANDLE         hThread;
    char           szTaskName[MAX_PATH];
    char           szHelpFileName[MAX_PATH];

    switch (message) {
        case WM_CREATE:
            return FALSE;

        case WM_INITDIALOG:

            SubclassControls( hwnd );

            //
            // OK is not enabled until the debugger thread finishes
            //
            EnableWindow( GetDlgItem( hwnd, IDOK ), FALSE );

            //
            // CANCEL is not enabled until debugactiveprocess is finished
            //
            EnableWindow( GetDlgItem( hwnd, IDCANCEL ), FALSE );

            //
            //  make sure that the user can see the dialog box
            //
            SetForegroundWindow( hwnd );

            //
            // get the task name and display it on the dialog box
            //
            dwSize = sizeof(szTaskName);
            GetTaskName( dp->dwPidToDebug, szTaskName, &dwSize );

            //
            // prevent recursion in the case where drwatson faults
            //
            if (_stricmp(szTaskName,"drwtsn32")==0) {
                ExitProcess(0);
            }

            SetDlgItemText( hwnd, ID_TEXT1, szTaskName);

            return TRUE;

        case WM_ACTIVATEAPP:
        case WM_SETFOCUS:
            SetFocusToCurrentControl();
            return 0;

        case WM_COMMAND:
            switch (wParam) {
                case IDOK:
                    PostQuitMessage( 0 );
                    break;

                case IDCANCEL:
                    //
                    // terminate the debugger thread
                    //
                    TerminateThread( hThreadDebug, 0 );

                    //
                    // create a thread to terminate the debuggee
                    // this is necessary if cancel is pressed before the
                    // debugger thread finishes the postmortem dump
                    //
                    hThread = CreateThread( NULL,
                                  16000,
                                  (LPTHREAD_START_ROUTINE)TerminationThread,
                                  dp,
                                  0,
                                  (LPDWORD)&dwThreadId
                                );

                    //
                    // wait for the termination thread to kill the debuggee
                    //
                    WaitForSingleObject( hThread, 30000 );

                    CloseHandle( hThread );

                    //
                    // now post a quit message so that DrWatson will go away
                    //
                    PostQuitMessage( 0 );
                    break;

                case ID_HELP:
                    //
                    // call winhelp
                    //
                    GetHelpFileName( szHelpFileName, sizeof(szHelpFileName) );
                    WinHelp( hwnd, szHelpFileName, HELP_FINDER, IDH_WHAT );
                    break;
            }
            break;

        case WM_NEXTDLGCTL:
            DefDlgProc( hwnd, message, wParam, lParam );
            return 0;

        case WM_DUMPCOMPLETE:

            //
            // the message is received from the debugger thread
            // when the postmortem dump is finished.  all we need to do
            // is enable the OK button and wait for the user to press the
            // OK button or for the timer to expire.  in either case
            // DrWatson will terminate.
            //
            EnableWindow( GetDlgItem( hwnd, IDOK ), TRUE );
            SetFocus( GetDlgItem(hwnd, IDOK) );
            SetFocusToCurrentControl();
            return 0;

        case WM_ATTACHCOMPLETE:

            //
            // the message is received from the debugger thread when
            // the debugactiveprocess() is completed
            //
            EnableWindow( GetDlgItem( hwnd, IDCANCEL ), TRUE );
            return 0;

        case WM_EXCEPTIONINFO:

            SetDlgItemText( hwnd, ID_TEXT2, (char *) lParam);
            return 0;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            return 0;
    }

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

BOOLEAN
GetCommandLineArgs( LPDWORD dwPidToDebug, LPHANDLE hEventToSignal )

/*++

Routine Description:

    Parses the command line for the 3 possible command lines
    arguments:

         -p %ld        process id
         -e %ld        event id
         -g            go

Arguments:

    dp             - pointer to a debug packet

Return Value:

    None.

--*/

{
    char        *lpstrCmd = GetCommandLine();
    UCHAR       ch;
    char        buf[4096];
    BOOLEAN     rval = FALSE;

    // skip over program name
    do {
        ch = *lpstrCmd++;
    }
    while (ch != ' ' && ch != '\t' && ch != '\0');

    //  skip over any following white space
    while (ch == ' ' || ch == '\t') {
        ch = *lpstrCmd++;
    }

    //  process each switch character '-' as encountered

    while (ch == '-') {
        ch = *lpstrCmd++;
        //  process multiple switch characters as needed
        do {
            switch (ch) {
                case 'e':
                case 'E':
                    // event to signal takes decimal argument
                    // skip whitespace
                    do {
                        ch = *lpstrCmd++;
                    }
                    while (ch == ' ' || ch == '\t');
                    while (ch >= '0' && ch <= '9') {
                        (DWORD)*hEventToSignal =
                                  (DWORD)*hEventToSignal * 10 + ch - '0';
                        ch = *lpstrCmd++;
                    }
                    rval = TRUE;
                    break;

                case 'p':
                case 'P':
                    // pid debug takes decimal argument

                    do
                        ch = *lpstrCmd++;
                    while (ch == ' ' || ch == '\t');

                    if ( ch == '-' ) {
                        ch = *lpstrCmd++;
                        if ( ch == '1' ) {
                            *dwPidToDebug = 0xffffffff;
                            ch = *lpstrCmd++;
                        }
                    }
                    else {
                        while (ch >= '0' && ch <= '9') {
                            *dwPidToDebug =
                                       *dwPidToDebug * 10 + ch - '0';
                            ch = *lpstrCmd++;
                        }
                    }
                    rval = TRUE;
                    break;

                case 'g':
                case 'G':
                    ch = *lpstrCmd++;
                    break;

                case '?':
                    DialogBox( GetModuleHandle(NULL),
                               MAKEINTRESOURCE(USAGEDIALOG),
                               NULL,
                               UsageDialogProc
                             );
                    rval = TRUE;
                    ch = *lpstrCmd++;
                    break;

                case 'i':
                case 'I':
                    FormatMessage(
                      FORMAT_MESSAGE_FROM_HMODULE,
                      NULL,
                      MSG_INSTALL_NOTIFY,
                      0,
                      buf,
                      sizeof(buf),
                      NULL
                      );
                    RegInstallDrWatson( tolower(lpstrCmd[0]) == 'q' );
                    MessageBox( NULL,
                                buf,
                                "Dr. Watson for Windows NT",
                                MB_ICONINFORMATION | MB_OK |
                                MB_SETFOREGROUND );
                    rval = TRUE;
                    ch = *lpstrCmd++;
                    break;

                default:
                    return rval;
            }
        }
        while (ch != ' ' && ch != '\t' && ch != '\0');

        while (ch == ' ' || ch == '\t') {
            ch = *lpstrCmd++;
        }
    }
    return rval;
}

BOOL CALLBACK
UsageDialogProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)

/*++

Routine Description:

    This is the dialog procedure for the assert dialog box.  Normally
    an assertion box is simply a message box but in this case a Help
    button is desired so a dialog box is used.

Arguments:

    hDlg       - window handle to the dialog box
    message    - message number
    wParam     - first message parameter
    lParam     - second message parameter

Return Value:

    TRUE       - did not process the message
    FALSE      - did process the message

--*/

{
    char        buf[4096];

    switch (message) {
        case WM_INITDIALOG:
            FormatMessage(
              FORMAT_MESSAGE_FROM_HMODULE,
              NULL,
              MSG_USAGE,
              0, // GetUserDefaultLangID(),
              buf,
              sizeof(buf),
              NULL
              );
            SetDlgItemText( hDlg, ID_USAGE, buf );
            break;

        case WM_COMMAND:
            switch (wParam) {
                case IDOK:
                    EndDialog( hDlg, 0 );
                    break;
            }
            break;
    }

    return FALSE;
}


PROCESS.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    process.c

Abstract:

    This code provides access to the task list.

Author:

    Wesley Witt (wesw) 16-June-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <winperf.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "drwatson.h"
#include "proto.h"
#include "messages.h"

//
// task list structure returned from GetTaskList()
//
typedef struct _TASK_LIST {
    DWORD   dwProcessId;
    char    ProcessName[MAX_PATH];
} TASK_LIST, *PTASK_LIST;

//
// defines
//
#define INITIAL_SIZE        51200
#define EXTEND_SIZE         25600
#define REGKEY_PERF         "software\\microsoft\\windows nt\\currentversion\\perflib"
#define REGSUBKEY_COUNTERS  "Counters"
#define PROCESS_COUNTER     "process"
#define PROCESSID_COUNTER   "id process"
#define UNKNOWN_TASK        "unknown"

//
// prototypes
//
PTASK_LIST GetTaskList( LPLONG pNumTasks );

void
LogTaskList( void )

/*++

Routine Description:

    This function gets the current task list and logs the process id &
    process name to the log file.

Arguments:

    None.

Return Value:

    None.

--*/

{
    PTASK_LIST   pTask;
    PTASK_LIST   pTaskBegin;
    LONG         NumTasks;

    lprintf( MSG_TASK_LIST );

    pTask = pTaskBegin = GetTaskList( &NumTasks );

    if (pTask == NULL) {
        printf( "ERROR: could not get the task list\n" );
    }

    while (NumTasks--) {
        lprintfs("%4d %s\r\n",pTask->dwProcessId, pTask->ProcessName );
        pTask++;
    }
    lprintfs( "\r\n" );

    free( pTaskBegin );
}

void
GetTaskName( ULONG pid, char *szTaskName, LPDWORD pdwSize )

/*++

Routine Description:

    Gets the task name for a given process id.

Arguments:

    pid              - Process id to look for.
    szTaskName       - Buffer to put the task name into.
    lpdwSize         - Pointer to a dword.  On entry it contains the
                       size of the szTaskName buffer.  On exit it contains
                       the number of characters in the buffer.

Return Value:

    None.

--*/

{
    PTASK_LIST   pTask;
    PTASK_LIST   pTaskBegin;
    LONG         NumTasks;

    pTask = pTaskBegin = GetTaskList( &NumTasks );

    if (pTask == NULL) {
        if (szTaskName) {
            strncpy( szTaskName, "unknown", *pdwSize );
        }
        *pdwSize = min( 7, *pdwSize );

    } else {

        while (NumTasks--) {
            if (pTask->dwProcessId == pid) {
                if (szTaskName) {
                    strncpy( szTaskName, pTask->ProcessName, *pdwSize );
                }
                *pdwSize = min( strlen(pTask->ProcessName), *pdwSize );
                break;
            }
            pTask++;
        }

        if (NumTasks < 0) {
            if (szTaskName) {
                strncpy( szTaskName, "<exited>", *pdwSize );
            }
            *pdwSize = min( 8, *pdwSize );
        }

        free( pTaskBegin );
    }
}

PTASK_LIST
GetTaskList( LPLONG pNumTasks )

/*++

Routine Description:

    Provides an API for getting a list of tasks running at the time of the
    API call.  This function uses the registry performance data to get the
    task list and is therefor straight WIN32 calls that anyone can call.

Arguments:

    pNumTasks      - pointer to a dword that will be set to the
                       number of tasks returned.

Return Value:

    PTASK_LIST       - pointer to an array of TASK_LIST records.

--*/

{
    DWORD                        rc;
    HKEY                         hKeyNames;
    DWORD                        dwType;
    DWORD                        dwSize;
    LPBYTE                       buf = NULL;
    char                         szSubKey[1024];
    LANGID                       lid;
    LPSTR                        p;
    LPSTR                        p2;
    PPERF_DATA_BLOCK             pPerf;
    PPERF_OBJECT_TYPE            pObj;
    PPERF_INSTANCE_DEFINITION    pInst;
    PPERF_COUNTER_BLOCK          pCounter;
    PPERF_COUNTER_DEFINITION     pCounterDef;
    DWORD                        i;
    DWORD                        dwProcessIdTitle;
    DWORD                        dwProcessIdCounter;
    PTASK_LIST                   pTask;
    PTASK_LIST                   pTaskReturn = NULL;
    char                         szProcessName[MAX_PATH];

    //
    // set the number of tasks to zero until we get some
    //
    *pNumTasks = 0;

    //
    // Look for the list of counters.  Always use the neutral
    // English version, regardless of the local language.  We
    // are looking for some particular keys, and we are always
    // going to do our looking in English.  We are not going
    // to show the user the counter names, so there is no need
    // to go find the corresponding name in the local language.
    //
    lid = MAKELANGID( LANG_ENGLISH, SUBLANG_NEUTRAL );
    sprintf( szSubKey, "%s\\%03x", REGKEY_PERF, lid );
    rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                       szSubKey,
                       0,
                       KEY_READ,
                       &hKeyNames
                     );
    if (rc != ERROR_SUCCESS) {
        goto exit;
    }

    //
    // get the buffer size for the counter names
    //
    rc = RegQueryValueEx( hKeyNames,
                          REGSUBKEY_COUNTERS,
                          NULL,
                          &dwType,
                          NULL,
                          &dwSize
                        );

    if (rc != ERROR_SUCCESS) {
        goto exit;
    }

    //
    // allocate the counter names buffer
    //
    buf = (LPBYTE) malloc( dwSize );
    if (buf == NULL) {
        goto exit;
    }
    memset( buf, 0, dwSize );

    //
    // read the counter names from the registry
    //
    rc = RegQueryValueEx( hKeyNames,
                          REGSUBKEY_COUNTERS,
                          NULL,
                          &dwType,
                          buf,
                          &dwSize
                        );

    if (rc != ERROR_SUCCESS) {
        goto exit;
    }

    //
    // now loop thru the counter names looking for the following counters:
    //
    //      1.  "Process"           process name
    //      2.  "ID Process"        process id
    //
    // the buffer contains multiple null terminated strings and then
    // finally null terminated at the end.  the strings are in pairs of
    // counter number and counter name.
    //

    p = buf;
    while (*p) {
        if (_stricmp(p, PROCESS_COUNTER) == 0) {
            //
            // look backwards for the counter number
            //
            for( p2=p-2; isdigit(*p2); p2--) ;
            strcpy( szSubKey, p2+1 );
        }
        else
        if (_stricmp(p, PROCESSID_COUNTER) == 0) {
            //
            // look backwards for the counter number
            //
            for( p2=p-2; isdigit(*p2); p2--) ;
            dwProcessIdTitle = atol( p2+1 );
        }
        //
        // next string
        //
        p += (strlen(p) + 1);
    }

    //
    // free the counter names buffer
    //
    free( buf );

    //
    // allocate the initial buffer for the performance data
    //
    dwSize = INITIAL_SIZE;
    buf = malloc( dwSize );
    if (buf == NULL) {
        goto exit;
    }
    memset( buf, 0, dwSize );

    while (TRUE) {

        rc = RegQueryValueEx( HKEY_PERFORMANCE_DATA,
                              szSubKey,
                              NULL,
                              &dwType,
                              buf,
                              &dwSize
                            );

        pPerf = (PPERF_DATA_BLOCK) buf;

        //
        // check for success and valid perf data block signature
        //
        if ((rc == ERROR_SUCCESS) &&
            (dwSize > 0) &&
            (pPerf)->Signature[0] == (WCHAR)'P' &&
            (pPerf)->Signature[1] == (WCHAR)'E' &&
            (pPerf)->Signature[2] == (WCHAR)'R' &&
            (pPerf)->Signature[3] == (WCHAR)'F' ) {
            break;
        }

        //
        // if buffer is not big enough, reallocate and try again
        //
        if (rc == ERROR_MORE_DATA) {
            dwSize += EXTEND_SIZE;
            buf = realloc( buf, dwSize );
            memset( buf, 0, dwSize );
        }
        else {
            goto exit;
        }
    }

    //
    // set the perf_object_type pointer
    //
    pObj = (PPERF_OBJECT_TYPE) ((DWORD)pPerf + pPerf->HeaderLength);

    //
    // loop thru the performance counter definition records looking
    // for the process id counter and then save its offset
    //
    pCounterDef = (PPERF_COUNTER_DEFINITION) ((DWORD)pObj + pObj->HeaderLength);
    for (i=0; i<(DWORD)pObj->NumCounters; i++) {
        if (pCounterDef->CounterNameTitleIndex == dwProcessIdTitle) {
            dwProcessIdCounter = pCounterDef->CounterOffset;
            break;
        }
        pCounterDef++;
    }

    //
    // allocate a buffer for the returned task list
    //
    dwSize = pObj->NumInstances * sizeof(TASK_LIST);
    pTask = pTaskReturn = (PTASK_LIST) malloc( dwSize );
    if (pTask == NULL) {
        goto exit;
    }
    memset( pTask, 0, dwSize);

    //
    // loop thru the performance instance data extracting each process name
    // and process id
    //
    *pNumTasks = pObj->NumInstances;
    pInst = (PPERF_INSTANCE_DEFINITION) ((DWORD)pObj + pObj->DefinitionLength);
    for (i=0; i<(DWORD)pObj->NumInstances; i++) {
        //
        // pointer to the process name
        //
        p = (LPSTR) ((DWORD)pInst + pInst->NameOffset);

        //
        // convert it to ascii
        //
        rc = WideCharToMultiByte( CP_ACP,
                                  0,
                                  (LPCWSTR)p,
                                  -1,
                                  szProcessName,
                                  sizeof(szProcessName),
                                  NULL,
                                  NULL
                                );

        if (!rc) {
            //
            // if we cant convert the string then use a bogus value
            //
            strcpy( pTask->ProcessName, UNKNOWN_TASK );
        }

        if (strlen(szProcessName)+4 <= sizeof(pTask->ProcessName)) {
            strcpy( pTask->ProcessName, szProcessName );
            strcat( pTask->ProcessName, ".exe" );
        }

        //
        // get the process id
        //
        pCounter = (PPERF_COUNTER_BLOCK) ((DWORD)pInst + pInst->ByteLength);
        pTask->dwProcessId = *((LPDWORD) ((DWORD)pCounter + dwProcessIdCounter));

        //
        // next process
        //
        pTask++;
        pInst = (PPERF_INSTANCE_DEFINITION) ((DWORD)pCounter + pCounter->ByteLength);
    }

exit:
    if (buf) {
        free( buf );
    }

    RegCloseKey( hKeyNames );

    return pTaskReturn;
}


BROWSE.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    browse.c

Abstract:
    This file implements the functions that make use of the common
    file open dialogs for browsing for files/directories.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <direct.h>

#include "drwatson.h"
#include "proto.h"
#include "resource.h"

static char   szHelpFileName[MAX_PATH];
static char   szLastWaveFile[MAX_PATH];
static char   szLastDumpFile[MAX_PATH];

LRESULT PASCAL
BrowseHookProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )

/*++

Routine Description:

    Hook procedure for directory browse common file dialog.  This hook
    procedure is required to provide help, put the window in the
    foreground, and set the edit so that the common file dialog dll
    thinks the user entered a value.

Arguments:

    hwnd       - window handle to the dialog box
    message    - message number
    wParam     - first message parameter
    lParam     - second message parameter

Return Value:

    TRUE       - did not process the message
    FALSE      - did process the message

--*/

{
    if (message==WM_INITDIALOG) {
        SetForegroundWindow( hwnd );
    }
    else
    if (message==WM_PAINT) {
        SetDlgItemText( hwnd, edt1, "drwatson.log" );
    }
    else
    if (message==WM_COMMAND && wParam==psh15) {
        //
        // get the help file name
        //
        GetHelpFileName( szHelpFileName, sizeof( szHelpFileName ) );

        //
        // call winhelp
        //
        WinHelp( hwnd, szHelpFileName, HELP_FINDER, IDH_LOGFILELOCATION );
    }
    return FALSE;
}

BOOL
BrowseForDirectory( char *szCurrDir )

/*++

Routine Description:

    Presents a common file open dialog that contains only the directory
    tree.  The use can select a directory for use as a storage location
    for the DRWTSN32 log file.

Arguments:

    szCurrDir  - current directory

Return Value:

    TRUE       - got a good directory (user pressed the OK button)
    FALSE      - got nothing (user pressed the CANCEL button)

    the szCurrDir is also changed to have the selected directory.

--*/

{
    OPENFILENAME   of;
    char           ftitle     [MAX_PATH];
    char           title      [MAX_PATH];
    char           fname      [MAX_PATH];
    char           szDrive    [_MAX_DRIVE];
    char           szDir      [_MAX_DIR];

    ftitle[0] = 0;
    strcpy( fname, "*.*" );
    of.lStructSize = sizeof( OPENFILENAME );
    of.hwndOwner = NULL;
    of.hInstance = GetModuleHandle( NULL );
    of.lpstrFilter = NULL;
    of.lpstrCustomFilter = NULL;
    of.nMaxCustFilter = 0;
    of.nFilterIndex = 0;
    of.lpstrFile = fname;
    of.nMaxFile = MAX_PATH;
    of.lpstrFileTitle = ftitle;
    of.nMaxFileTitle = MAX_PATH;
    of.lpstrInitialDir = szCurrDir;
    strcpy( title, LoadRcString( IDS_LOGBROWSE_TITLE ) );
    of.lpstrTitle = title;
    of.Flags = OFN_NONETWORKBUTTON |
               OFN_ENABLEHOOK      |
               OFN_NOCHANGEDIR     |
               OFN_SHOWHELP        |
               OFN_ENABLETEMPLATE;
    of.nFileOffset = 0;
    of.nFileExtension = 0;
    of.lpstrDefExt = NULL;
    of.lCustData = 0;
    of.lpfnHook = BrowseHookProc;
    of.lpTemplateName = MAKEINTRESOURCE(DIRBROWSEDIALOG);
    if (GetSaveFileName( &of )) {
        _splitpath( fname, szDrive, szDir, NULL, NULL );
        strcpy( szCurrDir, szDrive );
        strcat( szCurrDir, szDir );
        szCurrDir[strlen(szCurrDir)-1] = '\0';
        return TRUE;
    }
    return FALSE;
}

LRESULT PASCAL
WaveHookProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )

/*++

Routine Description:

    Hook procedure for wave file selection common file dialog.  This hook
    procedure is required to provide help, put the window in the
    foreground, and provide a test button for listening to a wave file.

Arguments:

    hwnd       - window handle to the dialog box
    message    - message number
    wParam     - first message parameter
    lParam     - second message parameter

Return Value:

    TRUE       - did not process the message
    FALSE      - did process the message

--*/

{
    char szWave[MAX_PATH];

    if (message==WM_INITDIALOG) {
        SetForegroundWindow( hwnd );
    }
    else
    if (message == WM_COMMAND) {
        switch (wParam) {
            case ID_TEST_WAVE:
                GetDlgItemText( hwnd, edt1, szWave, sizeof(szWave) );
                PlaySound( szWave, NULL, SND_FILENAME );
                break;

            case psh15:
                //
                // get the help file name
                //
                GetHelpFileName( szHelpFileName, sizeof( szHelpFileName ) );

                //
                // call winhelp
                //
                WinHelp( hwnd, szHelpFileName, HELP_FINDER, IDH_WAVEFILE );
                break;
        }
    }

    return FALSE;
}

BOOL
GetWaveFileName( char *szWaveName )

/*++

Routine Description:

    Presents a common file open dialog for the purpose of selecting a
    wave file to be played when an application error occurs.

Arguments:

    szWaveName - name of the selected wave file

Return Value:

    TRUE       - got a good wave file name (user pressed the OK button)
    FALSE      - got nothing (user pressed the CANCEL button)

    the szWaveName is changed to have the selected wave file name.

--*/

{
    OPENFILENAME   of;
    char           ftitle[MAX_PATH];
    char           title[MAX_PATH];
    char           fname[MAX_PATH];
    char           filter[1024];
    char           szDrive    [_MAX_DRIVE];
    char           szDir      [_MAX_DIR];

    ftitle[0] = 0;
    strcpy( fname, "*.wav" );
    of.lStructSize = sizeof( OPENFILENAME );
    of.hwndOwner = NULL;
    of.hInstance = GetModuleHandle( NULL );
    strcpy( filter, LoadRcString( IDS_WAVE_FILTER ) );
    strcpy( &filter[strlen(filter)+1], "*.wav" );
    filter[strlen(filter)+1] = '\0';
    of.lpstrFilter = filter;
    of.lpstrCustomFilter = NULL;
    of.nMaxCustFilter = 0;
    of.nFilterIndex = 0;
    of.lpstrFile = fname;
    of.nMaxFile = MAX_PATH;
    of.lpstrFileTitle = ftitle;
    of.nMaxFileTitle = MAX_PATH;
    of.lpstrInitialDir = szLastWaveFile;
    strcpy( title, LoadRcString( IDS_WAVEBROWSE_TITLE ) );
    of.lpstrTitle = title;
    of.Flags = OFN_NONETWORKBUTTON |
               OFN_ENABLEHOOK      |
               OFN_ENABLETEMPLATE  |
               OFN_SHOWHELP        |
               OFN_NOCHANGEDIR;
    of.nFileOffset = 0;
    of.nFileExtension = 0;
    of.lpstrDefExt = "wav";
    of.lCustData = 0;
    of.lpfnHook = WaveHookProc;
    of.lpTemplateName = MAKEINTRESOURCE(WAVEFILEOPENDIALOG);
    if (GetOpenFileName( &of )) {
        strcpy( szWaveName, fname );
        _splitpath( fname, szDrive, szDir, NULL, NULL );
        strcpy( szLastWaveFile, szDrive );
        strcat( szLastWaveFile, szDir );
        return TRUE;
    }
    return FALSE;
}

LRESULT PASCAL
DumpHookProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )

/*++

Routine Description:

    Hook procedure for wave file selection common file dialog.  This hook
    procedure is required to provide help, put the window in the
    foreground, and provide a test button for listening to a wave file.

Arguments:

    hwnd       - window handle to the dialog box
    message    - message number
    wParam     - first message parameter
    lParam     - second message parameter

Return Value:

    TRUE       - did not process the message
    FALSE      - did process the message

--*/

{
    if (message == WM_INITDIALOG) {
        SetForegroundWindow( hwnd );
    }
    else
    if (message == WM_COMMAND) {
        switch (wParam) {
            case psh15:
                //
                // get the help file name
                //
                GetHelpFileName( szHelpFileName, sizeof( szHelpFileName ) );

                //
                // call winhelp
                //
                WinHelp( hwnd, szHelpFileName, HELP_FINDER, IDH_CRASH_DUMP );
                break;
        }
    }

    return FALSE;
}

BOOL
GetDumpFileName( char *szDumpName )

/*++

Routine Description:

    Presents a common file open dialog for the purpose of selecting a
    wave file to be played when an application error occurs.

Arguments:

    szWaveName - name of the selected wave file

Return Value:

    TRUE       - got a good wave file name (user pressed the OK button)
    FALSE      - got nothing (user pressed the CANCEL button)

    the szWaveName is changed to have the selected wave file name.

--*/

{
    OPENFILENAME   of;
    char           ftitle[MAX_PATH];
    char           title[MAX_PATH];
    char           fname[MAX_PATH];
    char           filter[1024];
    char           szDrive    [_MAX_DRIVE];
    char           szDir      [_MAX_DIR];

    ftitle[0] = 0;
    strcpy( fname, "*.dmp" );
    of.lStructSize = sizeof( OPENFILENAME );
    of.hwndOwner = NULL;
    of.hInstance = GetModuleHandle( NULL );
    strcpy( filter, LoadRcString( IDS_DUMP_FILTER ) );
    strcpy( &filter[strlen(filter)+1], "*.dmp" );
    filter[strlen(filter)+1] = '\0';
    of.lpstrFilter = filter;
    of.lpstrCustomFilter = NULL;
    of.nMaxCustFilter = 0;
    of.nFilterIndex = 0;
    of.lpstrFile = fname;
    of.nMaxFile = MAX_PATH;
    of.lpstrFileTitle = ftitle;
    of.nMaxFileTitle = MAX_PATH;
    of.lpstrInitialDir = szLastDumpFile;
    strcpy( title, LoadRcString( IDS_DUMPBROWSE_TITLE ) );
    of.lpstrTitle = title;
    of.Flags = OFN_NONETWORKBUTTON |
               OFN_ENABLEHOOK      |
               OFN_ENABLETEMPLATE  |
               OFN_SHOWHELP        |
               OFN_NOCHANGEDIR;
    of.nFileOffset = 0;
    of.nFileExtension = 0;
    of.lpstrDefExt = "dmp";
    of.lCustData = 0;
    of.lpfnHook = DumpHookProc;
    of.lpTemplateName = MAKEINTRESOURCE(DUMPFILEOPENDIALOG);
    if (GetOpenFileName( &of )) {
        strcpy( szDumpName, fname );
        _splitpath( fname, szDrive, szDir, NULL, NULL );
        strcpy( szLastDumpFile, szDrive );
        strcat( szLastDumpFile, szDir );
        return TRUE;
    }
    return FALSE;
}


REGISTRY.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    registry.c

Abstract:

    This file implements the apis for DRWTSN32 to access the registry.
    All access to the registry are done in this file.  If additional
    registry control is needed then a function should be added in this file
    and exposed to the other files in DRWTSN32.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"
#include "messages.h"

//
// string constants for accessing the registry
// there is a string constant here for each key and each value
// that is accessed in the registry.
//
#define DRWATSON_EXE_NAME           "drwtsn32.exe"
#define REGKEY_SOFTWARE             "software\\microsoft"
#define REGKEY_MESSAGEFILE          "EventMessageFile"
#define REGKEY_TYPESSUPP            "TypesSupported"
#define REGKEY_SYSTEMROOT           "%SystemRoot%\\System32\\"
#define REGKEY_EVENTLOG             "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\"
#define REGKEY_APPNAME              "ApplicationName"
#define REGKEY_FUNCTION             "FunctionName"
#define REGKEY_EXCEPTIONCODE        "ExceptionCode"
#define REGKEY_ADDRESS              "Address"
#define REGKEY_LOG_PATH             "LogFilePath"
#define REGKEY_DUMPSYMBOLS          "DumpSymbols"
#define REGKEY_DUMPALLTHREADS       "DumpAllThreads"
#define REGKEY_APPENDTOLOGFILE      "AppendToLogFile"
#define REGKEY_INSTRUCTIONS         "Instructions"
#define REGKEY_VISUAL               "VisualNotification"
#define REGKEY_SOUND                "SoundNotification"
#define REGKEY_CRASH_DUMP           "CreateCrashDump"
#define REGKEY_CRASH_FILE           "CrashDumpFile"
#define REGKEY_WAVE_FILE            "WaveFile"
#define REGKEY_NUM_CRASHES          "NumberOfCrashes"
#define REGKEY_MAX_CRASHES          "MaximumCrashes"
#define REGKEY_CURRENTVERSION       "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
#define REGKEY_CONTROLWINDOWS       "SYSTEM\\CurrentControlSet\\Control\\Windows"
#define REGKEY_CSD_VERSION          "CSDVersion"
#define REGKEY_CURRENT_BUILD        "CurrentBuildNumber"
#define REGKEY_CURRENT_TYPE         "CurrentType"
#define REGKEY_REG_ORGANIZATION     "RegisteredOrganization"
#define REGKEY_REG_OWNER            "RegisteredOwner"
#define REGKEY_AEDEBUG              "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug"
#define REGKEY_AUTO                 "Auto"
#define REGKEY_DEBUGGER             "Debugger"
#define REGKEY_PROCESSOR            "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"
#define REGKEY_PROCESSOR_ID         "Identifier"

//
// local prototypes
//
void  RegSetDWORD( HKEY hkey, char *szSubKey, DWORD dwValue );
void  RegSetBOOL( HKEY hkey, char *szSubKey, BOOL dwValue );
void  RegSetSZ( HKEY hkey, char *szSubKey, char *szValue );
void  RegSetEXPANDSZ( HKEY hkey, char *szSubKey, char *szValue );
BOOL  RegQueryBOOL( HKEY hkey, char *szSubKey );
DWORD RegQueryDWORD( HKEY hkey, char *szSubKey );
void  RegQuerySZ( HKEY hkey, char *szSubKey, char *szValue );
BOOL  RegSaveAllValues( HKEY hKeyDrWatson, POPTIONS o );
BOOL  RegGetAllValues( POPTIONS o, HKEY hKeyDrWatson );
BOOL  RegInitializeDefaults( HKEY hKeyDrWatson );
HKEY  RegGetAppKey( void );
BOOL  RegCreateEventSource( void );

BOOL
RegGetAllValues( POPTIONS o, HKEY hKeyDrWatson )

/*++

Routine Description:

    This functions retrieves all registry data for DRWTSN32 and puts
    the data in the OPTIONS structure passed in.

Arguments:

    o              - pointer to an OPTIONS structure
    hKeyDrWatson   - handle to a registry key for DRWTSN32 registry data

Return Value:

    TRUE       - retrieved all data without error
    FALSE      - errors occurred and did not get all data

--*/

{
    RegQuerySZ( hKeyDrWatson, REGKEY_LOG_PATH, o->szLogPath );
    RegQuerySZ( hKeyDrWatson, REGKEY_WAVE_FILE, o->szWaveFile );
    RegQuerySZ( hKeyDrWatson, REGKEY_CRASH_FILE, o->szCrashDump );
    o->fDumpSymbols = RegQueryBOOL( hKeyDrWatson, REGKEY_DUMPSYMBOLS );
    o->fDumpAllThreads = RegQueryBOOL( hKeyDrWatson, REGKEY_DUMPALLTHREADS );
    o->fAppendToLogFile = RegQueryBOOL( hKeyDrWatson, REGKEY_APPENDTOLOGFILE );
    o->fVisual = RegQueryBOOL( hKeyDrWatson, REGKEY_VISUAL );
    o->fSound = RegQueryBOOL( hKeyDrWatson, REGKEY_SOUND );
    o->fCrash = RegQueryBOOL( hKeyDrWatson, REGKEY_CRASH_DUMP );
    o->dwInstructions = RegQueryDWORD( hKeyDrWatson, REGKEY_INSTRUCTIONS );
    o->dwMaxCrashes = RegQueryDWORD( hKeyDrWatson, REGKEY_MAX_CRASHES );

    return TRUE;
}

BOOL
RegSaveAllValues( HKEY hKeyDrWatson, POPTIONS o )

/*++

Routine Description:

    This functions saves all registry data for DRWTSN32 that is passed
    in via the OPTIONS structure.

Arguments:

    hKeyDrWatson   - handle to a registry key for DRWTSN32 registry data
    o              - pointer to an OPTIONS structure

Return Value:

    TRUE       - saved all data without error
    FALSE      - errors occurred and did not save all data

--*/

{
    RegSetSZ( hKeyDrWatson, REGKEY_LOG_PATH, o->szLogPath );
    RegSetSZ( hKeyDrWatson, REGKEY_WAVE_FILE, o->szWaveFile );
    RegSetSZ( hKeyDrWatson, REGKEY_CRASH_FILE, o->szCrashDump );
    RegSetBOOL( hKeyDrWatson, REGKEY_DUMPSYMBOLS, o->fDumpSymbols );
    RegSetBOOL( hKeyDrWatson, REGKEY_DUMPALLTHREADS, o->fDumpAllThreads );
    RegSetBOOL( hKeyDrWatson, REGKEY_APPENDTOLOGFILE, o->fAppendToLogFile );
    RegSetBOOL( hKeyDrWatson, REGKEY_VISUAL, o->fVisual );
    RegSetBOOL( hKeyDrWatson, REGKEY_SOUND, o->fSound );
    RegSetBOOL( hKeyDrWatson, REGKEY_CRASH_DUMP, o->fCrash );
    RegSetDWORD( hKeyDrWatson, REGKEY_INSTRUCTIONS, o->dwInstructions );
    RegSetDWORD( hKeyDrWatson, REGKEY_MAX_CRASHES, o->dwMaxCrashes );

    return TRUE;
}

BOOL
RegInitializeDefaults( HKEY hKeyDrWatson )

/*++

Routine Description:

    This functions initializes the registry with the default values.

Arguments:

    hKeyDrWatson   - handle to a registry key for DRWTSN32 registry data

Return Value:

    TRUE       - saved all data without error
    FALSE      - errors occurred and did not save all data

--*/

{
    OPTIONS o;

    strcpy( o.szLogPath, "%windir%" );
    strcpy( o.szCrashDump, "%windir%\\user.dmp" );
    o.szWaveFile[0] = '\0';
    o.fDumpSymbols = FALSE;
    o.fDumpAllThreads = TRUE;
    o.fAppendToLogFile = TRUE;
    o.fVisual = TRUE;
    o.fSound = FALSE;
    o.fCrash = TRUE;
    o.dwInstructions = 10;
    o.dwMaxCrashes = 10;

    RegSetNumCrashes( 0 );

    RegSaveAllValues( hKeyDrWatson, &o );

    RegCreateEventSource();

    return TRUE;
}

BOOL
RegCreateEventSource( void )

/*++

Routine Description:

    This function creates an event source in the registry.  The event
    source is used by the event viewer to display the data in a
    presentable manner.

Arguments:

    None.

Return Value:

    TRUE       - saved all data without error
    FALSE      - errors occurred and did not save all data

--*/

{
    HKEY   hk;
    char   szBuf[1024];
    DWORD  dwDisp;
    char   szAppName[MAX_PATH];

    GetAppName( szAppName, sizeof(szAppName) );
    strcpy( szBuf, REGKEY_EVENTLOG );
    strcat( szBuf, szAppName );
    if (RegCreateKeyEx( HKEY_LOCAL_MACHINE,
                        szBuf,
                        0,
                        NULL,
                        REG_OPTION_NON_VOLATILE,
                        KEY_QUERY_VALUE | KEY_SET_VALUE,
                        NULL,
                        &hk,
                        &dwDisp
                      )) {
        return FALSE;
    }

    if (dwDisp == REG_OPENED_EXISTING_KEY) {
        RegCloseKey(hk);
        return TRUE;
    }

    strcpy( szBuf, REGKEY_SYSTEMROOT );
    strcat( szBuf, DRWATSON_EXE_NAME );
    RegSetEXPANDSZ( hk, REGKEY_MESSAGEFILE, szBuf );
    RegSetDWORD( hk, REGKEY_TYPESSUPP, EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE );

    RegCloseKey(hk);

    return TRUE;
}

HKEY
RegGetAppKey( void )

/*++

Routine Description:

    This function gets a handle to the DRWTSN32 registry key.

Arguments:

    None.

Return Value:

    Valid handle   - handle opened ok
    NULL           - could not open the handle

--*/

{
    DWORD   rc;
    DWORD   dwDisp;
    HKEY    hKeyDrWatson;
    HKEY    hKeyMicrosoft;
    char    szAppName[MAX_PATH];

    rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                       REGKEY_SOFTWARE,
                       0,
                       KEY_QUERY_VALUE | KEY_SET_VALUE |
                       KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS,
                       &hKeyMicrosoft
                     );

    if (rc != ERROR_SUCCESS) {
        return NULL;
    }

    GetAppName( szAppName, sizeof(szAppName) );

    rc = RegCreateKeyEx( hKeyMicrosoft,
                         szAppName,
                         0,
                         NULL,
                         REG_OPTION_NON_VOLATILE,
                         KEY_READ | KEY_WRITE,
                         NULL,
                         &hKeyDrWatson,
                         &dwDisp
                       );

    if (rc != ERROR_SUCCESS) {
        return NULL;
    }

    if (dwDisp == REG_CREATED_NEW_KEY) {
        RegInitializeDefaults( hKeyDrWatson );
    }

    return hKeyDrWatson;
}

BOOL
RegInitialize( POPTIONS o )

/*++

Routine Description:

    This function is used to initialize the OPTIONS structure passed in
    with the current values in the registry.  Note that if the registry
    is empty then the defaults are stored in the registry and also
    returned in the OPTIONS structure.

Arguments:

    None.

Return Value:

    TRUE           - all data was retrieved ok
    NULL           - could not get all data

--*/

{
    HKEY    hKeyDrWatson;

    hKeyDrWatson = RegGetAppKey();
    Assert( hKeyDrWatson != NULL );

    if (!RegGetAllValues( o, hKeyDrWatson )) {
        return FALSE;
    }

    RegCloseKey( hKeyDrWatson );

    return TRUE;
}

BOOL
RegSave( POPTIONS o )

/*++

Routine Description:

    This function is used to save the data in the OPTIONS structure
    to the registry.

Arguments:

    o              - pointer to an OPTIONS structure

Return Value:

    TRUE           - all data was saved ok
    NULL           - could not save all data

--*/

{
    HKEY    hKeyDrWatson;

    hKeyDrWatson = RegGetAppKey();
    Assert( hKeyDrWatson != NULL );
    RegSaveAllValues( hKeyDrWatson, o );
    RegCloseKey( hKeyDrWatson );

    return TRUE;
}

void
RegSetNumCrashes( DWORD dwNumCrashes )

/*++

Routine Description:

    This function changes the value in the registry that contains the
    number of crashes that have occurred.

Arguments:

    dwNumCrashes   - the number of craches to save

Return Value:

    None.

--*/

{
    HKEY    hKeyDrWatson;

    hKeyDrWatson = RegGetAppKey();
    Assert( hKeyDrWatson != NULL );
    RegSetDWORD( hKeyDrWatson, REGKEY_NUM_CRASHES, dwNumCrashes );
    RegCloseKey( hKeyDrWatson );

    return;
}

DWORD
RegGetNumCrashes( void )

/*++

Routine Description:

    This function get the value in the registry that contains the
    number of crashes that have occurred.

Arguments:

    None.

Return Value:

    the number of craches that have occurred

--*/

{
    HKEY    hKeyDrWatson;
    DWORD   dwNumCrashes;

    hKeyDrWatson = RegGetAppKey();
    Assert( hKeyDrWatson != NULL );
    dwNumCrashes = RegQueryDWORD( hKeyDrWatson, REGKEY_NUM_CRASHES );
    RegCloseKey( hKeyDrWatson );

    return dwNumCrashes;
}

BOOLEAN
RegInstallDrWatson( BOOL fQuiet )

/*++

Routine Description:

    This function gets a handle to the DRWTSN32 registry key.

Arguments:

    None.

Return Value:

    Valid handle   - handle opened ok
    NULL           - could not open the handle

--*/

{
    DWORD     rc;
    HKEY      hKeyMicrosoft;
    OPTIONS   o;

    rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                       REGKEY_AEDEBUG,
                       0,
                       KEY_QUERY_VALUE | KEY_SET_VALUE,
                       &hKeyMicrosoft
                     );

    if (rc != ERROR_SUCCESS) {
        return FALSE;
    }

    RegSetSZ( hKeyMicrosoft, REGKEY_AUTO, "1" );
    RegSetSZ( hKeyMicrosoft, REGKEY_DEBUGGER, "drwtsn32 -p %ld -e %ld -g" );

    RegCloseKey( hKeyMicrosoft );

    RegInitialize( &o );
    if (fQuiet) {
        o.fVisual = FALSE;
        o.fSound = FALSE;
        RegSave( &o );
    }

    return TRUE;
}

void
RegSetDWORD( HKEY hkey, char *szSubKey, DWORD dwValue )

/*++

Routine Description:

    This function changes a DWORD value in the registry using the
    hkey and szSubKey as the registry key info.

Arguments:

    hkey          - handle to a registry key
    szSubKey      - pointer to a subkey string
    dwValue       - new registry value

Return Value:

    None.

--*/

{
    DWORD rc;

    rc = RegSetValueEx( hkey, szSubKey, 0, REG_DWORD, (LPBYTE)&dwValue, 4 );
    Assert( rc == ERROR_SUCCESS );
}

void
RegSetBOOL( HKEY hkey, char *szSubKey, BOOL dwValue )

/*++

Routine Description:

    This function changes a BOOL value in the registry using the
    hkey and szSubKey as the registry key info.

Arguments:

    hkey          - handle to a registry key
    szSubKey      - pointer to a subkey string
    dwValue       - new registry value

Return Value:

    None.

--*/

{
    DWORD rc;

    rc = RegSetValueEx( hkey, szSubKey, 0, REG_DWORD, (LPBYTE)&dwValue, 4 );
    Assert( rc == ERROR_SUCCESS );
}

void
RegSetSZ( HKEY hkey, char *szSubKey, char *szValue )

/*++

Routine Description:

    This function changes a SZ value in the registry using the
    hkey and szSubKey as the registry key info.

Arguments:

    hkey          - handle to a registry key
    szSubKey      - pointer to a subkey string
    szValue       - new registry value

Return Value:

    None.

--*/

{
    DWORD rc;

    rc = RegSetValueEx( hkey, szSubKey, 0, REG_SZ, szValue, strlen(szValue)+1 );
    Assert( rc == ERROR_SUCCESS );
}

void
RegSetEXPANDSZ( HKEY hkey, char *szSubKey, char *szValue )

/*++

Routine Description:

    This function changes a SZ value in the registry using the
    hkey and szSubKey as the registry key info.

Arguments:

    hkey          - handle to a registry key
    szSubKey      - pointer to a subkey string
    szValue       - new registry value

Return Value:

    None.

--*/

{
    DWORD rc;

    rc = RegSetValueEx( hkey, szSubKey, 0, REG_EXPAND_SZ, szValue, strlen(szValue)+1 );
    Assert( rc == ERROR_SUCCESS );
}

BOOL
RegQueryBOOL( HKEY hkey, char *szSubKey )

/*++

Routine Description:

    This function queries BOOL value in the registry using the
    hkey and szSubKey as the registry key info.  If the value is not
    found in the registry, it is added with a FALSE value.

Arguments:

    hkey          - handle to a registry key
    szSubKey      - pointer to a subkey string

Return Value:

    TRUE or FALSE.

--*/

{
    DWORD   rc;
    DWORD   len;
    DWORD   dwType;
    BOOL    fValue;

    len = 4;
    rc = RegQueryValueEx( hkey, szSubKey, 0, &dwType, (LPBYTE)&fValue, &len );
    if (rc != ERROR_SUCCESS) {
        if (rc == ERROR_FILE_NOT_FOUND) {
            fValue = FALSE;
            RegSetBOOL( hkey, szSubKey, fValue );
        }
        else {
            Assert( rc == ERROR_SUCCESS );
        }
    }
    else {
        Assert( dwType == REG_DWORD );
    }

    return fValue;
}

DWORD
RegQueryDWORD( HKEY hkey, char *szSubKey )

/*++

Routine Description:

    This function queries BOOL value in the registry using the
    hkey and szSubKey as the registry key info.  If the value is not
    found in the registry, it is added with a zero value.

Arguments:

    hkey          - handle to a registry key
    szSubKey      - pointer to a subkey string

Return Value:

    registry value

--*/

{
    DWORD   rc;
    DWORD   len;
    DWORD   dwType;
    DWORD   fValue;

    len = 4;
    rc = RegQueryValueEx( hkey, szSubKey, 0, &dwType, (LPBYTE)&fValue, &len );
    if (rc != ERROR_SUCCESS) {
        if (rc == ERROR_FILE_NOT_FOUND) {
            fValue = 0;
            RegSetDWORD( hkey, szSubKey, fValue );
        }
        else {
            Assert( rc == ERROR_SUCCESS );
        }
    }
    else {
        Assert( dwType == REG_DWORD );
    }

    return fValue;
}

void
RegQuerySZ( HKEY hkey, char *szSubKey, char *szValue )

/*++

Routine Description:

    This function queries BOOL value in the registry using the
    hkey and szSubKey as the registry key info.  If the value is not
    found in the registry, it is added with a zero value.

Arguments:

    hkey          - handle to a registry key
    szSubKey      - pointer to a subkey string

Return Value:

    registry value

--*/

{
    DWORD   rc;
    DWORD   len;
    DWORD   dwType;
    char    buf[1024];

    len = sizeof(buf);
    rc = RegQueryValueEx( hkey, szSubKey, 0, &dwType, (LPBYTE)buf, &len );
    if (rc != ERROR_SUCCESS) {
        if (rc == ERROR_FILE_NOT_FOUND) {
            buf[0] = 0;
            RegSetSZ( hkey, szSubKey, buf );
        }
        else {
            Assert( rc == ERROR_SUCCESS );
        }
    }
    else {
        Assert( dwType == REG_SZ );
    }

    strcpy( szValue, buf );
}

/*++

Routine Description:

    This function writes system and user info. to the log file

Arguments:

    None

Return Value:

    registry value

History:

    8/21/97 a-paulbr fixed bug 658
--*/

void
RegLogCurrentVersion( void )
{
    char    buf[1024];
    DWORD   rc;
    HKEY    hKeyCurrentVersion = NULL;
    HKEY    hKeyControlWindows = NULL;
    DWORD   dwSPNum = 0;
    DWORD   dwType = REG_DWORD;
    DWORD   dwSize = sizeof(DWORD);

    rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                       REGKEY_CURRENTVERSION,
                       0,
                       KEY_QUERY_VALUE,
                       &hKeyCurrentVersion
                     );

    if (rc != ERROR_SUCCESS) {
        return;
    }
    rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                       REGKEY_CONTROLWINDOWS,
                       0,
                       KEY_QUERY_VALUE,
                       &hKeyControlWindows);
    if (hKeyControlWindows) {
        //
        // I'm using RegQueryValueEx() because there is an assertion in
        // RegQueryDWORD() if the key does not exist.
        //
        RegQueryValueEx(hKeyControlWindows,
                        REGKEY_CSD_VERSION,
                        NULL,
                        &dwType,
                        (BYTE*)&dwSPNum,
                        &dwSize);
    }

    RegQuerySZ( hKeyCurrentVersion,REGKEY_CURRENT_BUILD,     buf );
    lprintf( MSG_CURRENT_BUILD, buf );

    if ((hKeyControlWindows) &&
        (dwType == REG_DWORD) &&
        (HIBYTE(LOWORD(dwSPNum)) != 0)) {
        sprintf(buf, "%hu", HIBYTE(LOWORD(dwSPNum)));
        lprintf( MSG_CSD_VERSION, buf );
    } else {
        sprintf(buf, "None");
        lprintf( MSG_CSD_VERSION, buf );
    }

    RegQuerySZ( hKeyCurrentVersion,REGKEY_CURRENT_TYPE,      buf );
    lprintf( MSG_CURRENT_TYPE, buf );
    RegQuerySZ( hKeyCurrentVersion,REGKEY_REG_ORGANIZATION,  buf );
    lprintf( MSG_REG_ORGANIZATION, buf );
    RegQuerySZ( hKeyCurrentVersion,REGKEY_REG_OWNER,         buf );
    lprintf( MSG_REG_OWNER, buf );

    //
    // Close the keys that we opened
    //
    RegCloseKey(hKeyCurrentVersion);
    RegCloseKey(hKeyControlWindows);

    return;
}

void
RegLogProcessorType( void )
{
    char    buf[1024];
    DWORD   rc;
    HKEY    hKey;

    rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                       REGKEY_PROCESSOR,
                       0,
                       KEY_QUERY_VALUE,
                       &hKey
                     );

    if (rc != ERROR_SUCCESS) {
        return;
    }

    RegQuerySZ( hKey, REGKEY_PROCESSOR_ID, buf );
    lprintf( MSG_SYSINFO_PROC_TYPE, buf );

    return;
}


SYMBOLS.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    symbols.c

Abstract:

    This file contains all support for the symbol table.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <imagehlp.h>

#include "drwatson.h"
#include "proto.h"
#include "messages.h"

BOOL
SymbolEnumFunc(
    LPSTR   SymbolName,
    ULONG   Address,
    ULONG   Size,
    PVOID   Cxt
    )
{
    lprintfs( "%08x %08x   %s\r\n", Address, Size, SymbolName );
    return TRUE;
}

VOID
DumpSymbols(
    PDEBUGPACKET dp
    )
{
    IMAGEHLP_MODULE   mi;

    if (SymGetModuleInfo( dp->hProcess, 0, &mi )) {
        lprintf( MSG_SYMBOL_TABLE );
        do {
            lprintfs( "%s\r\n\r\n", mi.ImageName );
            SymEnumerateSymbols( dp->hProcess, mi.BaseOfImage, SymbolEnumFunc, NULL );
        } while( SymGetModuleInfo( dp->hProcess, (DWORD)-1, &mi ));
    }
}


UI.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    drwtsnui.c

Abstract:

    This function implements the ui (dialog) that controls the
    options maintenace for drwatson.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <mmsystem.h>
#include <direct.h>
#include <shellapi.h>

#include "drwatson.h"
#include "proto.h"
#include "resource.h"

void InitializeDialog( HWND hwnd );
void InitializeCrashList( HWND hwnd );
BOOL GetDialogValues( HWND hwnd );
BOOL CALLBACK LogFileViewerDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT DrWatsonWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LPSTR ExpandPath(LPSTR lpPath);

void
DrWatsonWinMain( void )

/*++

Routine Description:

    This is the entry point for DRWTSN32

Arguments:

    None.

Return Value:

    None.

--*/

{
    HWND           hwnd;
    MSG            msg;
    WNDCLASS       wndclass;
    HINSTANCE      hInst;

    hInst                   = GetModuleHandle( NULL );
    wndclass.style          = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc    = DrWatsonWndProc;
    wndclass.cbClsExtra     = 0;
    wndclass.cbWndExtra     = DLGWINDOWEXTRA;
    wndclass.hInstance      = hInst;
    wndclass.hIcon          = LoadIcon( hInst, MAKEINTRESOURCE(APPICON) );
    wndclass.hCursor        = LoadCursor( NULL, IDC_ARROW );
    wndclass.hbrBackground  = (HBRUSH) (COLOR_3DFACE + 1);
    wndclass.lpszMenuName   = NULL;
    wndclass.lpszClassName  = "DrWatsonDialog";
    RegisterClass( &wndclass );

    hwnd = CreateDialog( hInst,
                         MAKEINTRESOURCE( DRWATSONDIALOG ),
                         0,
                         DrWatsonWndProc
                       );

    ShowWindow( hwnd, SW_SHOWNORMAL );

    while (GetMessage (&msg, NULL, 0, 0)) {
        if (!IsDialogMessage( hwnd, &msg )) {
            TranslateMessage (&msg) ;
            DispatchMessage (&msg) ;
        }
    }

    return;
}

LRESULT
DrWatsonWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

/*++

Routine Description:

    Window procedure for the DRWTSN32.EXE main user interface.

Arguments:

    hwnd       - window handle to the dialog box
    message    - message number
    wParam     - first message parameter
    lParam     - second message parameter

Return Value:

    TRUE       - did not process the message
    FALSE      - did process the message

--*/

{
    DWORD    helpId;
    UINT     Checked;
    char     szCurrDir[MAX_PATH];
    char     szWave[MAX_PATH];
    char     szDump[MAX_PATH];
    char     szHelpFileName[MAX_PATH];
    LPSTR    p;

    switch (message) {
        case WM_CREATE:
            return 0;

        case WM_INITDIALOG:
            SubclassControls( hwnd );
            InitializeDialog( hwnd );
            SetTimer( hwnd, 1, 50, NULL );
            return 1;

        case WM_TIMER:
            if (GetKeyState(VK_F1) & 0x8000) {
                if ((GetFocus() != hwnd) && (GetParent(GetFocus()) != hwnd)) {
                    return 0;
                }
                switch (GetDlgCtrlID( GetFocus() )) {
                    case ID_LOGPATH:
                        helpId = IDH_LOGFILELOCATION;
                        break;

                    case ID_BROWSE_LOGPATH:
                        helpId = IDH_LOGFILELOCATION;
                        break;

                    case ID_WAVEFILE_TEXT:
                        helpId = IDH_WAVEFILE;
                        break;

                    case ID_WAVE_FILE:
                        helpId = IDH_WAVEFILE;
                        break;

                    case ID_BROWSE_WAVEFILE:
                        helpId = IDH_WAVEFILE;
                        break;

                    case ID_TEST_WAVE:
                        helpId = IDH_WAVEFILE;
                        break;

                    case ID_INSTRUCTIONS:
                        helpId = IDH_NUMBEROFINSTR;
                        break;

                    case ID_NUM_CRASHES:
                        helpId = IDH_NUMBEROFCRASHES;
                        break;

                    case ID_DUMPSYMBOLS:
                        helpId = IDH_DUMPSYMBOLS;
                        break;

                    case ID_DUMPALLTHREADS:
                        helpId = IDH_DUMPALLTHREADS;
                        break;

                    case ID_APPENDTOLOGFILE:
                        helpId = IDH_APPENDTOLOGFILE;
                        break;

                    case ID_VISUAL:
                        helpId = IDH_VISUAL;
                        break;

                    case ID_SOUND:
                        helpId = IDH_SOUND;
                        break;

                    case ID_CRASHES:
                        helpId = IDH_APPERRORS;
                        break;

                    case ID_LOGFILE_VIEW:
                        helpId = IDH_VIEW;
                        break;

                    case ID_CLEAR:
                        helpId = IDH_CLEAR;
                        break;

                    case ID_CRASH:
                        helpId = IDH_CRASH;
                        break;

                    case ID_CRASH_DUMP:
                        helpId = IDH_CRASH_DUMP;
                        break;

                    case IDOK:
                        helpId = IDH_INDEX;
                        break;

                    case IDCANCEL:
                        helpId = IDH_INDEX;
                        break;

                    case ID_HELP:
                        helpId = IDH_INDEX;
                        break;

                    default:
                        helpId = IDH_INDEX;
                        break;
                }
                //
                // call winhelp
                //
                GetHelpFileName( szHelpFileName, sizeof(szHelpFileName ) );
                WinHelp( hwnd, szHelpFileName, HELP_FINDER, helpId );
            }
            return 1;

        case WM_ACTIVATEAPP:
        case WM_SETFOCUS:
            SetFocusToCurrentControl();
            return 0;

        case WM_SYSCOMMAND:
            if (wParam == ID_ABOUT) {
                char title[256];
                char extra[256];

                strcpy( title, LoadRcString( IDS_ABOUT_TITLE ) );
                strcpy( extra, LoadRcString( IDS_ABOUT_EXTRA ) );

                ShellAbout( hwnd,
                            title,
                            extra,
                            LoadIcon( GetModuleHandle(NULL),
                                      MAKEINTRESOURCE(APPICON)
                                    )
                          );

                return 0;
            }
            break;

        case WM_COMMAND:
            switch (wParam) {
                case IDOK:
                    if (GetDialogValues( hwnd )) {
                        PostQuitMessage( 0 );
                    }
                    break;

                case IDCANCEL:
                    PostQuitMessage( 0 );
                    break;

                case ID_BROWSE_LOGPATH:
                    GetDlgItemText( hwnd, ID_LOGPATH, szCurrDir, MAX_PATH );
                    p = ExpandPath( szCurrDir );
                    if (p) {
                        strcpy( szCurrDir, p );
                        free( p );
                    }
                    EnableWindow( GetDlgItem( hwnd, ID_BROWSE_LOGPATH ), FALSE );
                    if (BrowseForDirectory( szCurrDir )) {
                        SetDlgItemText( hwnd, ID_LOGPATH, szCurrDir );
                    }
                    EnableWindow( GetDlgItem( hwnd, ID_BROWSE_LOGPATH ), TRUE );
                    SetFocus( GetDlgItem(hwnd, ID_BROWSE_LOGPATH) );
                    return FALSE;
                    break;

                case ID_BROWSE_WAVEFILE:
                    szWave[0] = '\0';
                    EnableWindow( GetDlgItem( hwnd, ID_BROWSE_WAVEFILE ), FALSE );
                    if (GetWaveFileName( szWave )) {
                        SetDlgItemText( hwnd, ID_WAVE_FILE, szWave );
                    }
                    EnableWindow( GetDlgItem( hwnd, ID_BROWSE_WAVEFILE ), TRUE );
                    SetFocus( GetDlgItem(hwnd, ID_BROWSE_WAVEFILE) );
                    return FALSE;
                    break;

                case ID_BROWSE_CRASH:
                    szDump[0] = '\0';
                    EnableWindow( GetDlgItem( hwnd, ID_BROWSE_CRASH ), FALSE );
                    if (GetDumpFileName( szDump )) {
                        SetDlgItemText( hwnd, ID_CRASH_DUMP, szDump );
                    }
                    EnableWindow( GetDlgItem( hwnd, ID_BROWSE_CRASH ), TRUE );
                    SetFocus( GetDlgItem(hwnd, ID_BROWSE_CRASH) );
                    return FALSE;
                    break;

                case ID_CLEAR:
                    ElClearAllEvents();
                    InitializeCrashList( hwnd );
                    break;

                case ID_TEST_WAVE:
                    GetDlgItemText( hwnd, ID_WAVE_FILE, szWave, sizeof(szWave) );
                    PlaySound( szWave, NULL, SND_FILENAME );
                    break;

                case ID_LOGFILE_VIEW:
                    DialogBoxParam( GetModuleHandle( NULL ),
                           MAKEINTRESOURCE( LOGFILEVIEWERDIALOG ),
                           hwnd,
                           LogFileViewerDialogProc,
                           SendMessage((HWND)GetDlgItem(hwnd,ID_CRASHES),
                                        LB_GETCURSEL,0,0)
                         );
                    break;

                case ID_HELP:
                    //
                    // call winhelp
                    //
                    GetHelpFileName( szHelpFileName, sizeof(szHelpFileName ) );
                    WinHelp( hwnd, szHelpFileName, HELP_FINDER, IDH_INDEX );
                    SetFocus( GetDlgItem(hwnd, ID_HELP) );
                    break;

                default:
                    if (((HWND)lParam == GetDlgItem( hwnd, ID_CRASHES )) &&
                        (HIWORD( wParam ) == LBN_DBLCLK)) {
                        DialogBoxParam( GetModuleHandle( NULL ),
                               MAKEINTRESOURCE( LOGFILEVIEWERDIALOG ),
                               hwnd,
                               LogFileViewerDialogProc,
                               SendMessage((HWND)lParam,LB_GETCURSEL,0,0)
                             );
                    }
                    if (((HWND)lParam == GetDlgItem( hwnd, ID_CRASH )) &&
                        (HIWORD( wParam ) == BN_CLICKED)) {
                        Checked = IsDlgButtonChecked( hwnd, ID_CRASH );
                        EnableWindow( GetDlgItem( hwnd, ID_CRASH_DUMP_TEXT ), Checked == 1 );
                        EnableWindow( GetDlgItem( hwnd, ID_CRASH_DUMP ), Checked == 1 );
                        EnableWindow( GetDlgItem( hwnd, ID_BROWSE_CRASH ), Checked == 1 );
                    }
                    break;
            }
            break;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            return 0;
    }

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

BOOL CALLBACK
EnumCrashes( PCRASHINFO crashInfo )

/*++

Routine Description:

    Enumeration function for crash records.  This function is called
    once for each crash record.  This function places the formatted
    crash data in a listbox.

Arguments:

    crashInfo      - pointer to a CRASHINFO structure

Return Value:

    TRUE           - caller should continue calling the enum procedure
    FALSE          - caller should stop calling the enum procedure

--*/

{
    SIZE size;
    char buf[1024];

    wsprintf( buf, "%s  %08x  %s(%08x)",
              crashInfo->crash.szAppName,
              crashInfo->crash.dwExceptionCode,
              crashInfo->crash.szFunction,
              crashInfo->crash.dwAddress
            );
    SendMessage( crashInfo->hList, LB_ADDSTRING, 0, (LPARAM)buf );

    GetTextExtentPoint( crashInfo->hdc, buf, strlen(buf), &size );
    if (size.cx > (LONG)crashInfo->cxExtent) {
        crashInfo->cxExtent = size.cx;
    }

    return TRUE;
}

void
InitializeCrashList( HWND hwnd )

/*++

Routine Description:

    Initializes the listbox that contains the crash information.

Arguments:

    None.

Return Value:

    None.

--*/

{
    CRASHINFO     crashInfo;
    TEXTMETRIC    tm;
    HFONT         hFont;

    crashInfo.hList = GetDlgItem( hwnd, ID_CRASHES );
    SendMessage( crashInfo.hList, LB_RESETCONTENT, FALSE, 0L );
    SendMessage( crashInfo.hList, WM_SETREDRAW, FALSE, 0L );
    crashInfo.hdc = GetDC( crashInfo.hList );
    crashInfo.cxExtent = 0;

    ElEnumCrashes( &crashInfo, EnumCrashes );

    hFont = (HFONT)SendMessage( crashInfo.hList, WM_GETFONT, 0, 0L );
    if (hFont != NULL) {
        SelectObject( crashInfo.hdc, hFont );
    }
    GetTextMetrics( crashInfo.hdc, &tm );
    ReleaseDC( crashInfo.hList, crashInfo.hdc );
    SendMessage( crashInfo.hList, LB_SETHORIZONTALEXTENT, crashInfo.cxExtent, 0L );
    SendMessage( crashInfo.hList, WM_SETREDRAW, TRUE, 0L );

    return;
}

void
InitializeDialog( HWND hwnd )

/*++

Routine Description:

    Initializes the DRWTSN32 user interface dialog with the values
    stored in the registry.

Arguments:

    hwnd       - window handle to the dialog

Return Value:

    None.

--*/

{
    OPTIONS       o;
    char          buf[256];
    HMENU         hMenu;

    RegInitialize( &o );
    SetDlgItemText( hwnd, ID_LOGPATH, o.szLogPath );
    SetDlgItemText( hwnd, ID_WAVE_FILE, o.szWaveFile );
    SetDlgItemText( hwnd, ID_CRASH_DUMP, o.szCrashDump );
    wsprintf( buf, "%d", o.dwMaxCrashes );
    SetDlgItemText( hwnd, ID_NUM_CRASHES, buf );
    wsprintf( buf, "%d", o.dwInstructions );
    SetDlgItemText( hwnd, ID_INSTRUCTIONS, buf );
    SendMessage( GetDlgItem( hwnd, ID_DUMPSYMBOLS ), BM_SETCHECK, o.fDumpSymbols, 0 );
    SendMessage( GetDlgItem( hwnd, ID_DUMPALLTHREADS ), BM_SETCHECK, o.fDumpAllThreads, 0 );
    SendMessage( GetDlgItem( hwnd, ID_APPENDTOLOGFILE ), BM_SETCHECK, o.fAppendToLogFile, 0 );
    SendMessage( GetDlgItem( hwnd, ID_VISUAL ), BM_SETCHECK, o.fVisual, 0 );
    SendMessage( GetDlgItem( hwnd, ID_SOUND ), BM_SETCHECK, o.fSound, 0 );
    SendMessage( GetDlgItem( hwnd, ID_CRASH ), BM_SETCHECK, o.fCrash, 0 );

    if (waveOutGetNumDevs() == 0) {
        EnableWindow( GetDlgItem( hwnd, ID_WAVEFILE_TEXT ), FALSE );
        EnableWindow( GetDlgItem( hwnd, ID_WAVE_FILE ), FALSE );
        EnableWindow( GetDlgItem( hwnd, ID_BROWSE_WAVEFILE ), FALSE );
    }

    EnableWindow( GetDlgItem( hwnd, ID_CRASH_DUMP_TEXT ), o.fCrash );
    EnableWindow( GetDlgItem( hwnd, ID_CRASH_DUMP ), o.fCrash );
    EnableWindow( GetDlgItem( hwnd, ID_BROWSE_CRASH ), o.fCrash );

    InitializeCrashList( hwnd );

    if (SendMessage( GetDlgItem( hwnd, ID_CRASHES ), LB_GETCOUNT, 0 ,0 ) == 0) {
        EnableWindow( GetDlgItem( hwnd, ID_CLEAR ), FALSE );
        EnableWindow( GetDlgItem( hwnd, ID_LOGFILE_VIEW ), FALSE );
    }

    hMenu = GetSystemMenu( hwnd, FALSE );
    if (hMenu != NULL) {
        AppendMenu( hMenu, MF_SEPARATOR, 0, NULL );
        AppendMenu( hMenu, MF_STRING, ID_ABOUT, LoadRcString( IDS_ABOUT ) );
    }

    return;
}

BOOL
GetDialogValues( HWND hwnd )

/*++

Routine Description:

    Retrieves the values in the DRWTSN32 dialog controls and saves
    them in the registry.

Arguments:

    hwnd       - window handle to the dialog

Return Value:

    TRUE       - all values were retrieved and saved
    FALSE      - an error occurred

--*/

{
    OPTIONS  o;
    char     buf[256];
    DWORD    dwFa;
    LPSTR    p,p1;
    char     szDrive    [_MAX_DRIVE];
    char     szDir      [_MAX_DIR];
    char     szPath     [MAX_PATH];

    RegInitialize( &o );

    GetDlgItemText( hwnd, ID_LOGPATH, buf, sizeof(buf) );
    p = ExpandPath( buf );
    if (p) {
        dwFa = GetFileAttributes( p );
        free( p );
    } else {
        dwFa = GetFileAttributes( buf );
    }
    if ((dwFa == 0xffffffff) || (!(dwFa&FILE_ATTRIBUTE_DIRECTORY))) {
        NonFatalError( LoadRcString(IDS_INVALID_PATH) );
        return FALSE;
    }
    if (strlen(buf) > 0) {
        strcpy( o.szLogPath, buf );
    }

    o.fCrash = SendMessage( GetDlgItem( hwnd, ID_CRASH ), BM_GETCHECK, 0, 0 );

    GetDlgItemText( hwnd, ID_CRASH_DUMP, buf, sizeof(buf) );
    if (o.fCrash) {
        p = ExpandPath( buf );
        if (p) {
            dwFa = GetFileAttributes( p );
            free( p );
        } else {
            dwFa = GetFileAttributes( buf );
        }
        if (dwFa == 0xffffffff) {
            //
            // file does not exist, check to see if the dir is ok
            //
            p = ExpandPath( buf );
            if (p) {
                p1 = p;
            } else {
                p1 = buf;
            }
            _splitpath( p1, szDrive, szDir, NULL, NULL );
            _makepath( szPath, szDrive, szDir, NULL, NULL );
            if (p) {
                free( p );
            }
            dwFa = GetFileAttributes( szPath );
            if (dwFa == 0xffffffff) {
                NonFatalError( LoadRcString(IDS_INVALID_CRASH_PATH) );
                return FALSE;
            }
        } else if (dwFa & FILE_ATTRIBUTE_DIRECTORY) {
            NonFatalError( LoadRcString(IDS_INVALID_CRASH_PATH) );
            return FALSE;
        }
        if (strlen(buf) > 0) {
            strcpy( o.szCrashDump, buf );
        }
    }

    GetDlgItemText( hwnd, ID_WAVE_FILE, buf, sizeof(buf) );
    if (strlen(buf) > 0) {
        dwFa = GetFileAttributes( buf );
        if ((dwFa == 0xffffffff) || (dwFa&FILE_ATTRIBUTE_DIRECTORY)) {
            NonFatalError( LoadRcString(IDS_INVALID_WAVE) );
            return FALSE;
        }
    }

    strcpy( o.szWaveFile, buf );

    GetDlgItemText( hwnd, ID_NUM_CRASHES, buf, sizeof(buf) );
    o.dwMaxCrashes = (DWORD) atol( buf );

    GetDlgItemText( hwnd, ID_INSTRUCTIONS, buf, sizeof(buf) );
    o.dwInstructions = (DWORD) atol( buf );

    o.fDumpSymbols = SendMessage( GetDlgItem( hwnd, ID_DUMPSYMBOLS ), BM_GETCHECK, 0, 0 );
    o.fDumpAllThreads = SendMessage( GetDlgItem( hwnd, ID_DUMPALLTHREADS ), BM_GETCHECK, 0, 0 );
    o.fAppendToLogFile = SendMessage( GetDlgItem( hwnd, ID_APPENDTOLOGFILE ), BM_GETCHECK, 0, 0 );
    o.fVisual = SendMessage( GetDlgItem( hwnd, ID_VISUAL ), BM_GETCHECK, 0, 0 );
    o.fSound = SendMessage( GetDlgItem( hwnd, ID_SOUND ), BM_GETCHECK, 0, 0 );

    RegSave( &o );

    return TRUE;
}

BOOL CALLBACK
EnumCrashesForViewer( PCRASHINFO crashInfo )

/*++

Routine Description:

    Enumeration function for crash records.  This function is called
    once for each crash record.  This function looks for s specific crash
    that is identified by the crashIndex.

Arguments:

    crashInfo      - pointer to a CRASHINFO structure

Return Value:

    TRUE           - caller should continue calling the enum procedure
    FALSE          - caller should stop calling the enum procedure

--*/

{
    char *p;

    if ((crashInfo->dwIndex == crashInfo->dwIndexDesired) &&
        (crashInfo->dwCrashDataSize > 0) ) {
        p = crashInfo->pCrashData;
        crashInfo->pCrashData = malloc( crashInfo->dwCrashDataSize+10 );
        memcpy( crashInfo->pCrashData, p, crashInfo->dwCrashDataSize+10 );
        crashInfo->pCrashData[crashInfo->dwCrashDataSize] = 0;
        return FALSE;
    }

    crashInfo->dwIndex++;

    return TRUE;
}

BOOL CALLBACK
LogFileViewerDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

/*++

Routine Description:

    Window procedure for the log file viewer dialog box.

Arguments:

    hwnd       - window handle to the dialog box
    message    - message number
    wParam     - first message parameter
    lParam     - second message parameter

Return Value:

    TRUE       - did not process the message
    FALSE      - did process the message

--*/

{
    static CRASHINFO    crashInfo;
    HFONT               hFont;

    switch (message) {
        case WM_INITDIALOG:
            crashInfo.dwIndex = 0;
            crashInfo.dwIndexDesired = lParam;
            ElEnumCrashes( &crashInfo, EnumCrashesForViewer );
            if (crashInfo.dwIndex != crashInfo.dwIndexDesired) {
                MessageBeep( 0 );
                EndDialog( hwnd, 0 );
                return FALSE;
            }
            SetDlgItemText( hwnd, ID_LOGFILE_VIEW, crashInfo.pCrashData );

            hFont = GetStockObject( SYSTEM_FIXED_FONT );
            Assert( hFont != NULL );

            SendDlgItemMessage( hwnd,
                                ID_LOGFILE_VIEW,
                                WM_SETFONT,
                                (WPARAM) hFont,
                                (LPARAM) FALSE
                              );
            return TRUE;

        case WM_COMMAND:
            if (wParam == IDOK) {
                free( crashInfo.pCrashData );
                EndDialog( hwnd, 0 );
            }
            break;
    }

    return FALSE;
}


UTIL.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    util.c

Abstract:
    This file implements common utilitarian functions.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <direct.h>

#include "drwatson.h"
#include "proto.h"
#include "resource.h"

void
GetAppName( char *pszAppName, DWORD len )
{
    LoadString( GetModuleHandle(NULL), IDS_APPLICATION_NAME, pszAppName, len );
}

void
GetHelpFileName( char *pszHelpFileName, DWORD len )
{
    char           szDrive[_MAX_DRIVE];
    char           szDir[_MAX_DIR];

    //
    // find out the path where DrWatson was run from
    //
    GetModuleFileName( GetModuleHandle(NULL), pszHelpFileName, len );

    //
    // take the path and append the help file name
    //
    _splitpath( pszHelpFileName, szDrive, szDir, NULL, NULL );
    wsprintf( pszHelpFileName, "%s%sdrwtsn32.hlp", szDrive, szDir );

    return;
}

char *
LoadRcString( UINT wId )

/*++

Routine Description:

    Loads a resource string from DRWTSN32 and returns a pointer
    to the string.

Arguments:

    wId        - resource string id

Return Value:

    pointer to the string

--*/

{
    static char buf[1024];

    LoadString( GetModuleHandle(NULL), wId, buf, sizeof(buf) );

    return buf;
}


WALK.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    walk.c

Abstract:

    This file provides support for stack walking.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"

LPVOID
SwFunctionTableAccess(
    PDEBUGPACKET    dp,
    DWORD           dwPCAddr
    )
{
    return SymFunctionTableAccess( dp->hProcess, dwPCAddr );
}

DWORD
SwGetModuleBase(
    PDEBUGPACKET    dp,
    DWORD           ReturnAddress
    )
{
    IMAGEHLP_MODULE    ModuleInfo;

    if (SymGetModuleInfo( dp->hProcess, ReturnAddress, &ModuleInfo )) {
        return ModuleInfo.BaseOfImage;
    }

    return 0;
}

BOOL
SwReadProcessMemory(
    PDEBUGPACKET    dp,
    LPCVOID         lpBaseAddress,
    LPVOID          lpBuffer,
    DWORD           nSize,
    LPDWORD         lpNumberOfBytesRead
    )
{
    return DoMemoryRead(
        dp,
        lpBaseAddress,
        lpBuffer,
        nSize,
        lpNumberOfBytesRead
        );
}

DWORD
SwTranslateAddress(
    PDEBUGPACKET    dp,
    HANDLE          hThread,
    LPADDRESS       lpaddr
    )
{
    return 0;
}


CONTEXT.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    context.c

Abstract:

    This file provides access to thread context information.

Author:

    Miche Baker-Harvey (v-michbh) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"

void
GetContextForThread( PDEBUGPACKET dp )
{
    PTHREADCONTEXT ptctx = dp->tctx;

    memset(&ptctx->context,0,sizeof(CONTEXT));

    ptctx->context.ContextFlags = CONTEXT_FULL;

    if (!GetThreadContext( ptctx->hThread, &ptctx->context )) {
        lprintfs( ">>>>> GetThreadContext failed: err(%d), hthread(0x%x)\n",
                  GetLastError(), ptctx->hThread );
        ptctx->pc = 0;
        ptctx->frame = 0;
        ptctx->stack = 0;
    } else {
        ptctx->pc =    (DWORD) ptctx->context.Fir;
        ptctx->frame = (DWORD) ptctx->context.IntSp;
        ptctx->stack = (DWORD) ptctx->context.IntSp;
    }

    return;
}


DISASM.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    regs.c

Abstract:

    This file provides disassembly  ( alpha )

Author:

    Miche Baker-Harvey (v-michbh) 1-May-1993  (ported from ntsd)

Environment:

    User Mode

--*/

#include <windows.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>

#include <alphaops.h>
#include "disasm.h"
#include "regs.h"
#include "optable.h"

#include "drwatson.h"
#include "proto.h"

void BlankFill(ULONG);
void OutputHex(ULONG, ULONG, BOOLEAN);

void OutputEffectiveAddress(PDEBUGPACKET, ULONG);
void OutputString(char *);
void OutputReg(ULONG);
void OutputFReg(ULONG);
void GetNextOffset(PDEBUGPACKET, PULONG, BOOLEAN);
ULONG GetIntRegNumber(ULONG);

ALPHA_INSTRUCTION disinstr;

extern PUCHAR pszReg[];

BOOLEAN disasm(PDEBUGPACKET, PDWORD, PUCHAR, BOOLEAN);

static char *pBufStart;
static char *pBuf;

#define OPRNDCOL  27            // Column for first operand
#define EACOL     40            // column for effective address
#define FPTYPECOL 40            // .. for the type of FP instruction

BOOLEAN
disasm (PDEBUGPACKET dp, PDWORD poffset, PUCHAR bufptr, BOOLEAN fEAout)
{
    static      initialized = 0;
    ULONG       opcode;
    ULONG       Ea;                     // Effective Address
    POPTBLENTRY pEntry;

    if (!initialized) {
        opTableInit();
        initialized = 1;
    }
    pBufStart = pBuf = bufptr;          // Initialize pointers to buffer that
                                        //  will receive the disassembly text
    OutputHex(*poffset, 8, FALSE);// Output Hex address of instruction
    *pBuf++ = ':';
    *pBuf++ = ' ';

    if (!DoMemoryRead( dp,
                       (LPVOID)*poffset,
                       (LPVOID)&disinstr.Long,
                       sizeof(DWORD),
                       NULL
                       )) {
        OutputString("???????? ????");
        *pBuf = '\0';
        return(FALSE);
    }

    OutputHex(disinstr.Long, 8, FALSE); // Output instruction in Hex
    *pBuf++ = ' ';

    opcode = disinstr.Memory.Opcode;    // Select disassembly procedure from

    pEntry = findOpCodeEntry(opcode);   // Get non-func entry for this code
    if (pEntry == (POPTBLENTRY)-1) {
        OutputString("??????? ????");
        *pBuf = '\0';
        return (FALSE);
    }

    switch (pEntry->iType) {
    case ALPHA_UNKNOWN:
        OutputString(pEntry->pszAlphaName);
        break;

    case ALPHA_MEMORY:
        OutputString(pEntry->pszAlphaName);
        BlankFill(OPRNDCOL);
        OutputReg(disinstr.Memory.Ra);
        *pBuf++ = ',';
        OutputHex(disinstr.Memory.MemDisp, (WIDTH_MEM_DISP + 3)/4, TRUE );
        *pBuf++ = '(';
        OutputReg(disinstr.Memory.Rb);
        *pBuf++ = ')';

        break;

    case ALPHA_FP_MEMORY:
        OutputString(pEntry->pszAlphaName);
        BlankFill(OPRNDCOL);
        OutputFReg(disinstr.Memory.Ra);
        *pBuf++ = ',';
        OutputHex(disinstr.Memory.MemDisp, (WIDTH_MEM_DISP + 3)/4, TRUE );
        *pBuf++ = '(';
        OutputReg(disinstr.Memory.Rb);
        *pBuf++ = ')';

        break;

    case ALPHA_MEMSPC:
        OutputString(findFuncName(pEntry, disinstr.Memory.MemDisp & BITS_MEM_DISP));
        //
        // Some memory special instructions have an operand
        //

        switch (disinstr.Memory.MemDisp & BITS_MEM_DISP) {
        case FETCH_FUNC:
        case FETCH_M_FUNC:
             // one operand, in Rb
             BlankFill(OPRNDCOL);
             *pBuf++ = '0';
             *pBuf++ = '(';
             OutputReg(disinstr.Memory.Rb);
             *pBuf++ = ')';
             break;

        case RS_FUNC:
        case RC_FUNC:
        case RPCC_FUNC:
             // one operand, in Ra
             BlankFill(OPRNDCOL);
             OutputReg(disinstr.Memory.Ra);
             break;

        case MB_FUNC:
        case WMB_FUNC:
        case MB2_FUNC:
        case MB3_FUNC:
        case TRAPB_FUNC:
        case EXCB_FUNC:
             // no operands
             break;

        default:
             printf("we shouldn't get here \n");
             break;
        }

        break;

    case ALPHA_JUMP:
        OutputString(findFuncName(pEntry, disinstr.Jump.Function));
        BlankFill(OPRNDCOL);
        OutputReg(disinstr.Jump.Ra);
        *pBuf++ = ',';
        *pBuf++ = '(';
        OutputReg(disinstr.Jump.Rb);
        *pBuf++ = ')';
        *pBuf++ = ',';
        OutputHex(disinstr.Jump.Hint, (WIDTH_HINT + 3)/4, TRUE);

        Ea = (ULONG) GetRegValue(dp, GetIntRegNumber(disinstr.Jump.Rb)) & (~3);
        OutputEffectiveAddress(dp, Ea);
        break;

    case ALPHA_BRANCH:
        OutputString(pEntry->pszAlphaName);
        BlankFill(OPRNDCOL);
        OutputReg(disinstr.Branch.Ra);
        *pBuf++ = ',';

        //
        // The next line might be a call to GetNextOffset, but
        // GetNextOffset assumes that it should work from REGFIR.
        //

        Ea = *poffset +
             sizeof(DWORD) +
             (disinstr.Branch.BranchDisp << 2);
        OutputHex(Ea, 8, FALSE);
        OutputEffectiveAddress(dp, Ea);

        break;

    case ALPHA_FP_BRANCH:
        OutputString(pEntry->pszAlphaName);
        BlankFill(OPRNDCOL);
        OutputFReg(disinstr.Branch.Ra);
        *pBuf++ = ',';

        //
        // The next line might be a call to GetNextOffset, but
        // GetNextOffset assumes that it should work from REGFIR.
        //

        Ea = *poffset +
             sizeof(DWORD) +
             (disinstr.Branch.BranchDisp << 2);
        OutputHex(Ea, 8, FALSE);
        OutputEffectiveAddress(dp, Ea);

        break;

    case ALPHA_OPERATE:
        OutputString(findFuncName(pEntry, disinstr.OpReg.Function));
        BlankFill(OPRNDCOL);
        OutputReg(disinstr.OpReg.Ra);
        *pBuf++ = ',';
        if (disinstr.OpReg.RbvType) {
            *pBuf++ = '#';
            OutputHex(disinstr.OpLit.Literal, (WIDTH_LIT + 3)/4, TRUE);
        } else
            OutputReg(disinstr.OpReg.Rb);
        *pBuf++ = ',';
        OutputReg(disinstr.OpReg.Rc);
        break;

    case ALPHA_FP_OPERATE:

      {
        ULONG Function;
        ULONG Flags;

        Flags = disinstr.FpOp.Function & MSK_FP_FLAGS;
        Function = disinstr.FpOp.Function & MSK_FP_OP;

//        if (fVerboseOutput) {
//           dprintf("In FP_OPERATE: Flags %08x Function %08x\n",
//                    Flags, Function);
//           dprintf("opcode %d \n", opcode);
//        }

        //
        // CVTST and CVTST/S are different: they look like
        // CVTTS with some flags set
        //
        if (Function == CVTTS_FUNC) {
            if (disinstr.FpOp.Function == CVTST_S_FUNC) {
                Function = CVTST_S_FUNC;
                Flags = NONE_FLAGS;
            }
            if (disinstr.FpOp.Function == CVTST_FUNC) {
                Function = CVTST_FUNC;
                Flags = NONE_FLAGS;
            }
        }

        OutputString(findFuncName(pEntry, Function));

        //
        // Append the opcode qualifier, if any, to the opcode name.
        //

        if ( (opcode == IEEEFP_OP) || (opcode == VAXFP_OP)
                                   || (Function == CVTQL_FUNC) ) {
            OutputString(findFlagName(Flags, Function));
        }

        BlankFill(OPRNDCOL);
        //
        // If this is a convert instruction, only Rb and Rc are used
        //
        if (strncmp("cvt", findFuncName(pEntry, Function), 3) != 0) {
            OutputFReg(disinstr.FpOp.Fa);
            *pBuf++ = ',';
        }
        OutputFReg(disinstr.FpOp.Fb);
        *pBuf++ = ',';
        OutputFReg(disinstr.FpOp.Fc);

        break;
      }

    case ALPHA_FP_CONVERT:
        OutputString(pEntry->pszAlphaName);
        BlankFill(OPRNDCOL);
        OutputFReg(disinstr.FpOp.Fa);
        *pBuf++ = ',';
        OutputFReg(disinstr.FpOp.Fb);
        break;

    case ALPHA_CALLPAL:
        OutputString(findFuncName(pEntry, disinstr.Pal.Function));
        break;

    case ALPHA_EV4_PR:
        if ((disinstr.Long & MSK_EV4_PR) == 0)
                OutputString("NOP");
        else {
            OutputString(pEntry->pszAlphaName);
            BlankFill(OPRNDCOL);
            OutputReg(disinstr.EV4_PR.Ra);
            *pBuf++ = ',';
            if(disinstr.EV4_PR.Ra != disinstr.EV4_PR.Rb) {
                OutputReg(disinstr.EV4_PR.Rb);
                *pBuf++ = ',';
            };
            OutputString(findFuncName(pEntry, (disinstr.Long & MSK_EV4_PR)));
        };
        break;
    case ALPHA_EV4_MEM:
        OutputString(pEntry->pszAlphaName);
        BlankFill(OPRNDCOL);
        OutputReg(disinstr.EV4_MEM.Ra);
        *pBuf++ = ',';
        OutputReg(disinstr.EV4_MEM.Rb);
        break;
    case ALPHA_EV4_REI:
        OutputString(pEntry->pszAlphaName);
        break;
    default:
        OutputString("Invalid type");
        break;
    };

    *poffset += sizeof(ULONG);
    *pBuf = '\0';
    return(TRUE);
}

/* BlankFill - blank-fill buffer
*
*   Purpose:
*       To fill the buffer at *pBuf with blanks until
*       position count is reached.
*
*   Input:
*       None.
*
*   Output:
*       None.
*
***********************************************************************/

void BlankFill(ULONG count)
{
    do
        *pBuf++ = ' ';
    while (pBuf < pBufStart + count);
}

/* OutputHex - output hex value
*
*   Purpose:
*       Output the value in outvalue into the buffer
*       pointed by *pBuf.  The value may be signed
*       or unsigned depending on the value fSigned.
*
*   Input:
*       outvalue - value to output
*       length - length in digits
*       fSigned - TRUE if signed else FALSE
*
*   Output:
*       None.
*
***********************************************************************/

UCHAR HexDigit[16] = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };

void OutputHex (ULONG outvalue, ULONG length, BOOLEAN fSigned)
{
    UCHAR   digit[8];
    LONG    index = 0;

    if (fSigned && (LONG)outvalue < 0) {
        *pBuf++ = '-';
        outvalue = - (LONG)outvalue;
    }
    do {
        digit[index++] = HexDigit[outvalue & 0xf];
        outvalue >>= 4;
    }

    while ((fSigned && outvalue) || (!fSigned && index < (LONG)length))
        ;
    while (--index >= 0)
        *pBuf++ = digit[index];
}

/* OutputString - output string
*
*   Purpose:
*       Copy the string into the buffer pointed by pBuf.
*
*   Input:
*       *pStr - pointer to string
*
*   Output:
*       None.
*
***********************************************************************/

void OutputString (char *pStr)
{
    while (*pStr)
        *pBuf++ = *pStr++;
}

void OutputReg (ULONG regnum)
{
    OutputString(pszReg[GetIntRegNumber(regnum)]);
}

void OutputFReg (ULONG regnum)
{
    *pBuf++ = 'f';
    if (regnum > 9)
        *pBuf++ = (UCHAR)('0' + regnum / 10);
    *pBuf++ = (UCHAR)('0' + regnum % 10);
}

/*** OutputEffectiveAddress - Print EA symbolically
*
*   Purpose:
*       Given the effective address (for a branch, jump or
*       memory instruction, print it symbolically, if
*       symbols are available.
*
*   Input:
*       offset - computed by the caller as
*               for jumps, the value in Rb
*               for branches, func(PC, displacement)
*               for memory, func(PC, displacement)
*
*   Returns:
*       None
*
*************************************************************************/
void OutputEffectiveAddress(PDEBUGPACKET dp, ULONG offset)
{
    ULONG   displacement;
    PUCHAR  pszTemp;
    UCHAR   ch;

    BlankFill(EACOL);

    if (SymGetSymFromAddr( dp->hProcess, offset, &displacement, sym )) {
        pszTemp = sym->Name;
        while (ch = *pszTemp++)
            *pBuf++ = ch;
        if (displacement) {
            *pBuf++ = '+';
            OutputHex(displacement, 8, TRUE);
            }
    }
    else {
        OutputHex(offset, 8, FALSE);
    }
}

/*** GetNextOffset - compute offset for trace or step
*
*   Purpose:
*       From a limited disassembly of the instruction pointed
*       by the FIR register, compute the offset of the next
*       instruction for either a trace or step operation.
*
*       trace -> the next instruction to execute
*       step -> the instruction in the next memory location or the
*               next instruction executed due to a branch (step over
*               subroutine calls).
*
*   Input:
*       result - where to put the next offset
*       fStep - TRUE for step offset; FALSE for trace offset
*
*   Returns:
*       step or trace offset if input is TRUE or FALSE, respectively
*       in result
*
*************************************************************************/
/*
void
GetNextOffset (PDEBUGPACKET dp, PULONG result, BOOLEAN fStep)
{
    ULONG   rv;
    ULONG   opcode;
    ULONG   firaddr;
    ULONG   updatedpc;
    ULONG   branchTarget;
    DWORD   fir;

    // Canonical form to shorten tests; Abs is absolute value

    LONG    Can, Abs;

    CONVERTED_DOUBLE    Rav;
    CONVERTED_DOUBLE    Fav;
    CONVERTED_DOUBLE    Rbv;

    //
    // Get current address
    //

    firaddr = (ULONG) GetRegValue(dp, REGFIR);

    //
    // relative addressing updates PC first
    // Assume next sequential instruction is next offset
    //

    updatedpc = firaddr + sizeof(ULONG);
    rv = updatedpc;

    DoMemoryRead( dp,
                  (LPVOID)firaddr,
                  (LPVOID)&disinstr.Long,
                  sizeof(ULONG),
                  NULL
                  );

    opcode = disinstr.Memory.Opcode;

    switch(findOpCodeEntry(opcode)->iType) {

    case ALPHA_JUMP:

        switch(disinstr.Jump.Function) {

        case JSR_FUNC:
        case JSR_CO_FUNC:

            if (fStep) {

                //
                // Step over the subroutine call;
                //

                break;
            }

            //
            // fall through
            //

        case JMP_FUNC:
        case RET_FUNC:

            GetQuadRegValue(dp, GetIntRegNumber(disinstr.Jump.Rb), &Rbv);
            rv = (Rbv.li.LowPart & (~3));
            break;

        }

        break;

        case ALPHA_BRANCH:

        branchTarget = (updatedpc + (disinstr.Branch.BranchDisp * 4));

        GetQuadRegValue(dp, GetIntRegNumber(disinstr.Branch.Ra), &Rav.li);

        //
        // set up a canonical value for computing the branch test
        // - works with ALPHA, MIPS and 386 hosts
        //

        Can = Rav.li.LowPart & 1;

        if ((LONG)Rav.li.HighPart < 0) {
            Can |= 0x80000000;
        }

        if ((Rav.li.LowPart & 0xfffffffe) || (Rav.li.HighPart & 0x7fffffff)) {
            Can |= 2;
        }

//        if (fVerboseOutput) {
//           dprintf("Rav High %08lx Low %08lx Canonical %08lx\n",
//                    Rav.li.HighPart, Rav.li.LowPart, Can);
//           dprintf("returnvalue %08lx branchTarget %08lx\n",
//                    rv, branchTarget);
//        }

        switch(opcode) {
        case BR_OP:                         rv = branchTarget; break;
        case BSR_OP:  if (!fStep)           rv = branchTarget; break;
        case BEQ_OP:  if (Can == 0)         rv = branchTarget; break;
        case BLT_OP:  if (Can <  0)         rv = branchTarget; break;
        case BLE_OP:  if (Can <= 0)         rv = branchTarget; break;
        case BNE_OP:  if (Can != 0)         rv = branchTarget; break;
        case BGE_OP:  if (Can >= 0)         rv = branchTarget; break;
        case BGT_OP:  if (Can >  0)         rv = branchTarget; break;
        case BLBC_OP: if ((Can & 0x1) == 0) rv = branchTarget; break;
        case BLBS_OP: if ((Can & 0x1) == 1) rv = branchTarget; break;
        };

        break;

    case ALPHA_FP_BRANCH:

        branchTarget = (updatedpc + (disinstr.Branch.BranchDisp * 4));

        GetFloatingPointRegValue(dp, disinstr.Branch.Ra, &Fav);

        //
        // Set up a canonical value for computing the branch test
        //

        Can = Fav.li.HighPart & 0x80000000;

        //
        // The absolute value is needed -0 and non-zero computation
        //

        Abs = Fav.li.LowPart || (Fav.li.HighPart & 0x7fffffff);

        if (Can && (Abs == 0x0)) {

            //
            // negative 0 should be considered as zero
            //

            Can = 0x0;

        } else {

            Can |= Abs;

        }

//        if (fVerboseOutput) {
//           dprintf("Fav High %08lx Low %08lx Canonical %08lx Absolute %08lx\n",
//                    Fav.li.HighPart, Fav.li.LowPart, Can, Abs);
//           dprintf("returnvalue %08lx branchTarget %08lx\n",
//                    rv, branchTarget);
//        }

        switch(opcode) {
        case FBEQ_OP: if (Can == 0)  rv =  branchTarget; break;
        case FBLT_OP: if (Can <  0)  rv =  branchTarget; break;
        case FBNE_OP: if (Can != 0)  rv =  branchTarget; break;
        case FBLE_OP: if (Can <= 0)  rv =  branchTarget; break;
        case FBGE_OP: if (Can >= 0)  rv =  branchTarget; break;
        case FBGT_OP: if (Can >  0)  rv =  branchTarget; break;
        };

        break;
    };

//    if (fVerboseOutput) {
//        dprintf("GetNextOffset returning %08lx\n", rv);
//    }

    *result = rv;
}

*/


DISASM.H

#ifndef _NTDIS_
#define _NTDIS_

// #include <excpt.h>
// #include <ntdef.h>

#
// this stuff is all "#if 0"'ed in alphaops.h
//
// Bit manipulations for Alpha instructions
//

#define SHFT_OPCODE     26
#define SHFT_RA         21
#define SHFT_RB         16
#define SHFT_JMP_FNC    14
#define SHFT_OP_FNC     5
#define SHFT_RBV_TYPE   12
#define SHFT_LIT        13
#define SHFT_FP_FNC     5

#define WIDTH_OPCODE    6
#define WIDTH_REG       5
#define WIDTH_MEM_DISP  16
#define WIDTH_HINT      14
#define WIDTH_JMP_FNC   2
#define WIDTH_BR_DISP   21
#define WIDTH_OP_FNC    7
#define WIDTH_LIT       8
#define WIDTH_FP_FNC    11
#define WIDTH_PAL_FNC   26

#define BITS_OPCODE     ~(-1 << WIDTH_OPCODE)
#define BITS_REG        ~(-1 << WIDTH_REG)
#define BITS_MEM_DISP   ~(-1 << WIDTH_MEM_DISP)
#define BITS_HINT       ~(-1 << WIDTH_HINT)
#define BITS_JMP_FNC    ~(-1 << WIDTH_JMP_FNC)
#define BITS_BR_DISP    ~(-1 << WIDTH_BR_DISP)
#define BITS_OP_FNC     ~(-1 << WIDTH_OP_FNC)
#define BITS_LIT        ~(-1 << WIDTH_LIT)
#define BITS_FP_FNC     ~(-1 << WIDTH_FP_FNC)
#define BITS_PAL_FNC    ~(-1 << WIDTH_PAL_FNC)

#define OPCODE(a)       ((BITS_OPCODE & (a)) << SHFT_OPCODE)
#define REG_A(a)           ((BITS_REG & (a)) << SHFT_RA)
#define REG_B(a)           ((BITS_REG & (a)) << SHFT_RB)
#define REG_C(a)           (BITS_REG & (a))
#define MEM_DISP(a)     (BITS_MEM_DISP & (a))
#define MEM_FUNC(a)MEM_DISP(a)
#define HINT(a)         (BITS_HINT & (a))
#define JMP_FNC(a)      ((BITS_JMP_FNC & (a)) << SHFT_JMP_FNC)
#define BR_DISP(a)      (BITS_BR_DISP & (a))
#define OP_FNC(a)       ((BITS_OP_FNC & (a)) << SHFT_OP_FNC)
#define RBV_TYPE(a)     ((1 & (a)) << SHFT_RBV_TYPE)
#define LIT(a)          ((BITS_LIT & (a)) << SHFT_LIT)
#define FP_FNC(a)       ((BITS_FP_FNC & (a)) << SHFT_FP_FNC)
#define PAL_FNC(a)      (BITS_PAL_FNC & (a))

#define MSK_OPCODE      OPCODE(BITS_OPCODE)
#define MSK_RA          GET_RA(BITS_REG)
#define MSK_RB          REG_B(BITS_REG)
#define MSK_RC          REG_C(BITS_REG)
#define MSK_MEM_DISP    DISP(BITS_MEM_DISP)
#define MSK_HINT        HINT(BITS_HINT)
#define MSK_JMP_FNC     JMP_FNC(BITS_JMP_FNC)
#define MSK_BR_DISP     BR_DISP(BITS_BR_DISP)
#define MSK_RBV_TYPE    RBV_TYPE(1)
#define MSK_LIT         LIT(BITS_LIT)
#define MSK_FP_FNC      FP_FNC(BITS_FP_FNC)
#define MSK_PAL_FNC     PAL_FNC(BITS_PAL_FNC)

#define EXTR_OPCODE(a)   (((a) & MSK_OPCODE) >> SHFT_OPCODE)
#define EXTR_RA(a)       (((a) & MSK_RA) >> SHFT_RA)
#define EXTR_RB(a)       (((a) & MSK_RB) >> SHFT_RA)
#define EXTR_RC(a)        ((a) & MSK_RC)
#define EXTR_MEM_DISP(a)  ((a) & MSK_MEM_DISP)
#define EXTR_HINT(a)      ((a) & MSK_HINT)
#define EXTR_JMP_FNC(a)  (((a) & MSK_JMP_FNC) >> SHFT_JMP_FNC)
#define EXTR_BR_DISP(a)   ((a) & MSK_BR_DISP)
#define EXTR_RBV_TYPE(a) (((a) & MSK_RBV_TYPE) >> SHFT_RBV_TYPE)
#define EXTR_LIT(a)      (((a) & MSK_LIT) >> SHFT_LIT)
#define EXTR_FP_FNC(a)   (((a) & MSK_FP_FNC) >> SHFT_FP_FNC)
#define EXTR_PAL_FNC(a)  ((a) & MSK_PAL_FNC)

//
// Bit manipulations for EV4 PAL mode instructions
//

#define SHFT_EV4_IBOX      5
#define SHFT_EV4_ABOX      6
#define SHFT_EV4_PALTEMP   7
#define SHFT_EV4_QWORD     12
#define SHFT_EV4_RWCHECK   13
#define SHFT_EV4_ALT       14
#define SHFT_EV4_PHYSICAL  15

#define WIDTH_EV4_INDEX    5
#define WIDTH_EV4_IBOX     1
#define WIDTH_EV4_ABOX     1
#define WIDTH_EV4_PALTEMP  1
#define WIDTH_EV4_DISP     12
#define WIDTH_EV4_QWORD    1
#define WIDTH_EV4_RWCHECK  1
#define WIDTH_EV4_ALT      1
#define WIDTH_EV4_PHYSICAL 1

#define BITS_EV4_INDEX     ~(-1 << WIDTH_EV4_INDEX)
#define BITS_EV4_IBOX      ~(-1 << WIDTH_EV4_IBOX)
#define BITS_EV4_ABOX      ~(-1 << WIDTH_EV4_ABOX)
#define BITS_EV4_PALTEMP   ~(-1 << WIDTH_EV4_PALTEMP)
#define BITS_EV4_DISP      ~(-1 << WIDTH_EV4_DISP)
#define BITS_EV4_QWORD     ~(-1 << WIDTH_EV4_QWORD)
#define BITS_EV4_RWCHECK   ~(-1 << WIDTH_EV4_RWCHECK)
#define BITS_EV4_ALT       ~(-1 << WIDTH_EV4_ALT)
#define BITS_EV4_PHYSICAL  ~(-1 << WIDTH_EV4_PHYSICAL)

#define EV4_INDEX(a)     (BITS_EV4_INDEX & (a))
#define EV4_IBOX(a)     ((BITS_EV4_IBOX & (a)) << SHFT_EV4_IBOX)
#define EV4_ABOX(a)     ((BITS_EV4_ABOX & (a)) << SHFT_EV4_ABOX)
#define EV4_PALTEMP(a)  ((BITS_EV4_PALTEMP & (a)) << SHFT_EV4_PALTEMP)
#define EV4_DISP(a)      (BITS_EV4_DISP & (a))
#define EV4_QWORD(a)    ((BITS_EV4_QWORD & (a)) << SHFT_EV4_QWORD)
#define EV4_RWCHECK(a)  ((BITS_EV4_RWCHECK & (a)) << SHFT_EV4_RWCHECK)
#define EV4_ALT(a)      ((BITS_EV4_ALT & (a)) << SHFT_EV4_ALT)
#define EV4_PHYSICAL(a) ((BITS_EV4_PHYSICAL & (a)) << SHFT_EV4_PHYSICAL)

#define MSK_EV4_INDEX       EV4_INDEX(BITS_EV4_INDEX)
#define MSK_EV4_IBOX        EV4_IBOX(BITS_EV4_IBOX)
#define MSK_EV4_ABOX        EV4_ABOX(BITS_EV4_ABOX)
#define MSK_EV4_PALTEMP     EV4_PALTEMP(BITS_EV4_PALTEMP)
#define MSK_EV4_PR          (MSK_EV4_INDEX | MSK_EV4_IBOX | MSK_EV4_ABOX | MSK_EV4_PALTEMP)
#define MSK_EV4_DISP        EV4_DISP(BITS_EV4_DISP)
#define MSK_EV4_QWORD       EV4_QWORD(BITS_EV4_QWORD)
#define MSK_EV4_RWCHECK     EV4_RWCHECK(BITS_EV4_RWCHECK)
#define MSK_EV4_ALT         EV4_ALT(BITS_EV4_ALT)
#define MSK_EV4_PHYSICAL    EV4_PHYSICAL(BITS_EV4_PHYSICAL)

#define EXTR_EV4_INDEX(a)        (MSK_EV4_INDEX & (a))
#define EXTR_EV4_IBOX(a)        ((MSK_EV4_IBOX & (a)) >> SHFT_EV4_IBOX)
#define EXTR_EV4_ABOX(a)        ((MSK_EV4_ABOX & (a)) >> SHFT_EV4_ABOX)
#define EXTR_EV4_PALTEMP(a)     ((MSK_EV4_PALTEMP & (a)) >> SHFT_EV4_PALTEMP)
#define EXTR_EV4_DISP(a)         (MSK_EV4_DISP & (a))
#define EXTR_EV4_QWORD(a)       ((MSK_EV4_QWORD & (a)) >> SHFT_EV4_QWORD)
#define EXTR_EV4_RWCHECK(a)     ((MSK_EV4_RWCHECK & (a)) >> SHFT_EV4_RWCHECK)
#define EXTR_EV4_ALT(a)         ((MSK_EV4_ALT & (a)) >> SHFT_EV4_ALT)
#define EXTR_EV4_PHYSICAL(a)    ((MSK_EV4_PHYSICAL & (a)) >> SHFT_EV4_PHYSICAL)

#define EV4_TB_TAG        (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(0))
#define EV4_ITB_PTE       (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(1))
#define EV4_ICCSR         (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(2))
#define EV4_ITM_PTE_TEMP  (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(3))
#define EV4_EXC_ADDR      (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(4))
#define EV4_SL_RCV        (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(5))
#define EV4_ITBZAP        (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(6))
#define EV4_ITBASM        (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(7))
#define EV4_ITBIS         (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(8))
#define EV4_PS            (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(9))
#define EV4_EXC_SUM       (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(10))
#define EV4_PAL_BASE      (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(11))
#define EV4_HIRR          (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(12))
#define EV4_SIRR          (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(13))
#define EV4_ASTRR         (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(14))
#define EV4_HIER          (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(16))
#define EV4_SIER          (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(17))
#define EV4_ASTER         (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(18))
#define EV4_SL_CLR        (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(19))
#define EV4_SL_XMIT       (EV4_PALTEMP(0) | EV4_ABOX(0) | EV4_IBOX(1) | EV4_INDEX(22))
#define EV4_DTB_CTL       (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(0))
#define EV4_DTB_PTE       (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(2))
#define EV4_DTB_PTE_TEMP  (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(3))
#define EV4_MMCSR         (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(4))
#define EV4_VA            (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(5))
#define EV4_DTBZAP        (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(6))
#define EV4_DTASM         (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(7))
#define EV4_DTBIS         (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(8))
#define EV4_BIU_ADDR      (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(9))
#define EV4_BIU_STAT      (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(10))
#define EV4_DC_ADDR       (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(11))
#define EV4_DC_STAT       (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(12))
#define EV4_FILL_ADDR     (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(13))
#define EV4_ABOX_CTL      (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(14))
#define EV4_ALT_MODE      (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(15))
#define EV4_CC            (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(16))
#define EV4_CC_CTL        (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(17))
#define EV4_BIU_CTL       (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(18))
#define EV4_FILL_SYNDROME (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(19))
#define EV4_BC_TAG        (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(20))
#define EV4_FLUSH_IC      (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(21))
#define EV4_FLUSH_IC_ASM  (EV4_PALTEMP(0) | EV4_ABOX(1) | EV4_IBOX(0) | EV4_INDEX(23))
#define EV4_PAL_TEMP(x)   (EV4_PALTEMP(1) | EV4_ABOX(0) | EV4_IBOX(0) | EV4_INDEX(x))

#endif


OPTABLE.C

/*++

Copyright (c) 1993  Digital Equipment Corporation

Module Name:

    optable.c

Abstract:

    Declaration for -
    Table of operations, their names and charactersitics
    Used by ntsd, windbg and acc's dissassembler

Author:

    Miche Baker-Harvey (mbh) 10-Jan-1993

Revision History:

--*/
#include <excpt.h>
#include <windef.h>
#include <winnt.h>
#include <alphaops.h>
#define DEFINE_STRINGS
#include "strings.h"
#include "optable.h"

// for strcmp
#include <string.h>

// for fVerboseOutput
BOOLEAN fVerboseOutput = FALSE;

//
// These are the parsing functions.  In ntsd, they are defined
// in ntasm.c.  In windbg, they are defined in MBH - WHERE????
//

ULONG ParseIntMemory (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);
ULONG ParseFltMemory (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);
ULONG ParseMemSpec   (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);
ULONG ParseJump      (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);
ULONG ParseIntBranch (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);
ULONG ParseFltBranch (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);
ULONG ParseIntOp     (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);
ULONG ParsePal       (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);
ULONG ParseUnknown   (PUCHAR, PUCHAR *, POPTBLENTRY, PULONG);

#define NOFNCTBL (POPTBLENTRY) 0
#define NOSIZE (ULONG)0

//
// These fields are used to find the beginning of the sections
// containing different "ENTRY_TYPE"s
//

POPTBLENTRY InvalidTab;
POPTBLENTRY NonTerminalTab;
POPTBLENTRY TerminalTab;
POPTBLENTRY FunctionTab;

ULONG        InvalidTabSize;
ULONG        NonTerminalTabSize;
ULONG        TerminalTabSize;
ULONG        FunctionTabSize;

//
// THE OPCODE TABLE ITSELF
//
// The opcode table "opTable" describes each opcode and function.
// There is an entry for each opcode, and for each function.
//
// The table is organized as follows:
//                invalid-ops,
//                non-terminal-ops,
//                terminal-ops,
//                functions,
//
//    This organization is NOT required:
//           no assumptions are made on it.
//
//    Searches based on opcode must search
//             INVALID, TERMINAL and NON_TERMINAL
//
//    Searches based on instruction name must search
//             TERMINAL and FUNCTION
//
//

OPTBLENTRY opTable[] = {

      //
      // First, the INVALID_ETYPE section.
      // (opcode searches begin here)
      //

{ "?Opc01", ParseUnknown, _01_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc02", ParseUnknown, _02_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc03", ParseUnknown, _03_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc04", ParseUnknown, _04_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc05", ParseUnknown, _05_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc06", ParseUnknown, _06_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc07", ParseUnknown, _07_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc0A", ParseUnknown, _0A_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc0C", ParseUnknown, _0C_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc0D", ParseUnknown, _0D_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc0E", ParseUnknown, _0E_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc14", ParseUnknown, _14_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},
{ "?Opc1C", ParseUnknown, _1C_OP, NO_FUNC, ALPHA_UNKNOWN, INVALID_ETYPE},

//
//         This is what hasn't been done yet -
//  the EV4 stuff - there are no names for it
//  in the alphaops.h header file.  Should we
//  put them there?  Should they be elsewhere?
//  Do we want to assemble them?
//
// For the moment, just pretend they are invalid.  They never
// come up for all practical purposes, anyway.
//

{ "MFPR", ParseUnknown, MFPR_OP, NO_FUNC,
                                      ALPHA_EV4_PR, INVALID_ETYPE },
{ "MTPR", ParseUnknown, MTPR_OP, NO_FUNC,
                                      ALPHA_EV4_PR, INVALID_ETYPE },

      //
      // Secondly, the NON_TERMINAL_ETYPE section
      //

{  (char *)NOFNCTBL, NOSIZE, CALLPAL_OP, NO_FUNC,
                                   ALPHA_CALLPAL, NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, ARITH_OP, NO_FUNC,
                                   ALPHA_OPERATE, NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, BIT_OP, NO_FUNC,
                                   ALPHA_OPERATE, NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, BYTE_OP, NO_FUNC,
                                   ALPHA_OPERATE, NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, MUL_OP, NO_FUNC,
                                   ALPHA_OPERATE, NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, MEMSPC_OP, NO_FUNC,
                                   ALPHA_MEMSPC,  NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, JMP_OP, NO_FUNC,
                                   ALPHA_JUMP,    NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, VAXFP_OP, NO_FUNC,
                                   ALPHA_FP_OPERATE, NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, IEEEFP_OP, NO_FUNC,
                                   ALPHA_FP_OPERATE, NON_TERMINAL_ETYPE },
{  (char *)NOFNCTBL, NOSIZE, FPOP_OP,    NO_FUNC,
                                   ALPHA_FP_OPERATE, NON_TERMINAL_ETYPE },

      //
      // Thirdly, the TERMINAL_ETYPE section
      // (everything from here on has an instruction name)
      //

{ szLda,   ParseIntMemory, LDA_OP,  NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szLdah,  ParseIntMemory, LDAH_OP, NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szLdl,   ParseIntMemory, LDL_OP,  NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szLdq,   ParseIntMemory, LDQ_OP,  NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szLdf,   ParseFltMemory, LDF_OP,  NO_FUNC, ALPHA_FP_MEMORY, TERMINAL_ETYPE },
{ szLdg,   ParseFltMemory, LDG_OP,  NO_FUNC, ALPHA_FP_MEMORY, TERMINAL_ETYPE },
{ szLds,   ParseFltMemory, LDS_OP,  NO_FUNC, ALPHA_FP_MEMORY, TERMINAL_ETYPE },
{ szLdt,   ParseFltMemory, LDT_OP,  NO_FUNC, ALPHA_FP_MEMORY, TERMINAL_ETYPE },
{ szLdq_u, ParseIntMemory, LDQ_U_OP,NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szLdl_l, ParseIntMemory, LDL_L_OP,NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szLdq_l, ParseIntMemory, LDQ_L_OP,NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },

{ szStl,   ParseIntMemory, STL_OP,  NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szStq,   ParseIntMemory, STQ_OP,  NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szStf,   ParseFltMemory, STF_OP,  NO_FUNC, ALPHA_FP_MEMORY, TERMINAL_ETYPE },
{ szStg,   ParseFltMemory, STG_OP,  NO_FUNC, ALPHA_FP_MEMORY, TERMINAL_ETYPE },
{ szSts,   ParseFltMemory, STS_OP,  NO_FUNC, ALPHA_FP_MEMORY, TERMINAL_ETYPE },
{ szStt,   ParseFltMemory, STT_OP,  NO_FUNC, ALPHA_FP_MEMORY, TERMINAL_ETYPE },
{ szStq_u, ParseIntMemory, STQ_U_OP,NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szStl_c, ParseIntMemory, STL_C_OP,NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },
{ szStq_c, ParseIntMemory, STQ_C_OP,NO_FUNC, ALPHA_MEMORY, TERMINAL_ETYPE },

{ szBeq,   ParseIntBranch, BEQ_OP,  NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBne,   ParseIntBranch, BNE_OP,  NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBlt,   ParseIntBranch, BLT_OP,  NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBle,   ParseIntBranch, BLE_OP,  NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBgt,   ParseIntBranch, BGT_OP,  NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBge,   ParseIntBranch, BGE_OP,  NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBlbc,  ParseIntBranch, BLBC_OP, NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBlbs,  ParseIntBranch, BLBS_OP, NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBr,    ParseIntBranch, BR_OP,   NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },
{ szBsr,   ParseIntBranch, BSR_OP,  NO_FUNC, ALPHA_BRANCH, TERMINAL_ETYPE },

{ szFbeq,  ParseFltBranch, FBEQ_OP, NO_FUNC, ALPHA_FP_BRANCH, TERMINAL_ETYPE },
{ szFbne,  ParseFltBranch, FBNE_OP, NO_FUNC, ALPHA_FP_BRANCH, TERMINAL_ETYPE },
{ szFblt,  ParseFltBranch, FBLT_OP, NO_FUNC, ALPHA_FP_BRANCH, TERMINAL_ETYPE },
{ szFble,  ParseFltBranch, FBLE_OP, NO_FUNC, ALPHA_FP_BRANCH, TERMINAL_ETYPE },
{ szFbgt,  ParseFltBranch, FBGT_OP, NO_FUNC, ALPHA_FP_BRANCH, TERMINAL_ETYPE },
{ szFbge,  ParseFltBranch, FBGE_OP, NO_FUNC, ALPHA_FP_BRANCH, TERMINAL_ETYPE },

{ "REI",   ParseUnknown, PAL1B_OP, NO_FUNC, ALPHA_EV4_REI, TERMINAL_ETYPE},
{ "HW_LD", ParseUnknown, PAL1E_OP, NO_FUNC, ALPHA_EV4_MEM, TERMINAL_ETYPE},
{ "HW_ST", ParseUnknown, PAL1F_OP, NO_FUNC, ALPHA_EV4_MEM, TERMINAL_ETYPE},

      //
      // Fourthly, (and finally) the FUNCTION_ETYPE section
      // (opcode searches needn't include this section)
      //

           //
           // The memory-special functions
           //

{ szMb,   ParseMemSpec, MEMSPC_OP, MB_FUNC,    ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szWmb,  ParseMemSpec, MEMSPC_OP, WMB_FUNC,   ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szMb2,  ParseMemSpec, MEMSPC_OP, MB2_FUNC,   ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szMb3,  ParseMemSpec, MEMSPC_OP, MB3_FUNC,   ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szFetch,ParseMemSpec, MEMSPC_OP, FETCH_FUNC, ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szFetch_m,ParseMemSpec,MEMSPC_OP,FETCH_M_FUNC,ALPHA_MEMSPC,FUNCTION_ETYPE },
{ szRs,   ParseMemSpec, MEMSPC_OP, RS_FUNC,    ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szTrapb,ParseMemSpec, MEMSPC_OP, TRAPB_FUNC, ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szExcb, ParseMemSpec, MEMSPC_OP, EXCB_FUNC,  ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szRpcc, ParseMemSpec, MEMSPC_OP, RPCC_FUNC,  ALPHA_MEMSPC, FUNCTION_ETYPE },
{ szRc,   ParseMemSpec, MEMSPC_OP, RC_FUNC,    ALPHA_MEMSPC, FUNCTION_ETYPE },

           //
           // The jump functions
           //

{ szJmp,  ParseJump,  JMP_OP, JMP_FUNC, ALPHA_JUMP, FUNCTION_ETYPE },
{ szJsr,  ParseJump,  JMP_OP, JSR_FUNC, ALPHA_JUMP, FUNCTION_ETYPE },
{ szRet,  ParseJump,  JMP_OP, RET_FUNC, ALPHA_JUMP, FUNCTION_ETYPE },
{ szJsr_co,  ParseJump,  JMP_OP, JSR_CO_FUNC, ALPHA_JUMP, FUNCTION_ETYPE },

           //
           // The arithmetic ops, which are ALPHA_OPERATE
           //

{ szAddl,   ParseIntOp, ARITH_OP, ADDL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szAddlv,  ParseIntOp, ARITH_OP, ADDLV_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szAddq,   ParseIntOp, ARITH_OP, ADDQ_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szAddqv,  ParseIntOp, ARITH_OP, ADDQV_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szSubl,   ParseIntOp, ARITH_OP, SUBL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szSublv,  ParseIntOp, ARITH_OP, SUBLV_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szSubq,   ParseIntOp, ARITH_OP, SUBQ_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szSubqv,  ParseIntOp, ARITH_OP, SUBQV_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },

{ szCmpeq,  ParseIntOp, ARITH_OP, CMPEQ_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmplt,  ParseIntOp, ARITH_OP, CMPLT_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmple,  ParseIntOp, ARITH_OP, CMPLE_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmpult, ParseIntOp, ARITH_OP, CMPULT_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmpule, ParseIntOp, ARITH_OP, CMPULE_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmpbge, ParseIntOp, ARITH_OP, CMPBGE_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },

{ szS4addl, ParseIntOp, ARITH_OP, S4ADDL_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szS4addq, ParseIntOp, ARITH_OP, S4ADDQ_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szS4subl, ParseIntOp, ARITH_OP, S4SUBL_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szS4subq, ParseIntOp, ARITH_OP, S4SUBQ_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szS8addl, ParseIntOp, ARITH_OP, S8ADDL_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szS8addq, ParseIntOp, ARITH_OP, S8ADDQ_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szS8subl, ParseIntOp, ARITH_OP, S8SUBL_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szS8subq, ParseIntOp, ARITH_OP, S8SUBQ_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },

           //
           // The bit ops, which are ALPHA_OPERATE
           //

{ szAnd,   ParseIntOp, BIT_OP, AND_FUNC,   ALPHA_OPERATE, FUNCTION_ETYPE },
{ szBic,   ParseIntOp, BIT_OP, BIC_FUNC,   ALPHA_OPERATE, FUNCTION_ETYPE },
{ szBis,   ParseIntOp, BIT_OP, BIS_FUNC,   ALPHA_OPERATE, FUNCTION_ETYPE },
{ szOrnot, ParseIntOp, BIT_OP, ORNOT_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szXor,   ParseIntOp, BIT_OP, XOR_FUNC,   ALPHA_OPERATE, FUNCTION_ETYPE },
{ szEqv,   ParseIntOp, BIT_OP, EQV_FUNC,   ALPHA_OPERATE, FUNCTION_ETYPE },

{ szCmoveq,  ParseIntOp, BIT_OP, CMOVEQ_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmovne,  ParseIntOp, BIT_OP, CMOVNE_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmovlbs, ParseIntOp, BIT_OP, CMOVLBS_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmovlt,  ParseIntOp, BIT_OP, CMOVLT_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmovge,  ParseIntOp, BIT_OP, CMOVGE_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmovlbc, ParseIntOp, BIT_OP, CMOVLBC_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmovle,  ParseIntOp, BIT_OP, CMOVLE_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szCmovgt,  ParseIntOp, BIT_OP, CMOVGT_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },

           //
           // The byte ops, which are ALPHA_OPERATE
           //

{ szSll,    ParseIntOp, BYTE_OP, SLL_FUNC,    ALPHA_OPERATE, FUNCTION_ETYPE },
{ szSra,    ParseIntOp, BYTE_OP, SRA_FUNC,    ALPHA_OPERATE, FUNCTION_ETYPE },
{ szSrl,    ParseIntOp, BYTE_OP, SRL_FUNC,    ALPHA_OPERATE, FUNCTION_ETYPE },
{ szExtbl,  ParseIntOp, BYTE_OP, EXTBL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szExtwl,  ParseIntOp, BYTE_OP, EXTWL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szExtll,  ParseIntOp, BYTE_OP, EXTLL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szExtql,  ParseIntOp, BYTE_OP, EXTQL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szExtwh,  ParseIntOp, BYTE_OP, EXTWH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szExtlh,  ParseIntOp, BYTE_OP, EXTLH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szExtqh,  ParseIntOp, BYTE_OP, EXTQH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szInsbl,  ParseIntOp, BYTE_OP, INSBL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szInswl,  ParseIntOp, BYTE_OP, INSWL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szInsll,  ParseIntOp, BYTE_OP, INSLL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szInsql,  ParseIntOp, BYTE_OP, INSQL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szInswh,  ParseIntOp, BYTE_OP, INSWH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szInslh,  ParseIntOp, BYTE_OP, INSLH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szInsqh,  ParseIntOp, BYTE_OP, INSQH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMskbl,  ParseIntOp, BYTE_OP, MSKBL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMskwl,  ParseIntOp, BYTE_OP, MSKWL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMskll,  ParseIntOp, BYTE_OP, MSKLL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMskql,  ParseIntOp, BYTE_OP, MSKQL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMskwh,  ParseIntOp, BYTE_OP, MSKWH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMsklh,  ParseIntOp, BYTE_OP, MSKLH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMskqh,  ParseIntOp, BYTE_OP, MSKQH_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szZap,    ParseIntOp, BYTE_OP, ZAP_FUNC,    ALPHA_OPERATE, FUNCTION_ETYPE },
{ szZapnot, ParseIntOp, BYTE_OP, ZAPNOT_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },

           //
           // The multiply ops, which are ALPHA_OPERATE
           //

{ szMull,   ParseIntOp,  MUL_OP, MULL_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMulqv,  ParseIntOp,  MUL_OP, MULQV_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMullv,  ParseIntOp,  MUL_OP, MULLV_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szUmulh,  ParseIntOp,  MUL_OP, UMULH_FUNC, ALPHA_OPERATE, FUNCTION_ETYPE },
{ szMulq,   ParseIntOp,  MUL_OP, MULQ_FUNC,  ALPHA_OPERATE, FUNCTION_ETYPE },

           //
           // The call pal functions
           //

{ szBpt,       ParsePal, CALLPAL_OP,  BPT_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szCallsys,   ParsePal, CALLPAL_OP, CALLSYS_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szImb,       ParsePal, CALLPAL_OP, IMB_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdteb,     ParsePal, CALLPAL_OP, RDTEB_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szGentrap,   ParsePal, CALLPAL_OP, GENTRAP_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szKbpt,      ParsePal, CALLPAL_OP, KBPT_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szCallKD,    ParsePal, CALLPAL_OP, CALLKD_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szHalt, ParsePal, CALLPAL_OP, HALT_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRestart, ParsePal, CALLPAL_OP, RESTART_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szDraina, ParsePal, CALLPAL_OP, DRAINA_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szInitpal, ParsePal, CALLPAL_OP, INITPAL_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szWrentry, ParsePal, CALLPAL_OP, WRENTRY_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szSwpirql, ParsePal, CALLPAL_OP, SWPIRQL_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdirql, ParsePal, CALLPAL_OP, RDIRQL_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szDi, ParsePal, CALLPAL_OP, DI_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szEi, ParsePal, CALLPAL_OP, EI_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szSwppal, ParsePal, CALLPAL_OP, SWPPAL_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szSsir, ParsePal, CALLPAL_OP, SSIR_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szCsir, ParsePal, CALLPAL_OP, CSIR_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRfe, ParsePal, CALLPAL_OP, RFE_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRetsys, ParsePal, CALLPAL_OP, RETSYS_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szSwpctx, ParsePal, CALLPAL_OP, SWPCTX_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szSwpprocess, ParsePal, CALLPAL_OP, SWPPROCESS_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdmces, ParsePal, CALLPAL_OP, RDMCES_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szWrmces, ParsePal, CALLPAL_OP, WRMCES_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szTbia, ParsePal, CALLPAL_OP, TBIA_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szTbis, ParsePal, CALLPAL_OP, TBIS_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szDtbis, ParsePal, CALLPAL_OP, DTBIS_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdksp, ParsePal, CALLPAL_OP, RDKSP_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szSwpksp, ParsePal, CALLPAL_OP, SWPKSP_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdpsr, ParsePal, CALLPAL_OP, RDPSR_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdpcr, ParsePal, CALLPAL_OP, RDPCR_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdthread, ParsePal, CALLPAL_OP, RDTHREAD_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdcounters, ParsePal, CALLPAL_OP, RDCOUNTERS_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRdstate, ParsePal, CALLPAL_OP, RDSTATE_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szInitpcr, ParsePal, CALLPAL_OP, INITPCR_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szWrperfmon, ParsePal, CALLPAL_OP, WRPERFMON_FUNC,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szMt, ParsePal, CALLPAL_OP, MTPR_OP,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szMf, ParsePal, CALLPAL_OP, MFPR_OP,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szHwld, ParsePal, CALLPAL_OP, HWLD_OP,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szHwst, ParsePal, CALLPAL_OP, HWST_OP,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },
{ szRei, ParsePal, CALLPAL_OP, REI_OP,
                                  ALPHA_CALLPAL, FUNCTION_ETYPE },

           //
           // The VAX Floating point functions
           //

{ szAddf,   ParseUnknown, VAXFP_OP, ADDF_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtdg,  ParseUnknown, VAXFP_OP, CVTDG_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szAddg,   ParseUnknown, VAXFP_OP, ADDG_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCmpgeq, ParseUnknown, VAXFP_OP, CMPGEQ_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCmpglt, ParseUnknown, VAXFP_OP, CMPGLT_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCmpgle, ParseUnknown, VAXFP_OP, CMPGLE_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtgf,  ParseUnknown, VAXFP_OP, CVTGF_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtgd,  ParseUnknown, VAXFP_OP, CVTGD_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtqf,  ParseUnknown, VAXFP_OP, CVTQF_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtqg,  ParseUnknown, VAXFP_OP, CVTQG_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szDivf,   ParseUnknown, VAXFP_OP, DIVF_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szDivg,   ParseUnknown, VAXFP_OP, DIVG_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szMulf,   ParseUnknown, VAXFP_OP, MULF_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szMulg,   ParseUnknown, VAXFP_OP, MULG_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szSubf,   ParseUnknown, VAXFP_OP, SUBF_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szSubg,   ParseUnknown, VAXFP_OP, SUBG_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtgq,  ParseUnknown, VAXFP_OP, CVTGQ_FUNC,
                                    ALPHA_FP_OPERATE, FUNCTION_ETYPE },
           //
           // The IEEE Floating point functions
           //

{ szAdds,   ParseUnknown, IEEEFP_OP, ADDS_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szSubs,   ParseUnknown, IEEEFP_OP, SUBS_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szMuls,   ParseUnknown, IEEEFP_OP, MULS_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szDivs,   ParseUnknown, IEEEFP_OP, DIVS_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szAddt,   ParseUnknown, IEEEFP_OP, ADDT_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szSubt,   ParseUnknown, IEEEFP_OP, SUBT_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szMult,   ParseUnknown, IEEEFP_OP, MULT_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szDivt,   ParseUnknown, IEEEFP_OP, DIVT_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCmptun, ParseUnknown, IEEEFP_OP, CMPTUN_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCmpteq, ParseUnknown, IEEEFP_OP, CMPTEQ_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCmptlt, ParseUnknown, IEEEFP_OP, CMPTLT_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCmptle, ParseUnknown, IEEEFP_OP, CMPTLE_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtts,  ParseUnknown, IEEEFP_OP, CVTTS_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvttq,  ParseUnknown, IEEEFP_OP, CVTTQ_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtqs,  ParseUnknown, IEEEFP_OP, CVTQS_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtqt,  ParseUnknown, IEEEFP_OP, CVTQT_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtst,  ParseUnknown, IEEEFP_OP, CVTST_FUNC,
                                     ALPHA_FP_CONVERT, FUNCTION_ETYPE },
{ szCvtsts, ParseUnknown, IEEEFP_OP, CVTST_S_FUNC,
                                     ALPHA_FP_CONVERT, FUNCTION_ETYPE },

           //
           // The Common Floating point functions
           //

{ szCvtlq,    ParseUnknown,  FPOP_OP, CVTLQ_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCpys,     ParseUnknown,  FPOP_OP, CPYS_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCpysn,    ParseUnknown,  FPOP_OP, CPYSN_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCpyse,    ParseUnknown,  FPOP_OP, CPYSE_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szMt_fpcr,  ParseUnknown,  FPOP_OP, MT_FPCR_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szMf_fpcr,  ParseUnknown,  FPOP_OP, MF_FPCR_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szFcmoveq,  ParseUnknown,  FPOP_OP, FCMOVEQ_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szFcmovne,  ParseUnknown,  FPOP_OP, FCMOVNE_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szFcmovlt,  ParseUnknown,  FPOP_OP, FCMOVLT_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szFcmovge,  ParseUnknown,  FPOP_OP, FCMOVGE_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szFcmovle,  ParseUnknown,  FPOP_OP, FCMOVLE_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szFcmovgt,  ParseUnknown,  FPOP_OP, FCMOVGT_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtql,    ParseUnknown,  FPOP_OP, CVTQL_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtqlv,   ParseUnknown,  FPOP_OP, CVTQLV_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },
{ szCvtqlsv,  ParseUnknown,  FPOP_OP, CVTQLSV_FUNC,
                                     ALPHA_FP_OPERATE, FUNCTION_ETYPE },

};       // end of opTable

#define SEARCHNUM  sizeof(opTable) / sizeof(OPTBLENTRY)

//
// Here are the tables of Floating Point flags.
//

FPFLAGS ConvertFlags[] = {
    { NONE_FLAGS, NONE_FLAGS_STR },
    { C_FLAGS, C_FLAGS_STR },
    { V_FLAGS, V_FLAGS_STR },
    { VC_FLAGS, VC_FLAGS_STR },
    { SV_FLAGS, SV_FLAGS_STR },
    { SVC_FLAGS, SVC_FLAGS_STR },
    { SVI_FLAGS, SVI_FLAGS_STR },
    { SVIC_FLAGS, SVIC_FLAGS_STR },

    { D_FLAGS, D_FLAGS_STR },
    { VD_FLAGS, VD_FLAGS_STR },
    { SVD_FLAGS, SVD_FLAGS_STR },
    { SVID_FLAGS, SVID_FLAGS_STR },
    { M_FLAGS, M_FLAGS_STR },
    { VM_FLAGS, VM_FLAGS_STR },
    { SVM_FLAGS, SVM_FLAGS_STR },
    { SVIM_FLAGS, SVIM_FLAGS_STR },

    { S_FLAGS, S_FLAGS_STR },
    { SC_FLAGS, SC_FLAGS_STR },
    { FPFLAGS_NOT_AN_ENTRY, "" }
};

FPFLAGS FloatOpFlags[] = {
    { C_FLAGS, C_FLAGS_STR },
    { M_FLAGS, M_FLAGS_STR },
    { NONE_FLAGS, NONE_FLAGS_STR },
    { D_FLAGS, D_FLAGS_STR },
    { UC_FLAGS, UC_FLAGS_STR },
    { UM_FLAGS, UM_FLAGS_STR },
    { U_FLAGS, U_FLAGS_STR },
    { UD_FLAGS, UD_FLAGS_STR },
    { SC_FLAGS, SC_FLAGS_STR },
    { S_FLAGS, S_FLAGS_STR },
    { SUC_FLAGS, SUC_FLAGS_STR },
    { SUM_FLAGS, SUM_FLAGS_STR },
    { SU_FLAGS, SU_FLAGS_STR },
    { SUD_FLAGS, SUD_FLAGS_STR },
    { SUIC_FLAGS, SUIC_FLAGS_STR },
    { SUIM_FLAGS, SUIM_FLAGS_STR },
    { SUI_FLAGS, SUI_FLAGS_STR },
    { SUID_FLAGS, SUID_FLAGS_STR },
    { FPFLAGS_NOT_AN_ENTRY, "" }
};

/*** findNameEntry - find POPTBLENTRY based on name
*
*   Purpose:
*       Search the opTable for a match with the token
*       pointed by *pszOp.  Must search through the
*       TERMINAL and the FUNCTION tables
*
*   Input:
*       *pszOp - string to search as mnemonic
*
*   Returns:
*       Pointer to entry in the opTable
*
*************************************************************************/

POPTBLENTRY
findStringEntry (PUCHAR pszOp)
{

    POPTBLENTRY pEntry;

    for (pEntry = TerminalTab;
         pEntry < &TerminalTab[TerminalTabSize];
         pEntry++) {

        if (!strcmp(pszOp, pEntry->pszAlphaName))
                return(pEntry);
    }

    for (pEntry = FunctionTab;
         pEntry < &FunctionTab[FunctionTabSize];
         pEntry++) {

        if (!strcmp(pszOp, pEntry->pszAlphaName))
                return(pEntry);

}

    return((POPTBLENTRY)-1);
}

/* findOpCodeEntry - find POPTBLENTRY based on opcode
*
*   Purpose:
*       Search the opTable for a match with the opcode
*       Must search through the
*       INVALID, TERMINAL and NON_TERMINAL tables
*
*   Input:
*       pOpEntry - pointer to NON_TERMINAL_ETYPE in opTable
*       function - the function value to be looked up
*
*   Output:
*       pointer to string mnemonic for the function
*
***********************************************************************/

POPTBLENTRY
findOpCodeEntry(ULONG opcode)
{
    POPTBLENTRY pEntry;

    for (pEntry = TerminalTab;
         pEntry < &TerminalTab[TerminalTabSize];
         pEntry++) {

        if (pEntry->opCode == opcode)
                return(pEntry);
    }

    for (pEntry = NonTerminalTab;
         pEntry < &NonTerminalTab[NonTerminalTabSize];
         pEntry++) {

        if (pEntry->opCode == opcode)
                return(pEntry);
    }

    for (pEntry = InvalidTab;
         pEntry < &InvalidTab[InvalidTabSize];
         pEntry++) {

        if (pEntry->opCode == opcode)
                return(pEntry);
    }

    return((POPTBLENTRY)-1);
}

/*** findTypeFromOpcode - find the instruction type
*
*   Purpose:
*       This routine finds the type of the instruction (ALPHA_*)
*       from the opcode.  It returns a single value so that it
*       can be called from other dll's without passing storage.
*
*   Input:
*       the opcode number.
*
*   Output:
*       the type of the opcode (ALPHA_* in alphaops.h)
*
*   Errors:
*       returns ALPHA_UNKNOWN for an invalid opcode
*
*   Exceptions:
*       None.
*
*
*************************************************************************/

ULONG
findTypeFromOpcode(ULONG opcode)
{
    POPTBLENTRY pEntry;

    pEntry = findOpCodeEntry(opcode);

    if (pEntry != (POPTBLENTRY)(-1)) {
        return(pEntry->iType);
    } else {
        return(ALPHA_UNKNOWN);
    }
}

/*** findNonTerminalEntry - find pointer to set of functions
*
*   Purpose:
*       This routine finds the entry in the table which the is
*       nonterminal entry for an opcode.
*
*   Input:
*       The type of function that is interesting
*
*   Output:
*       Pointer to the nonterminal entry in opTable
*
*   Errors:
*       If the entry is not found, a message is printed, and the
*       routine exits.
*
*   Exceptions:
*       None.
*
*   Note:
*       This routine is called BEFORE NonTerminalTable is established!
*       (it's used to set up these tables, in fact).
*
*************************************************************************/

POPTBLENTRY
findNonTerminalEntry(ULONG opCode)
{
    ULONG index;

    for ( index = 0 ; index < SEARCHNUM; index++ ) {

        if ( ( opTable[index].eType == NON_TERMINAL_ETYPE ) &&
             ( opTable[index].opCode == opCode ) ) {

                 return(&opTable[index]);
        }
    }

}

/* findFuncName - get string name for a function
*
*   Purpose:
*       to get function name, given the function number, and a
*       pointer to the opTable entry for the NON_TERMINAL_ETYPE
*       opcode associated with the function
*
*   Input:
*       pOpEntry - pointer to NON_TERMINAL_ETYPE in opTable
*       function - the function value to be looked up
*
*   Output:
*       pointer to string mnemonic for the function
*
***********************************************************************/

char *
findFuncName(POPTBLENTRY pEntry, ULONG function)
{
    int cIndex;
    POPTBLENTRY pFncEntry;

    pFncEntry = pEntry->funcTable;
    cIndex = (int)pEntry->funcTableSize;

    //
    // make sure that this entry pts to a function table
    //

    if (pEntry->eType != NON_TERMINAL_ETYPE) {
        return("???");
    }

    while(cIndex-- > 0) {
        if (function == pFncEntry->funcCode)
            return(pFncEntry->pszAlphaName);
        pFncEntry++;
    };

    return("???");
}

/** findFlagName - get the string associated with a flag
*
*    Purpose - return a string associated with the flags for a
*              floating point instruction
*
*    Input:
*      flag    - the flags on the opcode
*      opcode  - the opcode; if it's Cvt*, we use different flags
*
*    Output:
*      pointer to string describing flags, or "/???"
*
***************/

char *
findFlagName(ULONG flag, ULONG function)
{

     PFPFLAGS table;

     if (function == CVTQL_FUNC) {
         switch (flag) {
         case C_FLAGS:
              return "";
         case VC_FLAGS:
              return "/v";
         case SVC_FLAGS:
              return "/sv";
         }
     }

     if ((function == CVTTQ_FUNC) || (function == CVTGQ_FUNC)) {
         table = ConvertFlags;
     } else {
         table = FloatOpFlags;
     }

     while (table->flags != FPFLAGS_NOT_AN_ENTRY) {

         if (table->flags == flag) {
              return(table->flagname);
         }
         table++;
    }

    // no match found
    //

    return("/???");
}

/*** opTableInit - initialize fields used in and with the opTable
*
*   Purpose:
*       This routine is called once, and sets up pointers to the
*       subtables embedded in the opTable, such as AddOpTab, and
*       sizes for these subtables.  It also checks that all like
*       instructions are grouped together in the table, which is
*       the only requirement on it.
*
*   Input:
*       None.
*
*   Output:
*       None.
*
*   Errors:
*       If the table is not properly organized (four types separated,
*       and the functions for a single opcode grouped), this prints a
*       messages and fails
*
*   Exceptions:
*       None.
*
*************************************************************************/

void
opTableInit()
{

    ULONG typesDone[4] = {0,0,0,0};

    ULONG palDone, arithDone, bitDone, byteDone, jmpDone;
    ULONG fpopDone, vaxDone, IEEEDone, mulDone, memSpcDone;

    ULONG        index;

    POPTBLENTRY  entry;

    ENTRY_TYPE   curType = NOT_AN_ETYPE;
    ULONG        curFunc = NO_FUNC;    // OPCODE field in func entry

    //
    // To set the end of the table, and its size, without having
    // nested case statements, maintain pointers to the entry and
    // function tables we are currently walking through
    //

    PULONG        curTypeSize,       curFuncSize;
    POPTBLENTRY * curTypeTable,    * curFuncTable;

    //
    // these will be reset before they are needed, but not before
    // they are used.
    //

    curTypeTable = (POPTBLENTRY *)&curTypeTable;
    curTypeSize  = (PULONG)&curTypeSize;
    curFuncTable = (POPTBLENTRY *)&curFuncTable;
    curFuncSize  = (PULONG)&curFuncSize;

    palDone = arithDone = bitDone = byteDone = jmpDone = 0;
    fpopDone = vaxDone = IEEEDone = mulDone = memSpcDone = 0;

    for (index = 0 ; index < SEARCHNUM; index++) {

        entry = &opTable[index];

        switch(entry->eType) {

         case INVALID_ETYPE:

             if (curType == entry->eType)
                 continue;

             //
             // Finish off the old tables
             //

             *curTypeSize = entry - *curTypeTable;
             if (curType == FUNCTION_ETYPE) {
                 *curFuncSize = entry - *curFuncTable;
             }

             //
             // Set up the new table
             //

             InvalidTab = entry;
             curTypeSize = &InvalidTabSize;
             curTypeTable = &InvalidTab;
             curType = INVALID_ETYPE;
             typesDone[INVALID_ETYPE] = 1;
             break;

         case NON_TERMINAL_ETYPE:

             if (curType == entry->eType)
                 continue;

             *curTypeSize = entry - *curTypeTable;
             if (curType == FUNCTION_ETYPE) {
                 *curFuncSize = entry - *curFuncTable;
             }

             NonTerminalTab = entry;
             curTypeSize = &NonTerminalTabSize;
             curTypeTable = &NonTerminalTab;
             curType = NON_TERMINAL_ETYPE;
             typesDone[NON_TERMINAL_ETYPE] = 1;
             break;

         case TERMINAL_ETYPE:

             if (curType == entry->eType)
                 continue;

             *curTypeSize = entry - *curTypeTable;
             if (curType == FUNCTION_ETYPE) {
                 *curFuncSize = entry - *curFuncTable;
             }

             TerminalTab = entry;
             curTypeSize = &TerminalTabSize;
             curTypeTable = &TerminalTab;
             curType = TERMINAL_ETYPE;
             typesDone[TERMINAL_ETYPE] = 1;
             break;

         case FUNCTION_ETYPE:

             if (entry->opCode == curFunc)
                      continue;

             //
             // Take care of a new eType table; this exactly
             // parallels the three cases above (*_ETYPE)
             //

             if (curType != FUNCTION_ETYPE) {

                 *curTypeSize = entry - *curTypeTable;

                 FunctionTab = entry;
                 curTypeSize = &FunctionTabSize;
                 curTypeTable = &FunctionTab;
                 curType = FUNCTION_ETYPE;
                 typesDone[FUNCTION_ETYPE] = 1;

             }

             //
             // Next, handle a new function table when this is a new
             // function (==> when this is the first entry in the
             // FunctionTab)
             //

             switch(entry->opCode) {

             POPTBLENTRY e;

             case CALLPAL_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(CALLPAL_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = CALLPAL_OP;
                 palDone = 1;

                 break;

             case ARITH_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(ARITH_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = ARITH_OP;
                 arithDone = 1;

                 break;

             case BIT_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(BIT_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = BIT_OP;
                 bitDone = 1;

                 break;

             case BYTE_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(BYTE_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = BYTE_OP;
                 byteDone = 1;

                 break;

             case MUL_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(MUL_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = MUL_OP;
                 mulDone = 1;

                 break;

             case MEMSPC_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(MEMSPC_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = MEMSPC_OP;
                 memSpcDone = 1;

                 break;

             case JMP_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(JMP_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = JMP_OP;
                 jmpDone = 1;

                 break;

             case VAXFP_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(VAXFP_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = VAXFP_OP;
                 vaxDone = 1;

                 break;

             case IEEEFP_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(IEEEFP_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = IEEEFP_OP;
                 IEEEDone = 1;

                 break;

             case FPOP_OP:

                 *curFuncSize = entry - *curFuncTable;

                 e = findNonTerminalEntry(FPOP_OP);
                 e->funcTable = entry;
                 curFuncSize = &(e->funcTableSize);
                 curFuncTable = &(e->funcTable);

                 curFunc = FPOP_OP;
                 fpopDone = 1;

                 break;

             default:
                 break;

            }  // end of Function table switch

            break;

        default:
            break;

        }      // end of etype table switch
    }          // end of For switch

    //
    // close out the size of the last tables
    //

    if (curType == FUNCTION_ETYPE) {
        *curFuncSize = &opTable[SEARCHNUM] - *curFuncTable;
    }
    *curTypeSize = &opTable[SEARCHNUM] - *curTypeTable;

#if 0
    if (fVerboseOutput) {
        printTable();
    }
#endif

}              // end of opTableInit

void
printTable()
{
   ULONG i;
   POPTBLENTRY e;

   for (i = 0 ; i < SEARCHNUM; i++) {
       e = &opTable[i];
       switch (e->eType) {
       case INVALID_ETYPE:
           break;

       case TERMINAL_ETYPE:
           break;

       case FUNCTION_ETYPE:
           break;

       case NON_TERMINAL_ETYPE:
           break;
       }
   }
}

//
// These routines are part of the assembler, which is not
// yet referenced in windbg, so these are place holders till then
//

ULONG ParseIntMemory (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}
ULONG ParseFltMemory (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}
ULONG ParseMemSpec   (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}
ULONG ParseJump      (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}
ULONG ParseIntBranch (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}
ULONG ParseFltBranch (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}
ULONG ParseIntOp     (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}
ULONG ParsePal       (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}
ULONG ParseUnknown   (PUCHAR a, PUCHAR * b, POPTBLENTRY c, PULONG d)
{
   return(0);
}


OPTABLE.H

/*++

Copyright (c) 1993  Digital Equipment Corporation

Module Name:

    optable.h

Abstract:

    Definitions for -
    Table of operations, their names and charactersitics
    Used by ntsd, windbg and acc's dissassembler

Author:

    Miche Baker-Harvey (mbh) 10-Jan-1993

Revision History:

--*/

#ifndef _OPTABLE_
#define _OPTABLE_

//
// Each entry in the opTable is either for a
// +  function - one option on a particular opcode
// +  terminal opcode - an opcode without a function field
//      the above two can both appear directly in disassembly
//
// +  non terminal opcode - an opcode with a function field:
//      these entries do not represent values which can be
//      executed directly:  they require a function entry.
//
// + invalid opcode - this is an opcode reserved to digital
//

typedef enum ENTRY_TYPE {

   INVALID_ETYPE,
   NON_TERMINAL_ETYPE,
   TERMINAL_ETYPE,
   FUNCTION_ETYPE,
   NOT_AN_ETYPE

} ENTRY_TYPE;

#define NO_FUNC   (ULONG)-1

typedef ULONG (* PFOPPARSE)();

typedef struct _OPTBLENTRY {

    union {

        struct {

            PUCHAR     _pszName;                // Name of the instruction
            PFOPPARSE  _parsFunc;               // Function to parse operands

        } s0;                  // functions and terminal opcodes

        struct {

            struct _OPTBLENTRY * _funcTable;    // Describes funcs for opcode
            ULONG            _funcTableSize;    // Number of possible funcs

        } s1;                  // non-terminal opcodes

    } u;

    //
    // These fields describe the instruction
    //

    ULONG opCode;       // Top 6 bits of a 32-bit alpha instr
    ULONG funcCode;     // Function; meaning is opcode dependent
    ULONG iType;        // type of the instr: branch, fp, mem...
                        // values are ALPHA_* in alphaops.h

    ENTRY_TYPE eType;   // type of this entry in the opTable

} OPTBLENTRY, * POPTBLENTRY;

//
// MBH - hack workaround:
// I tried to do this with nameless functions and structures;
// it works just fine on ALPHA, but dies on 386, so use this
// ugly hack instead.
// The name "pszAlphaName" is used instead of the more obvious
// "pszName" because other structures contain pszName.
//
#define pszAlphaName  u.s0._pszName
#define parsFunc      u.s0._parsFunc
#define funcTable     u.s1._funcTable
#define funcTableSize u.s1._funcTableSize

POPTBLENTRY findNonTerminalEntry(ULONG);
POPTBLENTRY findStringEntry(PUCHAR);
POPTBLENTRY findOpCodeEntry(ULONG);
char * findFuncName(POPTBLENTRY, ULONG);
char * findFlagName(ULONG, ULONG);
void printTable();
void opTableInit();

//
// This structure is used for the floating point flag names.
//

#define FPFLAGS_NOT_AN_ENTRY 0xffffffff

typedef struct _FPFLAGS {

    ULONG flags;        // the flags on the opcode
    PUCHAR flagname;    // the string mnemonic for the flags

} FPFLAGS, * PFPFLAGS;

#endif   // _OPTABLE_


REGS.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    regs.c

Abstract:

    This file provides access to the machine's register set.

Author:

    Miche Baker-Harvey (v-michbh) 1-May-1993  (ported from ntsd)

Environment:

    User Mode

--*/

/*
//
// This line keeps alpha pseudo ops from being defined in kxalpha.h
#ifdef ALPHA
#define HEADER_FILE
#endif
*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"
#include "regs.h"

#include <alphaops.h>
#include "strings.h"

// we want the definitions of PSR_MODE, etc, which
// are derived in genalpha.c by Joe for ksalpha.h.
// They don't exist as separate defines anywhere else.
// However, if we include ksalpha.h, we get bunches
// of duplicate definitions.  So for now (hack,hack),
// just make a local copy of the definitions.
// MBH TODO bugbug - ksalpha.h hack
// #include <ksalpha.h>

#define PSR_USER_MODE 0x1

#define PSR_MODE 0x0                    // Mode bit in PSR (bit 0)
#define PSR_MODE_MASK 0x1               // Mask (1 bit) for mode in PSR
#define PSR_IE 0x1                      // Interrupt Enable bit in PSR (bit 1)
#define PSR_IE_MASK 0x1                 // Mask (1 bit) for IE in PSR
#define PSR_IRQL 0x2                    // IRQL in PSR (bit 2)
#define PSR_IRQL_MASK 0x7               // Mask (2 bits) for IRQL in PSR

CONTEXT SavedRegisterContext;

extern  ULONG   EXPRLastExpression;     // from module ntexpr.c
extern  ULONG   EXPRLastDump;           // from module ntcmd.c
extern  int     fControlC;

PUCHAR  UserRegs[10] = {0};

ULONG   GetIntRegNumber(ULONG);
BOOLEAN UserRegTest(ULONG);
BOOLEAN NeedUpper(DWORDLONG);

void    GetFloatingPointRegValue(PDEBUGPACKET, ULONG , PCONVERTED_DOUBLE);
PUCHAR  RegNameFromIndex(ULONG);
ULONG   GetRegString(PUCHAR);

//
// This is the length of an instruction, and the instruction
// to be used in setting a breakpoint (common code writes the
// breakpoint instruction into the memory stream.
//
ULONG   cbBrkptLength = 4;
// these are defined in alphaops.h
ULONG   trapInstr = CALLPAL_OP | BPT_FUNC ;
ULONG   breakInstrs[] = {CALLPAL_OP | BPT_FUNC,
                         CALLPAL_OP | KBPT_FUNC,
                         CALLPAL_OP | CALLKD_FUNC};

ULONG   ContextType = CONTEXT_FULL;

#define IS_FLOATING_SAVED(Register) ((SAVED_FLOATING_MASK >> Register) & 1L)
#define IS_INTEGER_SAVED(Register) ((SAVED_INTEGER_MASK >> Register) & 1L)

//
// Define saved register masks.

#define SAVED_FLOATING_MASK 0xfff00000  // saved floating registers
#define SAVED_INTEGER_MASK 0xf3ffff02   // saved integer registers

//
// Instruction opcode values are defined in alphaops.h
//

//
// Define stack register and zero register numbers.
//

#define RA 0x1a                         // integer register 26
#define SP 0x1e                         // integer register 30
#define ZERO 0x1f                        // integer register 31

//
// Some Alpha specific register names
//

#define FP 0x0f                         // integer register 15
#define GP 0x1d                         // integer register 29

//
// This parallels ntreg.h
//

PUCHAR  pszReg[] = {
    szF0,  szF1,  szF2,  szF3,  szF4,  szF5,  szF6,  szF7,
    szF8,  szF9,  szF10, szF11, szF12, szF13, szF14, szF15,
    szF16, szF17, szF18, szF19, szF20, szF21, szF22, szF23,
    szF24, szF25, szF26, szF27, szF28, szF29, szF30, szF31,

    szR0,  szR1,  szR2,  szR3,  szR4,  szR5,  szR6,  szR7,
    szR8,  szR9,  szR10, szR11, szR12, szR13, szR14, szR15,
    szR16, szR17, szR18, szR19, szR20, szR21, szR22, szR23,
    szR24, szR25, szR26, szR27, szR28, szR29, szR30, szR31,

    szFpcr, szSoftFpcr, szFir, szPsr, //szIE,

    szFlagMode, szFlagIe, szFlagIrql,
//
// Currently assuming this is right since shadows alpha.h;
// but know that alpha.h flag's are wrong.
//
    szEaPReg, szExpPReg, szRaPReg, szGPReg,             //  psuedo-registers
    szU0Preg, szU1Preg,  szU2Preg, szU3Preg, szU4Preg,
    szU5Preg, szU6Preg,  szU7Preg, szU8Preg, szU9Preg
    };

#define REGNAMESIZE     sizeof(pszReg) / sizeof(PUCHAR)

//
// from ntsdp.h: ULONG RegIndex, Shift, Mask;
// PSR & IE definitions are from ksalpha.h
// which is generated automatically.
// Steal from \\bbox2\alphado\nt\public\sdk\inc\ksalpha.h
// NB: our masks are already shifted:
//
struct Reg {
        char    *psz;
        ULONG   value;
        };

struct SubReg {
        ULONG   regindex;
        ULONG   shift;
        ULONG   mask;
        };

struct SubReg subregname[] = {
    { REGPSR,   PSR_MODE,  PSR_MODE_MASK },
    { REGPSR,   PSR_IE,    PSR_IE_MASK   },
    { REGPSR,   PSR_IRQL,  PSR_IRQL_MASK },
    };

/*** UserRegTest - test if index is a user-defined register
*
*   Purpose:
*       Test if register is user-defined for upper routines.
*
*   Input:
*       index - index of register
*
*   Returns:
*       TRUE if user-defined register, else FALSE
*
*************************************************************************/

BOOLEAN UserRegTest (ULONG index)
{
    return (BOOLEAN)(index >= PREGU0 && index <= PREGU12);
}

/*** GetRegFlagValue - get register or flag value
*
*   Purpose:
*       Return the value of the specified register or flag.
*       This routine calls GetRegValue to get the register
*       value and shifts and masks appropriately to extract a
*       flag value.
*
*   Input:
*       regnum - register or flag specification
*
*   Returns:
*       Value of register or flag.

*************************************************************************/

DWORDLONG
GetRegFlagValue (
    PDEBUGPACKET dp,
    ULONG regnum
    )
{
    DWORDLONG value;

    if (regnum < FLAGBASE || regnum >= PREGBASE) {
        value = GetRegValue(dp, regnum);
    }else {
        regnum -= FLAGBASE;
        value = GetRegValue(dp, subregname[regnum].regindex);
        value = (value >> subregname[regnum].shift) & subregname[regnum].mask;
    }
    return value;
}

BOOLEAN
NeedUpper(
    DWORDLONG value
    )
{
    //
    // if the high bit of the low part is set, then the
    // high part must be all ones, else it must be zero.
    //

    return ( ((value & 0xffffffff80000000L) != 0xffffffff80000000L) &&
         (((value & 0x80000000L) != 0) || ((value & 0xffffffff00000000L) != 0)) );
}

/*** GetRegValue - get register value
*
*   Purpose:
*       Returns the value of the register from the processor
*       context structure.
*
*   Input:
*       regnum - register specification
*
*   Returns:
*       value of the register from the context structure
*
*************************************************************************/

DWORDLONG
GetRegValue (
    PDEBUGPACKET dp,
    ULONG regnum
    )
{
    return *(&dp->tctx->context.FltF0 + regnum);
}

void
GetFloatingPointRegValue(
    PDEBUGPACKET dp,
    ULONG regnum,
    PCONVERTED_DOUBLE dv
    )
{
    dv->li.LowPart  = (DWORD)(*(&dp->tctx->context.FltF0 + regnum) & 0xffffffff);
    dv->li.HighPart = (DWORD)(*(&dp->tctx->context.FltF0 + regnum) >> 32);

}

/*** GetIntRegNumber - Get a register number
*
*
*   Purpose:
*       Get a register number, from an index value.
*       There are places where we want integers to be
*       numbered from 0-31, and this converts into
*       a CONTEXT structure.
*
*   Input:
*       index: integer register number, between 0 and 31
*
*   Output:
*       regnum: offset into the CONTEXT structure
*
*   Exceptions:
*       None
*
*   Notes:
*       This is dependent on the CONTEXT structure
*
******************************************************************/

ULONG GetIntRegNumber (ULONG index)
{
/*
        if (index == 26) {
                return(REGRA);
        }

        if (index < 26) {
                return(REGBASE + index);
        }
        if (index > 26) {
                return(REGBASE + index - 1);
        }
*/
        return(REGBASE + index);
}

ULONG GetRegString (PUCHAR pszString)
{
    ULONG   count;

    for (count = 0; count < REGNAMESIZE; count++)
        if (!strcmp(pszString, pszReg[count]))
            return count;
    return 0xffffffff;
}

void GetRegPCValue (PDEBUGPACKET dp, PDWORDLONG Address)
{

    *Address =  GetRegValue(dp, REGFIR);
    return;
}

#if 0
PULONG GetRegFPValue (PDEBUGPACKET dp)
{
    static DWORDLONG addrFP;

    addrFP =  GetRegValue(dp, FP_REG);
    return &addrFP;
}
#endif

/*** OutputAllRegs - output all registers and present instruction
*
*   Purpose:
*       Function of "r" command.
*
*       To output the current register state of the processor.
*       All integer registers are output as well as processor status
*       registers.  Important flag fields are also output separately.
*       OutDisCurrent is called to output the current instruction(s).
*
*   Input:
*       None.
*
*   Output:
*       None.
*
*************************************************************************/

VOID
OutputAllRegs(
    PDEBUGPACKET dp,
    BOOL Show64
    )
{
    int     regindex;
    int     regnumber;
    DWORDLONG regvalue;

    for (regindex = 0; regindex < 34; regindex++) {

        regnumber = GetIntRegNumber(regindex);
        regvalue = GetRegValue(dp, regnumber);

        if ( Show64 || regindex == 32 || regindex == 33) {

            lprintfs("%4s=%08lx %08lx",
                    pszReg[regnumber],
                    (ULONG)(regvalue >> 32),
                    (ULONG)(regvalue & 0xffffffff));
            if (regindex % 3 == 2) {
                lprintfs("\r\n");
            } else {
                lprintfs(" ");
            }

        } else {

            lprintfs("%4s=%08lx%c",
                    pszReg[regnumber],
                    (ULONG)(regvalue & 0xfffffff),
                    NeedUpper(regvalue) ? '*' : ' ' );
            if (regindex % 5 == 4) {
                lprintfs("\r\n");
            } else {
                lprintfs(" ");
            }

        }
    }

    //
    // print out the fpcr as 64 bits regardless,
    // and the FIR and Fpcr's - assuming we know they follow
    // the floating and integer registers.
    //

    regnumber = GetIntRegNumber(34);    // Fir
    lprintfs("%4s=%08lx\r\n", pszReg[regnumber],
                           (ULONG)GetRegValue(dp, regnumber));

    regnumber = GetIntRegNumber(35);    // Psr
    lprintfs("%4s=%08lx\r\n", pszReg[regnumber],
                           (ULONG)GetRegValue(dp, regnumber));

    lprintfs("mode=%1lx ie=%1lx irql=%1lx \r\n",
                (ULONG)GetRegFlagValue(dp, FLAGMODE),
                (ULONG)GetRegFlagValue(dp, FLAGIE),
                (ULONG)GetRegFlagValue(dp, FLAGIRQL));
}

/*** OutputOneReg - output one register value
*
*   Purpose:
*       Function for the "r <regname>" command.
*
*       Output the value for the specified register or flag.
*
*   Input:
*       regnum - register or flag specification
*
*   Output:
*       None.
*
*************************************************************************/

VOID
OutputOneReg (
    PDEBUGPACKET dp,
    ULONG regnum,
    BOOLEAN Show64
    )
{
    DWORDLONG value;

    value = GetRegFlagValue(dp, regnum);
    if (regnum >= FLAGBASE) {
        lprintfs("%lx\r\n", (ULONG)value);
    } else if (Show64) {
        lprintfs("%08lx %08lx\r\n", (ULONG)(value >> 32), (ULONG)(value & 0xffffffff));
    } else {
        lprintfs("%08lx%s\r\n", (ULONG)value, NeedUpper(value)?"*":"");
    }
}

PUCHAR RegNameFromIndex (ULONG index)
{
    return pszReg[index];
}


REGS.H

#define FLTBASE   0
#define REGBASE    32          // offset of integer registers
#define FLAGBASE   FLAGMODE
#define PREGBASE   PREGEA

enum {

    REGFPCR = 64, REGSOFTPCR,
    REGFIR, REGPSR,

    FLAGMODE, FLAGIE, FLAGIRQL,
    FLAGINT5, FLAGINT4, FLAGINT3, FLAGINT2, FLAGINT1, FLAGINT0,

    FLAGFPC,

// Pseudo registers

    PREGEA, PREGEXP, PREGRA, PREGP,
    PREGU0, PREGU1,  PREGU2, PREGU3, PREGU4,
    PREGU5, PREGU6,  PREGU7, PREGU8, PREGU9,
    PREGU10, PREGU11, PREGU12
    };

//
// This union is used to convert between doubles, quads and
// large integers.
//
typedef union _CONVERTED_DOUBLE {
    double d;
    ULONG ul[2];
    LARGE_INTEGER li;
} CONVERTED_DOUBLE, *PCONVERTED_DOUBLE;


STRINGS.H

/*
**  STRINGS.H
**
**  This file contains all strings which are used in the EM for display
**  purposes.  This is done for internationalization purposes.
**
**  strings.c defines DEFINE_STRINGS before including this file.  Other
**  source files just include this file normally.
*/

/*
**  Modified by Miche Baker-Harvey for Alpha - September 12, 1992
**  Copyright 1992 Digital Equipment Corporation
*/

/*
**  strings.c should define DEFINE_STRINGS before including this file,
**  so that the strings will be defined rather than just declared.
*/

#ifdef DEFINE_STRINGS
#define DECL_STR(name, value)   char name[] = value
#undef  DEFINE_STRINGS
#else
#define DECL_STR(name, value)   extern char name[]
#endif

//
// The floating point registers
//

DECL_STR(   szF0  , "f0");
DECL_STR(   szF1  , "f1");
DECL_STR(   szF2  , "f2");
DECL_STR(   szF3  , "f3");
DECL_STR(   szF4  , "f4");
DECL_STR(   szF5  , "f5");
DECL_STR(   szF6  , "f6");
DECL_STR(   szF7  , "f7");
DECL_STR(   szF8  , "f8");
DECL_STR(   szF9  , "f9");
DECL_STR(   szF10 , "f10");
DECL_STR(   szF11 , "f11");
DECL_STR(   szF12 , "f12");
DECL_STR(   szF13 , "f13");
DECL_STR(   szF14 , "f14");
DECL_STR(   szF15 , "f15");
DECL_STR(   szF16 , "f16");
DECL_STR(   szF17 , "f17");
DECL_STR(   szF18 , "f18");
DECL_STR(   szF19 , "f19");
DECL_STR(   szF20 , "f20");
DECL_STR(   szF21 , "f21");
DECL_STR(   szF22 , "f22");
DECL_STR(   szF23 , "f23");
DECL_STR(   szF24 , "f24");
DECL_STR(   szF25 , "f25");
DECL_STR(   szF26 , "f26");
DECL_STR(   szF27 , "f27");
DECL_STR(   szF28 , "f28");
DECL_STR(   szF29 , "f29");
DECL_STR(   szF30 , "f30");
DECL_STR(   szF31 , "f31");

//
// The integer registers
//

DECL_STR(   szR0  , V0_REG_STR);
DECL_STR(   szR1  , T0_REG_STR);
DECL_STR(   szR2  , T1_REG_STR);
DECL_STR(   szR3  , T2_REG_STR);
DECL_STR(   szR4  , T3_REG_STR);
DECL_STR(   szR5  , T4_REG_STR);
DECL_STR(   szR6  , T5_REG_STR);
DECL_STR(   szR7  , T6_REG_STR);
DECL_STR(   szR8  , T7_REG_STR);
DECL_STR(   szR9  , S0_REG_STR);
DECL_STR(   szR10 , S1_REG_STR);
DECL_STR(   szR11 , S2_REG_STR);
DECL_STR(   szR12 , S3_REG_STR);
DECL_STR(   szR13 , S4_REG_STR);
DECL_STR(   szR14 , S5_REG_STR);
DECL_STR(   szR15 , FP_REG_STR);
DECL_STR(   szR16 , A0_REG_STR);
DECL_STR(   szR17 , A1_REG_STR);
DECL_STR(   szR18 , A2_REG_STR);
DECL_STR(   szR19 , A3_REG_STR);
DECL_STR(   szR20 , A4_REG_STR);
DECL_STR(   szR21 , A5_REG_STR);
DECL_STR(   szR22 , T8_REG_STR);
DECL_STR(   szR23 , T9_REG_STR);
DECL_STR(   szR24 , T10_REG_STR);
DECL_STR(   szR25 , T11_REG_STR);
DECL_STR(   szR26 , RA_REG_STR);
DECL_STR(   szR27 , T12_REG_STR);
DECL_STR(   szR28 , AT_REG_STR);
DECL_STR(   szR29 , GP_REG_STR);
DECL_STR(   szR30 , SP_REG_STR);
DECL_STR(   szR31 , ZERO_REG_STR);

//
// ALPHA other accessible registers
//

DECL_STR(   szFpcr , "fpcr");      // floating point control register
DECL_STR(   szSoftFpcr , "SoftFpcr");      // floating point control register
DECL_STR(   szFir  , "fir");       // fetched/faulting instruction: nextPC
DECL_STR(   szPsr  , "psr");       // processor status register: see flags

//
// these flags are associated with the psr);
// defined in ntalpha.h.
DECL_STR(   szFlagMode  , "mode");        // mode: 1? user : system
DECL_STR(   szFlagIe    , "ie");          // interrupt enable
DECL_STR(   szFlagIrql  , "irql");        // IRQL level: 3 bits
DECL_STR(   szFlagInt5  , "int5");
DECL_STR(   szFlagInt4  , "int4");
DECL_STR(   szFlagInt3  , "int3");
DECL_STR(   szFlagInt2  , "int2");
DECL_STR(   szFlagInt1  , "int1");
DECL_STR(   szFlagInt0  , "int0");

DECL_STR(    szEaPReg   , "$ea");
DECL_STR(    szExpPReg  , "$exp");
DECL_STR(    szRaPReg   , "$ra");
DECL_STR(    szPPReg    , "$p");

DECL_STR(    szGPReg    , "$gp");

DECL_STR(    szU0Preg   , "$u0");
DECL_STR(    szU1Preg   , "$u1");
DECL_STR(    szU2Preg   , "$u2");
DECL_STR(    szU3Preg   , "$u3");
DECL_STR(    szU4Preg   , "$u4");
DECL_STR(    szU5Preg   , "$u5");
DECL_STR(    szU6Preg   , "$u6");
DECL_STR(    szU7Preg   , "$u7");
DECL_STR(    szU8Preg   , "$u8");
DECL_STR(    szU9Preg   , "$u9");

//
// Thread states
//

DECL_STR(SzFrozen,      "Frozen");
DECL_STR(SzSuspended,   "Suspended");
DECL_STR(SzBlocked,     "Blocked");

DECL_STR(SzRunnable,    "Runnable");
DECL_STR(SzRunning,     "Running");
DECL_STR(SzStopped,     "Stopped");
DECL_STR(SzExiting,     "Exiting");
DECL_STR(SzDead,        "Dead");
DECL_STR(SzUnknown,     "UNKNOWN");

DECL_STR(SzExcept1st,   "Except1st");
DECL_STR(SzExcept2nd,   "Except2nd");
DECL_STR(SzRipped,      "RIP");

DECL_STR(SzCritSec,     "CritSec");

DECL_STR(SzStandard,    "Standard");

//
// taken from alphaops.h, and munged with emacs
//

DECL_STR( szLda, LDA_OP_STR );
DECL_STR( szLdah, LDAH_OP_STR );
DECL_STR( szLdq_u, LDQ_U_OP_STR );
DECL_STR( szStq_u, STQ_U_OP_STR );
DECL_STR( szLdf, LDF_OP_STR );
DECL_STR( szLdg, LDG_OP_STR );
DECL_STR( szLds, LDS_OP_STR );
DECL_STR( szLdt, LDT_OP_STR );
DECL_STR( szStf, STF_OP_STR );
DECL_STR( szStg, STG_OP_STR );
DECL_STR( szSts, STS_OP_STR );
DECL_STR( szStt, STT_OP_STR );
DECL_STR( szLdl, LDL_OP_STR );
DECL_STR( szLdq, LDQ_OP_STR );
DECL_STR( szLdl_l, LDL_L_OP_STR );
DECL_STR( szLdq_l, LDQ_L_OP_STR );
DECL_STR( szStl, STL_OP_STR );
DECL_STR( szStq, STQ_OP_STR );
DECL_STR( szStl_c, STL_C_OP_STR );
DECL_STR( szStq_c, STQ_C_OP_STR );
DECL_STR( szBr, BR_OP_STR );
DECL_STR( szFbeq, FBEQ_OP_STR );
DECL_STR( szFblt, FBLT_OP_STR );
DECL_STR( szFble, FBLE_OP_STR );
DECL_STR( szBsr, BSR_OP_STR );
DECL_STR( szFbne, FBNE_OP_STR );
DECL_STR( szFbge, FBGE_OP_STR );
DECL_STR( szFbgt, FBGT_OP_STR );
DECL_STR( szBlbc, BLBC_OP_STR );
DECL_STR( szBeq, BEQ_OP_STR );
DECL_STR( szBlt, BLT_OP_STR );
DECL_STR( szBle, BLE_OP_STR );
DECL_STR( szBlbs, BLBS_OP_STR );
DECL_STR( szBne, BNE_OP_STR );
DECL_STR( szBge, BGE_OP_STR );
DECL_STR( szBgt, BGT_OP_STR );
DECL_STR( szMb, MB_FUNC_STR );
DECL_STR( szWmb, MB1_FUNC_STR );
DECL_STR( szMb2, MB2_FUNC_STR );
DECL_STR( szMb3, MB3_FUNC_STR );
DECL_STR( szFetch, FETCH_FUNC_STR );
DECL_STR( szRs, RS_FUNC_STR );
DECL_STR( szTrapb, TRAPB_FUNC_STR );
DECL_STR( szExcb, EXCB_FUNC_STR );
DECL_STR( szFetch_m, FETCH_M_FUNC_STR );
DECL_STR( szRpcc, RPCC_FUNC_STR );
DECL_STR( szRc, RC_FUNC_STR );
DECL_STR( szJmp, JMP_FUNC_STR );
DECL_STR( szJsr, JSR_FUNC_STR );
DECL_STR( szRet, RET_FUNC_STR );
DECL_STR( szJsr_co, JSR_CO_FUNC_STR );

DECL_STR( szAddl, ADDL_FUNC_STR );
DECL_STR( szAddlv, ADDLV_FUNC_STR );
DECL_STR( szS4addl, S4ADDL_FUNC_STR );
DECL_STR( szS8addl, S8ADDL_FUNC_STR );
DECL_STR( szAddq, ADDQ_FUNC_STR );
DECL_STR( szAddqv, ADDQV_FUNC_STR );
DECL_STR( szS4addq, S4ADDQ_FUNC_STR );
DECL_STR( szS8addq, S8ADDQ_FUNC_STR );
DECL_STR( szSubl, SUBL_FUNC_STR );
DECL_STR( szSublv, SUBLV_FUNC_STR );
DECL_STR( szS4subl, S4SUBL_FUNC_STR );
DECL_STR( szS8subl, S8SUBL_FUNC_STR );
DECL_STR( szSubq, SUBQ_FUNC_STR );
DECL_STR( szSubqv, SUBQV_FUNC_STR );
DECL_STR( szS4subq, S4SUBQ_FUNC_STR );
DECL_STR( szS8subq, S8SUBQ_FUNC_STR );

DECL_STR( szCmpeq, CMPEQ_FUNC_STR );
DECL_STR( szCmplt, CMPLT_FUNC_STR );
DECL_STR( szCmple, CMPLE_FUNC_STR );
DECL_STR( szCmpult, CMPULT_FUNC_STR );
DECL_STR( szCmpule, CMPULE_FUNC_STR );
DECL_STR( szCmpbge, CMPBGE_FUNC_STR );
DECL_STR( szAnd, AND_FUNC_STR );
DECL_STR( szBic, BIC_FUNC_STR );
DECL_STR( szBis, BIS_FUNC_STR );
DECL_STR( szEqv, EQV_FUNC_STR );
DECL_STR( szOrnot, ORNOT_FUNC_STR );
DECL_STR( szXor, XOR_FUNC_STR );
DECL_STR( szCmoveq, CMOVEQ_FUNC_STR );
DECL_STR( szCmovge, CMOVGE_FUNC_STR );
DECL_STR( szCmovgt, CMOVGT_FUNC_STR );
DECL_STR( szCmovlbc, CMOVLBC_FUNC_STR );
DECL_STR( szCmovlbs, CMOVLBS_FUNC_STR );
DECL_STR( szCmovle, CMOVLE_FUNC_STR );
DECL_STR( szCmovlt, CMOVLT_FUNC_STR );
DECL_STR( szCmovne, CMOVNE_FUNC_STR );
DECL_STR( szSll, SLL_FUNC_STR );
DECL_STR( szSrl, SRL_FUNC_STR );
DECL_STR( szSra, SRA_FUNC_STR );
DECL_STR( szExtbl, EXTBL_FUNC_STR );
DECL_STR( szExtwl, EXTWL_FUNC_STR );
DECL_STR( szExtll, EXTLL_FUNC_STR );
DECL_STR( szExtql, EXTQL_FUNC_STR );
DECL_STR( szExtwh, EXTWH_FUNC_STR );
DECL_STR( szExtlh, EXTLH_FUNC_STR );
DECL_STR( szExtqh, EXTQH_FUNC_STR );
DECL_STR( szInsbl, INSBL_FUNC_STR );
DECL_STR( szInswl, INSWL_FUNC_STR );
DECL_STR( szInsll, INSLL_FUNC_STR );
DECL_STR( szInsql, INSQL_FUNC_STR );
DECL_STR( szInswh, INSWH_FUNC_STR );
DECL_STR( szInslh, INSLH_FUNC_STR );
DECL_STR( szInsqh, INSQH_FUNC_STR );
DECL_STR( szMskbl, MSKBL_FUNC_STR );
DECL_STR( szMskwl, MSKWL_FUNC_STR );
DECL_STR( szMskll, MSKLL_FUNC_STR );
DECL_STR( szMskql, MSKQL_FUNC_STR );
DECL_STR( szMskwh, MSKWH_FUNC_STR );
DECL_STR( szMsklh, MSKLH_FUNC_STR );
DECL_STR( szMskqh, MSKQH_FUNC_STR );
DECL_STR( szZap, ZAP_FUNC_STR );
DECL_STR( szZapnot, ZAPNOT_FUNC_STR );
DECL_STR( szMull, MULL_FUNC_STR );
DECL_STR( szMullv, MULLV_FUNC_STR );
DECL_STR( szMulq, MULQ_FUNC_STR );
DECL_STR( szMulqv, MULQV_FUNC_STR );
DECL_STR( szUmulh, UMULH_FUNC_STR );
DECL_STR( szCvtlq, CVTLQ_FUNC_STR );
DECL_STR( szCpys, CPYS_FUNC_STR );
DECL_STR( szCpysn, CPYSN_FUNC_STR );
DECL_STR( szCpyse, CPYSE_FUNC_STR );
DECL_STR( szMt_fpcr, MT_FPCR_FUNC_STR );
DECL_STR( szMf_fpcr, MF_FPCR_FUNC_STR );
DECL_STR( szFcmoveq, FCMOVEQ_FUNC_STR );
DECL_STR( szFcmovne, FCMOVNE_FUNC_STR );
DECL_STR( szFcmovlt, FCMOVLT_FUNC_STR );
DECL_STR( szFcmovge, FCMOVGE_FUNC_STR );
DECL_STR( szFcmovle, FCMOVLE_FUNC_STR );
DECL_STR( szFcmovgt, FCMOVGT_FUNC_STR );
DECL_STR( szCvtql, CVTQL_FUNC_STR );
DECL_STR( szCvtqlv, CVTQLV_FUNC_STR );
DECL_STR( szCvtqlsv, CVTQLSV_FUNC_STR );
DECL_STR( szAdds, ADDS_FUNC_STR );
DECL_STR( szSubs, SUBS_FUNC_STR );
DECL_STR( szMuls, MULS_FUNC_STR );
DECL_STR( szDivs, DIVS_FUNC_STR );
DECL_STR( szAddt, ADDT_FUNC_STR );
DECL_STR( szSubt, SUBT_FUNC_STR );
DECL_STR( szMult, MULT_FUNC_STR );
DECL_STR( szDivt, DIVT_FUNC_STR );
DECL_STR( szCmptun, CMPTUN_FUNC_STR );
DECL_STR( szCmpteq, CMPTEQ_FUNC_STR );
DECL_STR( szCmptlt, CMPTLT_FUNC_STR );
DECL_STR( szCmptle, CMPTLE_FUNC_STR );
DECL_STR( szCvtts, CVTTS_FUNC_STR );
DECL_STR( szCvttq, CVTTQ_FUNC_STR );
DECL_STR( szCvtqs, CVTQS_FUNC_STR );
DECL_STR( szCvtqt, CVTQT_FUNC_STR );
DECL_STR( szCvtst, CVTST_FUNC_STR );
DECL_STR( szCvtsts, CVTST_S_FUNC_STR );
DECL_STR( szAddf, ADDF_FUNC_STR );
DECL_STR( szCvtdg, CVTDG_FUNC_STR );
DECL_STR( szAddg, ADDG_FUNC_STR );
DECL_STR( szCmpgeq, CMPGEQ_FUNC_STR );
DECL_STR( szCmpglt, CMPGLT_FUNC_STR );
DECL_STR( szCmpgle, CMPGLE_FUNC_STR );
DECL_STR( szCvtgf, CVTGF_FUNC_STR );
DECL_STR( szCvtgd, CVTGD_FUNC_STR );
DECL_STR( szCvtqf, CVTQF_FUNC_STR );
DECL_STR( szCvtqg, CVTQG_FUNC_STR );
DECL_STR( szDivf, DIVF_FUNC_STR );
DECL_STR( szDivg, DIVG_FUNC_STR );
DECL_STR( szMulf, MULF_FUNC_STR );
DECL_STR( szMulg, MULG_FUNC_STR );
DECL_STR( szSubf, SUBF_FUNC_STR );
DECL_STR( szSubg, SUBG_FUNC_STR );
DECL_STR( szCvtgq, CVTGQ_FUNC_STR );
DECL_STR( szC, C_FLAGS_STR );
DECL_STR( szM, M_FLAGS_STR );
DECL_STR( szNone, NONE_FLAGS_STR );
DECL_STR( szD, D_FLAGS_STR );
DECL_STR( szUc, UC_FLAGS_STR );
DECL_STR( szVc, VC_FLAGS_STR );
DECL_STR( szUm, UM_FLAGS_STR );
DECL_STR( szVm, VM_FLAGS_STR );
DECL_STR( szU, U_FLAGS_STR );
DECL_STR( szV, V_FLAGS_STR );
DECL_STR( szUd, UD_FLAGS_STR );
DECL_STR( szVd, VD_FLAGS_STR );
DECL_STR( szSc, SC_FLAGS_STR );
DECL_STR( szS, S_FLAGS_STR );
DECL_STR( szSuc, SUC_FLAGS_STR );
DECL_STR( szSvc, SVC_FLAGS_STR );
DECL_STR( szSum, SUM_FLAGS_STR );
DECL_STR( szSvm, SVM_FLAGS_STR );
DECL_STR( szSu, SU_FLAGS_STR );
DECL_STR( szSv, SV_FLAGS_STR );
DECL_STR( szSud, SUD_FLAGS_STR );
DECL_STR( szSvd, SVD_FLAGS_STR );
DECL_STR( szSuic, SUIC_FLAGS_STR );
DECL_STR( szSvic, SVIC_FLAGS_STR );
DECL_STR( szSuim, SUIM_FLAGS_STR );
DECL_STR( szSvim, SVIM_FLAGS_STR );
DECL_STR( szSui, SUI_FLAGS_STR );
DECL_STR( szSvi, SVI_FLAGS_STR );
DECL_STR( szSuid, SUID_FLAGS_STR );
DECL_STR( szSvid, SVID_FLAGS_STR );

DECL_STR( szBpt, BPT_FUNC_STR );
DECL_STR( szCallsys, CALLSYS_FUNC_STR );
DECL_STR( szImb, IMB_FUNC_STR );
DECL_STR( szRdteb, RDTEB_FUNC_STR );
DECL_STR( szGentrap, GENTRAP_FUNC_STR );
DECL_STR( szKbpt, KBPT_FUNC_STR );
DECL_STR( szCallKD, CALLKD_FUNC_STR );
DECL_STR( szHalt, HALT_FUNC_STR );
DECL_STR( szRestart, RESTART_FUNC_STR );
DECL_STR( szDraina, DRAINA_FUNC_STR );
DECL_STR( szInitpal, INITPAL_FUNC_STR );
DECL_STR( szWrentry, WRENTRY_FUNC_STR );
DECL_STR( szSwpirql, SWPIRQL_FUNC_STR );
DECL_STR( szRdirql, RDIRQL_FUNC_STR );
DECL_STR( szDi, DI_FUNC_STR );
DECL_STR( szEi, EI_FUNC_STR );
DECL_STR( szSwppal, SWPPAL_FUNC_STR );
DECL_STR( szSsir, SSIR_FUNC_STR );
DECL_STR( szCsir, CSIR_FUNC_STR );
DECL_STR( szRfe, RFE_FUNC_STR );
DECL_STR( szRetsys, RETSYS_FUNC_STR );
DECL_STR( szSwpctx, SWPCTX_FUNC_STR );
DECL_STR( szSwpprocess, SWPPROCESS_FUNC_STR );
DECL_STR( szRdmces, RDMCES_FUNC_STR );
DECL_STR( szWrmces, WRMCES_FUNC_STR );
DECL_STR( szTbia, TBIA_FUNC_STR );
DECL_STR( szTbis, TBIS_FUNC_STR );
DECL_STR( szDtbis, DTBIS_FUNC_STR );
DECL_STR( szRdksp, RDKSP_FUNC_STR );
DECL_STR( szSwpksp, SWPKSP_FUNC_STR );
DECL_STR( szRdpsr, RDPSR_FUNC_STR );
DECL_STR( szRdpcr, RDPCR_FUNC_STR );
DECL_STR( szRdthread, RDTHREAD_FUNC_STR );
DECL_STR( szRdcounters, RDCOUNTERS_FUNC_STR );
DECL_STR( szRdstate, RDSTATE_FUNC_STR );
DECL_STR( szInitpcr, INITPCR_FUNC_STR );
DECL_STR( szWrperfmon, WRPERFMON_FUNC_STR );
DECL_STR( szMt, MTPR_OP_STR );
DECL_STR( szMf, MFPR_OP_STR );
DECL_STR( szHwld, HWLD_OP_STR );
DECL_STR( szHwst, HWST_OP_STR );
DECL_STR( szRei, REI_OP_STR );


CONTEXT.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    context.c

Abstract:

    This file provides access to thread context information.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"

void
GetContextForThread( PDEBUGPACKET dp )
{
    PTHREADCONTEXT ptctx = dp->tctx;

    memset(&ptctx->context,0,sizeof(CONTEXT));

    ptctx->context.ContextFlags = CONTEXT_FULL |
                                  CONTEXT_FLOATING_POINT |
                                  CONTEXT_DEBUG_REGISTERS;

    if (!GetThreadContext( ptctx->hThread, &ptctx->context )) {
        lprintfs( ">>>>> GetThreadContext failed: err(%d), hthread(0x%x)\n",
                  GetLastError(), ptctx->hThread );
        ptctx->pc = 0;
        ptctx->frame = 0;
        ptctx->stack = 0;
    }
    else {
        ptctx->pc = ptctx->context.Eip;
        ptctx->frame = ptctx->context.Ebp;
        ptctx->stack = ptctx->context.Esp;
    }

    return;
}


DISASM.C

/*++

Copyright 1993 - 1998 Microsoft Corporation

Module Name:

    disasm.c

Abstract:

    This file provides support disassembly ( x86 ).

Author:

    Gerd Immeyer       19-Oct-1989
    Wesley Witt (wesw) 1-May-1993    ( ported from ntsd to drwatson)

Environment:

    User Mode

--*/

#include <windows.h>
#include <stddef.h>
#include <string.h>
#include "regs.h"
#include "disasm.h"
#include "drwatson.h"
#include "proto.h"

/*****                     macros and defines                          *****/

#define BIT20(b) (b & 0x07)
#define BIT53(b) (b >> 3 & 0x07)
#define BIT76(b) (b >> 6 & 0x03)
#define MAXL     16
#define MAXOPLEN 10

#define OBOFFSET 26
#define OBOPERAND 34
#define OBLINEEND 77

/*****                     static tables and variables                 *****/

static char regtab[] = "alcldlblahchdhbhaxcxdxbxspbpsidi";  /* reg table */
static char *mrmtb16[] = { "bx+si",  /* modRM string table (16-bit) */
                           "bx+di",
                           "bp+si",
                           "bp+di",
                           "si",
                           "di",
                           "bp",
                           "bx"
                         };

static char *mrmtb32[] = { "eax",       /* modRM string table (32-bit) */
                           "ecx",
                           "edx",
                           "ebx",
                           "esp",
                           "ebp",
                           "esi",
                           "edi"
                         };

static char seg16[8]   = { REGDS,  REGDS,  REGSS,  REGSS,
                           REGDS,  REGDS,  REGSS,  REGDS };
static char reg16[8]   = { REGEBX, REGEBX, REGEBP, REGEBP,
                           REGESI, REGEDI, REGEBP, REGEBX };
static char reg16_2[4] = { REGESI, REGEDI, REGESI, REGEDI };

static char seg32[8]   = { REGDS,  REGDS,  REGDS,  REGDS,
                           REGSS,  REGSS,  REGDS,  REGDS };
static char reg32[8]   = { REGEAX, REGECX, REGEDX, REGEBX,
                           REGESP, REGEBP, REGESI, REGEDI };

static char sregtab[] = "ecsdfg";  // first letter of ES, CS, SS, DS, FS, GS

char    hexdigit[] = { '0', '1', '2', '3', '4', '5', '6', '7',
                       '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

static int              mod;            /* mod of mod/rm byte */
static int              rm;             /* rm of mod/rm byte */
static int              ttt;            /* return reg value (of mod/rm) */
static unsigned char    *pMem;          /* current position in instruction */
static int              mode_32;        /* local addressing mode indicator */
static int              opsize_32;      /* operand size flag */

ULONG                   EAaddr[2];      //  offset of effective address
static int              EAsize[2];      //  size of effective address item
static char             *pchEAseg[2];   //  normal segment for operand

int                     G_mode_32 = 1;  /* global address mode indicator */

static BOOLEAN          fMovX;          // indicates a MOVSX or MOVZX

//      internal function definitions

BOOLEAN disasm(PDEBUGPACKET, PULONG, PUCHAR, BOOLEAN);
void DIdoModrm(PDEBUGPACKET dp,char **, int, BOOLEAN);

void OutputHexString(char **, char *, int);
void OutputHexValue(char **, char *, int, int);
void OutputHexCode(char **, char *, int);
void OutputString(char **, char *);
void OutputSymbol(PDEBUGPACKET, char **, char *, int, int);

void GetNextOffset(PDEBUGPACKET, PULONG, BOOLEAN);
void OutputHexAddr(PUCHAR *, ULONG);
USHORT GetSegRegValue(PDEBUGPACKET,int);

/**** disasm - disassemble an 80x86/80x87 instruction
*
*  Input:
*       pOffset = pointer to offset to start disassembly
*       fEAout = if set, include EA (effective address)
*
*  Output:
*       pOffset = pointer to offset of next instruction
*       pchDst = pointer to result string
*
***************************************************************************/

BOOLEAN
disasm( PDEBUGPACKET dp, PULONG pOffset, PUCHAR pchDst, BOOLEAN fEAout )
{
    int     opcode;                     /* current opcode */
    int     olen = 2;                   /* operand length */
    int     alen = 2;                   /* address length */
    int     end = FALSE;                /* end of instruction flag */
    int     mrm = FALSE;                /* indicator that modrm is generated*/
    unsigned char *action;              /* action for operand interpretation*/
    long    tmp;                        /* temporary storage field */
    int     indx;                       /* temporary index */
    int     action2;                    /* secondary action */
    int     instlen;                    /* instruction length */
    int     cBytes;                     //  bytes read into instr buffer
    int     segOvr = 0;                 /* segment override opcode */
    char    membuf[MAXL];               /* current instruction buffer */
    char    *pEAlabel = "";             //  optional label for operand

    char    *pchResultBuf = pchDst;     //  working copy of pchDst pointer
    char    RepPrefixBuffer[32];        //  rep prefix buffer
    char    *pchRepPrefixBuf = RepPrefixBuffer; //  pointer to prefix buffer
    char    OpcodeBuffer[8];            //  opcode buffer
    char    *pchOpcodeBuf = OpcodeBuffer; //  pointer to opcode buffer
    char    OperandBuffer[80];          //  operand buffer
    char    *pchOperandBuf = OperandBuffer; //  pointer to operand buffer
    char    ModrmBuffer[80];            //  modRM buffer
    char    *pchModrmBuf = ModrmBuffer; //  pointer to modRM buffer
    char    EABuffer[42];               //  effective address buffer
    char    *pchEABuf = EABuffer;       //  pointer to EA buffer

    int     obOpcode = OBOFFSET;
    int     obOpcodeMin;
    int     obOpcodeMax;

    int     obOperand = OBOPERAND;
    int     obOperandMin;
    int     obOperandMax;

    int     cbOpcode;
    int     cbOperand;
    int     cbOffset;
    int     cbEAddr;

    int     fTwoLines = FALSE;

    fMovX = FALSE;
    EAsize[0] = EAsize[1] = 0;          //  no effective address
    pchEAseg[0] = dszDS_;
    pchEAseg[1] = dszES_;

    mode_32 = opsize_32 = (G_mode_32 == 1); /* local addressing mode */
    olen = alen = (1 + mode_32) << 1;   //  set operand/address lengths
                                        //  2 for 16-bit and 4 for 32-bit

    OutputHexAddr(&pchResultBuf, *pOffset);

    *pchResultBuf++ = ' ';

    DoMemoryRead( dp,
                  (LPVOID)*pOffset,
                  (LPVOID)membuf,
                  MAXL,
                  &cBytes
                );

                                        /* move full inst to local buffer */
    pMem = membuf;                      /* point to begin of instruction */
    opcode = *pMem++;                   /* get opcode */
    OutputString(&pchOpcodeBuf, distbl[opcode].instruct);
    action = actiontbl + distbl[opcode].opr; /* get operand action */

/*****          loop through all operand actions               *****/

    do {
        action2 = (*action) & 0xc0;
        switch((*action++) & 0x3f) {
            case ALT:                   /* alter the opcode if 32-bit */
                if (opsize_32) {
                    indx = *action++;
                    pchOpcodeBuf = &OpcodeBuffer[indx];
                    if (indx == 0)
                        OutputString(&pchOpcodeBuf, dszCWDE);
                    else {
                        *pchOpcodeBuf++ = 'd';
                        if (indx == 1)
                            *pchOpcodeBuf++ = 'q';
                        }
                    }
                break;

            case STROP:
                //  compute size of operands in indx
                //  also if dword operands, change fifth
                //  opcode letter from 'w' to 'd'.

                if (opcode & 1) {
                    if (opsize_32) {
                        indx = 4;
                        OpcodeBuffer[4] = 'd';
                        }
                    else
                        indx = 2;
                    }
                else
                    indx = 1;

                if (*action & 1) {
                    if (fEAout) {
                        EAaddr[0] = (DWORD)GetRegValue(dp, REGESI);
                        EAsize[0] = indx;
                        }
                    }
                if (*action++ & 2) {
                    if (fEAout) {
                        EAaddr[1] = (DWORD)GetRegValue(dp, REGEDI);
                        EAsize[1] = indx;
                        }
                    }
                break;

            case CHR:                   /* insert a character */
                *pchOperandBuf++ = *action++;
                break;

            case CREG:                  /* set debug, test or control reg */
                if ((opcode - 231) & 0x04)      //  remove bias from opcode
                    *pchOperandBuf++ = 't';
                else if ((opcode - 231) & 0x01)
                    *pchOperandBuf++ = 'd';
                else
                    *pchOperandBuf++ = 'c';
                *pchOperandBuf++ = 'r';
                *pchOperandBuf++ = (char)('0' + ttt);
                break;

            case SREG2:                 /* segment register */
                ttt = BIT53(opcode);    //  set value to fall through

            case SREG3:                 /* segment register */
                *pchOperandBuf++ = sregtab[ttt];  // reg is part of modrm
                *pchOperandBuf++ = 's';
                break;

            case BRSTR:                 /* get index to register string */
                ttt = *action++;        /*    from action table */
                goto BREGlabel;

            case BOREG:                 /* byte register (in opcode) */
                ttt = BIT20(opcode);    /* register is part of opcode */
                goto BREGlabel;

            case ALSTR:
                ttt = 0;                /* point to AL register */
BREGlabel:
            case BREG:                  /* general register */
                *pchOperandBuf++ = regtab[ttt * 2];
                *pchOperandBuf++ = regtab[ttt * 2 + 1];
                break;

            case WRSTR:                 /* get index to register string */
                ttt = *action++;        /*    from action table */
                goto WREGlabel;

            case VOREG:                 /* register is part of opcode */
                ttt = BIT20(opcode);
                goto VREGlabel;

            case AXSTR:
                ttt = 0;                /* point to eAX register */
VREGlabel:
            case VREG:                  /* general register */
                if (opsize_32)          /* test for 32bit mode */
                    *pchOperandBuf++ = 'e';
WREGlabel:
            case WREG:                  /* register is word size */
                *pchOperandBuf++ = regtab[ttt * 2 + 16];
                *pchOperandBuf++ = regtab[ttt * 2 + 17];
                break;

            case IST_ST:
                OutputString(&pchOperandBuf, "st(0),st");
                *(pchOperandBuf - 5) += rm;
                break;

            case ST_IST:
                OutputString(&pchOperandBuf, "st,");
            case IST:
                OutputString(&pchOperandBuf, "st(0)");
                *(pchOperandBuf - 2) += rm;
                break;

            case xBYTE:                 /* set instruction to byte only */
                EAsize[0] = 1;
                pEAlabel = "byte ptr ";
                break;

            case VAR:
                if (opsize_32)
                    goto DWORDlabel;

            case xWORD:
                EAsize[0] = 2;
                pEAlabel = "word ptr ";
                break;

            case EDWORD:
                opsize_32 = 1;    //  for control reg move, use eRegs
            case xDWORD:
DWORDlabel:
                EAsize[0] = 4;
                pEAlabel = "dword ptr ";
                break;

            case QWORD:
                EAsize[0] = 8;
                pEAlabel = "qword ptr ";
                break;

            case TTBYTE:
                EAsize[0] = 10;
                pEAlabel = "tbyte ptr ";
                break;

            case FARPTR:
                if (opsize_32) {
                    EAsize[0] = 6;
                    pEAlabel = "fword ptr ";
                    }
                else {
                    EAsize[0] = 4;
                    pEAlabel = "dword ptr ";
                    }
                break;

            case LMODRM:                //  output modRM data type
                if (mod != 3)
                    OutputString(&pchOperandBuf, pEAlabel);
                else
                    EAsize[0] = 0;

            case MODRM:                 /* output modrm string */
                if (segOvr)             /* in case of segment override */
                    OutputString(&pchOperandBuf, distbl[segOvr].instruct);
                *pchModrmBuf = '\0';
                OutputString(&pchOperandBuf, ModrmBuffer);
                break;

            case ADDRP:                 /* address pointer */
                OutputHexString(&pchOperandBuf, pMem + olen, 2); // segment
                *pchOperandBuf++ = ':';
                OutputSymbol(dp, &pchOperandBuf, pMem, olen, segOvr);
                pMem += olen + 2;
                break;

            case REL8:                  /* relative address 8-bit */
                if (opcode == 0xe3 && mode_32) {
                    pchOpcodeBuf = OpcodeBuffer;
                    OutputString(&pchOpcodeBuf, dszJECXZ);
                    }
                tmp = (long)*(char *)pMem++; /* get the 8-bit rel offset */
                goto DoRelDispl;

            case REL16:                 /* relative address 16-/32-bit */
                tmp = 0;
                memmove(&tmp,pMem,sizeof(long));
                pMem += alen;           /* skip over offset */
DoRelDispl:
                tmp += *pOffset + (pMem - membuf); /* calculate address */
                OutputSymbol(dp, &pchOperandBuf, (char *) &tmp, alen, segOvr);
                                                   // address
                break;

            case UBYTE:                 //  unsigned byte for int/in/out
                OutputHexString(&pchOperandBuf, pMem, 1);  //  ubyte
                pMem++;
                break;

            case IB:                    /* operand is immediate byte */
                if ((opcode & ~1) == 0xd4) {  // postop for AAD/AAM is 0x0a
                    if (*pMem++ != 0x0a) // test post-opcode byte
                        OutputString(&pchOperandBuf, dszRESERVED);
                    break;
                    }
                olen = 1;               /* set operand length */
                goto DoImmed;

            case IW:                    /* operand is immediate word */
                olen = 2;               /* set operand length */

            case IV:                    /* operand is word or dword */
DoImmed:
                OutputHexValue(&pchOperandBuf, pMem, olen, FALSE);
                pMem += olen;
                break;

            case OFFS:                  /* operand is offset */
                EAsize[0] = (opcode & 1) ? olen : 1;

                if (segOvr)             /* in case of segment override */
                    OutputString(&pchOperandBuf, distbl[segOvr].instruct);

                *pchOperandBuf++ = '[';
                OutputSymbol(dp,&pchOperandBuf, pMem, alen, segOvr);  //  offset
                pMem += alen;
                *pchOperandBuf++ = ']';
                break;

            case GROUPP:                 /* operand is of group 1,2,4,6 or 8 */
                                        /* output opcode symbol */
                OutputString(&pchOpcodeBuf, group[*action++][ttt]);
                break;

            case GROUPT:                /* operand is of group 3,5 or 7 */
                indx = *action;         /* get indx into group from action */
                goto doGroupT;

            case EGROUPT:               /* x87 ESC (D8-DF) group index */
                indx = BIT20(opcode) * 2; /* get group index from opcode */
                if (mod == 3) {         /* some operand variations exists */
                                        /*   for x87 and mod == 3 */
                    ++indx;             /* take the next group table entry */
                    if (indx == 3) {    /* for x87 ESC==D9 and mod==3 */
                        if (ttt > 3) {  /* for those D9 instructions */
                            indx = 12 + ttt; /* offset index to table by 12 */
                            ttt = rm;   /* set secondary index to rm */
                            }
                        }
                    else if (indx == 7) { /* for x87 ESC==DB and mod==3 */
                        if (ttt == 4)   /* only valid if ttt==4 */
                            ttt = rm;   /* set secondary group table index */
                        else
                            ttt = 7;    /* no an x87 instruction */
                        }
                    }
doGroupT:
                /* handle group with different types of operands */

                OutputString(&pchOpcodeBuf, groupt[indx][ttt].instruct);
                action = actiontbl + groupt[indx][ttt].opr;
                                                        /* get new action */
                break;

            case OPC0F:                 /* secondary opcode table (opcode 0F) */
                opcode = *pMem++;       /* get real opcode */
                fMovX  = (BOOLEAN)(opcode == 0xBF || opcode == 0xB7);
                if (opcode < 7) /* for the first 7 opcodes */
                    opcode += 256;      /* point begin of secondary opcode tab. */
                else if (opcode > 0x1f && opcode < 0x27)
                    opcode += 231;      /* adjust for non-existing opcodes */
                else if (opcode > 0x2f && opcode < 0x33)
                    opcode += 222;      /* adjust for non-existing opcodes */
                else if (opcode > 0x7e && opcode < 0xd0)
                    opcode += 148;      /* adjust for non-existing opcodes */
                else
                    opcode = 260;       /* all non-existing opcodes */
                goto getNxtByte1;

            case ADR_OVR:               /* address override */
                mode_32 = !G_mode_32;   /* override addressing mode */
                alen = (mode_32 + 1) << 1; /* toggle address length */
                goto getNxtByte;

            case OPR_OVR:               /* operand size override */
                opsize_32 = !G_mode_32; /* override operand size */
                olen = (opsize_32 + 1) << 1; /* toggle operand length */
                goto getNxtByte;

            case SEG_OVR:               /* handle segment override */
                segOvr = opcode;        /* save segment override opcode */
                pchOpcodeBuf = OpcodeBuffer;  // restart the opcode string
                goto getNxtByte;

            case REP:                   /* handle rep/lock prefixes */
                *pchOpcodeBuf = '\0';
                if (pchRepPrefixBuf != RepPrefixBuffer)
                    *pchRepPrefixBuf++ = ' ';
                OutputString(&pchRepPrefixBuf, OpcodeBuffer);
                pchOpcodeBuf = OpcodeBuffer;
getNxtByte:
                opcode = *pMem++;        /* next byte is opcode */
getNxtByte1:
                action = actiontbl + distbl[opcode].opr;
                OutputString(&pchOpcodeBuf, distbl[opcode].instruct);

            default:                    /* opcode has no operand */
                break;
            }
        switch (action2) {              /* secondary action */
            case MRM:                   /* generate modrm for later use */
                if (!mrm) {             /* ignore if it has been generated */
                    DIdoModrm(dp, &pchModrmBuf, segOvr, fEAout);
                                        /* generate modrm */
                    mrm = TRUE;         /* remember its generation */
                    }
                break;

            case COM:                   /* insert a comma after operand */
                *pchOperandBuf++ = ',';
                break;

            case END:                   /* end of instruction */
                end = TRUE;
                break;
            }
 } while (!end);                        /* loop til end of instruction */

/*****       prepare disassembled instruction for output              *****/

    instlen = pMem - membuf;

    if (instlen < cBytes)
        cBytes = instlen;

    OutputHexCode(&pchResultBuf, membuf, cBytes);

    if (instlen > cBytes) {
        *pchResultBuf++ = '?';
        *pchResultBuf++ = '?';
        (*pOffset)++;                   //  point past unread byte
        }

    *pOffset += instlen;                /* set instruction length */

    if (instlen > cBytes) {
        do
            *pchResultBuf++ = ' ';
        while (pchResultBuf < pchDst + OBOFFSET);
        OutputString(&pchResultBuf, "???");
        *pchResultBuf++ = '\0';
        return FALSE;
        }

    //  if fEAout is set, build each EA with trailing space in EABuf
    //  point back over final trailing space if buffer nonnull

    if (fEAout) {

        for (indx = 0; indx < 2; indx++)
            if (EAsize[indx]) {
                OutputString(&pchEABuf, segOvr ? distbl[segOvr].instruct
                                               : pchEAseg[indx]);
                OutputHexAddr(&pchEABuf, EAaddr[indx]);
                *pchEABuf++ = '=';

                DoMemoryRead( dp,
                              (LPVOID)EAaddr[indx],
                              (LPVOID)membuf,
                              EAsize[indx],
                              &tmp
                            );

                if (tmp == EAsize[indx])
                    OutputHexString(&pchEABuf, (char *)membuf,
                                               EAsize[indx]);
                else
                    while (EAsize[indx]--) {
                        *pchEABuf++ = '?';
                        *pchEABuf++ = '?';
                        }
                *pchEABuf++ = ' ';
                }
        if (pchEABuf != EABuffer)
            pchEABuf--;
        }

    //  compute lengths of component strings.
    //  if the rep string is nonnull,
    //      add the opcode string length to the operand
    //      make the rep string the opcode string

    cbOffset = pchResultBuf - pchDst;
    cbOperand = pchOperandBuf - OperandBuffer;
    cbOpcode = pchOpcodeBuf - OpcodeBuffer;
    if (pchRepPrefixBuf != RepPrefixBuffer) {
        cbOperand += cbOpcode + (cbOperand != 0);
        cbOpcode = pchRepPrefixBuf - RepPrefixBuffer;
        }
    cbEAddr = pchEABuf - EABuffer;

    //  for really long strings, where the opcode and operand
    //      will not fit on a 77-character line, make two lines
    //      with the opcode on offset 0 on the second line with
    //      the operand following after one space

    if (cbOpcode + cbOperand > OBLINEEND - 1) {
        fTwoLines = TRUE;
        obOpcode = 0;
        obOperand = cbOpcode + 1;
        }
    else {

        //  compute the minimum and maximum offset values for
        //      opcode and operand strings.
        //  if strings are nonnull, add extra for separating space

        obOpcodeMin = cbOffset + 1;
        obOperandMin = obOpcodeMin + cbOpcode + 1;
        obOperandMax = OBLINEEND - cbEAddr - (cbEAddr != 0) - cbOperand;
        obOpcodeMax = obOperandMax - (cbOperand != 0) - cbOpcode;

        //  if minimum offset is more than the maximum, the strings
        //      will not fit on one line.  recompute the min/max
        //      values with no offset and EA strings.

        if (obOpcodeMin > obOpcodeMax) {
            fTwoLines = TRUE;
            obOpcodeMin = 0;
            obOperandMin = cbOpcode + 1;
            obOperandMax = OBLINEEND - cbOperand;
            obOpcodeMax = obOperandMax - (cbOperand != 0) - cbOpcode;
            }

        //  compute the opcode and operand offsets.  set offset as
        //      close to the default values as possible.

        if (obOpcodeMin > OBOFFSET)
            obOpcode = obOpcodeMin;
        else if (obOpcodeMax < OBOFFSET)
            obOpcode = obOpcodeMax;

        obOperandMin = obOpcode + cbOpcode + 1;

        if (obOperandMin > OBOPERAND)
            obOperand = obOperandMin;
        else if (obOperandMax < OBOPERAND)
            obOperand = obOperandMax;
        }

    //  build the resultant string with the offsets computed

    //  if two lines are to be output,
    //      append the EAddr string
    //      output a new line and reset the pointer

    if (fTwoLines) {
        if (pchEABuf != EABuffer) {
            do
                *pchResultBuf++ = ' ';
            while (pchResultBuf < pchDst + OBLINEEND - cbEAddr);
            *pchEABuf = '\0';
            OutputString(&pchResultBuf, EABuffer);
            OutputString(&pchResultBuf, "\n        ");
            }
        pchDst = pchResultBuf;
        }

    //  output rep, opcode, and operand strings

    do
        *pchResultBuf++ = ' ';
    while (pchResultBuf < pchDst + obOpcode);

    if (pchRepPrefixBuf != RepPrefixBuffer) {
        *pchRepPrefixBuf = '\0';
        OutputString(&pchResultBuf, RepPrefixBuffer);
        do
            *pchResultBuf++ = ' ';
        while (pchResultBuf < pchDst + obOperand);
        }

    *pchOpcodeBuf = '\0';
    OutputString(&pchResultBuf, OpcodeBuffer);

    if (pchOperandBuf != OperandBuffer) {
        do
            *pchResultBuf++ = ' ';
        while (pchResultBuf < pchDst + obOperand);
        *pchOperandBuf = '\0';
        OutputString(&pchResultBuf, OperandBuffer);
        }

    //  if one line is to be output, append the EAddr string

    if (!fTwoLines && pchEABuf != EABuffer) {
        *pchEABuf = '\0';
        do
            *pchResultBuf++ = ' ';
        while (pchResultBuf < pchDst + OBLINEEND - cbEAddr);
        OutputString(&pchResultBuf, EABuffer);
        }

    *pchResultBuf = '\0';
    return TRUE;
}

/*...........................internal function..............................*/
/*                                                                          */
/*                       generate a mod/rm string                           */
/*                                                                          */

void
DIdoModrm (PDEBUGPACKET dp, char **ppchBuf, int segOvr, BOOLEAN fEAout)
{
    int     mrm;                        /* modrm byte */
    char    *src;                       /* source string */
    int     sib;
    int     ss;
    int     ind;
    int     oldrm;

    mrm = *pMem++;                      /* get the mrm byte from instruction */
    mod = BIT76(mrm);                   /* get mod */
    ttt = BIT53(mrm);                   /* get reg - used outside routine */
    rm  = BIT20(mrm);                   /* get rm */

    if (mod == 3) {                     /* register only mode */
        src = &regtab[rm * 2];          /* point to 16-bit register */
        if (EAsize[0] > 1) {
            src += 16;                  /* point to 16-bit register */
            if (opsize_32 && !fMovX)
                *(*ppchBuf)++ = 'e';    /* make it a 32-bit register */
            }
        *(*ppchBuf)++ = *src++;         /* copy register name */
        *(*ppchBuf)++ = *src;
        EAsize[0] = 0;                  //  no EA value to output
        return;
        }

    if (mode_32) {                      /* 32-bit addressing mode */
        oldrm = rm;
        if (rm == 4) {                  /* rm == 4 implies sib byte */
            sib = *pMem++;              /* get s_i_b byte */
            rm = BIT20(sib);            /* return base */
            }

        *(*ppchBuf)++ = '[';
        if (mod == 0 && rm == 5) {
            OutputSymbol(dp,ppchBuf, pMem, 4, segOvr); // offset
            pMem += 4;
            }
        else {
            if (fEAout) {
                if (segOvr) {
                    EAaddr[0] = (DWORD)GetRegValue(dp, reg32[rm]);
                    pchEAseg[0] = distbl[segOvr].instruct;
                    }
                else if (reg32[rm] == REGEBP || reg32[rm] == REGESP) {
                    EAaddr[0] = (DWORD)GetRegValue(dp, reg32[rm]);
                    pchEAseg[0] = dszSS_;
                    }
                else
                    EAaddr[0] = (DWORD)GetRegValue(dp, reg32[rm]);
                }
            OutputString(ppchBuf, mrmtb32[rm]);
            }

        if (oldrm == 4) {               //  finish processing sib
            ind = BIT53(sib);

if (ind != 4) {
                *(*ppchBuf)++ = '+';
                OutputString(ppchBuf, mrmtb32[ind]);
                ss = 1 << BIT76(sib);
                if (ss != 1) {
                    *(*ppchBuf)++ = '*';
                    *(*ppchBuf)++ = (char)(ss + '0');
                    }
                if (fEAout)
                    EAaddr[0] = (DWORD)GetRegValue(dp, reg32[ind]);
                }
            }
        }
    else {                              //  16-bit addressing mode
        *(*ppchBuf)++ = '[';
        if (mod == 0 && rm == 6) {
            OutputSymbol(dp,ppchBuf, pMem, 2, segOvr);   // 16-bit offset
            pMem += 2;
            }
        else {
            if (fEAout) {
                if (segOvr) {
                    EAaddr[0] = (DWORD)GetRegValue(dp, reg16[rm]);
                    pchEAseg[0] = distbl[segOvr].instruct;
                    }
                else if (reg16[rm] == REGEBP) {
                    EAaddr[0] = (DWORD)GetRegValue(dp, reg16[rm]);
                    pchEAseg[0] = dszSS_;
                    }
                else
                    EAaddr[0] = (DWORD)GetRegValue(dp, reg16[rm]);
                if (rm < 4)
                    EAaddr[0] += (DWORD)GetRegValue(dp, reg16_2[rm]);
            }
            OutputString(ppchBuf, mrmtb16[rm]);
            }
        }

    //  output any displacement

    if (mod == 1) {
        if (fEAout)
            EAaddr[0] += (ULONG)pMem;
        OutputHexValue(ppchBuf, pMem, 1, TRUE);
        pMem++;
        }
    else if (mod == 2) {
        long tmp = 0;
        if (mode_32) {
            memmove(&tmp,pMem,sizeof(long));
            if (fEAout)
                EAaddr[0] += (ULONG)tmp;
            OutputHexValue(ppchBuf, pMem, 4, TRUE);
            pMem += 4;
            }
        else {
            memmove(&tmp,pMem,sizeof(short));
            if (fEAout)
                EAaddr[0] += tmp;
            OutputHexValue(ppchBuf, pMem, 2, TRUE);
            pMem += 2;
            }
        }

    if (!mode_32 && fEAout) {
        EAaddr[0] &= 0xffff;
        EAaddr[1] &= 0xffff;
    }

    *(*ppchBuf)++ = ']';
}

/*** OutputHexValue - output hex value
*
*   Purpose:
*       Output the value pointed by *ppchBuf of the specified
*       length.  The value is treated as signed and leading
*       zeroes are not printed.  The string is prefaced by a
*       '+' or '-' sign as appropriate.
*
*   Input:
*       *ppchBuf - pointer to text buffer to fill
*       *pchMemBuf - pointer to memory buffer to extract value
*       length - length in bytes of value (1, 2, and 4 supported)
*       fDisp - set if displacement to output '+'
*
*   Output:
*       *ppchBuf - pointer updated to next text character
*
*************************************************************************/

void OutputHexValue (char **ppchBuf, char *pchMemBuf, int length, int fDisp)
{
    long    value;
    int     index;
    char    digit[8];

    value = 0;
    if (length == 1)
        value = (long)(*(char *)pchMemBuf);
    else if (length == 2)
        memmove(&value,pchMemBuf,2);
    else
        memmove(&value,pchMemBuf,sizeof(long));

    length <<= 1;               //  shift once to get hex length

    if (value != 0 || !fDisp) {
        if (fDisp)
            if (value < 0 && length == 2) {   //  use neg value for byte
                value = -value;               //    displacement
                *(*ppchBuf)++ = '-';
                }
            else
                *(*ppchBuf)++ = '+';

        *(*ppchBuf)++ = '0';
        *(*ppchBuf)++ = 'x';
        for (index = length - 1; index != -1; index--) {
            digit[index] = (char)(value & 0xf);
            value >>= 4;
            }
        index = 0;
        while (digit[index] == 0 && index < length - 1)
            index++;
        while (index < length)
            *(*ppchBuf)++ = hexdigit[digit[index++]];
        }
}

/*** OutputHexString - output hex string
*
*   Purpose:
*       Output the value pointed by *ppchMemBuf of the specified
*       length.  The value is treated as unsigned and leading
*       zeroes are printed.
*
*   Input:
*       *ppchBuf - pointer to text buffer to fill
*       *pchValue - pointer to memory buffer to extract value
*       length - length in bytes of value
*
*   Output:
*       *ppchBuf - pointer updated to next text character
*       *ppchMemBuf - pointer update to next memory byte
*
*************************************************************************/

void
OutputHexString (char **ppchBuf, char *pchValue, int length)
{
    unsigned char    chMem;

    pchValue += length;
    while (length--) {
        chMem = *--pchValue;
        *(*ppchBuf)++ = hexdigit[chMem >> 4];
        *(*ppchBuf)++ = hexdigit[chMem & 0x0f];
        }
}

/*** OutputHexCode - output hex code
*
*   Purpose:
*       Output the code pointed by pchMemBuf of the specified
*       length.  The value is treated as unsigned and leading
*       zeroes are printed.  This differs from OutputHexString
*       in that bytes are printed from low to high addresses.
*
*   Input:
*       *ppchBuf - pointer to text buffer to fill
*       pchMemBuf - pointer to memory buffer to extract value
*       length - length in bytes of value
*
*   Output:
*       *ppchBuf - pointer updated to next text character
*
*************************************************************************/

void
OutputHexCode (char **ppchBuf, char *pchMemBuf, int length)
{
    unsigned char    chMem;

    while (length--) {
        chMem = *pchMemBuf++;
        *(*ppchBuf)++ = hexdigit[chMem >> 4];
        *(*ppchBuf)++ = hexdigit[chMem & 0x0f];
        }
}

/*** OutputString - output string
*
*   Purpose:
*       Copy the string into the buffer pointed by *ppBuf.
*
*   Input:
*       *pStr - pointer to string
*
*   Output:
*       *ppBuf points to next character in buffer.
*
*************************************************************************/

void
OutputString (char **ppBuf, char *pStr)
{
    while (*pStr)
        *(*ppBuf)++ = *pStr++;
}

/*** OutputSymbol - output symbolic value
*
*   Purpose:
*       Output the value in outvalue into the buffer
*       pointed by *pBuf.  Express the value as a
*       symbol plus displacment, if possible.
*
*   Input:
*       *ppBuf - pointer to text buffer to fill
*       *pValue - pointer to memory buffer to extract value
*       length - length in bytes of value
*
*   Output:
*       *ppBuf - pointer updated to next text character
*
*************************************************************************/

void
OutputSymbol (PDEBUGPACKET dp, char **ppBuf, char *pValue, int length, int segOvr)
{
    ULONG               displacement;
    ULONG               value;
    char                *szSymName;

    value = 0;
    if (length == 1)
        value = (long)(*(char *)pValue);
    else if (length == 2)
        memmove(&value,pValue,sizeof(short));
    else
        memmove(&value,pValue,sizeof(long));

    EAaddr[0] = value;

    if (SymGetSymFromAddr( dp->hProcess, value, &displacement, sym )) {
        szSymName = sym->Name;
        OutputString(ppBuf, szSymName);
        OutputHexValue(ppBuf, (char *)&displacement, length, TRUE);
        *(*ppBuf)++ = ' ';
        *(*ppBuf)++ = '(';
        OutputHexString(ppBuf, pValue, length);
        *(*ppBuf)++ = ')';
        }
    else
        OutputHexString(ppBuf, pValue, length);
}

/*** X86GetNextOffset - compute offset for trace or step
*
*   Purpose:
*       From a limited disassembly of the instruction pointed
*       by the FIR register, compute the offset of the next
*       instruction for either a trace or step operation.
*
*   Input:
*       fStep - TRUE if step offset returned - FALSE for trace offset
*
*   Returns:
*       step or trace offset if input is TRUE or FALSE, respectively
*       -1 returned for trace flag to be used
*
*************************************************************************/

void
GetNextOffset (PDEBUGPACKET dp, PULONG pcaddr, BOOLEAN fStep)
{
    int     mode_32;
    int     opsize_32;
    int     cBytes;
    char    membuf[MAXL];               //  current instruction buffer
    ULONG   addrReturn;
    USHORT  retAddr[3];                 //  return address buffer
    char    *pMem;
    UCHAR   opcode;
    int     fPrefix = TRUE;
    int     fRepPrefix = FALSE;
    int     ttt;
    int     rm;
    ULONG   instroffset;

    //  read instruction stream bytes into membuf and set mode and
    //      opcode size flags

    *pcaddr = (DWORD)GetRegValue(dp,REGEIP);
    instroffset = *pcaddr;
    G_mode_32 = TRUE;
    mode_32 = opsize_32 = (G_mode_32 == 1); /* local addressing mode */
    DoMemoryRead( dp,
                  (LPVOID)*pcaddr,
                  (LPVOID)membuf,
                  MAXL,
                  &cBytes
                );
                                        /* move full inst to local buffer */
    pMem = membuf;                      /* point to begin of instruction */

    //  read and process any prefixes first

    do {
        opcode = (UCHAR)*pMem++;        /* get opcode */
        if (opcode == 0x66)
            opsize_32 = !G_mode_32;
        else if (opcode == 0x67)
            mode_32 = !G_mode_32;
        else if ((opcode & ~1) == 0xf2)
            fRepPrefix = TRUE;
        else if (opcode != 0xf0 && (opcode & ~0x18) != 0x26
                                && (opcode & ~1) != 0x64)
            fPrefix = FALSE;
        }
    while (fPrefix);

    //  for instructions that alter the TF (trace flag), return the
    //      offset of the next instruction despite the flag of fStep

    if (((opcode & ~0x3) == 0x9c))
        //  9c-9f, pushf, popf, sahf, lahf
        ;

    else if (opcode == 0xcf) {          //  cf - iret - get RA from stack

        addrReturn = (DWORD)GetRegValue(dp, REGESP);
        DoMemoryRead( dp,
                      (LPVOID)addrReturn,
                      (LPVOID)retAddr,
                      sizeof(retAddr),
                      NULL
                    );

        *pcaddr = retAddr[2];
        return;
        }

    //  repeated string/port instructions

    if (opcode == 0xe8)            //  near direct jump
        pMem += (1 + opsize_32) * 2;

    else if (opcode == 0x9a)            //  far direct jump
        pMem += (2 + opsize_32) * 2;

    else if (opcode == 0xcd ||
             (opcode >= 0xe0 && opcode <= 0xe2)) //  loop / int nn instrs
        pMem++;

    else if (opcode == 0xff) {          //  indirect call - compute length
        opcode = *pMem++;               //  get modRM
        ttt = BIT53(opcode);
        if ((ttt & ~1) == 2) {
            mod = BIT76(opcode);
            if (mod != 3) {                     //  nonregister operand
                rm = BIT20(opcode);
                if (mode_32) {
                    if (rm == 4)
                        rm = BIT20(*pMem++);    //  get base from SIB
                    if (mod == 0) {
                        if (rm == 5)
                            pMem += 4;          //  long direct address
                        }                       //  else register
                    else if (mod == 1)
                        pMem++;                 //  register with byte offset
                    else
                        pMem += 4;              //  register with long offset
                    }
                else {                          //  16-bit mode
                    if (mod == 0) {
                        if (rm == 6)
                            pMem += 2;          //  short direct address
                        }
                    else
                        pMem += mod;            //  reg, byte, word offset
                    }
                }
            }
        else
            instroffset = (ULONG)-1;            //  0xff, but not call
        }

    else if (!((fRepPrefix && ((opcode & ~3) == 0x6c ||
                               (opcode & ~3) == 0xa4 ||
                               (opcode & ~1) == 0xaa ||
                               (opcode & ~3) == 0xac)) ||
                               opcode == 0xcc || opcode == 0xce))
        instroffset = (ULONG)-1;                //  not repeated string op
                                                //  or int 3 / into

    //  if not enough bytes were read for instruction parse,
    //      just give up and trace the instruction

    if (cBytes < pMem - membuf)
        instroffset = (ULONG)-1;

    //  if not tracing, compute the new instruction offset

    if (instroffset != (ULONG)-1)
        instroffset += pMem - membuf;

    *pcaddr = instroffset;
}

void
OutputHexAddr (PUCHAR *ppBuffer, ULONG offset)
{
    OutputHexString(ppBuffer, (char *)&offset, sizeof(ULONG));
}

USHORT
GetSegRegValue (PDEBUGPACKET dp, int segOpcode)
{
    ULONG    regnum;

    switch (segOpcode) {
        case 0x26:
            regnum = REGES;
            break;
        case 0x2e:
            regnum = REGCS;
            break;
        case 0x36:
            regnum = REGSS;
            break;
        case 0x64:
            regnum = REGFS;
            break;
        case 0x65:
            regnum = REGGS;
            break;
        case 0x3e:
        default:
            regnum = REGDS;
        }

    return (USHORT)GetRegValue(dp,regnum);
}


DISASM.H

/********************************** module *********************************/
/*                                                                         */
/*                                 disasmtb                                */
/*                           disassembler for CodeView                     */
/*                                                                         */
/***************************************************************************/
/*                                                                         */
/*    @ Purpose:                                                           */
/*                                                                         */
/*    @ Functions included:                                                */
/*                                                                         */
/*                                                                         */
/*    @ Author: Gerd Immeyer              @ Version:                       */
/*                                                                         */
/*    @ Creation Date: 10.19.89           @ Modification Date:             */
/*                                                                         */
/***************************************************************************/

/* Strings: Operand mnemonics, Segment overrides, etc. for disasm          */

char dszAAA[]       = "aaa";
char dszAAD[]       = "aad";
char dszAAM[]       = "aam";
char dszAAS[]       = "aas";
char dszADC[]       = "adc";
char dszADD[]       = "add";
char dszADDRPRFX[]  = "";
char dszAND[]       = "and";
char dszARPL[]      = "arpl";
char dszBOUND[]     = "bound";
char dszBSF[]       = "bsf";
char dszBSR[]       = "bsr";
char dszBST[]       = "bst";
char dszBSWAP[]     = "bswap";
char dszBT[]        = "bt";
char dszBTC[]       = "btc";
char dszBTR[]       = "btr";
char dszBTS[]       = "bts";
char dszCALL[]      = "call";
char dszCBW[]       = "cbw";
char dszCDQ[]       = "cdq";
char dszCLC[]       = "clc";
char dszCLD[]       = "cld";
char dszCLI[]       = "cli";
char dszCLTS[]      = "clts";
char dszCMC[]       = "cmc";
char dszCMP[]       = "cmp";
char dszCMPS[]      = "cmps";
char dszCMPSB[]     = "cmpsb";
char dszCMPSD[]     = "cmpsd";
char dszCMPSW[]     = "cmpsw";
char dszCMPXCHG[]   = "cmpxchg";
char dszCMPXCHG8B[] = "cmpxchg8b";
char dszCPUID[]     = "cpuid";
char dszCS_[]       = "cs:";
char dszCWD[]       = "cwd";
char dszCWDE[]      = "cwde";
char dszDAA[]       = "daa";
char dszDAS[]       = "das";
char dszDEC[]       = "dec";
char dszDIV[]       = "div";
char dszDS_[]       = "ds:";
char dszENTER[]     = "enter";
char dszES_[]       = "es:";
char dszF2XM1[]     = "f2xm1";
char dszFABS[]      = "fabs";
char dszFADD[]      = "fadd";
char dszFADDP[]     = "faddp";
char dszFBLD[]      = "fbld";
char dszFBSTP[]     = "fbstp";
char dszFCHS[]      = "fchs";
char dszFCLEX[]     = "fclex";
char dszFCOM[]      = "fcom";
char dszFCOMP[]     = "fcomp";
char dszFCOMPP[]    = "fcompp";
char dszFCOS[]      = "fcos";
char dszFDECSTP[]   = "fdecstp";
char dszFDISI[]     = "fdisi";
char dszFDIV[]      = "fdiv";
char dszFDIVP[]     = "fdivp";
char dszFDIVR[]     = "fdivr";
char dszFDIVRP[]    = "fdivrp";
char dszFENI[]      = "feni";
char dszFFREE[]     = "ffree";
char dszFIADD[]     = "fiadd";
char dszFICOM[]     = "ficom";
char dszFICOMP[]    = "ficomp";
char dszFIDIV[]     = "fidiv";
char dszFIDIVR[]    = "fidivr";
char dszFILD[]      = "fild";
char dszFIMUL[]     = "fimul";
char dszFINCSTP[]   = "fincstp";
char dszFINIT[]     = "finit";
char dszFIST[]      = "fist";
char dszFISTP[]     = "fistp";
char dszFISUB[]     = "fisub";
char dszFISUBR[]    = "fisubr";
char dszFLD[]       = "fld";
char dszFLD1[]      = "fld1";
char dszFLDCW[]     = "fldcw";
char dszFLDENV[]    = "fldenv";
char dszFLDL2E[]    = "fldl2e";
char dszFLDL2T[]    = "fldl2t";
char dszFLDLG2[]    = "fldlg2";
char dszFLDLN2[]    = "fldln2";
char dszFLDPI[]     = "fldpi";
char dszFLDZ[]      = "fldz";
char dszFMUL[]      = "fmul";
char dszFMULP[]     = "fmulp";
char dszFNCLEX[]    = "fnclex";
char dszFNDISI[]    = "fndisi";
char dszFNENI[]     = "fneni";
char dszFNINIT[]    = "fninit";
char dszFNOP[]      = "fnop";
char dszFNSAVE[]    = "fnsave";
char dszFNSTCW[]    = "fnstcw";
char dszFNSTENV[]   = "fnstenv";
char dszFNSTSW[]    = "fnstsw";
char dszFNSTSWAX[]  = "fnstswax";
char dszFPATAN[]    = "fpatan";
char dszFPREM[]     = "fprem";
char dszFPREM1[]    = "fprem1";
char dszFPTAN[]     = "fptan";
char dszFRNDINT[]   = "frndint";
char dszFRSTOR[]    = "frstor";
char dszFSAVE[]     = "fsave";
char dszFSCALE[]    = "fscale";
char dszFSETPM[]    = "fsetpm";
char dszFSIN[]      = "fsin";
char dszFSINCOS[]   = "fsincos";
char dszFSQRT[]     = "fsqrt";
char dszFST[]       = "fst";
char dszFSTCW[]     = "fstcw";
char dszFSTENV[]    = "fstenv";
char dszFSTP[]      = "fstp";
char dszFSTSW[]     = "fstsw";
char dszFSTSWAX[]   = "fstswax";
char dszFSUB[]      = "fsub";
char dszFSUBP[]     = "fsubp";
char dszFSUBR[]     = "fsubr";
char dszFSUBRP[]    = "fsubrp";
char dszFS_[]       = "fs:";
char dszFTST[]      = "ftst";
char dszFUCOM[]     = "fucom";
char dszFUCOMP[]    = "fucomp";
char dszFUCOMPP[]   = "fucompp";
char dszFWAIT[]     = "fwait";
char dszFXAM[]      = "fxam";
char dszFXCH[]      = "fxch";
char dszFXTRACT[]   = "fxtract";
char dszFYL2X[]     = "fyl2x";
char dszFYL2XP1[]   = "fyl2xp1";
char dszGS_[]       = "gs:";
char dszHLT[]       = "hlt";
char dszIBTS[]      = "ibts";
char dszIDIV[]      = "idiv";
char dszIMUL[]      = "imul";
char dszIN[]        = "in";
char dszINC[]       = "inc";
char dszINS[]       = "ins";
char dszINSB[]      = "insb";
char dszINSD[]      = "insd";
char dszINSW[]      = "insw";
char dszINT[]       = "int";
char dszINTO[]      = "into";
char dszIRET[]      = "iret";
char dszIRETD[]     = "iretd";
char dszJA[]        = "ja";
char dszJAE[]       = "jae";
char dszJB[]        = "jb";
char dszJBE[]       = "jbe";
char dszJC[]        = "jc";
char dszJCXZ[]      = "jcxz";
char dszJE[]        = "je";
char dszJECXZ[]     = "jecxz";
char dszJG[]        = "jg";
char dszJGE[]       = "jge";
char dszJL[]        = "jl";
char dszJLE[]       = "jle";
char dszJMP[]       = "jmp";
char dszJNA[]       = "jna";
char dszJNAE[]      = "jnae";
char dszJNB[]       = "jnb";
char dszJNBE[]      = "jnbe";
char dszJNC[]       = "jnc";
char dszJNE[]       = "jne";
char dszJNG[]       = "jng";
char dszJNGE[]      = "jnge";
char dszJNL[]       = "jnl";
char dszJNLE[]      = "jnle";
char dszJNO[]       = "jno";
char dszJNP[]       = "jnp";
char dszJNS[]       = "jns";
char dszJNZ[]       = "jnz";
char dszJO[]        = "jo";
char dszJP[]        = "jp";
char dszJPE[]       = "jpe";
char dszJPO[]       = "jpo";
char dszJS[]        = "js";
char dszJZ[]        = "jz";
char dszLAHF[]      = "lahf";
char dszLAR[]       = "lar";
char dszLDS[]       = "lds";
char dszLEA[]       = "lea";
char dszLEAVE[]     = "leave";
char dszLES[]       = "les";
char dszLFS[]       = "lfs";
char dszLGDT[]      = "lgdt";
char dszLGS[]       = "lgs";
char dszLIDT[]      = "lidt";
char dszLLDT[]      = "lldt";
char dszLMSW[]      = "lmsw";
char dszLOADALL[]   = "loadall";
char dszLOCK[]      = "lock";
char dszLODS[]      = "lods";
char dszLODSB[]     = "lodsb";
char dszLODSD[]     = "lodsd";
char dszLODSW[]     = "lodsw";
char dszLOOP[]      = "loop";
char dszLOOPE[]     = "loope";
char dszLOOPNE[]    = "loopne";
char dszLOOPNZ[]    = "loopnz";
char dszLOOPZ[]     = "loopz";
char dszLSL[]       = "lsl";
char dszLSS[]       = "lss";
char dszLTR[]       = "ltr";
char dszMOV[]       = "mov";
char dszMOVS[]      = "movs";
char dszMOVSB[]     = "movsb";
char dszMOVSD[]     = "movsd";
char dszMOVSW[]     = "movsw";
char dszMOVSX[]     = "movsx";
char dszMOVZX[]     = "movzx";
char dszMUL[]       = "mul";
char dszNEG[]       = "neg";
char dszNOP[]       = "nop";
char dszNOT[]       = "not";
char dszOPPRFX[]    = "";
char dszOR[]        = "or";
char dszOUT[]       = "out";
char dszOUTS[]      = "outs";
char dszOUTSB[]     = "outsb";
char dszOUTSD[]     = "outsd";
char dszOUTSW[]     = "outsw";
char dszPOP[]       = "pop";
char dszPOPA[]      = "popa";
char dszPOPAD[]     = "popad";
char dszPOPF[]      = "popf";
char dszPOPFD[]     = "popfd";
char dszPUSH[]      = "push";
char dszPUSHA[]     = "pusha";
char dszPUSHAD[]    = "pushad";
char dszPUSHF[]     = "pushf";
char dszPUSHFD[]    = "pushfd";
char dszRCL[]       = "rcl";
char dszRCR[]       = "rcr";
char dszRDTSC[]     = "rdtsc";
char dszRDMSR[]     = "rdmsr";
char dszREP[]       = "rep ";
char dszREPE[]      = "repe";
char dszREPNE[]     = "repne ";
char dszREPNZ[]     = "repnz";
char dszREPZ[]      = "repz";
char dszRET[]       = "ret";
char dszRETF[]      = "retf";
char dszRETN[]      = "retn";
char dszROL[]       = "rol";
char dszROR[]       = "ror";
char dszRSM[]       = "rsm";
char dszSAHF[]      = "sahf";
char dszSAL[]       = "sal";
char dszSAR[]       = "sar";
char dszSBB[]       = "sbb";
char dszSCAS[]      = "scas";
char dszSCASB[]     = "scasb";
char dszSCASD[]     = "scasd";
char dszSCASW[]     = "scasw";
char dszSETA[]      = "seta";
char dszSETAE[]     = "setae";
char dszSETB[]      = "setb";
char dszSETBE[]     = "setbe";
char dszSETC[]      = "setc";
char dszSETE[]      = "sete";
char dszSETG[]      = "setg";
char dszSETGE[]     = "setge";
char dszSETL[]      = "setl";
char dszSETLE[]     = "setle";
char dszSETNA[]     = "setna";
char dszSETNAE[]    = "setnae";
char dszSETNB[]     = "setnb";
char dszSETNBE[]    = "setnbe";
char dszSETNC[]     = "setnc";
char dszSETNE[]     = "setne";
char dszSETNG[]     = "setng";
char dszSETNGE[]    = "setnge";
char dszSETNL[]     = "setnl";
char dszSETNLE[]    = "setnle";
char dszSETNO[]     = "setno";
char dszSETNP[]     = "setnp";
char dszSETNS[]     = "setns";
char dszSETNZ[]     = "setnz";
char dszSETO[]      = "seto";
char dszSETP[]      = "setp";
char dszSETPE[]     = "setpe";
char dszSETPO[]     = "setpo";
char dszSETS[]      = "sets";
char dszSETZ[]      = "setz";
char dszSGDT[]      = "sgdt";
char dszSHL[]       = "shl";
char dszSHLD[]      = "shld";
char dszSHR[]       = "shr";
char dszSHRD[]      = "shrd";
char dszSIDT[]      = "sidt";
char dszSLDT[]      = "sldt";
char dszSMSW[]      = "smsw";
char dszSS_[]       = "ss:";
char dszSTC[]       = "stc";
char dszSTD[]       = "std";
char dszSTI[]       = "sti";
char dszSTOS[]      = "stos";
char dszSTOSB[]     = "stosb";
char dszSTOSD[]     = "stosd";
char dszSTOSW[]     = "stosw";
char dszSTR[]       = "str";
char dszSUB[]       = "sub";
char dszTEST[]      = "test";
char dszVERR[]      = "verr";
char dszVERW[]      = "verw";
char dszWAIT[]      = "wait";
char dszWRMSR[]     = "wrmsr";
char dszXADD[]      = "xadd";
char dszXBTS[]      = "xbts";
char dszXCHG[]      = "xchg";
char dszXLAT[]      = "xlat";
char dszXOR[]       = "xor";
char dszRESERVED[]  = "???";
char dszMULTI[]     = "";
char dszDB[]        = "db";

#define MRM        0x40
#define COM        0x80
#define END        0xc0

/* Enumeration of valid actions that can be included in the action table */

enum oprtyp { ADDRP,  ADR_OVR, ALSTR,   ALT,     AXSTR,  BOREG,
              BREG,   BRSTR,   xBYTE,   CHR,     CREG,   xDWORD,
              EDWORD, EGROUPT, FARPTR,  GROUPP,  GROUPT, IB,
              IST,    IST_ST,  IV,      IW,      LMODRM, MODRM,
              NOP,    OFFS,    OPC0F,   OPR_OVR, QWORD,  REL16,
              REL8,   REP,     SEG_OVR, SREG2,   SREG3,  ST_IST,
              STROP,  TTBYTE,   UBYTE,   VAR,     VOREG,  VREG,
              xWORD,  WREG,    WRSTR
            };

/* Enumeration of indices into the action table for instruction classes */

#define O_DoDB          0
#define O_NoOperands    0
#define O_NoOpAlt5      O_NoOperands+1
#define O_NoOpAlt4      O_NoOpAlt5+2
#define O_NoOpAlt3      O_NoOpAlt4+2
#define O_NoOpAlt1      O_NoOpAlt3+2
#define O_NoOpAlt0      O_NoOpAlt1+2
#define O_NoOpStrSI     O_NoOpAlt0+2
#define O_NoOpStrDI     O_NoOpStrSI+2
#define O_NoOpStrSIDI   O_NoOpStrDI+2
#define O_bModrm_Reg    O_NoOpStrSIDI+2
#define O_vModrm_Reg    O_bModrm_Reg+3
#define O_Modrm_Reg     O_vModrm_Reg+3
#define O_bReg_Modrm    O_Modrm_Reg+3
#define O_fReg_Modrm    O_bReg_Modrm+3
#define O_Reg_Modrm     O_fReg_Modrm+3
#define O_AL_Ib         O_Reg_Modrm+3
#define O_AX_Iv         O_AL_Ib+2
#define O_sReg2         O_AX_Iv+2
#define O_oReg          O_sReg2+1
#define O_DoBound       O_oReg+1
#define O_Iv            O_DoBound+3
#define O_wModrm_Reg    O_Iv+1
#define O_Ib            O_wModrm_Reg+3
#define O_Imulb         O_Ib+1
#define O_Imul          O_Imulb+4
#define O_Rel8          O_Imul+4
#define O_bModrm_Ib     O_Rel8+1
#define O_Modrm_Ib      O_bModrm_Ib+3
#define O_Modrm_Iv      O_Modrm_Ib+3
#define O_Modrm_sReg3   O_Modrm_Iv+3
#define O_sReg3_Modrm   O_Modrm_sReg3+3
#define O_Modrm         O_sReg3_Modrm+3
#define O_FarPtr        O_Modrm+2
#define O_AL_Offs       O_FarPtr+1
#define O_Offs_AL       O_AL_Offs+2
#define O_AX_Offs       O_Offs_AL+2
#define O_Offs_AX       O_AX_Offs+2
#define O_oReg_Ib       O_Offs_AX+2
#define O_oReg_Iv       O_oReg_Ib+2
#define O_Iw            O_oReg_Iv+2
#define O_Enter         O_Iw+1
#define O_Ubyte_AL      O_Enter+2
#define O_Ubyte_AX      O_Ubyte_AL+2
#define O_AL_Ubyte      O_Ubyte_AX+2
#define O_AX_Ubyte      O_AL_Ubyte+2
#define O_DoInAL        O_AX_Ubyte+2
#define O_DoInAX        O_DoInAL+3
#define O_DoOutAL       O_DoInAX+3
#define O_DoOutAX       O_DoOutAL+3
#define O_Rel16         O_DoOutAX+3
#define O_ADR_OVERRIDE  O_Rel16+1
#define O_OPR_OVERRIDE  O_ADR_OVERRIDE+1
#define O_SEG_OVERRIDE  O_OPR_OVERRIDE+1
#define O_DoInt3        O_SEG_OVERRIDE+1

#if (O_DoInt3 != 115)
#error "operand table has been modified!"
#endif
/* #define O_DoInt      O_DoInt3+2 */

#define O_DoInt         117
#define O_OPC0F         O_DoInt+1
#define O_GROUP11       O_OPC0F+1
#define O_GROUP13       O_GROUP11+5
#define O_GROUP12       O_GROUP13+5
#define O_GROUP21       O_GROUP12+5
#define O_GROUP22       O_GROUP21+5
#define O_GROUP23       O_GROUP22+5
#define O_GROUP24       O_GROUP23+6
#define O_GROUP25       O_GROUP24+6
#define O_GROUP26       O_GROUP25+6
#define O_GROUP4        O_GROUP26+6
#define O_GROUP6        O_GROUP4+4
#define O_GROUP8        O_GROUP6+4
#define O_GROUP31       O_GROUP8+5
#define O_GROUP32       O_GROUP31+3
#define O_GROUP5        O_GROUP32+3
#define O_GROUP7        O_GROUP5+3
#define O_x87_ESC       O_GROUP7+3
#define O_bModrm        O_x87_ESC+2
#define O_wModrm        O_bModrm+2
#define O_dModrm        O_wModrm+2
#define O_fModrm        O_dModrm+2
#define O_vModrm        O_fModrm+2
#define O_vModrm_Iv     O_vModrm+2
#define O_Reg_bModrm    O_vModrm_Iv+3
#define O_Reg_wModrm    O_Reg_bModrm+3
#define O_Modrm_Reg_Ib  O_Reg_wModrm+3
#define O_Modrm_Reg_CL  O_Modrm_Reg_Ib+4
#define O_ST_iST        O_Modrm_Reg_CL+5
#define O_iST           O_ST_iST+2
#define O_iST_ST        O_iST+2
#define O_qModrm        O_iST_ST+2
#define O_tModrm        O_qModrm+2
#define O_DoRep         O_tModrm+2
#define O_Modrm_CReg    O_DoRep+1
#define O_CReg_Modrm    O_Modrm_CReg+3
#define O_AX_oReg       O_CReg_Modrm+3
#define O_length        O_AX_oReg+3

#if( O_length > 255 )
#error "operand table too large!"
#endif

/* The action table: range of lists of actions to be taken for each possible */
/*   instruction class.                                                      */

static unsigned char actiontbl[] = {
/* NoOperands  */ NOP+END,
/* NoOpAlt5    */ ALT+END,   5,
/* NoOpAlt4    */ ALT+END,   4,
/* NoOpAlt3    */ ALT+END,   3,
/* NoOpAlt1    */ ALT+END,   1,
/* NoOpAlt0    */ ALT+END,   0,
/* NoOpStrSI   */ STROP+END, 1,
/* NoOpStrDI   */ STROP+END, 2,
/* NoOpStrSIDI */ STROP+END, 3,
/* bModrm_Reg  */ xBYTE+MRM, MODRM+COM,  BREG+END,
/* vModrm_Reg  */ VAR+MRM,   LMODRM+COM, BREG+END,
/* Modrm_Reg   */ VAR+MRM,   MODRM+COM,  VREG+END,
/* bReg_Modrm  */ xBYTE+MRM, BREG+COM,   MODRM+END,
/* fReg_Modrm  */ FARPTR+MRM,VREG+COM,   MODRM+END,
/* Reg_Modrm   */ VAR+MRM,   VREG+COM,   MODRM+END,
/* AL_Ib       */ ALSTR+COM, IB+END,
/* AX_Iv       */ AXSTR+COM, IV+END,
/* sReg2       */ SREG2+END,
/* oReg        */ VOREG+END,
/* DoBound     */ VAR+MRM,   VREG+COM,   MODRM+END,
/* Iv          */ IV+END,
/* wModrm_Reg  */ xWORD+MRM, LMODRM+COM, WREG+END,
/* Ib          */ IB+END,
/* Imulb       */ VAR+MRM,   VREG+COM,   MODRM+COM, IB+END,
/* Imul        */ VAR+MRM,   VREG+COM,   MODRM+COM, IV+END,
/* REL8        */ REL8+END,
/* bModrm_Ib   */ xBYTE+MRM, LMODRM+COM, IB+END,
/* Modrm_Ib    */ VAR+MRM,   LMODRM+COM, IB+END,
/* Modrm_Iv    */ VAR+MRM,   LMODRM+COM, IV+END,
/* Modrm_sReg3 */ xWORD+MRM, MODRM+COM,  SREG3+END,
/* sReg3_Modrm */ xWORD+MRM, SREG3+COM,  MODRM+END,
/* Modrm       */ VAR+MRM,   MODRM+END,
/* FarPtr      */ ADDRP+END,
/* AL_Offs     */ ALSTR+COM, OFFS+END,
/* Offs_AL     */ OFFS+COM,  ALSTR+END,
/* AX_Offs     */ AXSTR+COM, OFFS+END,
/* Offs_AX     */ OFFS+COM,  AXSTR+END,
/* oReg_Ib     */ BOREG+COM, IB+END,
/* oReg_Iv     */ VOREG+COM, IV+END,
/* Iw          */ IW+END,
/* enter       */ IW+COM,    IB+END,
/* Ubyte_AL    */ UBYTE+COM, ALSTR+END,
/* Ubyte_AX    */ UBYTE+COM, AXSTR+END,
/* AL_Ubyte    */ ALSTR+COM, UBYTE+END,
/* AX_Ubyte    */ AXSTR+COM, UBYTE+END,
/* DoInAL      */ ALSTR+COM, WRSTR+END,  2,
/* DoInAX      */ AXSTR+COM, WRSTR+END,  2,
/* DoOutAL     */ WRSTR+COM, 2,          ALSTR+END,
/* DoOutAX     */ WRSTR+COM, 2,          AXSTR+END,
/* REL16       */ REL16+END,
/* ADR_OVERRIDE*/ ADR_OVR,
/* OPR_OVERRIDE*/ OPR_OVR,
/* SEG_OVERRIDE*/ SEG_OVR,
/* DoInt3      */ CHR+END,   '3',
/* DoInt       */ UBYTE+END,
/* Opcode0F    */ OPC0F,
/* group1_1    */ xBYTE+MRM, GROUPP,      0,         LMODRM+COM, IB+END,
/* group1_3    */ VAR+MRM,   GROUPP,      0,         LMODRM+COM, IB+END,
/* group1_2    */ VAR+MRM,   GROUPP,      0,         LMODRM+COM, IV+END,
/* group2_1    */ xBYTE+MRM, GROUPP,      1,         LMODRM+COM, IB+END,
/* group2_2    */ VAR+MRM,   GROUPP,      1,         LMODRM+COM, IB+END,
/* group2_3    */ xBYTE+MRM, GROUPP,      1,         LMODRM+COM, CHR+END, '1',
/* group2_4    */ VAR+MRM,   GROUPP,      1,         LMODRM+COM, CHR+END, '1',
/* group2_5    */ xBYTE+MRM, GROUPP,      1,         LMODRM+COM, BRSTR+END, 1,
/* group2_6    */ VAR+MRM,   GROUPP,      1,         LMODRM+COM, BRSTR+END, 1,
/* group4      */ xBYTE+MRM, GROUPP,      2,         LMODRM+END,
/* group6      */ xWORD+MRM, GROUPP,      3,         LMODRM+END,
/* group8      */ xWORD+MRM, GROUPP,      4,         LMODRM+COM, IB+END,
/* group3_1    */ xBYTE+MRM, GROUPT,     20,
/* group3_2    */ VAR+MRM,   GROUPT,     21,
/* group5      */ VAR+MRM,   GROUPT,     22,
/* group7      */ NOP+MRM,   GROUPT,     23,
/* x87_ESC     */ NOP+MRM,   EGROUPT,
/* bModrm      */ xBYTE+MRM, LMODRM+END,
/* wModrm      */ xWORD+MRM, LMODRM+END,
/* dModrm      */ xDWORD+MRM,LMODRM+END,
/* fModrm      */ FARPTR+MRM,LMODRM+END,
/* vModrm      */ VAR+MRM,   LMODRM+END,
/* vModrm_Iv   */ VAR+MRM,   LMODRM+COM, IV+END,
/* reg_bModrm  */ xBYTE+MRM, VREG+COM,   LMODRM+END,
/* reg_wModrm  */ xWORD+MRM, VREG+COM,   LMODRM+END,
/* Modrm_Reg_Ib*/ VAR+MRM,   MODRM+COM,  VREG+COM,   IB+END,
/* Modrm_Reg_CL*/ VAR+MRM,   MODRM+COM,  VREG+COM,   BRSTR+END, 1,
/* ST_iST      */ NOP+MRM,   ST_IST+END,
/* iST         */ NOP+MRM,   IST+END,
/* iST_ST      */ NOP+MRM,   IST_ST+END,
/* qModrm      */ QWORD+MRM, LMODRM+END,
/* tModrm      */ TTBYTE+MRM, LMODRM+END,
/* REP         */ REP,
/* Modrm_CReg  */ EDWORD+MRM,MODRM+COM,  CREG+END,
/* CReg_Modrm  */ EDWORD+MRM,CREG+COM,   MODRM+END,
/* AX_oReg     */ AXSTR+COM, VOREG+END
                  };

#if defined(_M_IX86)
#define BUILDING_ON_X86 1
#else
#define BUILDING_ON_X86 0
#endif

#if BUILDING_ON_X86 == 1
#pragma pack(1)
#endif

typedef struct Tdistbl{
    char *instruct;
    unsigned char opr;
    } Tdistbl;

#if BUILDING_ON_X86 == 1
#pragma pack()
#endif

/* List of ordered pairs for each instruction:                           */
/*    (pointer to string literal mnemonic,                               */
/*     instruction class index for action table)                         */

static Tdistbl distbl[] = {
    dszADD,   O_bModrm_Reg,             /* 00 ADD mem/reg, reg (byte)    */
    dszADD,   O_Modrm_Reg,              /* 01 ADD mem/reg, reg (word)    */
    dszADD,   O_bReg_Modrm,             /* 02 ADD reg, mem/reg (byte)    */
    dszADD,   O_Reg_Modrm,              /* 03 ADD reg, mem/reg (word)    */
    dszADD,   O_AL_Ib,                  /* 04 ADD AL, I                  */
    dszADD,   O_AX_Iv,                  /* 05 ADD AX, I                  */
    dszPUSH,  O_sReg2,                  /* 06 PUSH ES                    */
    dszPOP,   O_sReg2,                  /* 07 POP ES                     */
    dszOR,    O_bModrm_Reg,             /* 08 OR mem/reg, reg (byte)     */
    dszOR,    O_Modrm_Reg,              /* 09 OR mem/reg, reg (word)     */
    dszOR,    O_bReg_Modrm,             /* 0A OR reg, mem/reg (byte)     */
    dszOR,    O_Reg_Modrm,              /* 0B OR reg, mem/reg (word)     */
    dszOR,    O_AL_Ib,                  /* 0C OR AL, I                   */
    dszOR,    O_AX_Iv,                  /* 0D OR AX, I                   */
    dszPUSH,  O_sReg2,                  /* 0E PUSH CS                    */
    dszMULTI, O_OPC0F,                  /* 0F CLTS & protection ctl(286) */
    dszADC,   O_bModrm_Reg,             /* 10 ADC mem/reg, reg (byte)    */
    dszADC,   O_Modrm_Reg,              /* 11 ADC mem/reg, reg (word)    */
    dszADC,   O_bReg_Modrm,             /* 12 ADC reg, mem/reg (byte)    */
    dszADC,   O_Reg_Modrm,              /* 13 ADC reg, mem/reg (word)    */
    dszADC,   O_AL_Ib,                  /* 14 ADC AL, I                  */
    dszADC,   O_AX_Iv,                  /* 15 ADC AX, I                  */
    dszPUSH,  O_sReg2,                  /* 16 PUSH SS                    */
    dszPOP,   O_sReg2,                  /* 17 POP SS                     */
    dszSBB,   O_bModrm_Reg,             /* 18 SBB mem/reg, reg (byte)    */
    dszSBB,   O_Modrm_Reg,              /* 19 SBB mem/reg, reg (word)    */
    dszSBB,   O_bReg_Modrm,             /* 1A SBB reg, mem/reg (byte)    */
    dszSBB,   O_Reg_Modrm,              /* 1B SBB reg, mem/reg (word)    */
    dszSBB,   O_AL_Ib,                  /* 1C SBB AL, I                  */
    dszSBB,   O_AX_Iv,                  /* 1D SBB AX, I                  */
    dszPUSH,  O_sReg2,                  /* 1E PUSH DS                    */
    dszPOP,   O_sReg2,                  /* 1F POP DS                     */
    dszAND,   O_bModrm_Reg,             /* 20 AND mem/reg, reg (byte)    */
    dszAND,   O_Modrm_Reg,              /* 21 AND mem/reg, reg (word)    */
    dszAND,   O_bReg_Modrm,             /* 22 AND reg, mem/reg (byte)    */
    dszAND,   O_Reg_Modrm,              /* 23 AND reg, mem/reg (word)    */
    dszAND,   O_AL_Ib,                  /* 24 AND AL, I                  */
    dszAND,   O_AX_Iv,                  /* 25 AND AX, I                  */
    dszES_,   O_SEG_OVERRIDE,           /* 26 SEG ES:                    */
    dszDAA,   O_NoOperands,             /* 27 DAA                        */
    dszSUB,   O_bModrm_Reg,             /* 28 SUB mem/reg, reg (byte)    */
    dszSUB,   O_Modrm_Reg,              /* 29 SUB mem/reg, reg (word)    */
    dszSUB,   O_bReg_Modrm,             /* 2A SUB reg, mem/reg (byte)    */
    dszSUB,   O_Reg_Modrm,              /* 2B SUB reg, mem/reg (word)    */
    dszSUB,   O_AL_Ib,                  /* 2C SUB AL, I                  */
    dszSUB,   O_AX_Iv,                  /* 2D SUB AX, I                  */
    dszCS_,   O_SEG_OVERRIDE,           /* 2E SEG CS:                    */
    dszDAS,   O_NoOperands,             /* 2F DAS                        */
    dszXOR,   O_bModrm_Reg,             /* 30 XOR mem/reg, reg (byte)    */
    dszXOR,   O_Modrm_Reg,              /* 31 XOR mem/reg, reg (word)    */
    dszXOR,   O_bReg_Modrm,             /* 32 XOR reg, mem/reg (byte)    */
    dszXOR,   O_Reg_Modrm,              /* 33 XOR reg, mem/reg (word)    */
    dszXOR,   O_AL_Ib,                  /* 34 XOR AL, I                  */
    dszXOR,   O_AX_Iv,                  /* 35 XOR AX, I                  */
    dszSS_,   O_SEG_OVERRIDE,           /* 36 SEG SS:                    */
    dszAAA,   O_NoOperands,             /* 37 AAA                        */
    dszCMP,   O_bModrm_Reg,             /* 38 CMP mem/reg, reg (byte)    */
    dszCMP,   O_Modrm_Reg,              /* 39 CMP mem/reg, reg (word)    */
    dszCMP,   O_bReg_Modrm,             /* 3A CMP reg, mem/reg (byte)    */
    dszCMP,   O_Reg_Modrm,              /* 3B CMP reg, mem/reg (word)    */
    dszCMP,   O_AL_Ib,                  /* 3C CMP AL, I                  */
    dszCMP,   O_AX_Iv,                  /* 3D CMP AX, I                  */
    dszDS_,   O_SEG_OVERRIDE,           /* 3E SEG DS:                    */
    dszAAS,   O_NoOperands,             /* 3F AAS                        */
    dszINC,   O_oReg,                   /* 40 INC AX                     */
    dszINC,   O_oReg,                   /* 41 INC CX                     */
    dszINC,   O_oReg,                   /* 42 INC DX                     */
    dszINC,   O_oReg,                   /* 43 INC BX                     */
    dszINC,   O_oReg,                   /* 44 INC SP                     */
    dszINC,   O_oReg,                   /* 45 INC BP                     */
    dszINC,   O_oReg,                   /* 46 INC SI                     */
    dszINC,   O_oReg,                   /* 47 INC DI                     */
    dszDEC,   O_oReg,                   /* 48 DEC AX                     */
    dszDEC,   O_oReg,                   /* 49 DEC CX                     */
    dszDEC,   O_oReg,                   /* 4A DEC DX                     */
    dszDEC,   O_oReg,                   /* 4B DEC BX                     */
    dszDEC,   O_oReg,                   /* 4C DEC SP                     */
    dszDEC,   O_oReg,                   /* 4D DEC BP                     */
    dszDEC,   O_oReg,                   /* 4E DEC SI                     */
    dszDEC,   O_oReg,                   /* 4F DEC DI                     */
    dszPUSH,  O_oReg,                   /* 50 PUSH AX                    */
    dszPUSH,  O_oReg,                   /* 51 PUSH CX                    */
    dszPUSH,  O_oReg,                   /* 52 PUSH DX                    */
    dszPUSH,  O_oReg,                   /* 53 PUSH BX                    */
    dszPUSH,  O_oReg,                   /* 54 PUSH SP                    */
    dszPUSH,  O_oReg,                   /* 55 PUSH BP                    */
    dszPUSH,  O_oReg,                   /* 56 PUSH SI                    */
    dszPUSH,  O_oReg,                   /* 57 PUSH DI                    */
    dszPOP,   O_oReg,                   /* 58 POP AX                     */
    dszPOP,   O_oReg,                   /* 59 POP CX                     */
    dszPOP,   O_oReg,                   /* 5A POP DX                     */
    dszPOP,   O_oReg,                   /* 5B POP BX                     */
    dszPOP,   O_oReg,                   /* 5C POP SP                     */
    dszPOP,   O_oReg,                   /* 5D POP BP                     */
    dszPOP,   O_oReg,                   /* 5E POP SI                     */
    dszPOP,   O_oReg,                   /* 5F POP DI                     */
    dszPUSHA, O_NoOpAlt5,               /* 60 PUSHA (286) / PUSHAD (386) */
    dszPOPA,  O_NoOpAlt4,               /* 61 POPA (286) / POPAD (286)   */
    dszBOUND, O_DoBound,                /* 62 BOUND reg, Modrm (286)     */
    dszARPL,  O_Modrm_Reg,              /* 63 ARPL Modrm, reg (286)      */
    dszFS_,   O_SEG_OVERRIDE,           /* 64                            */
    dszGS_,   O_SEG_OVERRIDE,           /* 65                            */
    dszOPPRFX,O_OPR_OVERRIDE,           /* 66                            */
    dszADDRPRFX,O_ADR_OVERRIDE,         /* 67                            */
    dszPUSH,  O_Iv,                     /* 68 PUSH word (286)            */
    dszIMUL,  O_Imul,                   /* 69 IMUL (286)                 */
    dszPUSH,  O_Ib,                     /* 6A PUSH byte (286)            */
    dszIMUL,  O_Imulb,                  /* 6B IMUL (286)                 */
    dszINSB,  O_NoOperands,             /* 6C INSB (286)                 */
    dszINSW,  O_NoOpAlt3,               /* 6D INSW (286) / INSD (386)    */
    dszOUTSB, O_NoOperands,             /* 6E OUTSB (286)                */
    dszOUTSW, O_NoOpAlt4,               /* 6F OUTSW (286) / OUTSD (386)  */
    dszJO,    O_Rel8,                   /* 70 JO                         */
    dszJNO,   O_Rel8,                   /* 71 JNO                        */
    dszJB,    O_Rel8,                   /* 72 JB or JNAE or JC           */
    dszJNB,   O_Rel8,                   /* 73 JNB or JAE or JNC          */
    dszJZ,    O_Rel8,                   /* 74 JE or JZ                   */
    dszJNZ,   O_Rel8,                   /* 75 JNE or JNZ                 */
    dszJBE,   O_Rel8,                   /* 76 JBE or JNA                 */

dszJA,    O_Rel8,                   /* 77 JNBE or JA                 */
    dszJS,    O_Rel8,                   /* 78 JS                         */
    dszJNS,   O_Rel8,                   /* 79 JNS                        */
    dszJPE,   O_Rel8,                   /* 7A JP or JPE                  */
    dszJPO,   O_Rel8,                   /* 7B JNP or JPO                 */
    dszJL,    O_Rel8,                   /* 7C JL or JNGE                 */
    dszJGE,   O_Rel8,                   /* 7D JNL or JGE                 */
    dszJLE,   O_Rel8,                   /* 7E JLE or JNG                 */
    dszJG,    O_Rel8,                   /* 7F JNLE or JG                 */
    dszMULTI, O_GROUP11,                /* 80                            */
    dszMULTI, O_GROUP12,                /* 81                            */
    dszRESERVED, O_DoDB,                /* 82                            */
    dszMULTI, O_GROUP13,                /* 83                            */
    dszTEST,  O_bModrm_Reg,             /* 84 TEST reg, mem/reg (byte)   */
    dszTEST,  O_Modrm_Reg,              /* 85 TEST reg, mem/reg (word)   */
    dszXCHG,  O_bModrm_Reg,             /* 86 XCHG reg, mem/reg (byte)   */
    dszXCHG,  O_Modrm_Reg,              /* 87 XCHG reg, mem/reg (word)   */
    dszMOV,   O_bModrm_Reg,             /* 88 MOV mem/reg, reg (byte)    */
    dszMOV,   O_Modrm_Reg,              /* 89 MOV mem/reg, reg (word)    */
    dszMOV,   O_bReg_Modrm,             /* 8A MOV reg, mem/reg (byte)    */
    dszMOV,   O_Reg_Modrm,              /* 8B MOV reg, mem/reg (word)    */
    dszMOV,   O_Modrm_sReg3,            /* 8C MOV mem/reg, segreg        */
    dszLEA,   O_Reg_Modrm,              /* 8D LEA reg, mem               */
    dszMOV,   O_sReg3_Modrm,            /* 8E MOV segreg, mem/reg        */
    dszPOP,   O_Modrm,                  /* 8F POP mem/reg                */
    dszNOP,   O_NoOperands,             /* 90 NOP                        */
    dszXCHG,  O_AX_oReg,                /* 91 XCHG AX,CX                 */
    dszXCHG,  O_AX_oReg,                /* 92 XCHG AX,DX                 */
    dszXCHG,  O_AX_oReg,                /* 93 XCHG AX,BX                 */
    dszXCHG,  O_AX_oReg,                /* 94 XCHG AX,SP                 */
    dszXCHG,  O_AX_oReg,                /* 95 XCHG AX,BP                 */
    dszXCHG,  O_AX_oReg,                /* 96 XCHG AX,SI                 */
    dszXCHG,  O_AX_oReg,                /* 97 XCHG AX,DI                 */
    dszCBW,   O_NoOpAlt0,               /* 98 CBW / CWDE (386)           */
    dszCWD,   O_NoOpAlt1,               /* 99 CWD / CDQ (386)            */
    dszCALL,  O_FarPtr,                 /* 9A CALL seg:off               */
    dszWAIT,  O_NoOperands,             /* 9B WAIT                       */
    dszPUSHF, O_NoOpAlt5,               /* 9C PUSHF / PUSHFD (386)       */
    dszPOPF,  O_NoOpAlt4,               /* 9D POPF / POPFD (386)         */
    dszSAHF,  O_NoOperands,             /* 9E SAHF                       */
    dszLAHF,  O_NoOperands,             /* 9F LAHF                       */
    dszMOV,   O_AL_Offs,                /* A0 MOV AL, mem                */
    dszMOV,   O_AX_Offs,                /* A1 MOV AX, mem                */
    dszMOV,   O_Offs_AL,                /* A2 MOV mem, AL                */
    dszMOV,   O_Offs_AX,                /* A3 MOV mem, AX                */
    dszMOVSB, O_NoOpStrSIDI,            /* A4 MOVSB                      */
    dszMOVSW, O_NoOpStrSIDI,            /* A5 MOVSW / MOVSD (386)        */
    dszCMPSB, O_NoOpStrSIDI,            /* A6 CMPSB                      */
    dszCMPSW, O_NoOpStrSIDI,            /* A7 CMPSW / CMPSD (386)        */
    dszTEST,  O_AL_Ib,                  /* A8 TEST AL, I                 */
    dszTEST,  O_AX_Iv,                  /* A9 TEST AX, I                 */
    dszSTOSB, O_NoOpStrDI,              /* AA STOSB                      */
    dszSTOSW, O_NoOpStrDI,              /* AB STOSW / STOSD (386)        */
    dszLODSB, O_NoOpStrSI,              /* AC LODSB                      */
    dszLODSW, O_NoOpStrSI,              /* AD LODSW / LODSD (386)        */
    dszSCASB, O_NoOpStrDI,              /* AE SCASB                      */
    dszSCASW, O_NoOpStrDI,              /* AF SCASW / SCASD (386)        */
    dszMOV,   O_oReg_Ib,                /* B0 MOV AL, I                  */
    dszMOV,   O_oReg_Ib,                /* B1 MOV CL, I                  */
    dszMOV,   O_oReg_Ib,                /* B2 MOV DL, I                  */
    dszMOV,   O_oReg_Ib,                /* B3 MOV BL, I                  */
    dszMOV,   O_oReg_Ib,                /* B4 MOV AH, I                  */
    dszMOV,   O_oReg_Ib,                /* B5 MOV CH, I                  */
    dszMOV,   O_oReg_Ib,                /* B6 MOV DH, I                  */
    dszMOV,   O_oReg_Ib,                /* B7 MOV BH, I                  */
    dszMOV,   O_oReg_Iv,                /* B8 MOV AX, I                  */
    dszMOV,   O_oReg_Iv,                /* B9 MOV CX, I                  */
    dszMOV,   O_oReg_Iv,                /* BA MOV DX, I                  */
    dszMOV,   O_oReg_Iv,                /* BB MOV BX, I                  */
    dszMOV,   O_oReg_Iv,                /* BC MOV SP, I                  */
    dszMOV,   O_oReg_Iv,                /* BD MOV BP, I                  */
    dszMOV,   O_oReg_Iv,                /* BE MOV SI, I                  */
    dszMOV,   O_oReg_Iv,                /* BF MOV DI, I                  */
    dszMULTI, O_GROUP21,                /* C0 shifts & rotates (286)     */
    dszMULTI, O_GROUP22,                /* C1 shifts & rotates (286)     */
    dszRET,   O_Iw,                     /* C2 RET Rel16                  */
    dszRET,   O_NoOperands,             /* C3 RET                        */
    dszLES,   O_fReg_Modrm,             /* C4 LES reg, mem               */
    dszLDS,   O_fReg_Modrm,             /* C5 LDS reg, mem               */
    dszMOV,   O_bModrm_Ib,              /* C6 MOV mem/reg, I(byte)       */
    dszMOV,   O_Modrm_Iv,               /* C7 MOV mem/reg, I(word)       */
    dszENTER, O_Enter,                  /* C8 ENTER (286)                */
    dszLEAVE, O_NoOperands,             /* C9 LEAVE (286)                */
    dszRETF,  O_Iw,                     /* CA RETF I(word)               */
    dszRETF,  O_NoOperands,             /* CB RETF                       */
    dszINT,   O_DoInt3,                 /* CC INT 3                      */
    dszINT,   O_DoInt,                  /* CD INT                        */
    dszINTO,  O_NoOperands,             /* CE INTO                       */
    dszIRET,  O_NoOpAlt4,               /* CF IRET / IRETD (386)         */
    dszMULTI, O_GROUP23,                /* D0 shifts & rotates,1 (byte)  */
    dszMULTI, O_GROUP24,                /* D1 shifts & rotates,1 (word)  */
    dszMULTI, O_GROUP25,                /* D2 shifts & rotates,CL (byte) */
    dszMULTI, O_GROUP26,                /* D3 shifts & rotates,CL (word) */
    dszAAM,   O_Ib,                     /* D4 AAM                        */
    dszAAD,   O_Ib,                     /* D5 AAD                        */
    dszRESERVED, O_DoDB,                /* D6                            */
    dszXLAT,  O_NoOperands,             /* D7 XLAT                       */
    dszMULTI, O_x87_ESC,                /* D8 ESC                        */
    dszMULTI, O_x87_ESC,                /* D9 ESC                        */
    dszMULTI, O_x87_ESC,                /* DA ESC                        */
    dszMULTI, O_x87_ESC,                /* DB ESC                        */
    dszMULTI, O_x87_ESC,                /* DC ESC                        */
    dszMULTI, O_x87_ESC,                /* DD ESC                        */
    dszMULTI, O_x87_ESC,                /* DE ESC                        */
    dszMULTI, O_x87_ESC,                /* DF ESC                        */
    dszLOOPNE,O_Rel8,                   /* E0 LOOPNE or LOOPNZ           */
    dszLOOPE, O_Rel8,                   /* E1 LOOPE or LOOPZ             */
    dszLOOP,  O_Rel8,                   /* E2 LOOP                       */
    dszJCXZ,  O_Rel8,                   /* E3 JCXZ / JECXZ (386)         */
    dszIN,    O_AL_Ubyte,               /* E4 IN AL, I                   */
    dszIN,    O_AX_Ubyte,               /* E5 IN AX, I                   */
    dszOUT,   O_Ubyte_AL,               /* E6 OUT I, AL                  */
    dszOUT,   O_Ubyte_AX,               /* E7 OUT I, AX                  */
    dszCALL,  O_Rel16,                  /* E8 CALL Rel16                 */
    dszJMP,   O_Rel16,                  /* E9 JMP Rel16                  */
    dszJMP,   O_FarPtr,                 /* EA JMP seg:off                */
    dszJMP,   O_Rel8,                   /* EB JMP Rel8                   */
    dszIN,    O_DoInAL,                 /* EC IN AL, DX                  */
    dszIN,    O_DoInAX,                 /* ED IN AX, DX                  */
    dszOUT,   O_DoOutAL,                /* EE OUT DX, AL                 */
    dszOUT,   O_DoOutAX,                /* EF OUT DX, AX                 */
    dszLOCK,  O_DoRep,                  /* F0 LOCK                       */
    dszRESERVED, O_DoDB,                /* F1                            */
    dszREPNE, O_DoRep,                  /* F2 REPNE or REPNZ             */
    dszREP,   O_DoRep,                  /* F3 REP or REPE or REPZ        */
    dszHLT,   O_NoOperands,             /* F4 HLT                        */
    dszCMC,   O_NoOperands,             /* F5 CMC                        */
    dszMULTI, O_GROUP31,                /* F6 TEST, NOT, NEG, MUL, IMUL, */
    dszMULTI, O_GROUP32,                /* F7 DIv, IDIv F6=Byte F7=Word  */
    dszCLC,   O_NoOperands,             /* F8 CLC                        */
    dszSTC,   O_NoOperands,             /* F9 STC                        */
    dszCLI,   O_NoOperands,             /* FA CLI                        */
    dszSTI,   O_NoOperands,             /* FB STI                        */
    dszCLD,   O_NoOperands,             /* FC CLD                        */
    dszSTD,   O_NoOperands,             /* FD STD                        */
    dszMULTI, O_GROUP4,                 /* FE INC, DEC mem/reg (byte)    */
    dszMULTI, O_GROUP5,                 /* FF INC, DEC, CALL, JMP, PUSH  */

    dszMULTI, O_GROUP6,                 /* 0 MULTI                       */
    dszMULTI, O_GROUP7,                 /* 1 MULTI                       */
    dszLAR,   O_Reg_Modrm,              /* 2 LAR                         */
    dszLSL,   O_Reg_Modrm,              /* 3 LSL                         */
    dszRESERVED, O_DoDB,                /* 4                             */
    dszLOADALL, O_NoOperands,           /* 5 LOADALL                     */
    dszCLTS,  O_NoOperands,             /* 6 CLTS                        */
    dszMOV,   O_Modrm_CReg,             /* 20 MOV Rd,Cd                  */
    dszMOV,   O_Modrm_CReg,             /* 21 MOV Rd,Dd                  */
    dszMOV,   O_CReg_Modrm,             /* 22 MOV Cd,Rd                  */
    dszMOV,   O_CReg_Modrm,             /* 23 MOV Dd,Rd                  */
    dszMOV,   O_Modrm_CReg,             /* 24 MOV Rd,Td                  */
    dszRESERVED, O_DoDB,                /* 25                            */
    dszMOV,   O_CReg_Modrm,             /* 26 MOV Td,Rd                  */

    dszWRMSR, O_NoOperands,             /* 30 WRMSR                      */
    dszRDTSC, O_NoOperands,             /* 31 RDTSC                      */
    dszRDMSR, O_NoOperands,             /* 32 RDMSR                      */

    dszSETNL, O_bModrm,                 /* 7D SETNL                      */
    dszRESERVED, O_DoDB,                /* 7E                            */
    dszRESERVED, O_DoDB,                /* 7F                            */
    dszJO,    O_Rel16,                  /* 80 JO                         */
    dszJNO,   O_Rel16,                  /* 81 JNO                        */
    dszJB,    O_Rel16,                  /* 82 JB                         */
    dszJNB,   O_Rel16,                  /* 83 JNB                        */
    dszJE,    O_Rel16,                  /* 84 JE                         */
    dszJNE,   O_Rel16,                  /* 85 JNE                        */
    dszJBE,   O_Rel16,                  /* 86 JBE                        */
    dszJNBE,  O_Rel16,                  /* 87 JNBE                       */
    dszJS,    O_Rel16,                  /* 88 JS                         */
    dszJNS,   O_Rel16,                  /* 89 JNS                        */
    dszJP,    O_Rel16,                  /* 8A JP                         */
    dszJNP,   O_Rel16,                  /* 8B JNP                        */
    dszJL,    O_Rel16,                  /* 8C JL                         */
    dszJNL,   O_Rel16,                  /* 8D JNL                        */
    dszJLE,   O_Rel16,                  /* 8E JLE                        */
    dszJNLE,  O_Rel16,                  /* 8F JNLE                       */
    dszSETO,  O_bModrm,                 /* 90 SETO                       */
    dszSETNO, O_bModrm,                 /* 91 SETNO                      */
    dszSETB,  O_bModrm,                 /* 92 SETB                       */
    dszSETNB, O_bModrm,                 /* 93 SETNB                      */
    dszSETE,  O_bModrm,                 /* 94 SETE                       */
    dszSETNE, O_bModrm,                 /* 95 SETNE                      */
    dszSETBE, O_bModrm,                 /* 96 SETBE                      */
    dszSETA,  O_bModrm,                 /* 97 SETNBE                     */
    dszSETS,  O_bModrm,                 /* 98 SETS                       */
    dszSETNS, O_bModrm,                 /* 99 SETNS                      */
    dszSETP,  O_bModrm,                 /* 9A SETP                       */
    dszSETNP, O_bModrm,                 /* 9B SETNP                      */
    dszSETL,  O_bModrm,                 /* 9C SETL                       */
    dszSETGE, O_bModrm,                 /* 9D SETGE                      */
    dszSETLE, O_bModrm,                 /* 9E SETLE                      */
    dszSETNLE,O_bModrm,                 /* 9F SETNLE                     */
    dszPUSH,  O_sReg2,                  /* A0 PUSH FS                    */
    dszPOP,   O_sReg2,                  /* A1 POP FS                     */
    dszCPUID, O_NoOperands,             /* A2 CPUID                      */
    dszBT,    O_Modrm_Reg,              /* A3 BT                         */
    dszSHLD,  O_Modrm_Reg_Ib,           /* A4 SHLD                       */
    dszSHLD,  O_Modrm_Reg_CL,           /* A5 SHLD                       */
    dszCMPXCHG,O_bModrm_Reg,            /* A6 XBTS                       */
    dszCMPXCHG,O_Modrm_Reg,             /* A7 IBTS                       */
    dszPUSH,  O_sReg2,                  /* A8 PUSH GS                    */
    dszPOP,   O_sReg2,                  /* A9 POP GS                     */
    dszRSM,   O_NoOperands,             /* AA RSM                        */
    dszBTS,   O_vModrm_Reg,             /* AB BTS                        */
    dszSHRD,  O_Modrm_Reg_Ib,           /* AC SHRD                       */
    dszSHRD,  O_Modrm_Reg_CL,           /* AD SHRD                       */
    dszRESERVED, O_DoDB,                /* AE                            */
    dszIMUL,  O_Reg_Modrm,              /* AF IMUL                       */
    dszRESERVED, O_DoDB,                /* B0                            */
    dszRESERVED, O_DoDB,                /* B1                            */
    dszLSS,   O_fReg_Modrm,             /* B2 LSS                        */
    dszBTR,   O_Modrm_Reg,              /* B3 BTR                        */
    dszLFS,   O_fReg_Modrm,             /* B4 LFS                        */
    dszLGS,   O_fReg_Modrm,             /* B5 LGS                        */
    dszMOVZX, O_Reg_bModrm,             /* B6 MOVZX                      */
    dszMOVZX, O_Reg_wModrm,             /* B7 MOVZX                      */
    dszRESERVED, O_DoDB,                /* B8                            */
    dszRESERVED, O_DoDB,                /* B9                            */
    dszMULTI, O_GROUP8,                 /* BA MULTI                      */
    dszBTC,   O_Modrm_Reg,              /* BB BTC                        */
    dszBSF,   O_Reg_Modrm,              /* BC BSF                        */
    dszBSR,   O_Reg_Modrm,              /* BD BSR                        */
    dszMOVSX, O_Reg_bModrm,             /* BE MOVSX                      */
    dszMOVSX, O_Reg_wModrm,             /* BF MOVSX                      */
    dszXADD,  O_bModrm_Reg,             /* C0 XADD                       */
    dszXADD,  O_Modrm_Reg,              /* C1 XADD                       */
    dszRESERVED, O_DoDB,                /* C2                            */
    dszRESERVED, O_DoDB,                /* C3                            */
    dszRESERVED, O_DoDB,                /* C4                            */
    dszRESERVED, O_DoDB,                /* C5                            */
    dszRESERVED, O_DoDB,                /* C6                            */
    dszCMPXCHG8B, O_qModrm,             /* C7 CMPXCHG8B                  */
    dszBSWAP, O_oReg,                   /* C8 BSWAP                      */
    dszBSWAP, O_oReg,                   /* C9 BSWAP                      */
    dszBSWAP, O_oReg,                   /* CA BSWAP                      */
    dszBSWAP, O_oReg,                   /* CB BSWAP                      */
    dszBSWAP, O_oReg,                   /* CC BSWAP                      */
    dszBSWAP, O_oReg,                   /* CD BSWAP                      */
    dszBSWAP, O_oReg,                   /* CE BSWAP                      */
    dszBSWAP, O_oReg                    /* CF BSWAP                      */
};

/* Auxilary lists of mnemonics for groups of two byte instructions:      */
/*   All of the instructions within each of these groups are of the same */
/*   class, so only the mnemonic string is needed, the index into the    */
/*   action table is implicit.                                           */

static char *group[][8] = {

/* 00 */    {dszADD,  dszOR,    dszADC,  dszSBB,    /* group 1 */
             dszAND,  dszSUB,   dszXOR,  dszCMP},

/* 01 */    {dszROL,  dszROR,   dszRCL,      dszRCR,    /* group 2 */
             dszSHL,  dszSHR,   dszRESERVED, dszSAR},

/* 02 */    {dszINC,      dszDEC,      dszRESERVED, dszRESERVED, /* group 4 */
             dszRESERVED, dszRESERVED, dszRESERVED, dszRESERVED},

/* 03 */    {dszSLDT, dszSTR,   dszLLDT,     dszLTR,    /* group 6 */
             dszVERR, dszVERW,  dszRESERVED, dszRESERVED},

/* 04 */    {dszRESERVED, dszRESERVED, dszRESERVED, dszRESERVED, /* group 8 */
             dszBT,       dszBTS,      dszBTR,      dszBTC}

            };

/* Auxilary orderd pairs for groups of two byte instructions structured  */
/*   the same was as distbl above.                                       */

static Tdistbl groupt[][8] = {

/* 00  00                     x87-D8-1                   */
            { dszFADD,     O_dModrm,     /* D8-0 FADD    */
              dszFMUL,     O_dModrm,     /* D8-1 FMUL    */
              dszFCOM,     O_dModrm,     /* D8-2 FCOM    */
              dszFCOMP,    O_dModrm,     /* D8-3 FCOMP   */
              dszFSUB,     O_dModrm,     /* D8-4 FSUB    */
              dszFSUBR,    O_dModrm,     /* D8-5 FSUBR   */
              dszFDIV,     O_dModrm,     /* D8-6 FDIV    */
              dszFDIVR,    O_dModrm },   /* D8-7 FDIVR   */

/* 01                         x87-D8-2                   */
            { dszFADD,     O_ST_iST,     /* D8-0 FADD    */
              dszFMUL,     O_ST_iST,     /* D8-1 FMUL    */
              dszFCOM,     O_iST,        /* D8-2 FCOM    */
              dszFCOMP,    O_iST,        /* D8-3 FCOMP   */
              dszFSUB,     O_ST_iST,     /* D8-4 FSUB    */
              dszFSUBR,    O_ST_iST,     /* D8-5 FSUBR   */
              dszFDIV,     O_ST_iST,     /* D8-6 FDIV    */
              dszFDIVR,    O_ST_iST },   /* D8-7 FDIVR   */

/* 02   01                    x87-D9-1                   */
            { dszFLD,      O_dModrm,     /* D9-0 FLD     */
              dszRESERVED, O_DoDB,       /* D9-1         */
              dszFST,      O_dModrm,     /* D9-2 FST     */
              dszFSTP,     O_dModrm,     /* D9-3 FSTP    */
              dszFLDENV,   O_Modrm,      /* D9-4 FLDENV  */
              dszFLDCW,    O_Modrm,      /* D9-5 FLDCW   */
              dszFSTENV,   O_Modrm,      /* D9-6 FSTENV  */
              dszFSTCW,    O_Modrm },    /* D9-7 FSTCW   */

/* 03   01                    x87-D9-2 TTT=0,1,2,3       */
            { dszFLD,      O_iST,        /* D9-0 FLD     */
              dszFXCH,     O_iST,        /* D9-1 FXCH    */
              dszFNOP,     O_NoOperands, /* D9-2 FNOP    */
              dszFSTP,     O_iST,        /* D9-3 FSTP    */
              dszRESERVED, O_DoDB,       /* D9-4         */
              dszRESERVED, O_DoDB,       /* D9-5         */
              dszRESERVED, O_DoDB,       /* D9-6         */
              dszRESERVED, O_DoDB   },   /* D9-7         */

/* 04  02                     x89-DA-1                   */
            { dszFIADD,    O_dModrm,     /* DA-0 FIADD   */
              dszFIMUL,    O_dModrm,     /* DA-1 FIMUL   */
              dszFICOM,    O_dModrm,     /* DA-2 FICOM   */
              dszFICOMP,   O_dModrm,     /* DA-3 FICOMP  */
              dszFISUB,    O_dModrm,     /* DA-4 FISUB   */
              dszFISUBR,   O_dModrm,     /* DA-5 FISUBR  */
              dszFIDIV,    O_dModrm,     /* DA-6 FIDIV   */
              dszFIDIVR,   O_dModrm },   /* DA-7 FIDIVR  */

/* 05                         x87-DA-2                   */
            { dszRESERVED, O_DoDB,       /* DA-0         */
              dszRESERVED, O_DoDB,       /* DA-1         */
              dszRESERVED, O_DoDB,       /* DA-2         */
              dszRESERVED, O_DoDB,       /* DA-3         */
              dszRESERVED, O_DoDB,       /* DA-4         */
              dszFUCOMPP,  O_NoOperands, /* DA-5         */
              dszRESERVED, O_DoDB,       /* DA-6         */
              dszRESERVED, O_DoDB },     /* DA-7         */

/* 06  03                     x87-DB-1                   */
            { dszFILD,     O_dModrm,     /* DB-0 FILD    */
              dszRESERVED, O_DoDB,       /* DB-1         */
              dszFIST,     O_dModrm,     /* DB-2 FIST    */
              dszFISTP,    O_dModrm,     /* DB-3 FISTP   */
              dszRESERVED, O_DoDB,       /* DB-4         */
              dszFLD,      O_tModrm,     /* DB-5 FLD     */
              dszRESERVED, O_DoDB,       /* DB-6         */
              dszFSTP,     O_tModrm },   /* DB-7 FSTP    */

/* 07                      x87-DB-2 ttt=4        */
            { dszFENI,     O_NoOperands, /* DB-0 FENI    */
              dszFDISI,    O_NoOperands, /* DB-1 FDISI   */
              dszFCLEX,    O_NoOperands, /* DB-2 FCLEX   */
              dszFINIT,    O_NoOperands, /* DB-3 FINIT   */
              dszFSETPM,   O_DoDB,       /* DB-4 FSETPM  */
              dszRESERVED, O_DoDB,       /* DB-5         */
              dszRESERVED, O_DoDB,       /* DB-6         */
              dszRESERVED, O_DoDB },     /* DB-7         */

/* 08 04                      x87-DC-1                   */
            { dszFADD,     O_qModrm,     /* DC-0 FADD    */
              dszFMUL,     O_qModrm,     /* DC-1 FMUL    */
              dszFCOM,     O_qModrm,     /* DC-2 FCOM    */
              dszFCOMP,    O_qModrm,     /* DC-3 FCOMP   */
              dszFSUB,     O_qModrm,     /* DC-4 FSUB    */
              dszFSUBR,    O_qModrm,     /* DC-5 FSUBR   */
              dszFDIV,     O_qModrm,     /* DC-6 FDIV    */
              dszFDIVR,    O_qModrm },   /* DC-7 FDIVR   */

/* 09                         x87-DC-2                   */
            { dszFADD,     O_iST_ST,     /* DC-0 FADD    */
              dszFMUL,     O_iST_ST,     /* DC-1 FMUL    */
              dszFCOM,     O_iST,        /* DC-2 FCOM    */
              dszFCOMP,    O_iST,        /* DC-3 FCOMP   */
              dszFSUB,     O_iST_ST,     /* DC-4 FSUB    */
              dszFSUBR,    O_iST_ST,     /* DC-5 FSUBR   */
              dszFDIV,     O_iST_ST,     /* DC-6 FDIVR   */
              dszFDIVR,    O_iST_ST },   /* DC-7 FDIV    */

/* 10  05                     x87-DD-1                   */
            { dszFLD,      O_qModrm,     /* DD-0 FLD     */
              dszRESERVED, O_DoDB,       /* DD-1         */
              dszFST,      O_qModrm,     /* DD-2 FST     */
              dszFSTP,     O_qModrm,     /* DD-3 FSTP    */
              dszFRSTOR,   O_Modrm,      /* DD-4 FRSTOR  */
              dszRESERVED, O_DoDB,       /* DD-5         */
              dszFSAVE,    O_Modrm,      /* DD-6 FSAVE   */
              dszFSTSW,    O_Modrm },    /* DD-7 FSTSW   */

/* 11                         x87-DD-2                   */
            { dszFFREE,    O_iST,        /* DD-0 FFREE   */
              dszFXCH,     O_iST,        /* DD-1 FXCH    */
              dszFST,      O_iST,        /* DD-2 FST     */
              dszFSTP,     O_iST,        /* DD-3 FSTP    */
              dszFUCOM,    O_iST,        /* DD-4 FUCOM   */
              dszFUCOMP,   O_iST,        /* DD-5 FUCOMP  */
              dszRESERVED, O_DoDB,       /* DD-6         */
              dszRESERVED, O_DoDB },     /* DD-7         */

/* 12  06                     x87-DE-1                   */
            { dszFIADD,    O_wModrm,     /* DE-0 FIADD   */
              dszFIMUL,    O_wModrm,     /* DE-1 FIMUL   */
              dszFICOM,    O_wModrm,     /* DE-2 FICOM   */
              dszFICOMP,   O_wModrm,     /* DE-3 FICOMP  */
              dszFISUB,    O_wModrm,     /* DE-4 FISUB   */
              dszFISUBR,   O_wModrm,     /* DE-5 FISUBR  */
              dszFIDIV,    O_wModrm,     /* DE-6 FIDIV   */
              dszFIDIVR,   O_wModrm },   /* DE-7 FIDIVR  */

/* 13                         x87-DE-2                   */
            { dszFADDP,    O_iST_ST,     /* DE-0 FADDP   */
              dszFMULP,    O_iST_ST,     /* DE-1 FMULP   */
              dszFCOMP,    O_iST,        /* DE-2 FCOMP   */
              dszFCOMPP,   O_NoOperands, /* DE-3 FCOMPP  */
              dszFSUBP,    O_iST_ST,     /* DE-4 FSUBP   */
              dszFSUBRP,   O_iST_ST,     /* DE-5 FSUBRP  */
              dszFDIVP,    O_iST_ST,     /* DE-6 FDIVP   */
              dszFDIVRP,   O_iST_ST },   /* DE-7 FDIVRP  */

/* 14  07                     x87-DF-1                   */
            { dszFILD,     O_wModrm,     /* DF-0 FILD    */
              dszRESERVED, O_DoDB,       /* DF-1         */
              dszFIST,     O_wModrm,     /* DF-2 FIST    */
              dszFISTP,    O_wModrm,     /* DF-3 FISTP   */
              dszFBLD,     O_tModrm,     /* DF-4 FBLD    */
              dszFILD,     O_qModrm,     /* DF-5 FILD    */
              dszFBSTP,    O_tModrm,     /* DF-6 FBSTP   */
              dszFISTP,    O_qModrm },   /* DF-7 FISTP   */

/* 15                         x87-DF-2                   */
            { dszFFREE,    O_iST,        /* DF-0 FFREE   */
              dszFXCH,     O_iST,        /* DF-1 FXCH    */
              dszFST,      O_iST,        /* DF-2 FST     */
              dszFSTP,     O_iST,        /* DF-3 FSTP    */
              dszFSTSW,    O_NoOperands, /* DF-4 FSTSW   */
              dszRESERVED, O_DoDB,       /* DF-5         */
              dszRESERVED, O_DoDB,       /* DF-6         */
              dszRESERVED, O_DoDB },     /* DF-7         */

/* 16   01            x87-D9 Mod=3 TTT=4                 */
            { dszFCHS,     O_NoOperands, /* D9-0 FCHS    */
              dszFABS,     O_NoOperands,  /* D9-1 FABS   */
              dszRESERVED, O_DoDB,       /* D9-2         */
              dszRESERVED, O_DoDB,       /* D9-3         */
              dszFTST,     O_NoOperands, /* D9-4 FTST    */
              dszFXAM,     O_NoOperands, /* D9-5 FXAM    */
              dszRESERVED, O_DoDB,       /* D9-6         */
              dszRESERVED, O_DoDB },     /* D9-7         */

/* 17   01            x87-D9 Mod=3 TTT=5                 */
            { dszFLD1,     O_NoOperands, /* D9-0 FLD1    */
              dszFLDL2T,   O_NoOperands, /* D9-1 FLDL2T  */
              dszFLDL2E,   O_NoOperands, /* D9-2 FLDL2E  */
              dszFLDPI,    O_NoOperands, /* D9-3 FLDPI   */
              dszFLDLG2,   O_NoOperands, /* D9-4 FLDLG2  */
              dszFLDLN2,   O_NoOperands, /* D9-5 FLDLN2  */
              dszFLDZ,     O_NoOperands, /* D9-6 FLDZ    */
              dszRESERVED, O_DoDB },     /* D9-7         */

/* 18   01            x87-D9 Mod=3 TTT=6                   */
            { dszF2XM1,    O_NoOperands,   /* D9-0 F2XM1   */
              dszFYL2X,    O_NoOperands,   /* D9-1 FYL2X   */
              dszFPTAN,    O_NoOperands,   /* D9-2 FPTAN   */
              dszFPATAN,   O_NoOperands,   /* D9-3 FPATAN  */
              dszFXTRACT,  O_NoOperands,   /* D9-4 FXTRACT */
              dszFPREM1,   O_NoOperands,   /* D9-5 FPREM1  */
              dszFDECSTP,  O_NoOperands,   /* D9-6 FDECSTP */
              dszFINCSTP,  O_NoOperands }, /* D9-7 FINCSTP */

/* 19   01            x87-D9 Mod=3 TTT=7                   */
            { dszFPREM,    O_NoOperands,   /* D9-0 FPREM   */
              dszFYL2XP1,  O_NoOperands,   /* D9-1 FYL2XP1 */
              dszFSQRT,    O_NoOperands,   /* D9-2 FSQRT   */
              dszFSINCOS,  O_NoOperands,   /* D9-3 FSINCOS */
              dszFRNDINT,  O_NoOperands,   /* D9-4 FRNDINT */
              dszFSCALE,   O_NoOperands,   /* D9-5 FSCALE  */
              dszFSIN,     O_NoOperands,   /* D9-6 FSIN    */
              dszFCOS,     O_NoOperands }, /* D9-7 FCOS    */

/* 20                  group 3                             */

{ dszTEST,     O_bModrm_Ib,    /* F6-0 TEST    */
              dszRESERVED, O_DoDB,         /* F6-1         */
              dszNOT,      O_bModrm,       /* F6-2 NOT     */
              dszNEG,      O_bModrm,       /* F6-3 NEG     */
              dszMUL,      O_bModrm,       /* F6-4 MUL     */
              dszIMUL,     O_bModrm,       /* F6-5 IMUL    */
              dszDIV,      O_bModrm,       /* F6-6 DIV     */
              dszIDIV,     O_bModrm },     /* F6-7 IDIV    */

/* 21                  group 3                             */
            { dszTEST,     O_vModrm_Iv,    /* F7-0 TEST    */
              dszRESERVED, O_DoDB,         /* F7-1         */
              dszNOT,      O_vModrm,       /* F7-2 NOT     */
              dszNEG,      O_vModrm,       /* F7-3 NEG     */
              dszMUL,      O_vModrm,       /* F7-4 MUL     */
              dszIMUL,     O_vModrm,       /* F7-5 IMUL    */
              dszDIV,      O_vModrm,       /* F7-6 DIV     */
              dszIDIV,     O_vModrm },     /* F7-7 IDIV    */

/* 22                  group 5                             */
            { dszINC,      O_vModrm,     /* FF-0 INC       */
              dszDEC,      O_vModrm,     /* FF-1 DEC       */
              dszCALL,     O_vModrm,     /* FF-2 CALL      */
              dszCALL,     O_fModrm,     /* FF-3 CALL      */
              dszJMP,      O_vModrm,     /* FF-4 JMP       */
              dszJMP,      O_fModrm,     /* FF-5 JMP       */
              dszPUSH,     O_vModrm,     /* FF-6 PUSH      */
              dszRESERVED, O_DoDB },     /* FF-7           */

/* 23                  group 7                             */
            { dszSGDT,     O_Modrm,      /* 0F-0 SGDT      */
              dszSIDT,     O_Modrm,      /* 0F-1 SIDT      */
              dszLGDT,     O_Modrm,      /* 0F-2 LGDT      */
              dszLIDT,     O_Modrm,      /* 0F-3 LIDT      */
              dszSMSW,     O_wModrm,     /* 0F-4 MSW       */
              dszRESERVED, O_DoDB,       /* 0F-5           */
              dszLMSW,     O_wModrm,     /* 0F-6 LMSW      */
              dszRESERVED, O_DoDB }      /* 0F-7           */

            };


REGS.C

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    regs.c

Abstract:

    This file provides access to the machine's register set.

Author:

    Wesley Witt (wesw) 1-May-1993  (ported from ntsd)

Environment:

    User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "drwatson.h"
#include "proto.h"
#include "regs.h"

ULONG  GetDregValue (PDEBUGPACKET dp, ULONG index);

char    szGsReg[]    = "gs";
char    szFsReg[]    = "fs";
char    szEsReg[]    = "es";
char    szDsReg[]    = "ds";
char    szEdiReg[]   = "edi";
char    szEsiReg[]   = "esi";
char    szEbxReg[]   = "ebx";
char    szEdxReg[]   = "edx";
char    szEcxReg[]   = "ecx";
char    szEaxReg[]   = "eax";
char    szEbpReg[]   = "ebp";
char    szEipReg[]   = "eip";
char    szCsReg[]    = "cs";
char    szEflReg[]   = "efl";
char    szEspReg[]   = "esp";
char    szSsReg[]    = "ss";
char    szDiReg[]    = "di";
char    szSiReg[]    = "si";
char    szBxReg[]    = "bx";
char    szDxReg[]    = "dx";
char    szCxReg[]    = "cx";
char    szAxReg[]    = "ax";
char    szBpReg[]    = "bp";
char    szIpReg[]    = "ip";
char    szFlReg[]    = "fl";
char    szSpReg[]    = "sp";
char    szBlReg[]    = "bl";
char    szDlReg[]    = "dl";
char    szClReg[]    = "cl";
char    szAlReg[]    = "al";
char    szBhReg[]    = "bh";
char    szDhReg[]    = "dh";
char    szChReg[]    = "ch";
char    szAhReg[]    = "ah";
char    szIoplFlag[] = "iopl";
char    szFlagOf[]   = "of";
char    szFlagDf[]   = "df";
char    szFlagIf[]   = "if";
char    szFlagTf[]   = "tf";
char    szFlagSf[]   = "sf";
char    szFlagZf[]   = "zf";
char    szFlagAf[]   = "af";
char    szFlagPf[]   = "pf";
char    szFlagCf[]   = "cf";
char    szFlagVip[]  = "vip";
char    szFlagVif[]  = "vif";

struct Reg {
        char    *psz;
        ULONG   value;
        };

struct SubReg {
        ULONG   regindex;
        ULONG   shift;
        ULONG   mask;
        };

struct Reg regname[] = {
        { szGsReg,    REGGS    },
        { szFsReg,    REGFS    },
        { szEsReg,    REGES    },
        { szDsReg,    REGDS    },
        { szEdiReg,   REGEDI   },
        { szEsiReg,   REGESI   },
        { szEbxReg,   REGEBX   },
        { szEdxReg,   REGEDX   },
        { szEcxReg,   REGECX   },
        { szEaxReg,   REGEAX   },
        { szEbpReg,   REGEBP   },
        { szEipReg,   REGEIP   },
        { szCsReg,    REGCS    },
        { szEflReg,   REGEFL   },
        { szEspReg,   REGESP   },
        { szSsReg,    REGSS    },
        { szDiReg,    REGDI    },
        { szSiReg,    REGSI    },
        { szBxReg,    REGBX    },
        { szDxReg,    REGDX    },
        { szCxReg,    REGCX    },
        { szAxReg,    REGAX    },
        { szBpReg,    REGBP    },
        { szIpReg,    REGIP    },
        { szFlReg,    REGFL    },
        { szSpReg,    REGSP    },
        { szBlReg,    REGBL    },
        { szDlReg,    REGDL    },
        { szClReg,    REGCL    },
        { szAlReg,    REGAL    },
        { szBhReg,    REGBH    },
        { szDhReg,    REGDH    },
        { szChReg,    REGCH    },
        { szAhReg,    REGAH    },
        { szIoplFlag, FLAGIOPL },
        { szFlagOf,   FLAGOF   },
        { szFlagDf,   FLAGDF   },
        { szFlagIf,   FLAGIF   },
        { szFlagTf,   FLAGTF   },
        { szFlagSf,   FLAGSF   },
        { szFlagZf,   FLAGZF   },
        { szFlagAf,   FLAGAF   },
        { szFlagPf,   FLAGPF   },
        { szFlagCf,   FLAGCF   },
        { szFlagVip,  FLAGVIP  },
        { szFlagVif,  FLAGVIF  },
        };

#define REGNAMESIZE (sizeof(regname) / sizeof(struct Reg))

struct SubReg subregname[] = {
        { REGEDI,  0, 0xffff },         //  DI register
        { REGESI,  0, 0xffff },         //  SI register
        { REGEBX,  0, 0xffff },         //  BX register
        { REGEDX,  0, 0xffff },         //  DX register
        { REGECX,  0, 0xffff },         //  CX register
        { REGEAX,  0, 0xffff },         //  AX register
        { REGEBP,  0, 0xffff },         //  BP register
        { REGEIP,  0, 0xffff },         //  IP register
        { REGEFL,  0, 0xffff },         //  FL register
        { REGESP,  0, 0xffff },         //  SP register
        { REGEBX,  0,   0xff },         //  BL register
        { REGEDX,  0,   0xff },         //  DL register
        { REGECX,  0,   0xff },         //  CL register
        { REGEAX,  0,   0xff },         //  AL register
        { REGEBX,  8,   0xff },         //  BH register
        { REGEDX,  8,   0xff },         //  DH register
        { REGECX,  8,   0xff },         //  CH register
        { REGEAX,  8,   0xff },         //  AH register
        { REGEFL, 12,      3 },         //  IOPL level value
        { REGEFL, 11,      1 },         //  OF (overflow flag)
        { REGEFL, 10,      1 },         //  DF (direction flag)
        { REGEFL,  9,      1 },         //  IF (interrupt enable flag)
        { REGEFL,  8,      1 },         //  TF (trace flag)
        { REGEFL,  7,      1 },         //  SF (sign flag)
        { REGEFL,  6,      1 },         //  ZF (zero flag)
        { REGEFL,  4,      1 },         //  AF (aux carry flag)
        { REGEFL,  2,      1 },         //  PF (parity flag)
        { REGEFL,  0,      1 },         //  CF (carry flag)
        { REGEFL, 20,      1 },         //  VIP (virtual interrupt pending)
        { REGEFL, 19,      1 }          //  VIF (virtual interrupt flag)
        };

DWORDLONG
GetRegFlagValue (PDEBUGPACKET dp, ULONG regnum)
{
    DWORDLONG value;

    if (regnum < FLAGBASE)
        value = GetRegValue(dp, regnum);
    else {
        regnum -= FLAGBASE;
        value = GetRegValue(dp, subregname[regnum].regindex);
        value = (value >> subregname[regnum].shift) & subregname[regnum].mask;
        }
    return value;
}

DWORDLONG
GetRegValue (
    PDEBUGPACKET dp,
    ULONG regnum
    )
{
    switch (regnum) {
        case REGGS:
            return dp->tctx->context.SegGs;
        case REGFS:
            return dp->tctx->context.SegFs;
        case REGES:
            return dp->tctx->context.SegEs;
        case REGDS:
            return dp->tctx->context.SegDs;
        case REGEDI:
            return dp->tctx->context.Edi;
        case REGESI:
            return dp->tctx->context.Esi;
        case REGSI:
            return(dp->tctx->context.Esi & 0xffff);
        case REGDI:
            return(dp->tctx->context.Edi & 0xffff);
        case REGEBX:
            return dp->tctx->context.Ebx;
        case REGEDX:
            return dp->tctx->context.Edx;
        case REGECX:
            return dp->tctx->context.Ecx;
        case REGEAX:
            return dp->tctx->context.Eax;
        case REGEBP:
            return dp->tctx->context.Ebp;
        case REGEIP:
            return dp->tctx->context.Eip;
        case REGCS:
            return dp->tctx->context.SegCs;
        case REGEFL:
            return dp->tctx->context.EFlags;
        case REGESP:
            return dp->tctx->context.Esp;
        case REGSS:
            return dp->tctx->context.SegSs;
        case PREGEA:
            return 0;
        case PREGEXP:
            return 0;
        case PREGRA: {
            struct {
                ULONG   oldBP;
                ULONG   retAddr;
            } stackRead;
            DoMemoryRead( dp,
                          (LPVOID)dp->tctx->context.Ebp,
                          (LPVOID)&stackRead,
                          sizeof(stackRead),
                          NULL
                        );
            return stackRead.retAddr;
            }
        case PREGP:
            return 0;
        case REGDR0:
            return dp->tctx->context.Dr0;
        case REGDR1:
            return dp->tctx->context.Dr1;
        case REGDR2:
            return dp->tctx->context.Dr2;
        case REGDR3:
            return dp->tctx->context.Dr3;
        case REGDR6:
            return dp->tctx->context.Dr6;
        case REGDR7:
            return dp->tctx->context.Dr7;
        default:
            return 0;
        }
}

ULONG
GetRegString (PUCHAR pszString)
{
    ULONG   count;

    for (count = 0; count < REGNAMESIZE; count++) {
        if (!strcmp(pszString, regname[count].psz)) {
            return regname[count].value;
        }
    }
    return (ULONG)-1;
}

void
OutputAllRegs( PDEBUGPACKET dp, BOOL Show64 )
{
    lprintfs("eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\r\n",
                (DWORD)GetRegValue(dp,REGEAX),
                (DWORD)GetRegValue(dp,REGEBX),
                (DWORD)GetRegValue(dp,REGECX),
                (DWORD)GetRegValue(dp,REGEDX),
                (DWORD)GetRegValue(dp,REGESI),
                (DWORD)GetRegValue(dp,REGEDI));

    lprintfs("eip=%08lx esp=%08lx ebp=%08lx iopl=%1lx "
        "%s %s %s %s %s %s %s %s %s %s\r\n",
                (DWORD)GetRegValue(dp,REGEIP),
                (DWORD)GetRegValue(dp,REGESP),
                (DWORD)GetRegValue(dp,REGEBP),
                (DWORD)GetRegFlagValue(dp,FLAGIOPL),
        (DWORD)GetRegFlagValue(dp,FLAGVIP) ? "vip" : "   ",
        (DWORD)GetRegFlagValue(dp,FLAGVIF) ? "vif" : "   ",
        (DWORD)GetRegFlagValue(dp,FLAGOF) ? "ov" : "nv",
        (DWORD)GetRegFlagValue(dp,FLAGDF) ? "dn" : "up",
        (DWORD)GetRegFlagValue(dp,FLAGIF) ? "ei" : "di",
        (DWORD)GetRegFlagValue(dp,FLAGSF) ? "ng" : "pl",
        (DWORD)GetRegFlagValue(dp,FLAGZF) ? "zr" : "nz",
        (DWORD)GetRegFlagValue(dp,FLAGAF) ? "ac" : "na",
        (DWORD)GetRegFlagValue(dp,FLAGPF) ? "po" : "pe",
        (DWORD)GetRegFlagValue(dp,FLAGCF) ? "cy" : "nc");
    lprintfs("cs=%04lx  ss=%04lx  ds=%04lx  es=%04lx  fs=%04lx  gs=%04lx"
        "             efl=%08lx\r\n",
                (DWORD)GetRegValue(dp,REGCS),
                (DWORD)GetRegValue(dp,REGSS),
                (DWORD)GetRegValue(dp,REGDS),
                (DWORD)GetRegValue(dp,REGES),
                (DWORD)GetRegValue(dp,REGFS),
                (DWORD)GetRegValue(dp,REGGS),
        (DWORD)GetRegFlagValue(dp,REGEFL));
    lprintfs("\r\n\r\n");
}

void
OutputOneReg (PDEBUGPACKET dp, ULONG regnum, BOOL Show64)
{
    DWORD value;

    value = (DWORD)GetRegFlagValue(dp,regnum);
    if (regnum < FLAGBASE) {
        lprintfs("%08lx\r\n", value);
    } else {
        lprintfs("%lx\r\n", value);
    }
}

ULONG
GetDregValue (PDEBUGPACKET dp, ULONG index)
{
    if (index < 4) {
        index += REGDR0;
    } else {
        index += REGDR6 - 6;
    }
    return (DWORD)GetRegValue(dp,index);
}

PUCHAR
RegNameFromIndex (ULONG index)
{
    ULONG    count;

    for (count = 0; count < REGNAMESIZE; count++) {
        if (regname[count].value == index) {
            return regname[count].psz;
        }
    }
    return NULL;
}


REGS.H

#define REGGS           0
#define REGFS           1
#define REGES           2
#define REGDS           3
#define REGEDI          4
#define REGESI          5
#define REGEBX          6
#define REGEDX          7
#define REGECX          8
#define REGEAX          9
#define REGEBP          10
#define REGEIP          11
#define REGCS           12
#define REGEFL          13
#define REGESP          14
#define REGSS           15

#ifdef  KERNEL
#define REGCR0          16
#define REGCR2          17
#define REGCR3          18
#define REGCR4          19
#endif

#define REGDR0          20
#define REGDR1          21
#define REGDR2          22
#define REGDR3          23
#define REGDR6          24
#define REGDR7          25

#ifdef  KERNEL
#define REGGDTR         26
#define REGGDTL         27
#define REGIDTR         28
#define REGIDTL         29
#define REGTR           30
#define REGLDTR         31
#endif

// Pseudo-registers:
#define PREGEA          40
#define PREGBASE    PREGEA
#define PREGEXP         41
#define PREGRA          42
#define PREGP           43
#define PREGU0          44
#define PREGU1          45
#define PREGU2          46
#define PREGU3          47
#define PREGU4          48
#define PREGU5          49
#define PREGU6          50
#define PREGU7          51
#define PREGU8          52
#define PREGU9          53

#define FLAGBASE        100
#define REGDI           100
#define REGSI           101
#define REGBX           102
#define REGDX           103
#define REGCX           104
#define REGAX           105
#define REGBP           106
#define REGIP           107
#define REGFL           108
#define REGSP           109
#define REGBL           110
#define REGDL           111
#define REGCL           112
#define REGAL           113
#define REGBH           114
#define REGDH           115
#define REGCH           116
#define REGAH           117
#define FLAGIOPL        118
#define FLAGOF          119
#define FLAGDF          120
#define FLAGIF          121
#define FLAGTF          122
#define FLAGSF          123
#define FLAGZF          124
#define FLAGAF          125
#define FLAGPF          126
#define FLAGCF          127
#define FLAGVIP         128
#define FLAGVIF         129

#define REGFIR          REGEIP


CV.H

/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    cv.h

Abstract:

    This file contains all of the type definitions for accessing
    CODEVIEW data.

Author:

    Wesley A. Witt (wesw) 26-March-1992

Environment:

    Win32, User Mode

--*/

#define _REAL10

#include "cvinfo.h"
#include "cvexefmt.h"


CVEXEFMT.H

/***    cvexefmt.h - format of CodeView information in exe
 *
 *      Structures, constants, etc. for reading CodeView information
 *      from the executable.
 *
 */

/***    The master copy of this file resides in the CodeView project.
 *      All Microsoft projects are required to use the master copy without
 *      modification.  Modification of the master version or a copy
 *      without consultation with all parties concerned is extremely
 *      risky.
 *
 */

//  The following structures and constants describe the format of the
//  CodeView Debug OMF for that will be accepted by CodeView 4.0 and
//  later.  These are executables with signatures of NB05, NB06 and NB07.
//  There is some confusion about the signatures NB03 and NB04 so none
//  of the utilites will accept executables with these signatures.

//  All of the structures described below must start on a long word boundary
//  to maintain natural alignment.  Pad space can be inserted during the
//  write operation and the addresses adjusted without affecting the contents
//  of the structures.

//  Type of subsection entry.

#define sstModule           0x120
#define sstTypes            0x121
#define sstPublic           0x122
#define sstPublicSym        0x123   // publics as symbol (waiting for link)
#define sstSymbols          0x124
#define sstAlignSym         0x125
#define sstSrcLnSeg         0x126   // because link doesn't emit SrcModule
#define sstSrcModule        0x127
#define sstLibraries        0x128
#define sstGlobalSym        0x129
#define sstGlobalPub        0x12a
#define sstGlobalTypes      0x12b
#define sstMPC              0x12c
#define sstSegMap           0x12d
#define sstSegName          0x12e
#define sstPreComp          0x12f   // precompiled types
#define sstPreCompMap       0x130   // map precompiled types in global types
#define sstOffsetMap16      0x131
#define sstOffsetMap32      0x132
#define sstFileIndex        0x133
#define sstStaticSym        0x134

typedef enum OMFHash {
    OMFHASH_NONE,           // no hashing
    OMFHASH_SUMUC16,        // upper case sum of chars in 16 bit table
    OMFHASH_SUMUC32,        // upper case sum of chars in 32 bit table
    OMFHASH_ADDR16,         // sorted by increasing address in 16 bit table
    OMFHASH_ADDR32          // sorted by increasing address in 32 bit table
} OMFHASH;

//  CodeView Debug OMF signature.  The signature at the end of the file is
//  a negative offset from the end of the file to another signature.  At
//  the negative offset (base address) is another signature whose filepos
//  field points to the first OMFDirHeader in a chain of directories.
//  The NB05 signature is used by the link utility to indicated a completely
//  unpacked file.  The NB06 signature is used by ilink to indicate that the
//  executable has had CodeView information from an incremental link appended
//  to the executable.  The NB07 signature is used by cvpack to indicate that
//  the CodeView Debug OMF has been packed.  CodeView will only process
//  executables with the NB07 signature.

typedef struct OMFSignature {
    char            Signature[4];   // "NB05", "NB06" or "NB07"
    long            filepos;        // offset in file
} OMFSignature;

//  directory information structure
//  This structure contains the information describing the directory.
//  It is pointed to by the signature at the base address or the directory
//  link field of a preceeding directory.  The directory entries immediately
//  follow this structure.

typedef struct OMFDirHeader {
    unsigned short  cbDirHeader;    // length of this structure
    unsigned short  cbDirEntry;     // number of bytes in each directory entry
    unsigned long   cDir;           // number of directorie entries
    long            lfoNextDir;     // offset from base of next directory
    unsigned long   flags;          // status flags
} OMFDirHeader;

//  directory structure
//  The data in this structure is used to reference the data for each
//  subsection of the CodeView Debug OMF information.  Tables that are
//  not associated with a specific module will have a module index of
//  oxffff.  These tables are the global types table, the global symbol
//  table, the global public table and the library table.

typedef struct OMFDirEntry {
    unsigned short  SubSection;     // subsection type (sst...)
    unsigned short  iMod;           // module index
    long            lfo;            // large file offset of subsection
    unsigned long   cb;             // number of bytes in subsection
} OMFDirEntry;

//  information decribing each segment in a module

typedef struct OMFSegDesc {
    unsigned short  Seg;            // segment index
    unsigned short  pad;            // pad to maintain alignment
    unsigned long   Off;            // offset of code in segment
    unsigned long   cbSeg;          // number of bytes in segment
} OMFSegDesc;

//  per module information
//  There is one of these subsection entries for each module
//  in the executable.  The entry is generated by link/ilink.
//  This table will probably require padding because of the
//  variable length module name.

typedef struct OMFModule {
    unsigned short  ovlNumber;      // overlay number
    unsigned short  iLib;           // library that the module was linked from
    unsigned short  cSeg;           // count of number of segments in module
    char            Style[2];       // debugging style "CV"
    OMFSegDesc      SegInfo[1];     // describes segments in module
    char            Name[];         // length prefixed module name padded to
                                    // long word boundary
} OMFModule;

//  Symbol hash table format
//  This structure immediately preceeds the global publics table
//  and global symbol tables.

typedef struct  OMFSymHash {
    unsigned short  symhash;        // symbol hash function index
    unsigned short  addrhash;       // address hash function index
    unsigned long   cbSymbol;       // length of symbol information
    unsigned long   cbHSym;         // length of symbol hash data
    unsigned long   cbHAddr;        // length of address hashdata
} OMFSymHash;

//  Global types subsection format
//  This structure immediately preceeds the global types table.
//  The offsets in the typeOffset array are relative to the address
//  of ctypes.  Each type entry following the typeOffset array must
//  begin on a long word boundary.

typedef struct OMFTypeFlags {
    unsigned long   sig     :8;
    unsigned long   unused  :24;
} OMFTypeFlags;

typedef struct OMFGlobalTypes {
    OMFTypeFlags    flags;
    unsigned long   cTypes;         // number of types
    unsigned long   typeOffset[];   // array of offsets to types
} OMFGlobalTypes;

//  Precompiled types mapping table
//  This table should be ignored by all consumers except the incremental
//  packer.

typedef struct OMFPreCompMap {
    unsigned short  FirstType;      // first precompiled type index
    unsigned short  cTypes;         // number of precompiled types
    unsigned long   signature;      // precompiled types signature
    unsigned short  pad;
    CV_typ_t        map[];          // mapping of precompiled types
} OMFPreCompMap;

//  Source line to address mapping table.
//  This table is generated by the link/ilink utility from line number
//  information contained in the object file OMF data.  This table contains
//  only the code contribution for one segment from one source file.

typedef struct OMFSourceLine {
    unsigned short  Seg;            // linker segment index
    unsigned short  cLnOff;         // count of line/offset pairs
    unsigned long   offset[1];      // array of offsets in segment
    unsigned short  lineNbr[1];     // array of line lumber in source
} OMFSourceLine;

typedef OMFSourceLine FAR * LPSL;

//  Source file description
//  This table is generated by the linker

typedef struct OMFSourceFile {
    unsigned short  cSeg;           // number of segments from source file
    unsigned short  reserved;       // reserved
    unsigned long   baseSrcLn[1];   // base of OMFSourceLine tables
                                    // this array is followed by array
                                    // of segment start/end pairs followed by
                                    // an array of linker indices
                                    // for each segment in the file
    unsigned short  cFName;         // length of source file name
    char            Name;           // name of file padded to long boundary
} OMFSourceFile;

typedef OMFSourceFile FAR * LPSF;

//  Source line to address mapping header structure
//  This structure describes the number and location of the
//  OMFAddrLine tables for a module.  The offSrcLine entries are
//  relative to the beginning of this structure.

typedef struct OMFSourceModule {
    unsigned short  cFile;          // number of OMFSourceTables
    unsigned short  cSeg;           // number of segments in module
    unsigned long   baseSrcFile[1]; // base of OMFSourceFile table
                                    // this array is followed by array
                                    // of segment start/end pairs followed
                                    // by an array of linker indices
                                    // for each segment in the module
} OMFSourceModule;

typedef OMFSourceModule FAR * LPSM;

//  sstLibraries

typedef struct OMFLibrary {
    unsigned char   cbLibs;     // count of library names
    char            Libs[1];    // array of length prefixed lib names (first entry zero length)
} OMFLibrary;

//  Pcode support.  This subsection contains debug information generated
//  by the MPC utility used to process Pcode executables.  Currently
//  it contains a mapping table from segment index (zero based) to
//  frame paragraph.  MPC converts segmented exe's to non-segmented
//  exe's for DOS support.  To avoid backpatching all CV info, this
//  table is provided for the mapping.  Additional info may be provided
//  in the future for profiler support.

typedef struct OMFMpcDebugInfo {
    unsigned short  cSeg;       // number of segments in module
    unsigned short  mpSegFrame[1];  // map seg (zero based) to frame
} OMFMpcDebugInfo;

//  The following structures and constants describe the format of the
//  CodeView Debug OMF for linkers that emit executables with the NB02
//  signature.  Current utilities with the exception of cvpack and cvdump
//  will not accept or emit executables with the NB02 signature.  Cvdump
//  will dump an unpacked executable with the NB02 signature.  Cvpack will
//  read an executable with the NB02 signature but the packed executable
//  will be written with the table format, contents and signature of NB07.

//  subsection type constants

#define SSTMODULE       0x101   // Basic info. about object module
#define SSTPUBLIC       0x102   // Public symbols
#define SSTTYPES        0x103   // Type information
#define SSTSYMBOLS      0x104   // Symbol Data
#define SSTSRCLINES     0x105   // Source line information
#define SSTLIBRARIES    0x106   // Names of all library files used
#define SSTIMPORTS      0x107   // Symbols for DLL fixups
#define SSTCOMPACTED    0x108   // Compacted types section
#define SSTSRCLNSEG     0x109   // Same as source lines, contains segment

typedef struct DirEntry{
    unsigned short  SubSectionType;
    unsigned short  ModuleIndex;
    long            lfoStart;
    unsigned short  Size;
} DirEntry;

//  information decribing each segment in a module

typedef struct oldnsg {
    unsigned short  Seg;            // segment index
    unsigned short  Off;            // offset of code in segment
    unsigned short  cbSeg;          // number of bytes in segment
} oldnsg;

//   old subsection module information

typedef struct oldsmd {
    oldnsg          SegInfo;        // describes first segment in module
    unsigned short  ovlNbr;         // overlay number
    unsigned short  iLib;
    unsigned char   cSeg;           // Number of segments in module
    char            reserved;
    unsigned char   cbName[1];      // length prefixed name of module
    oldnsg          arnsg[];        // cSeg-1 structures exist for alloc text or comdat code
} oldsmd;

typedef struct{
    unsigned short  Seg;
    unsigned long   Off;
    unsigned long   cbSeg;
} oldnsg32;

typedef struct {
    oldnsg32        SegInfo;        // describes first segment in module
    unsigned short  ovlNbr;         // overlay number
    unsigned short  iLib;
    unsigned char   cSeg;           // Number of segments in module
    char            reserved;
    unsigned char   cbName[1];      // length prefixed name of module
    oldnsg32        arnsg[];        // cSeg-1 structures exist for alloc text or comdat code
} oldsmd32;


CVFMT.H

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    cvfmt.h

Abstract:

    Formatting array for cv data types

Author:

    Wesley Witt (wesw) 20-July-1993

Environment:

    User Mode

--*/

#define typecount  (sizeof (nametype) / sizeof (nametype[0]))

struct typestr {
    USHORT      typ;          // cv data type
    USHORT      mode;         // mode
    USHORT      size;         // data size
    LPSTR       name;         // data type name string
    LPSTR       fmt;          // printf format string
};

static struct typestr nametype[] = {
    T_ABS,                  0, 0, "absolute",                 "",
    T_NOTYPE,               0, 0, "<no type>",                "",
    T_SEGMENT,              0, 0, "_segment",                 "",

    T_CHAR,      CV_TM_DIRECT, 0, "char",                     "",
    T_SHORT,     CV_TM_DIRECT, 0, "short",                    "",
    T_LONG,      CV_TM_DIRECT, 0, "long",                     "",
    T_QUAD,      CV_TM_DIRECT, 0, "quad",                     "",
    T_UCHAR,     CV_TM_DIRECT, 0, "unsigned char",            "",
    T_USHORT,    CV_TM_DIRECT, 2, "unsigned short",           "0x%04hx",
    T_ULONG,     CV_TM_DIRECT, 4, "unsigned long",            "0x%08x",
    T_UQUAD,     CV_TM_DIRECT, 0, "unsigned quad",            "",
    T_REAL32,    CV_TM_DIRECT, 0, "float",                    "",
    T_REAL64,    CV_TM_DIRECT, 0, "double",                   "",
    T_REAL80,    CV_TM_DIRECT, 0, "long double",              "",
    T_VOID,      CV_TM_DIRECT, 0, "void",                     "",
    T_INT2,      CV_TM_DIRECT, 4, "int",                      "0x%08x",
    T_UINT2,     CV_TM_DIRECT, 0, "unsigned int",             "",
    T_INT4,      CV_TM_DIRECT, 4, "int",                      "0x%08x",
    T_UINT4,     CV_TM_DIRECT, 0, "unsigned int",             "",
    T_INT8,      CV_TM_DIRECT, 4, "int",                      "0x%08x",
    T_UINT8,     CV_TM_DIRECT, 0, "unsigned int",             "",
    T_RCHAR,     CV_TM_DIRECT, 0, "char",                     "",

    T_CHAR,      CV_TM_NPTR,   0, "char *",                   "",
    T_SHORT,     CV_TM_NPTR,   0, "short *",                  "",
    T_LONG,      CV_TM_NPTR,   0, "long *",                   "",
    T_QUAD,      CV_TM_NPTR,   0, "quad *",                   "",
    T_UCHAR,     CV_TM_NPTR,   0, "unsigned char *",          "",
    T_USHORT,    CV_TM_NPTR,   0, "unsigned short *",         "",
    T_ULONG,     CV_TM_NPTR,   0, "unsigned long *",          "",
    T_UQUAD,     CV_TM_NPTR,   0, "unsigned quad *",          "",
    T_REAL32,    CV_TM_NPTR,   0, "float *",                  "",
    T_REAL64,    CV_TM_NPTR,   0, "double *",                 "",
    T_REAL80,    CV_TM_NPTR,   0, "long double *",            "",
    T_VOID,      CV_TM_NPTR,   0, "void *",                   "",
    T_INT2,      CV_TM_NPTR,   0, "int *",                    "",
    T_UINT2,     CV_TM_NPTR,   0, "unsigned int *",           "",
    T_INT4,      CV_TM_NPTR,   0, "int *",                    "",
    T_UINT4,     CV_TM_NPTR,   0, "unsigned int *",           "",
    T_INT8,      CV_TM_NPTR,   0, "int *",                    "",
    T_UINT8,     CV_TM_NPTR,   0, "unsigned int *",           "",
    T_RCHAR,     CV_TM_NPTR,   0, "char *",                   "",
    T_32PRCHAR,  CV_TM_NPTR,   4, "char *",                   "0x%08x",

    T_CHAR,      CV_TM_FPTR,   0, "char far *",               "",
    T_SHORT,     CV_TM_FPTR,   0, "short far *",              "",
    T_LONG,      CV_TM_FPTR,   0, "long far *",               "",
    T_QUAD,      CV_TM_FPTR,   0, "quad far *",               "",
    T_UCHAR,     CV_TM_FPTR,   0, "unsigned char far *",      "",
    T_USHORT,    CV_TM_FPTR,   0, "unsigned short far *",     "",
    T_ULONG,     CV_TM_FPTR,   0, "unsigned long far *",      "",
    T_UQUAD,     CV_TM_FPTR,   0, "unsigned quad far *",      "",
    T_REAL32,    CV_TM_FPTR,   0, "float far *",              "",
    T_REAL64,    CV_TM_FPTR,   0, "double far *",             "",
    T_REAL80,    CV_TM_FPTR,   0, "long double far *",        "",
    T_VOID,      CV_TM_FPTR,   0, "void far *",               "",
    T_INT2,      CV_TM_FPTR,   0, "int far *",                "",
    T_UINT2,     CV_TM_FPTR,   0, "unsigned int far *",       "",
    T_INT4,      CV_TM_FPTR,   0, "int far *",                "",
    T_UINT4,     CV_TM_FPTR,   0, "unsigned int far *",       "",
    T_INT8,      CV_TM_FPTR,   0, "int far *",                "",
    T_UINT8,     CV_TM_FPTR,   0, "unsigned int far *",       "",
    T_RCHAR,     CV_TM_FPTR,   0, "char far *",               "",

    T_CHAR,      CV_TM_HPTR,   0, "char huge *",              "",
    T_SHORT,     CV_TM_HPTR,   0, "short huge *",             "",
    T_LONG,      CV_TM_HPTR,   0, "long huge *",              "",
    T_QUAD,      CV_TM_HPTR,   0, "quad huge *",              "",
    T_UCHAR,     CV_TM_HPTR,   0, "unsigned char huge *",     "",
    T_USHORT,    CV_TM_HPTR,   0, "unsigned short huge *",    "",
    T_ULONG,     CV_TM_HPTR,   0, "unsigned long huge *",     "",
    T_UQUAD,     CV_TM_HPTR,   0, "unsigned quad huge *",     "",
    T_REAL32,    CV_TM_HPTR,   0, "float huge *",             "",
    T_REAL64,    CV_TM_HPTR,   0, "double huge *",            "",
    T_REAL80,    CV_TM_HPTR,   0, "long double huge *",       "",
    T_VOID,      CV_TM_HPTR,   0, "void huge *",              "",
    T_INT2,      CV_TM_HPTR,   0, "int huge *",               "",
    T_UINT2,     CV_TM_HPTR,   0, "unsigned int huge *",      "",
    T_INT4,      CV_TM_HPTR,   0, "int huge *",               "",
    T_UINT4,     CV_TM_HPTR,   0, "unsigned int huge *",      "",
    T_INT8,      CV_TM_HPTR,   0, "int huge *",               "",
    T_UINT8,     CV_TM_HPTR,   0, "unsigned int huge *",      "",
    T_RCHAR,     CV_TM_HPTR,   0, "char huge *",              "",

    T_CHAR,      CV_TM_NPTR32, 0, "char *",                   "",
    T_SHORT,     CV_TM_NPTR32, 0, "short *",                  "",
    T_LONG,      CV_TM_NPTR32, 0, "long *",                   "",
    T_QUAD,      CV_TM_NPTR32, 0, "quad *",                   "",
    T_UCHAR,     CV_TM_NPTR32, 0, "unsigned char *",          "",
    T_USHORT,    CV_TM_NPTR32, 0, "unsigned short *",         "",
    T_ULONG,     CV_TM_NPTR32, 0, "unsigned long *",          "",
    T_UQUAD,     CV_TM_NPTR32, 0, "unsigned quad *",          "",
    T_REAL32,    CV_TM_NPTR32, 0, "float *",                  "",
    T_REAL64,    CV_TM_NPTR32, 0, "double *",                 "",
    T_REAL80,    CV_TM_NPTR32, 0, "long double *",            "",
    T_VOID,      CV_TM_NPTR32, 0, "void *",                   "",
    T_INT2,      CV_TM_NPTR32, 0, "int *",                    "",
    T_UINT2,     CV_TM_NPTR32, 0, "unsigned int *",           "",
    T_INT4,      CV_TM_NPTR32, 0, "int *",                    "",
    T_UINT4,     CV_TM_NPTR32, 0, "unsigned int *",           "",
    T_INT8,      CV_TM_NPTR32, 0, "int *",                    "",
    T_UINT8,     CV_TM_NPTR32, 0, "unsigned int *",           "",
    T_RCHAR,     CV_TM_NPTR32, 0, "char *",                   "",

    T_CHAR,      CV_TM_FPTR32, 0, "char far32 *",             "",
    T_SHORT,     CV_TM_FPTR32, 0, "short far32 *",            "",
    T_LONG,      CV_TM_FPTR32, 0, "long far32 *",             "",
    T_QUAD,      CV_TM_FPTR32, 0, "quad far32 *",             "",
    T_UCHAR,     CV_TM_FPTR32, 0, "unsigned char far32 *",    "",
    T_USHORT,    CV_TM_FPTR32, 0, "unsigned short far32 *",   "",
    T_ULONG,     CV_TM_FPTR32, 0, "unsigned long far32 *",    "",
    T_UQUAD,     CV_TM_FPTR32, 0, "unsigned quad far32 *",    "",
    T_REAL32,    CV_TM_FPTR32, 0, "float far32 *",            "",
    T_REAL64,    CV_TM_FPTR32, 0, "double far32 *",           "",
    T_REAL80,    CV_TM_FPTR32, 0, "long double far32 *",      "",
    T_VOID,      CV_TM_FPTR32, 0, "void far32 *",             "",
    T_INT2,      CV_TM_FPTR32, 0, "int far32 *",              "",
    T_UINT2,     CV_TM_FPTR32, 0, "unsigned int far32 *",     "",
    T_INT4,      CV_TM_FPTR32, 0, "int far32 *",              "",
    T_UINT4,     CV_TM_FPTR32, 0, "unsigned int far32 *",     "",
    T_INT8,      CV_TM_FPTR32, 0, "int far32 *",              "",
    T_UINT8,     CV_TM_FPTR32, 0, "unsigned int far32 *",     "",
    T_RCHAR,     CV_TM_FPTR32, 0, "char far32 *",             "",
};


CVINFO.H

/***    cvinfo.h - Generic CodeView information definitions
 *
 *      Structures, constants, etc. for accessing and interpreting
 *      CodeView information.
 *
 */

/***    The master copy of this file resides in the CodeView project.
 *      All Microsoft projects are required to use the master copy without
 *      modification.  Modification of the master version or a copy
 *      without consultation with all parties concerned is extremely
 *      risky.
 *
 *      The projects known to use this version (4.00.00) are:
 *
 *          Codeview
 *          Sequoia
 *          C7.00 (all passes)
 *          Cvpack
 *          Cvdump
 */

#ifndef _CV_INFO_INCLUDED
#define _CV_INFO_INCLUDED

#pragma pack(1)
typedef unsigned long   CV_uoff32_t;
typedef          long   CV_off32_t;
typedef unsigned short  CV_uoff16_t;
typedef          short  CV_off16_t;
typedef unsigned short  CV_typ_t;

#ifdef _REAL10
typedef struct {
    char b[10];
} REAL10;
#endif

#define CV_SIGNATURE_C6         0L /* Actual signature is >64K */
#define CV_SIGNATURE_C7         1L /* First explicit signature */
#define CV_SIGNATURE_RESERVED   2L /* All signatures from 2 to 64K are reserved */

#define CV_MAXOFFSET   0xffffffff

/**     CodeView Symbol and Type OMF type information is broken up into two
 *      ranges.  Type indices less than 0x1000 describe frequently used
 *      type information that is frequently used.  Type indices above
 *      0x1000 are used to describe more complex features such as function,
 *      arrays and structures.
 */

/**     Primitive types have predefined meaning that is encoded in the
 *      values of the various bit fields in the value.
 *
 *      A CodeView primitive type is defined as:
 *
 *      1 1
 *      1 089  7654  3  210
 *      r mode type  r  sub
 *
 *      Where
 *          mode is the pointer mode
 *          type is a type indicator
 *          sub  is a subtype enumeration
 *          r    is a reserved field
 *
 *      See Microsoft Symbol and Type OMF (Version 4.0) for more
 *      information.
 */

#define CV_MMASK        0x700   /* mode mask */
#define CV_TMASK        0x0f0   /* type mask */
#define CV_SMASK        0x007   /* subtype mask */

#define CV_MSHIFT       8       /* primitive mode right shift count */
#define CV_TSHIFT       4       /* primitive type right shift count */
#define CV_SSHIFT       0       /* primitive subtype right shift count */

/*
 *      macros to extract primitive mode, type and size
 */

#define CV_MODE(typ)    (((typ) & CV_MMASK) >> CV_MSHIFT)
#define CV_TYPE(typ)    (((typ) & CV_TMASK) >> CV_TSHIFT)
#define CV_SUBT(typ)    (((typ) & CV_SMASK) >> CV_SSHIFT)

/*
 *      macros to insert new primitive mode, type and size
 */

#define CV_NEWMODE(typ, nm)     (((typ) & ~CV_MMASK) | ((nm) << CV_MSHIFT))
#define CV_NEWTYPE(typ, nt)     (((typ) & ~CV_TMASK) | ((nt) << CV_TSHIFT))
#define CV_NEWSUBT(typ, ns)     (((typ) & ~CV_SMASK) | ((ns) << CV_SSHIFT))

/*
 *      pointer mode enumeration values
 */

typedef enum CV_prmode_e {
    CV_TM_DIRECT = 0,           /* mode is not a pointer */
    CV_TM_NPTR   = 1,           /* mode is a near pointer */
    CV_TM_FPTR   = 2,           /* mode is a far pointer */
    CV_TM_HPTR   = 3,           /* mode is a huge pointer */
    CV_TM_NPTR32 = 4,           /* mode is a 32 bit near pointer */
    CV_TM_FPTR32 = 5,           /* mode is a 32 bit far pointer */
    CV_TM_NPTR64 = 6            /* mode is a 64 bit near pointer */
} CV_prmode_e;

/*
 *      type enumeration values
 */

typedef enum CV_type_e {
    CV_SPECIAL      = 0x00,     /* special type size values */
    CV_SIGNED       = 0x01,     /* signed integral size values */
    CV_UNSIGNED     = 0x02,     /* unsigned integral size values */
    CV_BOOLEAN      = 0x03,     /* Boolean size values */
    CV_REAL         = 0x04,     /* real number size values */
    CV_COMPLEX      = 0x05,     /* complex number size values */
    CV_SPECIAL2     = 0x06,     /* second set of special types */
    CV_INT          = 0x07,     /* integral (int) values */
    CV_CVRESERVED   = 0x0f
} CV_type_e;

/*
 *      subtype enumeration values for CV_SPECIAL
 */

typedef enum CV_special_e {
    CV_SP_NOTYPE    = 0x00,
    CV_SP_ABS       = 0x01,
    CV_SP_SEGMENT   = 0x02,
    CV_SP_VOID      = 0x03,
    CV_SP_CURRENCY  = 0x04,
    CV_SP_NBASICSTR = 0x05,
    CV_SP_FBASICSTR = 0x06,
    CV_SP_NOTTRANS  = 0x07
} CV_special_e;

/*
 *      subtype enumeration values for CV_SPECIAL2
 */

typedef enum CV_special2_e {
    CV_S2_BIT       = 0x00,
    CV_S2_PASCHAR   = 0x01      /* Pascal CHAR */
} CV_special2_e;

/*
 *      subtype enumeration values for CV_SIGNED, CV_UNSIGNED and CV_BOOLEAN
 */

typedef enum CV_integral_e {
    CV_IN_1BYTE     = 0x00,
    CV_IN_2BYTE     = 0x01,
    CV_IN_4BYTE     = 0x02,
    CV_IN_8BYTE     = 0x03
} CV_integral_e;

/*
 *      subtype enumeration values for CV_REAL and CV_COMPLEX
 */

typedef enum CV_real_e {
    CV_RC_REAL32    = 0x00,
    CV_RC_REAL64    = 0x01,
    CV_RC_REAL80    = 0x02,
    CV_RC_REAL128   = 0x03,
    CV_RC_REAL48    = 0x04
} CV_real_e;

/*
 *      subtype enumeration values for CV_INT (really int)
 */

typedef enum CV_int_e {
    CV_RI_CHAR      = 0x00,
    CV_RI_WCHAR     = 0x01,
    CV_RI_INT2      = 0x02,
    CV_RI_UINT2     = 0x03,
    CV_RI_INT4      = 0x04,
    CV_RI_UINT4     = 0x05,
    CV_RI_INT8      = 0x06,
    CV_RI_UINT8     = 0x07
} CV_int_e;

/*
 *      macros to check the type of a primitive
 */

#define CV_TYP_IS_DIRECT(typ)   (CV_MODE(typ) == CV_TM_DIRECT)
#define CV_TYP_IS_PTR(typ)      (CV_MODE(typ) != CV_TM_DIRECT)
#define CV_TYP_IS_NPTR(typ)     (CV_MODE(typ) == CV_TM_NPTR)
#define CV_TYP_IS_FPTR(typ)     (CV_MODE(typ) == CV_TM_FPTR)
#define CV_TYP_IS_HPTR(typ)     (CV_MODE(typ) == CV_TM_HPTR)
#define CV_TYP_IS_NPTR32(typ)   (CV_MODE(typ) == CV_TM_NPTR32)
#define CV_TYP_IS_FPTR32(typ)   (CV_MODE(typ) == CV_TM_FPTR32)

#define CV_TYP_IS_SIGNED(typ)(((CV_TYPE(typ) == CV_SIGNED)  && \
                                        CV_TYP_IS_DIRECT(typ)) || \
                                        (typ == T_INT2)        || \
                                        (typ == T_INT4)        || \
                                        (typ == T_INT8)        || \
                                        (typ == T_RCHAR))

#define CV_TYP_IS_UNSIGNED(typ) (((CV_TYPE(typ) == CV_UNSIGNED) && \
                                         CV_TYP_IS_DIRECT(typ)) || \
                                         (typ == T_UINT2)       || \
                                         (typ == T_UINT4)       || \
                                         (typ == T_UINT8)       || \
                                         (typ == T_RCHAR))

#define CV_TYP_IS_REAL(typ)     ((CV_TYPE(typ) == CV_REAL)  && CV_TYP_IS_DIRECT(typ))

#define CV_FIRST_NONPRIM 0x1000
#define CV_IS_PRIMITIVE(typ)       ((typ) < CV_FIRST_NONPRIM)

/*
 *      selected values for type_index - for a more complete definition, see
 *      Microsoft Symbol and Type OMF document
 */

/*
 *      Special Types
 */

#define T_NOTYPE        0x0000  /* uncharacterized type (no type) */
#define T_ABS           0x0001  /* absolute symbol */
#define T_SEGMENT       0x0002  /* segment type */
#define T_VOID          0x0003  /* void */
#define T_PVOID         0x0103  /* near pointer to void */
#define T_PFVOID        0x0203  /* far pointer to void */
#define T_PHVOID        0x0303  /* huge pointer to void */
#define T_32PVOID       0x0403  /* 16:32 near pointer to void */
#define T_32PFVOID      0x0503  /* 16:32 far pointer to void */
#define T_CURRENCY      0x0004  /* BASIC 8 byte currency value */
#define T_NBASICSTR     0x0005  /* Near BASIC string */
#define T_FBASICSTR     0x0006  /* Far BASIC string */
#define T_NOTTRANS      0x0007  /* type not translated by cvpack */
#define T_BIT           0x0060  /* bit */
#define T_PASCHAR       0x0061  /* Pascal CHAR */

/*
 *      Character types
 */

#define T_CHAR          0x0010  /* 8 bit signed */
#define T_UCHAR         0x0020  /* 8 bit unsigned */
#define T_PCHAR         0x0110  /* near pointer to 8 bit signed */
#define T_PUCHAR        0x0120  /* near pointer to 8 bit unsigned */
#define T_PFCHAR        0x0210  /* far pointer to 8 bit signed */
#define T_PFUCHAR       0x0220  /* far pointer to 8 bit unsigned */
#define T_PHCHAR        0x0310  /* huge pointer to 8 bit signed */
#define T_PHUCHAR       0x0320  /* huge pointer to 8 bit unsigned */
#define T_32PCHAR       0x0410  /* 16:32 near pointer to 8 bit signed */
#define T_32PUCHAR      0x0420  /* 16:32 near pointer to 8 bit unsigned */
#define T_32PFCHAR      0x0510  /* 16:32 far pointer to 8 bit signed */
#define T_32PFUCHAR     0x0520  /* 16:32 far pointer to 8 bit unsigned */

/*
 *      really a character types
 */

#define T_RCHAR         0x0070  /* really a char */
#define T_PRCHAR        0x0170  /* 16:16 near pointer to a real char */
#define T_PFRCHAR       0x0270  /* 16:16 far pointer to a real char */
#define T_PHRCHAR       0x0370  /* 16:16 huge pointer to a real char */
#define T_32PRCHAR      0x0470  /* 16:32 near pointer to a real char */
#define T_32PFRCHAR     0x0570  /* 16:32 far pointer to a real char */

/*
 *      really a wide character types (UNICODE)
 */

#define T_WCHAR         0x0071  /* wide char */
#define T_PWCHAR        0x0171  /* 16:16 near pointer to a wide char */
#define T_PFWCHAR       0x0271  /* 16:16 far pointer to a wide char */
#define T_PHWCHAR       0x0371  /* 16:16 huge pointer to a wide char */
#define T_32PWCHAR      0x0471  /* 16:32 near pointer to a wide char */
#define T_32PFWCHAR     0x0571  /* 16:32 far pointer to a wide char */

/*
 *      16 bit short types
 */

#define T_SHORT         0x0011  /* 16 bit signed */
#define T_USHORT        0x0021  /* 16 bit unsigned */
#define T_PSHORT        0x0111  /* near pointer to 16 bit signed */
#define T_PUSHORT       0x0121  /* near pointer to 16 bit unsigned */
#define T_PFSHORT       0x0211  /* far pointer to 16 bit signed */
#define T_PFUSHORT      0x0221  /* far pointer to 16 bit unsigned */
#define T_PHSHORT       0x0311  /* huge pointer to 16 bit signed */
#define T_PHUSHORT      0x0321  /* huge pointer to 16 bit unsigned */

#define T_32PSHORT      0x0411  /* 16:32 near pointer to 16 bit signed */
#define T_32PUSHORT     0x0421  /* 16:32 near pointer to 16 bit unsigned */
#define T_32PFSHORT     0x0511  /* 16:32 far pointer to 16 bit signed */
#define T_32PFUSHORT    0x0521  /* 16:32 far pointer to 16 bit unsigned */

/*
 *      16 bit int types
 */

#define T_INT2          0x0072  /* 16 bit signed int */
#define T_UINT2         0x0073  /* 16 bit unsigned int */
#define T_PINT2         0x0172  /* near pointer to 16 bit signed int */
#define T_PUINT2        0x0173  /* near pointer to 16 bit unsigned int */
#define T_PFINT2        0x0272  /* far pointer to 16 bit signed int */
#define T_PFUINT2       0x0273  /* far pointer to 16 bit unsigned int */
#define T_PHINT2        0x0372  /* huge pointer to 16 bit signed int */
#define T_PHUINT2       0x0373  /* huge pointer to 16 bit unsigned int */

#define T_32PINT2       0x0472  /* 16:32 near pointer to 16 bit signed int */
#define T_32PUINT2      0x0473  /* 16:32 near pointer to 16 bit unsigned int */
#define T_32PFINT2      0x0572  /* 16:32 far pointer to 16 bit signed int */
#define T_32PFUINT2     0x0573  /* 16:32 far pointer to 16 bit unsigned int */

/*
 *      32 bit long types
 */

#define T_LONG          0x0012  /* 32 bit signed */
#define T_ULONG         0x0022  /* 32 bit unsigned */
#define T_PLONG         0x0112  /* near pointer to 32 bit signed */
#define T_PLONG         0x0112  /* near pointer to 32 bit signed */
#define T_PULONG        0x0122  /* near pointer to 32 bit unsigned */
#define T_PFLONG        0x0212  /* far pointer to 32 bit signed */
#define T_PFULONG       0x0222  /* far pointer to 32 bit unsigned */
#define T_PHLONG        0x0312  /* huge pointer to 32 bit signed */
#define T_PHULONG       0x0322  /* huge pointer to 32 bit unsigned */

#define T_32PLONG       0x0412  /* 16:32 near pointer to 32 bit signed */
#define T_32PULONG      0x0422  /* 16:32 near pointer to 32 bit unsigned */
#define T_32PFLONG      0x0512  /* 16:32 far pointer to 32 bit signed */
#define T_32PFULONG     0x0522  /* 16:32 far pointer to 32 bit unsigned */

/*
 *      32 bit int types
 */

#define T_INT4          0x0074  /* 32 bit signed int */
#define T_UINT4         0x0075  /* 32 bit unsigned int */
#define T_PINT4         0x0174  /* near pointer to 32 bit signed int */
#define T_PUINT4        0x0175  /* near pointer to 32 bit unsigned int */
#define T_PFINT4        0x0274  /* far pointer to 32 bit signed int */
#define T_PFUINT4       0x0275  /* far pointer to 32 bit unsigned int */
#define T_PHINT4        0x0374  /* huge pointer to 32 bit signed int */
#define T_PHUINT4       0x0375  /* huge pointer to 32 bit unsigned int */

#define T_32PINT4       0x0474  /* 16:32 near pointer to 32 bit signed int */
#define T_32PUINT4      0x0475  /* 16:32 near pointer to 32 bit unsigned int */
#define T_32PFINT4      0x0574  /* 16:32 far pointer to 32 bit signed int */
#define T_32PFUINT4     0x0575  /* 16:32 far pointer to 32 bit unsigned int */

/*
 *      64 bit quad types
 */

#define T_QUAD          0x0013  /* 64 bit signed */
#define T_UQUAD         0x0023  /* 64 bit unsigned */
#define T_PQUAD         0x0113  /* near pointer to 64 bit signed */
#define T_PUQUAD        0x0123  /* near pointer to 64 bit unsigned */
#define T_PFQUAD        0x0213  /* far pointer to 64 bit signed */
#define T_PFUQUAD       0x0223  /* far pointer to 64 bit unsigned */
#define T_PHQUAD        0x0313  /* huge pointer to 64 bit signed */
#define T_PHUQUAD       0x0323  /* huge pointer to 64 bit unsigned */

/*
 *      64 bit int types
 */

#define T_INT8          0x0076  /* 64 bit signed int */
#define T_UINT8         0x0077  /* 64 bit unsigned int */
#define T_PINT8         0x0176  /* near pointer to 64 bit signed int */
#define T_PUINT8        0x0177  /* near pointer to 64 bit unsigned int */
#define T_PFINT8        0x0276  /* far pointer to 64 bit signed int */
#define T_PFUINT8       0x0277  /* far pointer to 64 bit unsigned int */
#define T_PHINT8        0x0376  /* huge pointer to 64 bit signed int */
#define T_PHUINT8       0x0377  /* huge pointer to 64 bit unsigned int */

#define T_32PINT8       0x0476  /* 16:32 near pointer to 64 bit signed int */
#define T_32PUINT8      0x0477  /* 16:32 near pointer to 64 bit unsigned int */
#define T_32PFINT8      0x0576  /* 16:32 far pointer to 64 bit signed int */
#define T_32PFUINT8     0x0577  /* 16:32 far pointer to 64 bit unsigned int */

/*
 *      32 bit real types
 */

#define T_REAL32        0x0040  /* 32 bit real */
#define T_PREAL32       0x0140  /* near pointer to 32 bit real */
#define T_PFREAL32      0x0240  /* far pointer to 32 bit real */
#define T_PHREAL32      0x0340  /* huge pointer to 32 bit real */
#define T_32PREAL32     0x0440  /* 16:32 near pointer to 32 bit real */
#define T_32PFREAL32    0x0540  /* 16:32 far pointer to 32 bit real */

/*
 *      48 bit real types
 */

#define T_REAL48        0x0044  /* 48 bit real */
#define T_PREAL48       0x0144  /* near pointer to 48 bit real */
#define T_PFREAL48      0x0244  /* far pointer to 48 bit real */
#define T_PHREAL48      0x0344  /* huge pointer to 48 bit real */
#define T_32PREAL48     0x0444  /* 16:32 near pointer to 48 bit real */
#define T_32PFREAL48    0x0544  /* 16:32 far pointer to 48 bit real */

/*
 *      64 bit real types
 */

#define T_REAL64        0x0041  /* 64 bit real */
#define T_PREAL64       0x0141  /* near pointer to 64 bit real */
#define T_PFREAL64      0x0241  /* far pointer to 64 bit real */
#define T_PHREAL64      0x0341  /* huge pointer to 64 bit real */
#define T_32PREAL64     0x0441  /* 16:32 near pointer to 64 bit real */
#define T_32PFREAL64    0x0541  /* 16:32 far pointer to 64 bit real */

/*
 *      80 bit real types
 */

#define T_REAL80        0x0042  /* 80 bit real */
#define T_PREAL80       0x0142  /* near pointer to 80 bit real */
#define T_PFREAL80      0x0242  /* far pointer to 80 bit real */
#define T_PHREAL80      0x0342  /* huge pointer to 80 bit real */
#define T_32PREAL80     0x0442  /* 16:32 near pointer to 80 bit real */
#define T_32PFREAL80    0x0542  /* 16:32 far pointer to 80 bit real */

/*
 *      128 bit real types
 */

#define T_REAL128       0x0043  /* 128 bit real */
#define T_PREAL128      0x0143  /* near pointer to 128 bit real */
#define T_PFREAL128     0x0243  /* far pointer to 128 bit real */
#define T_PHREAL128     0x0343  /* huge pointer to 128 bit real */
#define T_32PREAL128    0x0443  /* 16:32 near pointer to 128 bit real */
#define T_32PFREAL128   0x0543  /* 16:32 far pointer to 128 bit real */

/*
 *      32 bit complex types
 */

#define T_CPLX32        0x0050  /* 32 bit complex */
#define T_PCPLX32       0x0150  /* near pointer to 32 bit complex */
#define T_PFCPLX32      0x0250  /* far pointer to 32 bit complex */
#define T_PHCPLX32      0x0350  /* huge pointer to 32 bit complex */
#define T_32PCPLX32     0x0450  /* 16:32 near pointer to 32 bit complex */
#define T_32PFCPLX32    0x0550  /* 16:32 far pointer to 32 bit complex */

/*
 *      48 bit complex types
 */

#define T_CPLX48        0x0054  /* 48 bit complex */
#define T_PCPLX48       0x0154  /* near pointer to 48 bit complex */
#define T_PFCPLX48      0x0254  /* far pointer to 48 bit complex */
#define T_PHCPLX48      0x0354  /* huge pointer to 48 bit complex */
#define T_32PCPLX48     0x0454  /* 16:32 near pointer to 48 bit complex */
#define T_32PFCPLX48    0x0554  /* 16:32 far pointer to 48 bit complex */

/*
 *      64 bit complex types
 */

#define T_CPLX64        0x0051  /* 64 bit complex */
#define T_PCPLX64       0x0151  /* near pointer to 64 bit complex */
#define T_PFCPLX64      0x0251  /* far pointer to 64 bit complex */
#define T_PHCPLX64      0x0351  /* huge pointer to 64 bit complex */
#define T_32PCPLX64     0x0451  /* 16:32 near pointer to 64 bit complex */
#define T_32PFCPLX64    0x0551  /* 16:32 far pointer to 64 bit complex */

/*
 *      80 bit complex types
 */

#define T_CPLX80        0x0052  /* 80 bit complex */
#define T_PCPLX80       0x0152  /* near pointer to 80 bit complex */
#define T_PFCPLX80      0x0252  /* far pointer to 80 bit complex */
#define T_PHCPLX80      0x0352  /* huge pointer to 80 bit complex */
#define T_32PCPLX80     0x0452  /* 16:32 near pointer to 80 bit complex */
#define T_32PFCPLX80    0x0552  /* 16:32 far pointer to 80 bit complex */

/*
 *      128 bit complex types
 */

#define T_CPLX128       0x0053  /* 128 bit complex */
#define T_PCPLX128      0x0153  /* near pointer to 128 bit complex */
#define T_PFCPLX128     0x0253  /* far pointer to 128 bit complex */
#define T_PHCPLX128     0x0353  /* huge pointer to 128 bit real */
#define T_32PCPLX128    0x0453  /* 16:32 near pointer to 128 bit complex */
#define T_32PFCPLX128   0x0553  /* 16:32 far pointer to 128 bit complex */

/*
 *      boolean types
 */

#define T_BOOL08        0x0030  /* 8 bit boolean */
#define T_BOOL16        0x0031  /* 16 bit boolean */
#define T_BOOL32        0x0032  /* 32 bit boolean */
#define T_PBOOL08       0x0130  /* near pointer to  8 bit boolean */
#define T_PBOOL16       0x0131  /* near pointer to 16 bit boolean */
#define T_PBOOL32       0x0132  /* near pointer to 32 bit boolean */
#define T_PFBOOL08      0x0230  /* far pointer to  8 bit boolean */
#define T_PFBOOL16      0x0231  /* far pointer to 16 bit boolean */
#define T_PFBOOL32      0x0232  /* far pointer to 32 bit boolean */
#define T_PHBOOL08      0x0330  /* huge pointer to  8 bit boolean */
#define T_PHBOOL16      0x0331  /* huge pointer to 16 bit boolean */
#define T_PHBOOL32      0x0332  /* huge pointer to 32 bit boolean */

#define T_32PBOOL08     0x0430  /* 16:32 near pointer to 8 bit boolean */
#define T_32PFBOOL08    0x0530  /* 16:32 far pointer to 8 bit boolean */
#define T_32PBOOL16     0x0431  /* 16:32 near pointer to 18 bit boolean */
#define T_32PFBOOL16    0x0531  /* 16:32 far pointer to 16 bit boolean */
#define T_32PBOOL32     0x0432  /* 16:32 near pointer to 32 bit boolean */
#define T_32PFBOOL32    0x0532  /* 16:32 far pointer to 32 bit boolean */

#define T_NCVPTR        0x01f0  /* CV Internal type for created near pointers */
#define T_FCVPTR        0x02f0  /* CV Internal type for created far pointers */
#define T_HCVPTR        0x03f0  /* CV Internal type for created huge pointers */

/**     No leaf index can have a value of 0x0000.  The leaf indices are
 *      separated into ranges depending upon the use of the type record.
 *      The second range is for the type records that are directly referenced
 *      in symbols. The first range is for type records that are not
 *      referenced by symbols but instead are referenced by other type
 *      records.  All type records must have a starting leaf index in these
 *      first two ranges.  The third range of leaf indices are used to build
 *      up complex lists such as the field list of a class type record.  No
 *      type record can begin with one of the leaf indices. The fourth ranges
 *      of type indices are used to represent numeric data in a symbol or
 *      type record. These leaf indices are greater than 0x8000.  At the
 *      point that type or symbol processor is expecting a numeric field, the
 *      next two bytes in the type record are examined.  If the value is less
 *      than 0x8000, then the two bytes contain the numeric value.  If the
 *      value is greater than 0x8000, then the data follows the leaf index in
 *      a format specified by the leaf index. The final range of leaf indices
 *      are used to force alignment of subfields within a complex type record..
 */

/*
 *      leaf indices starting records but referenced from symbol records
 */

#define LF_MODIFIER     0x0001
#define LF_POINTER      0x0002
#define LF_ARRAY        0x0003
#define LF_CLASS        0x0004
#define LF_STRUCTURE    0x0005
#define LF_UNION        0x0006
#define LF_ENUM         0x0007
#define LF_PROCEDURE    0x0008
#define LF_MFUNCTION    0x0009
#define LF_VTSHAPE      0x000a
#define LF_COBOL0       0x000b
#define LF_COBOL1       0x000c
#define LF_BARRAY       0x000d
#define LF_LABEL        0x000e
#define LF_NULL         0x000f
#define LF_NOTTRAN      0x0010
#define LF_DIMARRAY     0x0011
#define LF_VFTPATH      0x0012
#define LF_PRECOMP      0x0013  /* not refereced from symbol */
#define LF_ENDPRECOMP   0x0014  /* not refereced from symbol */
#define LF_OEM          0x0015
#define LF_TYPESERVER   0x0016

/*
 * leaf indices starting records but referenced only from type records
 */

#define LF_SKIP         0x0200
#define LF_ARGLIST      0x0201
#define LF_DEFARG       0x0202
#define LF_LIST         0x0203
#define LF_FIELDLIST    0x0204
#define LF_DERIVED      0x0205
#define LF_BITFIELD     0x0206
#define LF_METHODLIST   0x0207
#define LF_DIMCONU      0x0208
#define LF_DIMCONLU     0x0209
#define LF_DIMVARU      0x020a
#define LF_DIMVARLU     0x020b
#define LF_REFSYM       0x020c

#define LF_BCLASS       0x0400
#define LF_VBCLASS      0x0401
#define LF_IVBCLASS     0x0402
#define LF_ENUMERATE    0x0403
#define LF_FRIENDFCN    0x0404
#define LF_INDEX        0x0405
#define LF_MEMBER       0x0406
#define LF_STMEMBER     0x0407
#define LF_METHOD       0x0408
#define LF_NESTTYPE     0x0409
#define LF_VFUNCTAB     0x040a
#define LF_FRIENDCLS    0x040b

#define LF_NUMERIC      0x8000
#define LF_CHAR         0x8000
#define LF_SHORT        0x8001
#define LF_USHORT       0x8002
#define LF_LONG         0x8003
#define LF_ULONG        0x8004
#define LF_REAL32       0x8005
#define LF_REAL64       0x8006
#define LF_REAL80       0x8007
#define LF_REAL128      0x8008
#define LF_QUADWORD     0x8009
#define LF_UQUADWORD    0x800a
#define LF_REAL48       0x800b

#define LF_PAD0         0xf0
#define LF_PAD1         0xf1
#define LF_PAD2         0xf2
#define LF_PAD3         0xf3
#define LF_PAD4         0xf4
#define LF_PAD5         0xf5
#define LF_PAD6         0xf6
#define LF_PAD7         0xf7
#define LF_PAD8         0xf8
#define LF_PAD9         0xf9
#define LF_PAD10        0xfa
#define LF_PAD11        0xfb
#define LF_PAD12        0xfc
#define LF_PAD13        0xfd
#define LF_PAD14        0xfe
#define LF_PAD15        0xff

/*
 * end of leaf indices
 */

/*
 *      Type enum for pointer records
 *      Pointers can be one of the following types
 */

typedef enum CV_ptrtype_e {
    CV_PTR_NEAR         = 0x00, /* near pointer */
    CV_PTR_FAR          = 0x01, /* far pointer */
    CV_PTR_HUGE         = 0x02, /* huge pointer */
    CV_PTR_BASE_SEG     = 0x03, /* based on segment */
    CV_PTR_BASE_VAL     = 0x04, /* based on value of base */
    CV_PTR_BASE_SEGVAL  = 0x05, /* based on segment value of base */
    CV_PTR_BASE_ADDR    = 0x06, /* based on address of base */
    CV_PTR_BASE_SEGADDR = 0x07, /* based on segment address of base */
    CV_PTR_BASE_TYPE    = 0x08, /* based on type */
    CV_PTR_BASE_SELF    = 0x09, /* based on self */
    CV_PTR_NEAR32       = 0x0a, /* 16:32 near pointer */
    CV_PTR_FAR32        = 0x0b, /* 16:32 far pointer */
    CV_PTR_UNUSEDPTR    = 0x0c  /* first unused pointer type */
} CV_ptrtype_e;

/*
 *      Mode enum for pointers
 *      Pointers can have one of the following modes
 */

typedef enum CV_ptrmode_e {
    CV_PTR_MODE_PTR     = 0x00, /* "normal" pointer */
    CV_PTR_MODE_REF     = 0x01, /* reference */
    CV_PTR_MODE_PMEM    = 0x02, /* pointer to data member */
    CV_PTR_MODE_PMFUNC  = 0x03, /* pointer to member function */
    CV_PTR_MODE_RESERVED= 0x04  /* first unused pointer mode */
} CV_ptrmode_e;

/*
 *      Enumeration for function call type
 */

typedef enum CV_call_e {
    CV_CALL_NEAR_C      = 0x00, /* near right to left push, caller pops stack */
    CV_CALL_FAR_C       = 0x01, /* far right to left push, caller pops stack */
    CV_CALL_NEAR_PASCAL = 0x02, /* near left to right push, callee pops stack */
    CV_CALL_FAR_PASCAL  = 0x03, /* far left to right push, callee pops stack */
    CV_CALL_NEAR_FAST   = 0x04, /* near left to right push with regs, callee pops stack */
    CV_CALL_FAR_FAST    = 0x05, /* far left to right push with regs, callee pops stack */
    CV_CALL_PCODE       = 0x06, /* pcode */
    CV_CALL_NEAR_STD    = 0x07, /* near standard call */
    CV_CALL_FAR_STD     = 0x08, /* far standard call */
    CV_CALL_NEAR_SYS    = 0x09, /* near sys call */
    CV_CALL_FAR_SYS     = 0x0a, /* far sys call */
    CV_CALL_THISCALL    = 0x0b, /* this call (this passed in register) */
    CV_CALL_MIPSCALL    = 0x0c, /* Mips call */
    CV_CALL_GENERIC     = 0x0d, // Generic call sequence
CV_CALL_ALPHACALL= 0x0e, // Alpha call
CV_CALL_RESERVED= 0x0f// first unused call enumeration

} CV_call_e;

/*
 *      Values for the access protection of class attributes
 */

typedef enum CV_access_e {
    CV_private   = 1,
    CV_protected = 2,
    CV_public    = 3
} CV_access_e;

/*
 *      enumeration for method properties
 */

typedef enum CV_methodprop_e {
    CV_MTvanilla        = 0x00,
    CV_MTvirtual        = 0x01,
    CV_MTstatic         = 0x02,
    CV_MTfriend         = 0x03,
    CV_MTintro          = 0x04,
    CV_MTpurevirt       = 0x05,
    CV_MTpureintro      = 0x06
} CV_methodprop_e;

/*
 *      enumeration for virtual shape table entries
 */

typedef enum CV_VTS_desc_e {
    CV_VTS_near         = 0x00,
    CV_VTS_far          = 0x01,
    CV_VTS_thin         = 0x02,
    CV_VTS_outer        = 0x03,
    CV_VTS_meta         = 0x04,
    CV_VTS_near32       = 0x05,
    CV_VTS_far32        = 0x06,
    CV_VTS_unused       = 0x07
} CV_VTS_desc_e;

/*
 *      enumeration for LF_LABEL address modes
 */

typedef enum CV_LABEL_TYPE_e {
    CV_LABEL_NEAR = 0,          /* near return */
    CV_LABEL_FAR  = 4           /* far return */
} CV_LABEL_TYPE_e;

/*
 *      enumeration for LF_MODIFIER values
 */

typedef struct CV_modifier_t {
    unsigned short  MOD_const       :1;
    unsigned short  MOD_volatile    :1;
    unsigned short  MOD_unused      :14;
} CV_modifier_t;

/*
 *  bit field structure describing class/struct/union/enum properties
 */

typedef struct CV_prop_t {
    unsigned short  packed      :1; /* structure is packed */
    unsigned short  ctor        :1; /* constructors or destructors present */
    unsigned short  ovlops      :1; /* overloaded operators present */
    unsigned short  isnested    :1; /* this is a nested class */
    unsigned short  cnested     :1; /* this class contains nested types */
    unsigned short  opassign    :1; /* overloaded assignment (=) */
    unsigned short  opcast      :1; /* casting methods */
    unsigned short  fwdref      :1; /* forward reference (incomplete defn) */

unsigned short  scoped      :1; /* scoped definition */
    unsigned short  reserved    :7;
} CV_prop_t;

/*
 *  class field attribute
 */

typedef struct CV_fldattr_t {
    unsigned short  access      :2; /* access protection CV_access_t */
    unsigned short  mprop       :3; /* method properties CV_methodprop_t */
    unsigned short  pseudo      :1; /* compiler generated fcn and does not exist */
    unsigned short  noinherit   :1; /* true if class cannot be inherited */
    unsigned short  noconstruct :1; /* true if class cannot be constructed */
    unsigned short  unused      :8; /* unused */
} CV_fldattr_t;

/*
 *  Structures to access to the type records
 */

typedef struct TYPTYPE {
    unsigned short  len;
    unsigned short  leaf;
#ifdef CV
    unsigned char   data[];
#else
    unsigned char   data[1];
#endif
} TYPTYPE;                      /* general types record */

typedef enum CV_PMEMBER {
    CV_PDM16_NONVIRT    = 0x00, /* 16:16 data no virtual fcn or base (null = -1) */
    CV_PDM16_VFCN       = 0x01, /* 16:16 data with virtual functions null = 0 */
    CV_PDM16_VBASE      = 0x02, /* 16:16 data with virtual bases null = (,,-1) */
    CV_PDM32_NVVFCN     = 0x03, /* 16:32 data w/wo virtual functions null = 0x80000000 */
    CV_PDM32_VBASE      = 0x04, /* 16:32 data with virtual bases (,,-1L) */

    CV_PMF16_NEARNVSA   = 0x05, /* 16:16 near method nonvirtual single address point */
    CV_PMF16_NEARNVMA   = 0x06, /* 16:16 near method nonvirtual multiple address points */
    CV_PMF16_NEARVBASE  = 0x07, /* 16:16 near method virtual bases */
    CV_PMF16_FARNVSA    = 0x08, /* 16:16 far method nonvirtual single address point */
    CV_PMF16_FARNVMA    = 0x09, /* 16:16 far method nonvirtual multiple address points */
    CV_PMF16_FARVBASE   = 0x0a, /* 16:16 far method virtual bases */

    CV_PMF32_NVSA       = 0x0b, /* 16:32 method nonvirtual single address point */
    CV_PMF32_NVMA       = 0x0c, /* 16:32 method nonvirtual multiple address point */
    CV_PMF32_VBASE      = 0x0d  /* 16:32 method virtual bases */
} CV_PMEMBER;

/*
 *  memory representation of pointer to member.  These representations are
 *  indexed by the enumeration above in the LF_POINTER record
 */

/*  representation of a 16:16 pointer to data for a class with no
 *  virtual functions or virtual bases
 */

struct CV_PDMR16_NONVIRT {
    CV_off16_t      mdisp;      /* displacement to data (NULL = -1) */
};

/*
 *  representation of a 16:16 pointer to data for a class with virtual
 *  functions
 */

struct CV_PMDR16_VFCN {
    CV_off16_t      mdisp;      /* displacement to data ( NULL = 0) */
};

/*
 *  representation of a 16:16 pointer to data for a class with
 *  virtual bases
 */

struct CV_PDMR16_VBASE {
    CV_off16_t      mdisp;      /* displacement to data */
    CV_off16_t      pdisp;      /* this pointer displacement to vbptr */
    CV_off16_t      vdisp;      /* displacement within vbase table */
};

/*
 *  representation of a 16:32 near pointer to data for a class with
 *  or without virtual functions and no virtual bases
 */

struct CV_PDMR32_NVVFCN {
    CV_off32_t      mdisp;      /* displacement to data (NULL = 0x80000000) */
};

/*
 *  representation of a 16:32 near pointer to data for a class
 *  with virtual bases
 */

struct CV_PDMR32_VBASE {
    CV_off32_t      mdisp;      /* displacement to data */
    CV_off32_t      pdisp;      /* this pointer displacement */
    CV_off32_t      vdisp;      /* vbase table displacement */
};

/*
 *  representation of a 16:16 pointer to near member function for a
 *  class with no virtual functions or bases and a single address point
 */

struct CV_PMFR16_NEARNVSA {
    CV_uoff16_t     off;        /* near address of function (NULL = 0) */
};

/*
 *  representation of a 16:16 pointer to far member function for a
 *  class with no virtual bases and a single address point
 */

struct CV_PMFR16_FARNVSA {
    CV_uoff16_t     off;        /* offset of function (NULL = 0:0) */
    unsigned short  seg;        /* segment of function */
};

/*
 *  representation of a 16:16 near pointer to member functions of an
 *  class with no virtual bases and multiple address points
 */

struct CV_PMFR16_NEARNVMA {
    CV_uoff16_t     off;        /* offset of function (NULL = 0,x) */
    signed short    disp;
};

/*
 *  representation of a 16:16 far pointer to member functions of a
 *  class with no virtual bases and multiple address points
 */

struct CV_PMFR16_FARNVMA {
    CV_uoff16_t     off;        /* offset of function (NULL = 0:0,x) */
    unsigned short  seg;
    signed short    disp;
};

/*
 *  representation of a 16:16 near pointer to member function of a
 *  class with virtual bases
 */

struct CV_PMFR16_NEARVBASE {
    CV_uoff16_t     off;        /* offset of function (NULL = 0,x,x,x) */
    CV_off16_t      mdisp;      /* displacement to data */
    CV_off16_t      pdisp;      /* this pointer displacement */
    CV_off16_t      vdisp;      /* vbase table displacement */
};

/*
 *  representation of a 16:16 far pointer to member function of a
 *  class with virtual bases
 */

struct CV_PMFR16_FARVBASE {
    CV_uoff16_t     off;        /* offset of function (NULL = 0:0,x,x,x) */
    unsigned short  seg;
    CV_off16_t      mdisp;      /* displacement to data */
    CV_off16_t      pdisp;      /* this pointer displacement */
    CV_off16_t      vdisp;      /* vbase table displacement */

};

/*
 *  representation of a 16:32 near pointer to member function for a
 *  class with no virtual bases and a single address point
 */

struct CV_PMFR32_NVSA {
    CV_uoff32_t      off;       /* near address of function (NULL = 0L) */
};

/*
 *  representation of a 16:32 near pointer to member function for a
 *  class with no virtual bases and multiple address points
 */

struct CV_PMFR32_NVMA {
    CV_uoff32_t     off;        /* near address of function (NULL = 0L,x) */
    CV_off32_t      disp;
};

/*
 *  representation of a 16:32 near pointer to member function for a
 *  class with virtual bases
 */

struct CV_PMFR32_VBASE {
    CV_uoff32_t     off;        /* near address of function (NULL = 0L,x,x,x) */
    CV_off32_t      mdisp;      /* displacement to data */
    CV_off32_t      pdisp;      /* this pointer displacement */
    CV_off32_t      vdisp;      /* vbase table displacement */
};

/*
 *  Easy leaf - used for generic casting to reference leaf field
 *  of a subfield of a complex list
 */

typedef struct lfEasy {
    unsigned short  leaf;       /* LF_... */
} lfEasy;

/*      The following type records are basically variant records of the
 *      above structure.  The "unsigned short leaf" of the above structure and
 *      the "unsigned short leaf" of the following type definitions are the
 *      same symbol.  When the OMF record is locked via the MHOMFLock API
 *      call, the address of the "unsigned short leaf" is returned
 */

/*
 *      Type record for LF_MODIFIER
 */

typedef struct lfModifier {
    unsigned short  leaf;       /* LF_MODIFIER */
    CV_modifier_t   attr;       /* modifier attribute modifier_t */
    CV_typ_t        type;       /* modified type */
} lfModifier;

/*
 *      type record for LF_POINTER
 */

typedef struct lfPointer {
    struct lfPointerBody {
        unsigned short      leaf; /* LF_POINTER */
        struct {
            unsigned char   ptrtype     :5; /* ordinal specifying pointer type (ptrtype-t) */
            unsigned char   ptrmode     :3; /* ordinal specifying pointer mode (ptrmode_t) */
            unsigned char   isflat32    :1; /* true if 0:32 pointer */
            unsigned char   isvolatile  :1; /* TRUE if volatile pointer */
            unsigned char   isconst     :1; /* TRUE if const pointer */
            unsigned char   unused      :5;
        } attr;
        CV_typ_t    utype;      /* type index of the underlying type */
    } u;
    union  {
        struct {
            CV_typ_t        pmclass; /* index of containing class for pointer to member */
            unsigned short  pmenum; /* enumeration specifying pm format */
        } pm;
        unsigned short      bseg; /* base segment if PTR_BASE_SEG */
        unsigned char       Sym[1]; /* copy of base symbol record (including length) */
        struct  {
            unsigned short  index; /* type index if CV_PTR_BASE_TYPE */
            unsigned char   name[1]; /* name of base type */
        } btype;
    } pbase;
} lfPointer;

/*
 *      type record for LF_ARRAY
 */

typedef struct lfArray {
    unsigned short  leaf;       /* LF_ARRAY */
    CV_typ_t        elemtype;   /* type index of element type */
    CV_typ_t        idxtype;    /* type index of indexing type */
    unsigned char   data[];     /* variable length data specifying */
                                /* size in bytes and name */
} lfArray;

/*
 *      type record for LF_CLASS, LF_STRUCTURE
 */

typedef struct lfClass {
    unsigned short  leaf;       /* LF_CLASS, LF_STRUCT */
    unsigned short  count;      /* count of number of elements in class */
    CV_typ_t        field;      /* type index of LF_FIELD descriptor list */
    CV_prop_t       property;   /* property attribute field (prop_t) */
    CV_typ_t        derived;    /* type index of derived from list if not zero */
    CV_typ_t        vshape;     /* type index of vshape table for this class */
    unsigned char   data[];     /* data describing length of structure in */
                                /* bytes and name */
} lfClass;
typedef lfClass lfStructure;

/*
 *      type record for LF_UNION
 */

typedef struct lfUnion {
    unsigned short  leaf;       /* LF_UNION */
    unsigned short  count;      /* count of number of elements in class */
    CV_typ_t        field;      /* type index of LF_FIELD descriptor list */
    CV_prop_t       property;   /* property attribute field */
    unsigned char   data[];     /* variable length data describing length of */
                                /* structure and name */
} lfUnion;

/*
 *      type record for LF_ENUM
 */

typedef struct lfEnum {
    unsigned short  leaf;       /* LF_ENUM */
    unsigned short  count;      /* count of number of elements in class */
    CV_typ_t        utype;      /* underlying type of the enum */
    CV_typ_t        field;      /* type index of LF_FIELD descriptor list */
    CV_prop_t       property;   /* property attribute field */
    unsigned char   Name[1];    /* length prefixed name of enum */
} lfEnum;

/*
 *      Type record for LF_PROCEDURE
 */

typedef struct lfProc {
    unsigned short  leaf;       /* LF_PROCEDURE */
    CV_typ_t        rvtype;     /* type index of return value */
    unsigned char   calltype;   /* calling convention (call_t) */
    unsigned char   reserved;   /* reserved for future use */
    unsigned short  parmcount;  /* number of parameters */
    CV_typ_t        arglist;    /* type index of argument list */
} lfProc;

/*
 *      Type record for member function
 */

typedef struct lfMFunc {
    unsigned short  leaf;       /* LF_MFUNCTION */
    CV_typ_t        rvtype;     /* type index of return value */
    CV_typ_t        classtype;  /* type index of containing class */
    CV_typ_t        thistype;   /* type index of this pointer (model specific) */
    unsigned char   calltype;   /* calling convention (call_t) */
    unsigned char   reserved;   /* reserved for future use */
    unsigned short  parmcount;  /* number of parameters */
    CV_typ_t        arglist;    /* type index of argument list */
    long            thisadjust; /* this adjuster (long because pad required anyway) */
} lfMFunc;

/*
 *     type record for virtual function table shape
 */

typedef struct lfVTShape {
    unsigned short  leaf;       /* LF_VTSHAPE */
    unsigned short  count;      /* number of entries in vfunctable */
    unsigned char   desc[];     /* variable number of 4 bit (VTS_desc) descriptors */
} lfVTShape;

/*
 *      type record for cobol0
 */

typedef struct lfCobol0 {
    unsigned short  leaf;       /* LF_COBOL0 */
    CV_typ_t        type;
    unsigned char   data[];
} lfCobol0;

/*
 *      type record for cobol1
 */

typedef struct lfCobol1 {
    unsigned short  leaf;       /* LF_COBOL1 */
    unsigned char   data[];
} lfCobol1;

/*
 *      type record for basic array
 */

typedef struct lfBArray {
    unsigned short  leaf;       /* LF_BARRAY */
    CV_typ_t        utype;      /* type index of underlying type */
} lfBArray;

/*
 *      type record for assembler labels
 */

typedef struct lfLabel {
    unsigned short  leaf;       /* LF_LABEL */
    unsigned short  mode;       /* addressing mode of label */
} lfLabel;

/*
 *      type record for dimensioned arrays
 */

typedef struct lfDimArray {
    unsigned short  leaf;       /* LF_DIMARRAY */
    CV_typ_t        utype;      /* underlying type of the array */
    CV_typ_t        diminfo;    /* dimension information */
    char            name[1];    /* length prefixed name */
} lfDimArray;

/*
 *      type record describing path to virtual function table
 */

typedef struct lfVFTPath {
    unsigned short  leaf;       /* LF_VFTPATH */
    unsigned short  count;      /* count of number of bases in path */
    CV_typ_t        base[1];    /* bases from root to leaf */
} lfVFTPath;

/*
 *      type record describing inclusion of precompiled types
 */

typedef struct lfPreComp {
    unsigned short  leaf;       /* LF_PRECOMP */
    unsigned short  start;      /* starting type index included */
    unsigned short  count;      /* number of types in inclusion */
    unsigned long   signature;  /* signature */
    unsigned char   name[];     /* length prefixed name of included type file */
} lfPreComp;

/*
 *      type record describing end of precompiled types that will be
 *      included by another file
 */

typedef struct lfEndPreComp {
    unsigned short  leaf;       /* LF_ENDPRECOMP */
    unsigned long   signature;  /* signature */
} lfEndPreComp;

/*
 *      description of type records that can be referenced from
 *      type records referenced by symbols
 */

/*
 *      type record for skip record
 */

typedef struct lfSkip {
    unsigned short  leaf;       /* LF_SKIP */
    CV_typ_t        type;       /* next valid index */
    unsigned char   data[];     /* pad data */
} lfSkip;

/*
 *      argument list leaf
 */

typedef struct lfArgList {
    unsigned short  leaf;       /* LF_ARGLIST */
    unsigned short  count;      /* number of arguments */
    CV_typ_t        arg[];      /* number of arguments */
} lfArgList;

/*
 *      derived class list leaf
 */

typedef struct lfDerived {
    unsigned short  leaf;       /* LF_DERIVED */
    unsigned short  count;      /* number of arguments */
    CV_typ_t        drvdcls[];  /* type indices of derived classes */
} lfDerived;

/*
 *      leaf for default arguments
 */

typedef struct lfDefArg {
    unsigned short  leaf;       /* LF_DEFARG */
    CV_typ_t        type;       /* type of resulting expression */
    unsigned char   expr[];     /* length prefixed expression string */
} lfDefArg;

/*
 *      list leaf
 *          This list should no longer be used because the utilities cannot
 *          verify the contents of the list without knowing what type of list
 *          it is.  New specific leaf indices should be used instead.
 */

typedef struct lfList {
    unsigned short  leaf;       /* LF_LIST */
    char            data[];     /* data format specified by indexing type */
} lfList;

/*
 *      field list leaf
 *      This is the header leaf for a complex list of class and structure
 *      subfields.
 */

typedef struct lfFieldList {
    unsigned short  leaf;       /* LF_FIELDLIST */
    char            data[];     /* field list sub lists */
} lfFieldList;

/*
 *  type record for non-static methods and friends in method list
 */

typedef struct mlMethod {
    CV_fldattr_t   attr;        /* method attribute */
    CV_typ_t       index;       /* index to type record for procedure */
#ifdef CV
    unsigned long  vbaseoff[0]; /* offset in vfunctable if intro virtual */
#else
//    unsigned long   vbaseoff[1];
#endif
} mlMethod;

typedef struct lfMethodList {
    unsigned short leaf;
    unsigned char  mList[];     /* really a mlMethod type */
} lfMethodList;

/*
 *      type record for LF_BITFIELD
 */

typedef struct lfBitfield {
    unsigned short  leaf;       /* LF_BITFIELD */
    unsigned char   length;
    unsigned char   position;
    CV_typ_t        type;       /* type of bitfield */

} lfBitfield;

/*
 *      type record for dimensioned array with constant bounds
 */

typedef struct lfDimCon {
    unsigned short  leaf;       /* LF_DIMCONU or LF_DIMCONLU */
    unsigned short  rank;       /* number of dimensions */
    CV_typ_t        typ;        /* type of index */
    unsigned char   dim[];      /* array of dimension information with */
                                /* either upper bounds or lower/upper bound */
} lfDimCon;

/*
 *      type record for dimensioned array with variable bounds
 */

typedef struct lfDimVar {
    unsigned short  leaf;       /* LF_DIMVARU or LF_DIMVARLU */
    unsigned short  rank;       /* number of dimensions */
    CV_typ_t        typ;        /* type of index */
    unsigned char   dim[];      /* array of type indices for either */
                                /* variable upper bound or variable */
                                /* lower/upper bound.  The referenced */
                                /* types must be LF_REFSYM or T_VOID */
} lfDimVar;

/*
 *      type record for referenced symbol
 */

typedef struct lfRefSym {
    unsigned short  leaf;       /* LF_REFSYM */
    unsigned char   Sym[1];     /* copy of referenced symbol record */
                                /* (including length) */
} lfRefSym;

/**     the following are numeric leaves.  They are used to indicate the
 *      size of the following variable length data.  When the numeric
 *      data is a single byte less than 0x8000, then the data is output
 *      directly.  If the data is more the 0x8000 or is a negative value,
 *      then the data is preceeded by the proper index.
 */

/*      signed character leaf */

typedef struct lfChar {
    unsigned short  leaf;       /* LF_CHAR */
    signed char     val;        /* signed 8-bit value */
} lfChar;

/*      signed short leaf */

typedef struct lfShort {
    unsigned short  leaf;       /* LF_SHORT */
    short           val;        /* signed 16-bit value */
} lfShort;

/*      unsigned short leaf */

typedef struct lfUShort {
    unsigned short  leaf;       /* LF_unsigned short */
    unsigned short  val;        /* unsigned 16-bit value */
} lfUShort;

/*      signed long leaf */

typedef struct lfLong {
    unsigned short  leaf;       /* LF_LONG */
    long            val;        /* signed 32-bit value */
} lfLong;

/*      unsigned long leaf */

typedef struct lfULong {
    unsigned short  leaf;       /* LF_ULONG */
    unsigned long   val;        /* unsigned 32-bit value */
} lfULong;

/*      real 32-bit leaf */

typedef struct lfReal32 {
    unsigned short  leaf;       /* LF_REAL32 */
    float           val;        /* 32-bit real value */
} lfReal32;

/*      real 48-bit leaf */

typedef struct lfReal48 {
    unsigned short  leaf;       /* LF_REAL48 */
    unsigned char   val[6];     /* 48-bit real value */
} lfReal48;

/*      real 64-bit leaf */

typedef struct lfReal64 {
    unsigned short  leaf;       /* LF_REAL64 */
    double          val;        /* 64-bit real value */
} lfReal64;

/*      real 80-bit leaf */

typedef struct lfReal80 {
    unsigned short  leaf;       /* LF_REAL80 */
    REAL10          val;        /* real 80-bit value */
} lfReal80;

/*      real 128-bit leaf */

typedef struct lfReal128 {
    unsigned short  leaf;       /* LF_REAL128 */
    char            val[16];    /* real 128-bit value */
} lfReal128;

/************************************************************************/

/*      index leaf - contains type index of another leaf */
/*      a major use of this leaf is to allow the compilers to emit a */
/*      long complex list (LF_FIELD) in smaller pieces. */

typedef struct lfIndex {
    unsigned short  leaf;       /* LF_INDEX */
    CV_typ_t        index;      /* type index of referenced leaf */
} lfIndex;

/*      subfield record for base class field */

typedef struct lfBClass {
    unsigned short  leaf;       /* LF_BCLASS */
    CV_typ_t        index;      /* type index of base class */
    CV_fldattr_t    attr;       /* attribute */
    unsigned char   offset[];   /* variable length offset of base within class */
} lfBClass;

/*      subfield record for direct and indirect virtual base class field */

typedef struct lfVBClass {
    unsigned short  leaf;       /* LF_VBCLASS | LV_IVBCLASS */
    CV_typ_t        index;      /* type index of direct virtual base class */
    CV_typ_t        vbptr;      /* type index of virtual base pointer */
    CV_fldattr_t    attr;       /* attribute */
    unsigned char   vbpoff[];   /* virtual base pointer offset from address point */
                                /* followed by virtual base offset from vbtable */
} lfVBClass;

/*      subfield record for friend class */

typedef struct lfFriendCls {
    unsigned short  leaf;       /* LF_FRIENDCLS */
    CV_typ_t        index;      /* index to type record of friend class */
} lfFriendCls;

/*      subfield record for friend function */

typedef struct lfFriendFcn {
    unsigned short  leaf;       /* LF_FRIENDFCN */
    CV_typ_t        index;      /* index to type record of friend function */
    char            Name[1];    /* name of friend function */
} lfFriendFcn;

/*      subfield record for non-static data members */

typedef struct lfMember {
    unsigned short  leaf;       /* LF_MEMBER */
    CV_typ_t        index;      /* index of type record for field */
    CV_fldattr_t    attr;       /* attribute mask */
    unsigned char   offset[];   /* variable length offset of field followed */
                                /* by length prefixed name of field */
} lfMember;

/*  type record for static data members */

typedef struct lfSTMember {
    unsigned short  leaf;       /* LF_STMEMBER */
    CV_typ_t        index;      /* index of type record for field */
    CV_fldattr_t    attr;       /* attribute mask */
    char            Name[1];    /* length prefixed name of field */
} lfSTMember;

/*      subfield record for virtual function table pointer */

typedef struct lfVFuncTab {
    unsigned short  leaf;       /* LF_VFUNCTAB */
    CV_typ_t        type;       /* type index of pointer */
} lfVFuncTab;

/*      subfield record for method and friend list */

typedef struct lfMethod {
    unsigned short  leaf;       /* LF_METHOD */
    unsigned short  count;      /* number of occurances of function */
    CV_typ_t        mList;      /* index to LF_METHODLIST record */
    char            Name[1];    /* length prefixed name of method */
} lfMethod;

/*      subfield record for enumerate */

typedef struct lfEnumerate {
    unsigned short  leaf;       /* LF_ENUMERATE */
    CV_fldattr_t    attr;       /* access (ACC_...) */
    unsigned char   value[];    /* variable length value field followed */
                                /* by length prefixed name */
} lfEnumerate;

/*  type record for nested (scoped) type definition */

typedef struct lfNestType {
    unsigned short  leaf;       /* LF_NESTTYPE */
    CV_typ_t        index;      /* index of nested type definition */
    unsigned char   Name[1];    /* length prefixed type name */
} lfNestType;

/*  type record for pad leaf */

typedef struct lfPad {
    unsigned char   leaf;
} SYM_PAD;

/*  Symbol definitions */

typedef enum SYM_ENUM_e {
    S_COMPILE    =  0x0001,     /* Compile flags symbol */
    S_REGISTER   =  0x0002,     /* Register variable */
    S_CONSTANT   =  0x0003,     /* constant symbol */
    S_UDT        =  0x0004,     /* User defined type */
    S_SSEARCH    =  0x0005,     /* Start Search */
    S_END        =  0x0006,     /* Block, procedure, "with" or thunk end */
    S_SKIP       =  0x0007,     /* Reserve symbol space in $$Symbols table */
    S_CVRESERVE  =  0x0008,     /* Reserve symbol for CV internal use */
    S_OBJNAME    =  0x0009,     /* path to object file name */
    S_ENDARG     =  0x000a,     /* end of argument list */
    S_COBOLUDT   =  0x000b,     /* special UDT for cobol -- not packed */

    S_BPREL16    =  0x0100,     /* BP-relative */
    S_LDATA16    =  0x0101,     /* Module-local symbol */
    S_GDATA16    =  0x0102,     /* Global data symbol */
    S_PUB16      =  0x0103,     /* a public symbol */
    S_LPROC16    =  0x0104,     /* Local procedure start */
    S_GPROC16    =  0x0105,     /* Global procedure start */
    S_THUNK16    =  0x0106,     /* Thunk Start */
    S_BLOCK16    =  0x0107,     /* block start */
    S_WITH16     =  0x0108,     /* with start */
    S_LABEL16    =  0x0109,     /* code label */
    S_CEXMODEL16 =  0x010a,     /* change execution model */
    S_VFTABLE16  =  0x010b,     /* address of virtual function table */
    S_REGREL16   =  0x010c,     /* register relative address */

    S_BPREL32    =  0x0200,     /* BP-relative */
    S_LDATA32    =  0x0201,     /* Module-local symbol */
    S_GDATA32    =  0x0202,     /* Global data symbol */
    S_PUB32      =  0x0203,     /* a public symbol (CV internal reserved) */
    S_LPROC32    =  0x0204,     /* Local procedure start */
    S_GPROC32    =  0x0205,     /* Global procedure start */
    S_THUNK32    =  0x0206,     /* Thunk Start */
    S_BLOCK32    =  0x0207,     /* block start */
    S_WITH32     =  0x0208,     /* with start */
    S_LABEL32    =  0x0209,     /* code label */
    S_CEXMODEL32 =  0x020a,     /* change execution model */
    S_VFTABLE32  =  0x020b,     /* address of virtual function table */
    S_REGREL32   =  0x020c,     /* register relative address */
    S_LTHREAD32  =  0x020d,
    S_GTHREAD32  =  0x020e,

    S_LPROCMIPS  =  0x0300,     /* Local procedure start */
    S_GPROCMIPS  =  0x0301,     /* Global procedure start */

    S_PROCREF    =  0x400,      /* Procedure reference */
    S_DATAREF    =  0x401,      /* Data reference */
    S_ALIGN      =  0x402       /* Page Alignment */
} SYM_ENUM_e;

/*  enum describing the compile flag source language */

typedef enum {
    CV_CFL_C        = 0x00,
    CV_CFL_CXX      = 0x01,
    CV_CFL_FORTRAN  = 0x02,
    CV_CFL_MASM     = 0x03,
    CV_CFL_PASCAL   = 0x04,
    CV_CFL_BASIC    = 0x05,
    CV_CFL_COBOL    = 0x06
} CV_CFL_LANG;

/*  enum describing target processor */

typedef enum CV_CPU_TYPE_e {
    CV_CFL_8080         = 0x00,
    CV_CFL_8086         = 0x01,
    CV_CFL_80286        = 0x02,
    CV_CFL_80386        = 0x03,
    CV_CFL_80486        = 0x04,
    CV_CFL_PENTIUM      = 0x05,
    CV_CFL_MIPSR4000    = 0x10,
    CV_CFL_M68000       = 0x20,
    CV_CFL_M68010       = 0x21,
    CV_CFL_M68020       = 0x22,
    CV_CFL_M68030       = 0x23,
    CV_CFL_M68040       = 0x24,
    CV_CFL_ALPHA        = 0x30

} CV_CPU_TYPE_e;

/*  enum describing compile flag ambiant data model */

typedef enum {
    CV_CFL_DNEAR    = 0x00,
    CV_CFL_DFAR     = 0x01,
    CV_CFL_DHUGE    = 0x02
} CV_CFL_DATA;

/*  enum describing compile flag ambiant code model */

typedef enum CV_CFL_CODE_e {
    CV_CFL_CNEAR    = 0x00,
    CV_CFL_CFAR     = 0x01,
    CV_CFL_CHUGE    = 0x02
} CV_CFL_CODE_e;

/*  enum describing compile flag target floating point package */

typedef enum CV_CFL_FPKG_e {
    CV_CFL_NDP      = 0x00,
    CV_CFL_EMU      = 0x01,
    CV_CFL_ALT      = 0x02
} CV_CFL_FPKG_e;

typedef struct SYMTYPE {
    unsigned short      reclen; /* Record length */
    unsigned short      rectyp; /* Record type */
    char        data[];
} SYMTYPE;

/*
 *  cobol information ---
 */

typedef enum CV_COBOL_e {
    CV_COBOL_dontstop,
    CV_COBOL_pfm,
    CV_COBOL_false,
    CV_COBOL_extcall
} CV_COBOL_e;

struct {
    unsigned short subtype;     /* see CV_COBOL_e above */
    unsigned short flag;
} cobol;

/*      non-model specific symbol types */

typedef struct REGSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_REGISTER */
    unsigned short  typind;     /* Type index */
    unsigned short  reg;        /* register enumerate */

unsigned char   name[1];    /* Length-prefixed name */
} REGSYM;

typedef struct CONSTSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_CONSTANT */
    CV_typ_t        typind;     /* Type index (containing enum if enumerate) */
    unsigned short  value;      /* numeric leaf containing value */
    unsigned char   name[];     /* Length-prefixed name */
} CONSTSYM;

typedef struct UDTSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_UDT |S_COBOLUDT */
    CV_typ_t        typind;     /* Type index */
    unsigned char   name[1];    /* Length-prefixed name */
} UDTSYM;

typedef struct SEARCHSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_SSEARCH */
    unsigned long   startsym;   /* offset of the procedure */
    unsigned short  seg;        /* segment of symbol */
} SEARCHSYM;

typedef struct CFLAGSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_COMPILE */
    unsigned char   machine;    /* target processor */
    struct  {
        unsigned char   language    :8; /* language index */
        unsigned char   pcode       :1; /* true if pcode present */
        unsigned char   floatprec   :2; /* floating precision */
        unsigned char   floatpkg    :2; /* float package */
        unsigned char   ambdata     :3; /* ambiant data model */
        unsigned char   ambcode     :3; /* ambiant code model */
        unsigned char   mode32      :1; /* true if compiled 32 bit mode */
        unsigned char   pad         :4; /* reserved */
    } flags;
    unsigned char       ver[1]; /* Length-prefixed compiler version string */
} CFLAGSYM;

typedef struct OBJNAMESYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_OBJNAME */
    unsigned long   signature;  /* signature */
    unsigned char   name[1];    /* Length-prefixed name */
} OBJNAMESYM;

/*      symbol types for 16:16 memory model */

typedef struct BPRELSYM16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_BPREL16 */
    CV_off16_t      off;        /* BP-relative offset */
    CV_typ_t        typind;     /* Type index */
    unsigned char   name[1];    /* Length-prefixed name */
} BPRELSYM16;

typedef struct DATASYM16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_LDATA16 or S_GDATA16 or S_PUB16 */
    CV_uoff16_t     off;        /* offset of symbol */
    unsigned short  seg;        /* segment of symbol */
    CV_typ_t        typind;     /* Type index */
    unsigned char   name[1];    /* Length-prefixed name */
} DATASYM16;
typedef DATASYM16 PUBSYM16;

typedef struct PROCSYM16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_GPROC16 or S_LPROC16 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   pNext;      /* pointer to next symbol */
    unsigned short  len;        /* Proc length */
    unsigned short  DbgStart;   /* Debug start offset */
    unsigned short  DbgEnd;     /* Debug end offset */
    CV_uoff16_t     off;        /* offset of symbol */
    unsigned short  seg;        /* segment of symbol */
    CV_typ_t        typind;     /* Type index */
    char            rtntyp;     /* Return type (NEAR/FAR) */
    unsigned char   name[1];    /* Length-prefixed name */
} PROCSYM16;

typedef struct THUNKSYM16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_THUNK16 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   pNext;      /* pointer to next symbol */
    CV_uoff16_t     off;        /* offset of symbol */
    unsigned short  seg;        /* segment of symbol */
    unsigned short  len;        /* length of thunk */
    unsigned char   ord;        /* ordinal specifying type of thunk */
    unsigned char   name[1];    /* name of thunk */
#ifdef CV
    unsigned char   variant[0]; /* variant portion of thunk */
#else
    unsigned char   variant[1]; /* variant portion of thunk */
#endif
} THUNKSYM16;

typedef enum {
    THUNK_ORDINAL_NOTYPE,
    THUNK_ORDINAL_ADJUSTOR,
    THUNK_ORDINAL_VCALL
} THUNK_ORDINAL;

typedef struct LABELSYM16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_LABEL16 */
    CV_uoff16_t     off;        /* offset of symbol */
    unsigned short  seg;        /* segment of symbol */
    char            rtntyp;     /* Return type (NEAR/FAR) */
    unsigned char   name[1];    /* Length-prefixed name */
} LABELSYM16;

typedef enum CV_RETURN_TYPE_e {
    CV_RETURN_NEAR = 0,         /* near return */
    CV_RETURN_FAR  = 4          /* far return */
} CV_RETURN_TYPE_e;

typedef struct BLOCKSYM16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_BLOCK16 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned short  len;        /* Block length */
    CV_uoff16_t     off;        /* offset of symbol */
    unsigned short  seg;        /* segment of symbol */
    unsigned char   name[1];    /* Length-prefixed name */
} BLOCKSYM16;

typedef struct WITHSYM16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_WITH16 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned short  len;        /* Block length */
    CV_uoff16_t     off;        /* offset of symbol */
    unsigned short  seg;        /* segment of symbol */
    unsigned char   name[1];    /* Length-prefixed name */
} WITHSYM16;

typedef enum CEXM_MODEL_e {
    CEXM_MDL_table  = 0x00,     /* not executable */
    CEXM_MDL_native = 0x20,     /* native */
    CEXM_MDL_cobol  = 0x21,     /* cobol */
    CEXM_MDL_pcode  = 0x40      /* pcode */
} CEXM_MODEL_e;

typedef struct CEXMSYM16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_CEXMODEL16 */
    CV_uoff16_t     off;        /* offset of symbol */
    unsigned short  seg;        /* segment of symbol */
    unsigned short  model;      /* execution model */
    union variant {
        struct  {
            CV_uoff16_t pcdtable; /* offset to pcode function table */
            CV_uoff16_t pcdspi; /* offset to segment pcode information */
        } pcode;
    } u;
} CEXMSYM16;

typedef struct VPATHSYM16 {
    unsigned short  reclen;     /* record length */
    unsigned short  rectyp;     /* S_VFTPATH16 */
    CV_uoff16_t     off;        /* offset of virtual function table */
    unsigned short  seg;        /* segment of virtual function table */
    CV_typ_t        root;       /* type index of the root of path */
    CV_typ_t        path;       /* type index of the path record */
} VPATHSYM16;

typedef struct REGREL16 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_REGREL16 */
    CV_uoff16_t     off;        /* offset of symbol */
    unsigned short  reg;        /* register index */
    CV_typ_t        typind;     /* Type index */
    unsigned char   name[1];    /* Length-prefixed name */
} REGREL16;

typedef struct BPRELSYM32 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_BPREL32 */
    CV_off32_t      off;        /* BP-relative offset */
    CV_typ_t        typind;     /* Type index */
    unsigned char   name[1];    /* Length-prefixed name */
} BPRELSYM32;

typedef struct DATASYM32 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_LDATA32, S_GDATA32, S_LTHREAD32,
                                   S_GTHREAD32 or S_PUB32 */
    CV_uoff32_t     off;
    unsigned short  seg;
    CV_typ_t        typind;     /* Type index */
    unsigned char   name[1];    /* Length-prefixed name */
} DATASYM32;
typedef DATASYM32 PUBSYM32;

typedef struct PROCSYM32 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_GPROC32 or S_LPROC32 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   pNext;      /* pointer to next symbol */
    unsigned long   len;        /* Proc length */
    unsigned long   DbgStart;   /* Debug start offset */
    unsigned long   DbgEnd;     /* Debug end offset */
    CV_uoff32_t     off;
    unsigned short  seg;
    CV_typ_t        typind;     /* Type index */
    char            rtntyp;     /* Return type (NEAR/FAR) */
    unsigned char   name[1];    /* Length-prefixed name */
} PROCSYM32;

typedef struct THUNKSYM32 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_THUNK32 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   pNext;      /* pointer to next symbol */
    CV_uoff32_t     off;
    unsigned short  seg;
    unsigned short  len;        /* length of thunk */
    unsigned char   ord;        /* ordinal specifying type of thunk */
    unsigned char   name[1];    /* Length-prefixed name */
#ifdef CV
    unsigned char   variant[0]; /* variant portion of thunk */
#else
    unsigned char   variant[1]; /* variant portion of thunk */
#endif
} THUNKSYM32;

typedef struct LABELSYM32 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_LABEL32 */
    CV_uoff32_t     off;
    unsigned short  seg;
    char            rtntyp;     /* Return type (NEAR/FAR) */
    unsigned char   name[1];    /* Length-prefixed name */
} LABELSYM32;

typedef struct BLOCKSYM32 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_BLOCK32 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   len;        /* Block length */
    CV_uoff32_t     off;        /* Offset in code segment */
    unsigned short  seg;        /* segment of label */
    unsigned char   name[1];    /* Length-prefixed name */
} BLOCKSYM32;

typedef struct WITHSYM32 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_WITH32 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   len;        /* Block length */
    CV_uoff32_t     off;        /* Offset in code segment */
    unsigned short  seg;        /* segment of label */
    unsigned char   name[1];    /* Length-prefixed expression string */
} WITHSYM32;

typedef struct VPATHSYM32 {
    unsigned short  reclen;     /* record length */
    unsigned short  rectyp;     /* S_VFTPATH32 */
    CV_uoff32_t     off;        /* offset of virtual function table */
    unsigned short  seg;        /* segment of virtual function table */
    CV_typ_t        root;       /* type index of the root of path */
    CV_typ_t        path;       /* type index of the path record */
} VPATHSYM32;

typedef struct REGREL32 {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_REGREL32 */
    CV_uoff32_t     off;        /* offset of symbol */
    unsigned short  reg;        /* register index for symbol */
    CV_typ_t        typind;     /* Type index */
    unsigned char   name[1];    /* Length-prefixed name */
} REGREL32, * LPREGREL32;

typedef struct PROCSYMMIPS {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_GPROCMIPS or S_LPROCMIPS */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   pNext;      /* pointer to next symbol */
    unsigned long   len;        /* Proc length */
    unsigned long   DbgStart;   /* Debug start offset */
    unsigned long   DbgEnd;     /* Debug end offset */
    unsigned long   regSave;    /* int register save mask */
    unsigned long   fpSave;     /* fp register save mask */
    unsigned long   intOff;     /* int register save offset */
    unsigned long   fpOff;      /* fp register save offset */
    CV_uoff32_t     off;        /* Symbol offset */
    unsigned short  seg;        /* Symbol segment */
    CV_typ_t        typind;     /* Type index */
    char            retReg;     /* Register return value is in */
    char            frameReg;   /* Frame pointer register */
    unsigned char   name[1];    /* Length-prefixed name */
} PROCSYMMIPS, *PROCPTRMIPS;

/*  generic block definition symbols */
/*  these are similar to the equivalent 16:16 or 16:32 symbols but */
/*  only define the length, type and linkage fields */

typedef struct PROCSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_GPROC16 or S_LPROC16 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   pNext;      /* pointer to next symbol */
} PROCSYM;

typedef struct THUNKSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_THUNK */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
    unsigned long   pNext;      /* pointer to next symbol */
} THUNKSYM;

typedef struct BLOCKSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_BLOCK16 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
} BLOCKSYM;

typedef struct WITHSYM {
    unsigned short  reclen;     /* Record length */
    unsigned short  rectyp;     /* S_WITH16 */
    unsigned long   pParent;    /* pointer to the parent */
    unsigned long   pEnd;       /* pointer to this blocks end */
} WITHSYM;

typedef enum CV_HREG_e {
    /*
     *  Register set for the Intel 80x86 and ix86 processor series
     *  (plus PCODE registers)
     */

    CV_REG_NONE  =   0,
    CV_REG_AL    =   1,
    CV_REG_CL    =   2,
    CV_REG_DL    =   3,
    CV_REG_BL    =   4,
    CV_REG_AH    =   5,
    CV_REG_CH    =   6,
    CV_REG_DH    =   7,
    CV_REG_BH    =   8,
    CV_REG_AX    =   9,
    CV_REG_CX    =  10,
    CV_REG_DX    =  11,
    CV_REG_BX    =  12,
    CV_REG_SP    =  13,
    CV_REG_BP    =  14,
    CV_REG_SI    =  15,
    CV_REG_DI    =  16,
    CV_REG_EAX   =  17,
    CV_REG_ECX   =  18,
    CV_REG_EDX   =  19,
    CV_REG_EBX   =  20,
    CV_REG_ESP   =  21,
    CV_REG_EBP   =  22,
    CV_REG_ESI   =  23,
    CV_REG_EDI   =  24,
    CV_REG_ES    =  25,
    CV_REG_CS    =  26,
    CV_REG_SS    =  27,
    CV_REG_DS    =  28,
    CV_REG_FS    =  29,
    CV_REG_GS    =  30,
    CV_REG_IP    =  31,
    CV_REG_FLAGS =  32,
    CV_REG_EIP   =  33,
    CV_REG_EFLAGS = 34,
    CV_REG_TEMP  =  40,         /* PCODE Temp */
    CV_REG_TEMPH =  41,         /* PCODE TempH */
    CV_REG_QUOTE =  42,         /* PCODE Quote */
    CV_REG_PCDR3 =  43,         /* PCODE reserved */
    CV_REG_PCDR4 =  44,         /* PCODE reserved */
    CV_REG_PCDR5 =  45,         /* PCODE reserved */
    CV_REG_PCDR6 =  46,         /* PCODE reserved */
    CV_REG_PCDR7 =  47,         /* PCODE reserved */
    CV_REG_CR0   =  80,         /* CR0 -- control registers */
    CV_REG_CR1   =  81,
    CV_REG_CR2   =  82,
    CV_REG_CR3   =  83,
    CV_REG_DR0   =  90,         /* Debug register */
    CV_REG_DR1   =  91,
    CV_REG_DR2   =  92,
    CV_REG_DR3   =  93,
    CV_REG_DR4   =  94,
    CV_REG_DR5   =  95,
    CV_REG_DR6   =  96,
    CV_REG_DR7   =  97,
    CV_REG_ST0   =  128,
    CV_REG_ST1   =  129,
    CV_REG_ST2   =  130,
    CV_REG_ST3   =  131,
    CV_REG_ST4   =  132,
    CV_REG_ST5   =  133,
    CV_REG_ST6   =  134,
    CV_REG_ST7   =  135,
    CV_REG_CTRL  =  136,
    CV_REG_STAT  =  137,
    CV_REG_TAG   =  138,
    CV_REG_FPIP  =  139,
    CV_REG_FPCS  =  140,
    CV_REG_FPDO  =  141,
    CV_REG_FPDS  =  142,
    CV_REG_ISEM  =  143,
    CV_REG_FPEIP =  144,
    CV_REG_FPEDO =  145,

    /*
     * registers for the 68K processors
     */

    CV_R68_D0   =   0,
    CV_R68_D1   =   1,
    CV_R68_D2   =   2,
    CV_R68_D3   =   3,
    CV_R68_D4   =   4,
    CV_R68_D5   =   5,
    CV_R68_D6   =   6,
    CV_R68_D7   =   7,
    CV_R68_A0   =   8,
    CV_R68_A1   =   9,
    CV_R68_A2   =   10,
    CV_R68_A3   =   11,
    CV_R68_A4   =   12,
    CV_R68_A5   =   13,
    CV_R68_A6   =   14,
    CV_R68_A7   =   15,
    CV_R68_CCR  =   16,
    CV_R68_SR   =   17,
    CV_R68_USP  =   18,
    CV_R68_MSP  =   19,
    CV_R68_SFC  =   20,
    CV_R68_DFC  =   21,
    CV_R68_CACR =   22,
    CV_R68_VBR  =   23,
    CV_R68_CAAR =   24,
    CV_R68_ISP  =   25,
    CV_R68_PC   =   26,
                                /* reserved  27 */
    CV_R68_FPCR =   28,
    CV_R68_FPSR =   29,
    CV_R68_FPIAR=   30,
                                /* reserved  31 */
    CV_R68_FP0  =   32,
    CV_R68_FP1  =   33,
    CV_R68_FP2  =   34,
    CV_R68_FP3  =   35,
    CV_R68_FP4  =   36,
    CV_R68_FP5  =   37,
    CV_R68_FP6  =   38,
    CV_R68_FP7  =   39,
                                /* reserved  40-50 */
    CV_R68_PSR  =   51,
    CV_R68_PCSR =   52,
    CV_R68_VAL  =   53,
    CV_R68_CRP  =   54,
    CV_R68_SRP  =   55,
    CV_R68_DRP  =   56,
    CV_R68_TC   =   57,
    CV_R68_AC   =   58,
    CV_R68_SCC  =   59,
    CV_R68_CAL  =   60,
    CV_R68_TT0  =   61,
    CV_R68_TT1  =   62,
                                /* reserved  63 */
    CV_R68_BAD0 =   64,
    CV_R68_BAD1 =   65,
    CV_R68_BAD2 =   66,
    CV_R68_BAD3 =   67,
    CV_R68_BAD4 =   68,
    CV_R68_BAD5 =   69,
    CV_R68_BAD6 =   70,
    CV_R68_BAD7 =   71,
    CV_R68_BAC0 =   72,
    CV_R68_BAC1 =   73,
    CV_R68_BAC2 =   74,
    CV_R68_BAC3 =   75,
    CV_R68_BAC4 =   76,
    CV_R68_BAC5 =   77,
    CV_R68_BAC6 =   78,
    CV_R68_BAC7 =   79,

    /*
     * Register set for the MIPS 4000
     */

    CV_M4_NOREG    =   CV_REG_NONE,

    CV_M4_IntZERO  =   10,      /* CPU REGISTER */
    CV_M4_IntAT    =   11,
    CV_M4_IntV0    =   12,
    CV_M4_IntV1    =   13,
    CV_M4_IntA0    =   14,
    CV_M4_IntA1    =   15,
    CV_M4_IntA2    =   16,
    CV_M4_IntA3    =   17,
    CV_M4_IntT0    =   18,
    CV_M4_IntT1    =   19,
    CV_M4_IntT2    =   20,
    CV_M4_IntT3    =   21,
    CV_M4_IntT4    =   22,
    CV_M4_IntT5    =   23,
    CV_M4_IntT6    =   24,
    CV_M4_IntT7    =   25,
    CV_M4_IntS0    =   26,
    CV_M4_IntS1    =   27,
    CV_M4_IntS2    =   28,
    CV_M4_IntS3    =   29,
    CV_M4_IntS4    =   30,
    CV_M4_IntS5    =   31,
    CV_M4_IntS6    =   32,
    CV_M4_IntS7    =   33,
    CV_M4_IntT8    =   34,
    CV_M4_IntT9    =   35,
    CV_M4_IntKT0   =   36,
    CV_M4_IntKT1   =   37,
    CV_M4_IntGP    =   38,
    CV_M4_IntSP    =   39,
    CV_M4_IntS8    =   40,
    CV_M4_IntRA    =   41,
    CV_M4_IntLO    =   42,
    CV_M4_IntHI    =   43,

    CV_M4_Fir      =   50,
    CV_M4_Psr      =   51,

    CV_M4_FltF0    =   60,      /* Floating point registers */
    CV_M4_FltF1    =   61,
    CV_M4_FltF2    =   62,
    CV_M4_FltF3    =   63,
    CV_M4_FltF4    =   64,
    CV_M4_FltF5    =   65,
    CV_M4_FltF6    =   66,
    CV_M4_FltF7    =   67,
    CV_M4_FltF8    =   68,
    CV_M4_FltF9    =   69,
    CV_M4_FltF10   =   70,
    CV_M4_FltF11   =   71,
    CV_M4_FltF12   =   72,
    CV_M4_FltF13   =   73,
    CV_M4_FltF14   =   74,
    CV_M4_FltF15   =   75,
    CV_M4_FltF16   =   76,
    CV_M4_FltF17   =   77,
    CV_M4_FltF18   =   78,
    CV_M4_FltF19   =   79,
    CV_M4_FltF20   =   80,
    CV_M4_FltF21   =   81,
    CV_M4_FltF22   =   82,
    CV_M4_FltF23   =   83,
    CV_M4_FltF24   =   84,
    CV_M4_FltF25   =   85,
    CV_M4_FltF26   =   86,
    CV_M4_FltF27   =   87,
    CV_M4_FltF28   =   88,
    CV_M4_FltF29   =   89,
    CV_M4_FltF30   =   90,
    CV_M4_FltF31   =   92,
    CV_M4_FltFsr   =   93,

    /*
     * Register set for the ALPHA AXP
     */

    CV_ALPHA_NOREG    = CV_REG_NONE,

CV_ALPHA_FltF0  =   10,  /* Floating point registers */
CV_ALPHA_FltF1, // 11
CV_ALPHA_FltF2, // 12
CV_ALPHA_FltF3, // 13
CV_ALPHA_FltF4, // 14
CV_ALPHA_FltF5, // 15
CV_ALPHA_FltF6, // 16
CV_ALPHA_FltF7, // 17
CV_ALPHA_FltF8, // 18
CV_ALPHA_FltF9, // 19
CV_ALPHA_FltF10,// 20
CV_ALPHA_FltF11,// 21
CV_ALPHA_FltF12,// 22
CV_ALPHA_FltF13,// 23
CV_ALPHA_FltF14,// 24
CV_ALPHA_FltF15,// 25
CV_ALPHA_FltF16,// 26
CV_ALPHA_FltF17,// 27
CV_ALPHA_FltF18,// 28
CV_ALPHA_FltF19,// 29
CV_ALPHA_FltF20,// 30
CV_ALPHA_FltF21,// 31
CV_ALPHA_FltF22,// 32
CV_ALPHA_FltF23,// 33
CV_ALPHA_FltF24,// 34
CV_ALPHA_FltF25,// 35
CV_ALPHA_FltF26,// 36
CV_ALPHA_FltF27,// 37
CV_ALPHA_FltF28,// 38
CV_ALPHA_FltF29,// 39
CV_ALPHA_FltF30,// 30
CV_ALPHA_FltF31,// 41

CV_ALPHA_IntV0, // 42Integer registers
CV_ALPHA_IntT0, // 43
CV_ALPHA_IntT1, // 44
CV_ALPHA_IntT2, // 45
CV_ALPHA_IntT3, // 46
CV_ALPHA_IntT4, // 47
CV_ALPHA_IntT5, // 48
CV_ALPHA_IntT6, // 49
CV_ALPHA_IntT7, // 50
CV_ALPHA_IntS0, // 51
CV_ALPHA_IntS1, // 52
CV_ALPHA_IntS2, // 53
CV_ALPHA_IntS3, // 54
CV_ALPHA_IntS4, // 55
CV_ALPHA_IntS5, // 56
CV_ALPHA_IntFP, // 57
CV_ALPHA_IntA0, // 58
CV_ALPHA_IntA1, // 59
CV_ALPHA_IntA2, // 60
CV_ALPHA_IntA3, // 61
CV_ALPHA_IntA4, // 62
CV_ALPHA_IntA5, // 63
CV_ALPHA_IntT8, // 64
CV_ALPHA_IntT9, // 65
CV_ALPHA_IntT10,// 66
CV_ALPHA_IntT11,// 67
CV_ALPHA_IntRA, // 68
CV_ALPHA_IntT12,// 69
CV_ALPHA_IntAT, // 70
CV_ALPHA_IntGP, // 71
CV_ALPHA_IntSP, // 72
CV_ALPHA_IntZERO,// 73

CV_ALPHA_Fpcr,// 74Control registers
CV_ALPHA_Fir,// 75
CV_ALPHA_Psr,// 76
CV_ALPHA_FltFsr // 77

} CV_HREG_e;

#pragma pack()

#endif /* CV_INFO_INCLUDED */


DRWATSON.H

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    drwatson.h

Abstract:

    Common header file for drwatson data structures.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <imagehlp.h>

typedef struct _tagOPTIONS {
    CHAR                        szLogPath[MAX_PATH];
    CHAR                        szWaveFile[MAX_PATH];
    CHAR                        szCrashDump[MAX_PATH];
    BOOL                        fDumpSymbols;
    BOOL                        fDumpAllThreads;
    BOOL                        fAppendToLogFile;
    BOOL                        fVisual;
    BOOL                        fSound;
    BOOL                        fCrash;
    DWORD                       dwInstructions;
    DWORD                       dwMaxCrashes;
} OPTIONS, *POPTIONS;

typedef struct _tagCRASHES {
    char                        szAppName[256];
    char                        szFunction[256];
    SYSTEMTIME                  time;
    DWORD                       dwExceptionCode;
    DWORD                       dwAddress;
} CRASHES, *PCRASHES;

typedef struct _tagCRASHINFO {
    HWND       hList;
    CRASHES    crash;
    HDC        hdc;
    DWORD      cxExtent;
    DWORD      dwIndex;
    DWORD      dwIndexDesired;
    char       *pCrashData;
    DWORD      dwCrashDataSize;
} CRASHINFO, *PCRASHINFO;

typedef struct _tagTHREADCONTEXT {
    LIST_ENTRY                   ThreadList;
    HANDLE                       hThread;
    DWORD                        dwThreadId;
    DWORD                        pc;
    DWORD                        frame;
    DWORD                        stack;
    CONTEXT                      context;
    DWORD                        stackBase;
    DWORD                        stackRA;
    BOOL                         fFaultingContext;
} THREADCONTEXT, *PTHREADCONTEXT;

typedef struct _tagDEBUGPACKET {
    HWND                    hwnd;
    HANDLE                  hEvent;
    OPTIONS                 options;
    DWORD                   dwPidToDebug;
    HANDLE                  hEventToSignal;
    HANDLE                  hProcess;
    DWORD                   dwProcessId;
    LIST_ENTRY              ThreadList;
    PTHREADCONTEXT          tctx;
    DWORD                   stackBase;
    DWORD                   stackRA;
    DEBUG_EVENT             DebugEvent;
} DEBUGPACKET, *PDEBUGPACKET;

typedef BOOL (CALLBACK* CRASHESENUMPROC)(PCRASHINFO);

#if DBG
#define Assert(exp)    if(!(exp)) {AssertError(#exp,__FILE__,__LINE__);}
#else
#define Assert(exp)
#endif

#define WM_DUMPCOMPLETE       WM_USER+500
#define WM_EXCEPTIONINFO      WM_USER+501
#define WM_ATTACHCOMPLETE     WM_USER+502

extern PIMAGEHLP_SYMBOL sym;
extern char szApp[MAX_PATH];

#if defined(i386)

#define BP_SIZE 1
#define PC(C) ((C)->Eip)

#elif defined(PPC)

#define BP_SIZE 4
#define PC(C) ((C)->Iar)

#elif defined(MIPS)

#define BP_SIZE 4
#define PC(C) ((C)->Fir)

#elif defined(ALPHA)

#define BP_SIZE 4
#define PC(C) ((C)->Fir)

#else

#error "Unknown target CPU"

#endif

//  **** From ntddk.h
//
//  Doubly-linked list manipulation routines.  Implemented as macros
//  but logically these are procedures.
//

//
//  VOID
//  InitializeListHead(
//      PLIST_ENTRY ListHead
//      );
//

#define InitializeListHead(ListHead) (\
    (ListHead)->Flink = (ListHead)->Blink = (ListHead))

//
//  BOOLEAN
//  IsListEmpty(
//      PLIST_ENTRY ListHead
//      );
//

#define IsListEmpty(ListHead) \
    ((ListHead)->Flink == (ListHead))

//
//  PLIST_ENTRY
//  RemoveHeadList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveHeadList(ListHead) \
    (ListHead)->Flink;\
    {RemoveEntryList((ListHead)->Flink)}

//
//  PLIST_ENTRY
//  RemoveTailList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveTailList(ListHead) \
    (ListHead)->Blink;\
    {RemoveEntryList((ListHead)->Blink)}

//
//  VOID
//  RemoveEntryList(
//      PLIST_ENTRY Entry
//      );
//

#define RemoveEntryList(Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_Flink;\
    _EX_Flink = (Entry)->Flink;\
    _EX_Blink = (Entry)->Blink;\
    _EX_Blink->Flink = _EX_Flink;\
    _EX_Flink->Blink = _EX_Blink;\
    }

//
//  VOID
//  InsertTailList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertTailList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Blink = _EX_ListHead->Blink;\
    (Entry)->Flink = _EX_ListHead;\
    (Entry)->Blink = _EX_Blink;\
    _EX_Blink->Flink = (Entry);\
    _EX_ListHead->Blink = (Entry);\
    }

//
//  VOID
//  InsertHeadList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertHeadList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Flink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Flink = _EX_ListHead->Flink;\
    (Entry)->Flink = _EX_Flink;\
    (Entry)->Blink = _EX_ListHead;\
    _EX_Flink->Blink = (Entry);\
    _EX_ListHead->Flink = (Entry);\
    }

//
//
//  PSINGLE_LIST_ENTRY
//  PopEntryList(
//      PSINGLE_LIST_ENTRY ListHead
//      );
//

#define PopEntryList(ListHead) \
    (ListHead)->Next;\
    {\
        PSINGLE_LIST_ENTRY FirstEntry;\
        FirstEntry = (ListHead)->Next;\
        if (FirstEntry != NULL) {     \
            (ListHead)->Next = FirstEntry->Next;\
        }                             \
    }

//
//  VOID
//  PushEntryList(
//      PSINGLE_LIST_ENTRY ListHead,
//      PSINGLE_LIST_ENTRY Entry
//      );
//

#define PushEntryList(ListHead,Entry) \
    (Entry)->Next = (ListHead)->Next; \
    (ListHead)->Next = (Entry)


PROTO.H

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    proto.h

Abstract:

    Prototypes for drwatson.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#include <imagehlp.h>

// error.c
void NonFatalError(char *format, ...);
void FatalError(char *format, ...);
void AssertError( char *exp, char * file, DWORD line );
void dprintf(char *format, ...);

//log.c
void OpenLogFile( char *szFileName, BOOL fAppend, BOOL fVisual );
void CloseLogFile( void );
void lprintfs(char *format, ...);
void lprintf(DWORD dwFormatId, ...);
void MakeLogFileName( char *szName );
char * GetLogFileData( LPDWORD dwLogFileDataSize );

// walk.c
LPVOID SwFunctionTableAccess( HANDLE  hProcess, DWORD   dwPCAddr );
DWORD  SwGetModuleBase( HANDLE  hProcess, DWORD   ReturnAddress );
BOOL   SwReadProcessMemory( HANDLE  hProcess, LPCVOID lpBaseAddress, LPVOID  lpBuffer, DWORD   nSize, LPDWORD lpNumberOfBytesRead );
DWORD  SwTranslateAddress( HANDLE    hProcess, HANDLE    hThread, LPADDRESS lpaddr );

// regs.c
void   OutputAllRegs(PDEBUGPACKET dp, BOOL Show64);
DWORDLONG  GetRegValue(PDEBUGPACKET dp, ULONG regnum);
DWORDLONG GetRegFlagValue (PDEBUGPACKET dp, ULONG regnum);

// disasm.c
BOOLEAN disasm( PDEBUGPACKET dp, PULONG pOffset, PUCHAR pchDst, BOOLEAN fEAout );

// symbols.c
void DumpSymbols( PDEBUGPACKET dp );

// module.c
BOOL ProcessModuleLoad ( PDEBUGPACKET dp, LPDEBUG_EVENT de );

// debug.c
DWORD DispatchDebugEventThread( PDEBUGPACKET dp );
DWORD TerminationThread( PDEBUGPACKET dp );
BOOL  DoMemoryRead(PDEBUGPACKET dp, LPCVOID addr, LPVOID buf, DWORD size, LPDWORD lpcb);

// registry.c
BOOL RegInitialize( POPTIONS o );
BOOL RegSave( POPTIONS o );
DWORD RegGetNumCrashes( void );
void RegSetNumCrashes( DWORD dwNumCrashes );
void RegLogCurrentVersion( void );
BOOLEAN RegInstallDrWatson( BOOL fQuiet );
void RegLogProcessorType( void );

// eventlog.c
BOOL ElSaveCrash( PCRASHES crash, DWORD dwNumCrashes );
BOOL ElEnumCrashes( PCRASHINFO crashInfo, CRASHESENUMPROC lpEnumFunc );
BOOL ElClearAllEvents( void );

// process.c
void LogTaskList( void );
void LogProcessInformation( HANDLE hProcess );
void GetTaskName( ULONG pid, char *szTaskName, LPDWORD pdwSize );

// context.c
void GetContextForThread( PDEBUGPACKET dp );

// browse.c
BOOL BrowseForDirectory( char *szCurrDir );
BOOL GetWaveFileName( char *szWaveName );
BOOL GetDumpFileName( char *szDumpName );

// notify.c
void NotifyWinMain ( void );
BOOLEAN GetCommandLineArgs( LPDWORD dwPidToDebug, LPHANDLE hEventToSignal );

// ui.c
void DrWatsonWinMain ( void );

// util.c
void GetAppName( char *pszAppName, DWORD len );
void GetHelpFileName( char *pszHelpFileName, DWORD len );
char * LoadRcString( UINT wId );

// controls.c
BOOL SubclassControls( HWND hwnd );
void SetFocusToCurrentControl( void );

// dump.c
BOOL CreateDumpFile( PDEBUGPACKET dp, LPEXCEPTION_DEBUG_INFO  ed );


RESOURCE.H

/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    resource.h

Abstract:

    Resource file constants.

Author:

    Wesley Witt (wesw) 1-May-1993

Environment:

    User Mode

--*/

#define ID_LOGPATH              101
#define ID_INSTRUCTIONS         102
#define ID_DUMPSYMBOLS          103
#define ID_DUMPALLTHREADS       104
#define ID_APPENDTOLOGFILE      105
#define ID_VISUAL               106
#define ID_SOUND                107
#define ID_CRASHES              108
#define ID_BROWSE_LOGPATH       109
#define ID_WAVE_FILE            110
#define ID_BROWSE_WAVEFILE      111
#define ID_NUM_CRASHES          112
#define ID_CLEAR                113
#define ID_TEST_WAVE            114
#define ID_TEXT1                115
#define ID_TEXT2                116
#define ID_HELP                 117
#define ID_ASSERT_TEXT          118
#define ID_ASSERT_ICON          119
#define ID_LOGFILE_VIEW         120
#define ID_WAVEFILE_TEXT        121
#define ID_USAGE                122
#define ID_ABOUT                123
#define ID_CRASH                124
#define ID_CRASH_DUMP           125
#define ID_BROWSE_CRASH         126
#define ID_CRASH_DUMP_TEXT      127

#define IDS_APPLICATION_NAME    201
#define IDS_FATAL_ERROR         202
#define IDS_NONFATAL_ERROR      203
#define IDS_ASSERTION_ERROR     204
#define IDS_MEMORY              205
#define IDS_DEBUGPRIV           206
#define IDS_ATTACHFAIL          207
#define IDS_INVALID_PATH        208
#define IDS_INVALID_WAVE        209
#define IDS_CANT_OPEN_LOGFILE   210
#define IDS_INVALID_LOGFILE     211
#define IDS_ABOUT_TITLE         212
#define IDS_ABOUT_EXTRA         213
#define IDS_AE_TEXT             214
#define IDS_LOGBROWSE_TITLE     215
#define IDS_WAVEBROWSE_TITLE    216
#define IDS_WAVE_FILTER         217
#define IDS_UNKNOWN_MACHINE     218
#define IDS_UNKNOWN_USER        219
#define IDS_ABOUT               220
#define IDS_DUMPBROWSE_TITLE    221
#define IDS_DUMP_FILTER         222
#define IDS_INVALID_CRASH_PATH  223

#define IDH_INDEX               701
#define IDH_WHAT                702
#define IDH_OPTIONS             703
#define IDH_LOGFILELOCATION     704
#define IDH_WAVEFILE            705
#define IDH_NUMBEROFINSTR       706
#define IDH_NUMBEROFCRASHES     707
#define IDH_DUMPSYMBOLS         708
#define IDH_DUMPALLTHREADS      709
#define IDH_APPENDTOLOGFILE     710
#define IDH_VISUAL              711
#define IDH_SOUND               712
#define IDH_REGISTRY            713
#define IDH_EVENTLOG            714
#define IDH_WINDOWSDIR          715
#define IDH_PC                  716
#define IDH_LOGFILE             717
#define IDH_INSTALLATION        718
#define IDH_ASSERT              719
#define IDH_VIEW                720
#define IDH_CLEAR               721
#define IDH_APPERRORS           722
#define IDH_CRASH               723
#define IDH_CRASH_DUMP          724

#define NOTIFYDIALOG            501
#define DRWATSONDIALOG          502
#define DIRBROWSEDIALOG         503
#define WAVEFILEOPENDIALOG      504
#define ASSERTDIALOG            505
#define DRWATSONICON            506
#define LOGFILEVIEWERDIALOG     507
#define DRWATSONACCEL           508
#define APPICON                 509
#define USAGEDIALOG             510
#define DUMPFILEOPENDIALOG      511

#define stc1                 0x0440
#define stc2                 0x0441
#define stc3                 0x0442
#define stc4                 0x0443
#define edt1                 0x0480
#define lst1                 0x0460
#define lst2                 0x0461
#define cmb1                 0x0470
#define cmb2                 0x0471
#define psh14                0x040d
#define psh15                0x040e
#define chx1                 0x0410


CRASH.H

/*++

Copyright 1993 - 1998 Microsoft Corporation

Module Name:

    crash.h

Abstract:

    This module implements support for handling crash dump files.

    *** Use this file when linking againts crashxxx.lib

Revision History:

--*/

#ifndef _CRASHLIB_
#define _CRASHLIB_

#ifdef __cplusplus
#pragma warning(disable:4200)
extern "C" {
#endif

typedef struct _USERMODE_CRASHDUMP_HEADER {
    DWORD       Signature;
    DWORD       ValidDump;
    DWORD       MajorVersion;
    DWORD       MinorVersion;
    DWORD       MachineImageType;
    DWORD       ThreadCount;
    DWORD       ModuleCount;
    DWORD       MemoryRegionCount;
    DWORD       ThreadOffset;
    DWORD       ModuleOffset;
    DWORD       DataOffset;
    DWORD       MemoryRegionOffset;
    DWORD       DebugEventOffset;
    DWORD       ThreadStateOffset;
    DWORD       Spare0;
    DWORD       Spare1;
} USERMODE_CRASHDUMP_HEADER, *PUSERMODE_CRASHDUMP_HEADER;

typedef struct _CRASH_MODULE {
    DWORD       BaseOfImage;
    DWORD       SizeOfImage;
    DWORD       ImageNameLength;
    CHAR        ImageName[0];
} CRASH_MODULE, *PCRASH_MODULE;

typedef struct _CRASH_THREAD {
    DWORD       ThreadId;
    DWORD       SuspendCount;
    DWORD       PriorityClass;
    DWORD       Priority;
    DWORD       Teb;
    DWORD       Spare0;
    DWORD       Spare1;
    DWORD       Spare2;
    DWORD       Spare3;
    DWORD       Spare4;
    DWORD       Spare5;
    DWORD       Spare6;
} CRASH_THREAD, *PCRASH_THREAD;

//
// usermode crash dump data types
//
#define DMP_EXCEPTION                 1 // obsolete
#define DMP_MEMORY_BASIC_INFORMATION  2
#define DMP_THREAD_CONTEXT            3
#define DMP_MODULE                    4
#define DMP_MEMORY_DATA               5
#define DMP_DEBUG_EVENT               6
#define DMP_THREAD_STATE              7

//
// usermode crashdump callback function
//
typedef BOOL  (*PDMP_CREATE_DUMP_CALLBACK)(
    DWORD       DataType,
    PVOID*      DumpData,
    LPDWORD     DumpDataLength,
    PVOID       UserData
    );

BOOL
DmpCreateUserDump(
    IN LPSTR                       CrashDumpName,
    IN PDMP_CREATE_DUMP_CALLBACK   DmpCallback,
    IN PVOID                       lpv
    );

BOOL
DmpInitialize (
    IN  LPSTR               FileName,
    OUT PCONTEXT            *Context,
    OUT PEXCEPTION_RECORD   *Exception,
    OUT PVOID               *DmpHeader
    );

VOID
DmpUnInitialize (
    VOID
    );

DWORD
DmpReadMemory (
    IN PVOID BaseAddress,
    IN PVOID Buffer,
    IN ULONG Size
    );

DWORD
DmpWriteMemory (
    IN PVOID BaseAddress,
    IN PVOID Buffer,
    IN ULONG Size
    );

PVOID
VaToLocation (
    IN PVOID VirtualAddress
    );

PVOID
PhysicalToLocation (
    IN PVOID PhysicalAddress
    );

PVOID
PageToLocation (
    IN ULONG Page
    );

ULONG
GetPhysicalPage (
    IN PVOID PhysicalAddress
    );

BOOL
MapDumpFile(
    IN  LPSTR  FileName
    );

ULONG
PteToPfn (
    IN ULONG Pte
    );

ULONG
GetPhysicalPage (
    IN PVOID PhysicalAddress
    );

DWORD
DmpReadPhysicalMemory (
    IN PVOID BaseAddress,
    IN PVOID Buffer,
    IN ULONG Size
    );

DWORD
DmpWritePhysicalMemory (
    IN PVOID BaseAddress,
    IN PVOID Buffer,
    IN ULONG Size
    );

BOOL
DmpReadControlSpace(
    IN USHORT   Processor,
    IN PVOID    TargetBaseAddress,
    OUT PVOID   UserInterfaceBuffer,
    OUT ULONG   TransferCount,
    OUT PULONG  ActualBytesRead
    );

BOOL
DmpGetContext(
    IN  ULONG     Processor,
    OUT PVOID     Context
    );

INT
DmpGetCurrentProcessor(
    VOID
    );

BOOL
DmpGetThread(
    IN  ULONG           Processor,
    OUT PCRASH_THREAD   Thread
    );

#ifdef __cplusplus
}
#pragma warning(default:4200)
#endif

#endif


CV.H

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    cv.h

Abstract:

    This file contains all of the type definitions for accessing
    CODEVIEW data.

Environment:

    Win32, User Mode

--*/
#include <types.h>
#include <cvtypes.h>
#include <cvinfo.h>
#include <cvexefmt.h>

// Global Segment Info table
typedef struct _sgf {
    unsigned short      fRead   :1;
    unsigned short      fWrite  :1;
    unsigned short      fExecute:1;
    unsigned short      f32Bit  :1;
    unsigned short      res1    :4;
    unsigned short      fSel    :1;
    unsigned short      fAbs    :1;
    unsigned short      res2    :2;
    unsigned short      fGroup  :1;
    unsigned short      res3    :3;
} SGF;

typedef struct _sgi {
    SGF                 sgf;        // Segment flags
    unsigned short      iovl;       // Overlay number
    unsigned short      igr;        // Group index
    unsigned short      isgPhy;     // Physical segment index
    unsigned short      isegName;   // Index to segment name
    unsigned short      iclassName; // Index to segment class name
    unsigned long       doffseg;    // Starting offset inside physical segment
    unsigned long       cbSeg;      // Logical segment size
} SGI;

typedef struct _sgm {
    unsigned short      cSeg;       // number of segment descriptors
    unsigned short      cSegLog;    // number of logical segment descriptors
} SGM;

#define FileAlign(x)  ( ((x) + p->optrs.optHdr->FileAlignment - 1) &  \
                            ~(p->optrs.optHdr->FileAlignment - 1) )
#define SectionAlign(x) (((x) + p->optrs.optHdr->SectionAlignment - 1) &  \
                            ~(p->optrs.optHdr->SectionAlignment - 1) )

#define NextSym32(m)  ((DATASYM32 *) \
  (((DWORD)(m) + sizeof(DATASYM32) + \
    ((DATASYM32*)(m))->name[0] + 3) & ~3))

#define NextSym16(m)  ((DATASYM16 *) \
  (((DWORD)(m) + sizeof(DATASYM16) + \
    ((DATASYM16*)(m))->name[0] + 1) & ~1))


CVCOMMON.H

/*
 *   PROTOTYPES, TYPEDEFS and other items for cvcommon.c
 */

DWORD   CreateSignature( PPOINTERS p );
DWORD   CreateDirectories( PPOINTERS p );
DWORD   CreateModuleDirectoryEntries( PPOINTERS p );
DWORD   CreateSegMapDirectoryEntries( PPOINTERS p );
DWORD   CreateSrcModulesDirectoryEntries( PPOINTERS p );
DWORD   CreatePublicDirectoryEntries( PPOINTERS p );
DWORD   CreateSymbolHashTable( PPOINTERS p );
DWORD   CreateAddressSortTable( PPOINTERS p );
DWORD   DWordXorLrl(char * szSym);
OMFModule *     NextMod(OMFModule * pMod);
VOID    UpdatePtrs( PPOINTERS p, PPTRINFO pi, LPVOID lpv, DWORD count );


CVEXEFMT.H

/***    cvexefmt.h - format of CodeView information in exe
 *
 *      Structures, constants, etc. for reading CodeView information
 *      from the executable.
 *
 */

//  The following structures and constants describe the format of the
//  CodeView Debug OMF for that will be accepted by CodeView 4.0 and
//  later.  These are executables with signatures of NB05, NB06 and NB08.
//  There is some confusion about the signatures NB03 and NB04 so none
//  of the utilites will accept executables with these signatures.  NB07 is
//  the signature for QCWIN 1.0 packed executables.

//  All of the structures described below must start on a long word boundary
//  to maintain natural alignment.  Pad space can be inserted during the
//  write operation and the addresses adjusted without affecting the contents
//  of the structures.

#ifndef _CV_INFO_INCLUDED
#include "cvinfo.h"
#endif

#ifndef    FAR
#if _M_IX86 >= 300
#define    FAR
#else
#define FAR far
#endif
#endif

//  Type of subsection entry.

#define sstModule           0x120
#define sstTypes            0x121
#define sstPublic           0x122
#define sstPublicSym        0x123   // publics as symbol (waiting for link)
#define sstSymbols          0x124
#define sstAlignSym         0x125
#define sstSrcLnSeg         0x126   // because link doesn't emit SrcModule
#define sstSrcModule        0x127
#define sstLibraries        0x128
#define sstGlobalSym        0x129
#define sstGlobalPub        0x12a
#define sstGlobalTypes      0x12b
#define sstMPC              0x12c
#define sstSegMap           0x12d
#define sstSegName          0x12e
#define sstPreComp          0x12f   // precompiled types
#define sstPreCompMap       0x130   // map precompiled types in global types
#define sstOffsetMap16      0x131
#define sstOffsetMap32      0x132
#define sstFileIndex        0x133   // Index of file names
#define sstStaticSym        0x134

typedef enum OMFHash {
    OMFHASH_NONE,           // no hashing
    OMFHASH_SUMUC16,        // upper case sum of chars in 16 bit table
    OMFHASH_SUMUC32,        // upper case sum of chars in 32 bit table
    OMFHASH_ADDR16,         // sorted by increasing address in 16 bit table
    OMFHASH_ADDR32          // sorted by increasing address in 32 bit table
} OMFHASH;

//  CodeView Debug OMF signature.  The signature at the end of the file is
//  a negative offset from the end of the file to another signature.  At
//  the negative offset (base address) is another signature whose filepos
//  field points to the first OMFDirHeader in a chain of directories.
//  The NB05 signature is used by the link utility to indicated a completely
//  unpacked file.  The NB06 signature is used by ilink to indicate that the
//  executable has had CodeView information from an incremental link appended
//  to the executable.  The NB08 signature is used by cvpack to indicate that
//  the CodeView Debug OMF has been packed.  CodeView will only process
//  executables with the NB08 signature.

typedef struct OMFSignature {
    char        Signature[4];   // "NBxx"
    long        filepos;        // offset in file
} OMFSignature;

//  directory information structure
//  This structure contains the information describing the directory.
//  It is pointed to by the signature at the base address or the directory
//  link field of a preceeding directory.  The directory entries immediately
//  follow this structure.

typedef struct OMFDirHeader {
    unsigned short  cbDirHeader;    // length of this structure
    unsigned short  cbDirEntry;     // number of bytes in each directory entry
    unsigned long   cDir;           // number of directorie entries
    long            lfoNextDir;     // offset from base of next directory
    unsigned long   flags;          // status flags
} OMFDirHeader;

//  directory structure
//  The data in this structure is used to reference the data for each
//  subsection of the CodeView Debug OMF information.  Tables that are
//  not associated with a specific module will have a module index of
//  oxffff.  These tables are the global types table, the global symbol
//  table, the global public table and the library table.

typedef struct OMFDirEntry {
    unsigned short  SubSection;     // subsection type (sst...)
    unsigned short  iMod;           // module index
    long            lfo;            // large file offset of subsection
    unsigned long   cb;             // number of bytes in subsection
} OMFDirEntry;

//  information decribing each segment in a module

typedef struct OMFSegDesc {
    unsigned short  Seg;            // segment index
    unsigned short  pad;            // pad to maintain alignment
    unsigned long   Off;            // offset of code in segment
    unsigned long   cbSeg;          // number of bytes in segment
} OMFSegDesc;

//  per module information
//  There is one of these subsection entries for each module
//  in the executable.  The entry is generated by link/ilink.
//  This table will probably require padding because of the
//  variable length module name.

typedef struct OMFModule {
    unsigned short  ovlNumber;      // overlay number
    unsigned short  iLib;           // library that the module was linked from
    unsigned short  cSeg;           // count of number of segments in module
    char            Style[2];       // debugging style "CV"
    OMFSegDesc      SegInfo[1];     // describes segments in module
    char            Name[];         // length prefixed module name padded to
                                    // long word boundary
} OMFModule;

//  Symbol hash table format
//  This structure immediately preceeds the global publics table
//  and global symbol tables.

typedef struct OMFSymHash {
    unsigned short  symhash;        // symbol hash function index
    unsigned short  addrhash;       // address hash function index
    unsigned long   cbSymbol;       // length of symbol information
    unsigned long   cbHSym;         // length of symbol hash data
    unsigned long   cbHAddr;        // length of address hashdata
} OMFSymHash;

//  Global types subsection format
//  This structure immediately preceeds the global types table.
//  The offsets in the typeOffset array are relative to the address
//  of ctypes.  Each type entry following the typeOffset array must
//  begin on a long word boundary.

typedef struct OMFTypeFlags {
    unsigned long   sig     :8;
    unsigned long   unused  :24;
} OMFTypeFlags;

typedef struct OMFGlobalTypes {
    OMFTypeFlags    flags;
    unsigned long   cTypes;         // number of types
    unsigned long   typeOffset[];   // array of offsets to types
} OMFGlobalTypes;

//  Precompiled types mapping table
//  This table should be ignored by all consumers except the incremental
//  packer.

typedef struct OMFPreCompMap {
    unsigned short  FirstType;      // first precompiled type index
    unsigned short  cTypes;         // number of precompiled types
    unsigned long   signature;      // precompiled types signature
    unsigned short  pad;
    CV_typ_t        map[];          // mapping of precompiled types
} OMFPreCompMap;

//  Source line to address mapping table.
//  This table is generated by the link/ilink utility from line number
//  information contained in the object file OMF data.  This table contains
//  only the code contribution for one segment from one source file.

typedef struct OMFSourceLine {
    unsigned short  Seg;            // linker segment index
    unsigned short  cLnOff;         // count of line/offset pairs
    unsigned long   offset[1];      // array of offsets in segment
    unsigned short  lineNbr[1];     // array of line lumber in source
} OMFSourceLine;

typedef OMFSourceLine FAR * LPSL;

//  Source file description
//  This table is generated by the linker

typedef struct OMFSourceFile {
    unsigned short  cSeg;           // number of segments from source file
    unsigned short  reserved;       // reserved
    unsigned long   baseSrcLn[1];   // base of OMFSourceLine tables
                                    // this array is followed by array
                                    // of segment start/end pairs followed by
                                    // an array of linker indices
                                    // for each segment in the file
    unsigned short  cFName;         // length of source file name
    char            Name;           // name of file padded to long boundary
} OMFSourceFile;

typedef OMFSourceFile FAR * LPSF;

//  Source line to address mapping header structure
//  This structure describes the number and location of the
//  OMFAddrLine tables for a module.  The offSrcLine entries are
//  relative to the beginning of this structure.

typedef struct OMFSourceModule {
    unsigned short  cFile;          // number of OMFSourceTables
    unsigned short  cSeg;           // number of segments in module
    unsigned long   baseSrcFile[1]; // base of OMFSourceFile table
                                    // this array is followed by array
                                    // of segment start/end pairs followed
                                    // by an array of linker indices
                                    // for each segment in the module
} OMFSourceModule;

typedef OMFSourceModule FAR * LPSM;

//  sstLibraries

typedef struct OMFLibrary {
    unsigned char   cbLibs;     // count of library names
    char            Libs[1];    // array of length prefixed lib names (first entry zero length)
} OMFLibrary;

// sstFileIndex - An index of all of the files contributing to an
//  executable.

typedef struct OMFFileIndex {
    unsigned short  cmodules;       // Number of modules
    unsigned short  cfilerefs;      // Number of file references
    unsigned short  modulelist[1];  // Index to beginning of list of files
                                    // for module i. (0 for module w/o files)
    unsigned short  cfiles[1];      // Number of file names associated
                                    // with module i.
    unsigned long   ulNames[1];     // Offsets from the beginning of this
                                    // table to the file names
    char            Names[];        // The length prefixed names of files
} OMFFileIndex;

//  Offset mapping table
//  This table provides a mapping from logical to physical offsets.
//  This mapping is applied between the logical to physical mapping
//  described by the seg map table.

typedef struct OMFOffsetMap16 {
    unsigned long   csegment;       // Count of physical segments

    // The next six items are repeated for each segment

    unsigned long   crangeLog;      // Count of logical offset ranges
    unsigned short  rgoffLog[1];    // Array of logical offsets
    short           rgbiasLog[1];   // Array of logical->physical bias
    unsigned long   crangePhys;     // Count of physical offset ranges
    unsigned short  rgoffPhys[1];   // Array of physical offsets
    short           rgbiasPhys[1];  // Array of physical->logical bias
} OMFOffsetMap16;

typedef struct OMFOffsetMap32 {
    unsigned long   csection;       // Count of physical sections

    // The next six items are repeated for each section

    unsigned long   crangeLog;      // Count of logical offset ranges
    unsigned long   rgoffLog[1];    // Array of logical offsets
    long            rgbiasLog[1];   // Array of logical->physical bias
    unsigned long   crangePhys;     // Count of physical offset ranges
    unsigned long   rgoffPhys[1];   // Array of physical offsets
    long            rgbiasPhys[1];  // Array of physical->logical bias
} OMFOffsetMap32;

//  Pcode support.  This subsection contains debug information generated
//  by the MPC utility used to process Pcode executables.  Currently
//  it contains a mapping table from segment index (zero based) to
//  frame paragraph.  MPC converts segmented exe's to non-segmented
//  exe's for DOS support.  To avoid backpatching all CV info, this
//  table is provided for the mapping.  Additional info may be provided
//  in the future for profiler support.

typedef struct OMFMpcDebugInfo {
    unsigned short  cSeg;           // number of segments in module
    unsigned short  mpSegFrame[1];  // map seg (zero based) to frame
} OMFMpcDebugInfo;

//  The following structures and constants describe the format of the
//  CodeView Debug OMF for linkers that emit executables with the NB02
//  signature.  Current utilities with the exception of cvpack and cvdump
//  will not accept or emit executables with the NB02 signature.  Cvdump
//  will dump an unpacked executable with the NB02 signature.  Cvpack will
//  read an executable with the NB02 signature but the packed executable
//  will be written with the table format, contents and signature of NB08.

//  subsection type constants

#define SSTMODULE       0x101    // Basic info. about object module
#define SSTPUBLIC       0x102    // Public symbols
#define SSTTYPES        0x103    // Type information
#define SSTSYMBOLS      0x104    // Symbol Data
#define SSTSRCLINES     0x105    // Source line information
#define SSTLIBRARIES    0x106    // Names of all library files used
#define SSTIMPORTS      0x107    // Symbols for DLL fixups
#define SSTCOMPACTED    0x108    // Compacted types section
#define SSTSRCLNSEG     0x109    // Same as source lines, contains segment

typedef struct DirEntry{
    unsigned short  SubSectionType;
    unsigned short  ModuleIndex;
    long            lfoStart;
    unsigned short  Size;
} DirEntry;

//  information decribing each segment in a module

typedef struct oldnsg {
    unsigned short  Seg;         // segment index
    unsigned short  Off;         // offset of code in segment
    unsigned short  cbSeg;       // number of bytes in segment
} oldnsg;

//  old subsection module information

typedef struct oldsmd {
    oldnsg          SegInfo;     // describes first segment in module
    unsigned short  ovlNbr;      // overlay number
    unsigned short  iLib;
    unsigned char   cSeg;        // Number of segments in module
    char            reserved;
    unsigned char   cbName[1];   // length prefixed name of module
    oldnsg          arnsg[];     // cSeg-1 structures exist for alloc text or comdat code
} oldsmd;

typedef struct{
    unsigned short  Seg;
    unsigned long   Off;
    unsigned long   cbSeg;
} oldnsg32;

typedef struct {
    oldnsg32        SegInfo;     // describes first segment in module
    unsigned short  ovlNbr;      // overlay number
    unsigned short  iLib;
    unsigned char   cSeg;        // Number of segments in module
    char            reserved;
    unsigned char   cbName[1];   // length prefixed name of module
    oldnsg32        arnsg[];     // cSeg-1 structures exist for alloc text or comdat code
} oldsmd32;

// OMFSegMap - This table contains the mapping between the logical segment indices
// used in the symbol table and the physical segments where the program is loaded

typedef struct OMFSegMapDesc {
    unsigned short  flags;       // descriptor flags bit field.
    unsigned short  ovl;         // the logical overlay number
    unsigned short  group;       // group index into the descriptor array
    unsigned short  frame;       // logical segment index - interpreted via flags
    unsigned short  iSegName;    // segment or group name - index into sstSegName
    unsigned short  iClassName;  // class name - index into sstSegName
    unsigned long   offset;      // byte offset of the logical within the physical segment
    unsigned long   cbSeg;       // byte count of the logical segment or group
} OMFSegMapDesc;

typedef struct OMFSegMap {
    unsigned short  cSeg;        // total number of segment descriptors
    unsigned short  cSegLog;     // number of logical segment descriptors
    OMFSegMapDesc   rgDesc[0];   // array of segment descriptors
} OMFSegMap;


CVINFO.H

/***    cvinfo.h - Generic CodeView information definitions
 *
 *      Structures, constants, etc. for accessing and interpreting
 *      CodeView information.
 *
 */

#ifndef _CV_INFO_INCLUDED
#define _CV_INFO_INCLUDED

#ifdef  __cplusplus
#pragma warning ( disable: 4200 )
#endif

#ifndef __INLINE
#ifdef  __cplusplus
#define __INLINE inline
#else
#define __INLINE __inline
#endif
#endif

#include <pshpack1.h>
typedef unsigned long   CV_uoff32_t;
typedef          long   CV_off32_t;
typedef unsigned short  CV_uoff16_t;
typedef          short  CV_off16_t;
typedef unsigned short  CV_typ_t;

#if !defined (CV_ZEROLEN)
#define CV_ZEROLEN
#endif

#if !defined (FLOAT10)
#if defined (_M_ALPHA) ||  \
    defined (_M_MRX000) || \
    ((_MSC_VER >= 800) &&  \
     defined(_M_IX86) &&   \
     (_M_IX86 >= 300))
// above ifdef tests for 32-bit compiler and existence of long double support
typedef struct {
    char b[10];
} FLOAT10;

#else
typedef long double FLOAT10;
#endif
typedef FLOAT10 REAL10;
#endif

#define CV_SIGNATURE_C6         0L  // Actual signature is >64K
#define CV_SIGNATURE_C7         1L  // First explicit signature
#define CV_SIGNATURE_RESERVED   2L  // All signatures from 2 to 64K are reserved

#define CV_MAXOFFSET   0xffffffff

/**     CodeView Symbol and Type OMF type information is broken up into two
 *      ranges.  Type indices less than 0x1000 describe type information
 *      that is frequently used.  Type indices above 0x1000 are used to
 *      describe more complex features such as functions, arrays and
 *      structures.
 */

/**     Primitive types have predefined meaning that is encoded in the
 *      values of the various bit fields in the value.
 *
 *      A CodeView primitive type is defined as:
 *
 *      1 1
 *      1 089  7654  3  210
 *      r mode type  r  sub
 *
 *      Where
 *          mode is the pointer mode
 *          type is a type indicator
 *          sub  is a subtype enumeration
 *          r    is a reserved field
 *
 *      See Microsoft Symbol and Type OMF (Version 4.0) for more
 *      information.
 */

#define CV_MMASK        0x700   // mode mask
#define CV_TMASK        0x0f0   // type mask

#if CC_BIGINT
// can we use the reserved bit ??
#define CV_SMASK        0x008   // subtype mask
#else
#define CV_SMASK        0x007   // subtype mask
#endif

#define CV_MSHIFT       8       // primitive mode right shift count
#define CV_TSHIFT       4       // primitive type right shift count
#define CV_SSHIFT       0       // primitive subtype right shift count

// macros to extract primitive mode, type and size

#define CV_MODE(typ)    (((typ) & CV_MMASK) >> CV_MSHIFT)
#define CV_TYPE(typ)    (((typ) & CV_TMASK) >> CV_TSHIFT)
#define CV_SUBT(typ)    (((typ) & CV_SMASK) >> CV_SSHIFT)

// macros to insert new primitive mode, type and size

#define CV_NEWMODE(typ, nm)     ((CV_typ_t)(((typ) & ~CV_MMASK) | ((nm) << CV_MSHIFT)))
#define CV_NEWTYPE(typ, nt)     (((typ) & ~CV_TMASK) | ((nt) << CV_TSHIFT))
#define CV_NEWSUBT(typ, ns)     (((typ) & ~CV_SMASK) | ((ns) << CV_SSHIFT))

//     pointer mode enumeration values

typedef enum CV_prmode_e {
    CV_TM_DIRECT = 0,           // mode is not a pointer
    CV_TM_NPTR   = 1,           // mode is a near pointer
    CV_TM_FPTR   = 2,           // mode is a far pointer
    CV_TM_HPTR   = 3,           // mode is a huge pointer
    CV_TM_NPTR32 = 4,           // mode is a 32 bit near pointer
    CV_TM_FPTR32 = 5,           // mode is a 32 bit far pointer
    CV_TM_NPTR64 = 6            // mode is a 64 bit near pointer
#if CC_BIGINT
    ,
    CV_TM_NPTR128 = 7           // mode is a 128 bit near pointer
#endif
} CV_prmode_e;

//      type enumeration values

typedef enum CV_type_e {
    CV_SPECIAL      = 0x00,     // special type size values
    CV_SIGNED       = 0x01,     // signed integral size values
    CV_UNSIGNED     = 0x02,     // unsigned integral size values
    CV_BOOLEAN      = 0x03,     // Boolean size values
    CV_REAL         = 0x04,     // real number size values
    CV_COMPLEX      = 0x05,     // complex number size values
    CV_SPECIAL2     = 0x06,     // second set of special types
    CV_INT          = 0x07,     // integral (int) values
    CV_CVRESERVED   = 0x0f
} CV_type_e;

//      subtype enumeration values for CV_SPECIAL

typedef enum CV_special_e {
    CV_SP_NOTYPE    = 0x00,
    CV_SP_ABS       = 0x01,
    CV_SP_SEGMENT   = 0x02,
    CV_SP_VOID      = 0x03,
    CV_SP_CURRENCY  = 0x04,
    CV_SP_NBASICSTR = 0x05,
    CV_SP_FBASICSTR = 0x06,
    CV_SP_NOTTRANS  = 0x07
} CV_special_e;

//      subtype enumeration values for CV_SPECIAL2

typedef enum CV_special2_e {
    CV_S2_BIT       = 0x00,
    CV_S2_PASCHAR   = 0x01      // Pascal CHAR
} CV_special2_e;

//      subtype enumeration values for CV_SIGNED, CV_UNSIGNED and CV_BOOLEAN

typedef enum CV_integral_e {
    CV_IN_1BYTE     = 0x00,
    CV_IN_2BYTE     = 0x01,
    CV_IN_4BYTE     = 0x02,
    CV_IN_8BYTE     = 0x03
#if CC_BIGINT
    ,
    CV_IN_16BYTE    = 0x04
#endif
} CV_integral_e;

//      subtype enumeration values for CV_REAL and CV_COMPLEX

typedef enum CV_real_e {
    CV_RC_REAL32    = 0x00,
    CV_RC_REAL64    = 0x01,
    CV_RC_REAL80    = 0x02,
    CV_RC_REAL128   = 0x03,
    CV_RC_REAL48    = 0x04
} CV_real_e;

//      subtype enumeration values for CV_INT (really int)

typedef enum CV_int_e {
    CV_RI_CHAR      = 0x00,
    CV_RI_INT1      = 0x00,
    CV_RI_WCHAR     = 0x01,
    CV_RI_UINT1     = 0x01,
    CV_RI_INT2      = 0x02,
    CV_RI_UINT2     = 0x03,
    CV_RI_INT4      = 0x04,
    CV_RI_UINT4     = 0x05,
    CV_RI_INT8      = 0x06,
    CV_RI_UINT8     = 0x07
#if CC_BIGINT
    ,
    CV_RI_INT16     = 0x08,
    CV_RI_UINT16    = 0x09
#endif
} CV_int_e;

// macros to check the type of a primitive

#define CV_TYP_IS_DIRECT(typ)   (CV_MODE(typ) == CV_TM_DIRECT)
#define CV_TYP_IS_PTR(typ)      (CV_MODE(typ) != CV_TM_DIRECT)
#define CV_TYP_IS_NPTR(typ)     (CV_MODE(typ) == CV_TM_NPTR)
#define CV_TYP_IS_FPTR(typ)     (CV_MODE(typ) == CV_TM_FPTR)
#define CV_TYP_IS_HPTR(typ)     (CV_MODE(typ) == CV_TM_HPTR)
#define CV_TYP_IS_NPTR32(typ)   (CV_MODE(typ) == CV_TM_NPTR32)
#define CV_TYP_IS_FPTR32(typ)   (CV_MODE(typ) == CV_TM_FPTR32)

#if CC_BIGINT
#define CV_TYP_IS_SIGNED(typ)   (((CV_TYPE(typ) == CV_SIGNED)  && CV_TYP_IS_DIRECT(typ)) || \
                                    (typ == T_INT1) || \
                                    (typ == T_INT2) || \
                                    (typ == T_INT4) || \
                                    (typ == T_INT8) || \
                                    (typ == T_INT16) || \
                                    (typ == T_RCHAR))
#define CV_TYP_IS_UNSIGNED(typ) (((CV_TYPE(typ) == CV_UNSIGNED)  && CV_TYP_IS_DIRECT(typ)) || \
                                    (typ == T_UINT1) || \
                                    (typ == T_UINT2) || \
                                    (typ == T_UINT4) || \
                                    (typ == T_UINT8) || \
                                    (typ == T_UINT16))
#else
#define CV_TYP_IS_SIGNED(typ)   (((CV_TYPE(typ) == CV_SIGNED)  && CV_TYP_IS_DIRECT(typ)) || \
                                    (typ == T_INT1) || \
                                    (typ == T_INT2) || \
                                    (typ == T_INT4) || \
                                    (typ == T_RCHAR))

#define CV_TYP_IS_UNSIGNED(typ) (((CV_TYPE(typ) == CV_UNSIGNED)  && CV_TYP_IS_DIRECT(typ)) || \
                                    (typ == T_UINT1) || \
                                    (typ == T_UINT2) || \
                                    (typ == T_UINT4))
#endif
#define CV_TYP_IS_REAL(typ)     ((CV_TYPE(typ) == CV_REAL)  && CV_TYP_IS_DIRECT(typ))

#define CV_FIRST_NONPRIM 0x1000
#define CV_IS_PRIMITIVE(typ)    ((typ) < CV_FIRST_NONPRIM)
#define CV_TYP_IS_COMPLEX(typ)  ((CV_TYPE(typ) == CV_COMPLEX)   && CV_TYP_IS_DIRECT(typ))

// selected values for type_index - for a more complete definition, see
// Microsoft Symbol and Type OMF document

//      Special Types

#define T_NOTYPE        0x0000  // uncharacterized type (no type)
#define T_ABS           0x0001  // absolute symbol
#define T_SEGMENT       0x0002  // segment type
#define T_VOID          0x0003  // void
#define T_PVOID         0x0103  // near pointer to void
#define T_PFVOID        0x0203  // far pointer to void
#define T_PHVOID        0x0303  // huge pointer to void
#define T_32PVOID       0x0403  // 16:32 near pointer to void
#define T_32PFVOID      0x0503  // 16:32 far pointer to void
#define T_CURRENCY      0x0004  // BASIC 8 byte currency value
#define T_NBASICSTR     0x0005  // Near BASIC string
#define T_FBASICSTR     0x0006  // Far BASIC string
#define T_NOTTRANS      0x0007  // type not translated by cvpack
#define T_BIT           0x0060  // bit
#define T_PASCHAR       0x0061  // Pascal CHAR

//      Character types

#define T_CHAR          0x0010  // 8 bit signed
#define T_UCHAR         0x0020  // 8 bit unsigned
#define T_PCHAR         0x0110  // near pointer to 8 bit signed
#define T_PUCHAR        0x0120  // near pointer to 8 bit unsigned
#define T_PFCHAR        0x0210  // far pointer to 8 bit signed
#define T_PFUCHAR       0x0220  // far pointer to 8 bit unsigned
#define T_PHCHAR        0x0310  // huge pointer to 8 bit signed
#define T_PHUCHAR       0x0320  // huge pointer to 8 bit unsigned
#define T_32PCHAR       0x0410  // 16:32 near pointer to 8 bit signed
#define T_32PUCHAR      0x0420  // 16:32 near pointer to 8 bit unsigned
#define T_32PFCHAR      0x0510  // 16:32 far pointer to 8 bit signed
#define T_32PFUCHAR     0x0520  // 16:32 far pointer to 8 bit unsigned

//      really a character types

#define T_RCHAR         0x0070  // really a char
#define T_PRCHAR        0x0170  // 16:16 near pointer to a real char
#define T_PFRCHAR       0x0270  // 16:16 far pointer to a real char
#define T_PHRCHAR       0x0370  // 16:16 huge pointer to a real char
#define T_32PRCHAR      0x0470  // 16:32 near pointer to a real char
#define T_32PFRCHAR     0x0570  // 16:32 far pointer to a real char

//      really a wide character types

#define T_WCHAR         0x0071  // wide char
#define T_PWCHAR        0x0171  // 16:16 near pointer to a wide char
#define T_PFWCHAR       0x0271  // 16:16 far pointer to a wide char
#define T_PHWCHAR       0x0371  // 16:16 huge pointer to a wide char
#define T_32PWCHAR      0x0471  // 16:32 near pointer to a wide char
#define T_32PFWCHAR     0x0571  // 16:32 far pointer to a wide char

//      8 bit int types

#define T_INT1          0x0068  // 8 bit signed int
#define T_UINT1         0x0069  // 8 bit unsigned int
#define T_PINT1         0x0168  // near pointer to 8 bit signed int
#define T_PUINT1        0x0169  // near pointer to 8 bit unsigned int
#define T_PFINT1        0x0268  // far pointer to 8 bit signed int
#define T_PFUINT1       0x0269  // far pointer to 8 bit unsigned int
#define T_PHINT1        0x0368  // huge pointer to 8 bit signed int
#define T_PHUINT1       0x0369  // huge pointer to 8 bit unsigned int

#define T_32PINT1       0x0468  // 16:32 near pointer to 8 bit signed int
#define T_32PUINT1      0x0469  // 16:32 near pointer to 8 bit unsigned int
#define T_32PFINT1      0x0568  // 16:32 far pointer to 8 bit signed int
#define T_32PFUINT1     0x0569  // 16:32 far pointer to 8 bit unsigned int

//      16 bit short types

#define T_SHORT         0x0011  // 16 bit signed
#define T_USHORT        0x0021  // 16 bit unsigned
#define T_PSHORT        0x0111  // near pointer to 16 bit signed
#define T_PUSHORT       0x0121  // near pointer to 16 bit unsigned
#define T_PFSHORT       0x0211  // far pointer to 16 bit signed
#define T_PFUSHORT      0x0221  // far pointer to 16 bit unsigned
#define T_PHSHORT       0x0311  // huge pointer to 16 bit signed
#define T_PHUSHORT      0x0321  // huge pointer to 16 bit unsigned

#define T_32PSHORT      0x0411  // 16:32 near pointer to 16 bit signed
#define T_32PUSHORT     0x0421  // 16:32 near pointer to 16 bit unsigned
#define T_32PFSHORT     0x0511  // 16:32 far pointer to 16 bit signed
#define T_32PFUSHORT    0x0521  // 16:32 far pointer to 16 bit unsigned

//      16 bit int types

#define T_INT2          0x0072  // 16 bit signed int
#define T_UINT2         0x0073  // 16 bit unsigned int
#define T_PINT2         0x0172  // near pointer to 16 bit signed int
#define T_PUINT2        0x0173  // near pointer to 16 bit unsigned int
#define T_PFINT2        0x0272  // far pointer to 16 bit signed int
#define T_PFUINT2       0x0273  // far pointer to 16 bit unsigned int
#define T_PHINT2        0x0372  // huge pointer to 16 bit signed int
#define T_PHUINT2       0x0373  // huge pointer to 16 bit unsigned int

#define T_32PINT2       0x0472  // 16:32 near pointer to 16 bit signed int
#define T_32PUINT2      0x0473  // 16:32 near pointer to 16 bit unsigned int
#define T_32PFINT2      0x0572  // 16:32 far pointer to 16 bit signed int
#define T_32PFUINT2     0x0573  // 16:32 far pointer to 16 bit unsigned int

//      32 bit long types

#define T_LONG          0x0012  // 32 bit signed
#define T_ULONG         0x0022  // 32 bit unsigned
#define T_PLONG         0x0112  // near pointer to 32 bit signed
#define T_PULONG        0x0122  // near pointer to 32 bit unsigned
#define T_PFLONG        0x0212  // far pointer to 32 bit signed
#define T_PFULONG       0x0222  // far pointer to 32 bit unsigned
#define T_PHLONG        0x0312  // huge pointer to 32 bit signed
#define T_PHULONG       0x0322  // huge pointer to 32 bit unsigned

#define T_32PLONG       0x0412  // 16:32 near pointer to 32 bit signed
#define T_32PULONG      0x0422  // 16:32 near pointer to 32 bit unsigned
#define T_32PFLONG      0x0512  // 16:32 far pointer to 32 bit signed
#define T_32PFULONG     0x0522  // 16:32 far pointer to 32 bit unsigned

//      32 bit int types

#define T_INT4          0x0074  // 32 bit signed int
#define T_UINT4         0x0075  // 32 bit unsigned int
#define T_PINT4         0x0174  // near pointer to 32 bit signed int
#define T_PUINT4        0x0175  // near pointer to 32 bit unsigned int
#define T_PFINT4        0x0274  // far pointer to 32 bit signed int
#define T_PFUINT4       0x0275  // far pointer to 32 bit unsigned int
#define T_PHINT4        0x0374  // huge pointer to 32 bit signed int
#define T_PHUINT4       0x0375  // huge pointer to 32 bit unsigned int

#define T_32PINT4       0x0474  // 16:32 near pointer to 32 bit signed int
#define T_32PUINT4      0x0475  // 16:32 near pointer to 32 bit unsigned int
#define T_32PFINT4      0x0574  // 16:32 far pointer to 32 bit signed int
#define T_32PFUINT4     0x0575  // 16:32 far pointer to 32 bit unsigned int

//      64 bit quad types

#define T_QUAD          0x0013  // 64 bit signed
#define T_UQUAD         0x0023  // 64 bit unsigned
#define T_PQUAD         0x0113  // near pointer to 64 bit signed
#define T_PUQUAD        0x0123  // near pointer to 64 bit unsigned
#define T_PFQUAD        0x0213  // far pointer to 64 bit signed
#define T_PFUQUAD       0x0223  // far pointer to 64 bit unsigned
#define T_PHQUAD        0x0313  // huge pointer to 64 bit signed
#define T_PHUQUAD       0x0323  // huge pointer to 64 bit unsigned
#define T_32PQUAD       0x0413  // 16:32 near pointer to 64 bit signed
#define T_32PUQUAD      0x0423  // 16:32 near pointer to 64 bit unsigned
#define T_32PFQUAD      0x0513  // 16:32 far pointer to 64 bit signed
#define T_32PFUQUAD     0x0523  // 16:32 far pointer to 64 bit unsigned

//      64 bit int types

#define T_INT8          0x0076  // 64 bit signed int
#define T_UINT8         0x0077  // 64 bit unsigned int
#define T_PINT8         0x0176  // near pointer to 64 bit signed int
#define T_PUINT8        0x0177  // near pointer to 64 bit unsigned int
#define T_PFINT8        0x0276  // far pointer to 64 bit signed int
#define T_PFUINT8       0x0277  // far pointer to 64 bit unsigned int
#define T_PHINT8        0x0376  // huge pointer to 64 bit signed int
#define T_PHUINT8       0x0377  // huge pointer to 64 bit unsigned int

#define T_32PINT8       0x0476  // 16:32 near pointer to 64 bit signed int
#define T_32PUINT8      0x0477  // 16:32 near pointer to 64 bit unsigned int
#define T_32PFINT8      0x0576  // 16:32 far pointer to 64 bit signed int
#define T_32PFUINT8     0x0577  // 16:32 far pointer to 64 bit unsigned int

#if CC_BIGINT
//      128 bit octet types

#define T_OCT           0x0014  // 128 bit signed
#define T_UOCT          0x0024  // 128 bit unsigned
#define T_POCT          0x0114  // near pointer to 128 bit signed
#define T_PUOCT         0x0124  // near pointer to 128 bit unsigned
#define T_PFOCT         0x0214  // far pointer to 128 bit signed
#define T_PFUOCT        0x0224  // far pointer to 128 bit unsigned
#define T_PHOCT         0x0314  // huge pointer to 128 bit signed
#define T_PHUOCT        0x0324  // huge pointer to 128 bit unsigned

#define T_32POCT        0x0414  // 16:32 near pointer to 128 bit signed
#define T_32PUOCT       0x0424  // 16:32 near pointer to 128 bit unsigned
#define T_32PFOCT       0x0514  // 16:32 far pointer to 128 bit signed
#define T_32PFUOCT      0x0524  // 16:32 far pointer to 128 bit unsigned

//      128 bit int types

#define T_INT16         0x0078  // 128 bit signed int
#define T_UINT16        0x0079  // 128 bit unsigned int
#define T_PINT16        0x0178  // near pointer to 128 bit signed int
#define T_PUINT16       0x0179  // near pointer to 128 bit unsigned int
#define T_PFINT16       0x0278  // far pointer to 128 bit signed int
#define T_PFUINT16      0x0279  // far pointer to 128 bit unsigned int
#define T_PHINT16       0x0378  // huge pointer to 128 bit signed int
#define T_PHUINT16      0x0379  // huge pointer to 128 bit unsigned int

#define T_32PINT16      0x0478  // 16:32 near pointer to 128 bit signed int
#define T_32PUINT16     0x0479  // 16:32 near pointer to 128 bit unsigned int
#define T_32PFINT16     0x0578  // 16:32 far pointer to 128 bit signed int
#define T_32PFUINT16    0x0579  // 16:32 far pointer to 128 bit unsigned int

#endif

//      32 bit real types

#define T_REAL32        0x0040  // 32 bit real
#define T_PREAL32       0x0140  // near pointer to 32 bit real
#define T_PFREAL32      0x0240  // far pointer to 32 bit real
#define T_PHREAL32      0x0340  // huge pointer to 32 bit real
#define T_32PREAL32     0x0440  // 16:32 near pointer to 32 bit real
#define T_32PFREAL32    0x0540  // 16:32 far pointer to 32 bit real

//      48 bit real types

#define T_REAL48        0x0044  // 48 bit real
#define T_PREAL48       0x0144  // near pointer to 48 bit real
#define T_PFREAL48      0x0244  // far pointer to 48 bit real
#define T_PHREAL48      0x0344  // huge pointer to 48 bit real
#define T_32PREAL48     0x0444  // 16:32 near pointer to 48 bit real
#define T_32PFREAL48    0x0544  // 16:32 far pointer to 48 bit real

//      64 bit real types

#define T_REAL64        0x0041  // 64 bit real
#define T_PREAL64       0x0141  // near pointer to 64 bit real
#define T_PFREAL64      0x0241  // far pointer to 64 bit real
#define T_PHREAL64      0x0341  // huge pointer to 64 bit real
#define T_32PREAL64     0x0441  // 16:32 near pointer to 64 bit real
#define T_32PFREAL64    0x0541  // 16:32 far pointer to 64 bit real

//      80 bit real types

#define T_REAL80        0x0042  // 80 bit real
#define T_PREAL80       0x0142  // near pointer to 80 bit real
#define T_PFREAL80      0x0242  // far pointer to 80 bit real
#define T_PHREAL80      0x0342  // huge pointer to 80 bit real
#define T_32PREAL80     0x0442  // 16:32 near pointer to 80 bit real
#define T_32PFREAL80    0x0542  // 16:32 far pointer to 80 bit real

//      128 bit real types

#define T_REAL128       0x0043  // 128 bit real
#define T_PREAL128      0x0143  // near pointer to 128 bit real
#define T_PFREAL128     0x0243  // far pointer to 128 bit real
#define T_PHREAL128     0x0343  // huge pointer to 128 bit real
#define T_32PREAL128    0x0443  // 16:32 near pointer to 128 bit real
#define T_32PFREAL128   0x0543  // 16:32 far pointer to 128 bit real

//      32 bit complex types

#define T_CPLX32        0x0050  // 32 bit complex
#define T_PCPLX32       0x0150  // near pointer to 32 bit complex
#define T_PFCPLX32      0x0250  // far pointer to 32 bit complex
#define T_PHCPLX32      0x0350  // huge pointer to 32 bit complex
#define T_32PCPLX32     0x0450  // 16:32 near pointer to 32 bit complex
#define T_32PFCPLX32    0x0550  // 16:32 far pointer to 32 bit complex

//      64 bit complex types

#define T_CPLX64        0x0051  // 64 bit complex
#define T_PCPLX64       0x0151  // near pointer to 64 bit complex
#define T_PFCPLX64      0x0251  // far pointer to 64 bit complex
#define T_PHCPLX64      0x0351  // huge pointer to 64 bit complex
#define T_32PCPLX64     0x0451  // 16:32 near pointer to 64 bit complex
#define T_32PFCPLX64    0x0551  // 16:32 far pointer to 64 bit complex

//      80 bit complex types

#define T_CPLX80        0x0052  // 80 bit complex
#define T_PCPLX80       0x0152  // near pointer to 80 bit complex
#define T_PFCPLX80      0x0252  // far pointer to 80 bit complex
#define T_PHCPLX80      0x0352  // huge pointer to 80 bit complex
#define T_32PCPLX80     0x0452  // 16:32 near pointer to 80 bit complex
#define T_32PFCPLX80    0x0552  // 16:32 far pointer to 80 bit complex

//      128 bit complex types

#define T_CPLX128       0x0053  // 128 bit complex
#define T_PCPLX128      0x0153  // near pointer to 128 bit complex
#define T_PFCPLX128     0x0253  // far pointer to 128 bit complex
#define T_PHCPLX128     0x0353  // huge pointer to 128 bit real
#define T_32PCPLX128    0x0453  // 16:32 near pointer to 128 bit complex
#define T_32PFCPLX128   0x0553  // 16:32 far pointer to 128 bit complex

//      boolean types

#define T_BOOL08        0x0030  // 8 bit boolean
#define T_BOOL16        0x0031  // 16 bit boolean
#define T_BOOL32        0x0032  // 32 bit boolean
#define T_BOOL64        0x0033  // 64 bit boolean
#define T_PBOOL08       0x0130  // near pointer to  8 bit boolean
#define T_PBOOL16       0x0131  // near pointer to 16 bit boolean
#define T_PBOOL32       0x0132  // near pointer to 32 bit boolean
#define T_PBOOL64       0x0133  // near pointer to 64 bit boolean
#define T_PFBOOL08      0x0230  // far pointer to  8 bit boolean
#define T_PFBOOL16      0x0231  // far pointer to 16 bit boolean
#define T_PFBOOL32      0x0232  // far pointer to 32 bit boolean
#define T_PFBOOL64      0x0233  // far pointer to 64 bit boolean
#define T_PHBOOL08      0x0330  // huge pointer to  8 bit boolean
#define T_PHBOOL16      0x0331  // huge pointer to 16 bit boolean
#define T_PHBOOL32      0x0332  // huge pointer to 32 bit boolean
#define T_PHBOOL64      0x0333  // huge pointer to 64 bit boolean

#define T_32PBOOL08     0x0430  // 16:32 near pointer to 8 bit boolean
#define T_32PFBOOL08    0x0530  // 16:32 far pointer to 8 bit boolean
#define T_32PBOOL16     0x0431  // 16:32 near pointer to 18 bit boolean
#define T_32PFBOOL16    0x0531  // 16:32 far pointer to 16 bit boolean
#define T_32PBOOL32     0x0432  // 16:32 near pointer to 32 bit boolean
#define T_32PFBOOL32    0x0532  // 16:32 far pointer to 32 bit boolean
#define T_32PBOOL64     0x0433  // 16:32 near pointer to 64 bit boolean
#define T_32PFBOOL64    0x0533  // 16:32 far pointer to 64 bit boolean

#define T_NCVPTR        0x01f0  // CV Internal type for created near pointers
#define T_FCVPTR        0x02f0  // CV Internal type for created far pointers
#define T_HCVPTR        0x03f0  // CV Internal type for created huge pointers
#define T_32NCVPTR      0x04f0  // CV Internal type for created near 32-bit pointers
#define T_32FCVPTR      0x05f0  // CV Internal type for created far 32-bit pointers
#define T_64NCVPTR      0x06f0  // CV Internal type for created near 64-bit pointers

#define CV_IS_INTERNAL_PTR(typ) (CV_IS_PRIMITIVE(typ) && \
                                 CV_TYPE(typ) == CV_CVRESERVED && \
                                 CV_TYP_IS_PTR(typ))

/**     No leaf index can have a value of 0x0000.  The leaf indices are
 *      separated into ranges depending upon the use of the type record.
 *      The second range is for the type records that are directly referenced
 *      in symbols. The first range is for type records that are not
 *      referenced by symbols but instead are referenced by other type
 *      records.  All type records must have a starting leaf index in these
 *      first two ranges.  The third range of leaf indices are used to build
 *      up complex lists such as the field list of a class type record.  No
 *      type record can begin with one of the leaf indices. The fourth ranges
 *      of type indices are used to represent numeric data in a symbol or
 *      type record. These leaf indices are greater than 0x8000.  At the
 *      point that type or symbol processor is expecting a numeric field, the
 *      next two bytes in the type record are examined.  If the value is less
 *      than 0x8000, then the two bytes contain the numeric value. If the
 *      value is greater than 0x8000, then the data follows the leaf index in
 *      a format specified by the leaf index. The final range of leaf indices
 *      are used to force alignment of subfields within a complex type record..
 */

// leaf indices starting records but referenced from symbol records

#define LF_MODIFIER     0x0001
#define LF_POINTER      0x0002
#define LF_ARRAY        0x0003
#define LF_CLASS        0x0004
#define LF_STRUCTURE    0x0005
#define LF_UNION        0x0006
#define LF_ENUM         0x0007
#define LF_PROCEDURE    0x0008
#define LF_MFUNCTION    0x0009
#define LF_VTSHAPE      0x000a
#define LF_COBOL0       0x000b
#define LF_COBOL1       0x000c
#define LF_BARRAY       0x000d
#define LF_LABEL        0x000e
#define LF_NULL         0x000f
#define LF_NOTTRAN      0x0010
#define LF_DIMARRAY     0x0011
#define LF_VFTPATH      0x0012
#define LF_PRECOMP      0x0013  // not referenced from symbol
#define LF_ENDPRECOMP   0x0014  // not referenced from symbol
#define LF_OEM          0x0015  // oem definable type string
#define LF_TYPESERVER   0x0016  // not referenced from symbol

// leaf indices starting records but referenced only from type records

#define LF_SKIP         0x0200
#define LF_ARGLIST      0x0201
#define LF_DEFARG       0x0202
#define LF_LIST         0x0203
#define LF_FIELDLIST    0x0204
#define LF_DERIVED      0x0205
#define LF_BITFIELD     0x0206
#define LF_METHODLIST   0x0207
#define LF_DIMCONU      0x0208
#define LF_DIMCONLU     0x0209
#define LF_DIMVARU      0x020a
#define LF_DIMVARLU     0x020b
#define LF_REFSYM       0x020c

#define LF_BCLASS       0x0400
#define LF_VBCLASS      0x0401
#define LF_IVBCLASS     0x0402
#define LF_ENUMERATE    0x0403
#define LF_FRIENDFCN    0x0404
#define LF_INDEX        0x0405
#define LF_MEMBER       0x0406
#define LF_STMEMBER     0x0407
#define LF_METHOD       0x0408
#define LF_NESTTYPE     0x0409
#define LF_VFUNCTAB     0x040a
#define LF_FRIENDCLS    0x040b
#define LF_ONEMETHOD    0x040c
#define LF_VFUNCOFF     0x040d

#define LF_NUMERIC      0x8000
#define LF_CHAR         0x8000
#define LF_SHORT        0x8001
#define LF_USHORT       0x8002
#define LF_LONG         0x8003
#define LF_ULONG        0x8004
#define LF_REAL32       0x8005
#define LF_REAL64       0x8006
#define LF_REAL80       0x8007
#define LF_REAL128      0x8008
#define LF_QUADWORD     0x8009
#define LF_UQUADWORD    0x800a
#define LF_REAL48       0x800b
#define LF_COMPLEX32    0x800c
#define LF_COMPLEX64    0x800d
#define LF_COMPLEX80    0x800e
#define LF_COMPLEX128   0x800f
#define LF_VARSTRING    0x8010

#if CC_BIGINT
#define LF_OCTWORD      0x8017
#define LF_UOCTWORD     0x8018
#endif

#define LF_PAD0         0xf0
#define LF_PAD1         0xf1
#define LF_PAD2         0xf2
#define LF_PAD3         0xf3
#define LF_PAD4         0xf4
#define LF_PAD5         0xf5
#define LF_PAD6         0xf6
#define LF_PAD7         0xf7
#define LF_PAD8         0xf8
#define LF_PAD9         0xf9
#define LF_PAD10        0xfa
#define LF_PAD11        0xfb
#define LF_PAD12        0xfc
#define LF_PAD13        0xfd
#define LF_PAD14        0xfe
#define LF_PAD15        0xff

// end of leaf indices

//      Type enum for pointer records
//      Pointers can be one of the following types

typedef enum CV_ptrtype_e {
    CV_PTR_NEAR         = 0x00, // near pointer
    CV_PTR_FAR          = 0x01, // far pointer
    CV_PTR_HUGE         = 0x02, // huge pointer
    CV_PTR_BASE_SEG     = 0x03, // based on segment
    CV_PTR_BASE_VAL     = 0x04, // based on value of base

CV_PTR_BASE_SEGVAL  = 0x05, // based on segment value of base
    CV_PTR_BASE_ADDR    = 0x06, // based on address of base
    CV_PTR_BASE_SEGADDR = 0x07, // based on segment address of base
    CV_PTR_BASE_TYPE    = 0x08, // based on type
    CV_PTR_BASE_SELF    = 0x09, // based on self
    CV_PTR_NEAR32       = 0x0a, // 16:32 near pointer
    CV_PTR_FAR32        = 0x0b, // 16:32 far pointer
    CV_PTR_UNUSEDPTR    = 0x0c  // first unused pointer type
} CV_ptrtype_e;

//      Mode enum for pointers
//      Pointers can have one of the following modes

typedef enum CV_ptrmode_e {
    CV_PTR_MODE_PTR     = 0x00, // "normal" pointer
    CV_PTR_MODE_REF     = 0x01, // reference
    CV_PTR_MODE_PMEM    = 0x02, // pointer to data member
    CV_PTR_MODE_PMFUNC  = 0x03, // pointer to member function
    CV_PTR_MODE_RESERVED= 0x04  // first unused pointer mode
} CV_ptrmode_e;

//      Enumeration for function call type

typedef enum CV_call_e {
    CV_CALL_NEAR_C      = 0x00, // near right to left push, caller pops stack
    CV_CALL_FAR_C       = 0x01, // far right to left push, caller pops stack
    CV_CALL_NEAR_PASCAL = 0x02, // near left to right push, callee pops stack
    CV_CALL_FAR_PASCAL  = 0x03, // far left to right push, callee pops stack
    CV_CALL_NEAR_FAST   = 0x04, // near left to right push with regs, callee pops stack
    CV_CALL_FAR_FAST    = 0x05, // far left to right push with regs, callee pops stack
    CV_CALL_SKIPPED     = 0x06, // skipped (unused) call index
    CV_CALL_NEAR_STD    = 0x07, // near standard call
    CV_CALL_FAR_STD     = 0x08, // far standard call
    CV_CALL_NEAR_SYS    = 0x09, // near sys call
    CV_CALL_FAR_SYS     = 0x0a, // far sys call
    CV_CALL_THISCALL    = 0x0b, // this call (this passed in register)
    CV_CALL_MIPSCALL    = 0x0c, // Mips call
    CV_CALL_GENERIC     = 0x0d, // Generic call sequence
    CV_CALL_ALPHACALL   = 0x0e, // Alpha call
    CV_CALL_PPCCALL     = 0x0f, // PPC call
    CV_CALL_RESERVED    = 0x10  // first unused call enumeration
} CV_call_e;

//      Values for the access protection of class attributes

typedef enum CV_access_e {
    CV_private   = 1,
    CV_protected = 2,
    CV_public    = 3
} CV_access_e;

//      enumeration for method properties

typedef enum CV_methodprop_e {
    CV_MTvanilla    = 0x00,
    CV_MTvirtual    = 0x01,
    CV_MTstatic     = 0x02,
    CV_MTfriend     = 0x03,
    CV_MTintro      = 0x04,
    CV_MTpurevirt   = 0x05,
    CV_MTpureintro  = 0x06
} CV_methodprop_e;

//      enumeration for virtual shape table entries

typedef enum CV_VTS_desc_e {
    CV_VTS_near     = 0x00,
    CV_VTS_far      = 0x01,
    CV_VTS_thin     = 0x02,
    CV_VTS_outer    = 0x03,
    CV_VTS_meta     = 0x04,
    CV_VTS_near32   = 0x05,
    CV_VTS_far32    = 0x06,
    CV_VTS_unused   = 0x07
} CV_VTS_desc_e;

//      enumeration for LF_LABEL address modes

typedef enum CV_LABEL_TYPE_e {
    CV_LABEL_NEAR = 0,          // near return
    CV_LABEL_FAR  = 4           // far return
} CV_LABEL_TYPE_e;

//      enumeration for LF_MODIFIER values

typedef struct CV_modifier_t {
    unsigned short  MOD_const       :1;
    unsigned short  MOD_volatile    :1;
    unsigned short  MOD_unaligned   :1;
    unsigned short  MOD_unused      :13;
} CV_modifier_t;

//      bit field structure describing class/struct/union/enum properties

typedef struct CV_prop_t {
    unsigned short  packed      :1;     // true if structure is packed
    unsigned short  ctor        :1;     // true if constructors or destructors present
    unsigned short  ovlops      :1;     // true if overloaded operators present
    unsigned short  isnested    :1;     // true if this is a nested class
    unsigned short  cnested     :1;     // true if this class contains nested types
    unsigned short  opassign    :1;     // true if overloaded assignment (=)
    unsigned short  opcast      :1;     // true if casting methods
    unsigned short  fwdref      :1;     // true if forward reference (incomplete defn)
    unsigned short  scoped      :1;     // scoped definition
    unsigned short  reserved    :7;
} CV_prop_t;

//      class field attribute

typedef struct CV_fldattr_t {
    unsigned short  access      :2;     // access protection CV_access_t
    unsigned short  mprop       :3;     // method properties CV_methodprop_t
    unsigned short  pseudo      :1;     // compiler generated fcn and does not exist
    unsigned short  noinherit   :1;     // true if class cannot be inherited
    unsigned short  noconstruct :1;     // true if class cannot be constructed
    unsigned short  unused      :8;     // unused
} CV_fldattr_t;

//      Structures to access to the type records

typedef struct TYPTYPE {
    unsigned short  len;
    unsigned short  leaf;
    unsigned char   data[CV_ZEROLEN];
} TYPTYPE;                      // general types record

__INLINE char *NextType (char * pType) {
    return (pType + ((TYPTYPE *)pType)->len + sizeof(unsigned short));
}

typedef enum CV_PMEMBER {
    CV_PDM16_NONVIRT        = 0x00, // 16:16 data no virtual fcn or base
    CV_PDM16_VFCN           = 0x01, // 16:16 data with virtual functions
    CV_PDM16_VBASE          = 0x02, // 16:16 data with virtual bases
    CV_PDM32_NVVFCN         = 0x03, // 16:32 data w/wo virtual functions
    CV_PDM32_VBASE          = 0x04, // 16:32 data with virtual bases

    CV_PMF16_NEARNVSA       = 0x05, // 16:16 near method nonvirtual single address point
    CV_PMF16_NEARNVMA       = 0x06, // 16:16 near method nonvirtual multiple address points
    CV_PMF16_NEARVBASE      = 0x07, // 16:16 near method virtual bases
    CV_PMF16_FARNVSA        = 0x08, // 16:16 far method nonvirtual single address point
    CV_PMF16_FARNVMA        = 0x09, // 16:16 far method nonvirtual multiple address points
    CV_PMF16_FARVBASE       = 0x0a, // 16:16 far method virtual bases

    CV_PMF32_NVSA           = 0x0b, // 16:32 method nonvirtual single address point
    CV_PMF32_NVMA           = 0x0c, // 16:32 method nonvirtual multiple address point
    CV_PMF32_VBASE          = 0x0d  // 16:32 method virtual bases
} CV_PMEMBER;

//      memory representation of pointer to member.  These representations are
//      indexed by the enumeration above in the LF_POINTER record

//      representation of a 16:16 pointer to data for a class with no
//      virtual functions or virtual bases

struct CV_PDMR16_NONVIRT {
    CV_off16_t      mdisp;          // displacement to data (NULL = -1)
};

//      representation of a 16:16 pointer to data for a class with virtual
//      functions

struct CV_PMDR16_VFCN {
    CV_off16_t      mdisp;          // displacement to data ( NULL = 0)
};

//      representation of a 16:16 pointer to data for a class with
//      virtual bases

struct CV_PDMR16_VBASE {
    CV_off16_t      mdisp;          // displacement to data
    CV_off16_t      pdisp;          // this pointer displacement to vbptr
    CV_off16_t      vdisp;          // displacement within vbase table
                                    // NULL = (,,0xffff)
};

//      representation of a 16:32 near pointer to data for a class with
//      or without virtual functions and no virtual bases

struct CV_PDMR32_NVVFCN {
    CV_off32_t      mdisp;          // displacement to data (NULL = 0x80000000)
};

//      representation of a 16:32 near pointer to data for a class
//      with virtual bases

struct CV_PDMR32_VBASE {
    CV_off32_t      mdisp;          // displacement to data
    CV_off32_t      pdisp;          // this pointer displacement
    CV_off32_t      vdisp;          // vbase table displacement
                                    // NULL = (,,0xffffffff)
};

//      representation of a 16:16 pointer to near member function for a
//      class with no virtual functions or bases and a single address point

struct CV_PMFR16_NEARNVSA {
    CV_uoff16_t     off;            // near address of function (NULL = 0)
};

//      representation of a 16:16 near pointer to member functions of a
//      class with no virtual bases and multiple address points

struct CV_PMFR16_NEARNVMA {
    CV_uoff16_t     off;            // offset of function (NULL = 0,x)
    signed short    disp;
};

//      representation of a 16:16 near pointer to member function of a
//      class with virtual bases

struct CV_PMFR16_NEARVBASE {
    CV_uoff16_t     off;            // offset of function (NULL = 0,x,x,x)
    CV_off16_t      mdisp;          // displacement to data
    CV_off16_t      pdisp;          // this pointer displacement
    CV_off16_t      vdisp;          // vbase table displacement
};

//      representation of a 16:16 pointer to far member function for a
//      class with no virtual bases and a single address point

struct CV_PMFR16_FARNVSA {
    CV_uoff16_t     off;            // offset of function (NULL = 0:0)
    unsigned short  seg;            // segment of function
};

//      representation of a 16:16 far pointer to member functions of a
//      class with no virtual bases and multiple address points

struct CV_PMFR16_FARNVMA {
    CV_uoff16_t     off;            // offset of function (NULL = 0:0,x)
    unsigned short  seg;
    signed short    disp;
};

//      representation of a 16:16 far pointer to member function of a
//      class with virtual bases

struct CV_PMFR16_FARVBASE {
    CV_uoff16_t     off;            // offset of function (NULL = 0:0,x,x,x)
    unsigned short  seg;
    CV_off16_t      mdisp;          // displacement to data
    CV_off16_t      pdisp;          // this pointer displacement
    CV_off16_t      vdisp;          // vbase table displacement

};

//      representation of a 16:32 near pointer to member function for a
//      class with no virtual bases and a single address point

struct CV_PMFR32_NVSA {
    CV_uoff32_t      off;            // near address of function (NULL = 0L)
};

//      representation of a 16:32 near pointer to member function for a
//      class with no virtual bases and multiple address points

struct CV_PMFR32_NVMA {
    CV_uoff32_t     off;            // near address of function (NULL = 0L,x)
    CV_off32_t      disp;
};

//      representation of a 16:32 near pointer to member function for a
//      class with virtual bases

struct CV_PMFR32_VBASE {
    CV_uoff32_t     off;            // near address of function (NULL = 0L,x,x,x)
    CV_off32_t      mdisp;          // displacement to data
    CV_off32_t      pdisp;          // this pointer displacement
    CV_off32_t      vdisp;          // vbase table displacement
};

//      Easy leaf - used for generic casting to reference leaf field
//      of a subfield of a complex list

typedef struct lfEasy {
    unsigned short  leaf;           // LF_...
} lfEasy;

/**     The following type records are basically variant records of the
 *      above structure.  The "unsigned short leaf" of the above structure and
 *      the "unsigned short leaf" of the following type definitions are the same
 *      symbol.  When the OMF record is locked via the MHOMFLock API
 *      call, the address of the "unsigned short leaf" is returned
 */

//      Type record for LF_MODIFIER

typedef struct lfModifier {
    unsigned short  leaf;           // LF_MODIFIER
    CV_modifier_t   attr;           // modifier attribute modifier_t
    CV_typ_t        type;           // modified type
} lfModifier;

//      type record for LF_POINTER

#ifndef __cplusplus
typedef struct lfPointer {
#endif
    struct lfPointerBody {
        unsigned short      leaf;           // LF_POINTER
        struct lfPointerAttr {
            unsigned char   ptrtype     :5; // ordinal specifying pointer type (ptrtype-t)
            unsigned char   ptrmode     :3; // ordinal specifying pointer mode (ptrmode_t)
            unsigned char   isflat32    :1; // true if 0:32 pointer
            unsigned char   isvolatile  :1; // TRUE if volatile pointer
            unsigned char   isconst     :1; // TRUE if const pointer
            unsigned char   isunaligned :1; // TRUE if unaligned pointer
            unsigned char   unused      :4;
        } attr;
        CV_typ_t    utype;                  // type index of the underlying type
#if (defined(__cplusplus) || defined(_MSC_VER)) // for C++ and MS compilers that support unnamed unions
    };
#else
    } u;
#endif
#ifdef  __cplusplus
typedef struct lfPointer : public lfPointerBody {
#endif
    union  {
        struct {
            CV_typ_t        pmclass;    // index of containing class for pointer to member
            unsigned short  pmenum;     // enumeration specifying pm format
        } pm;
        unsigned short      bseg;       // base segment if PTR_BASE_SEG
        unsigned char       Sym[1];     // copy of base symbol record (including length)
        struct  {
            CV_typ_t        index;      // type index if CV_PTR_BASE_TYPE
            unsigned char   name[1];    // name of base type
        } btype;
    } pbase;
} lfPointer;

//      type record for LF_ARRAY

typedef struct lfArray {
    unsigned short  leaf;           // LF_ARRAY
    CV_typ_t        elemtype;       // type index of element type
    CV_typ_t        idxtype;        // type index of indexing type
    unsigned char   data[CV_ZEROLEN]; // variable length data specifying
                                    // size in bytes and name
} lfArray;

//      type record for LF_CLASS, LF_STRUCTURE

typedef struct lfClass {
    unsigned short  leaf;           // LF_CLASS, LF_STRUCT
    unsigned short  count;          // count of number of elements in class
    CV_typ_t        field;          // type index of LF_FIELD descriptor list
    CV_prop_t       property;       // property attribute field (prop_t)
    CV_typ_t        derived;        // type index of derived from list if not zero
    CV_typ_t        vshape;         // type index of vshape table for this class
    unsigned char   data[CV_ZEROLEN]; // data describing length of structure in
                                    // bytes and name
} lfClass;
typedef lfClass lfStructure;

//      type record for LF_UNION

typedef struct lfUnion {
    unsigned short  leaf;           // LF_UNION
    unsigned short  count;          // count of number of elements in class
    CV_typ_t        field;          // type index of LF_FIELD descriptor list
    CV_prop_t       property;       // property attribute field
    unsigned char   data[CV_ZEROLEN]; // variable length data describing length of
                                    // structure and name
} lfUnion;

//      type record for LF_ENUM

typedef struct lfEnum {
    unsigned short  leaf;           // LF_ENUM
    unsigned short  count;          // count of number of elements in class
    CV_typ_t        utype;          // underlying type of the enum
    CV_typ_t        field;          // type index of LF_FIELD descriptor list
    CV_prop_t       property;       // property attribute field
    unsigned char   Name[1];        // length prefixed name of enum
} lfEnum;

//      Type record for LF_PROCEDURE

typedef struct lfProc {
    unsigned short  leaf;           // LF_PROCEDURE
    CV_typ_t        rvtype;         // type index of return value
    unsigned char   calltype;       // calling convention (CV_call_t)
    unsigned char   reserved;       // reserved for future use
    unsigned short  parmcount;      // number of parameters
    CV_typ_t        arglist;        // type index of argument list
} lfProc;

//      Type record for member function

typedef struct lfMFunc {
    unsigned short  leaf;           // LF_MFUNCTION
    CV_typ_t        rvtype;         // type index of return value
    CV_typ_t        classtype;      // type index of containing class
    CV_typ_t        thistype;       // type index of this pointer (model specific)
    unsigned char   calltype;       // calling convention (call_t)
    unsigned char   reserved;       // reserved for future use
    unsigned short  parmcount;      // number of parameters
    CV_typ_t        arglist;        // type index of argument list
    long            thisadjust;     // this adjuster (long because pad required anyway)
} lfMFunc;

//     type record for virtual function table shape

typedef struct lfVTShape {
    unsigned short  leaf;           // LF_VTSHAPE
    unsigned short  count;          // number of entries in vfunctable
    unsigned char   desc[CV_ZEROLEN]; // 4 bit (CV_VTS_desc) descriptors
} lfVTShape;

//      type record for cobol0

typedef struct lfCobol0 {
    unsigned short  leaf;           // LF_COBOL0
    CV_typ_t        type;           // parent type record index
    unsigned char   data[CV_ZEROLEN];
} lfCobol0;

//      type record for cobol1

typedef struct lfCobol1 {
    unsigned short  leaf;           // LF_COBOL1
    unsigned char   data[CV_ZEROLEN];
} lfCobol1;

//      type record for basic array

typedef struct lfBArray {
    unsigned short  leaf;           // LF_BARRAY
    CV_typ_t        utype;          // type index of underlying type
} lfBArray;

//      type record for assembler labels

typedef struct lfLabel {
    unsigned short  leaf;           // LF_LABEL
    unsigned short  mode;           // addressing mode of label
} lfLabel;

//      type record for dimensioned arrays

typedef struct lfDimArray {
    unsigned short  leaf;           // LF_DIMARRAY
    CV_typ_t        utype;          // underlying type of the array
    CV_typ_t        diminfo;        // dimension information
    char            name[1];        // length prefixed name
} lfDimArray;

//      type record describing path to virtual function table

typedef struct lfVFTPath {
    unsigned short  leaf;           // LF_VFTPATH
    unsigned short  count;          // count of number of bases in path
    CV_typ_t        base[1];        // bases from root to leaf
} lfVFTPath;

//      type record describing inclusion of precompiled types

typedef struct lfPreComp {
    unsigned short  leaf;           // LF_PRECOMP
    unsigned short  start;          // starting type index included
    unsigned short  count;          // number of types in inclusion
    unsigned long   signature;      // signature
    unsigned char   name[CV_ZEROLEN]; // length prefixed name of included type file
} lfPreComp;

//      type record describing end of precompiled types that can be
//      included by another file

typedef struct lfEndPreComp {
    unsigned short  leaf;           // LF_ENDPRECOMP
    unsigned long   signature;      // signature
} lfEndPreComp;

//      type record for OEM definable type strings

typedef struct lfOEM {
    unsigned short  leaf;           // LF_OEM
    unsigned short  cvOEM;          // MS assigned OEM identified
    unsigned short  recOEM;         // OEM assigned type identifier
    unsigned short  count;          // count of type indices to follow
    CV_typ_t        index[CV_ZEROLEN];  // array of type indices followed
                                    // by OEM defined data
} lfOEM;

#define OEM_ODI                 0x0010

#define OEM_ODI_REC_BASELIST    0x0000

//      type record describing using of a type server

typedef struct lfTypeServer {
    unsigned short  leaf;           // LF_TYPESERVER
    unsigned long   signature;      // signature
    unsigned long   age;            // age of database used by this module
    unsigned char   name[CV_ZEROLEN];     // length prefixed name of PDB
} lfTypeServer;

//      description of type records that can be referenced from
//      type records referenced by symbols

//      type record for skip record

typedef struct lfSkip {
    unsigned short  leaf;           // LF_SKIP
    CV_typ_t        type;           // next valid index
    unsigned char   data[CV_ZEROLEN];     // pad data
} lfSkip;

//      argument list leaf

typedef struct lfArgList {
    unsigned short  leaf;           // LF_ARGLIST
    unsigned short  count;          // number of arguments
    CV_typ_t        arg[CV_ZEROLEN];  // number of arguments
} lfArgList;

//      derived class list leaf

typedef struct lfDerived {
    unsigned short  leaf;           // LF_DERIVED
    unsigned short  count;          // number of arguments
    CV_typ_t        drvdcls[CV_ZEROLEN];  // type indices of derived classes
} lfDerived;

//      leaf for default arguments

typedef struct lfDefArg {
    unsigned short  leaf;           // LF_DEFARG
    CV_typ_t        type;           // type of resulting expression
    unsigned char   expr[CV_ZEROLEN]; // length prefixed expression string
} lfDefArg;

//      list leaf
//      This list should no longer be used because the utilities cannot
//      verify the contents of the list without knowing what type of list
//      it is.  New specific leaf indices should be used instead.

typedef struct lfList {
    unsigned short  leaf;           // LF_LIST
    char            data[CV_ZEROLEN]; // data format specified by indexing type
} lfList;

//      field list leaf
//      This is the header leaf for a complex list of class and structure
//      subfields.

typedef struct lfFieldList {
    unsigned short  leaf;           // LF_FIELDLIST
    char            data[CV_ZEROLEN]; // field list sub lists
} lfFieldList;

//      type record for non-static methods and friends in overloaded method list

typedef struct mlMethod {
    CV_fldattr_t   attr;           // method attribute
    CV_typ_t       index;          // index to type record for procedure
    unsigned long  vbaseoff[CV_ZEROLEN];    // offset in vfunctable if intro virtual
} mlMethod;

typedef struct lfMethodList {
    unsigned short leaf;
    unsigned char  mList[CV_ZEROLEN];       // really a mlMethod type
} lfMethodList;

//      type record for LF_BITFIELD

typedef struct lfBitfield {
    unsigned short  leaf;           // LF_BITFIELD
    unsigned char   length;
    unsigned char   position;
    CV_typ_t        type;           // type of bitfield
} lfBitfield;

//      type record for dimensioned array with constant bounds

typedef struct lfDimCon {
    unsigned short  leaf;           // LF_DIMCONU or LF_DIMCONLU
    unsigned short  rank;           // number of dimensions
    CV_typ_t        typ;            // type of index
    unsigned char   dim[CV_ZEROLEN];  // array of dimension information with
                                    // either upper bounds or lower/upper bound
} lfDimCon;

//      type record for dimensioned array with variable bounds

typedef struct lfDimVar {
    unsigned short  leaf;           // LF_DIMVARU or LF_DIMVARLU
    unsigned short  rank;           // number of dimensions
    CV_typ_t        typ;            // type of index
    unsigned char   dim[CV_ZEROLEN];  // array of type indices for either
                                    // variable upper bound or variable
                                    // lower/upper bound.  The referenced
                                    // types must be LF_REFSYM or T_VOID
} lfDimVar;

//      type record for referenced symbol

typedef struct lfRefSym {
    unsigned short  leaf;           // LF_REFSYM
    unsigned char   Sym[1];         // copy of referenced symbol record
                                    // (including length)
} lfRefSym;

/**     the following are numeric leaves.  They are used to indicate the
 *      size of the following variable length data.  When the numeric
 *      data is a single byte less than 0x8000, then the data is output
 *      directly.  If the data is more the 0x8000 or is a negative value,
 *      then the data is preceeded by the proper index.
 */

//      signed character leaf

typedef struct lfChar {
    unsigned short  leaf;           // LF_CHAR
    signed char     val;            // signed 8-bit value
} lfChar;

//      signed short leaf

typedef struct lfShort {
    unsigned short  leaf;           // LF_SHORT
    short           val;            // signed 16-bit value
} lfShort;

//      unsigned short leaf

typedef struct lfUShort {
    unsigned short  leaf;           // LF_unsigned short
    unsigned short  val;            // unsigned 16-bit value
} lfUShort;

//      signed long leaf

typedef struct lfLong {
    unsigned short  leaf;           // LF_LONG
    long            val;            // signed 32-bit value
} lfLong;

//      unsigned long leaf

typedef struct lfULong {
    unsigned short  leaf;           // LF_ULONG
    unsigned long   val;            // unsigned 32-bit value
} lfULong;

//      signed quad leaf

typedef struct lfQuad {
    unsigned short  leaf;           // LF_QUAD
    unsigned char   val[8];         // signed 64-bit value
} lfQuad;

//      unsigned quad leaf

typedef struct lfUQuad {
    unsigned short  leaf;           // LF_UQUAD
    unsigned char   val[8];         // unsigned 64-bit value
} lfUQuad;

#if CC_BIGINT

//      signed int128 leaf

typedef struct lfOct {
    unsigned short  leaf;           // LF_OCT
    unsigned char   val[16];        // signed 128-bit value
} lfOct;

//      unsigned int128 leaf

typedef struct lfUOct {
    unsigned short  leaf;           // LF_UOCT
    unsigned char   val[16];        // unsigned 128-bit value
} lfUOct;

#endif

//      real 32-bit leaf

typedef struct lfReal32 {
    unsigned short  leaf;           // LF_REAL32
    float           val;            // 32-bit real value
} lfReal32;

//      real 48-bit leaf

typedef struct lfReal48 {
    unsigned short  leaf;           // LF_REAL48
    unsigned char   val[6];         // 48-bit real value
} lfReal48;

//      real 64-bit leaf

typedef struct lfReal64 {
    unsigned short  leaf;           // LF_REAL64
    double          val;            // 64-bit real value
} lfReal64;

//      real 80-bit leaf

typedef struct lfReal80 {
    unsigned short  leaf;           // LF_REAL80
    FLOAT10         val;            // real 80-bit value
} lfReal80;

//      real 128-bit leaf

typedef struct lfReal128 {
    unsigned short  leaf;           // LF_REAL128
    char            val[16];        // real 128-bit value
} lfReal128;

//      complex 32-bit leaf

typedef struct lfCmplx32 {
    unsigned short  leaf;           // LF_COMPLEX32
    float           val_real;       // real component
    float           val_imag;       // imaginary component
} lfCmplx32;

//      complex 64-bit leaf

typedef struct lfCmplx64 {
    unsigned short  leaf;           // LF_COMPLEX64
    double          val_real;       // real component
    double          val_imag;       // imaginary component
} flCmplx64;

//      complex 80-bit leaf

typedef struct lfCmplx80 {
    unsigned short  leaf;           // LF_COMPLEX80
    FLOAT10         val_real;       // real component
    FLOAT10         val_imag;       // imaginary component
} lfCmplx80;

//      complex 128-bit leaf

typedef struct lfCmplx128 {
    unsigned short  leaf;           // LF_COMPLEX128
    char            val_real[16];   // real component
    char            val_imag[16];   // imaginary component
} lfCmplx128;

//      variable length numeric field

typedef struct lfVarString {
    unsigned short  leaf;           // LF_VARSTRING
    unsigned short  len;            // length of value in bytes
    unsigned char   value[CV_ZEROLEN];  // value
} lfVarString;

//***********************************************************************

//      index leaf - contains type index of another leaf
//      a major use of this leaf is to allow the compilers to emit a
//      long complex list (LF_FIELD) in smaller pieces.

typedef struct lfIndex {
    unsigned short  leaf;           // LF_INDEX
    CV_typ_t        index;          // type index of referenced leaf
} lfIndex;

//      subfield record for base class field

typedef struct lfBClass {
    unsigned short  leaf;           // LF_BCLASS
    CV_typ_t        index;          // type index of base class
    CV_fldattr_t    attr;           // attribute
    unsigned char   offset[CV_ZEROLEN];   // variable length offset of base within class
} lfBClass;

//      subfield record for direct and indirect virtual base class field

typedef struct lfVBClass {
    unsigned short  leaf;           // LF_VBCLASS | LV_IVBCLASS
    CV_typ_t        index;          // type index of direct virtual base class
    CV_typ_t        vbptr;          // type index of virtual base pointer
    CV_fldattr_t    attr;           // attribute
    unsigned char   vbpoff[CV_ZEROLEN];   // virtual base pointer offset from address point
                                    // followed by virtual base offset from vbtable
} lfVBClass;

//      subfield record for friend class

typedef struct lfFriendCls {
    unsigned short  leaf;           // LF_FRIENDCLS
    CV_typ_t        index;          // index to type record of friend class
} lfFriendCls;

//      subfield record for friend function

typedef struct lfFriendFcn {
    unsigned short  leaf;           // LF_FRIENDFCN
    CV_typ_t        index;          // index to type record of friend function
    char            Name[1];        // name of friend function
} lfFriendFcn;

//      subfield record for non-static data members

typedef struct lfMember {
    unsigned short  leaf;           // LF_MEMBER
    CV_typ_t        index;          // index of type record for field
    CV_fldattr_t    attr;           // attribute mask
    unsigned char   offset[CV_ZEROLEN];   // variable length offset of field followed
                                    // by length prefixed name of field
} lfMember;

//      type record for static data members

typedef struct lfSTMember {
    unsigned short  leaf;           // LF_STMEMBER
    CV_typ_t        index;          // index of type record for field
    CV_fldattr_t    attr;           // attribute mask
    char            Name[1];        // length prefixed name of field
} lfSTMember;

//      subfield record for virtual function table pointer

typedef struct lfVFuncTab {
    unsigned short  leaf;           // LF_VFUNCTAB
    CV_typ_t        type;           // type index of pointer
} lfVFuncTab;

//      subfield record for virtual function table pointer with offset

typedef struct lfVFuncOff {
    unsigned short  leaf;           // LF_VFUNCTAB
    CV_typ_t        type;           // type index of pointer
    CV_off32_t      offset;         // offset of virtual function table pointer
} lfVFuncOff;

//      subfield record for overloaded method list

typedef struct lfMethod {
    unsigned short  leaf;           // LF_METHOD
    unsigned short  count;          // number of occurances of function
    CV_typ_t        mList;          // index to LF_METHODLIST record
    char            Name[1];        // length prefixed name of method
} lfMethod;

//      subfield record for nonoverloaded method

typedef struct lfOneMethod {
    unsigned short  leaf;           // LF_ONEMETHOD
    CV_fldattr_t    attr;           // method attribute
    CV_typ_t        index;          // index to type record for procedure
    unsigned long   vbaseoff[CV_ZEROLEN];    // offset in vfunctable if
                                    // intro virtual followed by
                                    // length prefixed name of method
} lfOneMethod;

//              subfield record for enumerate

typedef struct lfEnumerate {
    unsigned short  leaf;           // LF_ENUMERATE
    CV_fldattr_t    attr;           // access
    unsigned char   value[CV_ZEROLEN];  // variable length value field followed
                                        // by length prefixed name
} lfEnumerate;

//      type record for nested (scoped) type definition

typedef struct lfNestType {
    unsigned short  leaf;           // LF_NESTTYPE
    CV_typ_t        index;          // index of nested type definition
    unsigned char   Name[1];        // length prefixed type name
} lfNestType;

//      type record for pad leaf

typedef struct lfPad {
    unsigned char   leaf;
} SYM_PAD;

//      Symbol definitions

typedef enum SYM_ENUM_e {
    S_COMPILE    =  0x0001, // Compile flags symbol
    S_REGISTER   =  0x0002, // Register variable
    S_CONSTANT   =  0x0003, // constant symbol
    S_UDT        =  0x0004, // User defined type
    S_SSEARCH    =  0x0005, // Start Search
    S_END        =  0x0006, // Block, procedure, "with" or thunk end
    S_SKIP       =  0x0007, // Reserve symbol space in $$Symbols table
    S_CVRESERVE  =  0x0008, // Reserved symbol for CV internal use
    S_OBJNAME    =  0x0009, // path to object file name
    S_ENDARG     =  0x000a, // end of argument/return list
    S_COBOLUDT   =  0x000b, // special UDT for cobol that does not symbol pack
    S_MANYREG    =  0x000c, // multiple register variable
    S_RETURN     =  0x000d, // return description symbol
    S_ENTRYTHIS  =  0x000e, // description of this pointer on entry

    S_BPREL16    =  0x0100, // BP-relative
    S_LDATA16    =  0x0101, // Module-local symbol
    S_GDATA16    =  0x0102, // Global data symbol
    S_PUB16      =  0x0103, // a public symbol
    S_LPROC16    =  0x0104, // Local procedure start
    S_GPROC16    =  0x0105, // Global procedure start
    S_THUNK16    =  0x0106, // Thunk Start
    S_BLOCK16    =  0x0107, // block start
    S_WITH16     =  0x0108, // with start
    S_LABEL16    =  0x0109, // code label
    S_CEXMODEL16 =  0x010a, // change execution model
    S_VFTABLE16  =  0x010b, // address of virtual function table
    S_REGREL16   =  0x010c, // register relative address

    S_BPREL32    =  0x0200, // BP-relative
    S_LDATA32    =  0x0201, // Module-local symbol
    S_GDATA32    =  0x0202, // Global data symbol
    S_PUB32      =  0x0203, // a public symbol (CV internal reserved)
    S_LPROC32    =  0x0204, // Local procedure start
    S_GPROC32    =  0x0205, // Global procedure start
    S_THUNK32    =  0x0206, // Thunk Start
    S_BLOCK32    =  0x0207, // block start
    S_WITH32     =  0x0208, // with start
    S_LABEL32    =  0x0209, // code label
    S_CEXMODEL32 =  0x020a, // change execution model
    S_VFTABLE32  =  0x020b, // address of virtual function table
    S_REGREL32   =  0x020c, // register relative address
    S_LTHREAD32  =  0x020d, // local thread storage
    S_GTHREAD32  =  0x020e, // global thread storage
    S_SLINK32    =  0x020f, // static link for MIPS EH implementation

    S_LPROCMIPS  =  0x0300, // Local procedure start
    S_GPROCMIPS  =  0x0301, // Global procedure start

    S_PROCREF    =  0x0400, // Reference to a procedure
    S_DATAREF    =  0x0401, // Reference to data
    S_ALIGN      =  0x0402, // Used for page alignment of symbols
    S_LPROCREF   =  0x0403  // Local Reference to a procedure
} SYM_ENUM_e;

//      enum describing the compile flag source language

typedef enum CV_CFL_LANG {
    CV_CFL_C        = 0x00,
    CV_CFL_CXX      = 0x01,
    CV_CFL_FORTRAN  = 0x02,
    CV_CFL_MASM     = 0x03,
    CV_CFL_PASCAL   = 0x04,
    CV_CFL_BASIC    = 0x05,
    CV_CFL_COBOL    = 0x06
} CV_CFL_LANG;

//  enum describing target processor

typedef enum CV_CPU_TYPE_e {
    CV_CFL_8080         = 0x00,
    CV_CFL_8086         = 0x01,
    CV_CFL_80286        = 0x02,
    CV_CFL_80386        = 0x03,
    CV_CFL_80486        = 0x04,
    CV_CFL_PENTIUM      = 0x05,
    CV_CFL_MIPSR4000    = 0x10,
    CV_CFL_M68000       = 0x20,
    CV_CFL_M68010       = 0x21,
    CV_CFL_M68020       = 0x22,
    CV_CFL_M68030       = 0x23,
    CV_CFL_M68040       = 0x24,
    CV_CFL_ALPHA        = 0x30,
    CV_CFL_PPC601       = 0x40,
    CV_CFL_PPC603       = 0x41,
    CV_CFL_PPC604       = 0x42,
    CV_CFL_PPC620       = 0x43
} CV_CPU_TYPE_e;

//  enum describing compile flag ambiant data model

typedef enum CV_CFL_DATA {
    CV_CFL_DNEAR    = 0x00,
    CV_CFL_DFAR     = 0x01,
    CV_CFL_DHUGE    = 0x02
} CV_CFL_DATA;

//  enum describing compile flag ambiant code model

typedef enum CV_CFL_CODE_e {
    CV_CFL_CNEAR    = 0x00,
    CV_CFL_CFAR     = 0x01,
    CV_CFL_CHUGE    = 0x02
} CV_CFL_CODE_e;

//  enum describing compile flag target floating point package

typedef enum CV_CFL_FPKG_e {
    CV_CFL_NDP      = 0x00,
    CV_CFL_EMU      = 0x01,
    CV_CFL_ALT      = 0x02
} CV_CFL_FPKG_e;

// enum describing function return method

typedef struct CV_PROCFLAGS {
    union {
        unsigned char   bAll;
        struct {
            unsigned char CV_PFLAG_FPO  :1; // frame pointer omitted
            unsigned char CV_PFLAG_INT  :1; // interrupt return
            unsigned char CV_PFLAG_FAR  :1; // far return
            unsigned char CV_PFLAG_NEVER:1; // function does not return
            unsigned char unused        :4; //
        };
    };
} CV_PROCFLAGS;

// enum describing function data return method

typedef enum CV_GENERIC_STYLE_e {
    CV_GENERIC_VOID   = 0x00,       // void return type
    CV_GENERIC_REG    = 0x01,       // return data is in registers
    CV_GENERIC_ICAN   = 0x02,       // indirect caller allocated near
    CV_GENERIC_ICAF   = 0x03,       // indirect caller allocated far
    CV_GENERIC_IRAN   = 0x04,       // indirect returnee allocated near
    CV_GENERIC_IRAF   = 0x05,       // indirect returnee allocated far
    CV_GENERIC_UNUSED = 0x06        // first unused
} CV_GENERIC_STYLE_e;

typedef struct CV_GENERIC_FLAG {
    unsigned short  cstyle  :1;     // true push varargs right to left
    unsigned short  rsclean :1;     // true if returnee stack cleanup
    unsigned short  unused  :14;    // unused
} CV_GENERIC_FLAG;

typedef struct SYMTYPE {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // Record type
    char            data[CV_ZEROLEN];
} SYMTYPE;

__INLINE SYMTYPE *NextSym (SYMTYPE * pSym) {
    return (SYMTYPE *) ((char *)pSym + pSym->reclen + sizeof(unsigned short));
}

//              non-model specific symbol types

typedef struct REGSYM {
        unsigned short  reclen;         // Record length
        unsigned short  rectyp;         // S_REGISTER
        CV_typ_t                typind;         // Type index
        unsigned short  reg;            // register enumerate
        unsigned char   name[1];        // Length-prefixed name
} REGSYM;

typedef struct MANYREGSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_MANYREG
    CV_typ_t        typind;         // Type index
    unsigned char   count;          // count of number of registers
    unsigned char   reg[1];         // count register enumerates followed by
                                    // length-prefixed name.  Registers are
                                    // most significant first.
} MANYREGSYM;

typedef struct CONSTSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_CONSTANT
    CV_typ_t        typind;         // Type index (containing enum if enumerate)
    unsigned short  value;          // numeric leaf containing value
    unsigned char   name[CV_ZEROLEN];         // Length-prefixed name
} CONSTSYM;

typedef struct UDTSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_UDT | S_COBOLUDT
    CV_typ_t        typind;         // Type index
    unsigned char   name[1];        // Length-prefixed name
} UDTSYM;

typedef struct SEARCHSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_SSEARCH
    unsigned long   startsym;       // offset of the procedure
    unsigned short  seg;            // segment of symbol
} SEARCHSYM;

typedef struct CFLAGSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_COMPILE
    unsigned char   machine;        // target processor
    struct  {
        unsigned char   language    :8; // language index
        unsigned char   pcode       :1; // true if pcode present
        unsigned char   floatprec   :2; // floating precision
        unsigned char   floatpkg    :2; // float package
        unsigned char   ambdata     :3; // ambiant data model
        unsigned char   ambcode     :3; // ambiant code model
        unsigned char   mode32      :1; // true if compiled 32 bit mode
        unsigned char   pad         :4; // reserved
    } flags;
    unsigned char   ver[1];         // Length-prefixed compiler version string
} CFLAGSYM;

typedef struct OBJNAMESYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_OBJNAME
    unsigned long   signature;      // signature
    unsigned char   name[1];        // Length-prefixed name
} OBJNAMESYM;

typedef struct ENDARGSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_ENDARG
} ENDARGSYM;

typedef struct RETURNSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_RETURN
    CV_GENERIC_FLAG flags;          // flags
    CV_GENERIC_STYLE_e style;       // return style
                                    // followed by return method data
} RETURNSYM;

typedef struct ENTRYTHISSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_ENTRYTHIS
    unsigned char   thissym;        // symbol describing this pointer on entry
} ENTRYTHISSYM;

//  symbol types for 16:16 memory model

typedef struct BPRELSYM16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_BPREL16
    CV_off16_t      off;            // BP-relative offset
    CV_typ_t        typind;         // Type index
    unsigned char   name[1];        // Length-prefixed name
} BPRELSYM16;

typedef struct DATASYM16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_LDATA or S_GDATA
    CV_uoff16_t     off;            // offset of symbol
    unsigned short  seg;            // segment of symbol
    CV_typ_t        typind;         // Type index
    unsigned char   name[1];        // Length-prefixed name
} DATASYM16;
typedef DATASYM16 PUBSYM16;

typedef struct PROCSYM16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_GPROC16 or S_LPROC16
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   pNext;          // pointer to next symbol
    unsigned short  len;            // Proc length
    unsigned short  DbgStart;       // Debug start offset
    unsigned short  DbgEnd;         // Debug end offset
    CV_uoff16_t     off;            // offset of symbol
    unsigned short  seg;            // segment of symbol
    CV_typ_t        typind;         // Type index
    CV_PROCFLAGS    flags;          // Proc flags
    unsigned char   name[1];        // Length-prefixed name
} PROCSYM16;

typedef struct THUNKSYM16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_THUNK
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   pNext;          // pointer to next symbol
    CV_uoff16_t     off;            // offset of symbol
    unsigned short  seg;            // segment of symbol
    unsigned short  len;            // length of thunk
    unsigned char   ord;            // ordinal specifying type of thunk
    unsigned char   name[1];        // name of thunk
    unsigned char   variant[CV_ZEROLEN]; // variant portion of thunk
} THUNKSYM16;

typedef enum {
    THUNK_ORDINAL_NOTYPE,
    THUNK_ORDINAL_ADJUSTOR,
    THUNK_ORDINAL_VCALL,
    THUNK_ORDINAL_PCODE
} THUNK_ORDINAL;

typedef struct LABELSYM16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_LABEL16
    CV_uoff16_t     off;            // offset of symbol
    unsigned short  seg;            // segment of symbol
    CV_PROCFLAGS    flags;          // flags
    unsigned char   name[1];        // Length-prefixed name
} LABELSYM16;

typedef struct BLOCKSYM16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_BLOCK16
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned short  len;            // Block length
    CV_uoff16_t     off;            // offset of symbol
    unsigned short  seg;            // segment of symbol
    unsigned char   name[1];        // Length-prefixed name
} BLOCKSYM16;

typedef struct WITHSYM16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_WITH16
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned short  len;            // Block length
    CV_uoff16_t     off;            // offset of symbol
    unsigned short  seg;            // segment of symbol
    unsigned char   expr[1];        // Length-prefixed expression
} WITHSYM16;

typedef enum CEXM_MODEL_e {
    CEXM_MDL_table          = 0x00, // not executable
    CEXM_MDL_jumptable      = 0x01, // Compiler generated jump table
    CEXM_MDL_datapad        = 0x02, // Data padding for alignment
    CEXM_MDL_native         = 0x20, // native (actually not-pcode)
    CEXM_MDL_cobol          = 0x21, // cobol
    CEXM_MDL_codepad        = 0x22, // Code padding for alignment
    CEXM_MDL_code           = 0x23, // code
    CEXM_MDL_pcode          = 0x40, // pcode
    CEXM_MDL_pcode32Mac     = 0x41, // macintosh 32 bit pcode
    CEXM_MDL_pcode32MacNep  = 0x42  // macintosh 32 bit pcode native entry point
} CEXM_MODEL_e;

typedef enum CV_COBOL_e {
    CV_COBOL_dontstop,
    CV_COBOL_pfm,
    CV_COBOL_false,
    CV_COBOL_extcall
} CV_COBOL_e;

typedef struct CEXMSYM16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_CEXMODEL16
    CV_uoff16_t     off;            // offset of symbol
    unsigned short  seg;            // segment of symbol
    unsigned short  model;          // execution model
    union var16 {
        struct  {
            CV_uoff16_t pcdtable;   // offset to pcode function table
            CV_uoff16_t pcdspi;     // offset to segment pcode information
        } pcode;
        struct {
            unsigned short  subtype;   // see CV_COBOL_e above
            unsigned short  flag;
        } cobol;
    };
} CEXMSYM16;

typedef struct VPATHSYM16 {
    unsigned short  reclen;         // record length
    unsigned short  rectyp;         // S_VFTPATH16
    CV_uoff16_t     off;            // offset of virtual function table
    unsigned short  seg;            // segment of virtual function table
    CV_typ_t        root;           // type index of the root of path
    CV_typ_t        path;           // type index of the path record
} VPATHSYM16;

typedef struct REGREL16 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_REGREL16
    CV_uoff16_t     off;            // offset of symbol
    unsigned short  reg;            // register index
    CV_typ_t        typind;         // Type index
    unsigned char   name[1];        // Length-prefixed name
} REGREL16;

typedef struct BPRELSYM32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_BPREL32
    CV_off32_t      off;            // BP-relative offset
    CV_typ_t        typind;         // Type index
    unsigned char   name[1];        // Length-prefixed name
} BPRELSYM32;

typedef struct DATASYM32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_LDATA32, S_GDATA32 or S_PUB32
    CV_uoff32_t     off;
    unsigned short  seg;
    CV_typ_t        typind;         // Type index
    unsigned char   name[1];        // Length-prefixed name
} DATASYM32;
typedef DATASYM32 PUBSYM32;

typedef struct PROCSYM32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_GPROC32 or S_LPROC32
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   pNext;          // pointer to next symbol
    unsigned long   len;            // Proc length
    unsigned long   DbgStart;       // Debug start offset
    unsigned long   DbgEnd;         // Debug end offset
    CV_uoff32_t     off;
    unsigned short  seg;
    CV_typ_t        typind;         // Type index
    CV_PROCFLAGS    flags;          // Proc flags
    unsigned char   name[1];        // Length-prefixed name
} PROCSYM32;

typedef struct THUNKSYM32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_THUNK32
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   pNext;          // pointer to next symbol
    CV_uoff32_t     off;
    unsigned short  seg;
    unsigned short  len;            // length of thunk
    unsigned char   ord;            // ordinal specifying type of thunk
    unsigned char   name[1];        // Length-prefixed name
    unsigned char   variant[CV_ZEROLEN]; // variant portion of thunk
} THUNKSYM32;

typedef struct LABELSYM32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_LABEL32
    CV_uoff32_t     off;
    unsigned short  seg;
    CV_PROCFLAGS    flags;          // flags
    unsigned char   name[1];        // Length-prefixed name
} LABELSYM32;

typedef struct BLOCKSYM32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_BLOCK32
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   len;            // Block length
    CV_uoff32_t     off;            // Offset in code segment
    unsigned short  seg;            // segment of label
    unsigned char   name[1];        // Length-prefixed name
} BLOCKSYM32;

typedef struct WITHSYM32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_WITH32
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   len;            // Block length
    CV_uoff32_t     off;            // Offset in code segment
    unsigned short  seg;            // segment of label
    unsigned char   expr[1];        // Length-prefixed expression string
} WITHSYM32;

typedef struct CEXMSYM32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_CEXMODEL32
    CV_uoff32_t     off;            // offset of symbol
    unsigned short  seg;            // segment of symbol
    unsigned short  model;          // execution model
    union var32 {
        struct  {
            CV_uoff32_t pcdtable;   // offset to pcode function table
            CV_uoff32_t pcdspi;     // offset to segment pcode information
        } pcode;
        struct {
            unsigned short  subtype;   // see CV_COBOL_e above
            unsigned short  flag;
        } cobol;
        struct {
            CV_uoff32_t calltableOff; // offset to function table
            unsigned short calltableSeg; // segment of function table
        } pcode32Mac;
    };
} CEXMSYM32;

typedef struct VPATHSYM32 {
    unsigned short  reclen;         // record length
    unsigned short  rectyp;         // S_VFTPATH32
    CV_uoff32_t     off;            // offset of virtual function table
    unsigned short  seg;            // segment of virtual function table
    CV_typ_t        root;           // type index of the root of path
    CV_typ_t        path;           // type index of the path record
} VPATHSYM32;

typedef struct REGREL32 {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_REGREL32
    CV_uoff32_t     off;            // offset of symbol
    unsigned short  reg;            // register index for symbol
    CV_typ_t        typind;         // Type index
    unsigned char   name[1];        // Length-prefixed name
} REGREL32, FAR * LPREGREL32;

typedef struct THREADSYM32 {
    unsigned short  reclen;         // record length
    unsigned short  rectyp;         // S_LTHREAD32 | S_GTHREAD32
    CV_uoff32_t     off;            // offset into thread storage
    unsigned short  seg;            // segment of thread storage
    CV_typ_t        typind;         // type index
    unsigned char   name[1];        // length prefixed name
} THREADSYM32;

typedef struct SLINK32 {
    unsigned short  reclen;         // record length
    unsigned short  rectyp;         // S_SLINK32
    unsigned long   framesize;      // frame size of parent procedure
    CV_off32_t      off;            // signed offset where the static link was saved relative to the value of reg
    unsigned short  reg;
} SLINK32, FAR * LPSLINK32;

typedef struct PROCSYMMIPS {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_GPROCMIPS or S_LPROCMIPS
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   pNext;          // pointer to next symbol
    unsigned long   len;            // Proc length
    unsigned long   DbgStart;       // Debug start offset
    unsigned long   DbgEnd;         // Debug end offset
    unsigned long   regSave;        // int register save mask
    unsigned long   fpSave;         // fp register save mask
    CV_uoff32_t     intOff;         // int register save offset
    CV_uoff32_t     fpOff;          // fp register save offset
    CV_uoff32_t     off;            // Symbol offset
    unsigned short  seg;            // Symbol segment
    CV_typ_t        typind;         // Type index
    unsigned char   retReg;         // Register return value is in
    unsigned char   frameReg;       // Frame pointer register
    unsigned char   name[1];        // Length-prefixed name
} PROCSYMMIPS, FAR *PROCPTRMIPS;

typedef struct REFSYM {
    unsigned short  reclen;     // Record length
    unsigned short  rectyp;     // S_PROCREF or S_DATAREF
    unsigned long   sumName;    // SUC of the name
    unsigned long   ibSym;          // Offset of actual symbol in $$Symbols
    unsigned short  imod;       // Module containing the actual symbol
    unsigned short  usFill;     // align this record
} REFSYM;

typedef struct ALIGNSYM {
    unsigned short  reclen;     // Record length
    unsigned short  rectyp;     // S_PROCREF or S_DATAREF
} ALIGNSYM;

//  generic block definition symbols
//  these are similar to the equivalent 16:16 or 16:32 symbols but
//  only define the length, type and linkage fields

typedef struct PROCSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_GPROC16 or S_LPROC16
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   pNext;          // pointer to next symbol
} PROCSYM;

typedef struct THUNKSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_THUNK
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
    unsigned long   pNext;          // pointer to next symbol
} THUNKSYM;

typedef struct BLOCKSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_BLOCK16
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
} BLOCKSYM;

typedef struct WITHSYM {
    unsigned short  reclen;         // Record length
    unsigned short  rectyp;         // S_WITH16
    unsigned long   pParent;        // pointer to the parent
    unsigned long   pEnd;           // pointer to this blocks end
} WITHSYM;

typedef enum CV_HREG_e {
    //  Register set for the Intel 80x86 and ix86 processor series
    //  (plus PCODE registers)

    CV_REG_NONE     =   0,
    CV_REG_AL       =   1,
    CV_REG_CL       =   2,
    CV_REG_DL       =   3,
    CV_REG_BL       =   4,
    CV_REG_AH       =   5,

CV_REG_CH       =   6,
    CV_REG_DH       =   7,
    CV_REG_BH       =   8,
    CV_REG_AX       =   9,
    CV_REG_CX       =  10,
    CV_REG_DX       =  11,
    CV_REG_BX       =  12,
    CV_REG_SP       =  13,
    CV_REG_BP       =  14,
    CV_REG_SI       =  15,
    CV_REG_DI       =  16,
    CV_REG_EAX      =  17,
    CV_REG_ECX      =  18,
    CV_REG_EDX      =  19,
    CV_REG_EBX      =  20,
    CV_REG_ESP      =  21,
    CV_REG_EBP      =  22,
    CV_REG_ESI      =  23,
    CV_REG_EDI      =  24,
    CV_REG_ES       =  25,
    CV_REG_CS       =  26,
    CV_REG_SS       =  27,
    CV_REG_DS       =  28,
    CV_REG_FS       =  29,
    CV_REG_GS       =  30,
    CV_REG_IP       =  31,
    CV_REG_FLAGS    =  32,
    CV_REG_EIP      =  33,
    CV_REG_EFLAGS   =  34,
    CV_REG_TEMP     =  40,          // PCODE Temp
    CV_REG_TEMPH    =  41,          // PCODE TempH
    CV_REG_QUOTE    =  42,          // PCODE Quote
    CV_REG_PCDR3    =  43,          // PCODE reserved
    CV_REG_PCDR4    =  44,          // PCODE reserved
    CV_REG_PCDR5    =  45,          // PCODE reserved
    CV_REG_PCDR6    =  46,          // PCODE reserved
    CV_REG_PCDR7    =  47,          // PCODE reserved
    CV_REG_CR0      =  80,          // CR0 -- control registers
    CV_REG_CR1      =  81,
    CV_REG_CR2      =  82,
    CV_REG_CR3      =  83,
    CV_REG_CR4      =  84,          // Pentium
    CV_REG_DR0      =  90,          // Debug register
    CV_REG_DR1      =  91,
    CV_REG_DR2      =  92,
    CV_REG_DR3      =  93,
    CV_REG_DR4      =  94,
    CV_REG_DR5      =  95,
    CV_REG_DR6      =  96,
    CV_REG_DR7      =  97,
    CV_REG_GDTR     =  110,
    CV_REG_GDTL     =  111,
    CV_REG_IDTR     =  112,
    CV_REG_IDTL     =  113,
    CV_REG_LDTR     =  114,
    CV_REG_TR       =  115,

    CV_REG_PSEUDO1  =  116,
    CV_REG_PSEUDO2  =  117,
    CV_REG_PSEUDO3  =  118,
    CV_REG_PSEUDO4  =  119,
    CV_REG_PSEUDO5  =  120,
    CV_REG_PSEUDO6  =  121,
    CV_REG_PSEUDO7  =  122,
    CV_REG_PSEUDO8  =  123,
    CV_REG_PSEUDO9  =  124,

    CV_REG_ST0      =  128,
    CV_REG_ST1      =  129,
    CV_REG_ST2      =  130,
    CV_REG_ST3      =  131,
    CV_REG_ST4      =  132,
    CV_REG_ST5      =  133,
    CV_REG_ST6      =  134,
    CV_REG_ST7      =  135,
    CV_REG_CTRL     =  136,
    CV_REG_STAT     =  137,
    CV_REG_TAG      =  138,
    CV_REG_FPIP     =  139,
    CV_REG_FPCS     =  140,
    CV_REG_FPDO     =  141,
    CV_REG_FPDS     =  142,
    CV_REG_ISEM     =  143,
    CV_REG_FPEIP    =  144,
    CV_REG_FPEDO    =  145,

    // registers for the 68K processors

    CV_R68_D0       =   0,
    CV_R68_D1       =   1,
    CV_R68_D2       =   2,
    CV_R68_D3       =   3,
    CV_R68_D4       =   4,
    CV_R68_D5       =   5,
    CV_R68_D6       =   6,
    CV_R68_D7       =   7,
    CV_R68_A0       =   8,
    CV_R68_A1       =   9,
    CV_R68_A2       =   10,
    CV_R68_A3       =   11,
    CV_R68_A4       =   12,
    CV_R68_A5       =   13,
    CV_R68_A6       =   14,
    CV_R68_A7       =   15,
    CV_R68_CCR      =   16,
    CV_R68_SR       =   17,
    CV_R68_USP      =   18,
    CV_R68_MSP      =   19,
    CV_R68_SFC      =   20,
    CV_R68_DFC      =   21,
    CV_R68_CACR     =   22,
    CV_R68_VBR      =   23,
    CV_R68_CAAR     =   24,
    CV_R68_ISP      =   25,
    CV_R68_PC       =   26,
    //reserved          27
    CV_R68_FPCR     =   28,
    CV_R68_FPSR     =   29,
    CV_R68_FPIAR    =   30,
    //reserved          31
    CV_R68_FP0      =   32,
    CV_R68_FP1      =   33,
    CV_R68_FP2      =   34,
    CV_R68_FP3      =   35,
    CV_R68_FP4      =   36,
    CV_R68_FP5      =   37,
    CV_R68_FP6      =   38,
    CV_R68_FP7      =   39,
    //reserved      40-50
    CV_R68_PSR      =   51,
    CV_R68_PCSR     =   52,
    CV_R68_VAL      =   53,
    CV_R68_CRP      =   54,
    CV_R68_SRP      =   55,
    CV_R68_DRP      =   56,
    CV_R68_TC       =   57,
    CV_R68_AC       =   58,
    CV_R68_SCC      =   59,
    CV_R68_CAL      =   60,
    CV_R68_TT0      =   61,
    CV_R68_TT1      =   62,
    //reserved      63
    CV_R68_BAD0     =   64,
    CV_R68_BAD1     =   65,
    CV_R68_BAD2     =   66,
    CV_R68_BAD3     =   67,
    CV_R68_BAD4     =   68,
    CV_R68_BAD5     =   69,
    CV_R68_BAD6     =   70,
    CV_R68_BAD7     =   71,
    CV_R68_BAC0     =   72,
    CV_R68_BAC1     =   73,
    CV_R68_BAC2     =   74,
    CV_R68_BAC3     =   75,
    CV_R68_BAC4     =   76,
    CV_R68_BAC5     =   77,
    CV_R68_BAC6     =   78,
    CV_R68_BAC7     =   79,

    // Register set for the MIPS 4000

    CV_M4_NOREG     =   CV_REG_NONE,

    CV_M4_IntZERO   =   10,      /* CPU REGISTER */
    CV_M4_IntAT     =   11,
    CV_M4_IntV0     =   12,
    CV_M4_IntV1     =   13,
    CV_M4_IntA0     =   14,
    CV_M4_IntA1     =   15,
    CV_M4_IntA2     =   16,
    CV_M4_IntA3     =   17,
    CV_M4_IntT0     =   18,
    CV_M4_IntT1     =   19,
    CV_M4_IntT2     =   20,
    CV_M4_IntT3     =   21,
    CV_M4_IntT4     =   22,
    CV_M4_IntT5     =   23,
    CV_M4_IntT6     =   24,
    CV_M4_IntT7     =   25,
    CV_M4_IntS0     =   26,
    CV_M4_IntS1     =   27,
    CV_M4_IntS2     =   28,
    CV_M4_IntS3     =   29,
    CV_M4_IntS4     =   30,
    CV_M4_IntS5     =   31,
    CV_M4_IntS6     =   32,
    CV_M4_IntS7     =   33,
    CV_M4_IntT8     =   34,
    CV_M4_IntT9     =   35,
    CV_M4_IntKT0    =   36,
    CV_M4_IntKT1    =   37,
    CV_M4_IntGP     =   38,
    CV_M4_IntSP     =   39,
    CV_M4_IntS8     =   40,
    CV_M4_IntRA     =   41,
    CV_M4_IntLO     =   42,
    CV_M4_IntHI     =   43,

    CV_M4_Fir       =   50,
    CV_M4_Psr       =   51,

    CV_M4_FltF0     =   60,      /* Floating point registers */
    CV_M4_FltF1     =   61,
    CV_M4_FltF2     =   62,
    CV_M4_FltF3     =   63,
    CV_M4_FltF4     =   64,
    CV_M4_FltF5     =   65,
    CV_M4_FltF6     =   66,
    CV_M4_FltF7     =   67,
    CV_M4_FltF8     =   68,
    CV_M4_FltF9     =   69,
    CV_M4_FltF10    =   70,
    CV_M4_FltF11    =   71,
    CV_M4_FltF12    =   72,
    CV_M4_FltF13    =   73,
    CV_M4_FltF14    =   74,
    CV_M4_FltF15    =   75,
    CV_M4_FltF16    =   76,
    CV_M4_FltF17    =   77,
    CV_M4_FltF18    =   78,
    CV_M4_FltF19    =   79,
    CV_M4_FltF20    =   80,
    CV_M4_FltF21    =   81,
    CV_M4_FltF22    =   82,
    CV_M4_FltF23    =   83,
    CV_M4_FltF24    =   84,
    CV_M4_FltF25    =   85,
    CV_M4_FltF26    =   86,
    CV_M4_FltF27    =   87,
    CV_M4_FltF28    =   88,
    CV_M4_FltF29    =   89,
    CV_M4_FltF30    =   90,
    CV_M4_FltF31    =   91,
    CV_M4_FltFsr    =   92,

    // Register set for the ALPHA AXP

    CV_ALPHA_NOREG  = CV_REG_NONE,

    CV_ALPHA_FltF0  =   10,         /* Floating point registers */
    CV_ALPHA_FltF1  =   11,
    CV_ALPHA_FltF2  =   12,
    CV_ALPHA_FltF3  =   13,
    CV_ALPHA_FltF4  =   14,
    CV_ALPHA_FltF5  =   15,
    CV_ALPHA_FltF6  =   16,
    CV_ALPHA_FltF7  =   17,
    CV_ALPHA_FltF8  =   18,
    CV_ALPHA_FltF9  =   19,
    CV_ALPHA_FltF10 =   20,
    CV_ALPHA_FltF11 =   21,
    CV_ALPHA_FltF12 =   22,
    CV_ALPHA_FltF13 =   23,
    CV_ALPHA_FltF14 =   24,
    CV_ALPHA_FltF15 =   25,
    CV_ALPHA_FltF16 =   26,
    CV_ALPHA_FltF17 =   27,
    CV_ALPHA_FltF18 =   28,
    CV_ALPHA_FltF19 =   29,
    CV_ALPHA_FltF20 =   30,
    CV_ALPHA_FltF21 =   31,
    CV_ALPHA_FltF22 =   32,
    CV_ALPHA_FltF23 =   33,
    CV_ALPHA_FltF24 =   34,
    CV_ALPHA_FltF25 =   35,
    CV_ALPHA_FltF26 =   36,
    CV_ALPHA_FltF27 =   37,
    CV_ALPHA_FltF28 =   38,
    CV_ALPHA_FltF29 =   39,
    CV_ALPHA_FltF30 =   40,
    CV_ALPHA_FltF31 =   41,

    CV_ALPHA_IntV0  =   42,   // Integer registers
    CV_ALPHA_IntT0  =   43,
    CV_ALPHA_IntT1  =   44,
    CV_ALPHA_IntT2  =   45,
    CV_ALPHA_IntT3  =   46,
    CV_ALPHA_IntT4  =   47,
    CV_ALPHA_IntT5  =   48,
    CV_ALPHA_IntT6  =   49,
    CV_ALPHA_IntT7  =   50,
    CV_ALPHA_IntS0  =   51,
    CV_ALPHA_IntS1  =   52,
    CV_ALPHA_IntS2  =   53,
    CV_ALPHA_IntS3  =   54,
    CV_ALPHA_IntS4  =   55,
    CV_ALPHA_IntS5  =   56,
    CV_ALPHA_IntFP  =   57,
    CV_ALPHA_IntA0  =   58,
    CV_ALPHA_IntA1  =   59,
    CV_ALPHA_IntA2  =   60,
    CV_ALPHA_IntA3  =   61,
    CV_ALPHA_IntA4  =   62,
    CV_ALPHA_IntA5  =   63,
    CV_ALPHA_IntT8  =   64,
    CV_ALPHA_IntT9  =   65,
    CV_ALPHA_IntT10 =   66,
    CV_ALPHA_IntT11 =   67,
    CV_ALPHA_IntRA  =   68,
    CV_ALPHA_IntT12 =   69,
    CV_ALPHA_IntAT  =   70,
    CV_ALPHA_IntGP  =   71,
    CV_ALPHA_IntSP  =   72,
    CV_ALPHA_IntZERO =  73,

    CV_ALPHA_Fpcr   =   74,   //  Control registers
    CV_ALPHA_Fir    =   75,
    CV_ALPHA_Psr    =   76,
    CV_ALPHA_FltFsr =   77,
    CV_ALPHA_SoftFpcr = 78,   //  Control registers

    // Register Set for Motorola/IBM PowerPC

    /*
    ** PowerPC General Registers ( User Level )
    */
    CV_PPC_GPR0   = 1,
    CV_PPC_GPR1   = 2,
    CV_PPC_GPR2   = 3,
    CV_PPC_GPR3   = 4,
    CV_PPC_GPR4   = 5,
    CV_PPC_GPR5   = 6,
    CV_PPC_GPR6   = 7,
    CV_PPC_GPR7   = 8,
    CV_PPC_GPR8   = 9,
    CV_PPC_GPR9   = 10,
    CV_PPC_GPR10  = 11,
    CV_PPC_GPR11  = 12,
    CV_PPC_GPR12  = 13,
    CV_PPC_GPR13  = 14,
    CV_PPC_GPR14  = 15,
    CV_PPC_GPR15  = 16,
    CV_PPC_GPR16  = 17,
    CV_PPC_GPR17  = 18,
    CV_PPC_GPR18  = 19,
    CV_PPC_GPR19  = 20,
    CV_PPC_GPR20  = 21,
    CV_PPC_GPR21  = 22,
    CV_PPC_GPR22  = 23,
    CV_PPC_GPR23  = 24,
    CV_PPC_GPR24  = 25,
    CV_PPC_GPR25  = 26,
    CV_PPC_GPR26  = 27,
    CV_PPC_GPR27  = 28,
    CV_PPC_GPR28  = 29,
    CV_PPC_GPR29  = 30,
    CV_PPC_GPR30  = 31,
    CV_PPC_GPR31  = 32,

    /*
    ** PowerPC Condition Register ( User Level )
    */
    CV_PPC_CR     = 33,
    CV_PPC_CR0    = 34,
    CV_PPC_CR1    = 35,
    CV_PPC_CR2    = 36,
    CV_PPC_CR3    = 37,
    CV_PPC_CR4    = 38,
    CV_PPC_CR5    = 39,
    CV_PPC_CR6    = 40,
    CV_PPC_CR7    = 41,

    /*
    ** PowerPC Floating Point Registers ( User Level )
    */
    CV_PPC_FPR0   = 42,
    CV_PPC_FPR1   = 43,
    CV_PPC_FPR2   = 44,
    CV_PPC_FPR3   = 45,
    CV_PPC_FPR4   = 46,
    CV_PPC_FPR5   = 47,
    CV_PPC_FPR6   = 48,
    CV_PPC_FPR7   = 49,
    CV_PPC_FPR8   = 50,
    CV_PPC_FPR9   = 51,
    CV_PPC_FPR10  = 52,
    CV_PPC_FPR11  = 53,
    CV_PPC_FPR12  = 54,
    CV_PPC_FPR13  = 55,
    CV_PPC_FPR14  = 56,
    CV_PPC_FPR15  = 57,
    CV_PPC_FPR16  = 58,
    CV_PPC_FPR17  = 59,
    CV_PPC_FPR18  = 60,
    CV_PPC_FPR19  = 61,
    CV_PPC_FPR20  = 62,
    CV_PPC_FPR21  = 63,
    CV_PPC_FPR22  = 64,
    CV_PPC_FPR23  = 65,
    CV_PPC_FPR24  = 66,
    CV_PPC_FPR25  = 67,
    CV_PPC_FPR26  = 68,
    CV_PPC_FPR27  = 69,
    CV_PPC_FPR28  = 70,
    CV_PPC_FPR29  = 71,
    CV_PPC_FPR30  = 72,
    CV_PPC_FPR31  = 73,

    /*
    ** PowerPC Floating Point Status and Control Register ( User Level )
    */
    CV_PPC_FPSCR  = 74,

    /*
    ** PowerPC Machine State Register ( Supervisor Level )
    */
    CV_PPC_MSR    = 75,

    /*
    ** PowerPC Segment Registers ( Supervisor Level )
    */
    CV_PPC_SR0    = 76,
    CV_PPC_SR1    = 77,
    CV_PPC_SR2    = 78,
    CV_PPC_SR3    = 79,
    CV_PPC_SR4    = 80,
    CV_PPC_SR5    = 81,
    CV_PPC_SR6    = 82,
    CV_PPC_SR7    = 83,
    CV_PPC_SR8    = 84,
    CV_PPC_SR9    = 85,
    CV_PPC_SR10   = 86,
    CV_PPC_SR11   = 87,
    CV_PPC_SR12   = 88,
    CV_PPC_SR13   = 89,
    CV_PPC_SR14   = 90,
    CV_PPC_SR15   = 91,

    /*
    ** For all of the special purpose registers add 100 to the SPR# that the
    ** Motorola/IBM documentation gives with the exception of any imaginary
    ** registers.
    */

    /*
    ** PowerPC Special Purpose Registers ( User Level )
    */
    CV_PPC_PC     = 99,     // PC (imaginary register)

    CV_PPC_MQ     = 100,    // MPC601
    CV_PPC_XER    = 101,
    CV_PPC_RTCU   = 104,    // MPC601
    CV_PPC_RTCL   = 105,    // MPC601
    CV_PPC_LR     = 108,
    CV_PPC_CTR    = 109,

    /*
    ** PowerPC Special Purpose Registers ( Supervisor Level )
    */
    CV_PPC_DSISR  = 118,
    CV_PPC_DAR    = 119,
    CV_PPC_DEC    = 122,
    CV_PPC_SDR1   = 125,
    CV_PPC_SRR0   = 126,
    CV_PPC_SRR1   = 127,
    CV_PPC_SPRG0  = 372,
    CV_PPC_SPRG1  = 373,
    CV_PPC_SPRG2  = 374,
    CV_PPC_SPRG3  = 375,
    CV_PPC_ASR    = 280,    // 64-bit implementations only
    CV_PPC_EAR    = 382,
    CV_PPC_PVR    = 287,
    CV_PPC_BAT0U  = 628,
    CV_PPC_BAT0L  = 629,
    CV_PPC_BAT1U  = 630,
    CV_PPC_BAT1L  = 631,
    CV_PPC_BAT2U  = 632,
    CV_PPC_BAT2L  = 633,
    CV_PPC_BAT3U  = 634,
    CV_PPC_BAT3L  = 635,
    CV_PPC_DBAT0U = 636,
    CV_PPC_DBAT0L = 637,
    CV_PPC_DBAT1U = 638,
    CV_PPC_DBAT1L = 639,
    CV_PPC_DBAT2U = 640,
    CV_PPC_DBAT2L = 641,
    CV_PPC_DBAT3U = 642,
    CV_PPC_DBAT3L = 643,

    /*
    ** PowerPC Special Purpose Registers Implementation Dependent ( Supervisor Level )
    */

    /*
    ** Doesn't appear that IBM/Motorola has finished defining these.
    */

    CV_PPC_PMR0   = 1044,   // MPC620,
    CV_PPC_PMR1   = 1045,   // MPC620,
    CV_PPC_PMR2   = 1046,   // MPC620,
    CV_PPC_PMR3   = 1047,   // MPC620,
    CV_PPC_PMR4   = 1048,   // MPC620,
    CV_PPC_PMR5   = 1049,   // MPC620,
    CV_PPC_PMR6   = 1050,   // MPC620,
    CV_PPC_PMR7   = 1051,   // MPC620,
    CV_PPC_PMR8   = 1052,   // MPC620,
    CV_PPC_PMR9   = 1053,   // MPC620,
    CV_PPC_PMR10  = 1054,   // MPC620,
    CV_PPC_PMR11  = 1055,   // MPC620,
    CV_PPC_PMR12  = 1056,   // MPC620,
    CV_PPC_PMR13  = 1057,   // MPC620,
    CV_PPC_PMR14  = 1058,   // MPC620,
    CV_PPC_PMR15  = 1059,   // MPC620,

    CV_PPC_DMISS  = 1076,   // MPC603
    CV_PPC_DCMP   = 1077,   // MPC603
    CV_PPC_HASH1  = 1078,   // MPC603
    CV_PPC_HASH2  = 1079,   // MPC603
    CV_PPC_IMISS  = 1080,   // MPC603
    CV_PPC_ICMP   = 1081,   // MPC603
    CV_PPC_RPA    = 1082,   // MPC603

    CV_PPC_HID0   = 1108,   // MPC601, MPC603, MPC620
    CV_PPC_HID1   = 1109,   // MPC601
    CV_PPC_HID2   = 1110,   // MPC601, MPC603, MPC620 ( IABR )
    CV_PPC_HID3   = 1111,   // Not Defined
    CV_PPC_HID4   = 1112,   // Not Defined
    CV_PPC_HID5   = 1113,   // MPC601, MPC604, MPC620 ( DABR )
    CV_PPC_HID6   = 1114,   // Not Defined
    CV_PPC_HID7   = 1115,   // Not Defined
    CV_PPC_HID8   = 1116,   // MPC620 ( BUSCSR )
    CV_PPC_HID9   = 1117,   // MPC620 ( L2CSR )
    CV_PPC_HID10  = 1118,   // Not Defined
    CV_PPC_HID11  = 1119,   // Not Defined
    CV_PPC_HID12  = 1120,   // Not Defined
    CV_PPC_HID13  = 1121,   // MPC604 ( HCR )
    CV_PPC_HID14  = 1122,   // Not Defined
    CV_PPC_HID15  = 1123    // MPC601, MPC604, MPC620 ( PIR )

} CV_HREG_e;

#include <poppack.h>

#ifdef  __cplusplus
#pragma warning ( default: 4200 )
#endif

#endif /* CV_INFO_INCLUDED */


CVTYPES.H

/*************************************************************************
**                                                                      **
**                          CVTYPES.H                                   **
**                                                                      **
**************************************************************************
**                                                                      **
**  This file contains a common set of base type declarations           **
**  between multiple CodeView projects.  If you touch this in one       **
**  project be sure to copy it to all other projects as well.           **
**                                                                      **
*************************************************************************/

#ifndef CV_PROJECT_BASE_TYPES

#define CV_PROJECT_BASE_TYPES

#include "dbapiver.h"

#ifndef _CV_INFO_INCLUDED
#include "cvinfo.h"
#endif

#if !defined(OSDEBUG4)
/*
**  HDEP is a machine dependent size and passes as a general handle.
**  HIND is a machine independent sized handle and is used for things
**      which are passed between machines
**
*/

#ifdef QCWIN32
    typedef HANDLE      HDEP;
    typedef DWORD       HIND;
#else
    DECLARE_HANDLE(HDEP);
    DECLARE_HANDLE(HIND);
#endif // QCWIN32

typedef HDEP FAR *      LPHDEP;
typedef HIND FAR *      LPHIND;

/* HMEM should be avoided (HDEP should be used instead), but for now we'll
** define it for backwards compatibility.
*/

typedef HDEP            HMEM;
typedef HMEM FAR *      LPHMEM;
#endif // !OSDEBUG4

/* These values are used in the SegType field of the Expression Evaluator's
** TI structure, and as the third parameter to the Symbol Handler's
** SHGetNearestHsym function.
*/
#define EECODE          0x01
#define EEDATA          0x02
#define EEANYSEG        0xFFFF

#if !defined(OSDEBUG4)
/*
**  HPID
**  HTID
**
*/

DECLARE_HANDLE(HPID);
DECLARE_HANDLE(HTID);

typedef USHORT      SEGMENT;    // 32-bit compiler doesn't like "_segment"
typedef ULONG       UOFF32;
typedef USHORT      UOFF16;
typedef LONG        OFF32;
typedef SHORT       OFF16;

#if defined (ADDR_16)
    // we are operating as a 16:16 evaluator only
    // the address packet will be defined as an offset and a 16 bit filler
    typedef OFF16       OFFSET;
    typedef UOFF16      UOFFSET;
#else
    typedef OFF32       OFFSET;
    typedef UOFF32      UOFFSET;
#endif // ADDR_16

typedef UOFFSET FAR *LPUOFFSET;

//  address definitions
//  the address packet is always a 16:32 address.

typedef struct {
    UOFF32          off;
    SEGMENT         seg;
} address_t;

#define SegAddrT(a)   ((a).seg)
#define OffAddrT(a)   ((a).off)
#define AddrTInit(paddrT,segSet,offSet)     \
        {                                   \
            SegAddrT(*(paddrT)) = segSet;   \
            OffAddrT(*(paddrT)) = offSet;   \
        }

typedef struct {
    BYTE    fFlat   :1;         // true if address is flat
    BYTE    fOff32  :1;         // true if offset is 32 bits
    BYTE    fIsLI   :1;         // true if segment is linker index
    BYTE    fReal   :1;         // x86: is segment a real mode address
    BYTE    unused  :4;         // unused
} memmode_t;

#define MODE_IS_FLAT(m)     ((m).fFlat)
#define MODE_IS_OFF32(m)    ((m).fOff32)
#define MODE_IS_LI(m)       ((m).fIsLI)
#define MODE_IS_REAL(m)     ((m).fReal)

#define ModeInit(pmode,fFlat,fOff32,fLi,fRealSet)   \
        {                                           \
            MODE_IS_FLAT(*(pmode))    = fFlat;      \
            MODE_IS_OFF32(*(pmode))   = fOff32;     \
            MODE_IS_LI(*(pmode))      = fLi;        \
            MODE_IS_REAL(*(pmode))    = fRealSet;   \
        }

typedef HIND    HEMI;           // Executable Module Index

typedef struct ADDR {
    address_t       addr;
    HEMI            emi;
    memmode_t       mode;
} ADDR;             //* An address specifier
typedef ADDR FAR *  PADDR;      //* REVIEW: BUG: shouldn't be explicitly far
typedef ADDR FAR *  LPADDR;

#define addrAddr(a)     ((a).addr)
#define emiAddr(a)      ((a).emi)
#define modeAddr(a)     ((a).mode)

#define AddrInit(paddr,emiSet,segSet,offSet,fFlat,fOff32,fLi,fRealSet)  \
        {                                                               \
            AddrTInit( &(addrAddr(*(paddr))), segSet, offSet );         \
            emiAddr(*(paddr)) = emiSet;                                 \
            ModeInit( &(modeAddr(*(paddr))),fFlat,fOff32,fLi,fRealSet); \
        }

#define ADDR_IS_FLAT(a)     (MODE_IS_FLAT(modeAddr(a)))
#define ADDR_IS_OFF32(a)    (MODE_IS_OFF32(modeAddr(a)))
#define ADDR_IS_LI(a)       (MODE_IS_LI(modeAddr(a)))
#define ADDR_IS_REAL(a)     (MODE_IS_REAL(modeAddr(a)))

#define ADDRSEG16(a)   { ADDR_IS_FLAT(a) = FALSE; ADDR_IS_OFF32(a) = FALSE; }
#define ADDRSEG32(a)   { ADDR_IS_FLAT(a) = FALSE; ADDR_IS_OFF32(a) = TRUE;  }
#define ADDRLIN32(a)   { ADDR_IS_FLAT(a) = TRUE;  ADDR_IS_OFF32(a) = TRUE;  }

#define GetAddrSeg(a)   ((a).addr.seg)
#define GetAddrOff(a)   ((a).addr.off)
#define SetAddrSeg(a,s) ((a)->addr.seg=s)
#define SetAddrOff(a,o) ((a)->addr.off=o)

// Because an ADDR has some filler areas (in the mode and the address_t),
// it's bad to use memcmp two ADDRs to see if they're equal.  Use this
// macro instead.  (I deliberately left out the test for fAddr32(), because
// I think it's probably not necessary when comparing.)
#define FAddrsEq(a1, a2)                        \
    (                                           \
    GetAddrOff(a1) == GetAddrOff(a2) &&         \
    GetAddrSeg(a1) == GetAddrSeg(a2) &&         \
    ADDR_IS_LI(a1) == ADDR_IS_LI(a2) &&         \
    emiAddr(a1)    == emiAddr(a2)               \
    )

//      address definitions
//      the address packet is always a 16:32 address.
#endif // !OSDEBUG4

typedef struct FRAME {
    SEGMENT         SS;
    address_t       BP;
    SEGMENT         DS;
    memmode_t       mode;
    HPID            PID;
    HTID            TID;
    address_t       SLP;    // Static link pointer
} FRAME;
typedef FRAME FAR *PFRAME;      //* REVIEW: BUG: shouldn't be explicitly far

#define addrFrameSS(a)     ((a).SS)
#define addrFrameBP(a)     ((a).BP)
#define GetFrameBPOff(a)   ((a).BP.off)
#define GetFrameBPSeg(a)   ((a).BP.seg)
#define SetFrameBPOff(a,o) ((a).BP.off = o)
#define SetFrameBPSeg(a,s) ((a).BP.seg = s)
#define GetFrameSLPOff(a)   ((a).SLP.off)
#define GetFrameSLPSeg(a)   ((a).SLP.seg)
#define SetFrameSLPOff(a,o) ((a).SLP.off = o)
#define SetFrameSLPSeg(a,s) ((a).SLP.seg = s)
#define FRAMEMODE(a)       ((a).mode)
#define FRAMEPID(a)        ((a).PID)
#define FRAMETID(a)        ((a).TID)

#define FrameFlat(a)       MODE_IS_FLAT((a).mode)
#define FrameOff32(a)      MODE_IS_OFF32((a).mode)
#define FrameReal(a)       MODE_IS_REAL((a).mode)

#if !defined(OSDEBUG4)
/*
** A few public types related to the linked list manager
*/

typedef HDEP        HLLI;       //* A handle to a linked list
typedef HIND        HLLE;       //* A handle to a linked list entry

typedef void (FAR PASCAL * LPFNKILLNODE)( LPV );
typedef int  (FAR PASCAL * LPFNFCMPNODE)( LPV, LPV, LONG );

typedef USHORT      LLF;        //* Linked List Flags
#define llfNull             (LLF)0x0
#define llfAscending        (LLF)0x1
#define llfDescending       (LLF)0x2
#endif // !OSDEBUG4

/*
**  EXPCALL indicates that a function should use whatever calling
**      convention is preferable for exported functions.  Under most
**      operating systems, this is PASCAL, because the name will be
**      left undecorated if it's PASCAL; but under NT and Win32s,
**      PASCAL is #defined to __stdcall, but _cdecl will leave the
**      name (mostly) undecorated.  (It will add an underscore, but
**      that's desirable under Win32s and NT, because you can't
**      export a name that *doesn't* have an underscore.)
**
**      Furthermore, we use _cdecl instead of CDECL because the current
**      NT header files don't necessarily define CDECL to _cdecl.
**
**      EXPCALL does NOT do everything that may be necessary for an
**      exported function (e.g. LOADDS, EXPORT, and FAR), because
**      it's too hard at the moment to clean up all the uses of
**      EXPCALL to use the same modifiers.  Someday.
*/

#if HOST32
    #define EXPCALL     __stdcall
#else
    #define EXPCALL     PASCAL
#endif

typedef struct _MEMINFO {
    ADDR addr;
    ADDR addrAllocBase;
    UOFF32 uRegionSize;
    DWORD dwProtect;
    DWORD dwState;
    DWORD dwType;
} MEMINFO;
typedef MEMINFO FAR * LPMEMINFO;

/*
**      Return values for mtrcEndian -- big or little endian -- which
**      byte is [0] most or least significat byte
*/
enum _END {
    endBig,
    endLittle
};
typedef DWORD END;

enum _MPT {
    mptix86,
    mptm68k,
    mptdaxp,
    mptmips,
    mptmppc,
    mptUnknown
};
typedef DWORD MPT;

#if !defined(OSDEBUG4)
#include <dbgver.h>
#endif // !OSDEBUG4

#endif // CV_PROJECT_BASE_TYPES


DBAPIVER.H

#define DBG_API_VERSION 6
#define DBG_API_SUBVERSION 1


DBGVER.H

/*++

Copyright 1992 - 1998 Microsoft Corporation

Module Name:

    dbgver.h

Abstract:

Environment:

    Win32, User Mode

--*/

#if ! defined _DBGVER_
#define _DBGVER_

/*
**  DBG_API_VERSION is the major version number used to specify the
**      api version of the debugger or debug dll.  For release versions
**      dlls will export this and debuggers will check against this
**      version to verify that it can use the dll.
**
**      For beta and debug versions, this number will be used in
**      conjunction with minor and revision numbers (probably derived
**      from SLM rmm & rup) to verify compatibility.
**
**      Until the API has stabilized, we will most likely have to
**      rev this version number for every major product release.
**
*/

#include <dbapiver.h>

/*  AVS - Api Version Structure:
**
**      All debug dlls should be prepared to return a pointer to this
**      structure conaining its vital statistics.  The debugger should
**      check first two characters of the dll's name against rgchType
**      and the version numbers as described in the DBG_API_VERSION
**      and show the user an error if any of these tests fail.
**
*/

typedef enum {
    rlvtRelease,
    rlvtBeta,
    rlvtDebug
} RLVT;     // ReLease Version Type

typedef struct _AVS {
    CHAR rgchType [ 2 ];    // Component name (EE,EM,TL,SH,DM)
    WORD rlvt;              // ReLease Version Type
    BYTE iApiVer;           // DBG_API_VERSION
    BYTE iApiSubVer;        // DBG_API_SUBVERSION
    WORD iRup;              // Revision number
    WORD chBuild;           // Build of revision # (a,b,c,d)
    LSZ  lszTitle;          // User readable text describing the DLL
    MPT  mpt;               // CPU binary is running on
    WORD iRmj;              // Major version number
    WORD iRmm;              // Minor version number
} AVS;  // Api Version Structure
typedef AVS FAR *LPAVS;

/*  DBGVersionCheck:
**
**      All debug dlls should provide this API and support the return
**      of a pointer to the structure described above even before
**      initialization takes place.
*/

#if defined(_M_IX86)
#define __dbgver_cpu__ mptix86
#elif defined(_M_MRX000)
#define __dbgver_cpu__ mptmips
#elif defined(_M_ALPHA)
#define __dbgver_cpu__ mptdaxp
#elif defined(_M_PPC)
#define __dbgver_cpu__ mptmppc
#else
#error( "unknown target machine" );
#endif

#define DEBUG_VERSION(C1,C2,TITLE) \
AVS Avs = {      \
    { C1, C2 },         \
    rlvtDebug,          \
    DBG_API_VERSION,    \
    DBG_API_SUBVERSION, \
    0,                  \
    '\0',               \
    TITLE,              \
    __dbgver_cpu__,     \
    0,                \
    0,                \
    };

#define RELEASE_VERSION(C1,C2,TITLE)    \
AVS Avs = {      \
    { C1, C2 },         \
    rlvtRelease,        \
    DBG_API_VERSION,    \
    DBG_API_SUBVERSION, \
    0,                  \
    '\0',               \
    TITLE,              \
    __dbgver_cpu__,     \
    0,                \
    0,                \
};

#undef MINOR
#undef MAJOR

typedef LPAVS (LOADDS EXPCALL FAR *DBGVERSIONCHECK)(void);

#define DBGVERSIONPROCNAME "DBGVersionCheck"

typedef LPAVS (*DBGVERSIONPROC)(void);

LPAVS LOADDS EXPCALL FAR DBGVersionCheck( void );

#define DBGVERSIONFUNCTION() \
    LPAVS LOADDS EXPCALL FAR DBGVersionCheck( void ) { return &Avs; }

#endif // _DBGVER_


MIPSINST.H

/*++ BUILD Version: 0001    // Increment this if a change has global effects

Copyright (c) 1993-1997  Microsoft Corporation

Module Name:

    mipsinst.h

Abstract:

    Mips instruction and floating constant definitions.

Author:

    David N. Cutler (davec) 8-May-1992

Revision History:

--*/

#ifndef _MIPSINST_
#define _MIPSINST_

//
// Define MIPS instruction format structures.
//

typedef union _MIPS_INSTRUCTION {
    ULONG Long;
    UCHAR Byte[4];

    struct {
        ULONG Target : 26;
        ULONG Opcode : 6;
    } j_format;

    struct {
        LONG Simmediate : 16;
        ULONG Rt : 5;
        ULONG Rs : 5;
        ULONG Opcode : 6;
    } i_format;

    struct {
        ULONG Uimmediate : 16;
        ULONG Rt : 5;
        ULONG Rs : 5;
        ULONG Opcode : 6;
    } u_format;

    struct {
        ULONG Function : 6;
        ULONG Re : 5;
        ULONG Rd : 5;
        ULONG Rt : 5;
        ULONG Rs : 5;
        ULONG Opcode : 6;
    } r_format;

    struct {
        ULONG Function : 6;
        ULONG Re : 5;
        ULONG Rd : 5;
        ULONG Rt : 5;
        ULONG Format : 4;
        ULONG Fill1 : 1;
        ULONG Opcode : 6;
    } f_format;

    struct {
        ULONG Function : 6;
        ULONG Fd : 5;
        ULONG Fs : 5;
        ULONG Ft : 5;
        ULONG Format : 4;
        ULONG Fill1 : 1;
        ULONG Opcode : 6;
    } c_format;

} MIPS_INSTRUCTION, *PMIPS_INSTRUCTION;

//
// Define MIPS instruction opcode values.
//

#define SPEC_OP 0x0                     // special opcode - use function field
#define BCOND_OP 0x1                    // condition branch
#define J_OP 0x2                        // unconditional jump
#define JAL_OP 0x3                      // jump and link

#define BEQ_OP 0x4                      // branch equal
#define BNE_OP 0x5                      // branch not equal
#define BLEZ_OP 0x6                     // branch less than or equal
#define BGTZ_OP 0x7                     // branch greater than

#define ADDI_OP 0x8                     // add immediate signed integer
#define ADDIU_OP 0x9                    // add immediate unsigned integer
#define SLTI_OP 0xa                     // set less than signed integer
#define SLTIU_OP 0xb                    // set less than unsigned integer

#define ANDI_OP 0xc                     // and unsigned immediate integer
#define ORI_OP 0xd                      // or unsigned immediate integer
#define XORI_OP 0xe                     // exclusive or unsigned immediate
#define LUI_OP  0xf                     // load upper immediate integer

#define COP0_OP 0x10                    // coprocessor 0 operation
#define COP1_OP 0x11                    // coprocessor 1 operation

#define BEQL_OP 0x14                    // branch equal likely
#define BNEL_OP 0x15                    // branch not equal likely
#define BLEZL_OP 0x16                   // branch less than or equal likely
#define BGTZL_OP 0x17                   // branch greater than likely

#define LDL_OP 0x1a                     // load double left integer
#define LDR_OP 0x1b                     // load double right integer

#define LB_OP 0x20                      // load byte signed integer
#define LH_OP 0x21                      // load halfword signed integer
#define LWL_OP 0x22                     // load word left integer
#define LW_OP 0x23                      // load word integer

#define LBU_OP 0x24                     // load byte unsigned integer
#define LHU_OP 0x25                     // load halfword unsigned integer
#define LWR_OP 0x26                     // load word right integer
#define LWU_OP 0x27                     // load word unsigned integer

#define SB_OP 0x28                      // store byte integer
#define SH_OP 0x29                      // store halfword integer
#define SWL_OP 0x2a                     // store word left integer
#define SW_OP 0x2b                      // store word integer register

#define SDL_OP 0x2c                     // store double left integer
#define SDR_OP 0x2d                     // store double right integer
#define SWR_OP 0x2e                     // store word right integer
#define CACHE_OP 0x2f                   // cache operation

#define LL_OP 0x30                      // load linked integer register
#define LWC1_OP 0x31                    // load word floating
#define LWC2_OP 0x32                    // load word coprocessor 2

#define LLD_OP 0x34                     // load locked double integer
#define LDC1_OP 0x35                    // load word double floating
#define LDC2_OP 0x36                    // load double coprocessor 2
#define LD_OP 0x37                      // load double integer

#define SC_OP 0x38                      // store conditional word integer
#define SWC1_OP 0x39                    // store word floating
#define SWC2_OP 0x3a                    // store double coprocessor 2

#define SDC_OP 0x3c                     // store conditional double integer
#define SDC1_OP 0x3d                    // store double floating
#define SDC2_OP 0x3e                    // store double copreocessor 2
#define SD_OP 0x3f                      // store double integer register

//
// Define special function subopcodes.
//

#define SLL_OP 0x0                      // shift left logical integer
#define SRL_OP 0x2                      // shift right logical integer
#define SRA_OP 0x3                      // shift right arithmetic integer

#define SLLV_OP 0x4                     // shift left logical variable integer
#define SRLV_OP 0x6                     // shift right logical variable integer
#define SRAV_OP 0x7                     // shift right arithmetic variable integer

#define JR_OP 0x8                       // jump register
#define JALR_OP 0x9                     // jump and link register

#define SYSCALL_OP 0xc                  // system call trap
#define BREAK_OP 0xd                    // breakpoint trap

#define MFHI_OP 0x10                    // more from high integer
#define MTHI_OP 0x11                    // move to high integer
#define MFLO_OP 0x12                    // move from low integer
#define MTLO_OP 0x13                    // move to low integer

#define MULT_OP 0x18                    // multiply signed integer
#define MULTU_OP 0x19                   // multiply unsigned integer
#define DIV_OP 0x1a                     // divide signed integer
#define DIVU_OP 0x1b                    // divide unsigned integer

#define ADD_OP 0x20                     // add signed integer
#define ADDU_OP 0x21                    // add unsigned integer
#define SUP_OP 0x22                     // subtract signed integer
#define SUBU_OP 0x23                    // subtract unsigned integer

#define AND_OP 0x24                     // and integer
#define OR_OP 0x25                      // or integer
#define XOR_OP 0x26                     // exclusive or integer
#define NOR_OP 0x27                     // nor integer

#define SLT_OP 0x2a                     // set less signed integer
#define SLTU_OP 0x2b                    // set less unsigned integer

//
// Define branch conditional subopcodes.
//

#define BLTZ_OP 0x0                     // branch less that zero integer
#define BGEZ_OP 0x1                     // branch greater than or equal zero integer
#define BLTZL_OP 0x2                    // branch less that zero integer liekly
#define BGEZL_OP 0x3                    // branch greater than or equal zero integer likely

#define BLTZAL_OP 0x10                  // branch less than zero integer and link
#define BGEZAL_OP 0x11                  // branch greater than or equal zero integer and link
#define BLTZALL_OP 0x12                 // branch less than zero integer and link likely
#define BGEZALL_OP 0x13                 // branch greater than or equal zero integer and link likely

//
// Coprocessor branch true and false subfunctions and mask values.
//

#define COPz_BC_MASK 0x3e10000          // coprocessor z branch condition mask
#define COPz_BF 0x1000000               // coprocessor z branch false subfunction
#define COPz_BT 0x1010000               // coprocessor z branch true subfunction

//
// Define floating coprocessor 1 opcodes.
//

#define FLOAT_ADD 0                     // floating add
#define FLOAT_SUBTRACT 1                // floating subtract
#define FLOAT_MULTIPLY 2                // floating multiply
#define FLOAT_DIVIDE 3                  // floating divide
#define FLOAT_SQUARE_ROOT 4             // floating square root
#define FLOAT_ABSOLUTE 5                // floating absolute value
#define FLOAT_MOVE 6                    // floating move
#define FLOAT_NEGATE 7                  // floating negate

#define FLOAT_ROUND_QUADWORD 8          // floating round to longword
#define FLOAT_TRUNC_QUADWORD 9          // floating truncate to longword
#define FLOAT_CEIL_QUADWORD 10          // floating ceiling
#define FLOAT_FLOOR_QUADWORD 11         // floating floor

#define FLOAT_ROUND_LONGWORD 12         // floating round to longword
#define FLOAT_TRUNC_LONGWORD 13         // floating truncate to longword
#define FLOAT_CEIL_LONGWORD 14          // floating ceiling
#define FLOAT_FLOOR_LONGWORD 15         // floating floor

#define FLOAT_ILLEGAL 16                // illegal floating opcode

#define FLOAT_COMPARE_SINGLE 17         // floating compare single
#define FLOAT_COMPARE_DOUBLE 18         // floating compare double

#define FLOAT_CONVERT_SINGLE 32         // floating convert to single
#define FLOAT_CONVERT_DOUBLE 33         // floating convert to double

#define FLOAT_CONVERT_LONGWORD 36       // floating convert to longword integer
#define FLOAT_CONVERT_QUADWORD 37       // floating convert to quadword integer

#define FLOAT_COMPARE 48                // starting floating compare code

//
// Define floating format values.
//

#define FORMAT_SINGLE 0                 // single floating format
#define FORMAT_DOUBLE 1                 // double floating format
#define FORMAT_LONGWORD 4               // longword integer format
#define FORMAT_QUADWORD 5               // quadword integer format

//
// Define jump indirect return address register.
//

#define JUMP_RA 0x3e00008               // jump indirect return address

//
// Define maximum and minimum single and double exponent values.
//

#define DOUBLE_MAXIMUM_EXPONENT 2047
#define DOUBLE_MINIMUM_EXPONENT 0
#define SINGLE_MAXIMUM_EXPONENT 255
#define SINGLE_MINIMUM_EXPONENT 0

//
// Define single and double exponent bias values.
//

#define SINGLE_EXPONENT_BIAS 127
#define DOUBLE_EXPONENT_BIAS 1023

//
// Define the largest single and double values;
//

#define DOUBLE_MAXIMUM_VALUE 0x7fefffffffffffff
#define DOUBLE_MAXIMUM_VALUE_LOW 0xffffffff
#define DOUBLE_MAXIMUM_VALUE_HIGH 0x7fefffff
#define SINGLE_MAXIMUM_VALUE 0x7f7fffff

//
// Define single and double quite and signaling Nan values.
//

#define DOUBLE_NAN_LOW 0xffffffff
#define DOUBLE_QUIET_NAN 0x7ff7ffff
#define DOUBLE_SIGNAL_NAN 0x7fffffff
#define SINGLE_QUIET_NAN 0x7fbfffff
#define SINGLE_SIGNAL_NAN 0x7fffffff
#define DOUBLE_INTEGER_NAN 0x7fffffffffffffff
#define SINGLE_INTEGER_NAN 0x7fffffff

//
// Define positive single and double infinity values.
//

#define DOUBLE_INFINITY_VALUE 0x7ff0000000000000
#define DOUBLE_INFINITY_VALUE_LOW 0x0
#define DOUBLE_INFINITY_VALUE_HIGH 0x7ff00000
#define SINGLE_INFINITY_VALUE 0x7f800000

//
// Define rounding modes.
//

#define ROUND_TO_NEAREST 0              // round to nearest representable value
#define ROUND_TO_ZERO 1                 // round toward zero
#define ROUND_TO_PLUS_INFINITY 2        // round toward plus infinity
#define ROUND_TO_MINUS_INFINITY 3       // round toward minus infinity

#endif // MIPSINST


PPCINST.H

/*++

Copyright (c) 1993  IBM Corporation

Module Name:

    ppcinst.h

Abstract:

    PowerPC instruction and floating constant definitions.

Author:

    Rick Simpson   30 July 1993

Revision History:

--*/

#ifndef _PPCINST_
#define _PPCINST_

//
//     POWERPC INSTRUCTION FORMAT STRUCTURES
//

typedef union _PPC_INSTRUCTION {
    ULONG Long;
    UCHAR Byte[4];

    struct {
        ULONG bit31   :  1;
        ULONG bit30   :  1;
 LONG bit6_29 : 24;
ULONG bit0_5  :  6;
    } i_f;

#define Primary_Op  i_f.bit0_5

#define Iform_LI  i_f.bit6_29
#define Iform_AA  i_f.bit30
#define Iform_LK  i_f.bit31

    struct {
        ULONG bit31    :  1;
        ULONG bit30    :  1;
 LONG bit16_29 : 14;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
        ULONG bit0_5   :  6;
    } b_f;

#define Bform_BO  b_f.bit6_10
#define Bform_BI  b_f.bit11_15
#define Bform_BD  b_f.bit16_29
#define Bform_AA  b_f.bit30
#define Bform_LK  b_f.bit31

    struct {
ULONG bit31    :  1;
ULONG bit30    :  1;
ULONG bit6_29  : 24;
ULONG bit0_5   :  6;
    } sc_f;

#define SCform_XO  sc_f.bit30

    struct {
 LONG bit16_31 : 16;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } d_f1;
    struct {
ULONG bit16_31 : 16;
ULONG bit11_15 :  5;
ULONG bit10    :  1;
ULONG bit9     :  1;
ULONG bit6_8   :  3;
ULONG bit0_5   :  6;
    } d_f2;

#define Dform_RT   d_f1.bit6_10
#define Dform_RS   d_f1.bit6_10
#define Dform_TO   d_f1.bit6_10
#define Dform_FRT  d_f1.bit6_10
#define Dform_FRS  d_f1.bit6_10
#define Dform_BF   d_f2.bit6_8
#define Dform_L    d_f2.bit10
#define Dform_RA   d_f1.bit11_15
#define Dform_D    d_f1.bit16_31
#define Dform_SI   d_f1.bit16_31
#define Dform_UI   d_f2.bit16_31

    struct {
ULONG bit30_31 :  2;
 LONG bit16_29 : 14;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } ds_f;

#define DSform_RT  ds_f.bit6_10
#define DSform_RS  ds_f.bit6_10
#define DSform_RA  ds_f.bit11_15
#define DSform_DS  ds_f.bit16_29
#define DSform_XO  ds_f.bit30_31

    struct {
ULONG bit31    :  1;
ULONG bit21_30 : 10;
ULONG bit16_20 :  5;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } x_f1;
    struct {
ULONG bit20_31 : 12;
ULONG bit16_19 :  4;
ULONG bit14_15 :  2;
ULONG bit11_13 :  3;
ULONG bit10    :  1;
ULONG bit9     :  1;
ULONG bit6_8   :  3;
ULONG bit0_5   :  6;
    } x_f2;
    struct {
ULONG bit16_31 : 16;
ULONG bit12_15 :  4;
ULONG bit0_11  : 12;
    } x_f3;

#define Xform_RT   x_f1.bit6_10
#define Xform_RS   x_f1.bit6_10
#define Xform_TO   x_f1.bit6_10
#define Xform_FRT  x_f1.bit6_10
#define Xform_FRS  x_f1.bit6_10
#define Xform_BT   x_f1.bit6_10
#define Xform_BF   x_f2.bit6_8
#define Xform_L    x_f2.bit10
#define Xform_RA   x_f1.bit11_15
#define Xform_FRA  x_f1.bit11_15
#define Xform_BFA  x_f2.bit11_13
#define Xform_SR   x_f3.bit12_15
#define Xform_RB   x_f1.bit16_20
#define Xform_NB   x_f1.bit16_20
#define Xform_SH   x_f1.bit16_20
#define Xform_FRB  x_f1.bit16_20
#define Xform_U    x_f2.bit16_19
#define Xform_XO   x_f1.bit21_30
#define Xform_RC   x_f1.bit31

    struct {
ULONG bit31    :  1;
ULONG bit21_30 : 10;
ULONG bit16_20 :  5;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } xl_f1;
    struct {
ULONG bit14_31 : 18;
ULONG bit11_13 :  3;
ULONG bit9_10  :  2;
ULONG bit6_8   :  3;
ULONG bit0_5   :  6;
    } xl_f2;

#define XLform_LK   xl_f1.bit31
#define XLform_XO   xl_f1.bit21_30
#define XLform_BB   xl_f1.bit16_20
#define XLform_BA   xl_f1.bit11_15
#define XLform_BI   xl_f1.bit11_15
#define XLform_BFA  xl_f2.bit11_13
#define XLform_BT   xl_f1.bit6_10
#define XLform_BO   xl_f1.bit6_10
#define XLform_BF   xl_f2.bit6_8

    struct {
ULONG bit31    :  1;
ULONG bit21_30 : 10;
ULONG bit11_20 : 10;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } xfx_f1;
    struct {
ULONG bit20_31 : 12;
ULONG bit12_19 :  8;
ULONG bit6_11  :  6;
ULONG bit0_5   :  6;
    } xfx_f2;

#define XFXform_RT  xfx_f1.bit6_10
#define XFXform_RS  xfx_f1.bit6_10
#define XFXform_spr xfx_f1.bit11_20
#define XFXform_tbr xfx_f1.bit11_20
#define XFXform_spr xfx_f1.bit11_20
#define XFXform_FXM xfx_f2.bit12_19
#define XFXform_XO  xfx_f1.bit21_30

    struct {
ULONG bit31    :  1;
ULONG bit21_30 : 10;
ULONG bit16_20 :  5;
ULONG bit15    :  1;
ULONG bit7_14  :  8;
ULONG bit6     :  1;
ULONG bit0_5   :  6;
    } xfl_f;

#define XFLform_FLM xfl_f.bit7_14
#define XFLform_FRB xfl_f.bit16_20
#define XFLform_XO  xfl_f.bit21_30
#define XFLform_RC  xfl_f.bit31

    struct {
ULONG bit31    :  1;
ULONG bit30    :  1;
ULONG bit21_29 :  9;
ULONG bit16_20 :  5;
ULONG bit10_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } xs_f;

#define XSform_RS  xs_f.bit6_10
#define XSform_RA  xs_f.bit11_15
#define XSform_sh1 xs_f.bit16_20
#define XSform_XO  xs_f.bit21_29
#define XSform_sh2 xs_f.bit30
#define XSform_RC  xs_f.bit31

    struct {
ULONG bit31    :  1;
ULONG bit22_30 :  9;
ULONG bit21    :  1;
ULONG bit16_20 :  5;
ULONG bit10_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } xo_f;

#define XOform_RT  xo_f.bit6_10
#define XOform_RA  xo_f.bit11_15
#define XOform_RB  xo_f.bit16_20
#define XOform_OE  xo_f.bit21
#define XOform_XO  xo_f.bit22_30
#define XOform_RC  xo_f.bit31

    struct {
ULONG bit31    :  1;
ULONG bit26_30 :  5;
ULONG bit21_25 :  5;
ULONG bit16_20 :  5;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } a_f;

#define Aform_FRT  a_f.bit6_10
#define Aform_FRA  a_f.bit11_15
#define Aform_FRB  a_f.bit16_20
#define Aform_FRC  a_f.bit21_25
#define Aform_XO   a_f.bit26_30
#define Aform_RC   a_f.bit31

    struct {
ULONG bit31    :  1;
ULONG bit26_30 :  5;
ULONG bit21_25 :  5;
ULONG bit16_20 :  5;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } m_f;

#define Mform_RS  m_f.bit6_10
#define Mform_RA  m_f.bit11_15
#define Mform_RB  m_f.bit16_20
#define Mform_SH  m_f.bit16_20
#define Mform_MB  m_f.bit21_25
#define Mform_ME  m_f.bit26_30
#define Mform_RC  m_f.bit31

    struct {
ULONG bit31    :  1;
ULONG bit30    :  1;
ULONG bit27_29 :  3;
ULONG bit21_26 :  6;
ULONG bit16_20 :  5;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } md_f;

#define MDform_RS   md_f.bit6_10
#define MDform_RA   md_f.bit11_15
#define MDform_sh1  md_f.bit16_20
#define MDform_mb   md_f.bit21_26
#define MDform_me   md_f.bit21_26
#define MDform_XO   md_f.bit27_29
#define MDform_sh2  md_f.bit30
#define MDform_RC   md_f.bit31

    struct {
ULONG bit31    :  1;
ULONG bit27_30 :  4;
ULONG bit21_26 :  6;
ULONG bit16_20 :  5;
ULONG bit11_15 :  5;
ULONG bit6_10  :  5;
ULONG bit0_5   :  6;
    } mds_f;

#define MDSform_RS  mds_f.bit6_10
#define MDSform_RA  mds_f.bit11_15
#define MDSform_RB  mds_f.bit16_20
#define MDSform_mb  mds_f.bit21_26
#define MDSform_me  mds_f.bit21_26
#define MDSform_XO  mds_f.bit27_30
#define MDSform_RC  mds_f.bit31

} PPC_INSTRUCTION, *PPPC_INSTRUCTION;

//
//     POWERPC PRIMARY AND SECONDARY (EXTENDED) OPCODES
//

#define TDI_OP       2
#define TWI_OP       3
#define MULLI_OP     7
#define SUBFIC_OP    8
#define CMPLI_OP    10
#define CMPI_OP     11
#define ADDIC_OP    12
#define ADDIC_RC_OP 13
#define ADDI_OP     14
#define ADDIS_OP    15
#define BC_OP       16
#define SC_OP       17
#define B_OP        18

#define X19_OP      19    // Extended ops for primary code 19:
#define   MCRF_OP       0
#define   BCLR_OP      16
#define   CRNOR_OP     33
#define   RFI_OP       50
#define   CRANDC_OP   129
#define   ISYNC_OP    150
#define   CRXOR_OP    193
#define  CRNAND_OP   225
#define  CRAND_OP    257
#define  CREQV_OP    289
#define  CRORC_OP    417
#define  CROR_OP     449
#define  BCCTR_OP    528

#define RLWIMI_OP   20
#define RLWINM_OP   21
#define RLWNM_OP    23
#define ORI_OP      24
#defineORIS_OP    25
#define XORI_OP    26
#defineXORIS_OP    27
#defineANDI_RC_OP  28
#define ANDIS_RC_OP 29

#define X30_OP    30    // Extended ops for primary code 30:
#define   RLDICL_OP     0
#define   RLDICR_OP1
#define  RLDIC_OP2
#define  RLDIMI_OP3
#define   RLDCL_OP8
#define   RLDCR_OP9

#define X31_OP      31    // Extended ops for primary code 31:
#define   CMP_OP0
#define  TW_OP4
#define  SUBFC_OP8
#define  MULHDU_OP9
#define  ADDC_OP      10
#define  MULHWU_OP    11
#define  MFCR_OP      19
#define  LWARX_OP     20
#define  LDX_OP       21
#define  LWZX_OP      23
#define  SLW_OP       24
#define  CNTLZW_OP    26
#define  SLD_OP       27
#define  AND_OP       28
#define  CMPL_OP      32
#define  SUBF_OP      40
#define  LDUX_OP      53
#define  DCBST_OP     54
#define  LWZUX_OP     55
#define  CNTLZD_OP    58
#define  ANDC_OP      60
#define  TD_OP       68
#define  MULHD_OP     73
#define  MULHW_OP     75
#define  MFMSR_OP     83
#define  LDARX_OP     84
#define  DCBF_OP      86
#define  LBZX_OP      87
#define  NEG_OP      104
#define  LBZUX_OP    119
#define  NOR_OP      124
#define  SUBFE_OP    136
#define  ADDE_OP     138
#define  MTCRF_OP    144
#define  MTMSR_OP    146
#define  STDX_OP     149
#define  STWCX_RC_OP 150
#define  STWX_OP     151
#define  STDUX_OP    181
#define  STWUX_OP    183
#define  SUBFZE_OP   200
#define  ADDZE_OP    202
#define  MTSR_OP     210
#define  STDCX_RC_OP 214
#define  STBX_OP     215
#define  SUBFME_OP   232
#define  MULLD_OP    233
#define  ADDME_OP    234
#define  MULLW_OP    235
#define  MTSRIN_OP   242
#define  DCBTST_OP   246
#define  STBUX_OP    247
#define  ADD_OP      266
#define  DCBT_OP     278
#define  LHZX_OP     279
#define  EQV_OP      284
#define  TLBIE_OP    306
#define  ECIWX_OP    310
#define  LHZUX_OP    311
#define  XOR_OP      316
#define  MFSPR_OP    339
#define  LWAX_OP     341
#define  LHAX_OP     343
#define  TLBIA_OP    370
#define  MFTB_OP     371
#define  LWAUX_OP    373
#define  LHAUX_OP    375
#define  STHX_OP     407
#define  ORC_OP      412
#define  SRADI_OP    413
#define  SLBIE_OP    434
#define  ECOWX_OP    438
#define  STHUX_OP    439
#define  OR_OP      444
#define  DIVDU_OP    457
#define  DIVWU_OP    459
#define  MTSPR_OP    467
#define  DCBI_OP     470
#define  NAND_OP     476
#define  DIVD_OP     489
#define  DIVW_OP     491
#define  SLBIA_OP    498
#define  MCRXR_OP    512
#define  LSWX_OP     533
#define  LWBRX_OP    534
#define  LFSX_OP     535
#define  SRW_OP      536
#define  SRD_OP      539
#define  TLBSYNC_OP  566
#define  LFSUX_OP    567
#define  MFSR_OP     595
#define  LSWI_OP     597
#define  SYNC_OP     598
#define  LFDX_OP     599
#define  LFDUX_OP    631
#define  MFSRIN_OP   659
#define  STSWX_OP    661
#define  STWBRX_OP   662
#define  STFSX_OP    663
#define  STFSUX_OP   695
#define  STSWI_OP    725
#define  STFDX_OP    727
#define  STFDUX_OP   759
#define  LHBRX_OP    790
#define  SRAW_OP     792
#define  SRAD_OP     794
#define  SRAWI_OP    824
#define  EIEIO_OP    854
#define  STHBRX_OP   918
#define  EXTSH_OP    922
#define  EXTSB_OP    954
#define  ICBI_OP     982
#define  STFIWX_OP   983
#define  EXTSW_OP    986
#define  DCBZ_OP    1014

#define LWZ_OP      32
#define LWZU_OP    33
#defineLBZ_OP    34
#defineLBZU_OP    35
#defineSTW_OP    36
#defineSTWU_OP    37
#defineSTB_OP    38
#defineSTBU_OP    39
#defineLHZ_OP    40
#defineLHZU_OP    41
#defineLHA_OP    42
#defineLHAU_OP    43
#defineSTH_OP    44
#defineSTHU_OP    45
#defineLFS_OP    48
#defineLFSU_OP    49
#defineLFD_OP    50
#defineLFDU_OP    51
#define STFS_OP    52
#defineSTFSU_OP    53
#defineSTFD_OP    54
#defineSTFDU_OP    55

#define X58_OP      58    // Extended ops for primary code 58:
#define   LD_OP        0
#define  LDU_OP1
#define  LWA_OP2

#define X59_OP      59    // Extended ops for primary code 59:
#define  FDIVS_OP     18
#define  FSUBS_OP     20
#define  FADDS_OP     21
#define  FSQRTS_OP    22
#define  FRES_OP      24
#define  FMULS_OP     25
#define  FMSUBS_OP    28
#define   FMADDS_OP    29
#define  FNMSUBS_OP   30
#define  FNMADDS_OP   31

#define X62_OP      62    // Extended ops for primary code 62:
#define   STD_OP        0
#define   STDU_OP       1

#define X63_OP    63  // Extended ops for primary code 63:
#define   FCMPU_OP      0
#define  FRSP_OP      12
#define  FCTIW_OP     14
#define  FCTIWZ_OP    15
#define  FDIV_OP      18
#define  FSUB_OP      20
#define  FADD_OP      21
#define  FSQRT_OP     22
#define  FSEL_OP      23
#define  FMUL_OP      25
#define  FSQRTE_OP    26
#define  FMSUB_OP     28
#define   FMADD_OP     29
#define  FNMSUB_OP    30
#define  FNMADD_OP    31
#define  FCMPO_OP     32
#define  MTFSB1_OP    38
#define  FNEG_OP      40
#define  MCRFS_OP     64
#define  MTFSB0_OP    70
#define  FMR_OP       72
#define  MTFSFI_OP   134
#define  FNABS_OP    136
#define  FABS_OP     264
#define  MFFS_OP     583
#define  MTFSF_OP    711
#define  FCTID_OP    814
#define  FCTIDZ_OP   815
#define  FCFID_OP    846

//
// Define certain specific instructions
//

#define SYSTEM_CALL_INSTR  0x44000002L  // sc
#define RETURN_INSTR       0x4E800020L  // blr
#define BREAK_INSTR        0x0FE00000L  // break <number>
#define TRAP_INSTR         0x0C000000L  // trap word immediate
#define NO_OP_INSTR       0x60000000L  // ori r.0, r.0, 0
#define INVALID_INSTR   0x00000000L  // all 0's => invalid

//
// Define specific values for the TO field of the TRAP_INSTR
//

#define TO_BREAKPOINT                   0x03E00000L  // Unconditional breakpoint
#define TO_DIVIDE_BY_ZERO               0x00C00000L  // Integer divide by zero
#define TO_UNCONDITIONAL_DIVIDE_BY_ZERO 0x00E00000L  // Unconditional divide
                                                     // by zero

//
//     IEEE FLOATING POINT VALUE REPRESENTATION
//

//
// Define maximum and minimum single and double exponent values.
//

#define DOUBLE_MAXIMUM_EXPONENT  2047
#define DOUBLE_MINIMUM_EXPONENT  0
#define SINGLE_MAXIMUM_EXPONENT  255
#define SINGLE_MINIMUM_EXPONENT  0

//
// Define single and double exponent bias values.
//

#define SINGLE_EXPONENT_BIAS  127
#define DOUBLE_EXPONENT_BIAS  1023

//
// Define the largest single and double values;
//

#define DOUBLE_MAXIMUM_VALUE_LOW   0xffffffff
#define DOUBLE_MAXIMUM_VALUE_HIGH  0x7fefffff
#define SINGLE_MAXIMUM_VALUE       0x7f7fffff

//
// Define single and double quite and signaling Nan values.
//

#define DOUBLE_NAN_LOW     0xffffffff
#define DOUBLE_QUIET_NAN   0x7ff7ffff
#define DOUBLE_SIGNAL_NAN  0x7fffffff
#define SINGLE_QUIET_NAN   0x7fbfffff
#define SINGLE_SIGNAL_NAN  0x7fffffff

//
// Define positive single and double infinity values.
//

#define DOUBLE_INFINITY_VALUE_LOW   0x0
#define DOUBLE_INFINITY_VALUE_HIGH  0x7ff00000
#define SINGLE_INFINITY_VALUE       0x7f800000

//
// Define rounding modes.
//

#define ROUND_TO_NEAREST         0
#define ROUND_TO_ZERO            1
#define ROUND_TO_PLUS_INFINITY   2
#define ROUND_TO_MINUS_INFINITY  3

#endif // _PPCINST_


SYMCVT.H

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    symcvt.h

Abstract:

    This file contains all of the type definitions and prototypes
    necessary to access the symcvt library.

Environment:

    Win32, User Mode

--*/

typedef struct tagPTRINFO {
    DWORD                       size;
    DWORD                       count;
    PUCHAR                      ptr;
} PTRINFO, *PPTRINFO;

typedef struct tagIMAGEPOINTERS {
    char                        szName[MAX_PATH];
    HANDLE                      hFile;
    HANDLE                      hMap;
    DWORD                       fsize;
    PUCHAR                      fptr;
    BOOLEAN                     fRomImage;
    PIMAGE_DOS_HEADER           dosHdr;
    PIMAGE_NT_HEADERS           ntHdr;
    PIMAGE_ROM_HEADERS          romHdr;
    PIMAGE_FILE_HEADER          fileHdr;
    PIMAGE_OPTIONAL_HEADER      optHdr;
    PIMAGE_SEPARATE_DEBUG_HEADER sepHdr;
    int                         cDebugDir;
    PIMAGE_DEBUG_DIRECTORY *    rgDebugDir;
    PIMAGE_SECTION_HEADER       sectionHdrs;
    PIMAGE_SECTION_HEADER       debugSection;
    PIMAGE_SYMBOL               AllSymbols;
    PUCHAR                      stringTable;
    int                         numberOfSymbols;
    int                         numberOfSections;
    PCHAR *                     rgpbDebugSave;
} IMAGEPOINTERS, *PIMAGEPOINTERS;

#define COFF_DIR(x)             ((x)->rgDebugDir[IMAGE_DEBUG_TYPE_COFF])
#define CV_DIR(x)               ((x)->rgDebugDir[IMAGE_DEBUG_TYPE_CODEVIEW])

typedef struct _MODULEINFO {
    DWORD               iMod;
    DWORD               cb;
    DWORD               SrcModule;
    LPSTR               name;
} MODULEINFO, *LPMODULEINFO;

typedef struct tagPOINTERS {
    IMAGEPOINTERS               iptrs;         // input file pointers
    IMAGEPOINTERS               optrs;         // output file pointers
    PTRINFO                     pCvStart;      // start of cv info
    PUCHAR                      pCvCurr;       // current cv pointer
    PTRINFO                     pCvModules;    // module information
    PTRINFO                     pCvSrcModules; // source module information
    PTRINFO                     pCvPublics;    // publics information
    PTRINFO                     pCvSegName;    // segment names
    PTRINFO                     pCvSegMap;     // segment map
    PTRINFO                     pCvSymHash;    // symbol hash table
    PTRINFO                     pCvAddrSort;   // address sort table
    LPMODULEINFO                pMi;
    DWORD                       modcnt;
} POINTERS, *PPOINTERS;

typedef  char *  (* CONVERTPROC) (HANDLE, char *);

#define align(_n)       ((4 - (( (DWORD)_n ) % 4 )) & 3)

#ifdef _SYMCVT_SOURCE_
#define SYMCVTAPI
#else
#define SYMCVTAPI DECLSPEC_IMPORT
#endif

PUCHAR  SYMCVTAPI ConvertSymbolsForImage( HANDLE, char * );
BOOL    SYMCVTAPI ConvertCoffToCv( PPOINTERS p );
BOOL    SYMCVTAPI ConvertSymToCv( PPOINTERS p );
BOOL    SYMCVTAPI MapInputFile ( PPOINTERS p, HANDLE hFile, char *fname);
BOOL    SYMCVTAPI UnMapInputFile ( PPOINTERS p );
BOOL    SYMCVTAPI CalculateNtImagePointers( PIMAGEPOINTERS p );


TYPES.H

/** types.h - Generic types
 *
 *      This file contains generic types such as USHORT, ushort,
 *      WORD, etc., which are not directly related to CodeView.
 *      Every attempt is made to define them in such a way as they
 *      will not conflict with the standard header files such as
 *      windows.h and os2.h.
 */

#ifndef CVINC_TYPES /* whole file */
#define CVINC_TYPES

#ifdef HOST32   /* { */

#define _export

#ifndef LOADDS
#define LOADDS
#endif

#ifndef PASCAL
#define PASCAL __stdcall
#endif

#ifndef CDECL
#define CDECL _cdecl
#endif

#ifndef FASTCALL
#define FASTCALL __fastcall
#endif

#ifndef far
#define far
#endif

#ifndef FAR
#define FAR far
#endif

#ifndef near
#define near
#endif

#ifndef NEAR
#define NEAR near
#endif

#ifndef _HUGE_
#define _HUGE_
#endif

// Use SEGBASED for based on a segment [e.g. SEGBASED(__segname("_CODE"))]
#ifndef SEGBASED
#define SEGBASED(x)
#endif

/*
**  This set of functions need to be expanded to deal with
**  unicode and other problems.
*/

// These #defines are taken care of by windowsx.h

#ifndef _INC_WINDOWSX

#define _ncalloc    calloc
#define _nexpand    expand
#define _ffree      free
#define _fmalloc    malloc
#define _fmemccpy   _memccpy
#define _fmemchr    memchr
#define _fmemcmp    memcmp
#define _fmemcpy    memcpy
#define _fmemicmp   _memicmp
#define _fmemmove   memmove
#define _fmemset    memset
#define _fmsize     _msize
#define _frealloc   realloc
#define _fstrcat    strcat
#define _fstrchr    strchr
#define _fstrcmp    strcmp
#define _fstrcpy    strcpy
#define _fstrcspn   strcspn
#define _fstrdup    _strdup
#define _fstricmp   _stricmp
#define _fstrlen    strlen
#define _fstrlwr    _strlwr
#define _fstrncat   strncat
#define _fstrncmp   strncmp
#define _fstrncpy   strncpy
#define _fstrnicmp  _strnicmp
#define _fstrnset   _strnset
#define _fstrpbrk   strpbrk
#define _fstrrchr   strrchr
#define _fstrrev    _strrev
#define _fstrset    _strset
#define _fstrspn    strspn
#define _fstrstr    strstr
#define _fstrtok    strtok
#define _fstrupr    _strupr
#define _nfree      free
#define _nmalloc    malloc
#define _nmsize     msize
#define _nrealloc   realloc
#define _nstrdup    _strdup
#define hmemcpy     memcpy

#endif

#define FP_OFF(x) x

#else   // !HOST32 }{

#ifndef LOADDS
#define LOADDS _loadds
#endif

#ifndef PASCAL
#define PASCAL _pascal
#endif

#ifndef CDECL
#define CDECL _cdecl
#endif

#ifndef FASTCALL
#define FASTCALL _fastcall
#endif

#ifndef FAR
#define FAR _far
#endif

#ifndef NEAR
#define NEAR _near
#endif

#ifndef _HUGE_
#define _HUGE_ _huge
#endif

// Use SEGBASED for based on a segment [e.g. SEGBASED(__segname("_CODE"))]
#ifndef SEGBASED
#define SEGBASED(x) _based(x)
#endif

#endif  // HOST32 }

#ifndef INTERRUPT
#define INTERRUPT _interrupt
#endif

#ifndef LOCAL
#ifdef DEBUGVER
#define LOCAL
#else
#define LOCAL static
#endif
#endif

#ifndef GLOBAL
#define GLOBAL
#endif

#ifndef INLINE
#define INLINE __inline
#endif

//
// Things that come from either windows.h or os2.h
//

#if !defined(LOWORD) && !defined(OS2_INCLUDED)

    #define VOID            void
    typedef unsigned char   BYTE;
    typedef int             BOOL;
    #define LONG            long

#endif

//
// Things that come from windows.h and cwindows.h
//

#if !defined(LOWORD)

#if defined ( _WIN32 ) || defined ( _M_MPPC )
    typedef void *          HANDLE;
#else
    typedef unsigned int    HANDLE;
#endif

    typedef HANDLE          HWND;
    typedef char FAR *      LPSTR;

    typedef unsigned short  WORD;
    typedef unsigned long   DWORD;

    #define WNDPROC         FARPROC

#endif

#if !defined ( WIN32 ) && !defined ( WIN32S ) && !defined ( WIN )

    typedef unsigned long       DWORD;
    typedef int                 BOOL;
    typedef unsigned char       BYTE;
    typedef unsigned short      WORD;
    typedef float               FLOAT;
    typedef FLOAT               *PFLOAT;
    typedef BOOL near           *PBOOL;
    typedef BOOL far            *LPBOOL;
    typedef BYTE near           *PBYTE;
    typedef BYTE far            *LPBYTE;
    typedef int near            *PINT;
    typedef int far             *LPINT;
    typedef WORD near           *PWORD;
    typedef WORD far            *LPWORD;
    typedef long far            *LPLONG;
    typedef DWORD near          *PDWORD;
    typedef DWORD far           *LPDWORD;
    typedef void far            *LPVOID;

    typedef int                 INT;
    typedef unsigned int        UINT;
    typedef unsigned int        *PUINT;

    typedef HANDLE FAR          *LPHANDLE;

#endif

//
// Things that come from os2.h
//

#if !defined(OS2_INCLUDED)

    #define CHAR            char

    typedef unsigned char   UCHAR;
    typedef short           SHORT;
    typedef int             INT;
    typedef unsigned short  USHORT;
    typedef unsigned int    UINT;
    typedef unsigned long   ULONG;

    typedef char *          PCH;

#endif

#if !defined(LOWORD)
    #define LOWORD(l)       ((WORD)(l))
    #define HIWORD(l)       ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
#endif

#ifndef NULL
    #define NULL        ((void *) 0)
#endif

#if !defined(TRUE) || !defined(FALSE)
    #undef TRUE
    #undef FALSE

    #define FALSE       0
    #define TRUE        1
#endif

#if !defined(fTrue) || !defined(fFalse)
    #undef fTrue
    #undef fFalse

    #define fFalse      0
    #define fTrue       1
#endif

#ifndef min
#define min(a,b)        (((a) < (b)) ? (a) : (b))
#endif

#ifndef max
#define max(a,b)        (((a) > (b)) ? (a) : (b))
#endif

#ifndef Unreferenced
#define Unreferenced(a) ((void)a)
#endif

typedef unsigned short ushort;
typedef unsigned char  uchar;
typedef unsigned long  ulong;
typedef unsigned int   uint;

typedef void *      PV;
typedef void FAR *  LPV;

typedef char *      SZ;
typedef char FAR *  LSZ;
#ifndef _WINDOWS_
typedef char FAR *  LPCH;
#endif

typedef BOOL FAR *  LPF;
typedef BYTE FAR *  LPB;
typedef WORD FAR *  LPW;
typedef DWORD FAR * LPDW;
typedef LONG FAR *  LPL;
typedef ULONG FAR * LPUL;
typedef USHORT FAR *LPUS;
typedef DWORD FAR * LPDWORD;

typedef short       SWORD;

typedef UINT        WPARAM;
typedef LONG        LPARAM;

#ifdef HOST32
typedef ULONG       IWORD;
#else
typedef USHORT      IWORD;
#endif

#endif /* CVINC_TYPES */


COFFTOCV.C

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    cv.c

Abstract:

    This module handles the conversion activities requires for converting
    COFF debug data to CODEVIEW debug data.

Author:

    Wesley A. Witt (wesw) 19-April-1993

Environment:

    Win32, User Mode

--*/

#include <windows.h>
#include <imagehlp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "cv.h"
#define _SYMCVT_SOURCE_
#include "symcvt.h"
#include "cvcommon.h"

typedef struct tagOFFSETSORT {
    DWORD       dwOffset;          // offset for the symbol
    DWORD       dwSection;         // section number of the symbol
    DATASYM32   *dataSym;          // pointer to the symbol info
} OFFSETSORT;

#define n_name          N.ShortName
#define n_zeroes        N.Name.Short
#define n_nptr          N.LongName[1]
#define n_offset        N.Name.Long

static LPSTR GetSymName( PIMAGE_SYMBOL Symbol, PUCHAR StringTable, char *s );
DWORD  CreateModulesFromCoff( PPOINTERS p );
DWORD  CreatePublicsFromCoff( PPOINTERS p );
DWORD  CreateSegMapFromCoff( PPOINTERS p );
DWORD  CreateSrcLinenumbers( PPOINTERS p );

LONG
GuardPageFilterFunction(
    DWORD                ec,
    LPEXCEPTION_POINTERS lpep
    )

/*++

Routine Description:

    This function catches all exceptions from the convertcofftocv function
    and all that it calls.  The purpose of this function is allocate memory
    when it is necessary.  This happens because the cofftocv conversion cannot
    estimate the memory requirements before the conversion takes place.  To
    handle this properly space in the virtual address space is reserved, the
    reservation amount is 10 times the image size.  The first page is commited
    and then the conversion is started.  When an access violation occurs and the
    page that is trying to be access has a protection of noaccess then the
    page is committed.  Any other exception is not handled.

Arguments:

    ec      - the ecxeption code (should be EXCEPTION_ACCESS_VIOLATION)
    lpep    - pointer to the exception record and context record

Return Value:

    EXCEPTION_CONTINUE_EXECUTION    - access violation handled
    EXCEPTION_EXECUTE_HANDLER       - unknown exception and is not handled

--*/

{
    LPVOID                      vaddr;
    SYSTEM_INFO                 si;
    MEMORY_BASIC_INFORMATION    mbi;

    if (ec == EXCEPTION_ACCESS_VIOLATION) {
        vaddr = (LPVOID)lpep->ExceptionRecord->ExceptionInformation[1];
        VirtualQuery( vaddr, &mbi, sizeof(mbi) );
        if (mbi.AllocationProtect == PAGE_NOACCESS) {
            GetSystemInfo( &si );
            VirtualAlloc( vaddr, si.dwPageSize, MEM_COMMIT, PAGE_READWRITE );
            return EXCEPTION_CONTINUE_EXECUTION;
        }
    }

//  return EXCEPTION_CONTINUE_SEARCH;
    return EXCEPTION_EXECUTE_HANDLER;
}

BOOL
ConvertCoffToCv( PPOINTERS p )

/*++

Routine Description:

    This is the control function for the conversion of COFF to CODEVIEW
    debug data.  It calls individual functions for the conversion of
    specific types of debug data.

Arguments:

    p        - pointer to a POINTERS structure

Return Value:

    TRUE     - conversion succeded
    FALSE    - conversion failed

--*/

{
    SYSTEM_INFO                 si;
    DWORD                       cbsize;
    BOOL                        rval = TRUE;

    GetSystemInfo( &si );
    cbsize = max( p->iptrs.fsize * 10, si.dwPageSize * 10 );

    //
    // reserve all necessary pages
    //
    p->pCvCurr = p->pCvStart.ptr = VirtualAlloc( NULL, cbsize, MEM_RESERVE, PAGE_NOACCESS );

    //
    // commit the first pages
    //
    VirtualAlloc( p->pCvCurr, min( cbsize, 5 * si.dwPageSize), MEM_COMMIT, PAGE_READWRITE );

    try {

        CreateSignature( p );
        CreateModulesFromCoff( p );
        CreatePublicsFromCoff( p );
        CreateSymbolHashTable( p );
        CreateAddressSortTable( p );
        CreateSegMapFromCoff( p );
//      CreateSrcLinenumbers( p );
        CreateDirectories( p );

    } except ( GuardPageFilterFunction( GetExceptionCode(), GetExceptionInformation() )) {

        VirtualFree( p->pCvStart.ptr, cbsize, MEM_DECOMMIT );
        p->pCvStart.ptr = NULL;
        rval = FALSE;

    }

    if (rval) {
        p->pCvCurr = malloc( p->pCvStart.size );
        CopyMemory( p->pCvCurr, p->pCvStart.ptr, p->pCvStart.size );
        VirtualFree( p->pCvStart.ptr, cbsize, MEM_DECOMMIT );
        p->pCvStart.ptr = p->pCvCurr;
    }

    return rval;
}

DWORD
CreateModulesFromCoff( PPOINTERS p )

/*++

Routine Description:

    Creates the individual CV module records.  There is one CV module
    record for each .FILE record in the COFF debug data.  This is true
    even if the COFF size is zero.

Arguments:

    p        - pointer to a POINTERS structure

Return Value:

    The number of modules that were created.

--*/

{
    int                 i,j;
    DWORD               dwOff;
    DWORD               numaux;
    DWORD               nummods = 0;
    char                szSymName[256];
    PIMAGE_SYMBOL       Symbol;
    PIMAGE_AUX_SYMBOL   AuxSymbol;
    OMFModule           *m = NULL;
    int                 cSeg = 0;
    char *              pb;
    BOOLEAN             rgfCode[500];

    memset(rgfCode, 2, sizeof(rgfCode));

    for (i=0,j=0, Symbol=p->iptrs.AllSymbols;
         i<(int)p->iptrs.numberOfSymbols;
         i+=numaux+1,Symbol+=numaux+1) {

        numaux = Symbol->NumberOfAuxSymbols;
        if (Symbol->StorageClass == IMAGE_SYM_CLASS_FILE) {
            j++;
        }

    }

    p->pMi = (LPMODULEINFO) malloc( sizeof(MODULEINFO) * (j + 1) );
    ZeroMemory( p->pMi, sizeof(MODULEINFO) * (j + 1) );

    if (!j) {
        //
        //  Handle the situation where there are not any .file records in the
        //  COFF symbol table.  This can happen for ROM images.  If this happens
        //  then we will fabricate a bogus module.
        //
        m = (OMFModule *) p->pCvCurr;
        m->ovlNumber = 0;
        m->iLib = 0;
        m->Style[0] = 'C';
        m->Style[1] = 'V';

        for (i=0,j=0, dwOff=0; i<p->iptrs.numberOfSections; i++) {
            if (p->iptrs.sectionHdrs[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) {
                m->SegInfo[j].Seg = i + 1;
                m->SegInfo[j].cbSeg = p->iptrs.sectionHdrs[i].SizeOfRawData;
                m->SegInfo[j++].Off = dwOff;
            }
            dwOff += p->iptrs.sectionHdrs[i].SizeOfRawData;
        }

        m->cSeg = j;

        strcpy(szSymName,"foo.c");
        pb = (char *) &m->SegInfo[j];
        *pb = strlen(szSymName);
        memcpy(pb+1, szSymName, *pb);

        p->pMi[0].name = strdup(szSymName);
        p->pMi[0].iMod = 1;
        p->pMi[0].cb = 0;
        p->pMi[0].SrcModule = 0;

        m = NextMod(m);
        p->modcnt = 1;
        UpdatePtrs( p, &p->pCvModules, (LPVOID)m, 1 );

        return 1;
    }

    for (i=0, Symbol = p->iptrs.AllSymbols;
         i < (int) p->iptrs.numberOfSymbols;
         i += numaux + 1, Symbol += numaux + 1) {

        //
        // Get the number of aux symbol records for this symbol
        //
        numaux = Symbol->NumberOfAuxSymbols;
        AuxSymbol = (PIMAGE_AUX_SYMBOL) (Symbol+1);

        if ((i == 0) && ((Symbol+numaux+1)->StorageClass != IMAGE_SYM_CLASS_FILE)) {
            //
            // we have a situation where the first '.file' record
            // is missing.  currently this only happens with the
            // claxp compiler on alpha.
            //
            m = (OMFModule *) p->pCvCurr;
            cSeg = 0;
            m->ovlNumber = 0;
            m->iLib = 0;
            m->Style[0] = 'C';
            m->Style[1] = 'V';
            strcpy( szSymName, "fake.c" );
        } else

        //
        //  If this is a FILE record -- then we need to create a
        //      module item to correspond to this file record.
        //

        if (Symbol->StorageClass == IMAGE_SYM_CLASS_FILE) {
            if (m == NULL) {
                m = (OMFModule *) p->pCvCurr;
            } else {
                //
                //      Clean up the last item,  if we saw any
                //      section records then drop them in here
                //

                if (cSeg > 0) {
                    m->cSeg  = cSeg;
                    pb = (char *) &m->SegInfo[cSeg];
                    *pb = strlen(szSymName);
                    memcpy(pb+1, szSymName, *pb);

                    p->pMi[nummods].name = strdup(szSymName);
                    p->pMi[nummods].iMod = nummods + 1;
                    p->pMi[nummods].cb = 0;
                    p->pMi[nummods].SrcModule = 0;

                    m = NextMod(m);
                    nummods++;
                }
            }

            cSeg = 0;
            m->ovlNumber        = 0;
            m->iLib             = 0;
            m->Style[0]         = 'C';
            m->Style[1]         = 'V';

            /*
             *  Save off the file name to use when we have finished
             *  processing this module
             */

            memcpy(szSymName, (char *)AuxSymbol, numaux*sizeof(IMAGE_AUX_SYMBOL));
            szSymName[numaux*sizeof(IMAGE_AUX_SYMBOL)] = 0;

        }
        /*
         *  We have found a "SECTION" record.  Add the info to the
         *      module record
         */
        else
        if ((Symbol->SectionNumber & 0xffff) > 0xfff0) {
            continue;
        } else
        if (Symbol->SectionNumber > sizeof(rgfCode)/sizeof(rgfCode[0])) {
            return 0;
        } else
        if ((m != NULL) &&
            (rgfCode[Symbol->SectionNumber] != 0) &&
            (Symbol->StorageClass == IMAGE_SYM_CLASS_STATIC) &&
            ((*Symbol->n_name == '.') ||
             (Symbol->Type == IMAGE_SYM_TYPE_NULL)) &&
            (Symbol->NumberOfAuxSymbols == 1) &&
            (AuxSymbol->Section.Length != 0)) {

            if (rgfCode[Symbol->SectionNumber] == 2) {
                if ((p->iptrs.sectionHdrs[Symbol->SectionNumber - 1].
                    Characteristics & IMAGE_SCN_CNT_CODE) == 0) {
                    rgfCode[Symbol->SectionNumber] = 0;
                    continue;
                }
                rgfCode[Symbol->SectionNumber] = 1;
            }

            m->SegInfo[cSeg].Seg = Symbol->SectionNumber;
            m->SegInfo[cSeg].cbSeg = AuxSymbol->Section.Length;
            m->SegInfo[cSeg].Off = Symbol->Value -
                     p->iptrs.sectionHdrs[Symbol->SectionNumber-1].
                       VirtualAddress;
            cSeg += 1;
        }
    }

    /*
     *  Wrap up the last possible open module record
     */

    if (m != NULL) {
        if (cSeg > 0) {
            m->cSeg             = cSeg;
            pb = (char *) &m->SegInfo[cSeg];
            *pb = strlen(szSymName);
            memcpy(pb+1, szSymName, *pb);

            p->pMi[nummods].name = strdup(szSymName);
            p->pMi[nummods].iMod = nummods + 1;
            p->pMi[nummods].cb = 0;
            p->pMi[nummods].SrcModule = 0;

            m = NextMod(m);
            nummods++;
        }
    }

    p->modcnt = nummods;
    UpdatePtrs( p, &p->pCvModules, (LPVOID)m, nummods );

    return nummods;
}

DWORD
CreatePublicsFromCoff( PPOINTERS p )

/*++

Routine Description:

    Creates the individual CV public symbol records.  There is one CV
    public record created for each COFF symbol that is marked as EXTERNAL
    and has a section number greater than zero.  The resulting CV publics
    are sorted by section and offset.

Arguments:

    p        - pointer to a POINTERS structure

Return Value:

    The number of publics created.

--*/

{
    int                 i;
    DWORD               numaux;
    DWORD               numsyms = 0;
    char                szSymName[256];
    PIMAGE_SYMBOL       Symbol;
    OMFSymHash          *omfSymHash;
    DATASYM32           *dataSym;
    DATASYM32           *dataSym2;

    omfSymHash = (OMFSymHash *) p->pCvCurr;
    dataSym = (DATASYM32 *) (PUCHAR)((DWORD)omfSymHash + sizeof(OMFSymHash));

    for (i= 0, Symbol = p->iptrs.AllSymbols;
         i < p->iptrs.numberOfSymbols;
         i += numaux + 1, Symbol += numaux + 1) {

        if ((Symbol->StorageClass == IMAGE_SYM_CLASS_EXTERNAL) &&
            (Symbol->SectionNumber > 0)) {

            if (GetSymName( Symbol, p->iptrs.stringTable, szSymName )) {
                dataSym->rectyp = S_PUB32;
                dataSym->seg = Symbol->SectionNumber;
                dataSym->off = Symbol->Value -
                     p->iptrs.sectionHdrs[Symbol->SectionNumber-1].VirtualAddress;
                dataSym->typind = 0;
                dataSym->name[0] = strlen( szSymName );
                strcpy( &dataSym->name[1], szSymName );
                dataSym2 = NextSym32( dataSym );
                dataSym->reclen = (USHORT) ((DWORD)dataSym2 - (DWORD)dataSym) - 2;
                dataSym = dataSym2;
                numsyms += 1;
            }
        }
        numaux = Symbol->NumberOfAuxSymbols;
    }

    UpdatePtrs( p, &p->pCvPublics, (LPVOID)dataSym, numsyms );

    omfSymHash->cbSymbol = p->pCvPublics.size - sizeof(OMFSymHash);
    omfSymHash->symhash  = 0;
    omfSymHash->addrhash = 0;
    omfSymHash->cbHSym   = 0;
    omfSymHash->cbHAddr  = 0;

    return numsyms;
}                               /* CreatePublisFromCoff() */

DWORD
CreateSrcLinenumbers(
    PPOINTERS p
    )

/*++

Routine Description:

    Creates the individual CV soure line number records.

Arguments:

    p        - pointer to a POINTERS structure

Return Value:

    The number of publics created.

--*/

{
    typedef struct _SEGINFO {
        DWORD   start;
        DWORD   end;
        DWORD   cbLines;
        DWORD   ptrLines;
        DWORD   va;
        DWORD   num;
        BOOL    used;
    } SEGINFO, *LPSEGINFO;

    typedef struct _SRCINFO {
        LPSEGINFO   seg;
        DWORD       numSeg;
        DWORD       cbSeg;
        CHAR        name[MAX_PATH+1];
    } SRCINFO, *LPSRCINFO;

    typedef struct _SECTINFO {
        DWORD       va;
        DWORD       size;
        DWORD       ptrLines;
        DWORD       numLines;
    } SECTINFO, *LPSECTINFO;

    DWORD               i;
    DWORD               j;
    DWORD               k;
    DWORD               l;
    DWORD               actual;
    DWORD               sidx;
    DWORD               NumSrcFiles;
    DWORD               SrcFileCnt;
    DWORD               numaux;
    PIMAGE_SYMBOL       Symbol;
    PIMAGE_AUX_SYMBOL   AuxSymbol;
    BOOL                first = TRUE;
    OMFSourceModule     *SrcModule;
    OMFSourceFile       *SrcFile;
    OMFSourceLine       *SrcLine;
    LPBYTE              lpb;
    LPDWORD             lpdw;
    PUSHORT             lps;
    PUCHAR              lpc;
    PIMAGE_LINENUMBER   pil, pilSave;
    LPSRCINFO           si;
    LPSECTINFO          sections;

    //
    // setup the section info structure
    //
    sections = (LPSECTINFO) malloc( sizeof(SECTINFO) * p->iptrs.numberOfSections );
    for (i=0; i<(DWORD)p->iptrs.numberOfSections; i++) {
        sections[i].va        = p->iptrs.sectionHdrs[i].VirtualAddress;
        sections[i].size      = p->iptrs.sectionHdrs[i].SizeOfRawData;
        sections[i].ptrLines  = p->iptrs.sectionHdrs[i].PointerToLinenumbers;
        sections[i].numLines  = p->iptrs.sectionHdrs[i].NumberOfLinenumbers;
    }

    //
    // count the number of source files that contibute linenumbers
    //
    SrcFileCnt = 100;
    si = (LPSRCINFO) malloc( sizeof(SRCINFO) * SrcFileCnt );
    ZeroMemory( si, sizeof(SRCINFO) * SrcFileCnt );
    for (i=0, j=0, Symbol=p->iptrs.AllSymbols, NumSrcFiles=0;
         i<(DWORD)p->iptrs.numberOfSymbols;
         i+=(numaux+1), Symbol+=(numaux + 1)) {

        numaux = Symbol->NumberOfAuxSymbols;
        AuxSymbol = (PIMAGE_AUX_SYMBOL) (Symbol+1);

        if (Symbol->StorageClass == IMAGE_SYM_CLASS_FILE) {

            if (!first) {
                si[NumSrcFiles].cbSeg = j;
                NumSrcFiles++;
                if (NumSrcFiles == SrcFileCnt) {
                    SrcFileCnt += 100;
                    si = (LPSRCINFO) realloc( si, sizeof(SRCINFO) * SrcFileCnt );
                }
            }

            memcpy(si[NumSrcFiles].name, (char *)AuxSymbol, numaux*sizeof(IMAGE_AUX_SYMBOL));
            si[NumSrcFiles].name[numaux*sizeof(IMAGE_AUX_SYMBOL)] = 0;
            si[NumSrcFiles].numSeg = 100;
            si[NumSrcFiles].seg = (LPSEGINFO) malloc( sizeof(SEGINFO) * si[NumSrcFiles].numSeg );
            ZeroMemory( si[NumSrcFiles].seg, sizeof(SEGINFO) * si[NumSrcFiles].numSeg );
            first = FALSE;
            j = 0;

        }

        //
        // we do not want to look for segment information until we
        // have found a valid source file
        //
        if (first) {
            continue;
        }

        //
        // check the symbol to see if it is a segment record
        //
        if (numaux && Symbol->StorageClass == IMAGE_SYM_CLASS_STATIC &&
            (*Symbol->n_name == '.' ||
             ((Symbol->Type & 0xf) == IMAGE_SYM_TYPE_NULL && AuxSymbol->Section.Length)) &&
            AuxSymbol->Section.NumberOfLinenumbers > 0) {

            //
            // find the section that this symbol belongs to
            //
            for (k=0; k<(DWORD)p->iptrs.numberOfSections; k++) {
                if (Symbol->Value >= sections[k].va &&
                    Symbol->Value < sections[k].va + sections[k].size) {

                    sidx = k;
                    break;

                }
            }

            if (k != (DWORD)p->iptrs.numberOfSections &&
                p->iptrs.sectionHdrs[k].NumberOfLinenumbers) {

                pil = (PIMAGE_LINENUMBER) (p->iptrs.fptr + sections[sidx].ptrLines);
                k = 0;

                while( k < AuxSymbol->Section.NumberOfLinenumbers ) {

                    //
                    // count the linenumbers in this section or sub-section
                    //
                    for ( pilSave=pil,l=0;
                          k<AuxSymbol->Section.NumberOfLinenumbers;
                          k++,pilSave++,l++ ) {

                        if ((k != (DWORD)AuxSymbol->Section.NumberOfLinenumbers-1) &&
                            (pilSave->Linenumber > (pilSave+1)->Linenumber)) {
                            pilSave++;
                            l++;
                            break;
                        }

                    }

                    //
                    // pil     == beginning of the range
                    // pilSave == end of the range
                    //

                    si[NumSrcFiles].seg[j].start =
                                     (pil->Type.VirtualAddress - sections[sidx].va);

                    if (sections[sidx].numLines == l) {
                        pilSave--;
                        si[NumSrcFiles].seg[j].end =
                                     (pilSave->Type.VirtualAddress - sections[sidx].va) + 1;
//                                   (Symbol->Value - sections[sidx].va) + 1;
                    } else {
                        si[NumSrcFiles].seg[j].end =
                                     (pilSave->Type.VirtualAddress - sections[sidx].va) - 1;
//                                   (Symbol->Value - sections[sidx].va) - 1;
                    }

                    si[NumSrcFiles].seg[j].ptrLines = sections[sidx].ptrLines;
                    si[NumSrcFiles].seg[j].cbLines = l;
                    si[NumSrcFiles].seg[j].va = sections[sidx].va;
                    si[NumSrcFiles].seg[j].num = sidx + 1;
                    si[NumSrcFiles].seg[j].used = FALSE;

                    sections[sidx].ptrLines += (l * sizeof(IMAGE_LINENUMBER));
                    sections[sidx].numLines -= l;

                    j++;
                    if (j == si[NumSrcFiles].numSeg) {
                        si[NumSrcFiles].numSeg += 100;
                        si[NumSrcFiles].seg = (LPSEGINFO) realloc( si[NumSrcFiles].seg, sizeof(SEGINFO) * si[NumSrcFiles].numSeg );
                    }
                    k++;
                    pil = pilSave;
                }

            }

        }

    }

    lpb = (LPBYTE) p->pCvCurr;

    //
    // if there is nothing to do then bail out
    //
    if (!NumSrcFiles) {
        UpdatePtrs( p, &p->pCvSrcModules, (LPVOID)lpb, 0 );
        return 0;
    }

    for (i=0,actual=0,l=0; i<NumSrcFiles; i++) {

        if (si[i].cbSeg == 0) {
            continue;
        }

        //
        // create the source module header
        //
        SrcModule = (OMFSourceModule*) lpb;
        SrcModule->cFile = 1;
        SrcModule->cSeg = (USHORT)si[i].cbSeg;
        SrcModule->baseSrcFile[0] = 0;

        //
        // write the start/end pairs
        //
        lpdw = (LPDWORD) ((LPBYTE)SrcModule + sizeof(OMFSourceModule));
        for (k=0; k<si[i].cbSeg; k++) {
            *lpdw++ = si[i].seg[k].start;
            *lpdw++ = si[i].seg[k].end;
        }

        //
        // write the segment numbers
        //
        lps = (PUSHORT) lpdw;
        for (k=0; k<si[i].cbSeg; k++) {
            *lps++ = (USHORT)si[i].seg[k].num;
        }

        //
        // align to a dword boundry
        //
        lps = (PUSHORT) ((LPBYTE)lps + align(lps));

        //
        // update the base pointer
        //
        SrcModule->baseSrcFile[0] = (DWORD) ((LPBYTE)lps - (LPBYTE)SrcModule);

        //
        // write the source file record
        //
        SrcFile = (OMFSourceFile*) lps;
        SrcFile->cSeg = (USHORT)si[i].cbSeg;
        SrcFile->reserved = 0;

        for (k=0; k<si[i].cbSeg; k++) {
            SrcFile->baseSrcLn[k] = 0;
        }

        //
        // write the start/end pairs
        //
        lpdw = (LPDWORD) ((LPBYTE)SrcFile + 4 + (4 * si[i].cbSeg));
        for (k=0; k<si[i].cbSeg; k++) {
            *lpdw++ = si[i].seg[k].start;
            *lpdw++ = si[i].seg[k].end;
        }

        //
        // write the source file name
        //
        lpc = (PUCHAR) lpdw;
        k = strlen(si[i].name);
        *lpc++ = (UCHAR) k;
        strcpy( lpc, si[i].name );
        lpb = lpc + k;

        //
        // find the module info struct
        //
        for (; l<p->modcnt; l++) {
            if (stricmp(p->pMi[l].name,si[i].name)==0) {
                break;
            }
        }

        p->pMi[l].SrcModule = (DWORD) SrcModule;

        //
        // align to a dword boundry
        //
        lpb = (LPBYTE) (lpb + align(lpb));

        //
        // create the line number pairs
        //
        for (k=0; k<si[i].cbSeg; k++) {

            //
            // find the first line number that applies to this segment
            //
            pil = (PIMAGE_LINENUMBER) (p->iptrs.fptr + si[i].seg[k].ptrLines);

            //
            // update the base pointer
            //
            SrcFile->baseSrcLn[k] = (DWORD) (lpb - (LPBYTE)SrcModule);

            //
            // write the line numbers
            //
            SrcLine = (OMFSourceLine*) lpb;
            SrcLine->Seg = (USHORT)si[i].seg[k].num;
            SrcLine->cLnOff = (USHORT) si[i].seg[k].cbLines;
            pilSave = pil;
            lpdw = (LPDWORD) (lpb + 4);
            for (j=0; j<SrcLine->cLnOff; j++) {
                *lpdw++ = pil->Type.VirtualAddress - si[i].seg[k].va;
                pil++;
            }
            lps = (PUSHORT) lpdw;
            pil = pilSave;
            for (j=0; j<SrcLine->cLnOff; j++) {
                *lps++ = pil->Linenumber;
                pil++;
            }

            //
            // align to a dword boundry
            //
            lps = (PUSHORT) ((LPBYTE)lps + align(lps));

            lpb = (LPBYTE) lps;
        }

        p->pMi[l].cb = (DWORD)lpb - (DWORD)SrcModule;
        actual++;

    }

    UpdatePtrs( p, &p->pCvSrcModules, (LPVOID)lpb, actual );

    //
    // cleanup all allocated memory
    //

    free( sections );

    for (i=0; i<SrcFileCnt; i++) {
        if (si[i].seg) {
            free( si[i].seg );
        }
    }

    free( si );

    return NumSrcFiles;
}                               /* CreateSrcLinenumbers() */

DWORD
CreateSegMapFromCoff( PPOINTERS p )

/*++

Routine Description:

    Creates the CV segment map.  The segment map is used by debuggers
    to aid in address lookups.  One segment is created for each COFF
    section in the image.

Arguments:

    p        - pointer to a POINTERS structure

Return Value:

    The number of segments in the map.

--*/

{
    int                         i;
    SGM                         *sgm;
    SGI                         *sgi;
    PIMAGE_SECTION_HEADER       sh;

    sgm = (SGM *) p->pCvCurr;
    sgi = (SGI *) ((DWORD)p->pCvCurr + sizeof(SGM));

    sgm->cSeg = p->iptrs.numberOfSections;
    sgm->cSegLog = p->iptrs.numberOfSections;

    sh = p->iptrs.sectionHdrs;

    for (i=0; i<p->iptrs.numberOfSections; i++, sh++) {
        sgi->sgf.fRead        = (USHORT) (sh->Characteristics & IMAGE_SCN_MEM_READ) ==    IMAGE_SCN_MEM_READ;
        sgi->sgf.fWrite       = (USHORT) (sh->Characteristics & IMAGE_SCN_MEM_WRITE) ==   IMAGE_SCN_MEM_WRITE;
        sgi->sgf.fExecute     = (USHORT) (sh->Characteristics & IMAGE_SCN_MEM_EXECUTE) == IMAGE_SCN_MEM_EXECUTE;
        sgi->sgf.f32Bit       = 1;
        sgi->sgf.fSel         = 0;
        sgi->sgf.fAbs         = 0;
        sgi->sgf.fGroup       = 1;
        sgi->iovl             = 0;
        sgi->igr              = 0;
        sgi->isgPhy           = (USHORT) i + 1;
        sgi->isegName         = 0;
        sgi->iclassName       = 0;
        sgi->doffseg          = 0;
        sgi->cbSeg            = sh->SizeOfRawData;
        sgi++;
    }

    UpdatePtrs( p, &p->pCvSegMap, (LPVOID)sgi, i );

    return i;
}

LPSTR
GetSymName( PIMAGE_SYMBOL Symbol, PUCHAR StringTable, char *s )

/*++

Routine Description:

    Extracts the COFF symbol from the image symbol pointer and puts
    the ascii text in the character pointer passed in.

Arguments:

    Symbol        - COFF Symbol Record
    StringTable   - COFF string table
    s             - buffer for the symbol string

Return Value:

    void

--*/

{
    DWORD i;

    if (Symbol->n_zeroes) {
        for (i=0; i<8; i++) {
            if ((Symbol->n_name[i]>0x1f) && (Symbol->n_name[i]<0x7f)) {
                *s++ = Symbol->n_name[i];
            }
        }
        *s = 0;
    }
    else {
        if (StringTable[Symbol->n_offset] == '?') {
            i = UnDecorateSymbolName( &StringTable[Symbol->n_offset],
                                  s,
                                  255,
                                  UNDNAME_COMPLETE                |
                                  UNDNAME_NO_LEADING_UNDERSCORES  |
                                  UNDNAME_NO_MS_KEYWORDS          |
                                  UNDNAME_NO_FUNCTION_RETURNS     |
                                  UNDNAME_NO_ALLOCATION_MODEL     |
                                  UNDNAME_NO_ALLOCATION_LANGUAGE  |
                                  UNDNAME_NO_MS_THISTYPE          |
                                  UNDNAME_NO_CV_THISTYPE          |
                                  UNDNAME_NO_THISTYPE             |
                                  UNDNAME_NO_ACCESS_SPECIFIERS    |
                                  UNDNAME_NO_THROW_SIGNATURES     |
                                  UNDNAME_NO_MEMBER_TYPE          |
                                  UNDNAME_NO_RETURN_UDT_MODEL     |
                                  UNDNAME_NO_ARGUMENTS            |
                                  UNDNAME_NO_SPECIAL_SYMS         |
                                  UNDNAME_NAME_ONLY
                                );
            if (!i) {
                return NULL;
            }
        } else {
            strcpy( s, &StringTable[Symbol->n_offset] );

}
    }

    return s;
}


CVCOMMON.C

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    cvcommon.c

Abstract:

    This file contians a set of common routines which are used in
    doing symbol conversions from one type of symbols to CodeView
    symbols.

Author:

    Wesley A. Witt (wesw) 19-April-1993
    Jim Schaad (jimsch) 22 May 1993

--*/

#include        <windows.h>
#include        <stdlib.h>
#include        "cv.h"
#include        "symcvt.h"
#include        "cvcommon.h"

typedef struct tagSYMHASH {
    DWORD       dwHashVal;         // hash value for the symbol
    DWORD       dwHashBucket;      // hash bucket number
    DATASYM16 * dataSym;           // pointer to the symbol info
} SYMHASH;

typedef struct tagOFFSETSORT {
    DWORD       dwOffset;          // offset for the symbol
    DWORD       dwSection;         // section number of the symbol
    DATASYM16 * dataSym;           // pointer to the symbol info
} OFFSETSORT;

int _CRTAPI1 SymHashCompare( const void *arg1, const void *arg2 );
int _CRTAPI1 OffsetSortCompare( const void *arg1, const void *arg2 );

DWORD
CreateSignature( PPOINTERS p )

/*++

Routine Description:

    Creates the CODEVIEW signature record.  Currently this converter only
    generates NB09 data (MS C/C++ 8.0).

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    number of records generates, this is always 1.

--*/

{
    OMFSignature        *omfSig;

    omfSig = (OMFSignature *) p->pCvCurr;
    strcpy( omfSig->Signature, "NB09" );
    omfSig->filepos = 0;
    p->pCvStart.size += sizeof(OMFSignature);
    p->pCvCurr = (PUCHAR) p->pCvCurr + sizeof(OMFSignature);
    return 1;
}                               /* CreateSignature() */

DWORD
CreateDirectories( PPOINTERS p )

/*++

Routine Description:

    This is the control function for the generation of the CV directories.
    It calls individual functions for the generation of specific types of
    debug directories.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    the number of directories created.

--*/

{
    OMFDirHeader        *omfDir = (OMFDirHeader *)p->pCvCurr;
    OMFSignature        *omfSig = (OMFSignature *)p->pCvStart.ptr;
    OMFDirEntry         *omfDirEntry = NULL;

    omfSig->filepos = (DWORD)p->pCvCurr - (DWORD)p->pCvStart.ptr;

    omfDir->cbDirHeader = sizeof(OMFDirHeader);
    omfDir->cbDirEntry  = sizeof(OMFDirEntry);
    omfDir->cDir        = 0;
    omfDir->lfoNextDir  = 0;
    omfDir->flags       = 0;

    p->pCvStart.size += sizeof(OMFDirHeader);
    p->pCvCurr = (PUCHAR) p->pCvCurr + sizeof(OMFDirHeader);

    omfDir->cDir += CreateModuleDirectoryEntries( p );
    omfDir->cDir += CreatePublicDirectoryEntries( p );
    omfDir->cDir += CreateSegMapDirectoryEntries( p );
    omfDir->cDir += CreateSrcModulesDirectoryEntries( p );

    strcpy(p->pCvCurr, "NB090000");
    p->pCvStart.size += 8;
    p->pCvCurr += 8;
    *((DWORD *) (p->pCvCurr-4)) = p->pCvStart.size;

    return omfDir->cDir;
}                               /* CreateDirectories() */

DWORD
CreateModuleDirectoryEntries( PPOINTERS p )

/*++

Routine Description:

    Creates directory entries for each module in the image.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    the number of directory entries created.

--*/

{
    OMFDirEntry   *omfDirEntry = NULL;
    OMFModule     *m = NULL;
    OMFModule     *mNext = NULL;
    DWORD         i = 0;
    DWORD         mSize = 0;
    DWORD         lfo = (DWORD)p->pCvModules.ptr - (DWORD)p->pCvStart.ptr;

    m = (OMFModule *) p->pCvModules.ptr;
    for (i=0; i<p->pCvModules.count; i++) {
        mNext = NextMod(m);

        omfDirEntry = (OMFDirEntry *) p->pCvCurr;

        mSize = (DWORD)mNext - (DWORD)m;
        omfDirEntry->SubSection = sstModule;
        omfDirEntry->iMod       = (USHORT) i + 1;
        omfDirEntry->lfo        = lfo;
        omfDirEntry->cb         = mSize;

        lfo += mSize;

        p->pCvStart.size += sizeof(OMFDirEntry);
        p->pCvCurr = (PUCHAR) p->pCvCurr + sizeof(OMFDirEntry);

        m = mNext;
    }

    return p->pCvModules.count;
}                               /* CreateModuleDirectoryEntries() */

DWORD
CreatePublicDirectoryEntries( PPOINTERS p )

/*++

Routine Description:

    Creates the directory entry for the global publics.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    the number of directory entries created, always 1.

--*/

{
    OMFDirEntry   *omfDirEntry = (OMFDirEntry *) p->pCvCurr;

    omfDirEntry->SubSection = sstGlobalPub;
    omfDirEntry->iMod       = 0xffff;
    omfDirEntry->lfo        = (DWORD)p->pCvPublics.ptr - (DWORD)p->pCvStart.ptr;
    omfDirEntry->cb         = p->pCvPublics.size;

    p->pCvStart.size += sizeof(OMFDirEntry);
    p->pCvCurr = (PUCHAR) p->pCvCurr + sizeof(OMFDirEntry);

    return 1;
}                               /* CreatePublicDirectoryEntries() */

DWORD
CreateSegMapDirectoryEntries( PPOINTERS p )

/*++

Routine Description:

    Creates the directory entry for the segment map.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    the number of directory entries created, always 1.

--*/

{
    OMFDirEntry   *omfDirEntry = (OMFDirEntry *) p->pCvCurr;

    omfDirEntry->SubSection = sstSegMap;
    omfDirEntry->iMod       = 0xffff;
    omfDirEntry->lfo        = (DWORD)p->pCvSegMap.ptr - (DWORD)p->pCvStart.ptr;
    omfDirEntry->cb         = p->pCvSegMap.size;

    p->pCvStart.size += sizeof(OMFDirEntry);
    p->pCvCurr = (PUCHAR) p->pCvCurr + sizeof(OMFDirEntry);

    return 1;
}                               /* CreateSegMapDirectoryEntries() */

DWORD
CreateSrcModulesDirectoryEntries( PPOINTERS p )

/*++

Routine Description:

    Creates directory entries for each source module in the image.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    the number of directory entries created.

--*/

{
    OMFDirEntry         *omfDirEntry = NULL;
    DWORD               i;
    DWORD               lfo = (DWORD)p->pCvSrcModules.ptr - (DWORD)p->pCvStart.ptr;
    DWORD               j = lfo;
    OMFSourceModule     *m;

    //
    // if there were no linenumber conversions then bail out
    //
    if (!p->pCvSrcModules.count) {
        return 0;
    }

    for (i=0; i<p->pCvSrcModules.count; i++) {

        if (!p->pMi[i].SrcModule) {
            continue;
        }

        omfDirEntry = (OMFDirEntry *) p->pCvCurr;

        omfDirEntry->SubSection = sstSrcModule;
        omfDirEntry->iMod = (USHORT) p->pMi[i].iMod;
        omfDirEntry->lfo = lfo;
        omfDirEntry->cb = p->pMi[i].cb;

        m = (OMFSourceModule*) p->pMi[i].SrcModule;

        lfo += omfDirEntry->cb;

        p->pCvStart.size += sizeof(OMFDirEntry);
        p->pCvCurr = (PUCHAR) p->pCvCurr + sizeof(OMFDirEntry);
    }

    free( p->pMi );

    return p->pCvSrcModules.count;
}                               /* CreateSrcModulesDirectoryEntries() */

#define byt_toupper(b)      (b & 0xDF)
#define dwrd_toupper(dw)    (dw & 0xDFDFDFDF)

DWORD
DWordXorLrl( char *szSym )

/*++

Routine Description:

    This function will take an ascii character string and generate
    a hash for that string.  The hash algorithm is the CV NB09 hash
    algorithm.

Arguments:

    szSym    - a character pointer, the first char is the string length

Return Value:

    The generated hash value.

--*/

{
    char                *pName = szSym+1;
    int                 cb =  *szSym;
    char                *pch;
    char                c;
    DWORD               hash = 0, ulEnd = 0;
    DWORD UNALIGNED     *pul;

    // Replace all "::" with "__" for hashing purposes

    c = *(pName+cb);
    *(pName+cb) = '\0';
    pch = strstr( pName, "::" );
    if ( pch ) {
        *pch++ = '_';
        *pch   = '_';
    }
    *(pName+cb) = c;

    // If we're standard call, skip the trailing @999

    pch = pName + cb - 1;
    while (isdigit(*pch)) {
        pch--;
    }

    if (*pch == '@') {
        cb = pch - pName;
    }

    // If we're fastcall, skip the leading '@'

    if (*pName == '@') {
        pName++;
        cb--;
    }

    // Calculate the odd byte hash.

    while (cb & 3) {
        ulEnd |= byt_toupper (pName[cb-1]);
        ulEnd <<=8;
        cb--;
    }

    pul = (DWORD UNALIGNED *)pName;

    // calculate the dword hash for the remaining

    while (cb) {
        hash ^= dwrd_toupper(*pul);
        hash = _lrotl (hash, 4);
        pul++;
        cb -=4;
    }

    // or in the remainder

    hash ^= ulEnd;

    return hash;
}                               /* DWordXorLrl() */

OMFModule *
NextMod(
        OMFModule *             pMod
        )
/*++

Routine Description:

    description-of-function.

Arguments:

    argument-name - Supplies | Returns description of argument.
    .
    .

Return Value:

    return-value - Description of conditions needed to return value. - or -
    None.

--*/

{
    char *      pb;

    pb = (char *) &(pMod->SegInfo[pMod->cSeg]);
    pb += *pb + 1;
    pb = (char *) (((unsigned long) pb + 3) & ~3);

    return (OMFModule *) pb;
}                               /* NextMod() */

int
_CRTAPI1
SymHashCompare(
               const void *     arg1,
               const void *     arg2
               )
/*++

Routine Description:

    Sort compare function for sorting SYMHASH records by hashed
    bucket number.

Arguments:

    arg1     - record #1
    arg2     - record #2

Return Value:

   -1        - record #1 is < record #2
    0        - records are equal
    1        - record #1 is > record #2

--*/

{
    if (((SYMHASH*)arg1)->dwHashBucket < ((SYMHASH*)arg2)->dwHashBucket) {
        return -1;
    }
    if (((SYMHASH*)arg1)->dwHashBucket > ((SYMHASH*)arg2)->dwHashBucket) {
        return 1;
    }

    // BUGBUG: Should we second sort on the hash value?

    return 0;
}                               /* SymHashCompare() */

// Symbol Offset/Hash structure

typedef struct _SOH {
    DWORD uoff;
    DWORD ulHash;
} SOH;

#define MINHASH     6           // Don't create a hash with fewer than 6 slots

DWORD
CreateSymbolHashTable(
    PPOINTERS p
    )
/*++

Routine Description:

    Creates the CV symbol hash table.  This hash table is used
    primarily by debuggers to access symbols in a quick manner.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    The number of buckets is the hash table.

--*/
{
    DWORD           i;
    DWORD           j;
    int             k;
    DWORD           numsyms;
    DWORD           numbuckets;
    OMFSymHash      *omfSymHash;
    DATASYM16       *dataSymStart;
    DATASYM16       *dataSym;
    LPVOID          pHashData;
    USHORT          *pCHash;
    DWORD           *pHashTable;
    DWORD           *pBucketCounts;
    DWORD           *pChainTable;
    SYMHASH         *symHashStart;
    SYMHASH         *symHash;
//    DWORD           dwHashVal;
    char *          sz;

    numsyms = p->pCvPublics.count;
    numbuckets = (numsyms+9) / 10;
    numbuckets = (1 + numbuckets) & ~1;
    numbuckets = __max(numbuckets, MINHASH);

    symHashStart =
    symHash = (SYMHASH *) malloc( numsyms * sizeof(SYMHASH) );
    if (symHashStart == NULL) {
        return 0;
    }

    memset( symHashStart, 0, numsyms * sizeof(SYMHASH) );

    pHashData = (LPVOID) p->pCvCurr;
    pCHash = (USHORT *) pHashData;
    pHashTable = (DWORD *) ((DWORD)pHashData + sizeof(DWORD));
    pBucketCounts = (DWORD *) ((DWORD)pHashTable +
                                  (sizeof(DWORD) * numbuckets));
    memset(pBucketCounts, 0, sizeof(DWORD) * numbuckets);

    pChainTable = (DWORD *) ((DWORD)pBucketCounts +
                                 ((sizeof(ULONG) * numbuckets)));

    omfSymHash = (OMFSymHash *) p->pCvPublics.ptr;
    dataSymStart =
    dataSym = (DATASYM16 *) ((DWORD)omfSymHash + sizeof(OMFSymHash));

    *pCHash = (USHORT)numbuckets;

    /*
     *  cruise thru the symbols and calculate the hash values
     *  and the hash bucket numbers; save the info away for later use
     */
    for (i=0; i<numsyms; i++, symHash++) {
        switch( dataSym->rectyp ) {
        case S_PUB16:
            sz = dataSym->name;
            break;

        case S_PUB32:
            sz = ((DATASYM32 *) dataSym)->name;
            break;

        default:
            continue;
        }

        symHash->dwHashVal = DWordXorLrl( sz );
        symHash->dwHashBucket = symHash->dwHashVal % numbuckets;
        pBucketCounts[symHash->dwHashBucket] += 1;
        symHash->dataSym = dataSym;
        dataSym = ((DATASYM16 *) ((char *) dataSym + dataSym->reclen + 2));
    }

    qsort( (void*)symHashStart, numsyms, sizeof(SYMHASH), SymHashCompare );

    j = (char *)pChainTable - (char *)pHashData;
    for (i=0, k = 0;
         i < numbuckets;
         k += pBucketCounts[i], i += 1, pHashTable++ )
    {
        *pHashTable = (DWORD) j + (k * sizeof(DWORD) * 2);
    }

    dataSymStart = (DATASYM16 *) (PUCHAR)((DWORD)omfSymHash);
    for (i=0,symHash=symHashStart; i<numsyms; i++,symHash++,pChainTable++) {
        *pChainTable = (DWORD) (DWORD)symHash->dataSym - (DWORD)dataSymStart;
        ++pChainTable;
        *pChainTable = symHash->dwHashVal;
    }

    UpdatePtrs( p, &p->pCvSymHash, (LPVOID)pChainTable, numsyms );

    omfSymHash->symhash = 10;
    omfSymHash->cbHSym = p->pCvSymHash.size;

    free( symHashStart );

    return numbuckets;
}                               /* CreateSymbolHashTable() */

VOID
UpdatePtrs( PPOINTERS p, PPTRINFO pi, LPVOID lpv, DWORD count )

/*++

Routine Description:

    This function is called by ALL functions that put data into the
    CV data area.  After putting the data into the CV memory this function
    must be called.  It will adjust all of the necessary pointers so the
    the next guy doesn't get hosed.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)
    pi       - the CV pointer that is to be updated
    lpv      - current pointer into the CV data
    count    - the number of items that were placed into the CV data

Return Value:

    void

--*/

{
    if (!count) {
        return;
    }

    pi->ptr = p->pCvCurr;
    pi->size = (DWORD) ((DWORD)lpv - (DWORD)p->pCvCurr);
    pi->count = count;

    p->pCvStart.size += pi->size;
    p->pCvCurr = (PUCHAR) lpv;

    return;
}                               /* UpdatePtrs() */

int
_CRTAPI1
OffsetSortCompare( const void *arg1, const void *arg2 )

/*++

Routine Description:

    Sort compare function for sorting OFFETSORT records by section number.

Arguments:

    arg1     - record #1
    arg2     - record #2

Return Value:

   -1        - record #1 is < record #2
    0        - records are equal
    1        - record #1 is > record #2

--*/

{
    if (((OFFSETSORT*)arg1)->dwSection < ((OFFSETSORT*)arg2)->dwSection) {
        return -1;
    }
    if (((OFFSETSORT*)arg1)->dwSection > ((OFFSETSORT*)arg2)->dwSection) {
        return 1;
    }
    if (((OFFSETSORT*)arg1)->dwOffset < ((OFFSETSORT*)arg2)->dwOffset) {
        return -1;
    }
    if (((OFFSETSORT*)arg1)->dwOffset > ((OFFSETSORT*)arg2)->dwOffset) {
        return 1;
    }
    return 0;
}                               /* OffsetSortCompare() */

DWORD
CreateAddressSortTable( PPOINTERS p )

/*++

Routine Description:

    Creates the CV address sort table. This hash table is used
    primarily by debuggers to access symbols in a quick manner when
    all you have is an address.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    The number of sections in the table.

--*/

{
    DWORD               i;
    DWORD               j;
    int                 k;
    DWORD               numsyms = p->pCvPublics.count;
    DWORD               numsections;
    OMFSymHash          *omfSymHash;
    DATASYM16           *dataSymStart;
    DATASYM16           *dataSym;
    LPVOID              pAddressData;
    USHORT              *pCSeg;
    DWORD               *pSegTable;
    DWORD               *pOffsetCounts;
    DWORD               *pOffsetTable;
    OFFSETSORT          *pOffsetSortStart;
    OFFSETSORT          *pOffsetSort;

    extern int          CSymSegs;

    if (p->iptrs.fileHdr) {
        numsections = p->iptrs.fileHdr->NumberOfSections;
    } else if (p->iptrs.sepHdr) {
        numsections = p->iptrs.sepHdr->NumberOfSections;
    } else {
        numsections = CSymSegs;
    }

    pOffsetSortStart =
      pOffsetSort = (OFFSETSORT *) malloc( numsyms * sizeof(OFFSETSORT) );

    if (pOffsetSort == NULL) {
        return 0;
    }

    memset( pOffsetSortStart, 0, numsyms * sizeof(OFFSETSORT) );

    pAddressData = (LPVOID) p->pCvCurr;
    pCSeg = (USHORT *) pAddressData;
    pSegTable = (DWORD *) ((DWORD)pAddressData + sizeof(DWORD));
    pOffsetCounts = (DWORD *) ((DWORD)pSegTable +
                                (sizeof(DWORD) * numsections));
    pOffsetTable = (DWORD *) ((DWORD)pOffsetCounts +
                              ((sizeof(DWORD) * numsections)));
//    if (numsections & 1) {
//        pOffsetTable = (DWORD *) ((DWORD)pOffsetTable + 2);
//    }

    omfSymHash = (OMFSymHash *) p->pCvPublics.ptr;
    dataSymStart =
      dataSym = (DATASYM16 *) ((DWORD)omfSymHash + sizeof(OMFSymHash));

    *pCSeg = (USHORT)numsections;

    for (i=0;
         i<numsyms;
         i++, pOffsetSort++)
    {
        switch(dataSym->rectyp) {
        case S_PUB16:
            pOffsetSort->dwOffset = dataSym->off;
            pOffsetSort->dwSection = dataSym->seg;
            break;

        case S_PUB32:
            pOffsetSort->dwOffset = ((DATASYM32 *) dataSym)->off;
            pOffsetSort->dwSection = ((DATASYM32 *) dataSym)->seg;
        }

        pOffsetSort->dataSym = dataSym;
        pOffsetCounts[pOffsetSort->dwSection - 1] += 1;
        dataSym = ((DATASYM16 *) ((char *) dataSym + dataSym->reclen + 2));    }

//#if 0
    qsort((void*)pOffsetSortStart, numsyms, sizeof(OFFSETSORT),
          OffsetSortCompare );
//#endif

    j = (DWORD) (DWORD)pOffsetTable - (DWORD)pAddressData;
    for (i=0, k=0;
         i < numsections;
         k += pOffsetCounts[i], i += 1, pSegTable++)
    {
        *pSegTable = (DWORD) j + (k * sizeof(DWORD) * 2);
    }

    dataSymStart = (DATASYM16 *) (PUCHAR)((DWORD)omfSymHash);
    for (i=0, pOffsetSort=pOffsetSortStart;
         i < numsyms;
         i++, pOffsetSort++, pOffsetTable++)
    {
        *pOffsetTable = (DWORD)pOffsetSort->dataSym - (DWORD)dataSymStart;
        pOffsetTable++;
        *pOffsetTable = pOffsetSort->dwOffset;
    }

    UpdatePtrs( p, &p->pCvAddrSort, (LPVOID)pOffsetTable, numsyms );

    omfSymHash->addrhash = 12;
    omfSymHash->cbHAddr = p->pCvAddrSort.size;

    free( pOffsetSortStart );

    return numsections;
}                               /* CreateAddressSort() */


FILE.C

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    file.c

Abstract:

    This module handles all file i/o for SYMCVT.  This includes the
    mapping of all files and establishing all file pointers for the
    mapped file(s).

Author:

    Wesley A. Witt (wesw) 19-April-1993

Environment:

    Win32, User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define _SYMCVT_SOURCE_
#include "symcvt.h"

static BOOL CalculateOutputFilePointers( PIMAGEPOINTERS pi, PIMAGEPOINTERS po );
static BOOL CalculateInputFilePointers( PIMAGEPOINTERS p );

BOOL
MapInputFile (
              PPOINTERS   p,
              HANDLE      hFile,
              char *      fname
              )
/*++

Routine Description:

    Maps the input file specified by the fname argument and saves the
    file handle & file pointer in the POINTERS structure.

Arguments:

    p        - Supplies pointer to a POINTERS structure
    hFile    - OPTIONAL Supplies handle for file if already open
    fname    - Supplies ascii string for the file name

Return Value:

    TRUE     - file mapped ok
    FALSE    - file could not be mapped

--*/

{
    BOOL        rVal = TRUE;
    DWORD       lwFsize;

    memset( p, 0, sizeof(POINTERS) );

    strcpy( p->iptrs.szName, fname );

    if (hFile != NULL) {

        p->iptrs.hFile = hFile;

    } else {

        p->iptrs.hFile = CreateFile( p->iptrs.szName,
                                    GENERIC_READ,
                                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                                    NULL,
                                    OPEN_EXISTING,
                                    0,
                                    NULL );
    }

    if (p->iptrs.hFile == INVALID_HANDLE_VALUE) {

        rVal = FALSE;

    } else {

        p->iptrs.fsize = GetFileSize( p->iptrs.hFile, NULL );
        if (lwFsize == 0xffffffff) {
            ;
        }
        p->iptrs.hMap = CreateFileMapping( p->iptrs.hFile,
                                           NULL,
                                           PAGE_READONLY,
                                           0,
                                           0,
                                           NULL
                                         );

        if (p->iptrs.hMap == INVALID_HANDLE_VALUE) {

            p->iptrs.hMap = NULL;
            rVal = FALSE;

        } else {

            p->iptrs.fptr = MapViewOfFile( p->iptrs.hMap,
                                           FILE_MAP_READ,
                                           0, 0, 0 );
            if (p->iptrs.fptr == NULL) {
                CloseHandle( p->iptrs.hMap );
                p->iptrs.hMap = NULL;
                rVal = FALSE;
            }
        }
    }

    if (!hFile && p->iptrs.hFile != INVALID_HANDLE_VALUE) {
        CloseHandle(p->iptrs.hFile);
        p->iptrs.hFile = NULL;
    }

    return rVal;
}                               /* MapInputFile() */

BOOL
UnMapInputFile (
    PPOINTERS p
    )
/*++

Routine Description:

    Unmaps the input file specified by the fname argument and then
    closes the file.

Arguments:

    p        - pointer to a POINTERS structure

Return Value:

    TRUE     - file mapped ok
    FALSE    - file could not be mapped

--*/

{
    if ( p->iptrs.fptr ) {
        UnmapViewOfFile( p->iptrs.fptr );
        p->iptrs.fptr = NULL;
    }
    if ( p->iptrs.hMap ) {
        CloseHandle( p->iptrs.hMap );
        p->iptrs.hMap = NULL;
    }
    if (p->iptrs.hFile != NULL) {
        CloseHandle( p->iptrs.hFile );
        p->iptrs.hFile = NULL;
    }
    return TRUE;
}                               /* UnMapInputFile() */

BOOL
FillInSeparateImagePointers(
                            PIMAGEPOINTERS      p
                            )
/*++

Routine Description:

    This routine will go through the exe file and fill in the
    pointers needed relative to the separate debug information files

Arguments:

    p  - Supplies the structure to fill in

Return Value:

    TRUE if successful and FALSE otherwise.

--*/

{
    int                         li;
    int                         numDebugDirs;
    PIMAGE_DEBUG_DIRECTORY      pDebugDir;
    PIMAGE_COFF_SYMBOLS_HEADER  pCoffHdr;

    p->sectionHdrs = (PIMAGE_SECTION_HEADER)
      (p->fptr + sizeof(IMAGE_SEPARATE_DEBUG_HEADER));

    numDebugDirs = p->sepHdr->DebugDirectorySize/sizeof(IMAGE_DEBUG_DIRECTORY);

    if (numDebugDirs == 0) {
        return FALSE;
    }

    /*
     *  For each debug directory, determine the debug directory type
     *  and cache any information about them.
     */

    pDebugDir = (PIMAGE_DEBUG_DIRECTORY)
      (p->fptr + sizeof(IMAGE_SEPARATE_DEBUG_HEADER) +
       p->sepHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER) +
       p->sepHdr->ExportedNamesSize);

    for (li=0; li<numDebugDirs; li++, pDebugDir++) {
        if (((int) pDebugDir->Type) > p->cDebugDir) {
            p->cDebugDir += 10;
            p->rgDebugDir = realloc((char *) p->rgDebugDir,
                                    p->cDebugDir * sizeof(p->rgDebugDir[0]));
            memset(&p->rgDebugDir[p->cDebugDir-10], 0,
                   10*sizeof(p->rgDebugDir[0]));
        }

        p->rgDebugDir[pDebugDir->Type] = pDebugDir;
    }

    if (p->rgDebugDir[IMAGE_DEBUG_TYPE_COFF] != NULL) {
        pCoffHdr = (PIMAGE_COFF_SYMBOLS_HEADER) (p->fptr +
          p->rgDebugDir[IMAGE_DEBUG_TYPE_COFF]->PointerToRawData);
        p->AllSymbols = (PIMAGE_SYMBOL)
          ((char *) pCoffHdr + pCoffHdr->LvaToFirstSymbol);
        p->stringTable = pCoffHdr->NumberOfSymbols * IMAGE_SIZEOF_SYMBOL +
          (char *) p->AllSymbols;
        p->numberOfSymbols = pCoffHdr->NumberOfSymbols;
    }
    p->numberOfSections = p->sepHdr->NumberOfSections;

    return TRUE;
}                               /* FillInSeparateImagePointers() */

BOOL
CalculateNtImagePointers(
    PIMAGEPOINTERS p
    )
/*++

Routine Description:

    This function reads an NT image and its associated COFF headers
    and file pointers and build a set of pointers into the mapped image.
    The pointers are all relative to the image's mapped file pointer
    and allow direct access to the necessary data.

Arguments:

    p        - pointer to a IMAGEPOINTERS structure

Return Value:

    TRUE     - pointers were created
    FALSE    - pointers could not be created

--*/
{
    PIMAGE_DEBUG_DIRECTORY      debugDir;
    PIMAGE_SECTION_HEADER       sh;
    DWORD                       i, li, rva, numDebugDirs;
    PIMAGE_FILE_HEADER          pFileHdr;
    PIMAGE_OPTIONAL_HEADER      pOptHdr;
    DWORD                       offDebugInfo;

    try {
        /*
         *      Based on wheither or not we find the dos (MZ) header
         *      at the beginning of the file, attempt to get a pointer
         *      to where the PE header is suppose to be.
         */

        p->dosHdr = (PIMAGE_DOS_HEADER) p->fptr;
        if (p->dosHdr->e_magic == IMAGE_DOS_SIGNATURE) {
            p->ntHdr = (PIMAGE_NT_HEADERS)
              ((DWORD)p->dosHdr->e_lfanew + p->fptr);
            p->fRomImage = FALSE;
        } else if (p->dosHdr->e_magic == IMAGE_SEPARATE_DEBUG_SIGNATURE) {
            p->sepHdr = (PIMAGE_SEPARATE_DEBUG_HEADER) p->fptr;
            p->dosHdr = NULL;
            p->fRomImage = FALSE;
            return FillInSeparateImagePointers(p);
        } else {
            p->romHdr = (PIMAGE_ROM_HEADERS) p->fptr;
            if (p->romHdr->FileHeader.SizeOfOptionalHeader ==
                                          IMAGE_SIZEOF_ROM_OPTIONAL_HEADER &&
                p->romHdr->OptionalHeader.Magic ==
                                          IMAGE_ROM_OPTIONAL_HDR_MAGIC) {
                //
                // its a rom image
                //
                p->fRomImage = TRUE;
                p->ntHdr = NULL;
                p->dosHdr = NULL;
            } else {
                p->fRomImage = FALSE;
                p->ntHdr = (PIMAGE_NT_HEADERS) p->fptr;
                p->dosHdr = NULL;
                p->romHdr = NULL;
            }
        }

        /*
         *  What comes next must be a PE header.  If not then pop out
         */

        if ( p->ntHdr ) {
            if ( p->dosHdr && (DWORD)p->dosHdr->e_lfanew > (DWORD)p->fsize ) {
                return FALSE;
            }

            if ( p->ntHdr->Signature != IMAGE_NT_SIGNATURE ) {
                return FALSE;
            }

            /*
             *  We did find a PE header so start setting pointers to various
             *      structures in the exe file.
             */

            pFileHdr = p->fileHdr = &p->ntHdr->FileHeader;
            pOptHdr = p->optHdr = &p->ntHdr->OptionalHeader;
        } else if (p->romHdr) {
            pFileHdr = p->fileHdr = &p->romHdr->FileHeader;
            pOptHdr = (PIMAGE_OPTIONAL_HEADER) &p->romHdr->OptionalHeader;
            p->optHdr = (PIMAGE_OPTIONAL_HEADER) &p->romHdr->OptionalHeader;
        } else {
            return FALSE;
        }

        if (!(pFileHdr->Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE)) {
            return FALSE;
        }

        if (pFileHdr->Characteristics & IMAGE_FILE_DEBUG_STRIPPED) {
            return(FALSE);
        }

        /*
         *  If they exists then get a pointer to the symbol table and
         *      the string table
         */

        if (pFileHdr->PointerToSymbolTable) {
            p->AllSymbols = (PIMAGE_SYMBOL)
                              (pFileHdr->PointerToSymbolTable + p->fptr);
            p->stringTable = (LPSTR)((ULONG)p->AllSymbols +
                           (IMAGE_SIZEOF_SYMBOL * pFileHdr->NumberOfSymbols));
            p->numberOfSymbols = pFileHdr->NumberOfSymbols;
        }

        p->numberOfSections = pFileHdr->NumberOfSections;

        if (p->romHdr) {

            sh = p->sectionHdrs = (PIMAGE_SECTION_HEADER) (p->romHdr+1);

            p->cDebugDir = 10;
            p->rgDebugDir = calloc(sizeof(IMAGE_DEBUG_DIRECTORY) * 10, 1);

            debugDir = 0;

            for (i=0; i<pFileHdr->NumberOfSections; i++, sh++) {
                if (!strcmp(sh->Name, ".rdata")) {
                    debugDir = (PIMAGE_DEBUG_DIRECTORY)(sh->PointerToRawData + p->fptr);
                }

                if (strncmp(sh->Name,".debug",8)==0) {
                    p->debugSection = sh;
                }
            }

            if (debugDir) {
                do {
                    if ((int)debugDir->Type > p->cDebugDir) {
                        p->cDebugDir += 10;
                        p->rgDebugDir = realloc((char *) p->rgDebugDir,
                                            p->cDebugDir * sizeof(p->rgDebugDir[0]));
                        memset(&p->rgDebugDir[p->cDebugDir-10],
                                0,
                                10*sizeof(IMAGE_DEBUG_DIRECTORY));
                    }
                    p->rgDebugDir[debugDir->Type] = debugDir;
                    debugDir++;
                } while (debugDir->Type != 0);
            }
        } else {

            /*
             *  Locate the debug directory
             */

            rva =
              pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;

            numDebugDirs =
              pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size /
                sizeof(IMAGE_DEBUG_DIRECTORY);

            if (numDebugDirs == 0) {
                return FALSE;
            }

            sh = p->sectionHdrs = IMAGE_FIRST_SECTION( p->ntHdr );

            /*
             * Find the section the debug directory is in.
             */

            for (i=0; i<pFileHdr->NumberOfSections; i++, sh++) {
                if (rva >= sh->VirtualAddress &&
                    rva < sh->VirtualAddress+sh->SizeOfRawData) {
                    break;
                }
            }

            /*
             *   For each debug directory, determine the debug directory
             *      type and cache any information about them.
             */

            debugDir = (PIMAGE_DEBUG_DIRECTORY) ( rva - sh->VirtualAddress +
                                                 sh->PointerToRawData +
                                                 p->fptr );

            for (li=0; li<numDebugDirs; li++, debugDir++) {
                if (((int) debugDir->Type) > p->cDebugDir) {
                    p->cDebugDir += 10;
                    p->rgDebugDir = realloc((char *) p->rgDebugDir,
                                            p->cDebugDir * sizeof(p->rgDebugDir[0]));
                    memset(&p->rgDebugDir[p->cDebugDir-10], 0,
                           10*sizeof(p->rgDebugDir[0]));
                }
                p->rgDebugDir[debugDir->Type] = debugDir;
                offDebugInfo = debugDir->AddressOfRawData;
            }

            /*
             *  Check to see if the debug information is mapped and if
             *      there is a section called .debug
             */

            sh = p->sectionHdrs = IMAGE_FIRST_SECTION( p->ntHdr );

            for (i=0; i<pFileHdr->NumberOfSections; i++, sh++) {
                if ((offDebugInfo >= sh->VirtualAddress) &&
                    (offDebugInfo < sh->VirtualAddress+sh->SizeOfRawData)) {
                    p->debugSection = sh;
                    break;
                }
            }
        }

        return TRUE;
    } except (EXCEPTION_EXECUTE_HANDLER) {
        return FALSE;
    }
}                               /* CalcuateNtImagePointers() */


SYMCVT.C

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    symcvt.c

Abstract:

    This module is the shell for the SYMCVT DLL.  The DLL's purpose is
    to convert the symbols for the specified image.  The resulting
    debug data must conform to the CODEVIEW spec.

    Currently this DLL converts COFF symbols and C7/C8 MAPTOSYM SYM files.

Author:

    Wesley A. Witt (wesw) 19-April-1993

Environment:

    Win32, User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define _SYMCVT_SOURCE_
#include "symcvt.h"

PUCHAR
ConvertSymbolsForImage(
                       HANDLE      hFile,
                       char *      fname
    )
/*++

Routine Description:

    Calls the appropriate conversion routine based on the file contents.

Arguments:

    hFile         -  file handle for the image (may be NULL)
    fname         -  file name for the image (may not have correct path)

Return Value:

    NULL             - could not convert the symbols
    Valid Pointer    - a pointer to malloc'ed memory that contains the
                       CODEVIEW symbols

--*/
{
    POINTERS   p;
    char       szDrive    [_MAX_DRIVE];
    char       szDir      [_MAX_DIR];
    char       szFname    [_MAX_FNAME];
    char       szExt      [_MAX_EXT];
    char       szSymName  [MAX_PATH];
    PUCHAR     rVal;

    if (!MapInputFile( &p, hFile, fname)) {

        rVal = NULL;

    } else if (CalculateNtImagePointers( &p.iptrs )) {

        //
        // we were able to compute the nt image pointers so this must be
        // a nt PE image.  now we must decide if there are coff symbols
        // if there are then we do the cofftocv conversion.
        //
        // btw, this is where someone would convert some other type of
        // symbols that are in a nt PE image. (party on garth..)
        //

//      if (!COFF_DIR(&p.iptrs)) {
        if (!p.iptrs.numberOfSymbols) {
            rVal = NULL;
        } else {
            ConvertCoffToCv( &p );
            rVal = p.pCvStart.ptr;
        }
        UnMapInputFile( &p );

    } else {

        UnMapInputFile ( &p );

        _splitpath( fname, szDrive, szDir, szFname, szExt );
        _makepath( szSymName, szDrive, szDir, szFname, "sym" );

        if (!MapInputFile( &p, NULL, szSymName)) {

            rVal = NULL;

        } else {

            //
            // must be a wow/dos app and there is a .sym file so lets to
            // the symtocv conversion
            //

            ConvertSymToCv( &p );
            UnMapInputFile( &p );

            rVal = p.pCvStart.ptr;
        }

    }

    return rVal;
}


SYMTOCV.C

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    symtocv.c

Abstract:

    This module handles the conversion activities requires for converting
    C7/C8 SYM files to CODEVIEW debug data.

Author:

    Wesley A. Witt (wesw) 13-April-1993

Environment:

    Win32, User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "cv.h"
#define _SYMCVT_SOURCE_
#include "symcvt.h"
#include "cvcommon.h"

typedef struct tagSYMNAME {
    BYTE        length;
    char        name[1];
} SYMNAME, *PSYMNAME;

typedef struct tagSYMSYMBOL {
    WORD        offset;
    SYMNAME     symName;
} SYMSYMBOL, *PSYMSYMBOL;

typedef struct tagSYMFILEHEADER {
    DWORD       fileSize;
    WORD        reserved1;
    WORD        numSyms;
    DWORD       reserved2;
    WORD        nextOffset;
    BYTE        reserved3;
    SYMNAME     symName;
} SYMFILEHEADER, *PSYMFILEHEADER;

typedef struct tagSYMHEADER {
    WORD        nextOffset;
    WORD        numSyms;
    WORD        reserved1;
    WORD        segment;
    BYTE        reserved2[12];
    SYMNAME     symName;
} SYMHEADER, *PSYMHEADER;

#define SIZEOFSYMFILEHEADER   16
#define SIZEOFSYMHEADER       21
#define SIZEOFSYMBOL           3

#define SYM_SEGMENT_NAME       0
#define SYM_SYMBOL_NAME        1
#define SYM_SEGMENT_ABS        2
#define SYM_SYMBOL_ABS         3

typedef struct tagENUMINFO {
    DATASYM16           *dataSym;
    DATASYM16           *dataSym2;
    DWORD               numsyms;
    SGI                 *sgi;
} ENUMINFO, *PENUMINFO;

typedef BOOL (CALLBACK* SYMBOLENUMPROC)(PSYMNAME pSymName, int symType,
                                        SEGMENT segment, UOFF16 offset,
                                        PENUMINFO pEnumInfo);

static VOID   GetSymName( PIMAGE_SYMBOL Symbol, PUCHAR StringTable,
                          char * s );
DWORD  CreateModulesFromSyms( PPOINTERS p );
DWORD  CreatePublicsFromSyms( PPOINTERS p );
DWORD  CreateSegMapFromSyms( PPOINTERS p );
static BOOL   EnumSymbols( PPOINTERS p, SYMBOLENUMPROC lpEnumProc,
                           PENUMINFO pEnumInfo  );

int             CSymSegs;

BOOL CALLBACK
SymbolCount(PSYMNAME pSymName, int symType, SEGMENT segment,
            UOFF16 offset, PENUMINFO pEnumInfo )
{
    if ((symType == SYM_SEGMENT_NAME) && (segment > 0)) {
        CSymSegs += 1;
    }
    pEnumInfo->numsyms++;
    return TRUE;
}

BOOL
ConvertSymToCv( PPOINTERS p )

/*++

Routine Description:

    This is the control function for the conversion of COFF to CODEVIEW
    debug data.  It calls individual functions for the conversion of
    specific types of debug data.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    TRUE     - conversion succeded
    FALSE    - conversion failed

--*/

{
    ENUMINFO            enumInfo;
    DWORD               dwSize;

    CSymSegs = 0;
    enumInfo.numsyms = 0;
    EnumSymbols( p, SymbolCount, &enumInfo );
    dwSize = (enumInfo.numsyms * (sizeof(DATASYM16) + 10)) + 256000;
    p->pCvCurr = p->pCvStart.ptr = malloc( dwSize );
    if (p->pCvStart.ptr == NULL) {
        return FALSE;
    }
    memset( p->pCvStart.ptr, 0, dwSize );

    try {

        CreateSignature( p );
        CreatePublicsFromSyms( p );
        CreateSymbolHashTable( p );
        CreateAddressSortTable( p );
        CreateSegMapFromSyms( p );
        CreateModulesFromSyms( p );
        CreateDirectories( p );
        p->pCvStart.ptr = realloc( p->pCvStart.ptr, p->pCvStart.size );
        return TRUE;

    } except (EXCEPTION_EXECUTE_HANDLER) {

        free( p->pCvStart.ptr );
        p->pCvStart.ptr = NULL;
        return FALSE;

    }
}

DWORD
CreateModulesFromSyms( PPOINTERS p )

/*++

Routine Description:

    Creates the individual CV module records.  There is one CV module
    record for each .FILE record in the COFF debug data.  This is true
    even if the COFF size is zero.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    The number of modules that were created.

--*/

{
    char                szDrive    [_MAX_DRIVE];
    char                szDir      [_MAX_DIR];
    char                szFname    [_MAX_FNAME];
    char                szExt      [_MAX_EXT];
    OMFModule           *m;
    int                 i;
    char *              pb;

    _splitpath( p->iptrs.szName, szDrive, szDir, szFname, szExt );

    m = (OMFModule *) p->pCvCurr;

    m->ovlNumber        = 0;
    m->iLib             = 0;
    m->cSeg             = CSymSegs;
    m->Style[0]         = 'C';
    m->Style[1]         = 'V';
    for (i=0; i<CSymSegs; i++) {
        m->SegInfo[i].Seg   = i+1;
        m->SegInfo[i].pad   = 0;
        m->SegInfo[i].Off   = 0;
        m->SegInfo[i].cbSeg = 0xffff;
    }
    pb = (char *) &m->SegInfo[CSymSegs];
    sprintf( &pb[1], "%s.c", szFname );
    pb[0] = strlen( &pb[1] );

    pb = (char *) NextMod(m);

    UpdatePtrs( p, &p->pCvModules, (LPVOID)pb, 1 );

    return 1;
}

BOOL CALLBACK
ConvertASymtoPublic(PSYMNAME pSymName, int symType, SEGMENT segment,
                    UOFF16 offset, PENUMINFO pEnumInfo )
{
    if (symType != SYM_SYMBOL_NAME) {
        return TRUE;
    }

    pEnumInfo->dataSym->rectyp     = S_PUB16;
    pEnumInfo->dataSym->seg        = segment;
    pEnumInfo->dataSym->off        = offset;
    pEnumInfo->dataSym->typind     = 0;
    pEnumInfo->dataSym->name[0]    = pSymName->length;
    strncpy( &pEnumInfo->dataSym->name[1], pSymName->name, pSymName->length );
    pEnumInfo->dataSym2 = NextSym16( pEnumInfo->dataSym );
    pEnumInfo->dataSym->reclen = (USHORT) ((DWORD)pEnumInfo->dataSym2 -
                                  (DWORD)pEnumInfo->dataSym) - 2;
    pEnumInfo->dataSym = pEnumInfo->dataSym2;
    pEnumInfo->numsyms++;

    return TRUE;
}

DWORD
CreatePublicsFromSyms( PPOINTERS p )

/*++

Routine Description:

    Creates the individual CV public symbol records.  There is one CV
    public record created for each COFF symbol that is marked as EXTERNAL
    and has a section number greater than zero.  The resulting CV publics
    are sorted by section and offset.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    The number of publics created.

--*/

{
    OMFSymHash          *omfSymHash;
    ENUMINFO            enumInfo;

    enumInfo.dataSym = (DATASYM16 *)
                 (PUCHAR)((DWORD)p->pCvCurr + sizeof(OMFSymHash));
    enumInfo.numsyms = 0;

    EnumSymbols( p, ConvertASymtoPublic, &enumInfo );

    omfSymHash = (OMFSymHash *) p->pCvCurr;
    UpdatePtrs(p, &p->pCvPublics, (LPVOID)enumInfo.dataSym,
               enumInfo.numsyms );

    omfSymHash->cbSymbol = p->pCvPublics.size - sizeof(OMFSymHash);
    omfSymHash->symhash  = 0;
    omfSymHash->addrhash = 0;
    omfSymHash->cbHSym   = 0;
    omfSymHash->cbHAddr  = 0;

    return enumInfo.numsyms;
}

BOOL CALLBACK
ConvertASegment( PSYMNAME pSymName, int symType, SEGMENT segment,
            UOFF16 offset, PENUMINFO pEnumInfo )
{
    if (symType != SYM_SEGMENT_NAME) {
        return TRUE;
    }

    if (segment == 0) {
        return TRUE;
    }

    pEnumInfo->numsyms++;

    pEnumInfo->sgi->sgf.fRead        = TRUE;
    pEnumInfo->sgi->sgf.fWrite       = TRUE;
    pEnumInfo->sgi->sgf.fExecute     = TRUE;
    pEnumInfo->sgi->sgf.f32Bit       = 0;
    pEnumInfo->sgi->sgf.fSel         = 0;
    pEnumInfo->sgi->sgf.fAbs         = 0;
    pEnumInfo->sgi->sgf.fGroup       = 1;
    pEnumInfo->sgi->iovl             = 0;
    pEnumInfo->sgi->igr              = 0;
    pEnumInfo->sgi->isgPhy           = (USHORT) pEnumInfo->numsyms;
    pEnumInfo->sgi->isegName         = 0;
    pEnumInfo->sgi->iclassName       = 0;
    pEnumInfo->sgi->doffseg          = offset;
    pEnumInfo->sgi->cbSeg            = 0xFFFF;
    pEnumInfo->sgi++;

    return TRUE;
}

DWORD
CreateSegMapFromSyms( PPOINTERS p )

/*++

Routine Description:

    Creates the CV segment map.  The segment map is used by debuggers
    to aid in address lookups.  One segment is created for each COFF
    section in the image.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    The number of segments in the map.

--*/

{
    SGM          *sgm;
    ENUMINFO     enumInfo;

    sgm = (SGM *) p->pCvCurr;
    enumInfo.sgi = (SGI *) ((DWORD)p->pCvCurr + sizeof(SGM));
    enumInfo.numsyms = 0;

    EnumSymbols( p, ConvertASegment, &enumInfo );

    sgm->cSeg = (USHORT)enumInfo.numsyms;
    sgm->cSegLog = (USHORT)enumInfo.numsyms;

    UpdatePtrs( p, &p->pCvSegMap, (LPVOID)enumInfo.sgi, enumInfo.numsyms );

    return enumInfo.numsyms;
}

BOOL
EnumSymbols( PPOINTERS p, SYMBOLENUMPROC lpEnumProc, PENUMINFO pEnumInfo )

/*++

Routine Description:

    This function enumerates all symbols ine the mapped SYM file

Arguments:

    p             -  pointer to a POINTERS structure
    lpEnumProc    -  function to be called once for each function
    pEnumInfo     -  data to be passed between the caller and the enum func

Return Value:

    TRUE     - success
    FALSE    - failure

--*/

{
    PSYMFILEHEADER      pSymFileHead;
    PSYMHEADER          pSymHead;
    PSYMHEADER          pSymHead2;
    PSYMSYMBOL          pSymSymbol;
    DWORD               i;
    DWORD               startPosition;
    DWORD               position;
    BOOL                fV86Mode;
    WORD                Segment;
    UOFF16              Offset;

    pSymFileHead = (PSYMFILEHEADER) p->iptrs.fptr;
    pSymSymbol = (PSYMSYMBOL) ((DWORD)pSymFileHead + SIZEOFSYMFILEHEADER +
                               pSymFileHead->symName.length + 1);

    if (!lpEnumProc(&pSymFileHead->symName, SYM_SEGMENT_ABS,
                    0, 0, pEnumInfo )) {
        return FALSE;
    }

    for (i=0; i<pSymFileHead->numSyms; i++) {
        if (!lpEnumProc(&pSymSymbol->symName, SYM_SYMBOL_ABS,
                        0, pSymSymbol->offset, pEnumInfo )) {
            return FALSE;
        }
        pSymSymbol = (PSYMSYMBOL) ((DWORD)pSymSymbol + SIZEOFSYMBOL +
                                   pSymSymbol->symName.length);
    }

    position = startPosition = ((LONG)pSymFileHead->nextOffset) << 4;

    //
    //  Determine if this is a V86Mode sym file.
    //
    //  We'll read the first two headers. If their segment numbers are
    //  not 1 and 2, then we assume V86Mode.
    //
    pSymHead  = (PSYMHEADER) ((DWORD)p->iptrs.fptr + position);
    position  = ((LONG)pSymHead->nextOffset) << 4;
    if ( position != startPosition && position != 0 ) {
        pSymHead2 = (PSYMHEADER) ((DWORD)p->iptrs.fptr + position);
    } else {
        pSymHead2 = NULL;
    }

    if ( pSymHead->segment == 1 &&
         (!pSymHead2 || pSymHead2->segment == 2)) {
        fV86Mode = FALSE;
    } else {
        fV86Mode = TRUE;
        Segment  = 0;
    }

    position = startPosition;

    do {
        pSymHead = (PSYMHEADER) ((DWORD)p->iptrs.fptr + position);
        pSymSymbol = (PSYMSYMBOL) ((DWORD)pSymHead + SIZEOFSYMHEADER +
                                   pSymHead->symName.length);

        if ( fV86Mode ) {
            Segment++;
            Offset  = pSymHead->segment;
        } else {
            Segment = pSymHead->segment;
            Offset  = 0;
        }

        position = ((LONG)pSymHead->nextOffset) << 4;

        if (!lpEnumProc( &pSymHead->symName, SYM_SEGMENT_NAME,
                        Segment, Offset, pEnumInfo )) {
            return FALSE;
        }

        for (i=0; i<pSymHead->numSyms; i++) {
            if (!lpEnumProc(&pSymSymbol->symName, SYM_SYMBOL_NAME,
                            Segment, pSymSymbol->offset,
                            pEnumInfo )) {
                return FALSE;
            }
            pSymSymbol = (PSYMSYMBOL) ((DWORD)pSymSymbol + SIZEOFSYMBOL +
                                       pSymSymbol->symName.length);
        }
    } while ( position != startPosition && position != 0 );

    return 0;
}


STRINGS.H

#if ! defined( _RES_STR_ )
#define _RES_STR_

#ifdef RESOURCES
#define RES_STR(a, b, c) b, c
STRINGTABLE
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
BEGIN
#else

enum _RESOURCEIDS {
#define RES_STR(a, b, c) a = b,
#endif

RES_STR(ERR_OPEN_INPUT_FILE,    1,      "The file '%s' could not be openned for reading")
RES_STR(ERR_INVALID_PE,         2,      "'%s' is not a valid PE exe file with debug info")
RES_STR(ERR_NO_DEST,            3,      "Need to specify a destination for the converted debug information")
RES_STR(ERR_OPEN_WRITE_FILE,    4,      "Cannot open the file '%s' for writing")
RES_STR(ERR_EDIT_DBG_FILE,      5,      "Cannot edit name file in DBG file")
RES_STR(ERR_MAP_FILE,           6,      "Cannot map the file '%s'")
RES_STR(ERR_NO_COFF,            7,      "No COFF debug information present to be converted")
RES_STR(ERR_NOT_MAPPED,         8,      "Cannot add CV info unless debug information is mapped")
RES_STR(ERR_COFF_TO_CV,         9,      "Cannot convert COFF debug information to CodeView debug information")
RES_STR(ERR_OP_UNKNOWN,         10,     "Operation '%s' is unknown")
RES_STR(ERR_NO_MEMORY,          11,     "Out of memory")
RES_STR(ERR_FILE_PTRS,          12,     "INTERNAL: cannot set file pointers")
RES_STR(ERR_SET_EOF,            13,     "INTERNAL: cannot set the end of file markder")
RES_STR(ERR_CHECKSUM_CALC,      14,     "INTERNAL: cannot compute the image checksum")

#ifdef RESOURCES
END
#else
};
#endif

#endif // _RES_STR_


SYMEDIT.C

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    symedit.c

Abstract:

Author:

    Wesley A. Witt (wesw) 19-April-1993

Environment:

    Win32, User Mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "symcvt.h"
#include "cv.h"
#include "strings.h"

#include <imagehlp.h>

//  prototypes for this module

BOOL    CalculateOutputFilePointers( PIMAGEPOINTERS pi, PIMAGEPOINTERS po );
void    ProcessCommandLineArgs( int argc, char *argv[] );
void    PrintCopyright( void );
void    PrintUsage( void );
void    FatalError( int, ... );
BOOL    MapOutputFile ( PPOINTERS p, char *fname, int );
void    ComputeChecksum(  char *szExeFile );
void    ReadDebugInfo( PPOINTERS p );
void    WriteDebugInfo( PPOINTERS p, BOOL);
void    MungeDebugHeadersCoffToCv( PPOINTERS  p, BOOL fAddCV );
void    MungeExeName( PPOINTERS p, char * szExeName );
void    DoCoffToCv(char *, char *, BOOL);
void    DoSymToCv(char *, char *, char *, char *);
void    DoNameChange(char *, char *, char *);
void    DoExtract(char *, char *, char *);
void    DoStrip(char *, char *);

IMAGE_DEBUG_DIRECTORY   DbgDirSpare;

#define AdjustPtr(ptr) (((ptr) != NULL) ? \
                          ((DWORD)ptr - (DWORD)pi->fptr + (DWORD)po->fptr) : \
                          ((DWORD)(ptr)))

int _CRTAPI1
main(
    int        argc,
    char *     argv[]
    )
/*++

Routine Description:

    Shell for this utility.

Arguments:

    argc     - argument count
    argv     - argument pointers

Return Value:

    0        - image was converted
    >0       - image could not be converted

--*/

{
    // Scan the command line and check what operations we are doing

    ProcessCommandLineArgs( argc, argv );
    return 0;
}

__inline void PrintCopyright( void )
{
    puts( "\nMicrosoft(R) Windows NT SymEdit Version 1.0\n"
          "(C) 1989-1995 Microsoft Corp. All rights reserved.\n");
}

__inline void PrintUsage( void )
{
    PrintCopyright();
    puts ("\nUsage: SYMEDIT <OPERATION> -q -o<file out> <file in>\n\n"
          "\t<OPERATION> is:\n"
          "\tC\tModify CodeView symbol information\n"
          "\tN\tEdit name field\n"
          "\tX\tExtract debug information\n"
          "\tS\tStrip all debug information\n\n"
          "Options:\n"
          "\t-a\t\tAdd CodeView debug info to file\n"
          "\t-n<name>\tName to change to\n"
          "\t-o<file>\tspecify output file\n"
          "\t-q\t\tquiet mode\n"
          "\t-r\t\tReplace COFF debug info with CV info\n"
          "\t-s<file>\tSym file source");
}

void
ProcessCommandLineArgs(
    int argc,
    char *argv[]
    )

/*++

Routine Description:

    Processes the command line arguments and sets global flags to
    indicate the user's desired behavior.

Arguments:

    argc     - argument count
    argv     - argument pointers

Return Value:

    void

--*/

{
    int     i;
    BOOL    fQuiet = FALSE;
    BOOL    fSilent = FALSE;
    char *  szOutputFile = NULL;
    char *  szInputFile = NULL;
    char *  szExeName = NULL;
    char *  szDbgFile = NULL;
    char *  szSymFile = NULL;
    int     iOperation;
    BOOLEAN fAddCV = FALSE;

    // Minimun number of of arguments is 2 -- program and operation

    if (argc < 2 ||
        (strcmp(argv[1], "-?") == 0) ||
        (strcmp(argv[1], "?") == 0) )
    {
        PrintUsage();
        exit(1);
    }

    // All operations on 1 character wide

    if (argv[1][1] != 0) {
        FatalError(ERR_OP_UNKNOWN, argv[1]);
    }

    // Validate the operation

    switch( argv[1][0] ) {
        case 'C':
        case 'N':
        case 'X':
        case 'S':
            iOperation = argv[1][0];
            break;
        default:
            FatalError(ERR_OP_UNKNOWN, argv[1]);
    }

    // Parse out any other switches on the command line

    for (i=2; i<argc; i++) {
        if ((argv[i][0] == '-') || (argv[i][0] == '/')) {
            switch (toupper(argv[i][1])) {

                // Add the CV debug information section rather than
                // replace the COFF section with the CV info.

                case 'A':
                    fAddCV = TRUE;
                    break;

                // Specify the output name for the DBG file

                case 'D':
                    if (argv[i][2] == 0) {
                        i += 1;
                        szDbgFile = argv[i];
                    } else {
                        szDbgFile = &argv[i][2];
                    }
                    break;

                // Specify a new name to shove into the name of the
                // debuggee field in the Misc. Debug info field

                case 'N':
                    if (argv[i][2] == 0) {
                        i += 1;
                        szExeName = argv[i];
                    } else {
                        szExeName = &argv[i][2];
                    }
                    break;

                // Specify the name of the output file

                case 'O':
                    if (argv[i][2] == 0) {
                        i += 1;
                        szOutputFile = argv[i];
                    } else {
                        szOutputFile = &argv[i][2];
                    }
                    break;

                // Be quite and don't put out the banner

                case 'Q':
                    fQuiet = TRUE;
                    fSilent = TRUE;
                    break;

                //  Replace COFF debug information with CODEVIEW debug information

                case 'R':
                    break;

                //  Convert a Symbol File to CV info

                case 'S':
                    if (argv[i][2] == 0) {
                        i += 1;
                        szSymFile = argv[i];
                    } else {
                        szSymFile = &argv[i][2];
                    }
                    break;

                // Print the command line options

                case '?':
                    PrintUsage();
                    exit(1);
                    break;

                // Unrecognized option

                default:
                    FatalError( ERR_OP_UNKNOWN, argv[i] );
                    break;
            }
        } else {
            //  No leading switch character -- must be a file name

            szInputFile = &argv[i][0];

            //  Process the file(s)

            if (!fQuiet) {
                PrintCopyright();
                fQuiet = TRUE;
            }

            if (!fSilent) {
                printf("processing file: %s\n", szInputFile );
            }

            //  Do switch validation cheching and setup any missing global variables

            switch ( iOperation ) {

                // For conversions -- there are three types
                //
                //      1.  Coff to CV -- add
                //      2.  Coff to CV -- replace
                //      3.  SYM to CV --- add
                //      4.  SYM to CV -- seperate file
                //
                //      Optional input file (not needed for case 4)
                //      Optional output file
                //      Optional sym file (implys sym->CV)
                //      Optional DBG file

                case 'C':
                    if (szSymFile == NULL) {
                        DoCoffToCv(szInputFile, szOutputFile, fAddCV);
                    } else {
                        DoSymToCv(szInputFile, szOutputFile, szDbgFile, szSymFile);
                    }
                    szInputFile = NULL;
                    szOutputFile = NULL;
                    szDbgFile = NULL;
                    szSymFile = NULL;
                    break;

                //  For changing the name of the debuggee --
                //      Must specify input file
                //      Must specify new name
                //      Optional output file

                case 'N':
                    DoNameChange(szInputFile, szOutputFile, szExeName);
                    szInputFile = NULL;
                    szOutputFile = NULL;
                    szExeName = NULL;
                    break;

                //  For extraction of debug information
                //      Must specify input file
                //      Optional output file name
                //      Optional debug file name

                case 'X':
                    DoExtract(szInputFile, szOutputFile, szDbgFile);
                    break;

                //  For full strip of debug information
                //      Must specify input file
                //      Optional output file

                case 'S':
                    DoStrip(szInputFile, szOutputFile);
                    break;
            }
        }
    }
    return;
}

void
ReadDebugInfo(
    PPOINTERS   p
    )

/*++

Routine Description:

    This function will go out and read in all of the debug information
    into memory -- this is required because the input and output
    files might be the same, if so then writing out informaiton may
    destory data we need at a later time.

Arguments:

    p   - Supplies a pointer to the structure describing the debug info file

Return Value:

    None.

--*/

{
    int                         i;
//    int                         cb;
//    char *                      pb;
//    PIMAGE_COFF_SYMBOLS_HEADER  pCoffDbgInfo;

    // Allocate space to save pointers to debug info

    p->iptrs.rgpbDebugSave = (PCHAR *) malloc(p->iptrs.cDebugDir * sizeof(PCHAR));
    memset(p->iptrs.rgpbDebugSave, 0, p->iptrs.cDebugDir * sizeof(PCHAR));

    // Check each possible debug type record

    for (i=0; i<p->iptrs.cDebugDir; i++) {

        // If there was debug information then copy over the
        // description block and cache in the actual debug data.

        if (p->iptrs.rgDebugDir[i] != NULL) {
            p->iptrs.rgpbDebugSave[i] =
              malloc( p->iptrs.rgDebugDir[i]->SizeOfData );
            if (p->iptrs.rgpbDebugSave[i] == NULL) {
                FatalError(ERR_NO_MEMORY);
            }
            __try {
                memcpy(p->iptrs.rgpbDebugSave[i],
                       p->iptrs.fptr +
                       p->iptrs.rgDebugDir[i]->PointerToRawData,
                       p->iptrs.rgDebugDir[i]->SizeOfData );
            } __except(EXCEPTION_EXECUTE_HANDLER ) {
                free(p->iptrs.rgpbDebugSave[i]);
                p->iptrs.rgpbDebugSave[i] = NULL;
            }
        }
    }
    return;
}

void
WriteDebugInfo(
    PPOINTERS   p,
    BOOL        fAddCV
    )
/*++

Routine Description:

    This function will go out and read in all of the debug information
    into memory -- this is required because the input and output
    files might be the same, if so then writing out informaiton may
    destory data we need at a later time.

Arguments:

    p   - Supplies a pointer to the structure describing the debug info file

Return Value:

    None.

--*/

{
    ULONG  PointerToDebugData = 0; //  Offset from the start of the file
                                   //  to the current location to write
                                   //  debug information out.
    ULONG  BaseOfDebugData = 0;
    int    i, flen;
    PIMAGE_DEBUG_DIRECTORY  pDir, pDbgDir = NULL;

    if (p->optrs.debugSection) {
        BaseOfDebugData = PointerToDebugData =
          p->optrs.debugSection->PointerToRawData;
    } else if (p->optrs.sepHdr) {
        BaseOfDebugData =  PointerToDebugData =
          sizeof(IMAGE_SEPARATE_DEBUG_HEADER) +
          p->optrs.sepHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER) +
          p->optrs.sepHdr->ExportedNamesSize;
    }

    //  Step 2. If the debug information is mapped, we know this
    //          from the section headers, then we may need to write
    //          out a new debug director to point to the debug information

    if (fAddCV) {
        if (p->optrs.optHdr) {
            p->optrs.optHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].
              VirtualAddress = p->optrs.debugSection->VirtualAddress;
            p->optrs.optHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size +=
              sizeof(IMAGE_DEBUG_DIRECTORY);
        } else if (p->optrs.sepHdr) {
            p->optrs.sepHdr->DebugDirectorySize += sizeof(IMAGE_DEBUG_DIRECTORY);
        } else {
            exit(1);
        }

        if (p->optrs.sepHdr) {
            pDbgDir = (PIMAGE_DEBUG_DIRECTORY) malloc(p->optrs.cDebugDir * sizeof(IMAGE_DEBUG_DIRECTORY));
            for (i=0; i<p->optrs.cDebugDir; i++) {
                if (p->optrs.rgDebugDir[i] != NULL) {
                    pDbgDir[i] = *(p->optrs.rgDebugDir[i]);
                    p->optrs.rgDebugDir[i] = &pDbgDir[i];
                }
            }
        }
        for (i=0; i<p->optrs.cDebugDir; i++) {
            if (p->optrs.rgDebugDir[i]) {
                pDir = (PIMAGE_DEBUG_DIRECTORY) (PointerToDebugData +
                                                 p->optrs.fptr);
                *pDir = *(p->optrs.rgDebugDir[i]);
                p->optrs.rgDebugDir[i] = pDir;
                PointerToDebugData += sizeof(IMAGE_DEBUG_DIRECTORY);
            }
        }
    }

    // Step 3.  For every debug info type, write out the debug information
    //          and update any header information required

    for (i=0; i<p->optrs.cDebugDir; i++) {
        if (p->optrs.rgDebugDir[i] != NULL) {
            if (p->optrs.rgpbDebugSave[i] != NULL) {
                p->optrs.rgDebugDir[i]->PointerToRawData =
                  PointerToDebugData;
                if (p->optrs.debugSection) {
                    p->optrs.rgDebugDir[i]->AddressOfRawData =
                      p->optrs.debugSection->VirtualAddress +
                        PointerToDebugData - BaseOfDebugData;
                }
                memcpy(p->optrs.fptr + PointerToDebugData,
                       p->optrs.rgpbDebugSave[i],
                       p->optrs.rgDebugDir[i]->SizeOfData);

                if ((i == IMAGE_DEBUG_TYPE_COFF) &&
                    (p->optrs.fileHdr != NULL)) {

                    PIMAGE_COFF_SYMBOLS_HEADER  pCoffDbgInfo;
                    pCoffDbgInfo = (PIMAGE_COFF_SYMBOLS_HEADER)p->optrs.rgpbDebugSave[i];
                    p->optrs.fileHdr->PointerToSymbolTable =
                      PointerToDebugData + pCoffDbgInfo->LvaToFirstSymbol;
                }
            }
            PointerToDebugData += p->optrs.rgDebugDir[i]->SizeOfData;
        }
    }

    // Step 4.  Clean up any COFF structures if we are replacing
    //          the coff information with CV info.

    if ((p->optrs.rgDebugDir[IMAGE_DEBUG_TYPE_COFF] == NULL) &&
        (p->optrs.fileHdr != NULL)) {

        // Since there is no coff debug information -- clean out
        // both fields pointing to the debug info

        p->optrs.fileHdr->PointerToSymbolTable = 0;
        p->optrs.fileHdr->NumberOfSymbols = 0;
    }

    // Step 5.  Correct the alignments if needed.  If there is a real .debug
    //          section in the file (i.e. it is mapped) then update it.

    if (p->optrs.debugSection) {
        p->optrs.debugSection->SizeOfRawData =
          FileAlign(PointerToDebugData - BaseOfDebugData);

        // update the optional header with the new image size

        p->optrs.optHdr->SizeOfImage =
          SectionAlign(p->optrs.debugSection->VirtualAddress +
                       p->optrs.debugSection->SizeOfRawData);
        p->optrs.optHdr->SizeOfInitializedData +=
          p->optrs.debugSection->SizeOfRawData;
    }

    // calculate the new file size

    if (p->optrs.optHdr != NULL) {
        flen = FileAlign(PointerToDebugData);
    } else {
        flen = PointerToDebugData;
    }

    // finally, update the eof pointer and close the file

    UnmapViewOfFile( p->optrs.fptr );

    if (!SetFilePointer( p->optrs.hFile, flen, 0, FILE_BEGIN )) {
        FatalError( ERR_FILE_PTRS );
    }

    if (!SetEndOfFile( p->optrs.hFile )) {
        FatalError( ERR_SET_EOF );
    }

    CloseHandle( p->optrs.hFile );

    // Exit -- we are done.

    return;
}

void
MungeDebugHeadersCoffToCv(
    PPOINTERS   p,
    BOOL        fAddCV
    )

/*++

Routine Description:

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)

Return Value:

    void

--*/

{
    if (!fAddCV) {
        CV_DIR(&p->optrs) = COFF_DIR(&p->optrs);
        COFF_DIR(&p->optrs) = 0;
    } else {
        CV_DIR(&p->optrs) = &DbgDirSpare;
        *(COFF_DIR(&p->optrs)) = *(COFF_DIR(&p->iptrs));
    };

    *CV_DIR(&p->optrs) = *(COFF_DIR(&p->iptrs));
    CV_DIR(&p->optrs)->Type = IMAGE_DEBUG_TYPE_CODEVIEW;
    CV_DIR(&p->optrs)->SizeOfData =  p->pCvStart.size;
    p->optrs.rgpbDebugSave[IMAGE_DEBUG_TYPE_CODEVIEW] = p->pCvStart.ptr;

    return;
}

BOOL
MapOutputFile (
    PPOINTERS p,
    char *fname,
    int cb
    )

/*++

Routine Description:

    Maps the output file specified by the fname argument and saves the
    file handle & file pointer in the POINTERS structure.

Arguments:

    p        - pointer to a POINTERS structure (see symcvt.h)
    fname    - ascii string for the file name

Return Value:

    TRUE     - file mapped ok
    FALSE    - file could not be mapped

--*/

{
    BOOL    rval;
    HANDLE  hMap   = NULL;
    DWORD   oSize;

    rval = FALSE;

    p->optrs.hFile = CreateFile( fname,
                        GENERIC_READ | GENERIC_WRITE,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        NULL,
                        OPEN_ALWAYS,
                        0,
                        NULL );

    if (p->optrs.hFile == INVALID_HANDLE_VALUE) {
       goto exit;
    }

    oSize = p->iptrs.fsize;
    if (p->pCvStart.ptr != NULL) {
        oSize += p->pCvStart.size;
    }
    oSize += cb;
    oSize += p->iptrs.cDebugDir * sizeof(IMAGE_DEBUG_DIRECTORY);

    hMap = CreateFileMapping( p->optrs.hFile, NULL, PAGE_READWRITE,
                                0, oSize, NULL );

    if (hMap == NULL) {
       goto exit;
    }

    p->optrs.fptr = MapViewOfFile( hMap, FILE_MAP_WRITE, 0, 0, 0 );

    CloseHandle(hMap);

    if (p->optrs.fptr == NULL) {
       goto exit;
    }
    rval = TRUE;
exit:
    return rval;
}

BOOL
CalculateOutputFilePointers(
    PIMAGEPOINTERS pi,
    PIMAGEPOINTERS po
    )

/*++

Routine Description:

    This function calculates the output file pointers based on the
    input file pointers.  The same address is used but they are all
    re-based off the output file's file pointer.

Arguments:

    p        - pointer to a IMAGEPOINTERS structure (see symcvt.h)

Return Value:

    TRUE     - pointers were created
    FALSE    - pointers could not be created

--*/
{
    int i;

    // fixup the pointers relative the fptr for the output file
    po->dosHdr       = (PIMAGE_DOS_HEADER)      AdjustPtr(pi->dosHdr);
    po->ntHdr        = (PIMAGE_NT_HEADERS)      AdjustPtr(pi->ntHdr);
    po->fileHdr      = (PIMAGE_FILE_HEADER)     AdjustPtr(pi->fileHdr);
    po->optHdr       = (PIMAGE_OPTIONAL_HEADER) AdjustPtr(pi->optHdr);
    po->sectionHdrs  = (PIMAGE_SECTION_HEADER)  AdjustPtr(pi->sectionHdrs);
    po->sepHdr       = (PIMAGE_SEPARATE_DEBUG_HEADER) AdjustPtr(pi->sepHdr);
    po->debugSection = (PIMAGE_SECTION_HEADER)  AdjustPtr(pi->debugSection);
    po->AllSymbols   = (PIMAGE_SYMBOL)          AdjustPtr(pi->AllSymbols);
    po->stringTable  = (PUCHAR)                 AdjustPtr(pi->stringTable);

    // move the data from the input file to the output file
    memcpy( po->fptr, pi->fptr, pi->fsize );

    po->cDebugDir = pi->cDebugDir;
    po->rgDebugDir = malloc(po->cDebugDir * sizeof(po->rgDebugDir[0]));
    memset(po->rgDebugDir, 0, po->cDebugDir * sizeof(po->rgDebugDir[0]));

    for (i=0; i<po->cDebugDir; i++) {
        po->rgDebugDir[i] = (PIMAGE_DEBUG_DIRECTORY) AdjustPtr(pi->rgDebugDir[i]);
    }
    po->rgpbDebugSave = pi->rgpbDebugSave;

    return TRUE;
}

void
FatalError(
    int  idMsg,
    ...
    )
/*++

Routine Description:

    Prints a message string to stderr and then exits.

Arguments:

    s        - message string to be printed

Return Value:

    void

--*/

{
    va_list marker;
    char    rgchFormat[256];
    char    rgch[256];

    LoadString(GetModuleHandle(NULL), idMsg, rgchFormat, sizeof(rgchFormat));

    va_start(marker, idMsg);
    vsprintf(rgch, rgchFormat, marker);
    va_end(marker);

    fprintf(stderr, "%s\n", rgch);

    exit(1);
}

void
ComputeChecksum(
    char *szExeFile
    )

/*++

Routine Description:

    Computes a new checksum for the image by calling imagehlp.dll

Arguments:

    szExeFile - exe file name

Return Value:

    void

--*/

{
    DWORD              dwHeaderSum = 0;
    DWORD              dwCheckSum = 0;
    HANDLE             hFile;
    DWORD              cb;
    IMAGE_DOS_HEADER   dosHdr;
    IMAGE_NT_HEADERS   ntHdr;

    if (MapFileAndCheckSum(szExeFile, &dwHeaderSum, &dwCheckSum) != CHECKSUM_SUCCESS) {
        FatalError( ERR_CHECKSUM_CALC );
    }

    hFile = CreateFile( szExeFile,
                        GENERIC_READ | GENERIC_WRITE,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        0,
                        NULL
                      );

    // seek to the beginning of the file
    SetFilePointer( hFile, 0, 0, FILE_BEGIN );

    // read in the dos header
    if ((ReadFile(hFile, &dosHdr, sizeof(dosHdr), &cb, 0) == FALSE) || (cb != sizeof(dosHdr))) {
        FatalError( ERR_CHECKSUM_CALC );
    }

    // read in the pe header
    if ((dosHdr.e_magic != IMAGE_DOS_SIGNATURE) ||
        (SetFilePointer(hFile, dosHdr.e_lfanew, 0, FILE_BEGIN) == -1L)) {
        FatalError( ERR_CHECKSUM_CALC );
    }

    // read in the nt header
    if ((!ReadFile(hFile, &ntHdr, sizeof(ntHdr), &cb, 0)) || (cb != sizeof(ntHdr))) {
        FatalError( ERR_CHECKSUM_CALC );
    }

    if (SetFilePointer(hFile, dosHdr.e_lfanew, 0, FILE_BEGIN) == -1L) {
        FatalError( ERR_CHECKSUM_CALC );
    }

    ntHdr.OptionalHeader.CheckSum = dwCheckSum;

    if (!WriteFile(hFile, &ntHdr, sizeof(ntHdr), &cb, NULL)) {
        FatalError( ERR_CHECKSUM_CALC );
    }

    CloseHandle(hFile);
    return;
}

void
MungeExeName(
    PPOINTERS   p,
    char *      szExeName
    )
/*++

Routine Description:

    description-of-function.

Arguments:

    argument-name - Supplies | Returns description of argument.
    .
    .

Return Value:

    None.

--*/

{
    PIMAGE_DEBUG_MISC   pMiscIn;
    PIMAGE_DEBUG_MISC   pMiscOut;
    int                 cb;
    int                 i;

    for (i=0; i<p->iptrs.cDebugDir; i++) {
        if (p->optrs.rgDebugDir[i] != 0) {
            *(p->optrs.rgDebugDir[i]) = *(p->iptrs.rgDebugDir[i]);
        }
    }

    pMiscIn = (PIMAGE_DEBUG_MISC)
      p->iptrs.rgpbDebugSave[IMAGE_DEBUG_TYPE_MISC];

    if (p->optrs.rgDebugDir[IMAGE_DEBUG_TYPE_MISC] == NULL) {
        p->optrs.rgDebugDir[IMAGE_DEBUG_TYPE_MISC] = &DbgDirSpare;
        memset(&DbgDirSpare, 0, sizeof(DbgDirSpare));
    }

    pMiscOut = (PIMAGE_DEBUG_MISC)
      p->optrs.rgpbDebugSave[IMAGE_DEBUG_TYPE_MISC] =
      malloc(p->optrs.rgDebugDir[IMAGE_DEBUG_TYPE_MISC]->SizeOfData +
             strlen(szExeName));
    cb = p->optrs.rgDebugDir[IMAGE_DEBUG_TYPE_MISC]->SizeOfData;

    while ( cb > 0 ) {
        if (pMiscIn->DataType == IMAGE_DEBUG_MISC_EXENAME) {
            pMiscOut->DataType = IMAGE_DEBUG_MISC_EXENAME;
            pMiscOut->Length = (sizeof(IMAGE_DEBUG_MISC) +
                                strlen(szExeName) + 3) & ~3;
            pMiscOut->Unicode = FALSE;
            strcpy(&pMiscOut->Data[0], szExeName);
            szExeName = NULL;
        } else {
            memcpy(pMiscOut, pMiscIn, pMiscIn->Length);
        }

        p->optrs.rgDebugDir[IMAGE_DEBUG_TYPE_MISC]->SizeOfData +=
          (pMiscOut->Length - pMiscIn->Length);

        cb -= pMiscIn->Length;
        pMiscIn = (PIMAGE_DEBUG_MISC) (((char *) pMiscIn) + pMiscIn->Length);
        pMiscOut = (PIMAGE_DEBUG_MISC) (((char *) pMiscOut) + pMiscOut->Length);
    }

    if (szExeName) {
        pMiscOut->DataType = IMAGE_DEBUG_MISC_EXENAME;
        pMiscOut->Length = (sizeof(IMAGE_DEBUG_MISC) +
                            strlen(szExeName) + 3) & ~3;
        pMiscOut->Unicode = FALSE;
        strcpy(&pMiscOut->Data[0], szExeName);
    }

    return;
}

/***    DoCoffToCv
 *
 *
 */

void DoCoffToCv(
    char * szInput,
    char * szOutput,
    BOOL fAddCV
    )
{
    POINTERS    p;

    // Do default checking

    if (szOutput == NULL) {
        szOutput = szInput;
    }

    // Open the input file name and setup the pointers into the file

    if (!MapInputFile( &p, NULL, szInput )) {
        FatalError( ERR_OPEN_INPUT_FILE, szInput );
    }

    // Now, if we thing we are playing with PE exes then we need
    // to setup the pointers into the map file

    if (!CalculateNtImagePointers( &p.iptrs )) {
        FatalError( ERR_INVALID_PE, szInput );
    }

    // We are about to try and do the coff to cv symbol conversion.
    //
    // Verify that the operation is legal.
    //
    // 1.  We need to have coff debug information to start with
    // 2.  If the debug info is not mapped then we must not
    //     be trying to add CodeView info.

    if ((p.iptrs.AllSymbols == NULL) || (COFF_DIR(&p.iptrs) == NULL)) {
        FatalError( ERR_NO_COFF );
    }

    if (fAddCV && (p.iptrs.debugSection == 0) && (p.iptrs.sepHdr == NULL)) {
        FatalError( ERR_NOT_MAPPED );
    }

    // Now go out an preform the acutal conversion.

    if (!ConvertCoffToCv( &p )) {
        FatalError( ERR_COFF_TO_CV );
    }

    // Read in any additional debug information in the file

    ReadDebugInfo(&p);

    // Open the output file and adjust the pointers so that we are ok.

    if (!MapOutputFile( &p, szOutput, 0 )) {
        FatalError( ERR_MAP_FILE, szOutput );
    }

    CalculateOutputFilePointers( &p.iptrs, &p.optrs );

    // Munge the various debug information structures to preform the correct
    // operations

    MungeDebugHeadersCoffToCv( &p, fAddCV );

    // Free our handles on the input file

    UnMapInputFile(&p);

    // Write out the debug information to the end of the exe

    WriteDebugInfo( &p, fAddCV );

    // and finally compute the checksum

    if (p.iptrs.fileHdr != NULL) {
        ComputeChecksum( szOutput );
    }

    return;
}

/***    DoSymToCv
 *
 */

void
DoSymToCv(
    char * szInput,
    char * szOutput,
    char * szDbg,
    char * szSym
    )
{
    POINTERS    p;
    HANDLE      hFile;
    DWORD       cb;
    OFSTRUCT    ofs;

    // Open the input file name and setup the pointers into the file

    if (!MapInputFile( &p, NULL, szSym )) {
        FatalError(ERR_OPEN_INPUT_FILE, szSym);
    }

    // Now preform the desired operation

    if ((szOutput == NULL) && (szDbg == NULL)) {
        szOutput = szInput;
    }

    ConvertSymToCv( &p );

    if (szOutput) {
        if (szOutput != szInput) {
            if (OpenFile(szInput, &ofs, OF_EXIST) == 0) {
                FatalError(ERR_OPEN_INPUT_FILE, szInput);
            }
            if (CopyFile(szInput, szOutput, FALSE) == 0) {
                FatalError(ERR_OPEN_WRITE_FILE, szOutput);
            }
        }
        hFile = CreateFile(szOutput, GENERIC_WRITE, 0, NULL,
                           CREATE_ALWAYS,
                           FILE_ATTRIBUTE_NORMAL,  NULL);
        if (hFile == INVALID_HANDLE_VALUE) {

FatalError(ERR_OPEN_WRITE_FILE, szOutput);
        }
        SetFilePointer(hFile, 0, 0, FILE_END);
    } else if (szDbg) {
        hFile = CreateFile(szDbg, GENERIC_WRITE, 0, NULL,
                               OPEN_ALWAYS,
                               FILE_ATTRIBUTE_NORMAL,  NULL);
        if (hFile == INVALID_HANDLE_VALUE) {
            FatalError(ERR_OPEN_WRITE_FILE, szDbg);
        }
    }

    WriteFile(hFile, p.pCvStart.ptr, p.pCvStart.size, &cb, NULL);
    CloseHandle(hFile);

    return;
}

void
DoNameChange(
    char * szInput,
    char * szOutput,
    char * szNewName
    )
{
    POINTERS    p;

    // Open the input file name and setup the pointers into the file

    if (!MapInputFile( &p, NULL, szInput )) {
        FatalError(ERR_OPEN_INPUT_FILE, szInput);
    }

    // Now, if we thing we are playing with PE exes then we need
    // to setup the pointers into the map file

    if (!CalculateNtImagePointers( &p.iptrs )) {
        FatalError(ERR_INVALID_PE, szInput);
    }

    //  Now preform the desired operation

    if (szOutput == NULL) {
        szOutput = szInput;
    }

    if (szNewName == NULL) {
        szNewName = szOutput;
    }

    if (p.iptrs.sepHdr != NULL) {
        FatalError(ERR_EDIT_DBG_FILE);
    }

    // Read in all of the debug information

    ReadDebugInfo(&p);

    // Open the output file and adjust the pointers.

    if (!MapOutputFile(&p, szOutput,
                       sizeof(szNewName) * 2 + sizeof(IMAGE_DEBUG_MISC))) {
        FatalError(ERR_MAP_FILE, szOutput);
    }

    CalculateOutputFilePointers(&p.iptrs, &p.optrs);

    // Munge the name of the file

    MungeExeName(&p, szNewName);

    // Close the input file

    UnMapInputFile(&p);

    // Write out the debug information to the end of the exe

    WriteDebugInfo(&p, FALSE);

    // and finally compute the checksum

    if (p.iptrs.fileHdr != NULL) {
        ComputeChecksum( szOutput );
    }

    return;
}

void
DoStrip(
    char * szInput,
    char * szOutput
    )
{
    char OutputFile[_MAX_PATH];

    // Make sure we only have the path to the output file (it will always be
    //  named filename.DBG)

    if (szOutput != NULL) {
        CopyFileA(szInput, szOutput, FALSE);
    }

    SplitSymbols(szOutput, NULL, OutputFile, SPLITSYM_EXTRACT_ALL);

    // Always delete the output file.

    DeleteFileA(OutputFile);

    return;
}

void
DoExtract(
    char * szInput,
    char * szOutput,
    char * szDbgFile
    )
{
    char OutputFile[_MAX_PATH];
    char szExt[_MAX_EXT];
    char szFileName[_MAX_FNAME];

    if (szOutput != NULL) {
        CopyFileA(szInput, szOutput, FALSE);
        szInput = strdup(szOutput);
        _splitpath(szOutput, NULL, NULL, szFileName, szExt);
        *(szOutput + strlen(szOutput) - strlen(szFileName) - strlen(szExt)) = '\0';
    }

    SplitSymbols(szInput, szOutput, OutputFile, 0);

    CopyFileA(szDbgFile, OutputFile, TRUE);

    if (szOutput) {
        free(szInput);
    }

    return;
}


README.TXT

PFMON will run only on Windows NT.  It requires psapi.dll which is
distributed with the Win32 SDK.

USAGE: pfmon [pfmon switches] command-line-of-application


ERROR.C

/*++

Copyright 1995 - 1998 Microsoft Corporation

Module Name:

    error.c

Abstract:

    Error handle module for the pfmon program

Author:

    Mark Lucovsky (markl) 26-Jan-1995

Revision History:

--*/

#include "pfmonp.h"

VOID
CDECL
DeclareError(
    UINT ErrorCode,
    UINT SupplementalErrorCode,
    ...
    )
{
    va_list arglist;
    HMODULE ModuleHandle;
    DWORD Flags, Size;
    UCHAR MessageBuffer[ 512 ];

    va_start( arglist, SupplementalErrorCode );

    if ((ErrorCode & 0x0FFF0000) >> 16 == FACILITY_APPLICATION) {
        ModuleHandle = PfmonModuleHandle;
        Flags = FORMAT_MESSAGE_FROM_HMODULE;
        }
    else {
        ModuleHandle = NULL;
        Flags = FORMAT_MESSAGE_FROM_SYSTEM;
        }

    Size = FormatMessage( Flags,
                          (LPCVOID)ModuleHandle,
                          ErrorCode,
                          0,
                          MessageBuffer,
                          sizeof( MessageBuffer ),
                          &arglist
                        );
    va_end( arglist );

    if (Size != 0) {
        fprintf( stderr, "PFMON: %s", MessageBuffer );
        }
    else {
        fprintf( stderr, "PFMON: Unable to get message text for %08x\n", ErrorCode );
        }

    if (ModuleHandle == PfmonModuleHandle &&
        SupplementalErrorCode != 0 &&
        SupplementalErrorCode != ERROR_GEN_FAILURE
       ) {

        ModuleHandle = NULL;
        Flags = FORMAT_MESSAGE_FROM_SYSTEM;
        Size = FormatMessage( Flags,
                              (LPCVOID)ModuleHandle,
                              SupplementalErrorCode,
                              0,
                              MessageBuffer,
                              sizeof( MessageBuffer ),
                              NULL
                            );
        if (Size != 0) {
            while (Size != 0 && MessageBuffer[ Size ] <= ' ') {
                MessageBuffer[ Size ] = '\0';
                Size -= 1;
                }

            printf( "          '%s'\n", MessageBuffer );
            }
        else {
            printf( "PFMON: Unable to get message text for %08x\n", SupplementalErrorCode );
            }
        }

    return;
}


INIT.C

/*++

Copyright 1995 - 1998 Microsoft Corporation

Module Name:

    init.c

Abstract:

    This is the initialization module for the pfmon program.

Author:

    Mark Lucovsky (markl) 26-Jan-1995

Revision History:

--*/

#include "pfmonp.h"

BOOL
InitializePfmon( VOID )
{
    LPTSTR CommandLine;
    BOOL fShowUsage;
    DWORD Pid = 0;

    fShowUsage = FALSE;
    CommandLine = GetCommandLine();
    while (*CommandLine > ' ') {
        CommandLine += 1;
    }
    while (TRUE) {
        while (*CommandLine <= ' ') {
            if (*CommandLine == '\0') {
                break;
            } else {
                CommandLine += 1;
            }
        }

        if (!strnicmp( CommandLine, "/v", 2 ) || !strnicmp( CommandLine, "-v", 2 )) {
            CommandLine += 2;
            fVerbose = TRUE;
        } else if (!strnicmp( CommandLine, "/?", 2 ) || !strnicmp( CommandLine, "-?", 2 )) {
            CommandLine += 2;
            fShowUsage = TRUE;
            goto showusage;
        } else if (!strnicmp( CommandLine, "/c", 2 ) || !strnicmp( CommandLine, "-c", 2 )) {
            CommandLine += 2;
            fCodeOnly = TRUE;
        } else if (!strnicmp( CommandLine, "/h", 2 ) || !strnicmp( CommandLine, "-h", 2 )) {
            CommandLine += 2;
            fHardOnly = TRUE;
        } else if (!strnicmp( CommandLine, "/n", 2 ) || !strnicmp( CommandLine, "-n", 2 )) {
            CommandLine += 2;
            LogFile = fopen("pfmon.log","wt");
            fLogOnly = TRUE;
        } else if (!strnicmp( CommandLine, "/l", 2 ) || !strnicmp( CommandLine, "-l", 2 )) {
            CommandLine += 2;
            LogFile = fopen("pfmon.log","wt");
        } else if (!strnicmp( CommandLine, "/p", 2 ) || !strnicmp( CommandLine, "-p", 2 )) {
            CommandLine += 2;
            while (*CommandLine <= ' ') {
                if (*CommandLine == '\0') {
                    break;
                } else {
                    ++CommandLine;
                }
            }
            Pid = atoi(CommandLine);
            CommandLine = strchr(CommandLine,' ');
            if (CommandLine==NULL) {
                break;
            }
        } else if (!strnicmp( CommandLine, "/d", 2 ) || !strnicmp( CommandLine, "-d", 2 )) {
            CommandLine += 2;
            fDatabase = TRUE;
        } else {
            break;
        }
    }
showusage:
    if ( fShowUsage ) {
        fprintf(stdout,"Usage: PFMON [switches] application-command-line\n");
        fprintf(stdout,"             [-?] display this message\n");
        fprintf(stdout,"             [-n] don't display running faults, just log to pfmon.log\n");
        fprintf(stdout,"             [-l] log faults to pfmon.log\n");
        fprintf(stdout,"             [-c] only show code faults\n");
        fprintf(stdout,"             [-h] only show hard faults\n");
        fprintf(stdout,"             [-p pid] attach to existing process\n");
        fprintf(stdout,"             [-d] Database format (tab delimited)\n");
        fprintf(stdout,"                  format: pagefault number, Page Fault type (Hard or Soft),\n");
        fprintf(stdout,"                  Program Counter's Module, Symbol for PC, Decimal value of PC,\n");
        fprintf(stdout,"                  Decimal value of PC, Module of the virtual address accessed,\n");
        fprintf(stdout,"                  Symbol for VA, value of VA\n");
        return FALSE;
        };

    InitializeListHead( &ProcessListHead );
    InitializeListHead( &ModuleListHead );
    SetSymbolSearchPath();

    PfmonModuleHandle = GetModuleHandle( NULL );

    if (Pid != 0) {
        return(AttachApplicationForDebug(Pid));
    } else {
        return (LoadApplicationForDebug( CommandLine ));
    }

    return TRUE;
}

BOOL
LoadApplicationForDebug(
    LPSTR CommandLine
    )
{
    STARTUPINFO StartupInfo;
    PROCESS_INFORMATION ProcessInformation;

    ZeroMemory( &StartupInfo, sizeof( StartupInfo ) );
    StartupInfo.cb = sizeof(StartupInfo);

    if (!CreateProcess( NULL,
                        CommandLine,
                        NULL,
                        NULL,
                        FALSE,                          // No handles to inherit
                        DEBUG_PROCESS,
                        NULL,
                        NULL,
                        &StartupInfo,
                        &ProcessInformation)) {
        DeclareError( PFMON_CANT_DEBUG_PROGRAM,
                      GetLastError(),
                      CommandLine
                    );
        return FALSE;
    } else {
        hProcess = ProcessInformation.hProcess;
        SymInitialize(hProcess,NULL,FALSE);

        return InitializeProcessForWsWatch(hProcess);
    }
}

BOOL
AttachApplicationForDebug(
    DWORD Pid
    )
{

    if (!DebugActiveProcess(Pid)) {
        DeclareError( PFMON_CANT_DEBUG_ACTIVE_PROGRAM,
                      GetLastError(),
                      Pid );
        return FALSE;
    } else {
        hProcess = OpenProcess(PROCESS_VM_READ
                               | PROCESS_QUERY_INFORMATION
                               | PROCESS_SET_INFORMATION,
                               FALSE,
                               Pid);
        SymInitialize(hProcess,NULL,FALSE);

        return InitializeProcessForWsWatch(hProcess);
    }
}


MODULE.C

/*++

Copyright 1995 - 1998 Microsoft Corporation

Module Name:

    module.c

Abstract:

    This module maintains the module (symbol) information for the pfmon application

Author:

    Mark Lucovsky (markl) 27-Jan-1995

Revision History:

--*/

#include "pfmonp.h"

BOOL
AddModule(
    LPDEBUG_EVENT DebugEvent
    )
{
    PMODULE_INFO Module;
    LPVOID BaseAddress;
    HANDLE Handle;

    if ( DebugEvent->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
        Handle = DebugEvent->u.CreateProcessInfo.hFile;
        BaseAddress = DebugEvent->u.CreateProcessInfo.lpBaseOfImage;
        }
    else {
        Handle = DebugEvent->u.LoadDll.hFile;
        BaseAddress = DebugEvent->u.LoadDll.lpBaseOfDll;
        }

    Module = FindModuleContainingAddress(BaseAddress);
    if ( Module ) {
        DeleteModule(Module);
        }

    Module = LocalAlloc(LMEM_ZEROINIT, sizeof( *Module ) );

    if (Module == NULL) {
        return FALSE;
        }

    Module->Handle = Handle;
    Module->BaseAddress = BaseAddress;

    if ( !Module->Handle ) {
        LocalFree(Module);
        return FALSE;
        }

    Module->DebugInfo = MapDebugInformation(
                            Module->Handle,
                            NULL,
                            SymbolSearchPath,
                            (DWORD)Module->BaseAddress
                            );

    if ( !Module->DebugInfo ) {
        LocalFree(Module);
        return FALSE;
        }
    Module->VirtualSize = Module->DebugInfo->SizeOfImage;

    SymLoadModule(hProcess,Handle,NULL,NULL,(DWORD)BaseAddress,0);

    InsertTailList( &ModuleListHead, &Module->Entry );

    return TRUE;
}

BOOL
DeleteModule(
    PMODULE_INFO Module
    )
{
    CHAR Line[256];

    if ( Module ) {
        RemoveEntryList(&Module->Entry);

        sprintf(Line,"%16s Caused %6d faults had %6d Soft %6d Hard faulted VA's\n",
            Module->DebugInfo->ImageFileName,
            Module->NumberCausedFaults,
            Module->NumberFaultedSoftVas,
            Module->NumberFaultedHardVas
            );
        if ( !fLogOnly ) {
            fprintf(stdout,"%s",Line);
            }
        if ( LogFile ) {
            fprintf(LogFile,"%s",Line);
            }

        UnmapDebugInformation(Module->DebugInfo);
        LocalFree(Module);
        }

    return TRUE;
}

PMODULE_INFO
FindModuleContainingAddress(
    LPVOID Address
    )
{
    PLIST_ENTRY Next;
    PMODULE_INFO Module;

    Next = ModuleListHead.Flink;
    while ( Next != &ModuleListHead ) {
        Module = CONTAINING_RECORD(Next,MODULE_INFO,Entry);
        if ( Address >= Module->BaseAddress &&
             Address < (LPVOID)((PUCHAR)(Module->BaseAddress)+Module->VirtualSize) ) {
            return Module;
            }
        Next = Next->Flink;
        }
    return NULL;
}

VOID
SetSymbolSearchPath( )
{
    LPSTR lpSymPathEnv, lpAltSymPathEnv, lpSystemRootEnv;
    ULONG cbSymPath;
    DWORD dw;

    cbSymPath = 18;
    if (lpSymPathEnv = getenv("_NT_SYMBOL_PATH")) {
        cbSymPath += strlen(lpSymPathEnv) + 1;
    }
    if (lpAltSymPathEnv = getenv("_NT_ALT_SYMBOL_PATH")) {
        cbSymPath += strlen(lpAltSymPathEnv) + 1;
    }

    if (lpSystemRootEnv = getenv("SystemRoot")) {
        cbSymPath += strlen(lpSystemRootEnv) + 1;
    }

    SymbolSearchPath = LocalAlloc(LMEM_ZEROINIT,cbSymPath);

    if (lpAltSymPathEnv) {
        dw = GetFileAttributes(lpAltSymPathEnv);
        if ( dw != 0xffffffff && dw & FILE_ATTRIBUTE_DIRECTORY ) {
            strcat(SymbolSearchPath,lpAltSymPathEnv);
            strcat(SymbolSearchPath,";");
            }
    }
    if (lpSymPathEnv) {
        dw = GetFileAttributes(lpSymPathEnv);
        if ( dw != 0xffffffff && dw & FILE_ATTRIBUTE_DIRECTORY ) {
            strcat(SymbolSearchPath,lpSymPathEnv);
            strcat(SymbolSearchPath,";");
            }
    }

    if (lpSystemRootEnv) {
        dw = GetFileAttributes(lpSystemRootEnv);
        if ( dw != 0xffffffff && dw & FILE_ATTRIBUTE_DIRECTORY ) {
            strcat(SymbolSearchPath,lpSystemRootEnv);
            strcat(SymbolSearchPath,";");
            }
    }

    strcat(SymbolSearchPath,".;");

}


PFMON.C

/*++

Copyright 1995 - 1998 Microsoft Corporation

Module Name:

    pfmon.c

Abstract:

    USAGE: pfmon [pfmon switches] command-line-of-application

Platform:

   PFMON will run only on Windows NT.
    It requires psapi.dll which is distributed with the Win32 SDK.

Author:

    Mark Lucovsky (markl) 26-Jan-1995

--*/

#include "pfmonp.h"

#define WORKING_SET_BUFFER_ENTRYS 4096
PSAPI_WS_WATCH_INFORMATION WorkingSetBuffer[WORKING_SET_BUFFER_ENTRYS];

#define MAX_SYMNAME_SIZE  1024
CHAR PcSymBuffer[sizeof(IMAGEHLP_SYMBOL)+MAX_SYMNAME_SIZE];
PIMAGEHLP_SYMBOL PcSymbol = (PIMAGEHLP_SYMBOL) PcSymBuffer;
CHAR VaSymBuffer[sizeof(IMAGEHLP_SYMBOL)+MAX_SYMNAME_SIZE];
PIMAGEHLP_SYMBOL VaSymbol = (PIMAGEHLP_SYMBOL) VaSymBuffer;

int
main ()
{
    CHAR Line[256];

    if (!InitializePfmon()) {
        ExitProcess( 1 );
        }
    else {
        DebugEventLoop();
        sprintf(Line,"\n PFMON: Total Faults %d  (KM %d UM %d Soft %d, Hard %d, Code %d, Data %d)\n",
            TotalSoftFaults + TotalHardFaults,
            TotalKernelFaults,
            TotalUserFaults,
            TotalSoftFaults,
            TotalHardFaults,
            TotalCodeFaults,
            TotalDataFaults
            );
        fprintf(stdout,"%s",Line);
        if ( LogFile ) {
            fprintf(LogFile,"%s",Line);
            fclose(LogFile);
            }
        ExitProcess( 0 );
        }

    return 0;
}

VOID
ProcessPfMonData(
    VOID
    )
{
    BOOL b;
    BOOL DidOne;
    INT i;
    PMODULE_INFO PcModule;
    PMODULE_INFO VaModule;
    DWORD PcOffset;
    DWORD VaOffset;
    CHAR PcLine[256];
    CHAR VaLine[256];
    CHAR PcModuleStr[256];
    CHAR VaModuleStr[256];
    LPVOID Pc;
    LPVOID Va;
    BOOL SoftFault;
    BOOL CodeFault;
    BOOL KillLog;
    static int cPfCnt = 0;

    PcSymbol->SizeOfStruct  = sizeof(IMAGEHLP_SYMBOL);
    PcSymbol->MaxNameLength = MAX_SYMNAME_SIZE;
    VaSymbol->SizeOfStruct  = sizeof(IMAGEHLP_SYMBOL);
    VaSymbol->MaxNameLength = MAX_SYMNAME_SIZE;

    //Get the buffer of recent page faults from the process's data structure
    b = GetWsChanges(hProcess,&WorkingSetBuffer[0],sizeof(WorkingSetBuffer));

    if ( b ) {
        DidOne = FALSE;
        i = 0;
        while (WorkingSetBuffer[i].FaultingPc) {
            if ( WorkingSetBuffer[i].FaultingVa ) {
                Pc = WorkingSetBuffer[i].FaultingPc;
                Va = WorkingSetBuffer[i].FaultingVa;

                if ( (DWORD)Pc & 0x80000000 ) {
                    TotalKernelFaults++;
                    if ( !fKernel ) {
                        i++;
                        continue;
                        }
                    }
                else {
                    TotalUserFaults++;
                    if ( fKernelOnly ) {
                        i++;
                        continue;
                        }
                    }

                //Check least sig bit which stores whether it was a hard
                //or soft fault

                if ( (ULONG)Va & 1 ) {
                    TotalSoftFaults++;
                    SoftFault = TRUE;
                    }
                else {
                    TotalHardFaults++;
                    SoftFault = FALSE;
                    }

                Va = (LPVOID)( (ULONG)Va & 0xfffffffe);
                if ( (LPVOID)((ULONG)Pc & 0xfffffffe) == Va ) {
                    CodeFault = TRUE;
                    TotalCodeFaults++;
                    }
                else {
                    TotalDataFaults++;
                    CodeFault = FALSE;
                    }

                PcModule = FindModuleContainingAddress(Pc);
                VaModule = FindModuleContainingAddress(Va);

                if ( PcModule ) {
                    PcModule->NumberCausedFaults++;
                    sprintf(PcModuleStr, "%s", PcModule->DebugInfo->ImageFileName);
                    }
                else {
                    PcModuleStr[0] = '\0';
                    }

                //Va was either a code reference or global
                //reference as opposed to a heap reference

                if ( VaModule ) {
                    if ( SoftFault ) {
                        VaModule->NumberFaultedSoftVas++;
                        }
                    else {
                        VaModule->NumberFaultedHardVas++;
                        }
                    sprintf(VaModuleStr, "%s", VaModule->DebugInfo->ImageFileName);
                    }
                else
                    VaModuleStr[0] = '\0';

                if (SymGetSymFromAddr(hProcess, (DWORD)Pc, &PcOffset, PcSymbol)) {
                    if ( PcOffset ) {
                        sprintf(PcLine,"%s+0x%x",PcSymbol->Name,PcOffset);
                        }
                    else {
                        sprintf(PcLine,"%s",PcSymbol->Name);
                        }
                    }
                else {
                    sprintf(PcLine,"0x%08x : ",Pc);
                    }

                if (SymGetSymFromAddr(hProcess, (DWORD)Va, &VaOffset, VaSymbol)) {
                    if ( VaOffset ) {
                        sprintf(VaLine,"%s+0x%x",VaSymbol->Name,VaOffset);
                        }
                    else {
                        sprintf(VaLine,"%s",VaSymbol->Name);
                        }
                    }
                else {
                    sprintf(VaLine,"0x%08x",Va);
                    }

                KillLog = FALSE;

                if ( fCodeOnly && !CodeFault ) {
                    KillLog = TRUE;
                    }
                if ( fHardOnly && SoftFault ) {
                    KillLog = TRUE;
                    }

                if ( !KillLog ) {
                    if ( !fLogOnly ) {
                        if (!fDatabase) {
                            fprintf(stdout,"%s%s : %s\n",SoftFault ? "SOFT: " : "HARD: ",PcLine,VaLine);
                            }
                        else {

                            //Addresses are printed out in decimal
                            //because most databases don't support
                            //hex formats

                            fprintf(stdout,"%8d\t%s\t%s\t%s\t%u\t%s\t%s\t%u\n",
                                    cPfCnt,
                                    SoftFault ? "SOFT" : "HARD",
                                    PcModuleStr,
                                    PcLine,
                                    (DWORD) Pc,
                                    VaModuleStr,
                                    VaLine,
                                    (DWORD) Va);
                            }
                        }

                    if ( LogFile ) {
                        if (!fDatabase) {
                            fprintf(LogFile,"%s%s : %s\n",SoftFault ? "SOFT: " : "HARD: ",PcLine,VaLine);
                            }
                        else {
                            fprintf(LogFile,"%8d\t%s\t%s\t%s\t%u\t%s\t%s\t%u\n",
                                    cPfCnt,
                                    SoftFault ? "SOFT" : "HARD",
                                    PcModuleStr,
                                    PcLine,
                                    (DWORD) Pc,
                                    VaModuleStr,
                                    VaLine,
                                    (DWORD) Va);
                            }
                        }
                    DidOne = TRUE;
                    cPfCnt++;
                    }
                }
            i++;
            }

        if ( DidOne ) {
            if ( !fLogOnly  && !fDatabase) {
                fprintf(stdout,"\n");
                }
            }

        //If the buffer overflowed then a non-zero value for
        //the Va was stored in the last record.
        if ((ULONG)WorkingSetBuffer[i].FaultingVa)
            fprintf(stdout,"Warning: Page fault buffer has overflowed\n");

        }
}


PFMONP.H

/*++

Copyright 1995 - 1998 Microsoft Corporation

Module Name:

    instaler.h

Abstract:

    Main include file for the INSTALER application.

Author:

    Steve Wood (stevewo) 09-Aug-1994

Revision History:

--*/

#ifdef RC_INVOKED
#include <windows.h>
#else

#include <windows.h>
#include <imagehlp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "errormsg.h"

typedef struct _PSAPI_WS_WATCH_INFORMATION {
    LPVOID FaultingPc;
    LPVOID FaultingVa;
} PSAPI_WS_WATCH_INFORMATION, *PPSAPI_WS_WATCH_INFORMATION;

BOOL
WINAPI
GetWsChanges(
    HANDLE hProcess,
    PPSAPI_WS_WATCH_INFORMATION lpWatchInfo,
    DWORD cb
    );

BOOL
WINAPI
InitializeProcessForWsWatch(
    HANDLE hProcess
    );

//  Doubly-linked list manipulation routines.  Implemented as macros
//  but logically these are procedures.
//

//
//  VOID
//  InitializeListHead(
//      PLIST_ENTRY ListHead
//      );
//

#define InitializeListHead(ListHead) (\
    (ListHead)->Flink = (ListHead)->Blink = (ListHead))

//
//  BOOL
//  IsListEmpty(
//      PLIST_ENTRY ListHead
//      );
//

#define IsListEmpty(ListHead) \
    ((ListHead)->Flink == (ListHead))

//
//  PLIST_ENTRY
//  RemoveHeadList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveHeadList(ListHead) \
    (ListHead)->Flink;\
    {RemoveEntryList((ListHead)->Flink)}

//
//  PLIST_ENTRY
//  RemoveTailList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveTailList(ListHead) \
    (ListHead)->Blink;\
    {RemoveEntryList((ListHead)->Blink)}

//
//  VOID
//  RemoveEntryList(
//      PLIST_ENTRY Entry
//      );
//

#define RemoveEntryList(Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_Flink;\
    _EX_Flink = (Entry)->Flink;\
    _EX_Blink = (Entry)->Blink;\
    _EX_Blink->Flink = _EX_Flink;\
    _EX_Flink->Blink = _EX_Blink;\
    }

//
//  VOID
//  InsertTailList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertTailList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Blink = _EX_ListHead->Blink;\
    (Entry)->Flink = _EX_ListHead;\
    (Entry)->Blink = _EX_Blink;\
    _EX_Blink->Flink = (Entry);\
    _EX_ListHead->Blink = (Entry);\
    }

//
//  VOID
//  InsertHeadList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertHeadList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Flink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Flink = _EX_ListHead->Flink;\
    (Entry)->Flink = _EX_Flink;\
    (Entry)->Blink = _EX_ListHead;\
    _EX_Flink->Blink = (Entry);\
    _EX_ListHead->Flink = (Entry);\
    }

//
// Data structures and entry points in init.c
//

DWORD TotalSoftFaults;
DWORD TotalHardFaults;
DWORD TotalCodeFaults;
DWORD TotalDataFaults;
DWORD TotalKernelFaults;
DWORD TotalUserFaults;

BOOL fCodeOnly;
BOOL fHardOnly;

VOID
ProcessPfMonData(
    VOID
    );

//
// Data structures and entry points in init.c
//

BOOL fVerbose;
BOOL fLogOnly;
BOOL fKernelOnly;                 //flag for displaying kernel pagefaults
BOOL fKernel;                     //flag for displaying kernel pagefaults
BOOL fDatabase;                   //flag for outputing information in a
                                  //tab-delimited database format
FILE *LogFile;

BOOL
InitializePfmon(
    VOID
    );

BOOL
LoadApplicationForDebug(
    LPSTR CommandLine
    );

BOOL
AttachApplicationForDebug(
    DWORD Pid
    );

HANDLE hProcess;

//
// Data structures and entry points in error.c
//

HANDLE PfmonModuleHandle;

VOID
CDECL
DeclareError(
    UINT ErrorCode,
    UINT SupplementalErrorCode,
    ...
    );

//
// Data structures and entry points in DEBUG.C
//

VOID
DebugEventLoop( VOID );

//
// Data structures and entry points in process.c
//

typedef struct _PROCESS_INFO {
    LIST_ENTRY Entry;
    LIST_ENTRY ThreadListHead;
    DWORD Id;
    HANDLE Handle;
} PROCESS_INFO, *PPROCESS_INFO;

typedef struct _THREAD_INFO {
    LIST_ENTRY Entry;
    DWORD Id;
    HANDLE Handle;
    PVOID StartAddress;
} THREAD_INFO, *PTHREAD_INFO;

LIST_ENTRY ProcessListHead;

BOOL
AddProcess(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO *ReturnedProcess
    );

BOOL
DeleteProcess(
    PPROCESS_INFO Process
    );

BOOL
AddThread(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO Process,
    PTHREAD_INFO *ReturnedThread
    );

BOOL
DeleteThread(
    PPROCESS_INFO Process,
    PTHREAD_INFO Thread
    );

PPROCESS_INFO
FindProcessById(
    ULONG Id
    );

BOOL
FindProcessAndThreadForEvent(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO *ReturnedProcess,
    PTHREAD_INFO *ReturnedThread
    );
//
// Data structures and entry points in module.c
//

typedef struct _MODULE_INFO {
    LIST_ENTRY Entry;
    LPVOID BaseAddress;
    DWORD VirtualSize;
    DWORD NumberFaultedSoftVas;
    DWORD NumberFaultedHardVas;
    DWORD NumberCausedFaults;
    HANDLE Handle;
    PIMAGE_DEBUG_INFORMATION DebugInfo;
} MODULE_INFO, *PMODULE_INFO;

LPSTR SymbolSearchPath;
LIST_ENTRY ModuleListHead;

BOOL
LazyLoad(
    LPVOID Address
    );

// hack PRTL_PROCESS_MODULES LazyModuleInformation;

BOOL
AddModule(
    LPDEBUG_EVENT DebugEvent
    );

BOOL
DeleteModule(
    PMODULE_INFO Module
    );

PMODULE_INFO
FindModuleContainingAddress(
    LPVOID Address
    );

VOID
SetSymbolSearchPath( );

LONG
AddKernelDrivers( );

#ifdef _ALPHA_
#define CONTEXT_TO_PROGRAM_COUNTER(Context) ((Context)->Fir)
#define BPSKIP 4
#endif // _ALPHA_

#ifdef _MIPS_
#define CONTEXT_TO_PROGRAM_COUNTER(Context) ((Context)->Fir)
#define BPSKIP 4
#endif // _MIPS_

#ifdef _PPC_
#define CONTEXT_TO_PROGRAM_COUNTER(Context) ((Context)->Iar)
#define BPSKIP 4
#endif // _PPC_

#ifdef _X86_
#define CONTEXT_TO_PROGRAM_COUNTER(Context) ((Context)->Eip)
#define BPSKIP 1
#endif // _X86_

#endif // defined( RC_INVOKED )


PROCESS.C

/*++

Copyright 1995 - 1998 Microsoft Corporation

Module Name:

    process.c

Abstract:

    This module maintains state about each process/thread created by the application
    pfmon program.

Author:

    Mark Lucovsky (markl) 26-Jan-1995

Revision History:

--*/

#include "pfmonp.h"

BOOL
AddProcess(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO *ReturnedProcess
    )
{
    PPROCESS_INFO Process;

    Process = LocalAlloc(LMEM_ZEROINIT, sizeof( *Process ) );
    if (Process == NULL) {
        return FALSE;
        }

    Process->Id = DebugEvent->dwProcessId;
    Process->Handle = DebugEvent->u.CreateProcessInfo.hProcess;
    InitializeListHead( &Process->ThreadListHead );
    InsertTailList( &ProcessListHead, &Process->Entry );
    *ReturnedProcess = Process;

    return TRUE;
}

BOOL
DeleteProcess(
    PPROCESS_INFO Process
    )
{
    PLIST_ENTRY Next, Head;
    PTHREAD_INFO Thread;
    PMODULE_INFO Module;
    CHAR Line[256];

    RemoveEntryList( &Process->Entry );

    Head = &Process->ThreadListHead;
    Next = Head->Flink;
    while (Next != Head) {
        Thread = CONTAINING_RECORD( Next, THREAD_INFO, Entry );
        Next = Next->Flink;
        DeleteThread( Process, Thread );
        }

    LocalFree( Process );
    fprintf(stdout,"\n");

    Next = ModuleListHead.Flink;
    while ( Next != &ModuleListHead ) {
        Module = CONTAINING_RECORD(Next,MODULE_INFO,Entry);

        sprintf(Line,"%16s Caused %6d faults had %6d Soft %6d Hard faulted VA's\n",
            Module->DebugInfo->ImageFileName,
            Module->NumberCausedFaults,
            Module->NumberFaultedSoftVas,
            Module->NumberFaultedHardVas
            );
        if ( !fLogOnly ) {
            fprintf(stdout,"%s",Line);
            }
        if ( LogFile ) {
            fprintf(LogFile,"%s",Line);
            }

        Next = Next->Flink;
        }

    return TRUE;
}

BOOL
AddThread(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO Process,
    PTHREAD_INFO *ReturnedThread
    )
{
    PTHREAD_INFO Thread;

    Thread = LocalAlloc(LMEM_ZEROINIT, sizeof( *Thread ) );
    if (Thread == NULL) {
        return FALSE;
        }

    Thread->Id = DebugEvent->dwThreadId;
    if (DebugEvent->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
        Thread->Handle = DebugEvent->u.CreateProcessInfo.hThread;
        Thread->StartAddress = DebugEvent->u.CreateProcessInfo.lpStartAddress;
        }
    else {
        Thread->Handle = DebugEvent->u.CreateThread.hThread;
        Thread->StartAddress = DebugEvent->u.CreateThread.lpStartAddress;
        }
    InsertTailList( &Process->ThreadListHead, &Thread->Entry );
    *ReturnedThread = Thread;
    return TRUE;
}

BOOL
DeleteThread(
    PPROCESS_INFO Process,
    PTHREAD_INFO Thread
    )
{

    RemoveEntryList( &Thread->Entry );

    LocalFree( Thread );
    return TRUE;
}

PPROCESS_INFO
FindProcessById(
    ULONG Id
    )
{
    PLIST_ENTRY Next, Head;
    PPROCESS_INFO Process;

    Head = &ProcessListHead;
    Next = Head->Flink;
    while (Next != Head) {
        Process = CONTAINING_RECORD( Next, PROCESS_INFO, Entry );
        if (Process->Id == Id) {
            return Process;
            }

        Next = Next->Flink;
        }

    return NULL;
}

BOOL
FindProcessAndThreadForEvent(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO *ReturnedProcess,
    PTHREAD_INFO *ReturnedThread
    )
{
    PLIST_ENTRY Next, Head;
    PPROCESS_INFO Process;
    PTHREAD_INFO Thread;

    Head = &ProcessListHead;
    Next = Head->Flink;
    Process = NULL;
    Thread = NULL;
    while (Next != Head) {
        Process = CONTAINING_RECORD( Next, PROCESS_INFO, Entry );
        if (Process->Id == DebugEvent->dwProcessId) {
            Head = &Process->ThreadListHead;
            Next = Head->Flink;
            while (Next != Head) {
                Thread = CONTAINING_RECORD( Next, THREAD_INFO, Entry );
                if (Thread->Id == DebugEvent->dwThreadId) {
                    break;
                    }

                Thread = NULL;
                Next = Next->Flink;
                }

            break;
            }

        Process = NULL;
        Next = Next->Flink;
        }

    *ReturnedProcess = Process;
    *ReturnedThread = Thread;

    if (DebugEvent->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
        if (Process != NULL) {
            DeclareError( PFMON_DUPLICATE_PROCESS_ID, 0, DebugEvent->dwProcessId );
            return FALSE;
            }
        }
    else
    if (DebugEvent->dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT) {
        if (Thread != NULL) {
            DeclareError( PFMON_DUPLICATE_THREAD_ID, 0, DebugEvent->dwThreadId, DebugEvent->dwProcessId );
            return FALSE;
            }
        if (Process == NULL) {
            DeclareError( PFMON_MISSING_PROCESS_ID, 0, DebugEvent->dwProcessId );
            return FALSE;
            }
        }
    else
    if (Process == NULL) {
        DeclareError( PFMON_MISSING_PROCESS_ID, 0, DebugEvent->dwProcessId );
        return FALSE;
        }
    else
    if (Thread == NULL) {
        DeclareError( PFMON_MISSING_THREAD_ID, 0, DebugEvent->dwThreadId, DebugEvent->dwProcessId );
        return FALSE;
        }

    return TRUE;
}


PSAPI.H

/*++ BUILD Version: 0001    // Increment this if a change has global effects

Copyright (c) 1994-1997  Microsoft Corporation

Module Name:

    psapi.h

Abstract:

    Include file for APIs provided by PSAPI.DLL

Author:

    Richard Shupak   [richards]  06-Jan-1994

Revision History:

--*/

#ifndef _PSAPI_H_
#define _PSAPI_H_

#ifdef __cplusplus
extern "C" {
#endif

BOOL
WINAPI
EnumProcesses(
    DWORD * lpidProcess,
    DWORD   cb,
    DWORD * cbNeeded
    );

BOOL
WINAPI
EnumProcessModules(
    HANDLE hProcess,
    HMODULE *lphModule,
    DWORD cb,
    LPDWORD lpcbNeeded
    );

DWORD
WINAPI
GetModuleBaseNameA(
    HANDLE hProcess,
    HMODULE hModule,
    LPSTR lpBaseName,
    DWORD nSize
    );

DWORD
WINAPI
GetModuleBaseNameW(
    HANDLE hProcess,
    HMODULE hModule,
    LPWSTR lpBaseName,
    DWORD nSize
    );

#ifdef UNICODE
#define GetModuleBaseName  GetModuleBaseNameW
#else
#define GetModuleBaseName  GetModuleBaseNameA
#endif // !UNICODE

DWORD
WINAPI
GetModuleFileNameExA(
    HANDLE hProcess,
    HMODULE hModule,
    LPSTR lpFilename,
    DWORD nSize
    );

DWORD
WINAPI
GetModuleFileNameExW(
    HANDLE hProcess,
    HMODULE hModule,
    LPWSTR lpFilename,
    DWORD nSize
    );

#ifdef UNICODE
#define GetModuleFileNameEx  GetModuleFileNameExW
#else
#define GetModuleFileNameEx  GetModuleFileNameExA
#endif // !UNICODE

typedef struct _MODULEINFO {
    LPVOID lpBaseOfDll;
    DWORD SizeOfImage;
    LPVOID EntryPoint;
} MODULEINFO, *LPMODULEINFO;

BOOL
WINAPI
GetModuleInformation(
    HANDLE hProcess,
    HMODULE hModule,
    LPMODULEINFO lpmodinfo,
    DWORD cb
    );

BOOL
WINAPI
EmptyWorkingSet(
    HANDLE hProcess
    );

BOOL
WINAPI
QueryWorkingSet(
    HANDLE hProcess,
    PVOID pv,
    DWORD cb
    );

BOOL
WINAPI
InitializeProcessForWsWatch(
    HANDLE hProcess
    );

typedef struct _PSAPI_WS_WATCH_INFORMATION {
    LPVOID FaultingPc;
    LPVOID FaultingVa;
} PSAPI_WS_WATCH_INFORMATION, *PPSAPI_WS_WATCH_INFORMATION;

BOOL
WINAPI
GetWsChanges(
    HANDLE hProcess,
    PPSAPI_WS_WATCH_INFORMATION lpWatchInfo,
    DWORD cb
    );

DWORD
WINAPI
GetMappedFileNameW(
    HANDLE hProcess,
    LPVOID lpv,
    LPWSTR lpFilename,
    DWORD nSize
    );

DWORD
WINAPI
GetMappedFileNameA(
    HANDLE hProcess,
    LPVOID lpv,
    LPSTR lpFilename,
    DWORD nSize
    );

#ifdef UNICODE
#define GetMappedFilenameEx  GetMappedFilenameExW
#else
#define GetMappedFilenameEx  GetMappedFilenameExA
#endif // !UNICODE

BOOL
WINAPI
EnumDeviceDrivers(
    LPVOID *lpImageBase,
    DWORD cb,
    LPDWORD lpcbNeeded
    );

DWORD
WINAPI
GetDeviceDriverBaseNameA(
    LPVOID ImageBase,
    LPSTR lpBaseName,
    DWORD nSize
    );

DWORD
WINAPI
GetDeviceDriverBaseNameW(
    LPVOID ImageBase,
    LPWSTR lpBaseName,
    DWORD nSize
    );

#ifdef UNICODE
#define GetDeviceDriverBaseName  GetDeviceDriverBaseNameW
#else
#define GetDeviceDriverBaseName  GetDeviceDriverBaseNameA
#endif // !UNICODE

DWORD
WINAPI
GetDeviceDriverFileNameA(
    LPVOID ImageBase,
    LPSTR lpFilename,
    DWORD nSize
    );

DWORD
WINAPI
GetDeviceDriverFileNameW(
    LPVOID ImageBase,
    LPWSTR lpFilename,
    DWORD nSize
    );

#ifdef UNICODE
#define GetDeviceDriverFileName  GetDeviceDriverFileNameW
#else
#define GetDeviceDriverFileName  GetDeviceDriverFileNameA
#endif // !UNICODE

// Structure for GetProcessMemoryInfo()

typedef struct _PROCESS_MEMORY_COUNTERS {
    DWORD cb;
    DWORD PageFaultCount;
    DWORD PeakWorkingSetSize;
    DWORD WorkingSetSize;
    DWORD QuotaPeakPagedPoolUsage;
    DWORD QuotaPagedPoolUsage;
    DWORD QuotaPeakNonPagedPoolUsage;
    DWORD QuotaNonPagedPoolUsage;
    DWORD PagefileUsage;
    DWORD PeakPagefileUsage;
} PROCESS_MEMORY_COUNTERS;
typedef PROCESS_MEMORY_COUNTERS *PPROCESS_MEMORY_COUNTERS;

BOOL
WINAPI
GetProcessMemoryInfo(
    HANDLE Process,
    PPROCESS_MEMORY_COUNTERS ppsmemCounters,
    DWORD cb
    );

#ifdef __cplusplus
}
#endif

#endif


DEBUG.C

/*++

Copyright 1996 - 1997 Microsoft Corporation

Module Name:

    debug.c

Abstract:

    Main debug loop for pfmon

Author:

    Mark Lucovsky (markl) 26-Jan-1995

Revision History:

--*/

#include "pfmonp.h"

DWORD
DebugEventHandler(
    LPDEBUG_EVENT DebugEvent
    );

VOID
DebugEventLoop( VOID )
{
    DEBUG_EVENT DebugEvent;
    DWORD ContinueStatus;
    DWORD OldPriority;

    //
    // We want to process debug events quickly
    //

    OldPriority = GetPriorityClass( GetCurrentProcess() );
    SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS );

    do {
retry_debug_wait:
        ProcessPfMonData();
        if (!WaitForDebugEvent( &DebugEvent, 500 )) {
            if ( GetLastError() == ERROR_SEM_TIMEOUT ) {
                goto retry_debug_wait;
                }
            DeclareError( PFMON_WAITDEBUGEVENT_FAILED, GetLastError() );
            ExitProcess( 1 );
            }
        ProcessPfMonData();
        if ( fVerbose ) {
            if (DebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) {
                fprintf(stderr,"Debug exception event - Code: %x  Address: %x  Info: [%u] %x %x %x %x\n",
                        DebugEvent.u.Exception.ExceptionRecord.ExceptionCode,
                        DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress,
                        DebugEvent.u.Exception.ExceptionRecord.NumberParameters,
                        DebugEvent.u.Exception.ExceptionRecord.ExceptionInformation[ 0 ],
                        DebugEvent.u.Exception.ExceptionRecord.ExceptionInformation[ 1 ],
                        DebugEvent.u.Exception.ExceptionRecord.ExceptionInformation[ 2 ],
                        DebugEvent.u.Exception.ExceptionRecord.ExceptionInformation[ 3 ]
                        );
                }
            else {
                fprintf(stderr,"Debug %x event\n", DebugEvent.dwDebugEventCode);
                }
            }

        ContinueStatus = DebugEventHandler( &DebugEvent );

        if ( fVerbose ) {
            fprintf(stderr,"Continue Status %x\n", ContinueStatus);
            }

        if (!ContinueDebugEvent( DebugEvent.dwProcessId,
                                 DebugEvent.dwThreadId,
                                 ContinueStatus
                               )
           ) {
            DeclareError( PFMON_CONTDEBUGEVENT_FAILED, GetLastError() );
            ExitProcess( 1 );
            }
        }
    while (!IsListEmpty( &ProcessListHead ));

    //
    // Drop back to old priority to interact with user.
    //

    SetPriorityClass( GetCurrentProcess(), OldPriority );
}

DWORD
DebugEventHandler(
    LPDEBUG_EVENT DebugEvent
    )
{
    DWORD ContinueStatus;
    PPROCESS_INFO Process;
    PTHREAD_INFO Thread;
    CONTEXT Context;
    PCONTEXT pContext;

    ContinueStatus = (DWORD)DBG_CONTINUE;
    if (FindProcessAndThreadForEvent( DebugEvent, &Process, &Thread )) {
        switch (DebugEvent->dwDebugEventCode) {
            case CREATE_PROCESS_DEBUG_EVENT:
                //
                // Create process event includes first thread of process
                // as well.  Remember process and thread in our process tree
                //

                if (AddProcess( DebugEvent, &Process )) {
                    AddModule( DebugEvent );
                    AddThread( DebugEvent, Process, &Thread );
                    }
                break;

            case EXIT_PROCESS_DEBUG_EVENT:
                //
                // Exit process event includes last thread of process
                // as well.  Remove process and thread from our process tree
                //

                if (DeleteThread( Process, Thread )) {
                    DeleteProcess( Process );
                    }
                break;

            case CREATE_THREAD_DEBUG_EVENT:
                //
                // Create thread.  Remember thread in our process tree.
                //

                AddThread( DebugEvent, Process, &Thread );
                break;

            case EXIT_THREAD_DEBUG_EVENT:
                //
                // Exit thread.  Remove thread from our process tree.
                //

                DeleteThread( Process, Thread );
                break;

            case LOAD_DLL_DEBUG_EVENT:
                AddModule( DebugEvent );
                break;

            case UNLOAD_DLL_DEBUG_EVENT:
                break;

            case OUTPUT_DEBUG_STRING_EVENT:
            case RIP_EVENT:
                //
                // Ignore these
                //
                break;

            case EXCEPTION_DEBUG_EVENT:
                //
                // Assume we wont handle this exception
                //

                ContinueStatus = (DWORD)DBG_CONTINUE;
                switch (DebugEvent->u.Exception.ExceptionRecord.ExceptionCode) {
                    //
                    // Breakpoint exception.
                    //

                    case STATUS_BREAKPOINT:
                            Context.ContextFlags = CONTEXT_FULL;

                            if (!GetThreadContext( Thread->Handle, &Context )) {
                                fprintf(stderr,"Failed to get context for thread %x (%x) - %u\n", Thread->Id, Thread->Handle, GetLastError());
                                ExitProcess(1);
                                }
                            pContext = &Context;
                            CONTEXT_TO_PROGRAM_COUNTER(pContext) = (ULONG)((PCHAR)DebugEvent->u.Exception.ExceptionRecord.ExceptionAddress + BPSKIP);

                            if (!SetThreadContext( Thread->Handle, &Context )) {
                                fprintf(stderr,"Failed to set context for thread %x (%x) - %u\n", Thread->Id, Thread->Handle, GetLastError());
                                ExitProcess(1);
                                }

                        break;

                    default:
                        ContinueStatus = (DWORD) DBG_EXCEPTION_NOT_HANDLED;
                        if ( fVerbose ) {
                            fprintf(stderr,"Unknown exception: %08x at %08x\n",
                                    DebugEvent->u.Exception.ExceptionRecord.ExceptionCode,
                                    DebugEvent->u.Exception.ExceptionRecord.ExceptionAddress
                                    );
                            }
                        break;
                    }
                break;

            default:
                break;
            }
        }
    return( ContinueStatus );
}


README.TXT

Image Editor

The IMAGEDIT tool allows you to graphically create small bitmaps and icons.
It demonstrates a variety of useful user interface techniques. It also
serves as a reference for understanding the binary formats of bitmaps and
icons.


DEVINFO.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: devinfo.c
    *
    * Contains routines for manipulating the linked list of known target
    * devices for icons and cursors.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    STATICFN VOID ProcessDeviceSection(INT iType);
    STATICFN BOOL ParseDeviceLine(PSTR pszLine, PINT pnColors, PINT pcx,
        PINT pcy);

    /************************************************************************
    * InitDeviceList
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID InitDeviceList(VOID)
    {
        /*
         * Allocate the standard icon devices.
         */
        DeviceLinkAlloc(FT_ICON, ids(IDS_ICONDEVNAMEEGAVGA), 16, 32, 32);
        DeviceLinkAlloc(FT_ICON, ids(IDS_ICONDEVNAMEMONO), 2, 32, 32);
        DeviceLinkAlloc(FT_ICON, ids(IDS_ICONDEVNAMECGA), 2, 32, 16);

        ProcessDeviceSection(FT_ICON);

        /*
         * Allocate the standard cursor devices.
         */
        DeviceLinkAlloc(FT_CURSOR, ids(IDS_CURDEVNAMEVGAMONO), 2, 32, 32);
        DeviceLinkAlloc(FT_CURSOR, ids(IDS_CURDEVNAMEVGACOLOR), 16, 32, 32);

        ProcessDeviceSection(FT_CURSOR);
    }

    /************************************************************************
    * ProcessDeviceSection
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID ProcessDeviceSection(
        INT iType)
    {
        PSTR pszSectionName;
        CHAR szValueBuf[CCHTEXTMAX];
        CHAR szKeyNameBuf[CCHTEXTMAX];
        PSTR pszKeyName;
        INT nColors;
        INT cx;
        INT cy;

        if (iType == FT_ICON) {
            pszSectionName = ids(IDS_ICONINISECTION);
        }
        else {
            pszSectionName = ids(IDS_CURSORINISECTION);
        }

        if (!GetPrivateProfileString(pszSectionName, NULL, "",
                szKeyNameBuf, sizeof(szKeyNameBuf), ids(IDS_IMAGEDITINI)))
            return;

        pszKeyName = szKeyNameBuf;
        while (*pszKeyName) {
            if (GetPrivateProfileString(pszSectionName, pszKeyName, "",
                    szValueBuf, sizeof(szValueBuf), ids(IDS_IMAGEDITINI))) {
                if (ParseDeviceLine(szValueBuf, &nColors, &cx, &cy))
                    DeviceLinkAlloc(iType, pszKeyName, nColors, cx, cy);
            }

            pszKeyName += strlen(pszKeyName) + 1;
        }
    }

    /************************************************************************
    * ParseDeviceLine
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN BOOL ParseDeviceLine(
        PSTR pszLine,
        PINT pnColors,
        PINT pcx,
        PINT pcy)
    {
        static CHAR szSep[] = " ,";
        PSTR pstr;

        if (!(pstr = strtok(pszLine, szSep)))
            return FALSE;

        *pnColors = atoi(pstr);

        if (!(pstr = strtok(NULL, szSep)))
            return FALSE;

        *pcx = atoi(pstr);

        if (!(pstr = strtok(NULL, szSep)))
            return FALSE;

        *pcy = atoi(pstr);

        return TRUE;
    }

    /************************************************************************
    * DeviceLinkAlloc
    *
    * Allocates a DEVICE structure, initializes it with the given values
    * and adds it to the appropriate linked list.  Because these are
    * specified only at init time, there is no need to ever free them.
    *
    * There is a special case if the image type is FT_BITMAP.  Because
    * there is only one type of bitmap image device at any one time,
    * this routine does not really allocate a link for these.  Instead,
    * it uses a static structure for bitmaps.  It will be set to the
    * given values and a pointer to it will be returned.  This means
    * that for bitmaps, this function must be called every time that
    * the current bitmap image changes to set up the proper values in
    * the structure.
    *
    * Arguments:
    *   INT iType    - Type of image.  FT_* constant.
    *   PSTR pszName - Device name ("VGA", for example).
    *   INT nColors  - Number of colors.
    *   INT cx       - Width of image.
    *   INT cy       - Height of image.
    *
    * History:
    *
    ************************************************************************/

    PDEVICE DeviceLinkAlloc(
        INT iType,
        PSTR pszName,
        INT nColors,
        INT cx,
        INT cy)
    {
        static DEVICE DeviceBitmap;     // Device structure for bitmaps.
        PDEVICE pDevice;
        PDEVICE pDeviceT;
        PDEVICE *ppDeviceHead;

        /*
         * Currently we only support 1 and 4 plane devices.
         */
        if (nColors != 2 && nColors != 16) {
            Message(MSG_BADDEVICECOLORS);
            return NULL;
        }

        /*
         * There is a limit to the size of image we will edit.  For icons
         * and cursors, the field that carries the dimensions is a byte,
         * so the size cannot be greater than 256.  This is also what
         * we limit bitmaps to.
         */
        if (cx < 1 || cx > MAXIMAGEDIM || cy < 1 || cy > MAXIMAGEDIM) {
            Message(MSG_BADDEVICESIZE, MAXIMAGEDIM);
            return NULL;
        }

        /*
         * For bitmaps, don't really allocate a link, just reuse the
         * static DEVICE structure used for bitmaps.
         */
        if (iType == FT_BITMAP) {
            pDevice = &DeviceBitmap;
        }
        else {
            if (!(pDevice = (PDEVICE)MyAlloc(sizeof(DEVICE))))
                return NULL;

            switch (iType) {
                case FT_ICON:
                    ppDeviceHead = &gpIconDeviceHead;
                    gnIconDevices++;
                    break;

                case FT_CURSOR:
                    ppDeviceHead = &gpCursorDeviceHead;
                    gnCursorDevices++;
                    break;
            }
        }

        pDevice->pDeviceNext = NULL;
        pDevice->iType = iType;
        pDevice->nColors = nColors;
        pDevice->cx = cx;
        pDevice->cy = cy;

        if (pszName) {
            strcpy(pDevice->szName, pszName);
            wsprintf(pDevice->szDesc, "%s %d-Color %dx%d",  //   hardcoded strings!
                    (LPSTR)pszName, nColors, cx, cy);
        }
        else {
            *pDevice->szName = '\0';
            wsprintf(pDevice->szDesc, "%d-Color %dx%d", nColors, cx, cy);
        }

        /*
         * Because there is only one bitmap link, there is no need
         * for a list for these, only icons and cursors.
         */
        if (iType != FT_BITMAP) {
            /*
             * Insert the link in the specified list.
             */
            if (!(*ppDeviceHead)) {
                /*
                 * This is the first one.  Start the list.
                 */
                *ppDeviceHead = pDevice;
            }
            else {
                /*
                 * Find the end of the list and tack on the new link.
                 */
                for (pDeviceT = *ppDeviceHead; pDeviceT->pDeviceNext;
                        pDeviceT = pDeviceT->pDeviceNext)
                    ;

                pDeviceT->pDeviceNext = pDevice;
            }
        }

        return pDevice;
    }

    /************************************************************************
    * DeviceLinkFind
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    PDEVICE DeviceLinkFind(
        PDEVICE pDeviceHead,
        INT nColors,
        INT cx,
        INT cy)
    {
        PDEVICE pDevice;

        /*
         * Search the specified list.
         */
        for (pDevice = pDeviceHead; pDevice; pDevice = pDevice->pDeviceNext) {
            /*
             * Is this a match?
             */
            if (pDevice->nColors == nColors && pDevice->cx == cx &&
                    pDevice->cy == cy)
                break;
        }

        return pDevice;
    }

    /************************************************************************
    * DeviceLinkUsed
    *
    * This function returns TRUE if the specified device is used in
    * the current image list.
    *
    * Arguments:
    *   PDEVICE pDevice - Device to look for.
    *
    * History:
    *
    ************************************************************************/

    BOOL DeviceLinkUsed(
        PDEVICE pDevice)
    {
        PIMAGEINFO pImage;

        for (pImage = gpImageHead; pImage; pImage = pImage->pImageNext) {
            if (pImage->pDevice == pDevice)
                return TRUE;
        }

        return FALSE;
    }


DIALOGS.H

/****************************************************************************/
/*                                                                          */
/*                 Copyright (C) 1987-1996 Microsoft Corp.                */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: dialogs.h
*
* Contains ids for the dialogs and controls used in the image editor.
*
* History:
*
****************************************************************************/

#define DID_ABOUT                   100
#define DID_ABOUTVERSION            101

#define DID_BITMAPSIZE              200
#define DID_BITMAPSIZEWIDTH         201
#define DID_BITMAPSIZEHEIGHT        202
#define DID_BITMAPSIZE2             203
#define DID_BITMAPSIZE16            204

#define DID_COLOR                   300
#define DID_COLORLR                 301
#define DID_COLORBOX                302
#define DID_COLOREDIT               303
#define DID_COLORDEFAULT            304
#define DID_COLORSCREENLABEL        305
#define DID_COLORINVERSELABEL       306

#define DID_NEWCURSORIMAGE          400
#define DID_NEWIMAGELIST            401

#define DID_NEWICONIMAGE            500

#define DID_PASTEOPTIONS            600
#define DID_PASTEOPTIONSSTRETCH     601
#define DID_PASTEOPTIONSCLIP        602

#define DID_PROPBAR                 700
#define DID_PROPBARIMAGELABEL       701
#define DID_PROPBARIMAGE            702
#define DID_PROPBARPOS              703
#define DID_PROPBARSIZE             704
#define DID_PROPBARHOTSPOTLABEL     705
#define DID_PROPBARHOTSPOT          706

#define DID_RESOURCETYPE            800
#define DID_RESOURCETYPEBITMAP      801
#define DID_RESOURCETYPEICON        802
#define DID_RESOURCETYPECURSOR      803

#define DID_SELECTCURSORIMAGE       900
#define DID_SELECTIMAGELIST         901

#define DID_SELECTICONIMAGE         1000

/*
 * These dialog ids are dummy ones that are only used so that there
 * can be an entry in the dialog help table.  They are for the CommDlg
 * dialogs and the palettes that are not dialogs.
 */
#define DID_COMMONFILEOPEN          1100
#define DID_COMMONFILESAVE          1200
#define DID_COMMONFILEOPENPAL       1300
#define DID_COMMONFILESAVEPAL       1400
#define DID_COMMONFILECHOOSECOLOR   1500

#define DID_TOOLBOX                 1600
#define DID_VIEW                    1700

/*
 * This is the id of the common dialogs file open dialogs combobox
 * for the type of file.  It is used by the GetOpenFileNameHook
 * function.
 */
#define DID_COMMDLG_TYPECOMBO       0x0470


FILE.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: file.c
    *
    * Contains routines for handling files.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"

    #include <string.h>

 #include <windowsx.h>
    #include <commdlg.h>

    #ifdef WIN16
    typedef BOOL (APIENTRY *LPOFNHOOKPROC) (HWND, UINT, WPARAM, LONG);
    #endif

    STATICFN VOID NEAR AddFilterString(PSTR pszBuf, PSTR pszType, PSTR pszExt,
        BOOL fFirst);
    STATICFN PSTR NEAR DefExtFromFilter(INT index, PSTR pszFilter);
    STATICFN BOOL NEAR LoadFile(PSTR pszFullFileName);
    STATICFN INT NEAR GetTypeFromExt(PSTR pszFileName);
    STATICFN VOID NEAR FileCat(PSTR pchName, PSTR pchCat);

    static OPENFILENAME ofn;

    /************************************************************************
    * SetFileName
    *
    * Updates the globals that contain the file name of the currently
    * loaded file.  This routine will also cause the title bar to
    * be udpated with the new name.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID SetFileName(
        PSTR pszFullFileName)
    {
        CHAR szTitle[CCHMAXPATH];
        WIN32_FIND_DATA ffbuf;
        CHAR *pch;

        if (pszFullFileName) {
            HANDLE hfind;
            strcpy(gszFullFileName, pszFullFileName);
            gpszFileName = FileInPath(gszFullFileName);

            if((hfind = FindFirstFile( pszFullFileName, &ffbuf)) !=
                    INVALID_HANDLE_VALUE) {

                strcpy(gpszFileName, ffbuf.cFileName);
                FindClose(hfind);
            }

        }
        else {
            *gszFullFileName = '\0';
            gpszFileName = NULL;
        }

        strcpy(szTitle, ids(IDS_PGMTITLE));
        strcat(szTitle, " - ");
        pch = gpszFileName ? gpszFileName : ids(IDS_UNTITLED);
        strncat(szTitle, pch, sizeof(szTitle) - strlen(szTitle));
        szTitle[CCHMAXPATH-1] = '\0';
        SetWindowText(ghwndMain, szTitle);
    }

    /************************************************************************
    * FileInPath
    *
    * This function takes a path and returns a pointer to the file name
    * portion of it.  For instance, it will return a pointer to
    * "abc.res" if it is given the following path: "c:\windows\abc.res".
    *
    * Arguments:
    *   PSTR pstrPath - Path to look through.
    *
    * History:
    *
    ************************************************************************/

    PSTR FileInPath(
        PSTR pstrPath)
    {
        PSTR pstr;

        pstr = pstrPath + strlen(pstrPath);
        while (pstr > pstrPath) {
            pstr = FAR2NEAR(AnsiPrev(pstrPath, pstr));
            if (*pstr == '\\' || *pstr == ':' || *pstr == '/') {
                pstr = FAR2NEAR(AnsiNext(pstr));
                break;
            }
        }

        return pstr;
    }

    /************************************************************************
    * ClearResource
    *
    * Resets the editor back to a neutral state before editing any image.
    * This function can be called before starting to edit a new file
    * (but not just a new image).  Files should be saved before calling
    * this routine, because the entire image list is destroyed.
    *
    * History:
    *
    ************************************************************************/

    VOID ClearResource(VOID)
    {
        ImageLinkFreeList();

        SetFileName(NULL);

        gnImages = 0;
        fImageDirty = FALSE;
        fFileDirty = FALSE;
        gpImageCur = NULL;

        /*
         * Hide the workspace and view windows.
         */
        ShowWindow(ghwndWork, SW_HIDE);
        ViewShow(FALSE);

        /*
         * Destroy the image DC's.
         */
        ImageDCDelete();

        /*
         * Update the properties bar.
         */
        PropBarClearPos();
        PropBarClearSize();
        PropBarUpdate();
    }

    /************************************************************************
    * OpenDlg
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL OpenDlg(
        PSTR pszFileName,
        INT iType)
    {
        BOOL fGotName;
        INT idDlg;
        INT idPrevDlg;
        CHAR szFilter[CCHTEXTMAX];

        pszFileName[0] = '\0';

        switch (iType) {
            case FT_BITMAP:
            case FT_ICON:
            case FT_CURSOR:
                AddFilterString(szFilter, ids(IDS_BMPFILTER),
                        ids(IDS_BMPFILTEREXT), TRUE);
                AddFilterString(szFilter, ids(IDS_ICOFILTER),
                        ids(IDS_ICOFILTEREXT), FALSE);
                AddFilterString(szFilter, ids(IDS_CURFILTER),
                        ids(IDS_CURFILTEREXT), FALSE);
                AddFilterString(szFilter, ids(IDS_ALLFILTER),
                        ids(IDS_ALLFILTEREXT), FALSE);

                ofn.nFilterIndex = iType + 1;
                idDlg = DID_COMMONFILEOPEN;

                break;

            case FT_PALETTE:
                AddFilterString(szFilter, ids(IDS_PALFILTER),
                        ids(IDS_PALFILTEREXT), TRUE);
                AddFilterString(szFilter, ids(IDS_ALLFILTER),
                        ids(IDS_ALLFILTEREXT), FALSE);

                ofn.nFilterIndex = 1;
                idDlg = DID_COMMONFILEOPENPAL;

                break;
        }

        ofn.lStructSize = sizeof(OPENFILENAME);
        ofn.hwndOwner = ghwndMain;
        ofn.hInstance = NULL;
        ofn.lpstrFilter = szFilter;
        ofn.lpstrCustomFilter = NULL;
        ofn.nMaxCustFilter = 0;
        ofn.lpstrFile = pszFileName;
        ofn.nMaxFile = CCHMAXPATH;
        ofn.lpstrFileTitle = NULL;
        ofn.nMaxFileTitle = 0;
        ofn.lpstrInitialDir = NULL;
        ofn.lpstrTitle = NULL;
        ofn.Flags = OFN_HIDEREADONLY | OFN_SHOWHELP | OFN_FILEMUSTEXIST |
                OFN_ENABLEHOOK;
        ofn.nFileOffset = 0;
        ofn.nFileExtension = 0;
        ofn.lpstrDefExt = (LPCSTR)DefExtFromFilter((INT)ofn.nFilterIndex - 1,
                (PSTR)ofn.lpstrFilter);
        ofn.lCustData = 0;
        ofn.lpfnHook = (LPOFNHOOKPROC)MakeProcInstance(
                (FARPROC)GetOpenFileNameHook, ghInst);
        ofn.lpTemplateName = NULL;

        EnteringDialog(idDlg, &idPrevDlg, TRUE);
        fGotName = GetOpenFileName(&ofn);
        EnteringDialog(idPrevDlg, NULL, FALSE);

        FreeProcInstance((FARPROC)ofn.lpfnHook);

        return fGotName;
    }

    /************************************************************************
    * SaveAsDlg
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL SaveAsDlg(
        PSTR pszFileName,
        INT iType)
    {
        INT idDlg;
        BOOL fGotName;
        INT idPrevDlg;
        CHAR szFilter[CCHTEXTMAX];

        switch (iType) {
            case FT_BITMAP:
                AddFilterString(szFilter, ids(IDS_BMPFILTER),
                        ids(IDS_BMPFILTEREXT), TRUE);

                ofn.lpstrDefExt = ids(IDS_DEFEXTBMP);
                idDlg = DID_COMMONFILESAVE;

                break;

            case FT_ICON:
                AddFilterString(szFilter, ids(IDS_ICOFILTER),
                        ids(IDS_ICOFILTEREXT), TRUE);

                ofn.lpstrDefExt = ids(IDS_DEFEXTICO);
                idDlg = DID_COMMONFILESAVE;

                break;

            case FT_CURSOR:
                AddFilterString(szFilter, ids(IDS_CURFILTER),
                        ids(IDS_CURFILTEREXT), TRUE);

                ofn.lpstrDefExt = ids(IDS_DEFEXTCUR);
                idDlg = DID_COMMONFILESAVE;

                break;

            case FT_PALETTE:
                AddFilterString(szFilter, ids(IDS_PALFILTER),
                        ids(IDS_PALFILTEREXT), TRUE);

                ofn.lpstrDefExt = ids(IDS_DEFEXTPAL);
                idDlg = DID_COMMONFILESAVEPAL;

                break;
        }

        ofn.lStructSize = sizeof(OPENFILENAME);
        ofn.hwndOwner = ghwndMain;
        ofn.hInstance = NULL;
        ofn.lpstrFilter = szFilter;
        ofn.lpstrCustomFilter = NULL;
        ofn.nMaxCustFilter = 0;
        ofn.nFilterIndex = 1;
        ofn.lpstrFile = pszFileName;
        ofn.nMaxFile = CCHMAXPATH;
        ofn.lpstrFileTitle = NULL;
        ofn.nMaxFileTitle = 0;
        ofn.lpstrInitialDir = NULL;
        ofn.lpstrTitle = NULL;
        ofn.Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | OFN_SHOWHELP;
        ofn.nFileOffset = 0;
        ofn.nFileExtension = 0;
        ofn.lCustData = 0;
        ofn.lpfnHook = NULL;
        ofn.lpTemplateName = NULL;

        EnteringDialog(idDlg, &idPrevDlg, TRUE);
        fGotName = GetSaveFileName(&ofn);
        EnteringDialog(idPrevDlg, NULL, FALSE);

        return fGotName;
    }

    /************************************************************************
    * GetOpenFileNameHook
    *
    * This function is the hook function for the Common Dialogs
    * GetOpenFileName funtion.  It is used to be sure the default
    * extension that is used when the function exits is the same
    * as the image file type that the user specifies they want
    * opened.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC GetOpenFileNameHook(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_INITDIALOG:
                /*
                 * Tell Windows to set the focus for me.
                 */
                return TRUE;

            case WM_COMMAND:
                /*
                 * Did they change the type of file from the File Type
                 * combo box?
                 */
                if (GET_WM_COMMAND_ID(wParam, lParam) == DID_COMMDLG_TYPECOMBO &&
                        GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE) {
                    INT iSelect;

                    /*
                     * Get the selected file type, then change the default
                     * extension field of the ofn structure to match it.
                     * This ensures that the proper default extension
                     * gets added to the end of the file name if the user
                     * does not specify an extension explicitly.
                     */
                    if ((iSelect = (INT)SendDlgItemMessage(hwnd,
                            DID_COMMDLG_TYPECOMBO, CB_GETCURSEL, 0, 0L))
                            != CB_ERR) {
                        ofn.lpstrDefExt = (LPCSTR)DefExtFromFilter(
                                iSelect, (PSTR)ofn.lpstrFilter);
                    }
                }

                break;
        }

        /*
         * Process the message normally.
         */
        return FALSE;
    }

    /************************************************************************
    * AddFilterString
    *
    * This function adds a filter string pair to a filter string for
    * use by the common dialog open/save file functions.  The string
    * pair will be added to the end of the given filter string, unless
    * fFirst is TRUE, in which case it will be written out to the
    * start of the buffer.  A double null will always be written out
    * at the end of the filter string.
    *
    * Arguments:
    *   PSTR pszBuf  - Buffer to write to.
    *   PSTR pszType - Type string.  Something like "Icon files (*.ico)".
    *   PSTR pszExt  - Extension string.  Something like "*.ico".
    *   BOOL fFirst  - TRUE if this is the first filter string in the
    *                  buffer.  If FALSE, the new filter string pair will
    *                  be added to the end of pszBuf.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR AddFilterString(
        PSTR pszBuf,
        PSTR pszType,
        PSTR pszExt,
        BOOL fFirst)
    {
        PSTR psz;

        psz = pszBuf;

        /*
         * If this is not the first filter string pair, skip to the
         * terminating double null sequence.
         */
        if (!fFirst) {
            while (*psz || *(psz + 1))
                psz++;

            psz++;
        }

        strcpy(psz, pszType);
        psz += strlen(pszType) + 1;
        strcpy(psz, pszExt);
        psz += strlen(pszExt) + 1;
        *psz = '\0';
    }

    /************************************************************************
    * DefExtFromFilter
    *
    * This function returns the default extension for the given index
    * from the specified filter string chain.  The filter string chain
    * is in the format expected by the GetSaveFileName function.
    *
    * It will return NULL if the filter extension found is "*.*".
    *
    * Arguments:
    *   INT index      - Zero based index to the filter string.
    *   PSTR pszFilter - Pointer to the start of the filter chain.
    *
    * History:
    *
    ************************************************************************/

    STATICFN PSTR NEAR DefExtFromFilter(
        INT index,
        PSTR pszFilter)
    {
        if (!pszFilter)
            return NULL;

        /*
         * Skip to the specified filter string pair.
         */
        while (index--) {
            pszFilter += strlen(pszFilter) + 1;
            pszFilter += strlen(pszFilter) + 1;
        }

        /*
         * Skip the first string, then skip the '*' and the '.'.
         */
        pszFilter += strlen(pszFilter) + 1 + 1 + 1;

        /*
         * If the string found was "*.*", return NULL for the default
         * extension.
         */
        if (*pszFilter == '*')
            return NULL;

        /*
         * Return a pointer to the default extension.  This will be
         * something like "bmp" or "ico".
         */
        return pszFilter;
    }

    /************************************************************************
    * VerifySaveFile
    *
    * Prompts the user if they want to save the current file to disk.
    * If Yes, calls the appropriate save routine.
    *
    * Returns:
    *   Returns TRUE if either the file was not dirty (no save was done)
    *   or if it was and the user did not want to save it or the file
    *   was dirty and the user wanted to save it and the save was
    *   successful.
    *
    *   Returns FALSE if the user cancelled the operation, or an error
    *   occured with the save.
    *
    * History:
    *
    ************************************************************************/

    BOOL VerifySaveFile(VOID)
    {
        if (fImageDirty || fFileDirty) {
            switch (Message(MSG_SAVEFILE,
                    gpszFileName ? gpszFileName : ids(IDS_UNTITLED))) {
                case IDYES:
                    return SaveFile(FALSE);

                case IDNO:
                    fImageDirty = FALSE;
                    break;

                case IDCANCEL:
                    return FALSE;
            }
        }

        return TRUE;
    }

    /************************************************************************
    * SaveFile
    *
    * Does a save of the current file.  If the file is untitled, it
    * will ask the user for a file name.
    *
    * Arguments:
    *   BOOL fSaveAs - TRUE to force a Save As operation (always prompts
    *                  for the file name).
    *
    * Returns:
    *   Returns TRUE if the save was successful.
    *
    *   Returns FALSE if the user cancelled the operation, or an error
    *   occured with the save.
    *
    * History:
    *
    ************************************************************************/

    BOOL SaveFile(
        BOOL fSaveAs)
    {
        CHAR szFileName[CCHMAXPATH];

        if (gnImages == 0) {
            Message(MSG_NOIMAGES);
            return FALSE;
        }

        if (gpszFileName)
            strcpy(szFileName, gszFullFileName);
        else
            *szFileName = '\0';

        if (fSaveAs || !gpszFileName) {
            if (!SaveAsDlg(szFileName, giType))
                return FALSE;
        }

        switch (giType) {
            case FT_BITMAP:
                return SaveBitmapFile(szFileName);

            case FT_ICON:
            case FT_CURSOR:
                return SaveIconCursorFile(szFileName, giType);
        }

        return FALSE;
    }

    /************************************************************************
    * OpenAFile
    *
    * Prompts for a file name to open and then does the loading of it.
    *
    * History:
    *
    ************************************************************************/

    BOOL OpenAFile(VOID)
    {
        CHAR szFileName[CCHMAXPATH];

        if (OpenDlg(szFileName, giType)) {
            /*
             * Clear out the current resource.
             */
            ClearResource();

            LoadFile(szFileName);

            return TRUE;
        }
        else {
            return FALSE;
        }
    }

    /************************************************************************
    * LoadFile
    *
    * Loads the specified file for editing.
    *
    * Arguments:
    *   PSTR pszFullFileName - Full path name to the file to load.
    *
    * History:
    *
    ************************************************************************/

    STATICFN BOOL NEAR LoadFile(
        PSTR pszFullFileName)
    {
        switch (GetTypeFromExt(pszFullFileName)) {
            case FT_BITMAP:
                return LoadBitmapFile(pszFullFileName);

            case FT_ICON:
                return LoadIconCursorFile(pszFullFileName, TRUE);

            case FT_CURSOR:
                return LoadIconCursorFile(pszFullFileName, FALSE);
        }

        return FALSE;
    }

    /************************************************************************
    * GetTypeFromExt
    *
    * Returns the type of file based on it's file name extension.
    *
    * Arguments:
    *   PSTR pszFileName - File name to check.
    *
    * History:
    *
    ************************************************************************/

    STATICFN INT NEAR GetTypeFromExt(
        PSTR pszFileName)
    {
        PSTR pszExt;

        pszExt = pszFileName + strlen(pszFileName) - 3;

        if (strcmpi(pszExt, ids(IDS_DEFEXTICO)) == 0)
            return FT_ICON;
        else if (strcmpi(pszExt, ids(IDS_DEFEXTCUR)) == 0)
            return FT_CURSOR;
        else
            return FT_BITMAP;
    }

    /************************************************************************
    * OpenCmdLineFile
    *
    * Handles opening of the file specified on the command line.
    *
    * History:
    * Nov 7, 1989   Byron Dazey - Created
    *
    ************************************************************************/

    VOID OpenCmdLineFile(
        PSTR pstrFileName)
    {
        CHAR szFullPath[CCHMAXPATH];
        OFSTRUCT OfStruct;

        strcpy(szFullPath, pstrFileName);

        /*
         * If the file name does not already have an extension,
         * assume it is a bitmap file and add a .BMP extension
         * to it.
         */
        FileCat(szFullPath, ids(IDS_DOTBMP));

        if ((HFILE)OpenFile(szFullPath, &OfStruct, OF_EXIST) == (HFILE)-1) {
            Message(MSG_CANTOPEN, pstrFileName);
        }
        else {
            LoadFile(OfStruct.szPathName);
        }
    }

    /************************************************************************
    * FileCat
    *
    * This function checks for an extension on the give file name.
    * If an extension is not found, the extension specified by
    * pchCat is added to the file name.
    *
    * Arguments:
    *     PSTR pch          = The file spec to "cat" the extension to.
    *     PSTR pchCat       = The extension to "cat" on to pch,
    *                         including the '.'
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR FileCat(
        PSTR pchName,
        PSTR pchCat)
    {
        PSTR pch;

        pch = pchName + strlen(pchName);
        pch = FAR2NEAR(AnsiPrev(pchName, pch));

        /* back up to '.' or '\\' */
        while (*pch != '.') {
            if (*pch == '\\' || pch <= pchName) {
                /* no extension, add one */
                strcat(pchName, pchCat);
                return;
            }

            pch = FAR2NEAR(AnsiPrev(pchName, pch));
        }
    }

    /************************************************************************
    * MyFileRead
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL MyFileRead(
        HFILE hf,
        LPSTR lpBuffer,
        UINT nBytes,
        PSTR pszFileName,
        INT iType)
    {
        register UINT cb;

        cb = _lread((HFILE)hf, lpBuffer, nBytes);

        if (cb == -1) {
            Message(MSG_READERROR, pszFileName);
            return FALSE;
        }
        else if (cb != nBytes) {
            Message((iType == FT_BITMAP) ? MSG_BADBMPFILE : MSG_BADICOCURFILE,
                pszFileName);
            return FALSE;
        }
        else {
            return TRUE;
        }
    }

    /************************************************************************
    * MyFileWrite
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL MyFileWrite(
        HFILE hf,
        LPSTR lpBuffer,
        UINT nBytes,
        PSTR pszFileName)
    {
        register UINT cb;

        cb = _lwrite((HFILE)hf, lpBuffer, nBytes);

        if (cb == -1 || cb != nBytes) {
            Message(MSG_WRITEERROR, pszFileName);
            return FALSE;
        }
        else {
            return TRUE;
        }
    }


GLOBALS.C

/****************************************************************************/
/*                                                                          */
/*                 Copyright (C) 1987-1996 Microsoft Corp.                */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: globals.c
*
* Global data for the image editor.
*
* History:
*
****************************************************************************/

#include "imagedit.h"
#include "dialogs.h"
#include "iehelp.h"
#include "ids.h"

/*
 * Initialized data and structures -----------------------------------------
 */

/*
 * Initialization data structure.  This describes each profile entry
 * that is contained in the initialization file.
 */
INIENTRY gaie[] = {
    { "fGrid",          &gfGrid,            FALSE,              0 },
    { "fShowColor",     &gfShowColor,       TRUE,               0 },
    { "fShowView",      &gfShowView,        TRUE,               0 },
    { "fShowToolbox",   &gfShowToolbox,     TRUE,               0 },
    { "nBrushSize",     &gnBrushSize,       3,                  0 },
    { NULL,             NULL,               0,                  0 }
};

BOOL gfGrid;                        // TRUE if the grid is on.
BOOL gfShowColor;                   // TRUE if Color palette is to be shown.
BOOL gfShowView;                    // TRUE if View window is to be shown.
BOOL gfShowToolbox;                 // TRUE if Toolbox is to be shown.
INT gnBrushSize;                    // Current brush size.

CHAR szAppPos[] = "AppPos";         // App window's position keyname.
CHAR szTBPos[] = "TBPos";           // Toolbox window's position keyname.
CHAR szViewPos[] = "ViewPos";       // View window's position keyname.
CHAR szColorPos[] = "ColorPos";     // Color palette window's position keyname.
CHAR szrgbScreen[] = "rgbScreen";   // Screen color keyname.

/*
 * Instance handles, window handles and class strings ----------------------
 */

HANDLE ghInst;                      // App instance handle.
HANDLE haccelTbl;                   // Accelerator table handle.
HCURSOR hcurWait;                   // Standard hourglass cursor.

HWND ghwndMain;                     // Main app window handle.
HWND ghwndWork;                     // Workspace window handle.
HWND ghwndPropBar;                  // Properties Bar window handle.
HWND ghwndToolbox;                  // Toolbox window handle.
HWND ghwndView;                     // View window handle.
HWND ghwndColor;                    // Color palette window handle.

CHAR szMainClass[] = "ImagEdit";    // Main window class.
CHAR szWorkClass[] = "Work";        // Work window class.
CHAR szToolboxClass[] = "Toolbox";  // Toolbox window class.
CHAR szToolBtnClass[] = "ToolBtn";  // Toolbox button window class.
CHAR szViewClass[] = "View";        // View window class.
CHAR szColorBoxClass[] = "ColorBox";// Color box window class.
CHAR szColorLRClass[] = "ColorLR";  // Color Left-Right sample class.

/*
 * Device list globals -----------------------------------------------------
 */

PDEVICE gpIconDeviceHead = NULL;    // Head of icon device list.
INT gnIconDevices = 0;              // Number of icon devices.
PDEVICE gpCursorDeviceHead = NULL;  // Head of cursor device list.
INT gnCursorDevices = 0;            // Number of cursor devices.

/*
 * Globals that describe the current file and image being edited -----------
 */

CHAR gszFullFileName[CCHMAXPATH];   // Full path name of current file.
PSTR gpszFileName = NULL;           // Current file name (or NULL).
INT giType = FT_BITMAP;             // Type of object being edited currently.
PIMAGEINFO gpImageHead = NULL;      // Head of image linked list.
INT gnImages = 0;                   // Number of images in the file.
BOOL fFileDirty;                    // TRUE if the file is dirty.

PIMAGEINFO gpImageCur = NULL;       // Pointer to current image.
INT gcxImage;                       // Width of the image.
INT gcyImage;                       // Height of the image.
INT gnColors = 16;                  // Number of colors of current image.
BOOL fImageDirty;                   // TRUE if the image is dirty.

/*
 * Drawing DC's and bitmaps ------------------------------------------------
 */

HDC ghdcImage = NULL;               // Image XOR DC.
HBITMAP ghbmImage = NULL;           // Image XOR bitmap.

HDC ghdcANDMask = NULL;             // Image AND mask DC.
HBITMAP ghbmANDMask = NULL;         // Image AND mask bitmap.

HBITMAP ghbmUndo = NULL;            // Backup of XOR bitmap for undo.
HBITMAP ghbmUndoMask = NULL;        // Backup of AND mask bitmap for undo.

/*
 * Globals for the color palette and drawing -------------------------------
 */

INT giColorLeft;                    // Index to the left color in gargbCurrent.
INT giColorRight;                   // Index to the right color in gargbCurrent.
INT gfModeLeft;                     // Mode of the left color brush.
INT gfModeRight;                    // Mode of the right color brush.
HBRUSH ghbrLeft = NULL;             // Brush with left mouse button color.
HBRUSH ghbrLeftSolid = NULL;        // Brush with solid left button color.
HBRUSH ghbrRight = NULL;            // Brush with right mouse button color.
HBRUSH ghbrRightSolid = NULL;       // Brush with solid right button color.
HBRUSH ghbrScreen = NULL;           // Brush with screen color.
HBRUSH ghbrInverse = NULL;          // Brush with inverse screen color.
HPEN ghpenLeft = NULL;              // Left color pen.
HPEN ghpenRight = NULL;             // Right color pen.
DWORD grgbScreenDefault;            // Default screen color.
DWORD grgbScreen;                   // RGB of screen color.
DWORD grgbInverse;                  // RGB of inverse screen color.
DWORD *gargbCurrent;                // Points to the current color table.
DWORD gargbColor[COLORSMAX];        // Current color color table.
DWORD gargbMono[COLORSMAX];         // Current monochrome color table.
HPEN hpenDarkGray = NULL;           // A dark gray pen.

DRAWPROC gpfnDrawProc;              // Current drawing functions.
INT gCurTool = -1;                  // Current tool (TOOL_* define).
HBRUSH ghbrDraw = NULL;             // Current drawing brush.
HBRUSH ghbrDrawSolid = NULL;        // Current solid drawing brush.
HPEN ghpenDraw = NULL;              // Current drawing pen.
INT gfDrawMode;                     // Mode of current drawing brush.

/*
 * The default color palette.
 */
DWORD gargbDefaultColor[] = {
    RGB(255, 255, 255), RGB(0, 0, 0),
    RGB(192, 192, 192), RGB(128, 128, 128),
    RGB(255, 0, 0),     RGB(128, 0, 0),
    RGB(255, 255, 0),   RGB(128, 128, 0),
    RGB(0, 255, 0),     RGB(0, 128, 0),
    RGB(0, 255, 255),   RGB(0, 128, 128),
    RGB(0, 0, 255),     RGB(0, 0, 128),
    RGB(255, 0, 255),   RGB(128, 0, 128),
    RGB(255, 255, 128), RGB(128, 128, 64),
    RGB(0, 255, 128),   RGB(0, 64, 64),
    RGB(128, 255, 255), RGB(0, 128, 255),
    RGB(128, 128, 255), RGB(0, 64, 128),
    RGB(255, 0, 128),   RGB(64, 0, 128),
    RGB(255, 128, 64),  RGB(128, 64, 0)
};

/*
 * The default monochrome palette.
 */
DWORD gargbDefaultMono[] =   {
    RGB(255, 255, 255), RGB(0, 0, 0),
    RGB(128, 128, 128), RGB(9, 9, 9),
    RGB(137, 137, 137), RGB(18, 18, 18),
    RGB(146, 146, 146), RGB(27, 27, 27),
    RGB(155, 155, 155), RGB(37, 37, 37),
    RGB(164, 164, 164), RGB(46, 46, 46),
    RGB(173, 173, 173), RGB(55, 55, 55),
    RGB(182, 182, 182), RGB(63, 63, 63),
    RGB(191, 191, 191), RGB(73, 73, 73),
    RGB(201, 201, 201), RGB(82, 82, 82),
    RGB(212, 212, 212), RGB(92, 92, 92),
    RGB(222, 222, 222), RGB(101, 101, 101),
    RGB(231, 231, 231), RGB(110, 110, 110),
    RGB(245, 245, 245), RGB(119, 119, 119)
};

/*
 * Color table for monochrome DIB's.
 */
DWORD gargbColorTable2[] = {
    RGB(0, 0, 0),
    RGB(255, 255, 255)
};

/*
 * Array that describes each tool used in the editor.  This table
 * is indexed by the TOOL_* defines.
 */
TOOLS gaTools[] = {
    { PencilDP,     NULL,   IDBM_TUPENCIL,  NULL,   IDBM_TDPENCIL,  NULL,
        TRUE, FALSE },
    { BrushDP,      NULL,   IDBM_TUBRUSH,   NULL,   IDBM_TDBRUSH,   NULL,
        TRUE, FALSE },
    { PickDP,       NULL,   IDBM_TUSELECT,  NULL,   IDBM_TDSELECT,  NULL,
        FALSE, FALSE },
    { LineDP,       NULL,   IDBM_TULINE,    NULL,   IDBM_TDLINE,    NULL,
        FALSE, TRUE },
    { RectDP,       NULL,   IDBM_TURECT,    NULL,   IDBM_TDRECT,    NULL,
        FALSE, TRUE },
    { RectDP,       NULL,   IDBM_TUSRECT,   NULL,   IDBM_TDSRECT,   NULL,
        FALSE, TRUE },
    { CircleDP,     NULL,   IDBM_TUCIRCLE,  NULL,   IDBM_TDCIRCLE,  NULL,
        FALSE, TRUE },
    { CircleDP,     NULL,   IDBM_TUSCIRCL,  NULL,   IDBM_TDSCIRCL,  NULL,
        FALSE, TRUE },
    { FloodDP,      NULL,   IDBM_TUFLOOD,   NULL,   IDBM_TDFLOOD,   NULL,
        TRUE, FALSE },
    { HotSpotDP,    NULL,   IDBM_TUHOTSPT,  NULL,   IDBM_TDHOTSPT,  NULL,
        FALSE, FALSE }
};

/*
 * Globals and tables for messages and help --------------------------------
 */

/*
 * Message box messages, for the Message() function.
 */
MESSAGEDATA gamdMessages[] = {
    { IDS_OUTOFMEMORY,          MB_OK | MB_ICONHAND                 },
    { IDS_MEMERROR,             MB_OK | MB_ICONHAND                 },
    { IDS_BADBMPFILE,           MB_OK | MB_ICONEXCLAMATION          },
    { IDS_BADICOCURFILE,        MB_OK | MB_ICONEXCLAMATION          },
    { IDS_BADPALFILE,           MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CANTOPEN,             MB_OK | MB_ICONEXCLAMATION          },
    { IDS_READERROR,            MB_OK | MB_ICONEXCLAMATION          },
    { IDS_WRITEERROR,           MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CANTCREATE,           MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOCLIPBOARDFORMAT,    MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOCLIPBOARD,          MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CANTEDITIMAGE,        MB_OK | MB_ICONEXCLAMATION          },
    { IDS_SAVEFILE,             MB_YESNOCANCEL | MB_ICONEXCLAMATION },
    { IDS_ENTERANUMBER,         MB_OK | MB_ICONEXCLAMATION          },
    { IDS_BADDEVICESIZE,        MB_OK | MB_ICONEXCLAMATION          },
    { IDS_BADDEVICECOLORS,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOTSUPPORT,           MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOIMAGES,             MB_OK | MB_ICONEXCLAMATION          },
    { IDS_BADBMPSIZE,           MB_OK | MB_ICONEXCLAMATION          }
};

INT gidCurrentDlg = 0;              // Current dialog id (null if none).
INT gMenuSelected = 0;              // Currently selected menu item.
CHAR gszHelpFile[CCHMAXPATH];       // Path to the help file.
HHOOK ghhkMsgFilter;                // Hook handle for message filter func.
FARPROC lpfnMsgFilterHookFunc;      // The message filter proc instance.

/*
 * Table that maps menu items to help context id's for them.
 */
HELPMAP gahmapMenu[] = {
    {MENU_FILE_NEW,             HELPID_FILE_NEW                 },
    {MENU_FILE_OPEN,            HELPID_FILE_OPEN                },
    {MENU_FILE_SAVE,            HELPID_FILE_SAVE                },
    {MENU_FILE_SAVEAS,          HELPID_FILE_SAVEAS              },
    {MENU_FILE_LOADCOLORS,      HELPID_FILE_LOADCOLORS          },
    {MENU_FILE_SAVECOLORS,      HELPID_FILE_SAVECOLORS          },
    {MENU_FILE_DEFAULTCOLORS,   HELPID_FILE_DEFAULTCOLORS       },
    {MENU_FILE_EXIT,            HELPID_FILE_EXIT                },

    {MENU_EDIT_UNDO,            HELPID_EDIT_UNDO                },
    {MENU_EDIT_RESTORE,         HELPID_EDIT_RESTORE             },
    {MENU_EDIT_COPY,            HELPID_EDIT_COPY                },
    {MENU_EDIT_PASTE,           HELPID_EDIT_PASTE               },
    {MENU_EDIT_CLEAR,           HELPID_EDIT_CLEAR               },
    {MENU_EDIT_NEWIMAGE,        HELPID_EDIT_NEWIMAGE            },
    {MENU_EDIT_SELECTIMAGE,     HELPID_EDIT_SELECTIMAGE         },
    {MENU_EDIT_DELETEIMAGE,     HELPID_EDIT_DELETEIMAGE         },

    {MENU_OPTIONS_GRID,         HELPID_OPTIONS_GRID             },
    {MENU_OPTIONS_BRUSH2,       HELPID_OPTIONS_BRUSH2           },
    {MENU_OPTIONS_BRUSH3,       HELPID_OPTIONS_BRUSH3           },
    {MENU_OPTIONS_BRUSH4,       HELPID_OPTIONS_BRUSH4           },
    {MENU_OPTIONS_BRUSH5,       HELPID_OPTIONS_BRUSH5           },
    {MENU_OPTIONS_SHOWCOLOR,    HELPID_OPTIONS_SHOWCOLOR        },
    {MENU_OPTIONS_SHOWVIEW,     HELPID_OPTIONS_SHOWVIEW         },
    {MENU_OPTIONS_SHOWTOOLBOX,  HELPID_OPTIONS_SHOWTOOLBOX      },

    {MENU_HELP_CONTENTS,        HELPID_HELP_CONTENTS            },
    {MENU_HELP_SEARCH,          HELPID_HELP_SEARCH              },
    // No help for the About menu command.

    {0,                         0                               }
};

/*
 * Table that maps dialog ids to help context id's for them.
 */
HELPMAP gahmapDialog[] = {
    // No help for the About dialog.
    {DID_BITMAPSIZE,            HELPID_BITMAPSIZE               },
    {DID_PASTEOPTIONS,          HELPID_PASTEOPTIONS             },
    {DID_NEWCURSORIMAGE,        HELPID_NEWCURSORIMAGE           },
    {DID_NEWICONIMAGE,          HELPID_NEWICONIMAGE             },
    {DID_SELECTCURSORIMAGE,     HELPID_SELECTCURSORIMAGE        },
    {DID_SELECTICONIMAGE,       HELPID_SELECTICONIMAGE          },
    {DID_RESOURCETYPE,          HELPID_RESOURCETYPE             },

    {DID_COMMONFILEOPEN,        HELPID_COMMONFILEOPEN           },
    {DID_COMMONFILESAVE,        HELPID_COMMONFILESAVE           },
    {DID_COMMONFILEOPENPAL,     HELPID_COMMONFILEOPENPAL        },
    {DID_COMMONFILESAVEPAL,     HELPID_COMMONFILESAVEPAL        },
    {DID_COMMONFILECHOOSECOLOR, HELPID_COMMONFILECHOOSECOLOR    },

    {DID_TOOLBOX,               HELPID_TOOLBOX                  },
    {DID_PROPBAR,               HELPID_PROPERTIESBAR            },
    {DID_COLOR,                 HELPID_COLORPALETTE             },
    {DID_VIEW,                  HELPID_VIEW                     },

    {0,                         0                               }
};

/*
 * Misc. globals -----------------------------------------------------------
 */

INT gcxWorkSpace;                   // Width of workspace window.
INT gcyWorkSpace;                   // Height of workspace window.
INT gZoomFactor;                    // Magnification factor of image.

RECT grcPick;                       // The current picking rectangle.
INT gcxPick;                        // Width of picking rectangle.
INT gcyPick;                        // Height of picking rectangle.

UINT ClipboardFormat;               // ID of private clipboard format.
BOOL fStretchClipboardData = TRUE;  // TRUE to default to stretch on paste.

INT iNewFileType;                   // New file type the user selected.

INT gcyBorder;                      // System border height.
INT gcyPropBar;                     // Height of PropBar window.

WNDPROC lpfnPropBarDlgProc = NULL;  // Proc inst. of PropBar dialog proc.
WNDPROC lpfnColorDlgProc = NULL;    // Proc inst. of Color palette dlg proc.


GLOBALS.H

/****************************************************************************/
/*                                                                          */
/*                 Copyright (C) 1987-1996 Microsoft Corp.                */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: globals.h
*
* Declares global data for the image editor.
*
* History:
*
****************************************************************************/

/*
 * Initialized data and structures -----------------------------------------
 */

extern INIENTRY gaie[];             // Initialization data structure.

extern BOOL gfGrid;                 // TRUE if the grid is on.
extern BOOL gfShowColor;            // TRUE if Color palette is to be shown.
extern BOOL gfShowView;             // TRUE if View window is to be shown.
extern BOOL gfShowToolbox;          // TRUE if Toolbox is to be shown.
extern INT gnBrushSize;             // Current brush size.

extern CHAR szAppPos[];             // App window's position keyname.
extern CHAR szTBPos[];              // Toolbox window's position keyname.
extern CHAR szViewPos[];            // View window's position keyname.
extern CHAR szColorPos[];           // Color palette window's position keyname.
extern CHAR szrgbScreen[];          // Screen color keyname.

/*
 * Instance handles, window handles and class strings ----------------------
 */

extern HANDLE ghInst;               // App instance handle.
extern HANDLE haccelTbl;            // Accelerator table handle.
extern HCURSOR hcurWait;            // Standard hourglass cursor.

extern HWND ghwndMain;              // Main app window handle.
extern HWND ghwndWork;              // Workspace window handle.
extern HWND ghwndPropBar;           // Properties Bar window handle.
extern HWND ghwndToolbox;           // Toolbox window handle.
extern HWND ghwndView;              // View window handle.
extern HWND ghwndColor;             // Color palette window handle.

extern CHAR szMainClass[];          // Main window class.
extern CHAR szWorkClass[];          // Work window class.
extern CHAR szToolboxClass[];       // Toolbox window class.
extern CHAR szToolBtnClass[];       // Toolbox button window class.
extern CHAR szViewClass[];          // View window class.
extern CHAR szColorBoxClass[];      // Color box window class.
extern CHAR szColorLRClass[];       // Color Left-Right sample class.

/*
 * Device list globals -----------------------------------------------------
 */

extern PDEVICE gpIconDeviceHead;    // Head of icon device list.
extern INT gnIconDevices;           // Number of icon devices.
extern PDEVICE gpCursorDeviceHead;  // Head of cursor device list.
extern INT gnCursorDevices;         // Number of cursor devices.

/*
 * Globals that describe the current file and image being edited -----------
 */

extern CHAR gszFullFileName[];      // Full path name of current file.
extern PSTR gpszFileName;           // Current file name (or NULL).
extern INT giType;                  // Type of object being edited currently.
extern PIMAGEINFO gpImageHead;      // Head of image linked list.
extern INT gnImages;                // Number of images in the file.
extern BOOL fFileDirty;             // TRUE if the file is dirty.

extern PIMAGEINFO gpImageCur;       // Pointer to current image.
extern INT gcxImage;                // Width of the image.
extern INT gcyImage;                // Height of the image.
extern INT gnColors;                // Number of colors of current image.
extern BOOL fImageDirty;            // TRUE if the image is dirty.

/*
 * Drawing DC's and bitmaps ------------------------------------------------
 */

extern HDC ghdcImage;               // Image XOR DC.
extern HBITMAP ghbmImage;           // Image XOR bitmap.

extern HDC ghdcANDMask;             // Image AND mask DC.
extern HBITMAP ghbmANDMask;         // Image AND mask bitmap.

extern HBITMAP ghbmUndo;            // Backup of XOR bitmap for undo.
extern HBITMAP ghbmUndoMask;        // Backup of AND mask bitmap for undo.

/*
 * Globals for the color palette and drawing -------------------------------
 */

extern INT giColorLeft;             // Index to the left color in gargbCurrent.
extern INT giColorRight;            // Index to the right color in gargbCurrent.
extern INT gfModeLeft;              // Mode of the left color brush.
extern INT gfModeRight;             // Mode of the right color brush.
extern HBRUSH ghbrLeft;             // Brush with left mouse button color.
extern HBRUSH ghbrLeftSolid;        // Brush with solid left button color.
extern HBRUSH ghbrRight;            // Brush with right mouse button color.
extern HBRUSH ghbrRightSolid;       // Brush with solid right button color.
extern HBRUSH ghbrScreen;           // Brush with screen color.
extern HBRUSH ghbrInverse;          // Brush with inverse screen color.
extern HPEN ghpenLeft;              // Left color pen.
extern HPEN ghpenRight;             // Right color pen.
extern DWORD grgbScreenDefault;     // Default screen color.
extern DWORD grgbScreen;            // RGB of screen color.
extern DWORD grgbInverse;           // RGB of inverse screen color.
extern DWORD *gargbCurrent;         // Points to the current color table.
extern DWORD gargbColor[];          // Current color color table.
extern DWORD gargbMono[];           // Current monochrome color table.
extern HPEN hpenDarkGray;           // A dark gray pen.

extern DRAWPROC gpfnDrawProc;       // Current drawing functions.
extern INT gCurTool;                // Current tool (TOOL_* define).
extern HBRUSH ghbrDraw;             // Current drawing brush.
extern HBRUSH ghbrDrawSolid;        // Current solid drawing brush.
extern HPEN ghpenDraw;              // Current drawing pen.
extern INT gfDrawMode;              // Mode of current drawing brush.

extern DWORD gargbDefaultColor[];   // The default color palette.

extern DWORD gargbDefaultMono[];    // The default monochrome palette.

extern DWORD gargbColorTable2[];    // Color table for monochrome DIB's.

extern TOOLS gaTools[];             // Tool table.

/*
 * Globals and tables for messages and help --------------------------------
 */

extern MESSAGEDATA gamdMessages[];  // Message box messages table.

extern INT gidCurrentDlg;           // Current dialog id (null if none).
extern INT gMenuSelected;           // Currently selected menu item.
extern CHAR gszHelpFile[];          // Path to the help file.
extern HHOOK ghhkMsgFilter;         // Hook handle for message filter func.
extern FARPROC lpfnMsgFilterHookFunc;   // The message filter proc instance.

extern HELPMAP gahmapMenu[];        // Menu item to help topic mapping table.

extern HELPMAP gahmapDialog[];      // Dialog id to help topic mapping table.

/*
 * Misc. globals -----------------------------------------------------------
 */

extern INT gcxWorkSpace;            // Width of workspace window.
extern INT gcyWorkSpace;            // Height of workspace window.
extern INT gZoomFactor;             // Magnification factor of image.

extern RECT grcPick;                // The current picking rectangle.
extern INT gcxPick;                 // Width of picking rectangle.
extern INT gcyPick;                 // Height of picking rectangle.

extern UINT ClipboardFormat;        // ID of private clipboard format.
extern BOOL fStretchClipboardData;  // TRUE to default to stretch on paste.

extern INT iNewFileType;            // New file type the user selected.

extern INT gcyBorder;               // System border height.
extern INT gcyPropBar;              // Height of PropBar window.

extern WNDPROC lpfnPropBarDlgProc;  // Proc inst. of PropBar dialog proc.
extern WNDPROC lpfnColorDlgProc;    // Proc inst. of Color palette dlg proc.


ICCLIP.C

/***************************************************************************
     *                                                                         *
     *  MODULE      : ICClip.C                                                 *
     *                                                                         *
     *  DESCRIPTION : Clipboard functions for ImagEdit                         *
     *                                                                         *
     *  FUNCTIONS   : CopyImageClip ()  - Copies selected portion of image to  *
     *                                    the clipboard.                       *
     *                                                                         *
     *                PasteImageClip () - Pastes the clipboard image to        *
     *                                    selected portion of edit image.      *
     *                                                                         *
     *                                                                         *
     *  HISTORY     : 6/21/89 - created by LR                                  *
     *                                                                         *
     ***************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"
    #include "iehelp.h"

 #include <windowsx.h>

    /*==========================================================================
      |ImagEdit's clipboard data is in two formats:                              |
      |        a) a standard CF_BITMAP format  and                               |
      |        b) a private ImagEdit format described below.                     |
      |                                                                          |
      |The private ImagEdit format data consists of:                             |
      |   1.  a DWORD describing screen color when image was sent to clipboard   |
      |       followed by...                                                     |
      |   2.  the DIB bits of the monochrome AND image (in ghdcANDMask).         |
      |                                                                          |
      |The CF_BITMAP format consists of the image bitmap (the combined XOR and   |
      |AND images in ghdcImage for icons and cursors).                           |
      |                                                                          |
      |This information is sufficient to re-create the image correctly during    |
      |paste even if the screen viewing color is subsequently changed.           |
      |                                                                          |
      |Both formats are created if the image being edited is an icon or a cursor.|
      |Only the CF_BITMAP format is created if a bitmap is being edited.         |
      ==========================================================================*/

    /****************************************************************************
     *                                                                          *
     *  FUNCTION   : BOOL PASCAL CopyImageClip(fBitmap)                         *
     *                                                                          *
     *  PURPOSE    : Copies the information from the selected area of image to  *
     *               the clipboard.                                             *
     *                                                                          *
     *  SIDE EFFECTS: may change contents of the clipboard. The "pick" or clip  *
     *                rectangle is reset to cover the entire image.             *
     *                                                                          *
     ****************************************************************************/

    BOOL CopyImageClip(VOID)
    {
        HCURSOR hcurOld;
        HBITMAP hStdBitmap;
        HBITMAP hPrivBitmap;
        HDC hStdDC;
        HDC hPrivDC;
        HANDLE hOldSObj;
        HANDLE hOldPObj;
        HANDLE hPriv;
        LPSTR lpPriv;

        hcurOld = SetCursor(hcurWait);

        /* create a temp. bitmap and DC for the standard clipboard format
         * along the same lines as the image bitmap
         */
        hStdDC = CreateCompatibleDC(ghdcImage);
        hStdBitmap = MyCreateBitmap(ghdcImage, gcxPick, gcyPick, 16);
        hOldSObj = SelectObject(hStdDC, hStdBitmap);

        /* blt the image bits into standard format DC */
        BitBlt(hStdDC, 0, 0, gcxPick, gcyPick, ghdcImage,
                grcPick.left, grcPick.top, SRCCOPY);
        SelectObject(hStdDC, hOldSObj);

        if (giType != FT_BITMAP) {
            /* for icons and cursors, create a temp. DC and bitmap for the AND
            * mask and blt the mask bits into it.
            */
            hPrivDC = CreateCompatibleDC(ghdcANDMask);
            hPrivBitmap = CreateCompatibleBitmap(ghdcANDMask, gcxPick, gcyPick);
            hOldPObj = SelectObject(hPrivDC, hPrivBitmap);
            BitBlt(hPrivDC, 0, 0, gcxPick, gcyPick, ghdcANDMask,
                    grcPick.left, grcPick.top, SRCCOPY);

            /* Allocate a buffer for the private ImagEdit format */
            hPriv = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                    (DWORD)((gcxPick + 31) >> 3) * gcyPick + sizeof(DWORD));
            if (!hPriv) {
                DeleteDC(hStdDC);
                DeleteObject(hStdBitmap);
                DeleteDC(hPrivDC);
                DeleteObject(hPrivBitmap);
                return FALSE;
            }

            lpPriv = (LPSTR)GlobalLock(hPriv);

            /* Fill in the first DWORD with the screen color information */
            *((DWORD FAR *)lpPriv) = grgbScreen;

            /* Get the mask bits into the buffer */
            GetBitmapBits(hPrivBitmap, (DWORD)((gcxPick + 31) >> 3) * gcyPick,
                    (LPSTR)lpPriv + sizeof(DWORD));

            SelectObject(hPrivDC, hOldPObj);
            DeleteObject(hPrivBitmap);
            DeleteDC(hPrivDC);
        }

        /* Open clipboard and clear it of it's contents */
        if (!OpenClipboard(ghwndMain)) {
            DeleteDC(hStdDC);
            return(FALSE);
        }
        EmptyClipboard();

        if (giType != FT_BITMAP) {
            /* set the private ImagEdit format data into the clipboard */
            if (!SetClipboardData(ClipboardFormat, hPriv)) {
                DeleteDC(hStdDC);
                GlobalUnlock(hPriv);
                GlobalFree(hPriv);
                CloseClipboard();
                return(FALSE);
            }
            GlobalUnlock(hPriv);
        }
        /* set the standard CF_BITMAP format data in the clipboard */
        if (!SetClipboardData(CF_BITMAP, hStdBitmap)) {
            DeleteDC(hStdDC);
            GlobalFree(hPriv);  //   hPriv may not have been initialized (if giType == BITMAP).
            CloseClipboard();
            return(FALSE);
        }

        CloseClipboard();
        DeleteDC(hStdDC);

        /*
         * Reset pick rectangle to cover entire image.
         */
        PickSetRect(0, 0, gcxImage - 1, gcyImage - 1);

        /*
         * Erase the drag rectangle.
         */
        WorkUpdate();

        SetCursor(hcurOld);

        return TRUE;
    }

    /************************************************************************
    * PasteImageClip
    *
    * Pastes an image from the clipboard to the current image.
    *
    * It is assumed that this routine will not be called unless an
    * image is currently being edited.
    *
    * The pick rectangle is reset to cover the entire image if the
    * paste is successful.
    *
    * Basic outline of how Paste is done in ImagEdit.
    *
    * Find out what format is available in the clipboard:
    *    a. CF_BITMAP only
    *       --------------
    *       case 1: Pasting to an icon or cursor
    *                 * We don't have any screen color information.
    *                   Make the mask bits opaque and blt. the bitmap to
    *                   ghdcImage.
    *
    *       case 2: Pasting to a bitmap
    *                 * Blt the bitmap to the image DC.
    *
    *    b. both ImagEdit and CF_BITMAP
    *       ---------------------------
    *       case 1: Pasting to an icon or cursor
    *                 * Recover the image from the AND and screen color
    *                   data (in ImagEdit) and the combined image bitmap
    *                   (in CF_BITMAP). Use the information to make the
    *                   neccessary changes if the screen viewing color was
    *                   changed between Copy and Paste.
    *
    *       case 2: Pasting to a bitmap
    *                 * Blt the CF_BITMAP data to the image DC.
    *
    * If the destination image differs in dimensions from
    * source image, the source image is stretched or clipped to that of
    * destination, depending on preference
    *
    * History:
    *
    ************************************************************************/

    BOOL PasteImageClip(VOID)
    {
        HCURSOR hcurOld;
        INT cxClip;
        INT cyClip;
        INT cxTarget;
        INT cyTarget;
        INT cxSource;
        INT cySource;
        DWORD rgbClipScreen;
        BOOL fIEFormatFound;
        HANDLE hClipData;
        LPSTR lpClipData;
        BITMAP bmClip;
        HDC hdcClip;
        HBITMAP hbmClip;
        HBITMAP hbmClipOld;
        HDC hdcClipAND;
        HBITMAP hbmClipAND;
        HBITMAP hbmClipANDOld;
        HDC hdcClipAND16;
        HBITMAP hbmClipAND16;
        HBITMAP hbmClipAND16Old;
        HDC hdcTarget;
        HBITMAP hbmTarget;
        HBITMAP hbmTargetOld;
        HDC hdcTargetAND16;
        HBITMAP hbmTargetAND16;
        HBITMAP hbmTargetAND16Old;
        HDC hdcTargetAND;
        HBITMAP hbmTargetAND;
        HBITMAP hbmTargetANDOld;

        hcurOld = SetCursor(hcurWait);

        if (!OpenClipboard(ghwndMain)) {
            Message(MSG_NOCLIPBOARD);
            goto Error1;
        }

        if (!(hbmClip = GetClipboardData(CF_BITMAP))) {
            Message(MSG_NOCLIPBOARDFORMAT);
            goto Error2;
        }

        GetObject(hbmClip, sizeof(BITMAP), (LPSTR)&bmClip);
        cxClip = (INT)bmClip.bmWidth;
        cyClip = (INT)bmClip.bmHeight;

        /*
         * If the dimensions of the current pick rectangle don't match
         * the bitmap being pasted, ask the user if they want to stretch
         * or clip the pasted image.
         */
        cxTarget = gcxPick;
        cyTarget = gcyPick;
        cxSource = cxClip;
        cySource = cyClip;
        if (gcxPick != cxClip || gcyPick != cyClip) {
            if (DlgBox(DID_PASTEOPTIONS, (WNDPROC)PasteOptionsDlgProc) == IDCANCEL) {
                goto Error2;
            }

            /*
             * If clipping and the clipboard dimensions differ from the
             * selected pick rectangle, then either the target dimensions
             * or the source dimensions need to be sized down.
             */
            if (!fStretchClipboardData) {
                if (cxClip < gcxPick)
                    cxTarget = cxClip;
                else
                    cxSource = gcxPick;

                if (cyClip < gcyPick)
                    cyTarget = cyClip;
                else
                    cySource = gcyPick;
            }
        }

        /*
         * Update the undo buffer now that we are committed to the paste.
         */
        ImageUpdateUndo();

        /*
         * Determine if the private ImagEdit clipboard format is available.
         */
        fIEFormatFound = IsClipboardFormatAvailable(ClipboardFormat);

        if (giType != FT_BITMAP && fIEFormatFound) {
            /*
             * Get the AND mask bitmap and the old screen color out of
             * the private format.
             */
            hClipData = GetClipboardData(ClipboardFormat);
            lpClipData = (LPSTR)GlobalLock(hClipData);
            rgbClipScreen = *((DWORD FAR *)lpClipData);
            hdcClipAND = CreateCompatibleDC(ghdcImage);
            hbmClipAND = CreateBitmap(cxClip, cyClip, (BYTE)1, (BYTE)1,
                    (LPSTR)lpClipData + sizeof(DWORD));
            hbmClipANDOld = SelectObject(hdcClipAND, hbmClipAND);

            /*
             * Create a color bitmap for temporary use.
             */
            hdcClipAND16 = CreateCompatibleDC(ghdcImage);
            hbmClipAND16 = MyCreateBitmap(ghdcImage, cxSource, cySource, 16);
            hbmClipAND16Old = SelectObject(hdcClipAND16, hbmClipAND16);

            /*
             * Blt the AND mask onto the color bitmap.
             */
            BitBlt(hdcClipAND16, 0, 0, cxSource, cySource, hdcClipAND,
                    0, 0, SRCCOPY);

            /*
             * Create the color target AND mask bitmap.
             */
            hdcTargetAND16 = CreateCompatibleDC(ghdcImage);
            hbmTargetAND16 = MyCreateBitmap(ghdcImage, cxTarget, cyTarget, 16);
            hbmTargetAND16Old = SelectObject(hdcTargetAND16, hbmTargetAND16);

            /*
             * StretchBlt from the color AND mask bitmap to the color target
             * AND mask bitmap.  The blt must be done from a color bitmap to
             * a color bitmap, and the stretch blt mode must be set to
             * COLORONCOLOR.  All this is necessary so that the AND mask
             * stays exactly in sync with the stretch blt of the color
             * (XOR) mask.  If these steps are not done correctly, shrinking
             * an image with screen colored pixels in it can cause problems,
             * because the stretch blt will use a slightly different
             * algorithm to compress the monochrome AND mask and the color
             * XOR mask.
             */
            SetStretchBltMode(hdcTargetAND16, COLORONCOLOR);
            SetStretchBltMode(hdcClipAND16, COLORONCOLOR);     //
            StretchBlt(hdcTargetAND16, 0, 0, cxTarget, cyTarget, hdcClipAND16,
                    0, 0, cxSource, cySource, SRCCOPY);

            /*
             * Create the monochrome target AND mask bitmap.
             */
            hdcTargetAND = CreateCompatibleDC(ghdcImage);
            hbmTargetAND = MyCreateBitmap(ghdcImage, cxTarget, cyTarget, 2);
            hbmTargetANDOld = SelectObject(hdcTargetAND, hbmTargetAND);

            /*
             * Blt the color AND mask onto the monochrome AND mask.
             * The monochrome AND mask is the one that we will use
             * later.  It must be monochrome or the ImageDCSeparate
             * and ImageDCCombine functions will not work properly.
             */
            BitBlt(hdcTargetAND, 0, 0, cxTarget, cyTarget, hdcTargetAND16,
                    0, 0, SRCCOPY);

            /*
             * Cleanup.
             */
            SelectObject(hdcTargetAND16, hbmTargetAND16Old);
            DeleteObject(hbmTargetAND16);
            DeleteDC(hdcTargetAND16);
            SelectObject(hdcClipAND16, hbmClipAND16Old);
            DeleteObject(hbmClipAND16);
            DeleteDC(hdcClipAND16);
            SelectObject(hdcClipAND, hbmClipANDOld);
            DeleteObject(hbmClipAND);
            DeleteDC(hdcClipAND);
            GlobalUnlock(hClipData);
        }

        /*
         * Get the clipboard bitmap into a DC.
         */
        hdcClip = CreateCompatibleDC(ghdcImage);
        hbmClipOld = SelectObject(hdcClip, hbmClip);

        /*
         * Create the target bitmap.
         */
        hdcTarget = CreateCompatibleDC(ghdcImage);
        hbmTarget = MyCreateBitmap(ghdcImage, cxTarget, cyTarget, 16);
        hbmTargetOld = SelectObject(hdcTarget, hbmTarget);

        /*
         * StretchBlt the bitmap onto the target.
         */
        SetStretchBltMode(hdcTarget, COLORONCOLOR);
        SetStretchBltMode(hdcClip, COLORONCOLOR);     //
        StretchBlt(hdcTarget, 0, 0, cxTarget, cyTarget, hdcClip, 0, 0,
                cxSource, cySource, SRCCOPY);

        /*
         * Handle some special cases.
         */
        if (giType == FT_BITMAP || !fIEFormatFound) {
            /*
             * The image we are pasting into is either a bitmap, or
             * there does not exist an AND mask in the clipboard.
             */
            if (gnColors == 2) {
                /*
                 * We are pasting to a mono image.  We must convert the
                 * colors in the clipboard bitmap into monochrome.
                 */
                ImageDCMonoBlt(hdcTarget, cxTarget, cyTarget);
            }
        }
        else {
            /*
             * We are pasting into an icon or cursor image and we have
             * available an AND mask.  Is the current image monochrome?
             */
            if (gnColors == 2) {
                /*
                 * Remove the old screen/inverse colors from the image,
                 * convert it to monochrome, then put back in the
                 * current screen/inverse colors.
                 */
                ImageDCSeparate(hdcTarget, cxTarget, cyTarget, hdcTargetAND,
                        rgbClipScreen);
                ImageDCMonoBlt(hdcTarget, cxTarget, cyTarget);
                ImageDCCombine(hdcTarget, cxTarget, cyTarget, hdcTargetAND);
            }
            /*
             * Does the screen color specified in the clipboard
             * differ from the current screen color?
             */
            else if (rgbClipScreen != grgbScreen) {
                /*
                 * Remove the old screen/inverse colors, then put back
                 * in the current ones.
                 */
                ImageDCSeparate(hdcTarget, cxTarget, cyTarget, hdcTargetAND,
                        rgbClipScreen);
                ImageDCCombine(hdcTarget, cxTarget, cyTarget, hdcTargetAND);
            }
        }

        /*
         * Blt the clipboard image to the proper rectangle in the current image.
         */
        BitBlt(ghdcImage, grcPick.left, grcPick.top,
                cxTarget, cyTarget, hdcTarget, 0, 0, SRCCOPY);

        /*
         * If the current image is an icon or cursor, we must take care
         * of the AND mask also.
         */
        if (giType != FT_BITMAP) {
            /*
             * Is there an AND mask in the clipboard to use?
             */
            if (fIEFormatFound) {
                /*
                 * Blt it into the current image's AND mask.
                 */
                BitBlt(ghdcANDMask, grcPick.left, grcPick.top,
                        cxTarget, cyTarget, hdcTargetAND, 0, 0, SRCCOPY);
            }
            else {
                /*
                 * Make the AND mask opaque, because there is no
                 * screen color information.
                 */
                PatBlt(ghdcANDMask, grcPick.left, grcPick.top,
                        cxTarget, cyTarget, BLACKNESS);
            }
        }

        /*
         * Cleanup.
         */
        SelectObject(hdcTarget, hbmTargetOld);
        DeleteObject(hbmTarget);
        DeleteDC(hdcTarget);

        if (giType != FT_BITMAP && fIEFormatFound) {
            SelectObject(hdcTargetAND, hbmTargetANDOld);
            DeleteObject(hbmTargetAND);
            DeleteDC(hdcTargetAND);
        }

        SelectObject(hdcClip, hbmClipOld);
        DeleteDC(hdcClip);

        CloseClipboard();

        /*
         * Update the View and workspace windows.
         */
        ViewUpdate();

        /*
         * Reset pick rectangle to cover entire image.
         */
        PickSetRect(0, 0, gcxImage - 1, gcyImage - 1);

        fImageDirty = TRUE;

        SetCursor(hcurOld);

        return TRUE;

    Error2:
        CloseClipboard();

    Error1:
        SetCursor(hcurOld);

        return FALSE;
    }

    /************************************************************************
    * PasteOptionsDlgProc
    *
    * Proc for the dialog that asks the user whether they want to clip
    * or stretch the bitmap being pasted in.
    *
    * Upon return with an IDOK value, the fStretchClipboardData global
    * will be TRUE if they want to stretch, or FALSE if they want to clip.
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC PasteOptionsDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_INITDIALOG:
                CheckRadioButton(hwnd, DID_PASTEOPTIONSSTRETCH,
                        DID_PASTEOPTIONSCLIP,
                        fStretchClipboardData ?
                        DID_PASTEOPTIONSSTRETCH : DID_PASTEOPTIONSCLIP);

                CenterWindow(hwnd);

                break;

            case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                    case IDOK :
                        if (IsDlgButtonChecked(hwnd, DID_PASTEOPTIONSSTRETCH))
                            fStretchClipboardData = TRUE;
                        else
                            fStretchClipboardData = FALSE;

                        EndDialog(hwnd, IDOK);
                        break;

                    case IDCANCEL:
                        EndDialog(hwnd, IDCANCEL);
                        break;

                    case IDHELP:
                        WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                                HELPID_PASTEOPTIONS);
                        break;
                }

                break;

            default:
                return FALSE;
        }

        return TRUE;
    }

    /************************************************************************
    * PickSetRect
    *
    * Sets the globals for the picking rectangle size.  This affects
    * what is copied into the clipboard.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PickSetRect(
        INT xLeft,
        INT yTop,
        INT xRight,
        INT yBottom)
    {
        SetRect(&grcPick, xLeft, yTop, xRight, yBottom);
        gcxPick = (grcPick.right - grcPick.left) + 1;
        gcyPick = (grcPick.bottom - grcPick.top) + 1;
    }


IDS.H

/****************************************************************************/
/*                                                                          */
/*                 Copyright (C) 1987-1996 Microsoft Corp.                */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: ids.h
*
* Contains id values for the image editor.
*
* History:
*
****************************************************************************/

/*
 * Menu id's. --------------------------------------------------------------
 */

#define MENU_FILE_NEW               1000
#define MENU_FILE_OPEN              1001
#define MENU_FILE_SAVE              1002
#define MENU_FILE_SAVEAS            1003
#define MENU_FILE_LOADCOLORS        1004
#define MENU_FILE_SAVECOLORS        1005
#define MENU_FILE_DEFAULTCOLORS     1006
#define MENU_FILE_EXIT              1007

#define MENU_EDIT_UNDO              1010
#define MENU_EDIT_RESTORE           1011
#define MENU_EDIT_COPY              1012
#define MENU_EDIT_PASTE             1013
#define MENU_EDIT_CLEAR             1014
#define MENU_EDIT_NEWIMAGE          1015
#define MENU_EDIT_SELECTIMAGE       1016
#define MENU_EDIT_DELETEIMAGE       1017

#define MENU_OPTIONS_GRID           1020
#define MENU_OPTIONS_BRUSH2         1021
#define MENU_OPTIONS_BRUSH3         1022
#define MENU_OPTIONS_BRUSH4         1023
#define MENU_OPTIONS_BRUSH5         1024
#define MENU_OPTIONS_SHOWCOLOR      1025
#define MENU_OPTIONS_SHOWVIEW       1026
#define MENU_OPTIONS_SHOWTOOLBOX    1027

#define MENU_HELP_CONTENTS          1030
#define MENU_HELP_SEARCH            1031
#define MENU_HELP_ABOUT             1032

/*
 * Hidden menu commands (accessed by accelerators).
 */
#define MENU_HIDDEN_TOCOLORPAL      1040
#define MENU_HIDDEN_TOVIEW          1041
#define MENU_HIDDEN_TOTOOLBOX       1042
#define MENU_HIDDEN_TOPROPBAR       1043

/*
 * Various resource id's. --------------------------------------------------
 */

/*
 * ID's for the icons.
 */
#define IDICON_IMAGEDIT         7000

/*
 * ID's for the cursors.
 */
#define IDCUR_HOTSPOT           7010
#define IDCUR_FLOOD             7011
#define IDCUR_CROSS             7012
#define IDCUR_PENCIL            7013
#define IDCUR_BRUSH             7014

/*
 * Toolbox button bitmaps.  The IDBM_TU* id's are for the "up"
 * (not depressed) bitmaps and the IDBM_TD* id's are for the "down"
 * (depressed) bitmaps.
 */
#define IDBM_TUPENCIL           8000
#define IDBM_TUBRUSH            8001
#define IDBM_TUSELECT           8002
#define IDBM_TULINE             8003
#define IDBM_TURECT             8004
#define IDBM_TUSRECT            8005
#define IDBM_TUCIRCLE           8006
#define IDBM_TUSCIRCL           8007
#define IDBM_TUFLOOD            8008
#define IDBM_TUHOTSPT           8009

#define IDBM_TDPENCIL           8020
#define IDBM_TDBRUSH            8021
#define IDBM_TDSELECT           8022
#define IDBM_TDLINE             8023
#define IDBM_TDRECT             8024
#define IDBM_TDSRECT            8025
#define IDBM_TDCIRCLE           8026
#define IDBM_TDSCIRCL           8027
#define IDBM_TDFLOOD            8028
#define IDBM_TDHOTSPT           8029


IEFUNCS.H

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: iefuncs.h
    *
    * Function declarations for the Image Editor.
    *
    * History:
    *
    ****************************************************************************/

    /*
     * colorwp.c
     */

    VOID ColorShow(BOOL fShow);
    DIALOGPROC ColorDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    WINDOWPROC ColorBoxWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    WINDOWPROC ColorLRWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    VOID SetScreenColor(DWORD rgb);
    VOID SetColorPalette(INT nColors, INT fImageType, BOOL fForce);
    VOID RestoreDefaultColors(VOID);

    /*
     * devinfo.c
     */

    VOID InitDeviceList(VOID);
    PDEVICE DeviceLinkAlloc(INT iType, PSTR pszName, INT nColors, INT cx, INT cy);
    PDEVICE DeviceLinkFind(PDEVICE pDeviceHead, INT nColors, INT cx, INT cy);
    BOOL DeviceLinkUsed(PDEVICE pDevice);

    /*
     * file.c
     */

    VOID SetFileName(PSTR pszFullFileName);
    PSTR FileInPath(PSTR pstrPath);
    VOID ClearResource(VOID);
    BOOL OpenDlg(PSTR pszFileName, INT iType);
    BOOL SaveAsDlg(PSTR pszFileName, INT iType);
    DIALOGPROC GetOpenFileNameHook(HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam);
    BOOL VerifySaveFile(VOID);
    BOOL SaveFile(BOOL fSaveAs);
    BOOL OpenAFile(VOID);
    VOID OpenCmdLineFile(PSTR pstrFileName);
    BOOL MyFileRead(HFILE hf, LPSTR lpBuffer, UINT nBytes, PSTR pszFileName,
        INT iType);
    BOOL MyFileWrite(HFILE hf, LPSTR lpBuffer, UINT nBytes, PSTR pszFileName);

    /*
     * icclip.c
     */

    BOOL CopyImageClip(VOID);
    BOOL PasteImageClip(VOID);
    DIALOGPROC PasteOptionsDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam);
    VOID PickSetRect(INT xLeft, INT yTop, INT xRight, INT yBottom);

    /*
     * image.c
     */

    BOOL ImageNew(PDEVICE pDevice);
    BOOL ImageNewBitmap(INT cx, INT cy, INT nColors);
    BOOL ImageOpen(PIMAGEINFO pImage);
    BOOL ImageOpen2(PIMAGEINFO pImage);
    VOID ImageSave(VOID);

    /*
     * imagedc.c
     */

    BOOL ImageDCCreate(INT iType, INT cx, INT cy, INT nColors);
    VOID ImageDCDelete(VOID);
    VOID ImageDCClear(VOID);
    VOID ImageDCSeparate(HDC hdcImage, INT cx, INT cy, HDC hdcANDMask,
        DWORD rgbScreen);
    VOID ImageDCCombine(HDC hdcImage, INT cx, INT cy, HDC hdcANDMask);
    VOID ImageDCMonoBlt(HDC hdcImage, INT cx, INT cy);

    /*
     * imagedit.c
     */

    WINDOWPROC MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    BOOL ReadWindowPos(PSTR pstrKeyName, PINT px, PINT py, PINT pcx, PINT pcy,
        BOOL *pfMaximized);
    VOID WriteWindowPos(PRECT prc, BOOL fMaximized, PSTR pstrKeyName);

    /*
     * imagedlg.c
     */

    INT DlgBox(INT idDlg, WNDPROC lpfnDlg);
    VOID EnteringDialog(INT idDlg, PINT pidPrevDlg, BOOL fEntering);
    VOID ImageNewDialog(INT iType);
    VOID ImageSelectDialog(VOID);
    DIALOGPROC ResourceTypeDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam);
    DIALOGPROC NewCursorImageDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam);
    DIALOGPROC NewIconImageDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam);
    DIALOGPROC SelectImageDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam);
    DIALOGPROC BitmapSizeDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
        LPARAM lParam);

    /*
     * imaglink.c
     */

    PIMAGEINFO ImageLinkAlloc(PDEVICE pDevice, INT cx, INT cy, INT xHotSpot,
        INT yHotSpot, INT nColors);
    VOID ImageLinkFree(PIMAGEINFO pImageFree);
    VOID ImageLinkFreeList(VOID);
    VOID ImageDelete(VOID);

    /*
     * imagundo.c
     */

    VOID ImageUndo(VOID);
    VOID ImageUpdateUndo(VOID);
    VOID ImageFreeUndo(VOID);

    /*
     * menucmd.c
     */

    VOID InitMenu(HMENU hMenu);
    VOID MenuCmd(INT item);
    DWORD  APIENTRY MsgFilterHookFunc(INT nCode, WPARAM wParam, LPMSG lpMsg);
    VOID ShowHelp(BOOL fMenuHelp);
    DIALOGPROC AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

    /*
     * rwbmp.c
     */

    BOOL LoadBitmapFile(PSTR pszFullFileName);
    BOOL SaveBitmapFile(PSTR pszFullFileName);

    /*
     * rwicocur.c
     */

    BOOL LoadIconCursorFile(PSTR pszFullFileName, BOOL fIcon);
    BOOL IsValidDIB(LPBITMAPINFO pDIB, DWORD cbDIBSize, BOOL fIcoCur);
    BOOL SaveIconCursorFile(PSTR pszFullFileName, INT iType);

    /*
     * rwpal.c
     */

    VOID LoadColorFile(VOID);
    VOID SaveColorFile(VOID);

    /*
     * propbar.c
     */

    DIALOGPROC PropBarDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    VOID PropBarUpdate(VOID);
    VOID PropBarSetImage(PIMAGEINFO pImage);
    VOID PropBarSetPos(INT x, INT y);
    VOID PropBarClearPos(VOID);
    VOID PropBarSetSize(POINT pt1, POINT pt2);
    VOID PropBarClearSize(VOID);
    VOID PropBarSetHotSpot(INT xHotSpot, INT yHotSpot);
    VOID PropBarClearHotSpot(VOID);
    VOID PropBarShowHotSpot(BOOL fShow);

    /*
     * toolbox.c
     */

    VOID ToolboxCreate(VOID);
    VOID ToolboxShow(BOOL fShow);
    VOID ToolboxUpdate(VOID);
    WINDOWPROC ToolboxWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    WINDOWPROC ToolBtnWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    VOID ToolboxSelectTool(INT tool);

    /*
     * util.c
     */

    VOID *MyAlloc(INT cbAlloc);
    VOID *MyRealloc(VOID *npMem, INT cbNewAlloc);
    VOID *MyFree(VOID *npMem);
    INT Message(UINT idMsg, ...);
    VOID CenterWindow(HWND hwnd);
    VOID FitRectToScreen(PRECT prc);
    PSTR ids(UINT idString);
    HBITMAP MyCreateBitmap(HDC hdc, INT cx, INT cy, INT nColors);

    #if defined(DBG) && defined(WIN16)
    VOID DBGStackReport(BOOL fInit);
    #else
    #define DBGStackReport(fInit)
    #endif

    #ifdef DBG
    VOID DBGBltImage(HDC hdc);
    VOID DBGprintf(PSTR fmt, ...);
    #else
    #define DBGBltImage(hdc)
    #define DBGprintf
    #endif

    /*
     * viewwp.c
     */

    VOID ViewCreate(VOID);
    VOID ViewShow(BOOL fShow);
    VOID ViewUpdate(VOID);
    VOID ViewReset(VOID);
    WINDOWPROC ViewWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    VOID ViewSetPixel(INT x, INT y, INT nBrushSize);
    VOID DrawMarginBorder(HWND hwnd, HDC hdc);
    VOID DrawSunkenRect(PRECT prc, HDC hdc);

    /*
     * workwp.c
     */

    WINDOWPROC WorkWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    VOID WorkUpdate(VOID);
    VOID WorkReset(VOID);
    VOID PencilDP(HWND hwnd, UINT msg, POINT ptNew);
    VOID BrushDP(HWND hwnd, UINT msg, POINT ptNew);
    VOID PickDP(HWND hwnd, UINT msg, POINT ptNew);
    VOID LineDP(HWND hwnd, UINT msg, POINT ptNew);
    VOID RectDP(HWND hwnd, UINT msg, POINT ptNew);
    VOID CircleDP(HWND hwnd, UINT msg, POINT ptNew);
    VOID FloodDP(HWND hwnd, UINT msg, POINT ptNew);
    VOID HotSpotDP(HWND hwnd, UINT msg, POINT ptNew);
    VOID NormalizePoints(PPOINT pptStart, PPOINT pptEnd);


IEHELP.H

/****************************************************************************/
/*                                                                          */
/*                 Copyright (C) 1987-1996 Microsoft Corp.                */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: iehelp.h
*
* Contains help context id values for the image editor's help.
*
* History:
*
****************************************************************************/

/*
 * Help for menus. ---------------------------------------------------------
 */

/*
 * File menu.
 */
#define HELPID_FILE_NEW                 100
#define HELPID_FILE_OPEN                101
#define HELPID_FILE_SAVE                102
#define HELPID_FILE_SAVEAS              103
#define HELPID_FILE_LOADCOLORS          104
#define HELPID_FILE_SAVECOLORS          105
#define HELPID_FILE_DEFAULTCOLORS       106
#define HELPID_FILE_EXIT                107

/*
 * Edit menu.
 */
#define HELPID_EDIT_UNDO                200
#define HELPID_EDIT_RESTORE             201
//#define HELPID_EDIT_CUT                 202     // Doesn't need help (not yet implemented).
#define HELPID_EDIT_COPY                203
#define HELPID_EDIT_PASTE               204
#define HELPID_EDIT_CLEAR               205
#define HELPID_EDIT_NEWIMAGE            206
#define HELPID_EDIT_SELECTIMAGE         207
#define HELPID_EDIT_DELETEIMAGE         208

/*
 * Options menu.
 */
#define HELPID_OPTIONS_GRID             300
#define HELPID_OPTIONS_BRUSHSIZE        301
#define HELPID_OPTIONS_BRUSH2           302
#define HELPID_OPTIONS_BRUSH3           303
#define HELPID_OPTIONS_BRUSH4           304
#define HELPID_OPTIONS_BRUSH5           305
#define HELPID_OPTIONS_SHOWCOLOR        306
#define HELPID_OPTIONS_SHOWVIEW         307
#define HELPID_OPTIONS_SHOWTOOLBOX      308

/*
 * Help menu.
 */
#define HELPID_HELP_CONTENTS            400
#define HELPID_HELP_SEARCH              401
#define HELPID_HELP_ABOUT               402

/*
 * Help for dialogs. -------------------------------------------------------
 */

// #define HELPID_ABOUT                    1000    // Help.About cmd.
#define HELPID_BITMAPSIZE               1001    // File.New Bitmap cmd.
#define HELPID_PASTEOPTIONS             1002    // Edit.Paste (diff. size).
#define HELPID_NEWCURSORIMAGE           1003    // Edit.New Image (cursor).
#define HELPID_NEWICONIMAGE             1004    // Edit.New Image (icon).
#define HELPID_SELECTCURSORIMAGE        1005    // Edit.Select Image (cursor).
#define HELPID_SELECTICONIMAGE          1006    // Edit.Select Image (icon).
#define HELPID_RESOURCETYPE             1007    // File.New cmd.

/*
 * Common file open/save dialog help id's.
 */
#define HELPID_COMMONFILEOPEN           1100    // File.Open cmd.
#define HELPID_COMMONFILESAVE           1101    // File.Save As (or Save) cmd.
#define HELPID_COMMONFILEOPENPAL        1102    // File.Load Colors cmd.
#define HELPID_COMMONFILESAVEPAL        1103    // File.Save Colors cmd.
#define HELPID_COMMONFILECHOOSECOLOR    1104    // Edit Color Dialog.

/*
 * Miscellaneous subjects. -------------------------------------------------
 */

#define HELPID_TOOLBOX                  2000    // Toolbox has the focus.
#define HELPID_PROPERTIESBAR            2001    // Properties Bar has focus.
#define HELPID_COLORPALETTE             2002    // Color Palette has focus.
#define HELPID_VIEW                     2003    // View window has focus.


IMAGE.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: image.c
    *
    * Routines for opening and saving images.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"

    #include <stdio.h>
    #include <string.h>

    /************************************************************************
    * ImageNew
    *
    * Creates a new image for the specified device.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL ImageNew(
        PDEVICE pDevice)
    {
        PIMAGEINFO pImage;

        if (!(pImage = ImageLinkAlloc(pDevice, pDevice->cx, pDevice->cy,
                0, 0, pDevice->nColors)))
            return FALSE;

        /*
         * Allocate work space for the new image.
         */
        if (!ImageDCCreate(pDevice->iType, pImage->cx, pImage->cy,
                pImage->nColors)) {
            ImageLinkFree(pImage);
            return FALSE;
        }

        gpImageCur = pImage;
        gnImages++;
        giType = pDevice->iType;

        /*
         * Initialize the pick rectangle to encompass the entire image.
         */
        PickSetRect(0, 0, gcxImage - 1, gcyImage - 1);

        /*
         * Mark the newly created image as dirty to be sure it gets saved.
         */
        fImageDirty = TRUE;

        /*
         * Update the palettes.
         */
        SetColorPalette(gnColors, giType, FALSE);
        PropBarUpdate();
        ToolboxUpdate();
        ViewReset();

        /*
         * Reset the workspace window and then show it.
         */
        WorkReset();
        ShowWindow(ghwndWork, SW_SHOWNORMAL);

        return TRUE;
    }

    /************************************************************************
    * ImageNewBitmap
    *
    * Creates a new bitmap image given a set of characteristics.  After
    * device link is created for those characteristics, ImageNew() is
    * called to do the actual work.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL ImageNewBitmap(
        INT cx,
        INT cy,
        INT nColors)
    {
        PDEVICE pDevice;

        if (!(pDevice = DeviceLinkAlloc(FT_BITMAP, NULL, nColors, cx, cy)))
            return FALSE;

        return ImageNew(pDevice);
    }

    /************************************************************************
    * ImageOpen
    *
    * Determines what has to be done to open the specified image.  If it
    * is not already the current image, it will save the current image
    * then will call ImageOpen2() to open the new one.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL ImageOpen(
        PIMAGEINFO pImage)
    {
        /*
         * New image is already current.  Return success.
         */
        if (pImage == gpImageCur)
            return TRUE;

        /*
         * Is this an image for a known device?
         */
        if (pImage->pDevice) {
            /*
             * Save away the current image.
             */
            ImageSave();

            /*
             * Do the real open of the new image.
             */
            return ImageOpen2(pImage);
        }
        else {
            Message(MSG_CANTEDITIMAGE);
            return FALSE;
        }
    }

    /************************************************************************
    * ImageOpen2
    *
    * Unconditionally opens up the specified image for editing.  This involves
    * parsing the DIB into various globals, putting the bits onto the screen
    * and updating the different palettes appropriately.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL ImageOpen2(
        PIMAGEINFO pImage)
    {
        HCURSOR hcurOld;
        LPBITMAPINFO lpbi;
        INT iBitCount;
        INT cx;
        INT cy;
        INT nColors;
        DWORD cbColorTable;
        DWORD cbBits;
        LPBYTE lpDIBBits;
        HBITMAP hbmMono;
        HBITMAP hbmImage;
        PBITMAPINFO pbi;

        hcurOld = SetCursor(hcurWait);

        lpbi = (LPBITMAPINFO)pImage->DIBPtr;
        iBitCount = lpbi->bmiHeader.biBitCount;

        cx = (INT)lpbi->bmiHeader.biWidth;
        cy = (INT)lpbi->bmiHeader.biHeight;
        if (giType != FT_BITMAP)
            cy /= 2;

        nColors = pImage->nColors;

        /*
         * Allocate work space for the image.
         */
        if (!ImageDCCreate(giType, cx, cy, nColors))
            goto Error1;

        /*
         * Create a temporary bitmap.
         */
        if (!(hbmMono = CreateBitmap(1, 1, 1, 1, NULL))) {
            Message(MSG_OUTOFMEMORY);
            goto Error2;
        }

        cbColorTable = (1 << iBitCount) * sizeof(RGBQUAD);
        lpDIBBits = (LPSTR)lpbi + sizeof(BITMAPINFOHEADER) + cbColorTable;
        cbBits = (((((DWORD)cx * iBitCount) + 31) & 0xffffffe0) >> 3) * cy;

        /*
         * Make a copy of the info header and color table.
         */
        if (!(pbi = (PBITMAPINFO)MyAlloc(
                sizeof(BITMAPINFOHEADER) + (INT)cbColorTable)))
            goto Error3;

        memcpy((LPBYTE)pbi, lpbi, sizeof(BITMAPINFOHEADER) + (INT)cbColorTable);

        /*     * Adjust some fields.  The size field in an icon/cursor dib
         * includes the AND mask bits, which we don't want to include
         * right now.
         */
        pbi->bmiHeader.biHeight = cy;
        pbi->bmiHeader.biSizeImage = cbBits;

        /*
         * Set the bits into the XOR mask.
         */
        hbmImage = SelectObject(ghdcImage, hbmMono);
        SetDIBits(ghdcImage, hbmImage, 0, cy, lpDIBBits, pbi, DIB_RGB_COLORS);
        SelectObject(ghdcImage, hbmImage);

        /*
         * If we are editing an icon or cursor, we need to set the bits
         * for the AND mask also now.
         */
        if (giType != FT_BITMAP) {
            /*
             * Skip past the XOR mask bits to the AND bits that follow.
             */
            lpDIBBits += cbBits;

            cbColorTable = 2 * sizeof(RGBQUAD);

            /*
             * Adjust some fields in the copy of the bitmap info structure,
             * then copy a monochrome color table into it.  Note that we
             * are using the same allocated copy, which is ok because there
             * will always be enough room allocated for the monochrome
             * color table.
             */
            pbi->bmiHeader.biBitCount = 1;
            pbi->bmiHeader.biSizeImage =
                    cy * ((((DWORD)cx + 31) & 0xffffffe0) >> 3);
            pbi->bmiHeader.biClrImportant = 0;
            pbi->bmiHeader.biClrUsed = 0;
            memcpy((PBYTE)pbi->bmiColors, (PBYTE)gargbColorTable2,
                    (INT)cbColorTable);

            /*
             * Set the bits into the AND mask.
             */
            hbmImage = SelectObject(ghdcANDMask, hbmMono);
            SetDIBits(ghdcANDMask, hbmImage, 0, cy, lpDIBBits, pbi,
                    DIB_RGB_COLORS);
            SelectObject(ghdcANDMask, hbmImage);

            /*
             * Combine the XOR and AND masks into a viewable image.
             */
            ImageDCCombine(ghdcImage, gcxImage, gcyImage, ghdcANDMask);
        }

        MyFree(pbi);
        DeleteObject(hbmMono);

        /*
         * Set the current image pointer.
         */
        gpImageCur = pImage;
        fImageDirty = FALSE;
        /*
         * Initialize the pick rectangle to encompass the entire image.
         */
        PickSetRect(0, 0, gcxImage - 1, gcyImage - 1);

        SetColorPalette(gnColors, giType, FALSE);

        /*
         * Update the properties bar info and toolbox.
         */
        PropBarUpdate();
        ToolboxUpdate();

        ViewReset();

        /*
         * Reset the workspace window and then show it.
         */
        WorkReset();
        ShowWindow(ghwndWork, SW_SHOWNORMAL);

        SetCursor(hcurOld);

        return TRUE;

    Error3:
        DeleteObject(hbmMono);

    Error2:
        ImageDCDelete();

    Error1:
        SetCursor(hcurOld);

        return FALSE;
    }

    /************************************************************************
    * ImageSave
    *
    * Saves the state of the current image into the image list (if it
    * is dirty).
    *
    * History:
    *
    ************************************************************************/

    VOID ImageSave(VOID)
    {
        HCURSOR hcurOld;
        INT iBitCount;
        DWORD cbColorTable;
        DWORD cbXORBits;
        DWORD cbANDBits;
        HANDLE hDIB;
        DWORD dwDIBSize;
        LPBITMAPINFOHEADER lpbih;
        LPBYTE lpBits;
        HBITMAP hbmMono;
        HBITMAP hbmImage;

        if (!fImageDirty)
            return;

        hcurOld = SetCursor(hcurWait);

        /*
         * Separate out the XOR and AND masks for ico/cur images.
         */
        if (giType != FT_BITMAP)
            ImageDCSeparate(ghdcImage, gcxImage, gcyImage, ghdcANDMask, grgbScreen);

        /*
         * Create a temporary bitmap.
         */
        if (!(hbmMono = CreateBitmap(1, 1, 1, 1, NULL))) {
            Message(MSG_OUTOFMEMORY);
            goto Error1;
        }

        switch (gpImageCur->nColors) {
            case 2:
                iBitCount = 1;
                break;

            case 16:
                iBitCount = 4;
                break;
        }

        cbColorTable = (DWORD)gpImageCur->nColors * sizeof(RGBQUAD);
        cbXORBits = (((((DWORD)gpImageCur->cx * iBitCount) + 31)
                & 0xffffffe0) >> 3) * gpImageCur->cy;

        switch (giType) {
            case FT_BITMAP:
                cbANDBits = 0;
                break;

            case FT_ICON:
            case FT_CURSOR:
                cbANDBits = (DWORD)gpImageCur->cy *
                        ((((DWORD)gpImageCur->cx + 31) & 0xffffffe0) >> 3);
                break;
        }

        dwDIBSize = sizeof(BITMAPINFOHEADER) + cbColorTable + cbXORBits +
                cbANDBits;

        /*
         * Allocate space for the DIB for this image.
         */
        if (!(hDIB = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwDIBSize))) {
            Message(MSG_OUTOFMEMORY);
            goto Error2;
        }

        lpbih = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

        /*
         * For icons and cursors, we need to get the AND mask bits first.
         */
        if (giType != FT_BITMAP) {
            /*
             * Point to where the AND bits should go.
             */
            lpBits = (LPBYTE)lpbih + sizeof(BITMAPINFOHEADER) +
                    cbColorTable + cbXORBits;

            /*
             * Fill in the bitmap info header for getting the AND bits.
             */
            lpbih->biSize          = sizeof(BITMAPINFOHEADER);
            lpbih->biWidth         = gpImageCur->cx;
            lpbih->biHeight        = gpImageCur->cy;
            lpbih->biPlanes        = 1;
            lpbih->biBitCount      = 1;
            lpbih->biCompression   = BI_RGB;
            lpbih->biSizeImage     = cbANDBits;
            lpbih->biXPelsPerMeter = 0;
            lpbih->biYPelsPerMeter = 0;
            lpbih->biClrImportant  = 0;
            lpbih->biClrUsed       = 0;

            /*
             * Get the bits from the AND mask.
             */
            hbmImage = SelectObject(ghdcANDMask, hbmMono);
            GetDIBits(ghdcANDMask, hbmImage, 0, gpImageCur->cy, lpBits,
                    (LPBITMAPINFO)lpbih, DIB_RGB_COLORS);
            SelectObject(ghdcANDMask, hbmImage);
        }

        /*
         * Fill in the bitmap info header for getting the XOR bits.
         */
        lpbih->biSize          = sizeof(BITMAPINFOHEADER);
        lpbih->biWidth         = gpImageCur->cx;
        lpbih->biHeight        = gpImageCur->cy;
        lpbih->biPlanes        = 1;
        lpbih->biBitCount      = iBitCount;
        lpbih->biCompression   = BI_RGB;
        lpbih->biSizeImage     = cbXORBits;
        lpbih->biXPelsPerMeter = 0;
        lpbih->biYPelsPerMeter = 0;
        lpbih->biClrImportant  = 0;
        lpbih->biClrUsed       = 0;

        /*
         * Point to where the XOR bits should go.
         */
        lpBits = (LPBYTE)lpbih + sizeof(BITMAPINFOHEADER) + cbColorTable;

        /*
         * Get the bits from the XOR mask.
         */
        hbmImage = SelectObject(ghdcImage, hbmMono);
        GetDIBits(ghdcImage, hbmImage, 0, gpImageCur->cy, lpBits,
                (LPBITMAPINFO)lpbih, DIB_RGB_COLORS);
        SelectObject(ghdcImage, hbmImage);

        /*
         * For icons and cursors, we have a few extra steps.
         */
        if (giType != FT_BITMAP) {
            /*
             * Set the fields in the info structure to their final
             * values.  The saved value in the bitmap info header for the
             * height in an icon/cursor DIB is really twice the height of
             * the image, and the size of the image is the size of both
             * the XOR and the AND mask bits.
             */
            lpbih->biHeight *= 2;
            lpbih->biSizeImage = cbXORBits + cbANDBits;

            /*
             * Recombine the XOR and AND masks now that we have their bits.
             */
            ImageDCCombine(ghdcImage, gcxImage, gcyImage, ghdcANDMask);
        }

        /*
         * Free any old DIB.
         */
        if (gpImageCur->DIBhandle) {
            GlobalUnlock(gpImageCur->DIBhandle);
            GlobalFree(gpImageCur->DIBhandle);
        }

        /*
         * Set the image structure to point to the newly created DIB.
         */
        gpImageCur->DIBSize = dwDIBSize;
        gpImageCur->DIBhandle = hDIB;
        gpImageCur->DIBPtr = (LPBYTE)lpbih;

        fFileDirty = TRUE;
        fImageDirty = FALSE;

    Error2:
        DeleteObject(hbmMono);

    Error1:
        SetCursor (hcurOld);
    }


IMAGEDC.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: image.c
    *
    * Routines for manipulating images.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"

    /************************************************************************
    * ImageDCCreate
    *
    * Allocates the image DC's and bitmaps used to hold the current image
    * being edited.  This includes ghdcImage and ghbmImage.  For icons and
    * cursors, also allocates the DC and bitmap that holds the AND mask
    * that contains information about whether any given pixel is screen
    * color, inverted screen color or a true color.  These globals are
    * ghdcANDMask and ghbmANDMask.
    *
    * The image DC will contain the image as it will appear when displayed.
    * For icons and cursors, this means that it is the combined XOR and AND
    * portions of the image.  When the icon/cursor file is saved, the bits
    * will be separated back out, but having them combined in the image
    * makes it fast to display the image while editing.  Because of this,
    * however, care must be taken when changing the screen color because
    * it is not possible to determine from the image DC alone whether a
    * pixel is screen color or simply a true color that happens to be the
    * same as the current screen color.  The AND mask is very important for
    * keeping this straight and must be properly updated when drawing on
    * the image DC with any of the drawing tools!
    *
    * Arguments:
    *   INT iType     - Type of image.
    *   INT cx        - Width of the image.
    *   INT cy        - Height of the image.
    *   INT nColors   - Number of colors.
    *
    * Returns TRUE if successful, or FALSE if an error occurs.
    *
    * History:
    *
    ************************************************************************/

    BOOL ImageDCCreate(
        INT iType,
        INT cx,
        INT cy,
        INT nColors)
    {
        HDC hdcParent;

        /*
         * Delete the old image DC's.
         */
        ImageDCDelete();

        hdcParent = GetDC(ghwndMain);

        if (!(ghdcImage = CreateCompatibleDC(hdcParent)))
            goto Error1;

        if (!(ghbmImage = MyCreateBitmap(hdcParent, cx, cy, 16)))
            goto Error2;

        SelectObject(ghdcImage, ghbmImage);

        /*
         * If image is an icon or cursor, also allocate the AND mask
         * DC and bitmap.  Note that this cannot be a DIB (must use
         * CreateBitmap) or the ImageDCSeparate() code will fail.
         */
        if (iType == FT_ICON || iType == FT_CURSOR) {
            if (!(ghdcANDMask = CreateCompatibleDC(hdcParent)))
                goto Error2;

            if (!(ghbmANDMask = CreateBitmap(cx, cy, (BYTE)1, (BYTE)1, NULL)))
                goto Error3;

            SelectObject(ghdcANDMask, ghbmANDMask);
        }

        ReleaseDC(ghwndMain, hdcParent);

        /*
         * Set some globals.
         */
        gcxImage = cx;
        gcyImage = cy;
        gnColors = nColors;

        /*
         * Initialize the bits.
         */
        ImageDCClear();

        return TRUE;

    Error3:
        DeleteDC(ghdcANDMask);
        ghdcANDMask = NULL;

    Error2:
        DeleteDC(ghdcImage);
        ghdcImage = NULL;

    Error1:
        ReleaseDC(ghwndMain, hdcParent);

        return FALSE;
    }

    /************************************************************************
    * ImageDCDelete
    *
    * Deletes the current image DC.  For icons and cursors, also deletes the
    * mask DC.  Finally, it destroys the undo buffers.
    *
    * History:
    *
    ************************************************************************/

    VOID ImageDCDelete(VOID)
    {
        if (ghdcImage) {
            DeleteDC(ghdcImage);
            ghdcImage = NULL;
            DeleteObject(ghbmImage);
            ghbmImage = NULL;
        }

        if (ghdcANDMask) {
            DeleteDC(ghdcANDMask);
            ghdcANDMask = NULL;
            DeleteObject(ghbmANDMask);
            ghbmANDMask = NULL;
        }

        /*
         * Destroy the undo buffer.
         */
        ImageFreeUndo();
    }

    /************************************************************************
    * ImageDCClear
    *
    * Clears the image DC.  Sets it to completely white.  For icons and
    * cursors, sets the image mask to be fully opaque also.
    *
    * History:
    *
    ************************************************************************/

    VOID ImageDCClear(VOID)
    {
        PatBlt(ghdcImage, 0, 0, gcxImage, gcyImage, WHITENESS);

        /*
         * For icons and cursors, set all the mask bits to zero.
         */
        if (ghdcANDMask)
            PatBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, BLACKNESS);
    }

    /************************************************************************
    * ImageDCSeparate
    *
    * This function separates out the XOR mask in hdcImage so that it does
    * not have any pixels that are screen/inverse color.  This must be done
    * prior to saving the image (or changing the screen color) because
    * normally the hdcImage contains the image as it will be displayed,
    * which is really a combined view of the XOR and AND masks.  The function
    * ImageDCCombine can be used after this function to combine the masks
    * back together.
    *
    * History:
    *
    ************************************************************************/

    VOID ImageDCSeparate(
        HDC hdcImage,
        INT cx,
        INT cy,
        HDC hdcANDMask,
        DWORD rgbScreen)
    {
        HBITMAP hbmTemp;
        HDC hdcTemp;
        HBITMAP hbmOld;

        /*
         * Create a temporary bitmap and DC for the mask bits and
         * select bitmap into the DC.
         */
        hdcTemp = CreateCompatibleDC(hdcImage);
        hbmTemp = CreateCompatibleBitmap(hdcImage, cx, cy);
        hbmOld = SelectObject(hdcTemp, hbmTemp);

        /*
         * Background color of temporary DC is set to the specified
         * screen (transparent) color. The bits from mask DC (mono) are
         * transferred to temp. DC (color). Thus the 1s in the mask (corresp.
         * to "screen" and "inverse" portions) become "screen" colored pixels
         * in temporary DC.
         */
        SetBkColor(hdcTemp, rgbScreen);
        BitBlt(hdcTemp, 0, 0, cx, cy, hdcANDMask, 0, 0, SRCCOPY);

        /*
         * The bits in the temporary DC are XORed against the bits in the image
         * DC to recover the true XOR mask in hdcImage.  The AND mask is already
         * in hdcANDMask.
         */
        BitBlt(hdcImage, 0, 0, cx, cy, hdcTemp, 0, 0, SRCINVERT);

        SelectObject(hdcTemp, hbmOld);
        DeleteDC(hdcTemp);
        DeleteObject(hbmTemp);
    }

    /************************************************************************
    * ImageDCCombine
    *
    * This function takes the raw XOR mask in hdcImage and combines
    * it with the AND mask in hdcANDMask to put into hdcImage the
    * current image as it will be displayed.  This needs to be done after
    * it is separated (just prior to saving the file and also when changing
    * the screen color) and when the image is first opened.
    *
    * The current screen color in ghbrScreen is used when combining the
    * image in this routine.
    *
    * History:
    *
    ************************************************************************/

    VOID ImageDCCombine(
        HDC hdcImage,
        INT cx,
        INT cy,
        HDC hdcANDMask)
    {
        HBITMAP hbmTemp;
        HDC hdcTemp;
        HBITMAP hbmOld;
        HBRUSH hbrOld;

        /*
         * Make a copy of the image DC with the XOR mask in it.
         */
        hdcTemp = CreateCompatibleDC(hdcImage);
        hbmTemp = CreateCompatibleBitmap(hdcImage, cx, cy);
        hbmOld = SelectObject(hdcTemp, hbmTemp);
        BitBlt(hdcTemp, 0, 0, cx, cy, hdcImage, 0, 0, SRCCOPY);

        /*
         * Clear the image DC to the current screen color.
         */
        hbrOld = SelectObject(hdcImage, ghbrScreen);
        PatBlt(hdcImage, 0, 0, cx, cy , PATCOPY);
        SelectObject(hdcImage, hbrOld);

        /*
         * Reconstruct the image by superimposing the XOR and AND masks.
         */
        BitBlt(hdcImage, 0, 0, cx, cy, hdcANDMask, 0, 0, SRCAND);
        BitBlt(hdcImage, 0, 0, cx, cy, hdcTemp, 0, 0, SRCINVERT);

        SelectObject(hdcTemp, hbmOld);
        DeleteDC(hdcTemp);
        DeleteObject(hbmTemp);
    }

    /************************************************************************
    * ImageDCMonoBlt
    *
    * This function blts an image onto a monochrome bitmap, then back
    * again.  This converts it to a monochrome image.
    *
    * History:
    *
    ************************************************************************/

    VOID ImageDCMonoBlt(
        HDC hdcImage,
        INT cx,
        INT cy)
    {
        HBITMAP hbmTemp;
        HDC hdcTemp;
        HBITMAP hbmOld;

        /*
         * Create a temporary monochrome bitmap and dc.
         */
        hdcTemp = CreateCompatibleDC(hdcImage);
        hbmTemp = MyCreateBitmap(hdcImage, cx, cy, 2);
        hbmOld = SelectObject(hdcTemp, hbmTemp);

        /*
         * Blt the image to the mono bitmap, then back again.
         */
        BitBlt(hdcTemp, 0, 0, cx, cy, hdcImage, 0, 0, SRCCOPY);
        BitBlt(hdcImage, 0, 0, cx, cy, hdcTemp, 0, 0, SRCCOPY);

        /*
         * Cleanup.
         */
        SelectObject(hdcTemp, hbmOld);
        DeleteObject(hbmTemp);
        DeleteDC(hdcTemp);
    }


IMAGEDIT.C

/***************************************************************************
     *                                                                         *
     *  MODULE      : imagedit.c                                               *
     *                                                                         *
     *  DESCRIPTION : Contains main entry-level routine for ImagEdit.          *
     *                                                                         *
     *  FUNCTIONS   : WinMain ()        -  Program entry point.                *
     *                                                                         *
     *  HISTORY     : 3/14/89 - LR                                             *
     *                                                                         *
     ***************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"
    #include "ids.h"

    #include <string.h>
    #include <stdlib.h>

 #include <windowsx.h>
    #include <commdlg.h>

    /*
     * External declarations for the Windows variables that contain
     * command line information.
     */
    extern INT __argc;
    extern CHAR **__argv;

    STATICFN BOOL NEAR InitApplication(VOID);
    STATICFN BOOL NEAR InitInstance(LPSTR lpCmdLine, INT cmdShow);
    STATICFN VOID NEAR PenWinRegister(VOID);
    STATICFN VOID NEAR ReadEnv(VOID);
    STATICFN VOID NEAR WriteEnv(VOID);
    STATICFN VOID NEAR SizeRibbons(HWND hwnd);
    STATICFN VOID NEAR CleanUp(VOID);

    static RECT grcAppPos;              // Saves the app's window pos.
    static WORD gmsgHelp;               // Registered help msg from commdlg.dll
    static BOOL fStartAsIcon = FALSE;   // TRUE if app is started minimized.

    /*
     * Contains the address of the Pen Windows callback.
     */
    typedef VOID ( APIENTRY *LPFNPENWIN)(WORD, BOOL);
    static LPFNPENWIN lpfnRegisterPenApp;

    /****************************************************************************
     *                                                                          *
     *  FUNCTION :int PASCAL WinMain(hInstance,hPrevInstance,lpCmdLine,cmdShow) *
     *                                                                          *
     *  PURPOSE  :Serves as program entry point and contains message loop       *
     *                                                                          *
     ****************************************************************************/

    INT WINAPI
    WinMain(
        HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,
        INT nCmdShow)
    {
        MSG msg;

        DBGStackReport(TRUE);

        ghInst = hInstance;

        /* if this is the first instance then call initialization procedure */
        if (!hPrevInstance) {
            if (!InitApplication())
                return FALSE;
        }

        if (!InitInstance(lpCmdLine, nCmdShow))
            return FALSE;

    while (GetMessage(&msg, NULL, 0, 0)) {

                if (!TranslateAccelerator(ghwndMain, haccelTbl, &msg)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
    }

        DBGStackReport(FALSE);

        /*
         * Return the value from PostQuitMessage.
         */
        return msg.wParam;
    }

    /****************************************************************************
     *                                                                          *
     *  FUNCTION   : InitApplication()                                          *
     *                                                                          *
     *  PURPOSE    : To create all ImagEdit's window classes, namely those of   *
     *               the parent window, edit window, mode window and "palette"  *
     *               window.                                                    *
     *                                                                          *
     *  RETURNS    : TRUE if class registration was successful, FALSE otherwise *
     *                                                                          *
     *  SIDE EFFECTS: All class variables affected for all windows.             *
     *                                                                          *
     ****************************************************************************/

    STATICFN BOOL NEAR InitApplication(VOID)
    {
        WNDCLASS wc;

        /* assign values and register the parent window class */
        wc.style = 0;
        wc.lpfnWndProc = MainWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = ghInst;
        wc.hIcon = LoadIcon(ghInst, MAKEINTRESOURCE(IDICON_IMAGEDIT));
        wc.hCursor =  LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
        wc.lpszMenuName = "imagedit";
        wc.lpszClassName = szMainClass;
        if (!RegisterClass(&wc))
            return FALSE;

        wc.style = CS_DBLCLKS;
        wc.lpfnWndProc = ColorBoxWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = ghInst;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
        wc.lpszMenuName = (LPSTR)NULL;
        wc.lpszClassName = szColorBoxClass;
        if (!RegisterClass(&wc))
            return FALSE;

        wc.style = 0;
        wc.lpfnWndProc = ColorLRWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = ghInst;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
        wc.lpszMenuName = (LPSTR)NULL;
        wc.lpszClassName = szColorLRClass;
        if (!RegisterClass(&wc))
            return FALSE;

        wc.style = CS_DBLCLKS;
        wc.lpfnWndProc = WorkWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = ghInst;
        wc.hIcon = NULL;
        wc.hCursor = (HCURSOR)NULL;
        wc.hbrBackground = (HBRUSH)NULL;
        wc.lpszMenuName  = (LPSTR)NULL;
        wc.lpszClassName = szWorkClass;
        if (!RegisterClass(&wc))
            return FALSE;

        wc.style = 0;
        wc.lpfnWndProc = ToolboxWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = ghInst;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
        wc.lpszMenuName = NULL;
        wc.lpszClassName = szToolboxClass;
        if (!RegisterClass(&wc))
            return FALSE;

        wc.style = 0;
        wc.lpfnWndProc = ToolBtnWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = ghInst;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName = NULL;
        wc.lpszClassName = szToolBtnClass;
        if (!RegisterClass(&wc))
            return FALSE;

        wc.style = 0;
        wc.lpfnWndProc = ViewWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = ghInst;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
        wc.lpszMenuName = NULL;
        wc.lpszClassName = szViewClass;
        if (!RegisterClass(&wc))
            return FALSE;

        return TRUE;
    }

    /****************************************************************************
     *                                                                          *
     *  FUNCTION   : InitInstance(lpCmdLine, cmdShow)                           *
     *                                                                          *
     *  PURPOSE    : Load strings from resource file, make procedure instances  *
     *               of all dialog functions, create ImagEdit's windows, color  *
     *               "palettes", tool cursors and do a variety of other initial-*
     *               izations. Also prepare ImagEdit if started up from cmd line*
     *               with an argument. Some of these may be redundant, since a  *
     *               lot of pBrush stuff has been retained here.                *
     *                                                                          *
     *  SIDE EFFECTS: numerous                                                  *
     *                                                                          *
     ****************************************************************************/

    STATICFN BOOL NEAR InitInstance(
        LPSTR lpCmdLine,
        INT cmdShow)
    {
        INT i;
        INT iScrollBarWidth;        /* width of vertical scrollbar */
        INT iScrollBarHeight;       /* height of horizontal scrollbar */
        INT iScreenWid;
        INT iScreenHgt;             /* full screen width and height */
        INT x;
        INT y;
        INT cx;
        INT cy;
        BOOL fMaximized;
        RECT rcColor;
        RECT rcClient;
        RECT rc;
        POINT pt;

        /*
         * Load the "Out of memory." string now, since we may not be able to
         * load it if/when it needs to be displayed.
         */
        ids(IDS_OUTOFMEMORY);

        /*
         * Register for Pen Windows, if it is present.
         */
        PenWinRegister();

        /* register private ImagEdit clipboard format for icons and cursors */
        if (!(ClipboardFormat = RegisterClipboardFormat("ImagEdit")))
            return FALSE;

        if (!(haccelTbl = LoadAccelerators(ghInst, "imagedit")))
            return FALSE;

        hcurWait = LoadCursor(NULL, IDC_WAIT);
        gaTools[TOOL_PENCIL].hcur =
                LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_PENCIL));
        gaTools[TOOL_BRUSH].hcur =
                LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_BRUSH));
        gaTools[TOOL_SELECT].hcur =
        gaTools[TOOL_LINE].hcur =
        gaTools[TOOL_RECT].hcur =
        gaTools[TOOL_SOLIDRECT].hcur =
        gaTools[TOOL_CIRCLE].hcur =
        gaTools[TOOL_SOLIDCIRCLE].hcur =
                LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_CROSS));
        gaTools[TOOL_FLOODFILL].hcur =
                LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_FLOOD));
        gaTools[TOOL_HOTSPOT].hcur =
                LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_HOTSPOT));

        /*
         * Select the default tool.  Since the toolbox is not created yet,
         * this just sets up some globals.
         */
        ToolboxSelectTool(TOOL_FIRST);

        /*
         * Create a dark gray pen for use in borders later.
         */
        if (!(hpenDarkGray = CreatePen(PS_SOLID, 1, RGB_DARKGRAY)))
            return FALSE;

        /*
         * Initialize the two color palettes to the default colors.
         */
        for (i = 0; i < COLORSMAX; i++) {
            gargbColor[i] = gargbDefaultColor[i];
            gargbMono[i] = gargbDefaultMono[i];
        }

        /* get some system parameters */
        iScrollBarWidth  = GetSystemMetrics(SM_CXVSCROLL);
        iScrollBarHeight = GetSystemMetrics(SM_CYHSCROLL);
        iScreenWid       = GetSystemMetrics(SM_CXFULLSCREEN);
        iScreenHgt       = GetSystemMetrics(SM_CYFULLSCREEN);
        gcyBorder = GetSystemMetrics(SM_CYBORDER);

        /*
         * Build the help file name path.  Assume the help file is in the
         * same directory as the executable.
         */
        GetModuleFileName(ghInst, gszHelpFile, CCHMAXPATH);
        *FileInPath(gszHelpFile) = '\0';
        lstrcat(gszHelpFile, ids(IDS_HELPFILE));

        /*
         * Register the message for help from the common dialogs.
         */
        gmsgHelp = RegisterWindowMessage(HELPMSGSTRING);

        /*
         * Hook the message filter stream so that we can detect F1 keystrokes.
         */
        lpfnMsgFilterHookFunc =
                MakeProcInstance((FARPROC)MsgFilterHookFunc, ghInst);
        ghhkMsgFilter =
                SetWindowsHook(WH_MSGFILTER, (HOOKPROC)lpfnMsgFilterHookFunc);

        if (!ReadWindowPos(szAppPos, &x, &y, &cx, &cy, &fMaximized)) {
            x = 2 * iScrollBarWidth;
            y = iScrollBarHeight;
            cx = min(iScreenWid - (4 * iScrollBarWidth), MAXDEFAULTAPPCX);
            cy = min(iScreenHgt - (6 * iScrollBarHeight), MAXDEFAULTAPPCY);
            fMaximized = FALSE;
        }

        /* create parent window */
        ghwndMain = CreateWindow(szMainClass, ids(IDS_PGMTITLE),
                WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                x, y, cx, cy, NULL, NULL, ghInst, NULL);

        if (ghwndMain == NULL) {
            Message(MSG_OUTOFMEMORY);
            return FALSE;
        }

        /*
         * Read the preferences data saved in the ini file.
         */
        ReadEnv();

        /*
         * Create the Toolbox and the View window (invisible).
         */
        ToolboxCreate();
        ViewCreate();

        lpfnColorDlgProc = (WNDPROC)MakeProcInstance(
                (FARPROC)ColorDlgProc, ghInst);
        ghwndColor = CreateDialog(ghInst, MAKEINTRESOURCE(DID_COLOR),
                ghwndMain, lpfnColorDlgProc);

        ghwndWork = CreateWindow(szWorkClass, NULL,
                WS_CHILD | WS_BORDER,
                0, 0, 0, 0,
                ghwndMain,
                NULL, ghInst, NULL);

        /*
         * Build the device table.
         */
        InitDeviceList();

        SetColorPalette(gnColors, giType, TRUE);
        SetScreenColor(grgbScreenDefault);

        if (!ReadWindowPos(szColorPos, &x, &y, &cx, &cy, &fMaximized)) {
            /*
             * The previous position of the Color palette couldn't be found.
             * Position the palette just below the bottom left corner of the
             * client area of the editor, but make sure it is completely
             * visible.
             */
            GetWindowRect(ghwndColor, &rcColor);
            GetClientRect(ghwndMain, &rcClient);
            cx = rcColor.right - rcColor.left;
            cy = rcColor.bottom - rcColor.top;
            pt.x = rcClient.left + (2 * PALETTEMARGIN);
            pt.y = rcClient.bottom + (2 * PALETTEMARGIN);
            ClientToScreen(ghwndMain, &pt);
            SetRect(&rc, pt.x, pt.y, pt.x + cx, pt.y + cy);
            FitRectToScreen(&rc);
            x = rc.left;
            y = rc.top;
        }

        SetWindowPos(ghwndColor, NULL, x, y, 0, 0,
                SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);

        SetFileName(NULL);

        /*
         * If the app was saved when maximized (and they didn't start it up
         * with some kind of an option to have it minimized or in some
         * other funny initial state from the shell), then cause it to
         * be maximized when shown.
         */
        if (fMaximized && (cmdShow == SW_SHOWNORMAL || cmdShow == SW_SHOW))
            cmdShow = SW_SHOWMAXIMIZED;

        ShowWindow(ghwndMain, cmdShow);
        UpdateWindow(ghwndMain);

        /*
         * Did the user start this app minimized from the program manager?
         */
        if (IsIconic(ghwndMain)) {
            /*
             * Set a flag.  The showing of the palettes will be deferred
             * until the app is restored.
             */
            fStartAsIcon = TRUE;
        }
        else {
            /*
             * If they had the Toolbox/Color Palette before, show them now.
             */
            ToolboxShow(gfShowToolbox);
            ColorShow(gfShowColor);
        }

        /*
         * If there was a command line argument specified, try and open
         * it as the initial file.
         */
        if (__argc > 1)
            OpenCmdLineFile(__argv[1]);

        return TRUE;
    }

    /************************************************************************
    * PenWinRegister
    *
    * This function will register for Pen Windows, if it is present.
    *
    ************************************************************************/

    STATICFN VOID NEAR PenWinRegister(VOID)
    {
        HANDLE hmod;

        if (!(hmod = (HANDLE)GetSystemMetrics(SM_PENWINDOWS)))
            return;

        if (lpfnRegisterPenApp =
                (LPFNPENWIN)GetProcAddress(hmod, "RegisterPenApp"))
            (*lpfnRegisterPenApp)(1, TRUE);     // Be Pen-Enhanced!
    }

    /************************************************************************
    * MainWndProc
    *
    * Main window procedure for ImagEdit.
    *
    * History:
    *
    ************************************************************************/

    WINDOWPROC MainWndProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_CREATE:
                {
                    RECT rc;

                    /*
                     * Create the PropBar window.
                     */
                    lpfnPropBarDlgProc = (WNDPROC)MakeProcInstance(
                            (FARPROC)PropBarDlgProc, ghInst);
                    CreateDialog(ghInst, MAKEINTRESOURCE(DID_PROPBAR), hwnd,
                            lpfnPropBarDlgProc);

                    /*
                     * Save away its height for sizing later (like when
                     * the app is minimized then restored).
                     */
                    GetWindowRect(ghwndPropBar, &rc);
                    gcyPropBar = rc.bottom - rc.top;
                }

                break;

            case WM_NCCALCSIZE:
                /*
                 * Save away what is going to be the new window position.
                 */
                if (!IsIconic(hwnd) && !IsZoomed(hwnd))
                    grcAppPos = *((LPRECT)lParam);

                /*
                 * Now let the DefWindowProc calculate the client area normally.
                 */
                goto DoDefault;

            case WM_ACTIVATE:
                /*
                 * If the main window is getting activated, there is no
                 * currently active dialog.
                 */
                if (GET_WM_ACTIVATE_STATE(wParam, lParam))
                    gidCurrentDlg = 0;

                goto DoDefault;

            case WM_INITMENU:
                if (GetMenu(ghwndMain) == (HMENU)wParam)
                    InitMenu((HMENU)wParam);

                break;

            case WM_COMMAND:
                MenuCmd(GET_WM_COMMAND_ID(wParam, lParam));
                break;

            case WM_SIZE:
                SizeRibbons(hwnd);

                if (wParam != SIZEICONIC)
                    WorkReset();

                /*
                 * Did the app start minimized and is it being restored
                 * for the first time?  If so, show the palettes as
                 * the user has requested.
                 */
                if (fStartAsIcon && !IsIconic(hwnd)) {
                    ToolboxShow(gfShowToolbox);
                    ColorShow(gfShowColor);
                    fStartAsIcon = FALSE;
                }

                break;

            case WM_MENUSELECT:
                if (GET_WM_MENUSELECT_FLAGS(wParam, lParam) &
                        (MF_POPUP | MF_SYSMENU))
                    gMenuSelected = 0;
                else
                    gMenuSelected = GET_WM_MENUSELECT_CMD(wParam, lParam);

                break;

            case WM_CLOSE:
                if (VerifySaveFile()) {
                    DestroyWindow(hwnd);
                    CleanUp();
                }

                break;

            case WM_DESTROY:
                /*
                 * Save the position of the app's window.
                 */
                WriteWindowPos(&grcAppPos, IsZoomed(hwnd), szAppPos);

                WinHelp(ghwndMain, gszHelpFile, HELP_QUIT, 0L);

                PostQuitMessage(0);

                break;

            case WM_QUERYENDSESSION:
                return VerifySaveFile();

            default:
                /*
                 * Is this the registered help message from one of the common
                 * dialogs?  If so, show the help for it.
                 *
                 * The check to be sure gmsgHelp is non-zero is just in
                 * case the call to register the help message failed
                 * (it will return zero) and there happens to be a zero
                 * message that gets sent to this window somehow.
                 */
                if (msg == gmsgHelp && gmsgHelp) {
                    ShowHelp(FALSE);
                    return 0;
                }

            DoDefault:
                return DefWindowProc(hwnd, msg, wParam, lParam);
        }

        return 0;
    }

    /************************************************************************
    * ReadWindowPos
    *
    * This function retrieves the saved window position for a window and
    * returns it in the specified variables.  It is used between sessions
    * to restore the application windows to the position they had when
    * the editor was last exited.
    *
    * Returns TRUE if the position could be read, or FALSE otherwise.
    * If FALSE is returned, the values in the specified variables are
    * not valid!  The caller must be able to handle a FALSE return and
    * supply a default position for the window.
    *
    * Arguments:
    *   PSTR pstrKeyName  - KeyName the position was saved under.
    *   PINT px           - Saved x position.
    *   PINT py           - Saved y position.
    *   PINT pcx          - Saved width.
    *   PINT pcy          - Saved height.
    *   BOOL *pfMaximized - Set to TRUE if window was maximized when saved.
    *
    * History:
    *
    ************************************************************************/

    BOOL ReadWindowPos(
        PSTR pstrKeyName,
        PINT px,
        PINT py,
        PINT pcx,
        PINT pcy,
        BOOL *pfMaximized)
    {
        static CHAR szSep[] = " ,";
        CHAR szBuf[CCHTEXTMAX];
        PSTR pstr;

        if (!GetPrivateProfileString(ids(IDS_APPNAME),
                pstrKeyName, "", szBuf, CCHTEXTMAX, ids(IDS_IMAGEDITINI)))
            return FALSE;

        if (!(pstr = strtok(szBuf, szSep)))
            return FALSE;

        *px = atoi(pstr);

        if (!(pstr = strtok(NULL, szSep)))
            return FALSE;

        *py = atoi(pstr);

        if (!(pstr = strtok(NULL, szSep)))
            return FALSE;

        *pcx = atoi(pstr);

        if (!(pstr = strtok(NULL, szSep)))
            return FALSE;

        *pcy = atoi(pstr);

        /*
         * If there is a "1" following the coordinates, the window was
         * maximized when it was saved.
         */
        *pfMaximized = FALSE;
        if ((pstr = strtok(NULL, szSep)) && atoi(pstr) == 1)
            *pfMaximized = TRUE;

        /*
         * Don't allow a zero sized window.
         */
        if (*pcx == 0 || *pcy == 0)
            return FALSE;

        /*
         * Return success.
         */
        return TRUE;
    }

    /************************************************************************
    * WriteWindowPos
    *
    * This function writes the position of a window to the
    * editor's profile file under the specified keyname.
    * The ReadWindowPos function is the counterpart of this
    * function.
    *
    * Arguments:
    *   PRECT prc        - Rectangle for the "restored" window size.
    *   BOOL fMaximized  - TRUE if the window is maximized.
    *   PSTR pstrKeyName - KeyName to save the position under.
    *
    * History:
    *
    ************************************************************************/

    VOID WriteWindowPos(
        PRECT prc,
        BOOL fMaximized,
        PSTR pstrKeyName)
    {
        CHAR szBuf[CCHTEXTMAX];

        wsprintf(szBuf, "%d %d %d %d", prc->left, prc->top,
                prc->right - prc->left, prc->bottom - prc->top);

        if (fMaximized)
            strcat(szBuf, " 1");

        WritePrivateProfileString(ids(IDS_APPNAME),
                pstrKeyName, szBuf, ids(IDS_IMAGEDITINI));
    }

    /*************************************************************************
    * ReadEnv
    *
    * This function initializes variables from their counterparts
    * in the private profile file for ImagEdit.  The application
    * merely needs to construct an array of INIENTRY structures
    * to describe the variables that must be initialized.
    *
    * Note that the original value read from the profile is saved when
    * it is read.  This allows us to optimize what needs to be written
    * out with WriteEnv.
    *
    * History:
    *
    *************************************************************************/

    STATICFN VOID NEAR ReadEnv(VOID)
    {
        register INT i;
        HDC hdc;
        CHAR szBuf[CCHTEXTMAX];
        DWORD rgb;

        for (i = 0; gaie[i].pstrKeyName; i++) {
            *gaie[i].pnVar = gaie[i].nSave =
                    GetPrivateProfileInt(ids(IDS_APPNAME),
                    gaie[i].pstrKeyName, gaie[i].nDefault,
                    ids(IDS_IMAGEDITINI));
        }

        /*
         * Look for the saved screen color.
         */
        if (GetPrivateProfileString(ids(IDS_APPNAME), szrgbScreen, "",
                szBuf, CCHTEXTMAX, ids(IDS_IMAGEDITINI))) {
            rgb = (DWORD)atol(szBuf);
        }
        else {
            /*
             * The last screen color was not found.  The default will be
             * the current system screen background color.
             */
            rgb = GetSysColor(COLOR_BACKGROUND);
        }

        /*
         * Make the ImagEdit default screen color a solid color.
         */
        hdc = GetDC(ghwndMain);
        grgbScreenDefault = GetNearestColor(hdc, rgb);
        ReleaseDC(ghwndMain, hdc);
    }

    /*************************************************************************
    * WriteEnv
    *
    * This function is the counterpart to ReadEnv.  It saves values
    * in the profile file.
    *
    * History:
    *
    *************************************************************************/

    STATICFN VOID NEAR WriteEnv(VOID)
    {
        register INT i;
        CHAR szBuf[CCHTEXTMAX];

        for (i = 0; gaie[i].pstrKeyName; i++) {
            /*
             * Has the user changed the value since it was read?
             */
            if (gaie[i].nSave != *gaie[i].pnVar) {
                /*
                 * If the new value is the same as the default value,
                 * erase the entry from the ini file.  Otherwise,
                 * write the user-specified value out.
                 */
                if (*gaie[i].pnVar == gaie[i].nDefault) {
                    WritePrivateProfileString(ids(IDS_APPNAME),
                            gaie[i].pstrKeyName, NULL, ids(IDS_IMAGEDITINI));
                }
                else {
                    itoa(*gaie[i].pnVar, szBuf, 10);
                    WritePrivateProfileString(ids(IDS_APPNAME),
                            gaie[i].pstrKeyName, szBuf, ids(IDS_IMAGEDITINI));
                }
            }
        }

        /*
         * Save the current screen color.
         */
        ltoa((LONG)grgbScreen, szBuf, 10);
        WritePrivateProfileString(ids(IDS_APPNAME),
                szrgbScreen, szBuf, ids(IDS_IMAGEDITINI));
    }

    /************************************************************************
    * SizeRibbons
    *
    * This function positions and sizes the child ribbons in the editor.
    * It needs to be called any time the size of the main windows changes.
    *
    * Arguments:
    *   HWND hwnd - Parent window handle.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR SizeRibbons(

HWND hwnd)
    {
        RECT rcClient;

        if (ghwndPropBar && !IsIconic(hwnd)) {
            /*
             * Get the client area.
             */
            GetClientRect(hwnd, &rcClient);

            /*
             * Size/move the PropBar window to fit
             * the new client area.
             */
            SetWindowPos(ghwndPropBar, NULL,
                    0, 0,
                    rcClient.right - rcClient.left,
                    min(rcClient.bottom - rcClient.top, gcyPropBar),
                    SWP_NOACTIVATE | SWP_NOZORDER);
        }
    }

    /************************************************************************
    * CleanUp
    *
    * Cleans up all the resources allocated by the editor.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR CleanUp(VOID)
    {
        WriteEnv();

        if (ghdcANDMask) {
            DeleteDC(ghdcANDMask);
            DeleteObject(ghbmANDMask);
        }
        if (ghdcImage) {
            DeleteDC(ghdcImage);
            DeleteObject(ghbmImage);
        }

        ImageFreeUndo();

        if (hpenDarkGray)
            DeleteObject(hpenDarkGray);

        if (ghbrLeft)
            DeleteObject(ghbrLeft);

        if (ghbrLeftSolid)
            DeleteObject(ghbrLeftSolid);

        if (ghbrRight)
            DeleteObject(ghbrRight);

        if (ghbrRightSolid)
            DeleteObject(ghbrRightSolid);

        if (ghbrScreen)
            DeleteObject(ghbrScreen);

        if (ghbrInverse)
            DeleteObject(ghbrInverse);

        if (ghpenLeft)
            DeleteObject(ghpenLeft);

        if (ghpenRight)
            DeleteObject(ghpenRight);

        ImageLinkFreeList();

        if (lpfnMsgFilterHookFunc) {
            UnhookWindowsHook(WH_MSGFILTER, (HOOKPROC)lpfnMsgFilterHookFunc);
            FreeProcInstance(lpfnMsgFilterHookFunc);
        }

        if (lpfnPropBarDlgProc)
            FreeProcInstance((FARPROC)lpfnPropBarDlgProc);

        if (lpfnColorDlgProc)
            FreeProcInstance((FARPROC)lpfnColorDlgProc);
    }

/****************************************************************************
    My_mbschr:  strchr() DBCS version
****************************************************************************/
unsigned char * _CRTAPI1 My_mbschr(
    unsigned char *psz, unsigned short uiSep)
{
    while (*psz != '\0' && *psz != uiSep) {
        psz = CharNext(psz);
    }
    if (*psz == '\0' && uiSep != '\0') {
        return NULL;
    } else {
        return psz;
    }
}
/****************************************************************************
    My_mbstok:  strtok() DBCS version
****************************************************************************/
unsigned char * _CRTAPI1 My_mbstok(
    unsigned char *pszSrc, unsigned char *pszSep)
{
    static char *pszSave = NULL;
    char *pszHead;
    char *psz;

    if (pszSrc == NULL) {
        if (pszSave == NULL) {
            return NULL;
        } else {
            psz = pszSave;
        }
    } else {
        psz = pszSrc;
    }

    /*********************************************/
    /* Skip delimiters to find a head of a token */
    /*********************************************/
    while (*psz) {
        if (IsDBCSLeadByte(*psz)) {
            break;
        } else if (NULL == My_mbschr(pszSep, *psz)) {
            break;
        }
        psz++;
    }
    if (*psz == '\0') {
        //No more token
        return (pszSave = NULL);
    }
    pszHead = psz;

    /******************************/
    /* Search a Tail of the token */
    /******************************/
    while (*psz) {
        if (IsDBCSLeadByte(*psz)) {
            psz += 2;
            continue;
        } else if (NULL != My_mbschr(pszSep, *psz)) {
            break;
        }
        psz++;
    }
    if (*psz == '\0') {
        pszSave = NULL;
    } else {
        //Found next delimiter
        pszSave = psz + 1;
        *psz = '\0';
    }
    return pszHead;
}
/****************************************************************************
    My_mbsncat:
****************************************************************************/
unsigned char * _CRTAPI1 My_mbsncat(
    unsigned char *psz1, const unsigned char *psz2, size_t Length)
{
    int nLen = (int)Length;
    unsigned char *pszSv = psz1;

    while ('\0' != *psz1) {
        psz1++;
    }

    while (0 < nLen) {
        if (*psz2 == '\0') {
            *psz1++ = '\0';
            nLen--;
        } else if (IsDBCSLeadByte(*psz2)) {
            if (nLen == 1) {
                *psz1 = '\0';
            } else {
                *psz1++ = *psz2++;
                *psz1++ = *psz2++;
            }
            nLen -= 2;
        } else {
            *psz1++ = *psz2++;
            nLen--;
        }
    }
    return pszSv;
}


IMAGEDIT.H

/****************************************************************************/
/*                                                                          */
/*                 Copyright (C) 1987-1996 Microsoft Corp.                  */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: imagedit.h
*
* Main header file for the Image Editor.
*
* History:
*
****************************************************************************/

#define WIN31
#define _WINDOWS
#define NOMINMAX
#include <windows.h>

#ifdef DBG
#define STATICFN
#else
#define STATICFN         static
#endif

#define WINDOWPROC      LONG  APIENTRY
#define DIALOGPROC      BOOL  APIENTRY

/*
 * Typedef for a drawing proc (a tool).
 */
typedef VOID (*DRAWPROC)(HWND, UINT, POINT);

#define CCHTEXTMAX      256

/*
 * Maximum size of a file name plus path specification.
 */
#define CCHMAXPATH              260

/*
 * Defines for the different tools.  These are indexes into
 * the gaTools table.
 */
#define TOOL_FIRST                  0
#define TOOL_PENCIL                 0
#define TOOL_BRUSH                  1
#define TOOL_SELECT                 2
#define TOOL_LINE                   3
#define TOOL_RECT                   4
#define TOOL_SOLIDRECT              5
#define TOOL_CIRCLE                 6
#define TOOL_SOLIDCIRCLE            7
#define TOOL_FLOODFILL              8
#define TOOL_HOTSPOT                9

#define CTOOLS                      10      /* Number of tools.             */

/*
 * Macros to simplify working with menus. */
#define MyEnableMenuItem(hMenu, wIDEnableItem, fEnable) \
    EnableMenuItem((hMenu),(wIDEnableItem),(fEnable)?MF_ENABLED:MF_GRAYED)

#define MyCheckMenuItem(hMenu, wIDCheckItem, fCheck) \
    CheckMenuItem((hMenu),(wIDCheckItem),(fCheck)?MF_CHECKED:MF_UNCHECKED)

/*
 * Defines for strings.
 */
#define IDS_NULL                    0
#define IDS_VERSION                 1
#define IDS_VERSIONMINOR            2
#define IDS_OUTOFMEMORY             3
#define IDS_MEMERROR                4
#define IDS_BADBMPFILE              5
#define IDS_BADICOCURFILE           6
#define IDS_BADPALFILE              7
#define IDS_CANTOPEN                8
#define IDS_READERROR               9
#define IDS_WRITEERROR              10
#define IDS_CANTCREATE              11
#define IDS_NOCLIPBOARDFORMAT       12
#define IDS_NOCLIPBOARD             13
#define IDS_CANTEDITIMAGE           14
#define IDS_SAVEFILE                15
#define IDS_ENTERANUMBER            16
#define IDS_BADDEVICESIZE           17
#define IDS_BADDEVICECOLORS         18
#define IDS_NOTSUPPORT              19
#define IDS_NOIMAGES                20
#define IDS_BADBMPSIZE              21

#define IDS_APPNAME                 22
#define IDS_PGMTITLE                23
#define IDS_UNTITLED                24
#define IDS_DOTBMP                  25
#define IDS_HELPFILE                26
#define IDS_IMAGEDITINI             27
#define IDS_ICONIMAGELABEL          28
#define IDS_BITMAPIMAGELABEL        29
#define IDS_CURSORIMAGELABEL        30
#define IDS_ICONDEVNAMEEGAVGA       31
#define IDS_ICONDEVNAMEMONO         32
#define IDS_ICONDEVNAMECGA          33
#define IDS_CURDEVNAMEVGAMONO       34
#define IDS_CURDEVNAMEVGACOLOR      35
#define IDS_ICONINISECTION          36
#define IDS_CURSORINISECTION        37
#define IDS_BMPFILTER               38
#define IDS_BMPFILTEREXT            39
#define IDS_ICOFILTER               40
#define IDS_ICOFILTEREXT            41
#define IDS_CURFILTER               42
#define IDS_CURFILTEREXT            43
#define IDS_PALFILTER               44
#define IDS_PALFILTEREXT            45
#define IDS_ALLFILTER               46
#define IDS_ALLFILTEREXT            47
#define IDS_DEFEXTBMP               48
#define IDS_DEFEXTICO               49
#define IDS_DEFEXTCUR               50
#define IDS_DEFEXTPAL               51
#define IDS_UNKNOWNIMAGEFORMAT      52

/*
 * The total number of strings.  This MUST be updated if strings are
 * added or removed.
 */
#define CSTRINGS                    53

/*
 * Defines for messages.  These are indexes into the gamdMessage table.
 */
#define MSG_OUTOFMEMORY             0
#define MSG_MEMERROR                1
#define MSG_BADBMPFILE              2
#define MSG_BADICOCURFILE           3
#define MSG_BADPALFILE              4
#define MSG_CANTOPEN                5
#define MSG_READERROR               6
#define MSG_WRITEERROR              7
#define MSG_CANTCREATE              8
#define MSG_NOCLIPBOARDFORMAT       9
#define MSG_NOCLIPBOARD             10
#define MSG_CANTEDITIMAGE           11
#define MSG_SAVEFILE                12
#define MSG_ENTERANUMBER            13
#define MSG_BADDEVICESIZE           14
#define MSG_BADDEVICECOLORS         15
#define MSG_NOTSUPPORT              16
#define MSG_NOIMAGES                17
#define MSG_BADBMPSIZE              18

/*
 * Defines for the different file types.
 */
#define FT_BITMAP                   0
#define FT_ICON                     1
#define FT_CURSOR                   2
#define FT_PALETTE                  3

/*
 * Macro to properly cast a FAR pointer to a near pointer.
 * Casts a FAR pointer to void, then to an unsigned long integral
 * value, then truncate it to a short integral value, then cast
 * to a near pointer.
 */
#ifdef WIN32
#define FAR2NEAR(lpstr)     (lpstr)
#else
#define FAR2NEAR(lpstr)     ((PSTR)(WORD)(DWORD)(LPVOID)(lpstr))
#endif

/*
 * RGB color values.
 */
#define RGB_BLACK               RGB(  0,   0,   0)
#define RGB_WHITE               RGB(255, 255, 255)
#define RGB_LIGHTGRAY           RGB(192, 192, 192)
#define RGB_DARKGRAY            RGB(128, 128, 128)

/*
 * Maximum rows and columns in the color box.  This includes room for
 * screen/inverse and a separating blank column.
 */
#define COLORCOLS               16
#define COLORROWS               2

/*
 * Maximum colors in the palette (not including screen/inverse).
 */
#define COLORSMAX               28

/*
 * Defines for the different color modes that the left and right
 * mouse buttons can have.
 */
#define MODE_COLOR              0       // A standard color.
#define MODE_SCREEN             1       // The screen color.
#define MODE_INVERSE            2       // The inverse screen color.

/*
 * Margin (in pixels) within each of the palette and toolbox windows.
 */
#define PALETTEMARGIN           2

/* raster op combination modes */
#define ROP_DSna                0x00220326L
#define ROP_DSPao               0x00EA02E9L

#define DEFAULTBITMAPWIDTH      32      // Default bitmap width.
#define DEFAULTBITMAPHEIGHT     32      // Default bitmap height.
#define DEFAULTBITMAPCOLORS     16      // Default bitmap colors.

#define MAXIMAGEDIM             256     // Max image width/height (dimension).
#define MAXIMAGES               64      // Maximum images in an ico/cur file.

#define CCHDESCRIPTOR        80  /* length of image descriptor string */
#define CCHDEVICENAMEMAX     20  /* maximum length of a device name */

/*
 * Maximum sizes of the app window when running ImagEdit for the first time.
 * This makes it so that the editor does not default to a huge size when
 * run on a super-vga resolution monitor.
 */
#define MAXDEFAULTAPPCX         640
#define MAXDEFAULTAPPCY         480

/*************************************************************************/

/* 3.0 icon/cursor header  */
typedef struct {
    WORD iReserved;            /* always 0 */
    WORD iResourceType;
    WORD iResourceCount;       /* number of resources in file */
} ICOCURSORHDR;

/* 3.0 icon/cursor descriptor  */
typedef struct {
    BYTE iWidth;               /* width of image (icons only ) */
    BYTE iHeight;              /* height of image(icons only) */
    BYTE iColorCount;          /* number of colors in image */
    BYTE iUnused;              /*  */
    WORD iHotspotX;            /* hotspot x coordinate (CURSORS only) */
    WORD iHotspotY;            /* hotspot y coordinate (CURSORS only) */
    DWORD DIBSize;             /* size of DIB for this image */
    DWORD DIBOffset;           /* offset to DIB for this image */
} ICOCURSORDESC, *PICOCURSORDESC;

typedef struct DeviceNode {
    struct DeviceNode *pDeviceNext; // Pointer to next device node.
    INT iType;                      // Type of image (FT_*).
    INT nColors;                    // Number of colors.
    INT cx;                         // Width of image.
    INT cy;                         // Height of image.
    CHAR szName[CCHDEVICENAMEMAX];  // Device name.
    CHAR szDesc[CCHDESCRIPTOR];     // Full description string.
} DEVICE;
typedef DEVICE *PDEVICE;

/*
 * Structure that describes a link in the image list.
 */
typedef struct ImageNode {
    struct ImageNode *pImageNext;   // Pointer to next image.
    PDEVICE pDevice;                // Pointer to device structure.
    INT cx;                         // Image width.
    INT cy;                         // Image height.
    INT iHotspotX;                  // Hotspot x coordinate (cursors only).
    INT iHotspotY;                  // Hotspot y coordinate (cursors only).
    INT nColors;                    // Number of colors.
    DWORD DIBSize;                  // Size of DIB for this image.
    HANDLE DIBhandle;               // Handle to DIB bits.
    LPSTR DIBPtr;                   // Pointer to DIB bits.
} IMAGEINFO, *PIMAGEINFO;

/*
 * Defines an entry in the gamdMessages table of error and warning messages.
 */
typedef struct {
    UINT ids;                   /* String id for the message text.          */
    UINT fMessageBox;           /* Flags for the MessageBox function.       */
} MESSAGEDATA;

/*
 * This structure defines each tool used in the editor.
 */
typedef struct {
    DRAWPROC pfnDrawProc;   /* Drawing procedure for this tool type.        */
    HCURSOR hcur;           /* Handle to the cursor for this tool.          */
    INT idbmToolBtnUp;      /* ID of "up" bmp res. for the Toolbox button.  */
    HBITMAP hbmToolBtnUp;   /* hbm of "up" bitmap for the Toolbox button.   */
    INT idbmToolBtnDown;    /* ID of "down" bmp res. for the Toolbox button.*/
    HBITMAP hbmToolBtnDown; /* hbm of "down" bitmap for the Toolbox button. */
    UINT fDrawOnDown:1;     /* TRUE if tool draws on down click.            */
    UINT fDrawOnUp:1;       /* TRUE if tool draws on up click.              */
} TOOLS;

/*
 * One single entry for an environment setting saved in the
 * profile file.  Used by ReadEnv and WriteEnv.
 */
typedef struct _INIENTRY {
    PSTR pstrKeyName;
    PINT pnVar;
    INT nDefault;
    INT nSave;
} INIENTRY;

/*
 * Structure that maps a subject (like a menu id or a dialog id) with
 * a help context to pass in to WinHelp.
 */
typedef struct {
    INT idSubject;              // Subject, usually a menu or dialog id.
    INT HelpContext;            // The matching help context.
} HELPMAP;
typedef HELPMAP *PHELPMAP;

#include "iefuncs.h"

#include "globals.h"

#ifdef strcmpi
#undef strcmpi
#endif
#define strcmpi     lstrcmpi
#define strtok      My_mbstok
#define strncat     My_mbsncat
unsigned char * _CRTAPI1 My_mbstok(unsigned char *, unsigned char *);
unsigned char * _CRTAPI1 My_mbsncat(
                    unsigned char *, const unsigned char *, size_t);


IMAGEDLG.C

/***************************************************************************
     *                                                                         *
     *  MODULE      : Imagedlg.c                                               *
     *                                                                         *
     *  PURPOSE     : Dialog functions for ImagEdit's dialogs.                 *
     *                                                                         *
     *  HISTORY     : 3/03/89 by LR                                            *
     *                                                                         *
     ***************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"
    #include "iehelp.h"

 #include <windowsx.h>

    STATICFN BOOL NEAR NewImageOK(HWND hwnd, INT iType);
    STATICFN VOID NEAR SelectImageInit(HWND hwnd);
    STATICFN BOOL NEAR SelectImageOK(HWND hwnd);

    /************************************************************************
    * DlgBox
    *
    * This function basically does a DialogBox.
    *
    * Arguments:
    *   INT idDlg       = Ordinal name of the dialog.
    *   WNDPROC lpfnDlg = Dialog procedure to use (this function will
    *                     call Make/FreeProcInstance).
    *
    * Returns:
    *     What DialogBox returned.
    *
    * History:
    *
    ************************************************************************/

    INT DlgBox(
        INT idDlg,
        WNDPROC lpfnDlg)
    {
        WNDPROC lpfnDlgInst;
        INT nResult;
        INT idPrevDlg;

        EnteringDialog(idDlg, &idPrevDlg, TRUE);
        lpfnDlgInst = (WNDPROC)MakeProcInstance((FARPROC)lpfnDlg, ghInst);
        nResult = DialogBox(ghInst, MAKEINTRESOURCE(idDlg),
                ghwndMain, lpfnDlgInst);
        FreeProcInstance(lpfnDlgInst);
        EnteringDialog(idPrevDlg, NULL, FALSE);

        return nResult;
    }

    /************************************************************************
    * EnteringDialog
    *
    * This function enables or disables things based on whether we are
    * going to show one of the editor's dialogs.  It must be called
    * before and after showing a dialog box.
    *
    * Arguments:
    *   INT idDlg       - Ordinal name of the dialog.
    *   PINT pidPrevDlg - Points to where to save the id of the previous
    *                     (current) dialog.  If fEntering is FALSE, this
    *                     is not used and should be NULL.
    *   BOOL fEntering  - TRUE if about ready to show the dialog.  FALSE if
    *                     the dialog was just dismissed.  For the FALSE case,
    *                     the idDlg should be zero, or the id of the previous
    *                     dialog.
    *
    * History:
    *
    ************************************************************************/

    VOID EnteringDialog(
        INT idDlg,
        PINT pidPrevDlg,
        BOOL fEntering)
    {
        /*
         * If we are entering a new dialog, save the previous dialog
         * in the place specified.
         */
        if (fEntering)
            *pidPrevDlg = gidCurrentDlg;

        gidCurrentDlg = idDlg;

        if (ghwndToolbox)
            EnableWindow(ghwndToolbox, !fEntering);

        if (ghwndColor)
            EnableWindow(ghwndColor, !fEntering);

        if (ghwndView)
            EnableWindow(ghwndView, !fEntering);
    }

    /************************************************************************
    * ImageNewDialog
    *
    *
    *
    * History:
    *
    ************************************************************************/

    VOID ImageNewDialog(
        INT iType)
    {
        switch (iType) {
            case FT_ICON:
                DlgBox(DID_NEWICONIMAGE, (WNDPROC)NewIconImageDlgProc);
                break;

            case FT_CURSOR:
                DlgBox(DID_NEWCURSORIMAGE, (WNDPROC)NewCursorImageDlgProc);
                break;
        }
    }

    /************************************************************************
    * ImageSelectDialog
    *
    *
    *
    * History:
    *
    ************************************************************************/

    VOID ImageSelectDialog(VOID)
    {
        switch (giType) {
            case FT_ICON:
                DlgBox(DID_SELECTICONIMAGE, (WNDPROC)SelectImageDlgProc);
                break;

            case FT_CURSOR:
                DlgBox(DID_SELECTCURSORIMAGE, (WNDPROC)SelectImageDlgProc);
                break;
        }
    }

    /************************************************************************
    * ResourceTypeDlgProc
    *
    * Gets from the user the type of resource image that they want
    * to edit (bitmap, icon or cursor).
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC ResourceTypeDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        INT idChecked;

        switch (msg) {
            case WM_INITDIALOG:
                switch (giType) {
                    case FT_BITMAP:
                        idChecked = DID_RESOURCETYPEBITMAP;
                        break;

                    case FT_ICON:
                        idChecked = DID_RESOURCETYPEICON;
                        break;

                    case FT_CURSOR:
                        idChecked = DID_RESOURCETYPECURSOR;
                        break;
                }

                CheckDlgButton(hwnd, idChecked, 1);

                CenterWindow(hwnd);

                break;

            case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                    case IDOK:
                        if (IsDlgButtonChecked(hwnd, DID_RESOURCETYPEBITMAP))
                            iNewFileType = FT_BITMAP;
                        else if (IsDlgButtonChecked(hwnd, DID_RESOURCETYPEICON))
                            iNewFileType = FT_ICON;
                        else
                            iNewFileType = FT_CURSOR;

                        EndDialog(hwnd, IDOK);
                        break;

                    case IDCANCEL:
                        EndDialog(hwnd, IDCANCEL);
                        break;

                    case IDHELP:
                        WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                                HELPID_RESOURCETYPE);
                        break;
                }

                break;

            default:
                return FALSE;
        }

        return TRUE;
    }

    /************************************************************************
    * NewIconImageDlgProc
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC NewIconImageDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        HWND hwndList;
        INT iSel;
        PDEVICE pDevice;

        switch (msg) {
            case WM_INITDIALOG:
                hwndList = GetDlgItem(hwnd, DID_NEWIMAGELIST);
                SendMessage(hwndList, LB_RESETCONTENT, 0, 0);

                for (pDevice = gpIconDeviceHead; pDevice;
                        pDevice = pDevice->pDeviceNext) {
                    if (!DeviceLinkUsed(pDevice)) {
                        iSel = (INT)SendMessage(hwndList, LB_INSERTSTRING, (WPARAM)-1,
                                (LONG)(LPSTR)pDevice->szDesc);
                        SendMessage(hwndList, LB_SETITEMDATA, iSel,
                                (DWORD)(LPSTR)pDevice);
                    }
                }

                /*
                 * Select the first item.
                 */
                SendMessage(hwndList, LB_SETCURSEL, 0, 0L);

                CenterWindow(hwnd);

                break;

            case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                    case DID_NEWIMAGELIST:
                        if (GET_WM_COMMAND_CMD(wParam, lParam) == LBN_DBLCLK) {
                            if (NewImageOK(hwnd, FT_ICON))
                                EndDialog(hwnd, IDOK);
                        }

                        break;

                    case IDOK:
                        if (NewImageOK(hwnd, FT_ICON))
                            EndDialog(hwnd, IDOK);

                        break;

                    case IDCANCEL:
                        EndDialog(hwnd, IDCANCEL);
                        break;

                    case IDHELP:
                        WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                                HELPID_NEWICONIMAGE);
                        break;
                }

                break;

            default:
                return FALSE;
        }

        return TRUE;
    }

    /************************************************************************
    * NewImageOK
    *
    * Processes the selection of a new image from the New Icon (Cursor)
    * Image dialog procedures.
    *
    * History:
    *
    ************************************************************************/

    STATICFN BOOL NEAR NewImageOK(
        HWND hwnd,
        INT iType)
    {
        HWND hwndLB;
        INT iSelect;
        PDEVICE pDevice;

        hwndLB = GetDlgItem(hwnd, DID_NEWIMAGELIST);

        if ((iSelect = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0)) != LB_ERR) {
            /*
             * Save away the current image.
             */
            ImageSave();

            pDevice = (PDEVICE)SendMessage(hwndLB, LB_GETITEMDATA, iSelect, 0);

            if (ImageNew(pDevice))
                return TRUE;
        }

        return FALSE;
    }

    /************************************************************************
    * NewCursorImageDlgProc
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC NewCursorImageDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        HWND hwndList;
        INT iSel;
        PDEVICE pDevice;

        switch (msg) {
            case WM_INITDIALOG:
                hwndList = GetDlgItem(hwnd, DID_NEWIMAGELIST);
                SendMessage(hwndList, LB_RESETCONTENT, 0, 0);

                for (pDevice = gpCursorDeviceHead; pDevice;
                        pDevice = pDevice->pDeviceNext) {
                    if (!DeviceLinkUsed(pDevice)) {
                        iSel = (INT)SendMessage(hwndList, LB_INSERTSTRING, (WPARAM)-1,
                                (LONG)(LPSTR)pDevice->szDesc);
                        SendMessage(hwndList, LB_SETITEMDATA, iSel,
                                (DWORD)(LPSTR)pDevice);
                    }
                }

                /*
                 * Select the first item.
                 */
                SendMessage(hwndList, LB_SETCURSEL, 0, 0L);

                CenterWindow(hwnd);

                break;

            case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                    case DID_NEWIMAGELIST:
                        if (GET_WM_COMMAND_CMD(wParam, lParam) == LBN_DBLCLK) {
                            if (NewImageOK(hwnd, FT_CURSOR))
                                EndDialog(hwnd, IDOK);
                        }

                        break;

                    case IDOK:
                        if (NewImageOK(hwnd, FT_CURSOR))
                            EndDialog(hwnd, IDOK);

                        break;

                    case IDCANCEL:
                        EndDialog(hwnd, IDCANCEL);
                        break;

                    case IDHELP:
                        WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                                HELPID_NEWCURSORIMAGE);
                        break;
                }

                break;

            default:
                return FALSE;
        }

        return TRUE;
    }

    /************************************************************************
    * SelectImageDlgProc
    *
    * This is the Select Image dialog procedure.  This proc is used for both
    * icon and cursor images.
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC SelectImageDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_INITDIALOG:
                SelectImageInit(hwnd);
                return TRUE;

            case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                    case DID_SELECTIMAGELIST:
                        if (GET_WM_COMMAND_CMD(wParam, lParam) == LBN_DBLCLK) {
                            if (SelectImageOK(hwnd))
                                EndDialog(hwnd, IDOK);
                        }

                        break;

                    case IDOK:
                        if (SelectImageOK(hwnd))
                            EndDialog(hwnd, IDOK);

                        break;

                    case IDCANCEL:
                        EndDialog(hwnd, IDCANCEL);
                        break;

                    case IDHELP:
                        WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                                (giType == FT_ICON) ?
                                HELPID_SELECTICONIMAGE : HELPID_SELECTCURSORIMAGE);
                        break;
                }

                return TRUE;

            default:
                return FALSE;
        }
    }

    /************************************************************************
    * SelectImageInit
    *
    * Processes the WM_INITDIALOG message for the Open Image dialog
    * procedure.
    *
    * This function fills the listbox with the names of all the current
    * images for the current icon/cursor file.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR SelectImageInit(
        HWND hwnd)
    {
        HWND hwndLB;
        INT i;
        PIMAGEINFO pImage;

        hwndLB = GetDlgItem(hwnd, DID_SELECTIMAGELIST);

        for (pImage = gpImageHead; pImage; pImage = pImage->pImageNext) {
            i = (INT)SendMessage(hwndLB, LB_INSERTSTRING, (WPARAM)-1,
                    pImage->pDevice ?
                    (DWORD)(LPSTR)pImage->pDevice->szDesc :
                    (DWORD)(LPSTR)ids(IDS_UNKNOWNIMAGEFORMAT));

            SendMessage(hwndLB, LB_SETITEMDATA, i, (DWORD)(LPSTR)pImage);
        }

        /*
         * Select the first item.
         */
        SendMessage(hwndLB, LB_SETCURSEL, 0, 0L);

        CenterWindow(hwnd);
    }

    /************************************************************************
    * SelectImageOK
    *
    * Processes the selection of a new image from the Open Image
    * dialog procedure.
    *
    * History:
    *
    ************************************************************************/

    STATICFN BOOL NEAR SelectImageOK(
        HWND hwnd)
    {
        HWND hwndLB;
        INT iSelect;
        PIMAGEINFO pImage;

        hwndLB = GetDlgItem(hwnd, DID_SELECTIMAGELIST);

        if ((iSelect = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0)) == LB_ERR)
            return FALSE;

        /*
         * Get a pointer to the selected image (stored in the listbox
         * items data field).
         */
        pImage = (PIMAGEINFO)SendMessage(hwndLB, LB_GETITEMDATA, iSelect, 0L);

        return ImageOpen(pImage);
    }

    /************************************************************************
    * BitmapSizeDlgProc
    *
    * Dialog that asks for the width, height and number of colors for
    * a new bitmap file.
    *
    * The last values that the user successfully entered are remembered,
    * and these values will be the defaults the next time that the
    * dialog is invoked.
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC BitmapSizeDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        static INT cxLast = DEFAULTBITMAPWIDTH;
        static INT cyLast = DEFAULTBITMAPHEIGHT;
        static INT nColorsLast = DEFAULTBITMAPCOLORS;
        INT cx;
        INT cy;
        BOOL fTranslated;
        INT nColors;

        switch (msg) {
            case WM_INITDIALOG:
                SetDlgItemInt(hwnd, DID_BITMAPSIZEWIDTH, cxLast, TRUE);
                SetDlgItemInt(hwnd, DID_BITMAPSIZEHEIGHT, cyLast, TRUE);

                if (nColorsLast == 16)
                    CheckRadioButton(hwnd, DID_BITMAPSIZE2, DID_BITMAPSIZE16,
                            DID_BITMAPSIZE16);
                else
                    CheckRadioButton(hwnd, DID_BITMAPSIZE2, DID_BITMAPSIZE16,
                            DID_BITMAPSIZE2);

                CenterWindow(hwnd);

                break;

            case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                    case IDOK:
                        cx = GetDlgItemInt(hwnd, DID_BITMAPSIZEWIDTH, &fTranslated, FALSE);
                        if (!fTranslated) {
                            SetFocus(GetDlgItem(hwnd, DID_BITMAPSIZEWIDTH));
                            Message(MSG_ENTERANUMBER);
                            break;
                        }

                        cy = GetDlgItemInt(hwnd, DID_BITMAPSIZEHEIGHT, &fTranslated, FALSE);
                        if (!fTranslated) {
                            SetFocus(GetDlgItem(hwnd, DID_BITMAPSIZEHEIGHT));
                            Message(MSG_ENTERANUMBER);
                            break;
                        }

                        if (IsDlgButtonChecked(hwnd, DID_BITMAPSIZE2))
                            nColors = 2;
                        else
                            nColors = 16;

                        if (ImageNewBitmap(cx, cy, nColors)) {
                            cxLast = cx;
                            cyLast = cy;
                            nColorsLast = nColors;

                            EndDialog(hwnd, IDOK);
                        }

                        break;

                    case IDCANCEL:
                        EndDialog(hwnd, IDCANCEL);
                        break;

                    case IDHELP:
                        WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                                HELPID_BITMAPSIZE);
                        break;
                }

                break;

            default:
                return FALSE;
        }

        return TRUE;
    }


IMAGLINK.C

/****************************************************************************/
/*                                                                          */
/*                 Copyright (C) 1987-1996 Microsoft Corp.                */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: imaglink.c
*
* Contains routines for managing the linked list of images.
*
* History:
*
****************************************************************************/

#include "imagedit.h"

/************************************************************************
* ImageLinkAlloc
*
* Allocates an image link.  This is a node in the linked list of
* images for the current file.  This link will be added to the
* current linked list of images.
*
* Arguments:
*   PDEVICE pDevice  - Pointer to the device structure.
*   INT cx           - Width of the image.
*   INT cy           - Height of the image.
*   INT xHotSpot     - X location of the hotspot (cursors only).
*   INT yHotSpot     - Y location of the hotspot (cursors only).
*   INT nColors      - Number of colors (2 or 16).
*
* History:
*
************************************************************************/

PIMAGEINFO ImageLinkAlloc(
    PDEVICE pDevice,
    INT cx,
    INT cy,
    INT xHotSpot,
    INT yHotSpot,
    INT nColors)
{
    PIMAGEINFO pImage;
    PIMAGEINFO pImageT;

    if (!(pImage = (PIMAGEINFO)MyAlloc(sizeof(IMAGEINFO))))
        return NULL;

    pImage->pDevice = pDevice;
    pImage->cx = cx;
    pImage->cy = cy;
    pImage->iHotspotX = xHotSpot;
    pImage->iHotspotY = yHotSpot;
    pImage->nColors = nColors;
    pImage->DIBSize = 0;
    pImage->DIBhandle = NULL;
    pImage->DIBPtr = NULL;
    pImage->pImageNext = NULL;

    /*
     * Insert the link in the list.
     */
    if (!gpImageHead) {
        /*
         * This is the first one.  Start the list.
         */
        gpImageHead = pImage;
    }
    else {
        /*
         * Find the end of the list and tack on the new link.
         */
        for (pImageT = gpImageHead; pImageT->pImageNext;
                pImageT = pImageT->pImageNext)
            ;

        pImageT->pImageNext = pImage;
    }

    return pImage;
}

/************************************************************************
* ImageLinkFree
*
* Free's the specified image link and closes up the hole
* in the linked list.
*
* Arguments:
*   PIMAGEINFO pImageFree - The image link to free.
*
* History:
*
************************************************************************/

VOID ImageLinkFree(
    PIMAGEINFO pImageFree)
{
    PIMAGEINFO pImage;
    PIMAGEINFO pImagePrev;

    /*
     * Find the existing link and get it's previous link.
     */
    for (pImage = gpImageHead, pImagePrev = NULL;
            pImage && pImage != pImageFree;
            pImagePrev = pImage, pImage = pImage->pImageNext)
        ;

    /*
     * Was the image link found?
     */
    if (pImage) {
        /*
         * Close up the linked list.
         */
        if (pImagePrev)
            pImagePrev->pImageNext = pImageFree->pImageNext;
        else
            gpImageHead = pImageFree->pImageNext;

        /*
         * Unlock and free the allocated DIB image.
         */
        if (pImageFree->DIBhandle) {
            GlobalUnlock(pImageFree->DIBhandle);
            GlobalFree(pImageFree->DIBhandle);
        }

        /*
         * Free the link itself.
         */
        MyFree(pImageFree);
    }
}

/************************************************************************
* ImageLinkFreeList
*
* Free's all the nodes in the current image list (if there is one).
*
* Arguments:
*
* History:
*
************************************************************************/

VOID ImageLinkFreeList(VOID)
{
    while (gpImageHead)
        ImageLinkFree(gpImageHead);
}

/************************************************************************
* ImageDelete
*
* Deletes the current image (for icons/cursors).  If there are other images,
* the first one is opened.  If the last image was just deleted, the
* entire editor will be reset (if the file was untitled) or if there
* is currently a file opened, some other things will happen, such as
* hiding the workspace and view windows, etc.
*
* History:
*
************************************************************************/

VOID ImageDelete(VOID)
{
    PIMAGEINFO pImage;

    ImageDCDelete();
    ImageLinkFree(gpImageCur);
    gpImageCur = NULL;
    fFileDirty = TRUE;
    fImageDirty = FALSE;
    gnImages--;

    /*
     * Look for the first remaining image that has a valid device
     * (which means it is editable).
     */
    for (pImage = gpImageHead; pImage && !pImage->pDevice;
            pImage = pImage->pImageNext)
        ;

    /*
     * If there are other editable images, open the first one.
     * Otherwise, clear out some more things.
     */
    if (pImage) {
        ImageOpen2(pImage);
    }
    else {
        /*
         * Is this a file off of disk?
         */
        if (gpszFileName) {
            /*
             * Hide the Workspace and View windows.
             */
            ShowWindow(ghwndWork, SW_HIDE);
            ViewShow(FALSE);

            /*
             * Update the properties bar (refill the images combo).
             */
            PropBarUpdate();
        }
        else {
            /*
             * Since this file was opened new and they deleted the
             * last image present in it, just reset everything.
             */
            ClearResource();
        }
    }
}


IMAGUNDO.C

/****************************************************************************/
/*                                                                          */
/*                 Copyright (C) 1987-1996 Microsoft Corp.                */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: imagundo.c
*
* Contains routines for handling the Undo buffers.
*
* History:
*
****************************************************************************/

#include "imagedit.h"

STATICFN VOID NEAR ImageAllocUndo(VOID);

/************************************************************************
* ImageUndo
*
* Undoes the last editing operation by restoring the image to the
* saved undo buffer.
*
* History:
*
************************************************************************/

VOID ImageUndo(VOID)
{
    HDC hdcTemp;
    HBITMAP hbmOld;

    /*
     * Is there anything to undo?
     */
    if (!ghbmUndo)
        return;

    hdcTemp = CreateCompatibleDC(ghdcImage);
    hbmOld = SelectObject(hdcTemp, ghbmUndo);
    BitBlt(ghdcImage, 0, 0, gcxImage, gcyImage, hdcTemp, 0, 0, SRCCOPY);

    /*
     * For icons and cursors, restore the AND mask also.
     */
    if (giType != FT_BITMAP) {
        SelectObject(hdcTemp, ghbmUndoMask);
        BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, hdcTemp, 0, 0, SRCCOPY);
    }

    SelectObject(hdcTemp, hbmOld);
    DeleteDC(hdcTemp);

    fImageDirty = TRUE;

    /*
     * Delete the undo buffer, now that it has been used.
     */
    ImageFreeUndo();

    ViewUpdate();
}

/************************************************************************
* ImageUpdateUndo
*
* Makes a snapshot of the current image and places it in the undo
* buffer.
*
* Arguments:
*
* History:
*
************************************************************************/

VOID ImageUpdateUndo(VOID)
{
    HDC hdcTemp;
    HBITMAP hbmOld;

    /*
     * If there are currently no undo buffers, allocate them now.
     */
    if (!ghbmUndo)
        ImageAllocUndo();

    hdcTemp = CreateCompatibleDC(ghdcImage);
    hbmOld = SelectObject(hdcTemp, ghbmUndo);
    BitBlt(hdcTemp, 0, 0, gcxImage, gcyImage, ghdcImage, 0, 0, SRCCOPY);

    /*
     * For icons and cursors, update the undo AND mask also.
     */
    if (giType != FT_BITMAP) {
        SelectObject(hdcTemp, ghbmUndoMask);
        BitBlt(hdcTemp, 0, 0, gcxImage, gcyImage, ghdcANDMask, 0, 0, SRCCOPY);
    }

    SelectObject(hdcTemp, hbmOld);
    DeleteDC(hdcTemp);
}

/************************************************************************
* ImageAllocUndo
*
* Allocates buffers for an undo operation.  For icons and cursors,
* this includes an AND mask undo buffer.  This function does not
* initialize the bits.  The function ImageFreeUndo frees the buffers
* allocated by this function.
*
* History:
*
************************************************************************/

STATICFN VOID NEAR ImageAllocUndo(VOID)
{
    ImageFreeUndo();

    /*
     * Allocate an undo bitmap of the specified size.
     */
    if (!(ghbmUndo = MyCreateBitmap(ghdcImage, gcxImage, gcyImage, 16))) {
        Message(MSG_OUTOFMEMORY);
        return;
    }

    /*
     * For icons and cursors, allocate an undo AND mask also.
     */
    if (giType != FT_BITMAP) {
        if (!(ghbmUndoMask = CreateBitmap(gcxImage, gcyImage,
                (BYTE)1, (BYTE)1, NULL))) {
            ImageFreeUndo();
            Message(MSG_OUTOFMEMORY);
            return;
        }
    }
}

/************************************************************************
* ImageFreeUndo
*
* Free's the undo buffers.
*
* History:
*
************************************************************************/

VOID ImageFreeUndo(VOID)
{
    if (ghbmUndo) {
        DeleteObject(ghbmUndo);
        ghbmUndo = NULL;
    }

    if (ghbmUndoMask) {
        DeleteObject(ghbmUndoMask);
        ghbmUndoMask = NULL;
    }
}


MENUCMD.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: menucmd.c
    *
    * Contains routines to dispatch the menu commands.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"
    #include "ids.h"

    #include <direct.h>
    #include <string.h>

    STATICFN INT GetHelpContext(INT idSubject, PHELPMAP phmap);

    /************************************************************************
    * InitMenu
    *
    * This function grays/enables and checks/unchecks the menu items
    * appropriately for the given state.
    *
    * Arguments:
    *   HMENU hMenu - The menu handle.
    *
    * History:
    *
    ************************************************************************/

    VOID InitMenu(
        HMENU hMenu)
    {
        BOOL fEnable;
        INT i;

        MyEnableMenuItem(hMenu, MENU_FILE_SAVE, fImageDirty || fFileDirty);
        MyEnableMenuItem(hMenu, MENU_FILE_SAVEAS, gpImageHead);
        MyEnableMenuItem(hMenu, MENU_FILE_LOADCOLORS, gnColors != 2);
        MyEnableMenuItem(hMenu, MENU_FILE_SAVECOLORS, gnColors != 2);

        /*
         * Only enable the option to restore the default colors if this
         * is not a monochrome palette and at least one of the colors
         * has been changed.
         */
        fEnable = FALSE;
        if (gnColors != 2) {
            for (i = 0; i < COLORSMAX; i++) {
                if (gargbColor[i] != gargbDefaultColor[i]) {
                    fEnable = TRUE;
                    break;
                }
            }
        }

        MyEnableMenuItem(hMenu, MENU_FILE_DEFAULTCOLORS, fEnable);

        MyEnableMenuItem(hMenu, MENU_EDIT_UNDO, ghbmUndo);
        MyEnableMenuItem(hMenu, MENU_EDIT_RESTORE,
                gpImageCur && gpImageCur->DIBPtr && fImageDirty);
        MyEnableMenuItem(hMenu, MENU_EDIT_COPY, gpImageCur);
        MyEnableMenuItem(hMenu, MENU_EDIT_PASTE,
                gpImageCur && IsClipboardFormatAvailable(CF_BITMAP));
        MyEnableMenuItem(hMenu, MENU_EDIT_CLEAR, gpImageCur);

        /*
         * We can add new images if the current image is not a bitmap,
         * and we have possible new images to add, and there is a current
         * file being edited.  This last case is checked by looking to
         * see that there is either a current file name, or there is a
         * current image (the case for new files).
         */
        MyEnableMenuItem(hMenu, MENU_EDIT_NEWIMAGE,
                giType != FT_BITMAP &&
                ((giType == FT_ICON) ?
                (gnImages < gnIconDevices) : (gnImages < gnCursorDevices)) &&
                (gpImageCur || gpszFileName));

        MyEnableMenuItem(hMenu, MENU_EDIT_SELECTIMAGE,
                giType != FT_BITMAP && gnImages > 0);
        MyEnableMenuItem(hMenu, MENU_EDIT_DELETEIMAGE,
                giType != FT_BITMAP && gnImages > 0);

        MyCheckMenuItem(hMenu, MENU_OPTIONS_GRID, gfGrid);
        MyCheckMenuItem(hMenu, MENU_OPTIONS_BRUSH2, gnBrushSize == 2);
        MyCheckMenuItem(hMenu, MENU_OPTIONS_BRUSH3, gnBrushSize == 3);
        MyCheckMenuItem(hMenu, MENU_OPTIONS_BRUSH4, gnBrushSize == 4);
        MyCheckMenuItem(hMenu, MENU_OPTIONS_BRUSH5, gnBrushSize == 5);
        MyCheckMenuItem(hMenu, MENU_OPTIONS_SHOWCOLOR, gfShowColor);
        MyCheckMenuItem(hMenu, MENU_OPTIONS_SHOWVIEW, gfShowView);
        MyCheckMenuItem(hMenu, MENU_OPTIONS_SHOWTOOLBOX, gfShowToolbox);
    }

    /************************************************************************
    * MenuCmd
    *
    * Dispatches all the menu commands.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID MenuCmd(
        INT item)
    {
        switch (item) {

            /*
             * File menu ----------------------------------------------------
             */

            case MENU_FILE_OPEN:
                if (VerifySaveFile())
                    OpenAFile();

                break;

            case MENU_FILE_NEW:
                if (VerifySaveFile()) {
                    if (DlgBox(DID_RESOURCETYPE,
                            (WNDPROC)ResourceTypeDlgProc) == IDOK) {
                        /*
                         * Clear out the current resource.
                         */
                        ClearResource();

                        if (iNewFileType == FT_BITMAP)
                            DlgBox(DID_BITMAPSIZE, (WNDPROC)BitmapSizeDlgProc);
                        else
                            ImageNewDialog(iNewFileType);
                    }
                }

                break;

            case MENU_FILE_SAVE:
                SaveFile(FALSE);
                break;

            case MENU_FILE_SAVEAS:
                SaveFile(TRUE);
                break;

            case MENU_FILE_LOADCOLORS:
                LoadColorFile();
                break;

            case MENU_FILE_SAVECOLORS:
                SaveColorFile();
                break;

            case MENU_FILE_DEFAULTCOLORS:
                RestoreDefaultColors();
                break;

            case MENU_FILE_EXIT:
                SendMessage(ghwndMain, WM_SYSCOMMAND, SC_CLOSE, 0L);
                break;

            /*
             * Edit menu ----------------------------------------------------
             */

            case MENU_EDIT_UNDO:
                ImageUndo();
                break;

            case MENU_EDIT_RESTORE:
                /*
                 * Reopen the most recently retained image (without
                 * prompting for a save).
                 */
                ImageOpen2(gpImageCur);
                break;

            case MENU_EDIT_COPY:
                CopyImageClip();
                break;

            case MENU_EDIT_PASTE:
                PasteImageClip();
                break;

            case MENU_EDIT_CLEAR:
                ImageUpdateUndo();
                ImageDCClear();
                ViewUpdate();
                break;

            case MENU_EDIT_NEWIMAGE:
                ImageNewDialog(giType);
                break;

            case MENU_EDIT_SELECTIMAGE:
                ImageSelectDialog();
                break;

            case MENU_EDIT_DELETEIMAGE:
                ImageDelete();
                break;

            /*
             * Options menu -------------------------------------------------
             */

            case MENU_OPTIONS_GRID:
                /*
                 * Toggle the grid state.
                 */
                gfGrid ^= TRUE;

                /*
                 * Repaint the workspace window to show/remove the grid.
                 */
                WorkUpdate();

                break;

            case MENU_OPTIONS_BRUSH2:
            case MENU_OPTIONS_BRUSH3:
            case MENU_OPTIONS_BRUSH4:
            case MENU_OPTIONS_BRUSH5:
                switch (item) {
                    case MENU_OPTIONS_BRUSH2:
                        gnBrushSize = 2;
                        break;

                    case MENU_OPTIONS_BRUSH3:
                        gnBrushSize = 3;
                        break;

                    case MENU_OPTIONS_BRUSH4:
                        gnBrushSize = 4;
                        break;

                    case MENU_OPTIONS_BRUSH5:
                        gnBrushSize = 5;
                        break;
                }

                break;

            case MENU_OPTIONS_SHOWCOLOR:
                /*
                 * Toggle the state of the color palette.
                 */
                gfShowColor = gfShowColor ? FALSE : TRUE;
                ColorShow(gfShowColor);
                break;

            case MENU_OPTIONS_SHOWVIEW:
                /*
                 * Toggle the state of the view window.
                 */
                gfShowView = gfShowView ? FALSE : TRUE;
                ViewShow(gfShowView);
                break;

            case MENU_OPTIONS_SHOWTOOLBOX:
                /*
                 * Toggle the state of the Toolbox.
                 */
                gfShowToolbox = gfShowToolbox ? FALSE : TRUE;
                ToolboxShow(gfShowToolbox);
                break;

            /*
             * Help menu ----------------------------------------------------
             */

            case MENU_HELP_CONTENTS:
                WinHelp(ghwndMain, gszHelpFile, HELP_CONTENTS, 0L);
                break;

            case MENU_HELP_SEARCH:
                /*
                 * Tell winhelp to be sure this app's help file is current,
                 * then invoke a search with an empty starting key.
                 */
                WinHelp(ghwndMain, gszHelpFile, HELP_FORCEFILE, 0);
                WinHelp(ghwndMain, gszHelpFile, HELP_PARTIALKEY, (DWORD)(LPSTR)"");
                break;

            case MENU_HELP_ABOUT:
                DlgBox(DID_ABOUT, (WNDPROC)AboutDlgProc);
                break;

            /*
             * Hidden menu commands (accessed by accelerators) --------------
             */

            case MENU_HIDDEN_TOCOLORPAL:
                if (IsWindowVisible(ghwndColor))
                    SetFocus(ghwndColor);

                break;

            case MENU_HIDDEN_TOVIEW:
                if (IsWindowVisible(ghwndView))
                    SetFocus(ghwndView);

                break;

            case MENU_HIDDEN_TOTOOLBOX:
                if (IsWindowVisible(ghwndToolbox))
                    SetFocus(ghwndToolbox);

                break;

            case MENU_HIDDEN_TOPROPBAR:
                SetFocus(ghwndPropBar);
                break;
        }
    }

    /************************************************************************
    * MsgFilterHookFunc
    *
    * This is the exported message filter function that is hooked into
    * the message stream for detecting the pressing of the F1 key, at
    * which time it calls up the appropriate help.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    DWORD  APIENTRY MsgFilterHookFunc(
        INT nCode,
        WPARAM wParam,
        LPMSG lpMsg)
    {
        if ((nCode == MSGF_MENU || nCode == MSGF_DIALOGBOX) &&
                (lpMsg->message == WM_KEYDOWN && lpMsg->wParam == VK_F1)) {
            /*
             * Display help.
             */
            ShowHelp((nCode == MSGF_MENU) ? TRUE : FALSE);

            /*
             * Tell Windows to swallow this message.
             */
            return 1;
        }

        return DefHookProc(nCode, wParam, (LONG)lpMsg, &ghhkMsgFilter);
    }

    /************************************************************************
    * ShowHelp
    *
    * This function is called when the user has requested help.  It will
    * look at the menu state (if fMenuHelp is TRUE) or which dialog
    * is currently up to determine the help topic, then it calls WinHelp.
    *
    * Arguments:
    *   BOOL fMenuHelp - TRUE if this help is for a menu (help was requested
    *                    in the menu modal loop).  If FALSE, general help
    *                    or help for a dialog is assumed.
    *
    * History:
    *
    ************************************************************************/

    VOID ShowHelp(
        BOOL fMenuHelp)
    {
        INT nHelpContext = 0;
        HWND hwndFocus;

        if (fMenuHelp) {
            nHelpContext = GetHelpContext(gMenuSelected, gahmapMenu);
        }
        else {
            /*
             * Look for help for the current dialog.
             */
            if (gidCurrentDlg) {
                nHelpContext = GetHelpContext(gidCurrentDlg, gahmapDialog);
            }
            else {
                /*
                 * There is no current dialog.  Is the window with the
                 * focus a control on the Properties Bar?
                 */
                if ((hwndFocus = GetFocus()) && IsChild(ghwndPropBar, hwndFocus))
                    nHelpContext = GetHelpContext(DID_PROPBAR, gahmapDialog);
            }
        }

        /*
         * If there is help context, display it.  Otherwise display
         * the Contents screen.
         */
        if (nHelpContext)
            WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT, nHelpContext);
        else
            WinHelp(ghwndMain, gszHelpFile, HELP_CONTENTS, 0L);
    }

    /************************************************************************
    * GetHelpContext
    *
    * This function takes a subject and returns its matching help
    * context id from the given HELPMAP table.
    *
    * Arguments:
    *   INT idSubject   - ID of the subject to find the help context for.
    *   PHELPMAP phmap  - The help map table.  It is assumed that the
    *                     last entry in the table has a NULL subject id.
    *
    * History:
    *
    ************************************************************************/

    STATICFN INT GetHelpContext(
        INT idSubject,
        PHELPMAP phmap)
    {
        while (phmap->idSubject) {
            if (phmap->idSubject == idSubject)
                return phmap->HelpContext;

            phmap++;
        }

        return 0;
    }

    /************************************************************************
    * AboutDlgProc
    *
    * This is the About Box dialog procedure.
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC AboutDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_INITDIALOG:
                {
                    CHAR szVersion[CCHTEXTMAX];

                    strcpy(szVersion, ids(IDS_VERSION));
                    strcat(szVersion, ids(IDS_VERSIONMINOR));

    #ifdef DBG
                    strcat(szVersion, " (debug)");
    #endif

                    SetDlgItemText(hwnd, DID_ABOUTVERSION, szVersion);
                    CenterWindow(hwnd);
                }

                return TRUE;

            case WM_COMMAND:
                EndDialog(hwnd, IDOK);
                return TRUE;

            default:
                return FALSE;
        }
    }


PROPBAR.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: propbar.c
    *
    * Support for the Properties Bar.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"

 #include <windowsx.h>

    #define IMPOSSIBLEVALUE     0x7FFF

    STATICFN VOID NEAR PropBarProcessCommand(HWND hwnd, INT idCtrl,
        INT NotifyCode);

    /*
     * Cache variables.  These cache the values of some displayed fields.
     * The field is updated only if the value changes, which avoids flicker.
     * They are initialized to a large value so that the first "set" to
     * any value will always cause the field to be updated.
     */
    static INT xSave = IMPOSSIBLEVALUE;
    static INT ySave = IMPOSSIBLEVALUE;
    static INT cxSave = IMPOSSIBLEVALUE;
    static INT cySave = IMPOSSIBLEVALUE;
    static INT xHotSpotSave = IMPOSSIBLEVALUE;
    static INT yHotSpotSave = IMPOSSIBLEVALUE;

    /************************************************************************
    * PropBarDlgProc
    *
    * This is the dialog procedure for the PropBar ribbon window.
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC PropBarDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_INITDIALOG:
                /*
                 * Set this global right away.  Other routines that
                 * might be called before CreateDialog returns depend
                 * on this global.
                 */
                ghwndPropBar = hwnd;

                PropBarUpdate();

                /*
                 * Return TRUE so that the dialog manager does NOT
                 * set the focus for me.  This prevents the PropBar
                 * window from initially having the focus when the
                 * editor is started.
                 */
                return TRUE;

            case WM_PAINT:
                {
                    HDC hdc;
                    RECT rc;
                    PAINTSTRUCT ps;
                    HPEN hpenWindowFrame;

                    /*
                     * 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);

                    SelectObject(hdc, hpenDarkGray);
                    MoveToEx(hdc, rc.left, (rc.top + gcyPropBar) - gcyBorder - 1, NULL);
                    LineTo(hdc, rc.right, (rc.top + gcyPropBar) - gcyBorder - 1);

                    hpenWindowFrame = CreatePen(PS_SOLID, gcyBorder,
                            GetSysColor(COLOR_WINDOWFRAME));
                    SelectObject(hdc, hpenWindowFrame);
                    MoveToEx(hdc, rc.left, (rc.top + gcyPropBar) - gcyBorder, NULL);
                    LineTo(hdc, rc.right, (rc.top + gcyPropBar) - gcyBorder);

                    EndPaint(hwnd, &ps);
                    DeleteObject(hpenWindowFrame);
                }

                break;

            case WM_CTLCOLORBTN:
            case WM_CTLCOLORDLG:
            case WM_CTLCOLORLISTBOX:
            case WM_CTLCOLORSTATIC:
                switch (GET_WM_CTLCOLOR_TYPE(wParam, lParam, msg)) {
                    case CTLCOLOR_BTN:
                    case CTLCOLOR_DLG:
                    case CTLCOLOR_LISTBOX:
                        return (BOOL)GetStockObject(LTGRAY_BRUSH);

                    case CTLCOLOR_STATIC:
                        SetBkColor(GET_WM_CTLCOLOR_HDC(wParam, lParam, msg),
                                RGB_LIGHTGRAY);
                        return (BOOL)GetStockObject(LTGRAY_BRUSH);
                }

                return (BOOL)NULL;

            case WM_COMMAND:
                PropBarProcessCommand(hwnd,
                        GET_WM_COMMAND_ID(wParam, lParam),
                        GET_WM_COMMAND_CMD(wParam, lParam));
                break;

            case WM_DESTROY:
                /*
                 * Null out the global window handle for the prop bar
                 * for safety's sake.
                 */
                ghwndPropBar = NULL;

                break;

            default:
                return FALSE;
        }

        return FALSE;
    }

    /************************************************************************
    * PropBarProcessCommand
    *
    *
    * Arguments:
    *   HWND hwnd        - The window handle.
    *   INT idCtrl       - The id of the control the WM_COMMAND is for.
    *   INT NotifyCode   - The control's notification code.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR PropBarProcessCommand(
        HWND hwnd,
        INT idCtrl,
        INT NotifyCode)
    {
        INT iSelect;
        PIMAGEINFO pImage;

        switch (idCtrl) {
            case DID_PROPBARIMAGE:
                if (NotifyCode == CBN_SELCHANGE) {
                    if ((iSelect = (INT)SendDlgItemMessage(hwnd,
                            DID_PROPBARIMAGE, CB_GETCURSEL, 0, 0L)) != CB_ERR) {
                        /*
                         * Get a pointer to the selected image (stored in the
                         * listbox items data field).
                         */
                        pImage = (PIMAGEINFO)SendDlgItemMessage(hwnd,
                                DID_PROPBARIMAGE, CB_GETITEMDATA, iSelect, 0L);

                        /*
                         * Open the image.  If it fails, be sure to set the
                         * combobox selection back to the current image.
                         */
                        if (!ImageOpen(pImage)) {
                            PropBarSetImage(gpImageCur);
                            break;
                        }

                        SetFocus(ghwndMain);
                    }
                }

                break;

            case IDOK:
            case IDCANCEL:
                SetFocus(ghwndMain);
                break;
        }
    }

    /************************************************************************
    * PropBarUpdate
    *
    * This function updates the Properties Bar for the selection of a
    * new image or file.  It fills the Image combo with the names of
    * the images in the current file and shows/hides the HotSpot display.
    *
    * History:
    *
    ************************************************************************/

    VOID PropBarUpdate(VOID)
    {
        HWND hwndCombo;
        PIMAGEINFO pImage;
        INT idLabel;
        INT i;

        if (gpImageCur || gpszFileName) {
            switch (giType) {
                case FT_BITMAP:
                    idLabel = IDS_BITMAPIMAGELABEL;
                    break;

                case FT_ICON:
                    idLabel = IDS_ICONIMAGELABEL;
                    break;

                case FT_CURSOR:
                    idLabel = IDS_CURSORIMAGELABEL;
                    break;
            }
        }
        else {
            idLabel = IDS_NULL;
        }

        SetDlgItemText(ghwndPropBar, DID_PROPBARIMAGELABEL, ids(idLabel));

        /*
         * Get the handle to the combo box and clear out all items.
         */
        hwndCombo = GetDlgItem(ghwndPropBar, DID_PROPBARIMAGE);
        SendMessage(hwndCombo, CB_RESETCONTENT, 0, 0);

        /*
         * Fill the combo box with the images.
         */
        for (pImage = gpImageHead; pImage; pImage = pImage->pImageNext) {
            i = (INT)SendMessage(hwndCombo, CB_INSERTSTRING, (WPARAM)-1,
                    pImage->pDevice ?
                    (DWORD)(LPSTR)pImage->pDevice->szDesc :
                    (DWORD)(LPSTR)ids(IDS_UNKNOWNIMAGEFORMAT));

            SendMessage(hwndCombo, CB_SETITEMDATA, i, (DWORD)(LPSTR)pImage);
        }

        /*
         * Select the current image.
         */
        PropBarSetImage(gpImageCur);

        /*
         * Show/Hide the HotSpot info depending on whether this is
         * a cursor or not.
         */
        if (giType == FT_CURSOR) {
            if (gpImageCur)
                PropBarSetHotSpot(gpImageCur->iHotspotX, gpImageCur->iHotspotY);
            else
                PropBarClearHotSpot();

            PropBarShowHotSpot(TRUE);
        }
        else {
            PropBarShowHotSpot(FALSE);
        }
    }

    /************************************************************************
    * PropBarSetImage
    *
    *
    * History:
    *
    ************************************************************************/

    VOID PropBarSetImage(
        PIMAGEINFO pImage)
    {
        if (pImage)
            SendDlgItemMessage(ghwndPropBar, DID_PROPBARIMAGE, CB_SELECTSTRING,
                    (WPARAM)-1, (DWORD)(LPSTR)pImage->pDevice->szDesc);
        else
            SendDlgItemMessage(ghwndPropBar, DID_PROPBARIMAGE, CB_SETCURSEL,
                    (WPARAM)-1, 0);
    }

    /************************************************************************
    * PropBarSetPos
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PropBarSetPos(
        INT x,
        INT y)
    {
        CHAR szBuf[CCHTEXTMAX];

        if (x != xSave || y != ySave) {
            wsprintf(szBuf, "%d, %d", x, y);
            SetDlgItemText(ghwndPropBar, DID_PROPBARPOS, szBuf);

            /*
             *  Save them for the next time.
             */
            xSave = x;
            ySave = y;
        }
    }

    /************************************************************************
    * PropBarClearPos
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PropBarClearPos(VOID)
    {
        SetDlgItemText(ghwndPropBar, DID_PROPBARPOS, "");

        /*
         * Reset the cache variables so that the next "set" of
         * the position is sure to update the display fields.
         */
        xSave = IMPOSSIBLEVALUE;
        ySave = IMPOSSIBLEVALUE;
    }

    /************************************************************************
    * PropBarSetSize
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PropBarSetSize(
        POINT pt1,
        POINT pt2)
    {
        CHAR szBuf[CCHTEXTMAX];
        INT cx;
        INT cy;

        NormalizePoints(&pt1, &pt2);
        cx = ((pt2.x - pt1.x) / gZoomFactor) + 1;
        cy = ((pt2.y - pt1.y) / gZoomFactor) + 1;

        if (cx != cxSave || cy != cySave) {
            wsprintf(szBuf, "%dx%d", cx, cy);
            SetDlgItemText(ghwndPropBar, DID_PROPBARSIZE, szBuf);

            /*
             *  Save them for the next time.
             */
            cxSave = cx;
            cySave = cy;
        }
    }

    /************************************************************************
    * PropBarClearSize
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PropBarClearSize(VOID)
    {
        SetDlgItemText(ghwndPropBar, DID_PROPBARSIZE, "");

        /*
         * Reset the cache variables so that the next "set" of
         * the position is sure to update the display fields.
         */
        cxSave = IMPOSSIBLEVALUE;
        cySave = IMPOSSIBLEVALUE;
    }

    /************************************************************************
    * PropBarSetHotSpot
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PropBarSetHotSpot(
        INT xHotSpot,
        INT yHotSpot)
    {
        CHAR szBuf[CCHTEXTMAX];

        if (xHotSpot != xHotSpotSave || yHotSpot != yHotSpotSave) {
            wsprintf(szBuf, "%d, %d", xHotSpot, yHotSpot);
            SetDlgItemText(ghwndPropBar, DID_PROPBARHOTSPOT, szBuf);

            /*
             *  Save them for the next time.
             */
            xHotSpotSave = xHotSpot;
            yHotSpotSave = yHotSpot;
        }
    }

    /************************************************************************
    * PropBarClearHotSpot
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PropBarClearHotSpot(VOID)
    {
        SetDlgItemText(ghwndPropBar, DID_PROPBARHOTSPOT, "");

        /*
         * Reset the cache variables so that the next "set" of
         * the hotspot is sure to update the display fields.
         */
        xHotSpotSave = IMPOSSIBLEVALUE;
        yHotSpotSave = IMPOSSIBLEVALUE;
    }

    /************************************************************************
    * PropBarShowHotSpot
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    VOID PropBarShowHotSpot(
        BOOL fShow)
    {
        ShowWindow(GetDlgItem(ghwndPropBar, DID_PROPBARHOTSPOTLABEL),
                fShow ? SW_SHOWNA : SW_HIDE);
        ShowWindow(GetDlgItem(ghwndPropBar, DID_PROPBARHOTSPOT),
                fShow ? SW_SHOWNA : SW_HIDE);
    }


COLORWP.C

/***************************************************************************
     *                                                                         *
     *  MODULE      : ColorWP.c                                                *
     *                                                                         *
     *  DESCRIPTION : Window function for the colors window and related fns.   *
     *                                                                         *
     *  FUNCTIONS   : ColorWP ()            - Window function for colors       *
     *                                        window.                          *
     *                                                                         *
     *                ComputeInverseColor() - Gets the inverse RGB of a given  *
     *                                        RGB value                        *
     *                                                                         *
     *  HISTORY     : 6/21/89 - adapted from pBrush - LR                       *
     *                                                                         *
     ***************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"

 #include <windowsx.h>
    #include <commdlg.h>

    STATICFN VOID NEAR ColorInit(HWND hwnd);
    STATICFN VOID NEAR ColorProcessCommand(HWND hwnd, INT idCtrl, INT NotifyCode);
    STATICFN VOID NEAR ColorBoxPaint(HDC hdc);
    STATICFN VOID NEAR DrawColorRect(HDC hdc, DWORD rgb, INT x, INT y,
        INT cx, INT cy, HDC hdcMem, BOOL fMonoOK);
    STATICFN VOID NEAR MyRectangle(HDC hdc, INT left, INT top, INT right,
        INT bottom, HDC hdcMem, BOOL fMonoOK);
    STATICFN VOID NEAR ColorBoxClicked(UINT msg, PPOINT ppt);
    STATICFN BOOL NEAR ColorBoxHitTest(PPOINT ppt, PINT piColor, PINT pfMode);
    STATICFN VOID NEAR ColorLRPaint(HWND hwnd, HDC hdc);
    STATICFN VOID NEAR ColorLRDrawSamples(HDC hdc, PRECT prc, BOOL fLeft);
    STATICFN VOID NEAR ColorLRUpdate(BOOL fLeft);
    STATICFN VOID NEAR ColorEdit(VOID);
    STATICFN VOID NEAR SetLeftColor(INT iColor, INT iMode);
    STATICFN VOID NEAR SetRightColor(INT iColor, INT iMode);
    STATICFN HBRUSH NEAR MyCreateSolidBrush(DWORD rgb);
    STATICFN DWORD NEAR MyGetNearestColor(DWORD rgb, BOOL fMonoOK);
    STATICFN DWORD NEAR ComputeInverseColor(DWORD rgb);

    /*
     * Width/height of a single color square.
     */
    static INT gcxColorBox;

    /*
     * Vertical offset within the color box control to where to start the
     * top row of color squares (the color squares are vertically centered
     * within the color box control).
     */
    static INT gyColorBoxStart;

    /*
     * Number of colors and image type.  These globals are used by the
     * the color palette routines to know what mode the color palette
     * is in.
     */
    static INT gnColorPalColors;
    static INT giColorPalType;

    /****************************************************************************
    * ColorShow
    *
    * This function shows or hides the color palette.
    *
    * History:
    *
    ****************************************************************************/

    VOID ColorShow(
        BOOL fShow)
    {
        if (fShow)
            ShowWindow(ghwndColor, SW_SHOWNA);
        else
            ShowWindow(ghwndColor, SW_HIDE);
    }

    /************************************************************************
    * ColorDlgProc
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    DIALOGPROC ColorDlgProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_INITDIALOG:
                ColorInit(hwnd);

                /*
                 * Return TRUE so that the dialog manager does NOT set the focus
                 * for me.  This prevents the status window from initially having
                 * the focus when the editor is started.
                 */
                return TRUE;

            case WM_ACTIVATE:
                if (GET_WM_ACTIVATE_STATE(wParam, lParam))
                    gidCurrentDlg = DID_COLOR;

                break;

            case WM_CTLCOLORBTN:
            case WM_CTLCOLORDLG:
            case WM_CTLCOLORSTATIC:
                switch (GET_WM_CTLCOLOR_TYPE(wParam, lParam, msg)) {
                    case CTLCOLOR_BTN:
                    case CTLCOLOR_DLG:
                        return (BOOL)GetStockObject(LTGRAY_BRUSH);

                    case CTLCOLOR_STATIC:
                        SetBkColor(GET_WM_CTLCOLOR_HDC(wParam, lParam, msg),
                                RGB_LIGHTGRAY);
                        return (BOOL)GetStockObject(LTGRAY_BRUSH);
                }

                return (BOOL)NULL;

            case  WM_PAINT:
                {
                    HDC hdc;
                    PAINTSTRUCT ps;

                    hdc = BeginPaint(hwnd, &ps);
                    DrawMarginBorder(hwnd, hdc);
                    EndPaint(hwnd, &ps);
                }

                break;

            case WM_COMMAND:
                ColorProcessCommand(hwnd,
                        GET_WM_COMMAND_ID(wParam, lParam),
                        GET_WM_COMMAND_CMD(wParam, lParam));
                break;

            case WM_CLOSE:
                /*
                 * The user closed the color palette from the system menu.
                 * Hide the window (we don't actually destroy it so
                 * that it will appear in the same spot when they show
                 * it again).
                 */
                ColorShow(FALSE);
                gfShowColor = FALSE;
                break;

            case WM_DESTROY:
                {
                    RECT rc;

                    /*
                     * Save the position of the color palette.
                     */
                    GetWindowRect(hwnd, &rc);
                    WriteWindowPos(&rc, FALSE, szColorPos);

                    /*
                     * Null out the global window handle for the color palette
                     * for safety's sake.
                     */
                    ghwndColor = NULL;
                }

                break;

            default:
                return FALSE;
        }

        return FALSE;
    }

    /************************************************************************
    * ColorInit
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    STATICFN VOID NEAR ColorInit(
        HWND hwnd)
    {
        RECT rc;

        /*
         * Get the dimension of a single color square, and the vertical
         * offset to where the top of the squares are.
         */
        GetWindowRect(GetDlgItem(hwnd, DID_COLORBOX), &rc);
        gcxColorBox = (rc.right - rc.left) / COLORCOLS;
        gyColorBoxStart = ((rc.right - rc.left) - (gcxColorBox * COLORCOLS)) / 2;
    }

    /************************************************************************
    * ColorProcessCommand
    *
    *
    * Arguments:
    *   HWND hwnd        - The window handle.
    *   INT idCtrl       - The id of the control the WM_COMMAND is for.
    *   INT NotifyCode   - The control's notification code.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR ColorProcessCommand(
        HWND hwnd,
        INT idCtrl,
        INT NotifyCode)
    {
        switch (idCtrl) {
            case DID_COLOREDIT:
                ColorEdit();
                break;

            case DID_COLORDEFAULT:
                if (gfModeLeft == MODE_COLOR) {
                    gargbColor[giColorLeft] = gargbDefaultColor[giColorLeft];
                    InvalidateRect(GetDlgItem(ghwndColor, DID_COLORBOX),
                            NULL, TRUE);
                    SetLeftColor(giColorLeft, gfModeLeft);
                }

                break;
        }
    }

    /************************************************************************
    * ColorBoxWndProc
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    WINDOWPROC ColorBoxWndProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        POINT pt;
        HDC hdc;
        PAINTSTRUCT ps;
        INT iColor;
        INT iMode;

        switch (msg) {
            case WM_PAINT:
                hdc = BeginPaint(hwnd, &ps);
                ColorBoxPaint(hdc);
                EndPaint(hwnd, &ps);
                break;

            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                ColorBoxClicked(msg, &pt);
                break;

            case WM_LBUTTONDBLCLK:
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                if (ColorBoxHitTest(&pt, &iColor, &iMode))
                    ColorEdit();

                break;

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

        return 0;
    }

    /************************************************************************
    * ColorBoxPaint
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR ColorBoxPaint(
        HDC hdc)
    {
        HDC hdcMem;
        HBITMAP hbmMem;
        INT i;
        INT x;
        INT y;
        INT cx = gcxColorBox + 1;
        INT cy = gcxColorBox + 1;

        if (giColorPalType != FT_BITMAP) {
            x = 0;
            y = gyColorBoxStart;
            DrawColorRect(hdc, grgbScreen, x, y, cx, cy, NULL, FALSE);
            y += gcxColorBox;
            DrawColorRect(hdc, grgbInverse, x, y, cx, cy, NULL, FALSE);
        }

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

        /*
         * Create a bitmap.  It will have the same number of colors as the
         * current image.
         */
        if (!(hbmMem = MyCreateBitmap(hdc, cx, cy, gnColorPalColors))) {
            DeleteDC(hdcMem);
            return;
        }

        SelectObject(hdcMem, hbmMem);

        x = gcxColorBox * 2;
        y = gyColorBoxStart;

        for (i = 1; i <= COLORSMAX; i++) {
            DrawColorRect(hdc, gargbCurrent[i - 1], x, y, cx, cy, hdcMem, TRUE);

            if (i % COLORROWS) {
                y += gcxColorBox;
            }
            else {
                x += gcxColorBox;
                y = gyColorBoxStart;
            }
        }

        DeleteDC(hdcMem);
        DeleteObject(hbmMem);
    }

    /************************************************************************
    * DrawColorRect
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR DrawColorRect(
        HDC hdc,
        DWORD rgb,
        INT x,
        INT y,
        INT cx,
        INT cy,
        HDC hdcMem,
        BOOL fMonoOK)
    {
        HBRUSH hbr;
        HBRUSH hbrOld;

        hbr = CreateSolidBrush(rgb);
        hbrOld = SelectObject(hdc, hbr);
        MyRectangle(hdc, x, y, x + cx, y + cy, hdcMem, fMonoOK);
        SelectObject(hdc, hbrOld);
        DeleteObject(hbr);
    }

    /************************************************************************
    * MyRectangle
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR MyRectangle(
        HDC hdc,
        INT left,
        INT top,
        INT right,
        INT bottom,
        HDC hdcMem,
        BOOL fMonoOK)
    {
        HBITMAP hbmMem;
        HBRUSH hbr;
        HPEN hpen;
        HBRUSH hbrOld;
        HPEN hpenOld;
        BOOL fDCCreated = FALSE;
        INT cx = right - left;
        INT cy = bottom - top;
        INT nColors;

        /*
         * Do they want us to create the memory DC and bitmap for them?
         */
        if (!hdcMem) {
            if (!(hdcMem = CreateCompatibleDC(hdc)))
                return;

            /*
             * Create a bitmap.  It will be monochrome if in 2 color mode
             * and monochrome is ok, otherwise it will be 16 color.
             */
            nColors = gnColorPalColors;
            if (!fMonoOK)
                nColors = 16;

            if (!(hbmMem = MyCreateBitmap(hdc, cx, cy, nColors))) {
                DeleteDC(hdcMem);
                return;
            }

            SelectObject(hdcMem, hbmMem);
            fDCCreated = TRUE;
        }

        /*
         * Extract the current pen and brush out of the passed in DC.
         */
        hbr = SelectObject(hdc, GetStockObject(NULL_BRUSH));
        hpen = SelectObject(hdc, GetStockObject(NULL_PEN));

        /*
         * Select them into the memory DC.
         */
        hbrOld = SelectObject(hdcMem, hbr);
        hpenOld = SelectObject(hdcMem, hpen);

        /*
         * Draw the rectangle in the memory bitmap.
         */
        Rectangle(hdcMem, 0, 0, cx, cy);

        /*
         * Unselect the pen and brush from the memory DC.
         */
        SelectObject(hdcMem, hbrOld);
        SelectObject(hdcMem, hpenOld);

        /*
         * Restore the pen and brush to the original DC.
         */
        SelectObject(hdc, hbr);
        SelectObject(hdc, hpen);

        /*
         * Blit the memory image to the passed in DC.
         */
        BitBlt(hdc, left, top, cx, cy, hdcMem, 0, 0, SRCCOPY);

        if (fDCCreated) {
            DeleteDC(hdcMem);
            DeleteObject(hbmMem);
        }
    }

    /************************************************************************
    * ColorBoxClicked
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR ColorBoxClicked(
        UINT msg,
        PPOINT ppt)
    {
        INT iColor;
        INT iMode;

        if (ColorBoxHitTest(ppt, &iColor, &iMode)) {
            switch (msg) {
                case WM_LBUTTONDOWN:
                    SetLeftColor(iColor, iMode);
                    break;

                case WM_RBUTTONDOWN:
                    SetRightColor(iColor, iMode);
                    break;
            }
        }
    }

    /************************************************************************
    * ColorBoxHitTest
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN BOOL NEAR ColorBoxHitTest(
        PPOINT ppt,
        PINT piColor,
        PINT pfMode)
    {
        INT iCol;
        INT iRow;
        INT iBox;

        if (ppt->y < gyColorBoxStart)
            return FALSE;

        iCol = ppt->x / gcxColorBox;
        iRow = (ppt->y - gyColorBoxStart) / gcxColorBox;

        if (iCol >= (COLORSMAX / COLORROWS) + 2 || iRow >= COLORROWS)
            return FALSE;

        iBox = iRow + (iCol * COLORROWS);

        switch (iBox) {
            case 0:
                if (giColorPalType == FT_BITMAP)
                    return FALSE;

                *piColor = 0;
                *pfMode = MODE_SCREEN;
                return TRUE;

            case 1:
                if (giColorPalType == FT_BITMAP)
                    return FALSE;

                *piColor = 0;
                *pfMode = MODE_INVERSE;
                return TRUE;

            case 2:
            case 3:
                return FALSE;

            default:
                *piColor = iBox - (COLORROWS * 2);
                *pfMode = MODE_COLOR;
                return TRUE;
        }
    }

    /************************************************************************
    * ColorLRWndProc
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    WINDOWPROC ColorLRWndProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        HDC hdc;
        PAINTSTRUCT ps;

        switch (msg) {
            case WM_PAINT:
                hdc = BeginPaint(hwnd, &ps);
                ColorLRPaint(hwnd, hdc);
                EndPaint(hwnd, &ps);
                break;

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

        return 0;
    }

    /************************************************************************
    * ColorLRPaint
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR ColorLRPaint(
        HWND hwnd,
        HDC hdc)
    {
        RECT rc;

        GetClientRect(hwnd, &rc);
        DrawSunkenRect(&rc, hdc);
        ColorLRDrawSamples(hdc, &rc, TRUE);
        ColorLRDrawSamples(hdc, &rc, FALSE);
    }

    /************************************************************************
    * ColorLRDrawSamples
    *
    * Draws the sample colors in the Color Left-Right control.
    *
    * Arguments:
    *   HDC hdc    - DC to draw into.
    *   PRECT prc  - Rectangle of color sample control.  The samples will
    *                be centered within this with an appropriate margin.
    *   BOOL fLeft - TRUE if the left sample is to be drawn, FALSE for the right.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR ColorLRDrawSamples(
        HDC hdc,
        PRECT prc,
        BOOL fLeft)
    {
        INT xLeftStart;
        INT xRightStart;
        INT ySolidStart;
        INT yDitherStart;
        INT cx;
        INT cy;
        HBRUSH hbrOld;
        HPEN hpenOld;
        BOOL fMonoOK;

        /*
         * The width and height of each square includes the border.
         */
        cx = ((prc->right - prc->left) - (6 * PALETTEMARGIN)) / 2;
        cy = ((prc->bottom - prc->top) - (4 * PALETTEMARGIN)) / 2;

        xLeftStart = prc->left + (PALETTEMARGIN * 2) + 1;
        xRightStart = xLeftStart + cx + (PALETTEMARGIN * 2);

        ySolidStart = prc->top + (PALETTEMARGIN * 2) + 1;
        yDitherStart = ySolidStart - 1 + cy;

        /*
         * Draw either the left or the right color sample.
         */
        if (fLeft) {
            fMonoOK = (gfModeLeft == MODE_COLOR) ? TRUE : FALSE;

            /*
             * Draw the solid color.
             */
            hbrOld = SelectObject(hdc, ghbrLeftSolid);
            hpenOld = SelectObject(hdc, GetStockObject(NULL_PEN));
            MyRectangle(hdc, xLeftStart, ySolidStart,
                    xLeftStart + cx, yDitherStart + 1, NULL, fMonoOK);

            /*
             * Draw the true color (may be dithered).
             */
            SelectObject(hdc, ghbrLeft);
            MyRectangle(hdc, xLeftStart, yDitherStart,
                    xLeftStart + cx, yDitherStart + cy, NULL, fMonoOK);
        }
        else {
            fMonoOK = (gfModeRight == MODE_COLOR) ? TRUE : FALSE;

            hbrOld = SelectObject(hdc, ghbrRightSolid);
            hpenOld = SelectObject(hdc, GetStockObject(NULL_PEN));
            MyRectangle(hdc, xRightStart, ySolidStart,
                    xRightStart + cx, yDitherStart + 1, NULL, fMonoOK);

            SelectObject(hdc, ghbrRight);
            MyRectangle(hdc, xRightStart, yDitherStart,
                    xRightStart + cx, yDitherStart + cy, NULL, fMonoOK);
        }

        /*
         * Now draw the outline rectangle.
         */
        SelectObject(hdc, GetStockObject(BLACK_PEN));
        SelectObject(hdc, GetStockObject(NULL_BRUSH));

        if (fLeft) {
            Rectangle(hdc, xLeftStart - 1, ySolidStart - 1,
                    xLeftStart + cx, yDitherStart + cy);
        }
        else {
            Rectangle(hdc, xRightStart - 1, ySolidStart - 1,
                    xRightStart + cx, yDitherStart + cy);
        }

        /*
         * Clean up.
         */
        SelectObject(hdc, hpenOld);
        SelectObject(hdc, hbrOld);
    }

    /************************************************************************
    * ColorLRUpdate
    *
    * Called when the left or right color has been changed.  This function
    * will cause the specified color sample to be updated in the color palette.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR ColorLRUpdate(
        BOOL fLeft)
    {
        RECT rc;
        HWND hwndLR;
        HDC hdc;

        hwndLR = GetDlgItem(ghwndColor, DID_COLORLR);
        GetClientRect(hwndLR, &rc);
        hdc = GetDC(hwndLR);
        ColorLRDrawSamples(hdc, &rc, fLeft);
        ReleaseDC(hwndLR, hdc);
    }

    /************************************************************************
    * ColorEdit
    *
    * This function calls the standard color chooser dialog to get a
    * new color for the selected palette entry.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR ColorEdit(VOID)
    {
        /*
         * This array of custom colors is initialized to all white colors.
         * The custom colors will be remembered between calls, but not
         * between sessions.
         */
        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;
        DWORD rgbOld;
        BOOL fResult;
        INT idPrevDlg;

        switch (gfModeLeft) {
            case MODE_COLOR:
                /*
                 * The monochrome palette cannot be edited.
                 */
                if (gnColorPalColors == 2)
                    return;

                rgbOld = gargbCurrent[giColorLeft];
                break;

            case MODE_SCREEN:
                rgbOld = grgbScreen;
                break;

            case MODE_INVERSE:
                rgbOld = grgbInverse;
                break;
        }

        cc.lStructSize = sizeof(CHOOSECOLOR);
        cc.hwndOwner = ghwndMain;
        cc.hInstance = ghInst;
        cc.rgbResult = rgbOld;
        cc.lpCustColors = argbCust;
        cc.Flags = CC_RGBINIT | CC_SHOWHELP;
        cc.lCustData = 0;
        cc.lpfnHook = NULL;
        cc.lpTemplateName = NULL;

        EnteringDialog(DID_COMMONFILECHOOSECOLOR, &idPrevDlg, TRUE);
        fResult = ChooseColor(&cc);
        EnteringDialog(idPrevDlg, NULL, FALSE);

        if (fResult && rgbOld != cc.rgbResult) {
            switch (gfModeLeft) {
                case MODE_COLOR:
                    gargbCurrent[giColorLeft] = cc.rgbResult;
                    break;

                case MODE_SCREEN:
                    SetScreenColor(cc.rgbResult);
                    break;

                case MODE_INVERSE:
                    SetScreenColor(ComputeInverseColor(cc.rgbResult));
                    break;
            }

            SetLeftColor(giColorLeft, gfModeLeft);
            InvalidateRect(GetDlgItem(ghwndColor, DID_COLORBOX), NULL, TRUE);
        }
    }

    /************************************************************************
    * SetLeftColor
    *
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR SetLeftColor(
        INT iColor,
        INT iMode)
    {
        DWORD rgbSolid;
        BOOL fEnableDefault = FALSE;
        BOOL fEnableEdit = FALSE;

        if (ghbrLeft)
            DeleteObject(ghbrLeft);

        if (ghbrLeftSolid)
            DeleteObject(ghbrLeftSolid);

        if (ghpenLeft)
            DeleteObject(ghpenLeft);

        switch (iMode) {
            case MODE_COLOR:
                ghbrLeft = MyCreateSolidBrush(gargbCurrent[iColor]);
                rgbSolid = MyGetNearestColor(gargbCurrent[iColor], TRUE);
                ghbrLeftSolid = CreateSolidBrush(rgbSolid);
                ghpenLeft = CreatePen(PS_INSIDEFRAME, 1, rgbSolid);
                giColorLeft = iColor;

                /*
                 * We will enable the "Default" button if the current color
                 * on the left button is not the default color, and we are
                 * not in monochrome mode.
                 */
                if (gargbColor[giColorLeft] != gargbDefaultColor[giColorLeft] &&
                        gnColorPalColors > 2)
                    fEnableDefault = TRUE;

                /*
                 * For non-screen colors, the Edit button will be enabled
                 * if we are not in monochrome mode.
                 */
                if (gnColorPalColors > 2)
                    fEnableEdit = TRUE;

                break;

            case MODE_SCREEN:
                ghbrLeft = CreateSolidBrush(grgbScreen);
                ghbrLeftSolid = CreateSolidBrush(grgbScreen);
                ghpenLeft = CreatePen(PS_INSIDEFRAME, 1, grgbScreen);
                giColorLeft = 0;
                fEnableEdit = TRUE;
                break;

            case MODE_INVERSE:
                ghbrLeft = CreateSolidBrush(grgbInverse);
                ghbrLeftSolid = CreateSolidBrush(grgbInverse);
                ghpenLeft = CreatePen(PS_INSIDEFRAME, 1, grgbInverse);
                giColorLeft = 0;
                fEnableEdit = TRUE;
                break;
        }

        EnableWindow(GetDlgItem(ghwndColor, DID_COLORDEFAULT), fEnableDefault);
        EnableWindow(GetDlgItem(ghwndColor, DID_COLOREDIT), fEnableEdit);

        gfModeLeft = iMode;
        ColorLRUpdate(TRUE);
    }

    /************************************************************************
    * SetRightColor
    *
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR SetRightColor(
        INT iColor,
        INT iMode)
    {
        DWORD rgbSolid;

        if (ghbrRight)
            DeleteObject(ghbrRight);

        if (ghbrRightSolid)
            DeleteObject(ghbrRightSolid);

        if (ghpenRight)
            DeleteObject(ghpenRight);

        switch (iMode) {
            case MODE_COLOR:
                ghbrRight = MyCreateSolidBrush(gargbCurrent[iColor]);
                rgbSolid = MyGetNearestColor(gargbCurrent[iColor], TRUE);
                ghbrRightSolid = CreateSolidBrush(rgbSolid);
                ghpenRight = CreatePen(PS_INSIDEFRAME, 1, rgbSolid);
                giColorRight = iColor;
                break;

            case MODE_SCREEN:
                ghbrRight = CreateSolidBrush(grgbScreen);

ghbrRightSolid = CreateSolidBrush(grgbScreen);
                ghpenRight = CreatePen(PS_INSIDEFRAME, 1, grgbScreen);
                giColorRight = 0;
                break;

            case MODE_INVERSE:
                ghbrRight = CreateSolidBrush(grgbInverse);
                ghbrRightSolid = CreateSolidBrush(grgbInverse);
                ghpenRight = CreatePen(PS_INSIDEFRAME, 1, grgbInverse);
                giColorRight = 0;
                break;
        }

        gfModeRight = iMode;
        ColorLRUpdate(FALSE);
    }

    /************************************************************************
    * SetScreenColor
    *
    *
    * History:
    *
    ************************************************************************/

    VOID SetScreenColor(
        DWORD rgb)
    {
        DWORD rgbInverse;
        HDC hdcTemp;
        HBITMAP hbmOld;
        HDC hdcANDTemp;
        HBITMAP hbmANDOld;

        rgb = MyGetNearestColor(rgb, FALSE);

        /*
         * Because we are about to change the screen color, separate
         * out the XOR mask (but only for icons/cursors).
         */
        if (giColorPalType != FT_BITMAP) {
            if (gpImageCur) {
                ImageDCSeparate(ghdcImage, gcxImage, gcyImage, ghdcANDMask,
                        grgbScreen);

                /*
                 * Is there a pending undo buffer?  If so, it must be
                 * changed as well or an undo that is done after a screen
                 * color change will restore the wrong colors!
                 */
                if (ghbmUndo) {
                    /*
                     * Create some temporary DC's to use when separating
                     * out the undo buffer's masks.  These will be deleted
                     * a little later.
                     */
                    hdcTemp = CreateCompatibleDC(ghdcImage);
                    hbmOld = SelectObject(hdcTemp, ghbmUndo);
                    hdcANDTemp = CreateCompatibleDC(ghdcANDMask);
                    hbmANDOld = SelectObject(hdcANDTemp, ghbmUndoMask);

                    /*
                     * Separate out the undo buffer's colors, before
                     * changing the screen color.  It will be combined
                     * later.
                     */
                    ImageDCSeparate(hdcTemp, gcxImage, gcyImage, hdcANDTemp,
                            grgbScreen);
                }
            }
        }

        if (ghbrScreen)
            DeleteObject(ghbrScreen);

        ghbrScreen = CreateSolidBrush(rgb);
        grgbScreen = rgb;

        if (ghbrInverse)
            DeleteObject(ghbrInverse);

        rgbInverse = ComputeInverseColor(rgb);
        ghbrInverse = CreateSolidBrush(rgbInverse);
        grgbInverse = rgbInverse;

        /*
         * For icons and cursors, we might need to update a few more things.
         */
        if (giColorPalType != FT_BITMAP) {
            /*
             * Recombine the XOR and AND images now that there is a new screen
             * color.  This updates the image DC with the new color properly.
             */
            if (gpImageCur) {
                ImageDCCombine(ghdcImage, gcxImage, gcyImage, ghdcANDMask);

                /*
                 * Is there a pending undo buffer?  If so, it has to be
                 * recombined with the new screen color.
                 */
                if (ghbmUndo) {
                    ImageDCCombine(hdcTemp, gcxImage, gcyImage, hdcANDTemp);

                    /*
                     * Clean up the DC's that were allocated a little earlier.
                     */
                    SelectObject(hdcANDTemp, hbmANDOld);
                    DeleteDC(hdcANDTemp);
                    SelectObject(hdcTemp, hbmOld);
                    DeleteDC(hdcTemp);
                }
            }

            /*
             * Reset the colors on the mouse buttons, just in case a screen
             * or inverse screen color was assigned to either of them.
             */
            SetLeftColor(giColorLeft, gfModeLeft);
            SetRightColor(giColorRight, gfModeRight);

            InvalidateRect(GetDlgItem(ghwndColor, DID_COLORBOX), NULL, TRUE);
        }

        ViewUpdate();
    }

    /************************************************************************
    * MyCreateSolidBrush
    *
    *
    * History:
    *
    ************************************************************************/

    STATICFN HBRUSH NEAR MyCreateSolidBrush(
        DWORD rgb)
    {
        HDC hdc;
        HDC hdcMem;
        HBRUSH hbr;
        HBRUSH hbrOld;
        HBITMAP hbmPat;
        HBITMAP hbmOld;

        /*
         * First, create a brush for the given RGB value.
         */
        hbr = CreateSolidBrush(rgb);

        /*
         * Create a temporary memory DC.
         */
        hdc = GetDC(ghwndMain);
        hdcMem = CreateCompatibleDC(hdc);

        /*
         * Create a temporary bitmap.
         */
        hbmPat = MyCreateBitmap(hdc, 8, 8, gnColorPalColors);
        ReleaseDC(ghwndMain, hdc);

        /*
         * Draw the (possibly) dithered pattern on the temporary bitmap.
         */
        hbmOld = SelectObject(hdcMem, hbmPat);
        hbrOld = SelectObject(hdcMem, hbr);
        PatBlt(hdcMem, 0, 0, 8, 8, PATCOPY);
        SelectObject(hdcMem, hbrOld);
        SelectObject(hdcMem, hbmOld);
        DeleteDC(hdcMem);

        /*
         * Delete the first brush.
         */
        DeleteObject(hbr);

        /*
         * Now create a pattern brush out of the (dithered) bitmap.
         */
        hbr = CreatePatternBrush(hbmPat);

        DeleteObject(hbmPat);

        /*
         * Return the pattern brush.
         */
        return hbr;
    }

    /************************************************************************
    * MyGetNearestColor
    *
    * This function returns the RGB value of the nearest color to the
    * specified RGB value.  If fMonoOK is TRUE, it takes into account
    * the number of colors of the current image being edited.  In other
    * words, it will return the nearest solid color for a device that
    * has the number of colors of the current image.
    *
    * Arguments:
    *   DWORD rgb    - RGB value of the color.
    *   BOOL fMonoOK - TRUE if the returned color should be mapped to a
    *                  color in a monochrome palette, if the current image
    *                  is monochrome.  A value of FALSE will return a
    *                  color mapped to the closest color in a 16 color
    *                  palette.
    *
    * History:
    *
    ************************************************************************/

    STATICFN DWORD NEAR MyGetNearestColor(
        DWORD rgb,
        BOOL fMonoOK)
    {
        HDC hdc;
        HDC hdcMem;
        DWORD rgbNearest;
        HBITMAP hbmMem;
        HBITMAP hbmOld;

        hdc = GetDC(ghwndMain);
        hdcMem = CreateCompatibleDC(hdc);
        hbmMem = MyCreateBitmap(hdc, 1, 1, (fMonoOK) ? gnColorPalColors : 16);
        hbmOld = SelectObject(hdcMem, hbmMem);
        rgbNearest = GetNearestColor(hdcMem, rgb);
        SelectObject(hdcMem, hbmOld);
        DeleteObject(hbmMem);
        DeleteDC(hdcMem);
        ReleaseDC(ghwndMain, hdc);

        return rgbNearest;
    }

    /************************************************************************
    * ComputeInverseColor
    *
    * Computes the inverse value of a given rgb color.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN DWORD NEAR ComputeInverseColor(
        DWORD rgb)
    {
        HBITMAP hTempBit1;
        HBITMAP hTempBit2;
        HDC hTempDC1;
        HDC hTempDC2;
        HDC hdc;
        HANDLE hOldObj1;
        HANDLE hOldObj2;
        DWORD rgbInv;

        hdc = GetDC(ghwndMain);
        hTempDC1 = CreateCompatibleDC(hdc);
        hTempDC2 = CreateCompatibleDC(hdc);

        /* create two temporary 1x1, 16 color bitmaps */
        hTempBit1 = MyCreateBitmap(hdc, 1, 1, 16);
        hTempBit2 = MyCreateBitmap(hdc, 1, 1, 16);

        ReleaseDC(ghwndMain, hdc);

        hOldObj1 = SelectObject(hTempDC1, hTempBit1);
        hOldObj2 = SelectObject(hTempDC2, hTempBit2);

        /* method for getting inverse color : set the given pixel (rgb) on
         * one DC. Now blt it to the other DC using a SRCINVERT rop.
         * This yields a pixel of the inverse color on the destination DC
         */
        SetPixel(hTempDC1, 0, 0, rgb);
        PatBlt(hTempDC2, 0, 0, 1, 1, WHITENESS);
        BitBlt(hTempDC2, 0, 0, 1, 1, hTempDC1, 0, 0, SRCINVERT);
        rgbInv = GetPixel(hTempDC2, 0, 0);

        /* clean up ... */
        SelectObject(hTempDC1, hOldObj1);
        SelectObject(hTempDC2, hOldObj2);
        DeleteObject(hTempBit1);
        DeleteObject(hTempBit2);
        DeleteDC(hTempDC1);
        DeleteDC(hTempDC2);

        /* ...and return the inverted RGB value */
        return rgbInv;
    }

    /************************************************************************
    * SetColorPalette
    *
    *
    * History:
    *
    ************************************************************************/

    VOID SetColorPalette(
        INT nColors,
        INT iType,
        BOOL fForce)
    {
        /*
         * Quit if nothing changed (unless they are forcing it to be updated).
         */
        if (!fForce && nColors == gnColorPalColors && iType == giColorPalType)
            return;

        /*
         * Set the globals that all the color palette routines use.
         */
        gnColorPalColors = nColors;
        giColorPalType = iType;

        if (gnColorPalColors == 2)
            gargbCurrent = gargbMono;
        else
            gargbCurrent = gargbColor;

        ShowWindow(GetDlgItem(ghwndColor, DID_COLORSCREENLABEL),
                (giColorPalType == FT_BITMAP) ? SW_HIDE : SW_SHOW);
        ShowWindow(GetDlgItem(ghwndColor, DID_COLORINVERSELABEL),
                (giColorPalType == FT_BITMAP) ? SW_HIDE : SW_SHOW);

        SetLeftColor(1, MODE_COLOR);
        SetRightColor(0, MODE_COLOR);

        InvalidateRect(GetDlgItem(ghwndColor, DID_COLORBOX), NULL, TRUE);
    }

    /************************************************************************
    * RestoreDefaultColors
    *
    *
    * History:
    *
    ************************************************************************/

    VOID RestoreDefaultColors(VOID)
    {
        INT i;

        for (i = 0; i < COLORSMAX; i++)
            gargbColor[i] = gargbDefaultColor[i];

        SetColorPalette(16, giColorPalType, TRUE);
    }


RWBMP.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: rwbmp.c
    *
    * Routines for reading and writing bitmap files.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"

    #include <stdio.h>
    #include <io.h>
    #include <fcntl.h>                          // For NT fstat().
    #include <sys\types.h>                      // For fstat() types.
    #include <sys\stat.h>                       // For fstat() function.

    /************************************************************************
    * LoadBitmapFile
    *
    * Loads the specified bitmap file.  With ImagEdit, this must be a
    * Windows 3.0 DIB.
    *
    * Arguments:
    *   PSTR pszFullFileName - Name of the bitmap file to load.
    *
    * Returns:
    *   TRUE if successful, FALSE otherwise.
    *
    * History:
    *
    ************************************************************************/

    BOOL LoadBitmapFile(
        PSTR pszFullFileName)
    {
        HFILE hf;
        OFSTRUCT OfStruct;
        struct stat FileStatus;
        DWORD dwFileSize;
        DWORD dwDIBSize;
        BITMAPFILEHEADER bfh;
        BITMAPINFOHEADER bih;
        PIMAGEINFO pImage;
        INT nColors;
        HANDLE hDIB;
        PDEVICE pDevice;

        if ((hf = (HFILE)OpenFile(pszFullFileName, (LPOFSTRUCT)&OfStruct, OF_READ))
                == (HFILE)-1) {
            Message(MSG_CANTOPEN, pszFullFileName);
            return FALSE;
        }

        fstat((INT)_open_osfhandle((long)(hf), (int)(O_RDONLY)), &FileStatus);
        dwFileSize = (DWORD)FileStatus.st_size;

        ImageLinkFreeList();

        /*
         * Read the Bitmap File Header.
         */
        if (!MyFileRead(hf, (LPSTR)&bfh, sizeof(BITMAPFILEHEADER),
                pszFullFileName, FT_BITMAP))
            goto Error1;

        /*
         * Check for the "BM" at the start of the file, and the file size
         * in the header must match the real file size.
         */
        if (bfh.bfType != 0x4D42 || bfh.bfSize != dwFileSize) {
            Message(MSG_BADBMPFILE, pszFullFileName);
            goto Error1;
        }

        /*
         * Read the Bitmap Info Header.
         */
        if (!MyFileRead(hf, (LPSTR)&bih, sizeof(BITMAPINFOHEADER),
                pszFullFileName, FT_BITMAP))
            goto Error1;

        /*
         * The DIB size should be the size of the file less the bitmap
         * file header.
         */
        dwDIBSize = dwFileSize - sizeof(BITMAPFILEHEADER);

        if (!IsValidDIB((LPBITMAPINFO)&bih, dwDIBSize, FALSE)) {
            Message(MSG_BADBMPFILE, pszFullFileName);
            goto Error1;
        }

        /*
         * There is a limit to the size of image we will edit.  For icons
         * and cursors, the field that carries the dimensions is a byte,
         * so the size cannot be greater than 256.  This is also what
         * we limit bitmaps to.
         */
        if (bih.biWidth > MAXIMAGEDIM || bih.biHeight > MAXIMAGEDIM) {
            Message(MSG_BADBMPSIZE, MAXIMAGEDIM, MAXIMAGEDIM);
            goto Error1;
        }

        switch (bih.biBitCount) {
            case 1:
                nColors = 2;
                break;

            case 4:
                nColors = 16;
                break;

            default:
                Message(MSG_NOTSUPPORT);
                goto Error1;
        }

        if (!(pDevice = DeviceLinkAlloc(FT_BITMAP, NULL, nColors,
                (INT)bih.biWidth, (INT)bih.biHeight))) {
            goto Error1;
        }

        if (!(pImage = ImageLinkAlloc(pDevice,
                (INT)bih.biWidth, (INT)bih.biHeight, 0, 0, nColors)))
            goto Error1;

        /*
         * Allocate space for the DIB for this image.
         */
        if (!(hDIB = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwDIBSize))) {
            Message(MSG_OUTOFMEMORY);
            goto Error2;
        }

        pImage->DIBSize = dwDIBSize;
        pImage->DIBhandle = hDIB;
        pImage->DIBPtr = (LPSTR)GlobalLock(hDIB);

        /*
         * Jump back to the start of the DIB.
         */
        SetFilePointer((HANDLE)hf, sizeof(BITMAPFILEHEADER), NULL, (DWORD)0);

        /*
         * Read the entire DIB (including info and color table) into
         * the allocated memory for it.
         */
        if (!MyFileRead(hf, pImage->DIBPtr, (DWORD)pImage->DIBSize,
                pszFullFileName, FT_BITMAP))
            goto Error2;

        _lclose((HFILE)hf);

        fFileDirty = FALSE;
        SetFileName(pszFullFileName);
        giType = FT_BITMAP;

        /*
         * Bitmaps only have one "image" in the file.
         */
        gnImages = 1;

        ImageOpen2(pImage);

        return TRUE;

    Error2:
        ImageLinkFreeList();

    Error1:
        _lclose((HFILE)hf);

        return FALSE;
    }

    /************************************************************************
    * SaveBitmapFile
    *
    *
    *
    * Arguments:
    *
    * Returns:
    *   TRUE if successful, FALSE otherwise.
    *
    * History:
    *
    ************************************************************************/

    BOOL SaveBitmapFile(
        PSTR pszFullFileName)
    {
        HCURSOR hcurOld;
        BITMAPFILEHEADER bfh;
        HFILE hf;
        OFSTRUCT OfStruct;

        hcurOld = SetCursor(hcurWait);

        /*
         * Save the bits of the current image.
         */
        ImageSave();

        /*
         * Open the file for writing.
         */
        if ((hf = (HFILE)OpenFile(pszFullFileName, &OfStruct, OF_CREATE | OF_READWRITE))
                == (HFILE)-1) {
            Message(MSG_CANTCREATE, pszFullFileName);
            goto Error1;
        }

        bfh.bfType = 0x4D42;
        bfh.bfSize = sizeof(BITMAPFILEHEADER) + gpImageCur->DIBSize;
        bfh.bfReserved1 = 0;
        bfh.bfReserved2 = 0;
        bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
                (gpImageCur->nColors * sizeof(RGBQUAD));

        /*
         * Write the header to disk.
         */
        if (!MyFileWrite(hf, (LPSTR)&bfh, sizeof(BITMAPFILEHEADER),
                pszFullFileName))
            goto Error2;

        /*
         * Now write the DIB (a bitmap file only has one).
         */
        if (!MyFileWrite(hf, (LPSTR)gpImageCur->DIBPtr,
                (DWORD)gpImageCur->DIBSize, pszFullFileName))
            goto Error2;

        _lclose((HFILE)hf);

        fFileDirty = FALSE;
        SetFileName(pszFullFileName);

        SetCursor(hcurOld);

        return TRUE;

    Error2:
        _lclose((HFILE)hf);

    Error1:
        SetCursor(hcurOld);

        return FALSE;
    }


RWICOCUR.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: rwicocur.c
    *
    * Routines to read and write icon and cursor files.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"

    #include <io.h>
    #include <fcntl.h>                          // For NT fstat().
    #include <sys\types.h>                      // For fstat() types.
    #include <sys\stat.h>                       // For fstat() function.

    /************************************************************************
    * LoadIconCursorFile
    *
    * Loads the specified icon or cursor file.  It reads the images into
    * a list, then prompts for which one to open initially.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    BOOL LoadIconCursorFile(
        PSTR pszFullFileName,
        BOOL fIcon)
    {
        HFILE hf;
        INT i;
        PIMAGEINFO pImage;
        LPBITMAPINFO lpBitmapInfo;
        HANDLE hDIB;                    // Handle to DIB bits.
        OFSTRUCT OfStruct;
        struct stat FileStatus;
        ICOCURSORHDR hdr;               // Header structure of icon/cursor file.
        INT nImages;
        PICOCURSORDESC aIcoCurDesc;     // Array of ico/cur descriptors.
        DWORD dwFilePos;
        DWORD dwFileSize;
        INT iType;

        if ((hf = (HFILE)OpenFile(pszFullFileName, (LPOFSTRUCT)&OfStruct, OF_READ))
                == (HFILE)-1) {
            Message(MSG_CANTOPEN, pszFullFileName);
            return FALSE;
        }

        fstat((INT)_open_osfhandle((long)(hf), (int)(O_RDONLY)), &FileStatus);
        dwFileSize = (DWORD)FileStatus.st_size;

        ImageLinkFreeList();

        if (fIcon)
            iType = FT_ICON;
        else
            iType = FT_CURSOR;

        /*
         * Read the Icon/Cursor File header.
         */
        if (!MyFileRead(hf, (LPSTR)&hdr, sizeof(ICOCURSORHDR),
                pszFullFileName, iType))
            goto Error1;

        if (hdr.iReserved != 0) {
            Message(MSG_BADICOCURFILE, pszFullFileName);
            goto Error1;
        }

        /*
         * Get number of images in the file.
         */
        nImages = hdr.iResourceCount;

        if (!nImages || nImages > MAXIMAGES) {
            Message(MSG_BADICOCURFILE, pszFullFileName);
            goto Error1;
        }

        if (hdr.iResourceType != 1 && hdr.iResourceType != 2) {
            Message(MSG_BADICOCURFILE, pszFullFileName);
            goto Error1;
        }

        /*
         * Allocate room for the descriptor records.
         */
        if (!(aIcoCurDesc = (PICOCURSORDESC)MyAlloc(
                sizeof(ICOCURSORDESC) * nImages)))
            goto Error1;

        /*
         * Read in the descriptor records.
         */
        if (!MyFileRead(hf, (LPSTR)aIcoCurDesc, sizeof(ICOCURSORDESC) * nImages,
                pszFullFileName, iType))
            goto Error2;

        /*
         * Get the current file position (after the descriptors).  This
         * should be the start of the DIB's.
         */
        dwFilePos = (DWORD)SetFilePointer((HANDLE)hf, 0, NULL, (DWORD)1);

        /*
         * Validate the descriptor records.
         */
        for (i = 0; i < nImages; i++) {
            /*
             * Make sure the DIB's are sequential (not overlapping)
             * and they all fit within the file.
             */
            if (aIcoCurDesc[i].DIBOffset != dwFilePos ||
                    dwFilePos + aIcoCurDesc[i].DIBSize > dwFileSize) {
                Message(MSG_BADICOCURFILE, pszFullFileName);
                goto Error2;
            }

            /*
             * Jump to the next DIB.
             */
            dwFilePos += aIcoCurDesc[i].DIBSize;
        }

        for (i = 0; i < nImages; i++) {
            pImage = ImageLinkAlloc(NULL, 0, 0,
                    aIcoCurDesc[i].iHotspotX, aIcoCurDesc[i].iHotspotY,
                    (aIcoCurDesc[i].iColorCount == (BYTE)8) ?
                    aIcoCurDesc[i].iColorCount : 0);

            if (!pImage)
                goto Error3;

            /*
             * Allocate space for the DIB for this image.
             */
            if (!(hDIB = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
                    (DWORD)aIcoCurDesc[i].DIBSize))) {
                Message(MSG_OUTOFMEMORY);
                goto Error3;
            }

            pImage->DIBSize = aIcoCurDesc[i].DIBSize;
            pImage->DIBhandle = hDIB;
            pImage->DIBPtr = (LPSTR)GlobalLock(hDIB);
        }

        for (pImage = gpImageHead; pImage != NULL; pImage = pImage->pImageNext) {
            if (!MyFileRead(hf, pImage->DIBPtr, (DWORD)pImage->DIBSize,
                    pszFullFileName, iType))
                goto Error3;

            lpBitmapInfo = (LPBITMAPINFO)pImage->DIBPtr;

            if (!IsValidDIB(lpBitmapInfo, pImage->DIBSize, TRUE)) {
                Message(MSG_BADICOCURFILE, pszFullFileName);
                goto Error3;
            }

            /*
             * Fill the x and y size fields in image node from
             * information in the DIB header.
             */
            pImage->cx = (INT)lpBitmapInfo->bmiHeader.biWidth;
            pImage->cy = (INT)lpBitmapInfo->bmiHeader.biHeight / 2;
            if (pImage->nColors == 0)
                pImage->nColors = (1 << lpBitmapInfo->bmiHeader.biBitCount);

            pImage->pDevice = DeviceLinkFind(
                    fIcon ? gpIconDeviceHead : gpCursorDeviceHead,
                    pImage->nColors, pImage->cx, pImage->cy);
        }

        _lclose((HFILE)hf);

        fFileDirty = FALSE;
        SetFileName(pszFullFileName);
        giType = iType;

        gnImages = nImages;

        /*
         * Update the PropBar and the Toolbox so that they show
         * information about the opened file.  We do this now just
         * in case the user cancels out of the Image Select Dialog.
         */
        PropBarUpdate();
        ToolboxUpdate();

        /*
         * Open up an image.  If there are multiple images in the file,
         * show the Image Select dialog.  We also show the Image Select
         * dialog if the file only has one image but it is not for a known
         * device.
         */
        if (gnImages > 1 || !gpImageHead->pDevice)
            ImageSelectDialog();
        else
            ImageOpen2(gpImageHead);

        return TRUE;

    Error3:
        ImageLinkFreeList();

    Error2:
        MyFree(aIcoCurDesc);

    Error1:
        _lclose((HFILE)hf);

        return FALSE;
    }

    /************************************************************************
    * IsValidDIB
    *
    * This function determines if the given DIB is valid or not.  It does
    * this without touching memory outside the bounds of the cbDIBSize
    * passed in or the size of a BITMAPINFOHEADER, whichever is smaller.
    * Note that even if the DIB is valid, however, the current image
    * editor might not be able to edit it (the size might be too big, for
    * instance).
    *
    * Arguments:
    *   LPBITMAPINFO pDIB - Points to the DIB.
    *   DWORD cbDIBSize   - The size of the DIB.
    *   BOOL fIcoCur      - TRUE if this is an icon or cursor.  This effects
    *                       whether an AND mask is expected to be in the DIB.
    *
    * History:
    *
    ************************************************************************/

    BOOL IsValidDIB(
        LPBITMAPINFO pDIB,
        DWORD cbDIBSize,
        BOOL fIcoCur)
    {
        DWORD cbANDMask;
        DWORD cbXORMask;
        DWORD cbColorTable;
        DWORD nHeight;

        if (cbDIBSize < sizeof(BITMAPINFOHEADER))
            return FALSE;

        if (pDIB->bmiHeader.biSize != sizeof(BITMAPINFOHEADER))
            return FALSE;

        if (pDIB->bmiHeader.biPlanes != 1)
            return FALSE;

        if (pDIB->bmiHeader.biBitCount != 1 &&
                pDIB->bmiHeader.biBitCount != 4 &&
                pDIB->bmiHeader.biBitCount != 8 &&
                pDIB->bmiHeader.biBitCount != 24)
            return FALSE;

        if (fIcoCur) {
            nHeight = pDIB->bmiHeader.biHeight / 2;
            cbANDMask = (((pDIB->bmiHeader.biWidth + 31) & 0xffffffe0) >> 3) *
                    nHeight;
        }
        else {
            nHeight = pDIB->bmiHeader.biHeight;
            cbANDMask = 0;
        }

        cbColorTable = (1 << pDIB->bmiHeader.biBitCount) * sizeof(RGBQUAD);
        cbXORMask = ((((pDIB->bmiHeader.biWidth * pDIB->bmiHeader.biBitCount) +
                31) & 0xffffffe0) >> 3) * nHeight;

        /*
         * Check the size field in the header.  This must be either zero
         * or a valid size.
         */
        if (pDIB->bmiHeader.biSizeImage &&
                pDIB->bmiHeader.biSizeImage != cbXORMask + cbANDMask)
            return FALSE;

        if (cbDIBSize != sizeof(BITMAPINFOHEADER) + cbColorTable +
                cbXORMask + cbANDMask)
            return FALSE;

        return TRUE;
    }

    /************************************************************************
    * SaveIconCursorFile
    *
    *
    *
    * Arguments:
    *
    * Returns:
    *   TRUE if successful, FALSE otherwise.
    *
    * History:
    *
    ************************************************************************/

    BOOL SaveIconCursorFile(
        PSTR pszFullFileName,
        INT iType)
    {
        ICOCURSORHDR IcoCurHdr;     // Header structure of icon/cursor file.
        ICOCURSORDESC IcoCurDesc;   // Icon/cursor descriptor struct.
        HCURSOR hcurOld;            // Handle to old cursor.
        PIMAGEINFO pImage;          // Pointer to node in image list.
        DWORD iBitsOffset;          // Offset of the actual DIB bits for image.
        HFILE hf;
        OFSTRUCT OfStruct;

        hcurOld = SetCursor(hcurWait);

        /*
         * Save the bits of the current image.
         */
        ImageSave();

        /*
         * Open the file for writing.
         */
        if ((hf = (HFILE)OpenFile(pszFullFileName, &OfStruct, OF_CREATE | OF_READWRITE))
                == (HFILE)-1) {
            Message(MSG_CANTCREATE, pszFullFileName);
            goto Error1;
        }

        /*
         * This is crucial since this helps distinguish a 3.0 icon/cursor
         * from an old, old (2.1 format) icon/cursor, which has meaningful
         * information in this WORD.
         */
        IcoCurHdr.iReserved = (WORD)0;

        if (iType == FT_ICON)
            IcoCurHdr.iResourceType = 1;        // Icon type.
        else
            IcoCurHdr.iResourceType = 2;        // Cursor type.

        IcoCurHdr.iResourceCount = (WORD)gnImages;

        /*
         * Write the header to disk.
         */
        if (!MyFileWrite(hf, (LPSTR)&IcoCurHdr, sizeof(ICOCURSORHDR),
                pszFullFileName))
            goto Error2;

        /*
         * Write all the descriptors.
         */
        iBitsOffset = sizeof(ICOCURSORHDR) + (gnImages * sizeof(ICOCURSORDESC));
        for (pImage = gpImageHead; pImage; pImage = pImage->pImageNext) {
            IcoCurDesc.iWidth = (BYTE)pImage->cx;
            IcoCurDesc.iHeight = (BYTE)pImage->cy;
            IcoCurDesc.iColorCount = (giType == FT_ICON) ?
                    (BYTE)pImage->nColors : (BYTE)0;
            IcoCurDesc.iUnused = 0;
            IcoCurDesc.iHotspotX = (WORD)pImage->iHotspotX;
            IcoCurDesc.iHotspotY = (WORD)pImage->iHotspotY;
            IcoCurDesc.DIBSize = pImage->DIBSize;
            IcoCurDesc.DIBOffset = iBitsOffset;

            if (!MyFileWrite(hf, (LPSTR)&IcoCurDesc, sizeof(ICOCURSORDESC),
                    pszFullFileName))
                goto Error2;

            iBitsOffset += IcoCurDesc.DIBSize;
        }

        /*
         * Now write the DIB's.
         */
        for (pImage = gpImageHead; pImage; pImage = pImage->pImageNext) {
            if (!MyFileWrite(hf, (LPSTR)pImage->DIBPtr,
                    (DWORD)pImage->DIBSize, pszFullFileName))
                goto Error2;
        }

        _lclose((HFILE)hf);

        fFileDirty = FALSE;
        SetFileName(pszFullFileName);

        SetCursor(hcurOld);

        return TRUE;

    Error2:
        _lclose((HFILE)hf);

    Error1:
        SetCursor(hcurOld);

        return FALSE;
    }


RWPAL.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: rwpal.c
    *
    * Routines for reading and writing color palette files.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"

    #include <stdio.h>
    #include <fcntl.h>
    #include <io.h>
    #include <sys\types.h>                      // For fstat() types.
    #include <sys\stat.h>                       // For fstat() function.

    /*
     * The color palette is saved in a .PAL file.  This file consists
     * of a header followed by the colors.
     *
     * The header has the following format:
     *
     * struct {
     *     CHAR tag;                // Always 'C'.
     *     WORD colors;             // Number of colors.  Always COLORSMAX.
     *     CHAR reserved[47];       // Reserved bytes.
     * }
     *
     * Immediately following this is RGB quads for each of the colors in
     * the palette.
     */

    /*
     * Size in bytes of the header of a color palette file.
     */
    #define CBCOLORHDR          (sizeof(CHAR) + sizeof(WORD) + 47)

    /*
     * Size in bytes of the color information in the color file.
     */
    #define CBCOLORINFO         (sizeof(DWORD) * COLORSMAX)

    /*
     * Size in bytes of a color palette file.  This includes the
     * size of the header and room for all the colors.
     */
    #define CBCOLORFILE         (CBCOLORHDR + CBCOLORINFO)

    /************************************************************************
    * LoadColorFile
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID LoadColorFile(VOID)
    {
        HFILE hf;
        OFSTRUCT OfStruct;
        struct stat FileStatus;
        DWORD argb[COLORSMAX];
        UINT cbRead;
        INT i;
        CHAR tag;
        CHAR szFileName[CCHMAXPATH];

        *szFileName = '\0';
        if (!OpenDlg(szFileName, FT_PALETTE))
            return;

  if ((hf = (HFILE)OpenFile(szFileName, (LPOFSTRUCT)&OfStruct, OF_READ))
                == (HFILE)-1) {
            Message(MSG_CANTOPEN, szFileName);
            return;
        }

        fstat((INT)_open_osfhandle((long)(hf), (int)(O_RDONLY)), &FileStatus);

        if (FileStatus.st_size != CBCOLORFILE) {
            Message(MSG_BADPALFILE, szFileName);
            goto Error1;
        }

        if ((cbRead = _lread((HFILE)hf, &tag, 1)) == -1 || cbRead != 1) {
            Message(MSG_READERROR, szFileName);
            goto Error1;
        }

        if (tag != 'C') {
            Message(MSG_BADPALFILE, szFileName);
            goto Error1;
        }

        SetFilePointer((HANDLE)hf, CBCOLORHDR, NULL, (DWORD)0);
        if ((cbRead = _lread((HFILE)hf, (LPSTR)argb, CBCOLORINFO)) == -1 ||
                cbRead != CBCOLORINFO) {
            Message(MSG_READERROR, szFileName);
            goto Error1;
        }

        for (i = 0; i < COLORSMAX; i++)
            gargbColor[i] = argb[i];

        SetColorPalette(16, giType, TRUE);

    Error1:
        _lclose((HFILE)hf);
    }

    /************************************************************************
    * SaveColorFile
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID SaveColorFile(VOID)
    {
        INT i;
        HFILE hf;
        OFSTRUCT OfStruct;
        CHAR reserved[47];
        WORD wColors = COLORSMAX;
        CHAR tag = 'C';
        CHAR szFileName[CCHMAXPATH];

        *szFileName = '\0';
        if (!SaveAsDlg(szFileName, FT_PALETTE))
            return;

        if ((hf = (HFILE)OpenFile(szFileName, &OfStruct,
                OF_CREATE | OF_WRITE)) == (HFILE)-1) {
            Message(MSG_CANTCREATE, szFileName);
            return;
        }

        for (i = 0; i < sizeof(reserved); i++)
            reserved[i] = 0;

        if (_lwrite((HFILE)hf, (LPSTR)&tag, sizeof(tag)) != sizeof(tag) ||
                _lwrite((HFILE)hf, (LPSTR)&wColors, sizeof(wColors)) !=
                sizeof(wColors) ||
                _lwrite((HFILE)hf, (LPSTR)reserved, sizeof(reserved)) !=
                sizeof(reserved) ||
                _lwrite((HFILE)hf, (LPSTR)gargbColor, CBCOLORINFO) != CBCOLORINFO) {
            Message(MSG_WRITEERROR, szFileName);
        }

        _lclose((HFILE)hf);
    }


TOOLBOX.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: toolbox.c
    *
    * Contains routines that handle the toolbox.
    *
    * History:
    *
    * 04/30/91 - Copied from DlgEdit.
    *
    ****************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"

 #include <windowsx.h>

    #define TOOLBOXCOLUMNS  2       // Columns in the Toolbox.

    /*
     * Style of the toolbox window.
     */
    #define TOOLBOXSTYLE    (WS_POPUP | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU)

    STATICFN VOID NEAR ToolboxDrawBitmap(HDC hDC, INT tool);

    /*
     * Dimensions of a tool button bitmap.
     */
    static INT cxToolBtn;
    static INT cyToolBtn;

    /*
     * Index to the last available tool.  If tools at the end of the
     * toolbox are disabled, this number gets lowered.
     */
    static INT iToolLast = CTOOLS - 1;

    /****************************************************************************
    * ToolboxCreate
    *
    * This function creates the toolbox window.
    *
    * History:
    *
    ****************************************************************************/

    VOID ToolboxCreate(VOID)
    {
        BITMAP bmp;
        INT i;
        INT x;
        INT y;
        INT cx;
        INT cy;
        INT cxDummy;
        INT cyDummy;
        RECT rc;
        RECT rcClient;
        POINT pt;
        BOOL fMaximized;

        /*
         * Load the bitmaps.
         */
        for (i = 0; i < CTOOLS; i++) {
            if (!(gaTools[i].hbmToolBtnUp = LoadBitmap(ghInst,
                    MAKEINTRESOURCE(gaTools[i].idbmToolBtnUp))))
                return;

            if (!(gaTools[i].hbmToolBtnDown = LoadBitmap(ghInst,
                    MAKEINTRESOURCE(gaTools[i].idbmToolBtnDown))))
                return;
        }

        /*
         * Get the dimensions of the tool button bitmaps.
         */
        GetObject(gaTools[0].hbmToolBtnUp, sizeof(BITMAP), (PSTR)&bmp);
        cxToolBtn = bmp.bmWidth;
        cyToolBtn = bmp.bmHeight;

        /*
         * Calculate the required window size for the client area
         * size we want.  The size leaves room for a margin, and
         * assumes that adjacent buttons overlap their borders by
         * one pixel.
         */
        rc.left = 0;
        rc.top = 0;
        rc.right = PALETTEMARGIN + ((cxToolBtn - 1) * 2) + 1 + PALETTEMARGIN;
        rc.bottom = PALETTEMARGIN + ((cyToolBtn - 1) *
                (CTOOLS / 2)) + 1 + PALETTEMARGIN;
        AdjustWindowRect(&rc, TOOLBOXSTYLE, FALSE);
        cx = rc.right - rc.left;
        cy = rc.bottom - rc.top;

        /*
         * Get the saved position of the Toolbox.  Note that we throw away
         * the size fields, because we just calculated the required size.
         */
        if (!ReadWindowPos(szTBPos, &x, &y, &cxDummy, &cyDummy, &fMaximized)) {
            /*
             * The previous position of the Toolbox couldn't be found.
             * Position the toolbox to the upper right corner of the
             * client area of the editor, but make sure it is completely
             * visible.
             */
            GetClientRect(ghwndMain, &rcClient);
            pt.x = rcClient.right - cx - (2 * PALETTEMARGIN);
            pt.y = rcClient.top + gcyPropBar + (2 * PALETTEMARGIN);
            ClientToScreen(ghwndMain, &pt);
            SetRect(&rc, pt.x, pt.y, pt.x + cx, pt.y + cy);
            FitRectToScreen(&rc);
            x = rc.left;
            y = rc.top;
        }

        if (!(ghwndToolbox = CreateWindow(szToolboxClass, NULL, TOOLBOXSTYLE,
                x, y, cx, cy, ghwndMain, NULL, ghInst, NULL)))
            return;

        /*
         * Create the buttons.
         */
        x = PALETTEMARGIN;
        y = PALETTEMARGIN;
        for (i = 0; i < CTOOLS; i++) {
            CreateWindow(szToolBtnClass, NULL,
                    WS_CHILD | WS_VISIBLE,
                    x, y, cxToolBtn, cyToolBtn,
                    ghwndToolbox, (HMENU)i, ghInst, NULL);

            if (x == PALETTEMARGIN) {
                x += cxToolBtn - 1;
            }
            else {
                x = PALETTEMARGIN;
                y += cyToolBtn - 1;
            }
        }

        ToolboxUpdate();
    }

    /****************************************************************************
    * ToolboxShow
    *
    * This function shows or hides the toolbox window.
    *
    * History:
    *
    ****************************************************************************/

    VOID ToolboxShow(
        BOOL fShow)
    {
        if (fShow)
            ShowWindow(ghwndToolbox, SW_SHOWNA);
        else
            ShowWindow(ghwndToolbox, SW_HIDE);
    }

    /****************************************************************************
    * ToolboxUpdate
    *
    * This function updates the toolbox.  It should be called any time that
    * a new file is opened/created for editing.
    *
    * History:
    *
    ****************************************************************************/

    VOID ToolboxUpdate(VOID)
    {
        if (!ghwndToolbox)
            return;

        if (giType == FT_CURSOR) {
            ShowWindow(GetDlgItem(ghwndToolbox, TOOL_HOTSPOT), SW_SHOWNA);
            iToolLast = CTOOLS - 1;
        }
        else {
            ShowWindow(GetDlgItem(ghwndToolbox, TOOL_HOTSPOT), SW_HIDE);
            iToolLast = TOOL_HOTSPOT - 1;
            if (gCurTool == TOOL_HOTSPOT)
                ToolboxSelectTool(TOOL_FIRST);
        }
    }

    /****************************************************************************
    * ToolboxWndProc
    *
    * This is the window procedure for the toolbox window.
    *
    * History:
    *
    ****************************************************************************/

    WINDOWPROC ToolboxWndProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_CREATE:
                {
                    HMENU hmenu = GetSystemMenu(hwnd, FALSE);

                    RemoveMenu(hmenu, 7, MF_BYPOSITION);    // Second separator.
                    RemoveMenu(hmenu, 5, MF_BYPOSITION);    // First separator.

                    RemoveMenu(hmenu, SC_RESTORE, MF_BYCOMMAND);
                    RemoveMenu(hmenu, SC_SIZE, MF_BYCOMMAND);
                    RemoveMenu(hmenu, SC_MINIMIZE, MF_BYCOMMAND);
                    RemoveMenu(hmenu, SC_MAXIMIZE, MF_BYCOMMAND);
                    RemoveMenu(hmenu, SC_TASKLIST, MF_BYCOMMAND);
                }

                return 0;

            case WM_KEYDOWN:
                {
                    INT iToolNext;

                    switch (wParam) {
                        case VK_UP:
                            if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                    (GetKeyState(VK_CONTROL) & 0x8000))
                                break;

                            /*
                             * Go up a row, but don't go beyond the top.
                             */
                            iToolNext = gCurTool - TOOLBOXCOLUMNS;
                            if (iToolNext < 0)
                                break;

                            ToolboxSelectTool(iToolNext);

                            break;

                        case VK_DOWN:
                            if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                    (GetKeyState(VK_CONTROL) & 0x8000))
                                break;

                            /*
                             * Go down a row, but don't go beyond the bottom.
                             */
                            iToolNext = gCurTool + TOOLBOXCOLUMNS;
                            if (iToolNext > iToolLast)
                                break;

                            ToolboxSelectTool(iToolNext);

                            break;

                        case VK_LEFT:
                            if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                    (GetKeyState(VK_CONTROL) & 0x8000))
                                break;

                            /*
                             * Already at left edge.
                             */
                            if (!(gCurTool % TOOLBOXCOLUMNS))
                                break;

                            /*
                             * Go left a column.
                             */
                            ToolboxSelectTool(gCurTool - 1);

                            break;

                        case VK_RIGHT:
                            if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                    (GetKeyState(VK_CONTROL) & 0x8000))
                                break;

                            /*
                             * Already at right edge.
                             */
                            if ((gCurTool % TOOLBOXCOLUMNS) == TOOLBOXCOLUMNS - 1)
                                break;

                            /*
                             * Don't go off the end of the available tools.
                             */
                            if (gCurTool + 1 > iToolLast)
                                break;

                            /*
                             * Go right a column.
                             */
                            ToolboxSelectTool(gCurTool + 1);

                            break;

                        case VK_TAB:
                            if (GetKeyState(VK_CONTROL) & 0x8000)
                                break;

                            /*
                             * Is the shift key pressed also?
                             */
                            if (GetKeyState(VK_SHIFT) & 0x8000) {
                                if (gCurTool == TOOL_FIRST)
                                    iToolNext = iToolLast;
                                else
                                    iToolNext = gCurTool - 1;
                            }
                            else {
                                if (gCurTool == iToolLast)
                                    iToolNext = TOOL_FIRST;
                                else
                                    iToolNext = gCurTool + 1;
                            }

                            ToolboxSelectTool(iToolNext);

                            break;

                        case VK_END:
                            if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                    (GetKeyState(VK_CONTROL) & 0x8000))
                                break;

                            ToolboxSelectTool(iToolLast);

                            break;

                        case VK_HOME:
                        case VK_ESCAPE:
                            if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                    (GetKeyState(VK_CONTROL) & 0x8000))
                                break;

                            ToolboxSelectTool(TOOL_FIRST);

                            break;
                    }
                }

                break;

            case WM_ACTIVATE:
                if (GET_WM_ACTIVATE_STATE(wParam, lParam))
                    gidCurrentDlg = DID_TOOLBOX;

                break;

            case  WM_PAINT:
                {
                    HDC hdc;
                    PAINTSTRUCT ps;

                    hdc = BeginPaint(hwnd, &ps);
                    DrawMarginBorder(hwnd, hdc);
                    EndPaint(hwnd, &ps);
                }

                break;

            case WM_CLOSE:
                /*
                 * The user closed the toolbox from the system menu.
                 * Hide the toolbox (we don't actually destroy it so
                 * that it will appear in the same spot when they show
                 * it again).
                 */
                ToolboxShow(FALSE);
                gfShowToolbox = FALSE;
                break;

            case WM_DESTROY:
                {
                    INT i;
                    RECT rc;

                    for (i = 0; i < CTOOLS; i++) {
                        DeleteObject(gaTools[i].hbmToolBtnUp);
                        gaTools[i].hbmToolBtnUp = NULL;
                        DeleteObject(gaTools[i].hbmToolBtnDown);
                        gaTools[i].hbmToolBtnDown = NULL;
                    }

                    /*
                     * Save the position of the toolbox.
                     */
                    GetWindowRect(hwnd, &rc);
                    WriteWindowPos(&rc, FALSE, szTBPos);

                    /*
                     * Null out the global window handle for the toolbox
                     * for safety's sake.
                     */
                    ghwndToolbox = NULL;
                }

                break;

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

        return 0;
    }

    /****************************************************************************
    * ToolBtnWndProc
    *
    * This is the window procedure for the buttons in the toolbox window.
    *
    * History:
    *
    ****************************************************************************/

    WINDOWPROC ToolBtnWndProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_LBUTTONDOWN:
                /*
                 * Select the tool that was clicked on.
                 */
                ToolboxSelectTool((UINT)GetWindowLong((hwnd), GWL_ID));

                break;

            case WM_PAINT:
                {
                    HDC hDC;
                    PAINTSTRUCT ps;

                    hDC = BeginPaint(hwnd, &ps);
                    ToolboxDrawBitmap(hDC, (UINT)GetWindowLong((hwnd), GWL_ID));
                    EndPaint(hwnd, &ps);
                }

                break;

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

        return 0;
    }

    /****************************************************************************
    * ToolboxDrawBitmap
    *
    *
    * History:
    *
    ****************************************************************************/

    STATICFN VOID NEAR ToolboxDrawBitmap(
        HDC hDC,
        INT tool)
    {
        HDC hMemDC;
        HBITMAP hbmOld;

        /*
         * Draw the image.
         */
        hMemDC = CreateCompatibleDC(hDC);
        hbmOld = SelectObject(hMemDC, (tool == gCurTool) ?
                gaTools[tool].hbmToolBtnDown : gaTools[tool].hbmToolBtnUp);
        BitBlt(hDC, 0, 0, cxToolBtn, cyToolBtn, hMemDC, 0, 0, SRCCOPY);
        SelectObject(hMemDC, hbmOld);
        DeleteDC(hMemDC);
    }

    /****************************************************************************
    * ToolboxSelectTool
    *
    *
    * History:
    *
    ****************************************************************************/

    VOID ToolboxSelectTool(
        INT tool)
    {
        if (gCurTool != tool) {
            if (ghwndToolbox) {
                InvalidateRect(GetDlgItem(ghwndToolbox, gCurTool), NULL, FALSE);            InvalidateRect(GetDlgItem(ghwndToolbox, tool), NULL, FALSE);
            }

            gCurTool = tool;
            gpfnDrawProc = gaTools[gCurTool].pfnDrawProc;
        }
    }


UTIL.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: util.c
    *
    * Contains miscellaneous utility functions for ImagEdit.
    *
    * History:
    *
    ****************************************************************************/

    #include "imagedit.h"

    #include <stdio.h>
    #include <stdarg.h>

    #define CBOVERHEAD      (sizeof(INT)+sizeof(INT)+sizeof(INT))
    #define MEMSIGHEAD      0x1234
    #define MEMSIGTAIL      0x5678

    /****************************************************************************
    * MyAlloc
    *
    *
    *
    * History:
    *  25-Jul-1989   - Created
    ****************************************************************************/

    VOID *MyAlloc(
        INT cbAlloc)
    {
        register HANDLE hMem;

        if (hMem = LocalAlloc(LMEM_FIXED, cbAlloc)) {
            return (VOID *)hMem;
        }
        else {
            MessageBeep(0);
            Message(MSG_OUTOFMEMORY);

            return NULL;
        }
    }

    /****************************************************************************
    * MyRealloc
    *
    *
    *
    * History:
    *  25-Jul-1989   - Created
    ****************************************************************************/

    VOID *MyRealloc(
        VOID *npMem,
        INT cbNewAlloc)
    {
        npMem = (VOID *)LocalReAlloc((HANDLE)npMem, cbNewAlloc, LMEM_MOVEABLE);

        if (!npMem) {
            MessageBeep(0);
            Message(MSG_OUTOFMEMORY);

            return NULL;
        }

        return npMem;
    }

    /****************************************************************************
    * MyFree
    *
    *
    * History:
    *  25-Jul-1989   - Created
    ****************************************************************************/

    VOID *MyFree(
        VOID *npMem)
    {
        if (LocalFree((HANDLE)npMem)) {
            MessageBeep(0);
            Message(MSG_MEMERROR);

            return npMem;
        }

        return NULL;
    }

    /************************************************************************
    * Message
    *
    * This function puts up a message box.  The message is described in
    * the gamdMessages table.
    *
    * Arguments:
    *   UINT idMsg - Index to the message.
    *   ...        - Optional arguments.
    *
    * Returns:
    *     What MessageBox returns.
    *
    ************************************************************************/

    INT Message(
        UINT idMsg,
        ...)
    {
        va_list marker;
        INT RetCode;
        CHAR szT[CCHTEXTMAX];

        va_start(marker, idMsg);
        vsprintf(szT, ids(gamdMessages[idMsg].ids), marker);

        RetCode = MessageBox(NULL, szT, ids(IDS_PGMTITLE),
                gamdMessages[idMsg].fMessageBox | MB_TASKMODAL);

        va_end(marker);

        return RetCode;
    }

    /************************************************************************
    * CenterWindow
    *
    * This function centers the given window over its owner.  It ensures
    * that the window is entirely within the visible screen, however.
    * If the window does not have an owner, it is centered over the
    * desktop.
    *
    * Arguments:
    *   HWND hwnd - The window to center.
    *
    * History:
    *
    ************************************************************************/

    VOID CenterWindow(
        HWND hwnd)
    {
        RECT rc;
        RECT rcOwner;
        RECT rcCenter;
        HWND hwndOwner;

        GetWindowRect(hwnd, &rc);

        if (!(hwndOwner = GetWindow(hwnd, GW_OWNER)))
            hwndOwner = GetDesktopWindow();

        GetWindowRect(hwndOwner, &rcOwner);

        /*
         *  Calculate the starting x,y for the new
         *  window so that it would be centered.
         */
        rcCenter.left = rcOwner.left +
                (((rcOwner.right - rcOwner.left) -
                (rc.right - rc.left))
                / 2);

        rcCenter.top = rcOwner.top +
                (((rcOwner.bottom - rcOwner.top) -
                (rc.bottom - rc.top))
                / 2);

        rcCenter.right = rcCenter.left + (rc.right - rc.left);
        rcCenter.bottom = rcCenter.top + (rc.bottom - rc.top);

        FitRectToScreen(&rcCenter);

        SetWindowPos(hwnd, NULL, rcCenter.left, rcCenter.top, 0, 0,
                SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
    }

    /************************************************************************
    * FitRectToScreen
    *
    * This function ensures that the given rectangle is entirely within
    * the visible screen, adjusting it if necessary.
    *
    * Arguments:
    *   PRECT prc - The rectangle.
    *
    * History:
    *
    ************************************************************************/

    VOID FitRectToScreen(
        PRECT prc)
    {
        INT cxScreen;
        INT cyScreen;
        INT delta;

        cxScreen = GetSystemMetrics(SM_CXSCREEN);
        cyScreen = GetSystemMetrics(SM_CYSCREEN);

        if (prc->right > cxScreen) {
            delta = prc->right - prc->left;
            prc->right = cxScreen;
            prc->left = prc->right - delta;
        }

        if (prc->left < 0) {
            delta = prc->right - prc->left;
            prc->left = 0;
            prc->right = prc->left + delta;
        }

        if (prc->bottom > cyScreen) {
            delta = prc->bottom - prc->top;
            prc->bottom = cyScreen;
            prc->top = prc->bottom - delta;
        }

        if (prc->top < 0) {
            delta = prc->bottom - prc->top;
            prc->top = 0;
            prc->bottom = prc->top + delta;
        }
    }

    /************************************************************************
    * ids
    *
    * This function will return a string, given the string id.  If this is
    * the first time that the string has been retrieved, memory will be
    * allocated for it and it will be loaded.  After it is loaded once, it
    * is then cached in a PSTR array and is available for later without
    * having to load it again.
    *
    * Arguments:
    *   UINT idString - String ID of the string to retrieve.
    *
    * History:
    *
    ************************************************************************/

    PSTR ids(
        UINT idString)
    {
        static PSTR apstr[CSTRINGS];        // String resource array cache.
        PSTR pstr;
        INT cch;

        if (apstr[idString])
            return apstr[idString];

        if (!(pstr = MyAlloc(CCHTEXTMAX)))
            return "";

        if (!(cch = LoadString(ghInst, idString, pstr, CCHTEXTMAX))) {
            MyFree(pstr);
            return "";
        }

        apstr[idString] = pstr = MyRealloc(pstr, cch + 1);

        return (pstr ? pstr : "");
    }

    /************************************************************************
    * MyCreateBitmap
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    HBITMAP MyCreateBitmap(
        HDC hdc,
        INT cx,
        INT cy,
        INT nColors)
    {
        BITMAPINFOHEADER bmih;

        if (nColors == 2) {
            return CreateBitmap(cx, cy, 1, 1, NULL);
        }
        else {
            bmih.biSize = sizeof(BITMAPINFOHEADER);
            bmih.biWidth = cx;
            bmih.biHeight = cy;
            bmih.biPlanes = 1;              // 1 plane, 4 bpp is
            bmih.biBitCount = 4;            // 16 colors.

            bmih.biCompression =
            bmih.biSizeImage =
            bmih.biXPelsPerMeter =
            bmih.biYPelsPerMeter =
            bmih.biClrUsed =
            bmih.biClrImportant = 0;

            return CreateDIBitmap(hdc, &bmih, 0L, NULL, NULL, 0);
        }
    }

    #if defined(DBG) && defined(WIN16)
    /****************************************************************************
    * DBGStackReport
    *
    * This debugging function reports how much stack is used by a program.
    * To use it, call it with fInit == TRUE right at the beginning of the
    * program and then with fInit == FALSE just before the program exits.
    * The stack space used during the current run of the program will be
    * displayed on the debug terminal.
    *
    * It is implemented by filling the stack with a certain value down to
    * the bottom of the stack (if fInit is TRUE).  When it is called with
    * fInit == FALSE, it starts at the bottom of the stack and looks for
    * where this "signature" value has been overwritten with data, then
    * does a little math to compute the used stack.
    *
    * Arguments:
    *   BOOL fInit - TRUE if the stack should be initialized, FALSE to
    *                print out the report.
    *
    * History:
    *  28-Aug-1990  - Created
    ****************************************************************************/

    /*
     * This signature byte will be used to fill the stack.
     */
    #define STACKSIG    'A'

    /*
     * This is a C runtime global that is always at the very end of the
     * global data.  Taking its address is a way that the "bottom" of the
     * stack can be found.
     */
    extern CHAR end;

    VOID DBGStackReport(
        BOOL fInit)
    {
        static PBYTE pbStackTop;
        PBYTE pb;
        BYTE bDummy;

        if (fInit) {
            /*
             * The address of one of this functions local variables is
             * taken and considered the "top" of the stack.  This means
             * that it will work best when it is called first thing in
             * the program.
             */
            pbStackTop = pb = &bDummy;

            /*
             * Fill the stack up.
             */
            while (pb > &end)
                *pb-- = STACKSIG;
        }
        else {
            /*
             * Start at the bottom of the stack and search upwards.
             */
            pb = &end;
            while (*(++pb) == STACKSIG && pb < pbStackTop)
                ;

            /*
             * Display the results.
             */
            DBGprintf("ImagEdit stack used: %d bytes.", pbStackTop - pb);
        }
    }
    #endif

    #ifdef DBG
    /****************************************************************************
    * DBGBltImage
    *
    * This debugging function blits out the given image in the specified
    * DC to the screen.  Every time that it is called, it will blit the
    * image to the right of the last one, starting at the top of the
    * screen.  It assumes that each image is 32x32 pixels.
    *
    * Arguments:
    *   HDC hdc - The DC with the image to blit.  If this is NULL, the
    *             current XOR and AND images are blit'd, with the AND
    *             image below the XOR image.
    *
    * History:
    *  16-Sep-1991  - Created
    ****************************************************************************/

    VOID DBGBltImage(
        HDC hdc)
    {
        static INT x;
        HDC hdcScreen;

        hdcScreen = GetDC(NULL);

        if (hdc) {
            BitBlt(hdcScreen, x, 0, 32, 32, hdc, 0, 0, SRCCOPY);
        }
        else {
            BitBlt(hdcScreen, x, 0, 32, 32, ghdcImage, 0, 0, SRCCOPY);
            BitBlt(hdcScreen, x, 32 + 1, 32, 32, ghdcANDMask, 0, 0, SRCCOPY);
        }

        ReleaseDC(NULL, hdcScreen);
        x += 32 + 1;
    }

    /****************************************************************************
    * DBGprintf
    *
    * This debugging function prints out a string to the debug output.
    * An optional set of substitutional parameters can be specified,
    * and the final output will be the processed result of these combined
    * with the format string, just like printf.  A newline is always
    * output after every call to this function.
    *
    * Arguments:
    *   PSTR fmt - Format string (printf style).
    *   ...      - Variable number of arguments.
    *
    * History:
    *  28-Aug-1990  - Created
    ****************************************************************************/

    VOID DBGprintf(PSTR fmt, ...)
    {
        va_list marker;
        CHAR szBuf[CCHTEXTMAX];

        va_start(marker, fmt);
        vsprintf(szBuf, fmt, marker);
        va_end(marker);

        OutputDebugString(szBuf);
        OutputDebugString("\r\n");
    }
    #endif


VIEWWP.C

/****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: viewwp.c
    *
    * Contains routines that handle the View window.
    *
    * History:
    *
    * 07/17/91 -  - Created.
    *
    ****************************************************************************/

    #include "imagedit.h"
    #include "dialogs.h"

 #include <windowsx.h>

    /*
     * Style of the view window. */
    #define VIEWSTYLE       (WS_POPUP | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU)

    STATICFN VOID NEAR ViewChar(UINT uiChar);

    static INT gViewBackMargin;     // Margin of background within view window.

    /****************************************************************************
    * ViewCreate
    *
    * This function creates the View window.
    *
    * History:
    *
    ****************************************************************************/

    VOID ViewCreate(VOID)
    {
        INT x;
        INT y;
        INT cxDummy;
        INT cyDummy;
        RECT rc;
        BOOL fMaximized;

        gViewBackMargin = GetSystemMetrics(SM_CXVSCROLL) / 2;

        /*
         * Get the saved position of the Toolbox.  Note that we throw away
         * the size fields, because we will calculate the required size
         * later based on the image size.
         */
        if (!ReadWindowPos(szViewPos, &x, &y, &cxDummy, &cyDummy, &fMaximized)) {
            /*
             * The previous position of the View window couldn't be found.
             * Position the window to the right side of the editor, just
             * below the Toolbox.
             */
            if (ghwndToolbox) {
                GetWindowRect(ghwndToolbox, &rc);
                x = rc.left;
                y = rc.bottom + (2 * PALETTEMARGIN);
            }
            else {
                /*
                 * Last resort.  Position it in the upper left corner
                 * of the screen if the Toolbox cannot be found.  This
                 * is unlikely because if the previous position of the
                 * View window could not be found, this implies that
                 * the editor has not been run before on this machine.
                 * If this is true, the toolbox will come up by default
                 * before this routine is called.  But just in case...
                 */
                x = 2 * PALETTEMARGIN;
                y = 2 * PALETTEMARGIN;
            }
        }

        if (!(ghwndView = CreateWindow(szViewClass, NULL, VIEWSTYLE,
                x, y, 0, 0, ghwndMain, NULL, ghInst, NULL)))
            return;
    }

    /****************************************************************************
    * ViewShow
    *
    * This function shows or hides the view window.
    *
    * History:
    *
    ****************************************************************************/

    VOID ViewShow(
        BOOL fShow)
    {
        if (fShow) {
            /*
             * Only show it if there is an image to display!
             */
            if (gpImageCur)
                ShowWindow(ghwndView, SW_SHOWNA);
        }
        else {
            ShowWindow(ghwndView, SW_HIDE);
        }
    }

    /****************************************************************************
    * ViewUpdate
    *
    * This function updates the view window.  It should be called any time that
    * the image changes (is drawn upon).
    *
    * History:
    *
    ****************************************************************************/

    VOID ViewUpdate(VOID)
    {
        InvalidateRect(ghwndView, NULL, TRUE);

        /*
         * Update the workspace window also, because it must always
         * match the state of the View window.
         */
        WorkUpdate();
    }

    /****************************************************************************
    * ViewReset
    *
    * This function resets the view window, sizing it to fit a new
    * image.  It should be called any time that the current image
    * is changed to another one.
    *
    * History:
    *
    ****************************************************************************/

    VOID ViewReset(VOID)
    {
        RECT rc;
        RECT rcT;

        GetWindowRect(ghwndView, &rc);

        rcT.left = 0;
        rcT.top = 0;
        rcT.right = PALETTEMARGIN + gViewBackMargin +
                gpImageCur->cx + gViewBackMargin + PALETTEMARGIN;
        rcT.bottom = PALETTEMARGIN + gViewBackMargin +
                gpImageCur->cy + gViewBackMargin + PALETTEMARGIN;
        AdjustWindowRect(&rcT, VIEWSTYLE, FALSE);

        rc.right = rc.left + (rcT.right - rcT.left);
        rc.bottom = rc.top + (rcT.bottom - rcT.top);
        FitRectToScreen(&rc);

        SetWindowPos(ghwndView, NULL, rc.left, rc.top,
                rc.right - rc.left, rc.bottom - rc.top,
                SWP_NOACTIVATE | SWP_NOZORDER);

        /*
         * If the user wants it, show the View window now.
         */
        if (gfShowView)
            ViewShow(TRUE);

        ViewUpdate();

        /*
         * Clear out the propbar size and position fields, because they
         * probably show the wrong information now.
         */
        PropBarClearPos();
        PropBarClearSize();
    }

    /****************************************************************************
    * ViewWndProc
    *
    * This is the window procedure for the view window.
    *
    * History:
    *
    ****************************************************************************/

    WINDOWPROC ViewWndProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        switch (msg) {
            case WM_CREATE:
                {
                    HMENU hmenu = GetSystemMenu(hwnd, FALSE);

                    RemoveMenu(hmenu, 7, MF_BYPOSITION);    // Second separator.
                    RemoveMenu(hmenu, 5, MF_BYPOSITION);    // First separator.

                    RemoveMenu(hmenu, SC_RESTORE, MF_BYCOMMAND);
                    RemoveMenu(hmenu, SC_SIZE, MF_BYCOMMAND);
                    RemoveMenu(hmenu, SC_MINIMIZE, MF_BYCOMMAND);
                    RemoveMenu(hmenu, SC_MAXIMIZE, MF_BYCOMMAND);
                    RemoveMenu(hmenu, SC_TASKLIST, MF_BYCOMMAND);
                }

                return 0;

            case  WM_PAINT:
                {
                    HDC hdc;
                    PAINTSTRUCT ps;
                    HBRUSH hbrOld;
                    RECT rc;

                    hdc = BeginPaint(hwnd, &ps);

                    /*
                     * The view window should not be showing if there
                     * is not an image to view!
                     */
                    if (gpImageCur) {
                        DrawMarginBorder(hwnd, hdc);

                        GetClientRect(hwnd, &rc);
                        hbrOld = SelectObject(hdc, ghbrScreen);
                        PatBlt(hdc, PALETTEMARGIN + 1, PALETTEMARGIN + 1,
                                rc.right - (PALETTEMARGIN * 2) - 2,
                                rc.bottom - (PALETTEMARGIN * 2) - 2,
                                PATCOPY);
                        SelectObject(hdc, hbrOld);

                        BitBlt(hdc, PALETTEMARGIN + gViewBackMargin,
                                PALETTEMARGIN + gViewBackMargin,
                                gcxImage, gcyImage, ghdcImage, 0, 0, SRCCOPY);
                    }

                    EndPaint(hwnd, &ps);
                }

                break;

            case WM_ACTIVATE:
                if (GET_WM_ACTIVATE_STATE(wParam, lParam))
                    gidCurrentDlg = DID_VIEW;

                break;

            case WM_LBUTTONDOWN:
                SetScreenColor(gargbCurrent[giColorLeft]);
                break;

            case WM_CHAR:
                ViewChar(wParam);
                break;

            case WM_CLOSE:
                /*
                 * The user closed the view window from the system menu.
                 * Hide the window (we don't actually destroy it so
                 * that it will appear in the same spot when they show
                 * it again).
                 */
                ViewShow(FALSE);
                gfShowView = FALSE;
                break;

            case WM_DESTROY:
                {
                    RECT rc;

                    /*
                     * Save the position of the toolbox.
                     */
                    GetWindowRect(hwnd, &rc);
                    WriteWindowPos(&rc, FALSE, szViewPos);

                    /*
                     * Null out the global window handle for the view window
                     * for safety's sake.
                     */
                    ghwndView = NULL;
                }

                break;

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

        return 0;
    }

    /************************************************************************
    * ViewChar
    *
    * Handles WM_CHAR messages for the view window.  Currently this just
    * includes the '+' and '-' keys, which are used to cycle through all
    * the possible screen colors.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR ViewChar(
        UINT uiChar)
    {
        INT i;
        INT iNext;

        switch (uiChar) {
            /*
             * Advance to the next screen color.
             */
            case '+':
                iNext = 0;
                for (i = 0; i < 16; i++) {
                    if (grgbScreen == gargbDefaultColor[i]) {
                        iNext = i + 1;
                        break;
                    }
                }

                if (iNext >= 16)
                    iNext = 0;

                SetScreenColor(gargbDefaultColor[iNext]);

                break;

            /*
             * Back up to the prior screen color.
             */
            case '-':
                iNext = 16 - 1;
                for (i = 0; i < 16; i++) {
                    if (grgbScreen == gargbDefaultColor[i]) {
                        iNext = i - 1;
                        break;
                    }
                }

                if (iNext < 0)
                    iNext = 16 - 1;

                SetScreenColor(gargbDefaultColor[iNext]);

                break;
        }
    }

    /****************************************************************************
    * ViewSetPixel
    *
    * This function colors a pixel in the View window directly.  It is
    * provided as an optimization when drawing a point.  The ghdcImage
    * bitmap must be updated as well or the image on the screen will
    * get out of synch with it.
    *
    * History:
    *
    ****************************************************************************/

    VOID ViewSetPixel(
        INT x,
        INT y,
        INT nBrushSize)
    {
        HDC hDC;
        HBRUSH hbrOld;
        INT Size;
        INT SizeX;
        INT SizeY;

        hDC = GetDC(ghwndView);
        hbrOld = SelectObject(hDC, ghbrDrawSolid);
        SizeX = x - nBrushSize / 2;
        SizeY = y - nBrushSize / 2;
        PatBlt(hDC, PALETTEMARGIN + gViewBackMargin + (SizeX >= 0 ? SizeX : 0),
                PALETTEMARGIN + gViewBackMargin + (SizeY >=  0 ? SizeY :  0),
                ((Size = gcxImage - SizeX) >= nBrushSize ?
                nBrushSize : Size),
                ((Size = gcyImage - SizeY) >= nBrushSize ?
                nBrushSize : Size), PATCOPY);
        SelectObject(hDC, hbrOld);
        ReleaseDC(ghwndView, hDC);
    }

    /****************************************************************************
    * DrawMarginBorder
    *
    *
    * History:
    *
    ****************************************************************************/

    VOID DrawMarginBorder(
        HWND hwnd,
        HDC hdc)
    {
        HBRUSH hbrOld;
        HPEN hpenOld;
        RECT rc;

        GetClientRect(hwnd, &rc);
        hpenOld = SelectObject(hdc, GetStockObject(BLACK_PEN));
        hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
        Rectangle(hdc, PALETTEMARGIN, PALETTEMARGIN,
                rc.right - PALETTEMARGIN,
                rc.bottom - PALETTEMARGIN);
        SelectObject(hdc, hpenOld);
        SelectObject(hdc, hbrOld);
    }

    /****************************************************************************
    * DrawSunkenRect
    *
    *
    * History:
    *
    ****************************************************************************/

    VOID DrawSunkenRect(
        PRECT prc,
        HDC hdc)
    {
        HPEN hpenOld;

        hpenOld = SelectObject(hdc, hpenDarkGray);
        MoveToEx(hdc, prc->left, prc->top, NULL);
        LineTo(hdc, prc->right - 1, prc->top);
        MoveToEx(hdc, prc->left, prc->top, NULL);
        LineTo(hdc, prc->left, prc->bottom - 1);

        SelectObject(hdc, GetStockObject(WHITE_PEN));
        MoveToEx(hdc, prc->left + 1, prc->bottom - 1, NULL);
        LineTo(hdc, prc->right, prc->bottom - 1);
        MoveToEx(hdc, prc->right - 1, prc->top + 1, NULL);
        LineTo(hdc, prc->right - 1, prc->bottom - 1);

        SelectObject(hdc, hpenOld);
    }


WORKWP.C

/***************************************************************************
     *                                                                         *
     *  MODULE      : WorkWP.c                                                 *
     *                                                                         *
     *  DESCRIPTION : Functions controlling the workspace window.              *
     *                                                                         *
     *  HISTORY     : 6/21/89 LR                                               *
     *                                                                         *
     ***************************************************************************/

    #include "imagedit.h"

    #include <stdlib.h>

    /*
     * This structure is used in conjunction with DeltaGenInit() and DeltaGen().
     */
    typedef struct _DELTAGEN { /* dg */
        BOOL fSwap;
        INT xf;
        INT yf;
        INT dx;
        INT dy;
        INT d;
        INT incx;
        INT incy;
        INT inc1;
        INT inc2;
    } DELTAGEN;
    typedef DELTAGEN *PDELTAGEN;

    STATICFN VOID NEAR WorkPaint(HWND hwnd);
    STATICFN VOID WorkButtonDown(HWND hwnd, UINT msg, PPOINT ppt);
    STATICFN VOID WorkButtonMouseMove(HWND hwnd, UINT msg, PPOINT ppt);
    STATICFN VOID WorkButtonUp(HWND hwnd, UINT msg, PPOINT ppt);
    STATICFN VOID NEAR SnapPointToGrid(PPOINT ppt);
    STATICFN VOID DrawToPoint(HWND hwnd, PPOINT ppt, BOOL fBrush);
    STATICFN BOOL NEAR DeltaGenInit(PDELTAGEN pdg, INT x0, INT y0,
        INT xf, INT yf, PINT px, PINT py);
    STATICFN BOOL NEAR DeltaGen(PDELTAGEN pdg, PINT px, PINT py);
    STATICFN VOID DrawPoint(HWND hwnd, PPOINT ppt, BOOL fBrush);
    STATICFN VOID NEAR RubberBandLine(BOOL fFirstTime);
    STATICFN VOID NEAR RectDPDraw(HWND hwnd);
    STATICFN VOID NEAR RubberBandRect(BOOL fFirstTime);
    STATICFN VOID NEAR CircleDPDraw(HWND hwnd);
    STATICFN VOID NEAR RubberBandCircle(BOOL fFirstTime);
    STATICFN VOID NEAR MarkHotSpotPosition(INT x, INT y);
    STATICFN VOID NEAR StartRubberBanding(HWND hwnd);
    STATICFN VOID NEAR EndRubberBanding(HWND hwnd);

    static BOOL fDrawing = FALSE;           // TRUE if mouse button is down.
    static BOOL fLeftButtonDown;            // TRUE if left button was pressed.
    static POINT ptStart;                   // Saves the starting point.
    static POINT ptEnd;                     // Saves the ending point.
    static POINT ptPrev;                    // Saves the previous point.
    static HDC hdcRubberBand;               // DC used during rubber banding.
    static BOOL fRubberBanding = FALSE;     // Tracking is in progress.

    /****************************************************************************
     * WorkWndProc
     *                                                                          *
     * purpose: Processes basic create and size and paint messages for the      *
     *          workspace window.
     *                                                                          *
     ****************************************************************************/

    WINDOWPROC WorkWndProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        LPCREATESTRUCT cs;
        POINT pt;

        switch (msg) {
            case WM_CREATE:
                /* set up image variables */
                cs = (LPCREATESTRUCT)lParam;
                gcxWorkSpace = cs->cx;
                gcyWorkSpace = cs->cy;
                break;

            case WM_SIZE:
                gcxWorkSpace = LOWORD(lParam);
                gcyWorkSpace = HIWORD(lParam);

                break;

            case WM_PAINT:
                WorkPaint(hwnd);
                break;

            case WM_MOUSEMOVE:
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                WorkButtonMouseMove(hwnd, msg, &pt);
                break;

            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                WorkButtonDown(hwnd, msg, &pt);
                break;

            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                WorkButtonUp(hwnd, msg, &pt);
                break;

            case WM_KEYDOWN:
                switch (wParam) {
                    case VK_ESCAPE:
                        if (fDrawing) {
                            if (fRubberBanding) {
                                EndRubberBanding(hwnd);
                                WorkUpdate();
                            }

                            PropBarClearSize();
                            ReleaseCapture();
                            fDrawing = FALSE;
                        }

                        break;
                }

                break;

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

        return 0;
    }

    /****************************************************************************
    * WorkPaint
    *
    * Handles WM_PAINT for the workspace window.
    *
    * History:
    *
    ****************************************************************************/

    STATICFN VOID NEAR WorkPaint(    HWND hwnd)
    {
        register INT i;
        PAINTSTRUCT ps;
        HCURSOR hcurOld;
        HDC hdcTemp;
        HBITMAP hbmTemp;

        hcurOld = SetCursor(hcurWait);
        BeginPaint(hwnd, &ps);

        /*
         * Do they want a grid and is there enough room to show the lines?
         */
        if (gfGrid && gZoomFactor > 1) {
            /*
             * Stretch the bits onto a temporary DC.
             */
            hdcTemp = CreateCompatibleDC(ghdcImage);
            hbmTemp = CreateCompatibleBitmap(ghdcImage,
                    gcxWorkSpace, gcyWorkSpace);
            SelectObject(hdcTemp, hbmTemp);
            StretchBlt(hdcTemp, 0, 0, gcxWorkSpace, gcyWorkSpace,
                    ghdcImage, 0, 0, gcxImage, gcyImage, SRCCOPY);

            /*
             * Draw the grid lines on the temp DC.
             */
            for (i = gZoomFactor - 1; i < gcxWorkSpace; i += gZoomFactor)
                PatBlt(hdcTemp, i, 0, 1, gcyWorkSpace, BLACKNESS);
            for (i = gZoomFactor - 1; i < gcyWorkSpace; i += gZoomFactor)
                PatBlt(hdcTemp, 0, i, gcxWorkSpace, 1, BLACKNESS);

            /*
             * Copy the bits to the screen.
             */
            BitBlt(ps.hdc, 0, 0, gcxWorkSpace, gcyWorkSpace,
                    hdcTemp, 0, 0, SRCCOPY);

            DeleteDC(hdcTemp);
            DeleteObject(hbmTemp);
        }
        else {
            /*
             * No grid.  Just stretch the image to the screen.
             */
            StretchBlt(ps.hdc, 0, 0, gcxWorkSpace, gcyWorkSpace,
                    ghdcImage, 0, 0, gcxImage, gcyImage, SRCCOPY);
        }

        EndPaint(hwnd, &ps);
        SetCursor(hcurOld);
    }

    /****************************************************************************
    * WorkUpdate
    *
    * This function updates the workspace window.
    *
    * History:
    *
    ****************************************************************************/

    VOID WorkUpdate(VOID)
    {
        /*
         * Invalidate the workspace window.  Because the image will be
         * be blt'ed onto it, we do not need to force the background to
         * be cleared first.
         */
        InvalidateRect(ghwndWork, NULL, FALSE);
    }

    /****************************************************************************
    * WorkReset
    *
    * This function reset the workspace window.  It should be called
    * any time that a new image is loaded (because the size could
    * change) or the size of the main window changes (because the
    * workspace window needs to be resized to fit).
    *
    * History:
    *
    ****************************************************************************/

    VOID WorkReset(VOID)
    {
        RECT rcClient;
        INT cx;
        INT cy;
        INT xScale;
        INT yScale;
        INT cxBorder;
        INT cyBorder;

        cxBorder = GetSystemMetrics(SM_CXBORDER);
        cyBorder = GetSystemMetrics(SM_CYBORDER);

        if (!gcxImage || !gcyImage) {
            gZoomFactor = 1;
        }
        else {
            GetClientRect(ghwndMain, &rcClient);
            cx = rcClient.right - (2 * PALETTEMARGIN) - (2 * cxBorder);
            cy = rcClient.bottom - (2 * PALETTEMARGIN) - (2 * cyBorder) -
                    gcyPropBar;

            xScale = cx / gcxImage;
            yScale = cy / gcyImage;

            if (xScale > 0 && yScale > 0)
                gZoomFactor = min(xScale, yScale);
            else
                gZoomFactor = 1;
        }

        SetWindowPos(ghwndWork, NULL,
                PALETTEMARGIN, PALETTEMARGIN + gcyPropBar,
                (gZoomFactor * gcxImage) + (2 * cxBorder),
                (gZoomFactor * gcyImage) + (2 * cyBorder),
                SWP_NOACTIVATE | SWP_NOZORDER);
        WorkUpdate();
    }

    /************************************************************************
    * WorkButtonDown
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID WorkButtonDown(
        HWND hwnd,
        UINT msg,
        PPOINT ppt)
    {
        /*
         * If the other button is already down, just ignore this one.
         */
        if (fDrawing)
            return;

        SetFocus(hwnd);
        fLeftButtonDown = (msg == WM_LBUTTONDOWN) ? TRUE : FALSE;

        SnapPointToGrid(ppt);
        ptStart = ptPrev = ptEnd = *ppt;

        if (fLeftButtonDown) {
            ghbrDraw = ghbrLeft;
            ghbrDrawSolid = ghbrLeftSolid;
            gfDrawMode = gfModeLeft;
            ghpenDraw = ghpenLeft;
        }
        else {
            ghbrDraw = ghbrRight;
            ghbrDrawSolid = ghbrRightSolid;
            gfDrawMode = gfModeRight;
            ghpenDraw = ghpenRight;
        }

        /*
         * If this tool draws on the down-click, update the undo
         * buffer now.
         */
        if (gaTools[gCurTool].fDrawOnDown)
            ImageUpdateUndo();

        SetCapture(ghwndWork);
        fDrawing = TRUE;

        (*gpfnDrawProc)(hwnd, msg, *ppt);

        PropBarSetSize(ptStart, ptEnd);
    }

    /************************************************************************
    * WorkButtonMouseMove
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID WorkButtonMouseMove(
        HWND hwnd,
        UINT msg,
        PPOINT ppt)
    {
        static POINT ptNZLast;  // Saves the last point (non-zoomed).
        POINT ptNZ;

        SetCursor(gaTools[gCurTool].hcur);

        SnapPointToGrid(ppt);

        /*
         * Calculate the point as it would be on the actual image
         * (non-zoomed).
         */
        ptNZ.x = ppt->x / gZoomFactor;
        ptNZ.y = ppt->y / gZoomFactor;

        /*
         * Only call the drawing proc if the point changed enough to
         * jump over a zoomed pixels width (it jumped a grid square).
         * This prevents calling the DrawProc for a mouse move of
         * a single pixel (unless the zoom factor is 1, of course).
         */
        if (ptNZLast.x != ptNZ.x || ptNZLast.y != ptNZ.y) {
            ptEnd = *ppt;
            (*gpfnDrawProc)(hwnd, msg, *ppt);
            ptPrev = ptEnd;

            PropBarSetPos(ptNZ.x, ptNZ.y);

            if (fDrawing)
                PropBarSetSize(ptStart, ptEnd);

            ptNZLast = ptNZ;
        }
    }

    /************************************************************************
    * WorkButtonUp
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID WorkButtonUp(
        HWND hwnd,
        UINT msg,
        PPOINT ppt)
    {
        /*
         * Pass this on to the draw procs, but only if we are still drawing.
         * The drawing could have been cancelled by the Escape key, in
         * which case we just ignore the button up message.
         */
        if (fDrawing) {
            SnapPointToGrid(ppt);

            /*
             * If this tool draws on the up-click, update the undo
             * buffer now.
             */
            if (gaTools[gCurTool].fDrawOnUp)
                ImageUpdateUndo();

            (*gpfnDrawProc)(hwnd, msg, *ppt);

            ReleaseCapture();
            fDrawing = FALSE;
        }
    }

    /******************************************************************************
     * SnapPointToGrid
     *
     * PURPOSE : Snap the current mouse coordinate to the nearest grid intersection.
     *
     * PARAMS  : PPOINT ppt : current mouse coordinates
     *
     *****************************************************************************/

    STATICFN VOID NEAR SnapPointToGrid(
        PPOINT ppt)
    {
        /*
         * Scale the point down (this gridizes it at the same time).
         */
        ppt->x = ppt->x / gZoomFactor;
        ppt->y = ppt->y / gZoomFactor;

        /*
         * Limit the point to within the image.
         */
        if (ppt->x < 0)
            ppt->x = 0;

        if (ppt->y < 0)
            ppt->y = 0;

        if (ppt->x >= gcxImage)
            ppt->x = gcxImage - 1;

        if (ppt->y >= gcyImage)
            ppt->y = gcyImage - 1;

        /*
         * Finally, scale it back up to the workspace window size.
         */
        ppt->x *= gZoomFactor;
        ppt->y *= gZoomFactor;
    }

    /************************************************************************
    * PencilDP
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PencilDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
                DrawPoint(hwnd, &ptNew, FALSE);
                break;

            case WM_MOUSEMOVE:
                if (fDrawing)
                    DrawToPoint(hwnd, &ptNew, FALSE);

                break;
        }
    }

    /************************************************************************
    * BrushDP
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID BrushDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
                DrawPoint(hwnd, &ptNew, TRUE);
                break;

            case WM_MOUSEMOVE:
                if (fDrawing)
                    DrawToPoint(hwnd, &ptNew, TRUE);

                break;
        }
    }

    /************************************************************************
    * DrawToPoint
    *
    * This function draws from the previous point to the given point.
    * This includes all points between.
    *
    * The global ptPrev must have been initialized prior to the  first time
    * this function is called during a drawing operation.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID DrawToPoint(
        HWND hwnd,
        PPOINT ppt,
        BOOL fBrush)
    {
        DELTAGEN dg;
        BOOL fContinue;
        POINT pt;
        INT x;
        INT y;

        x = ppt->x / gZoomFactor;
        y = ppt->y / gZoomFactor;
        DeltaGenInit(&dg, ptPrev.x / gZoomFactor, ptPrev.y / gZoomFactor,
                ppt->x / gZoomFactor, ppt->y / gZoomFactor, &x, &y);
        do {
            pt.x = x * gZoomFactor;
            pt.y = y * gZoomFactor;
            DrawPoint(hwnd, &pt, fBrush);
            fContinue = DeltaGen(&dg, &x, &y);
        } while (fContinue);
    }

    /***************************** Public  Function ****************************\
    * DeltaGenInit
    *
    * This routine initializes the pdg, px and py in preparation for using
    * DeltaGen().  Returns fContinue.
    *
    * Algorithm derived from BRESENHAM line algorighm on p. 435 of Fund. of
    * interactive computer graphics, Foley/VanDam, addison-wesley 1983.
    *
    * History:
    *       3/7/89  sanfords        created
    \***************************************************************************/

    STATICFN BOOL NEAR DeltaGenInit(
        PDELTAGEN pdg,
        INT x0,
        INT y0,
        INT xf,
        INT yf,
        PINT px,
        PINT py)
    {
        INT nT;

        pdg->xf = xf;
        pdg->yf = yf;

        if (x0 == xf && y0 == yf)
            return FALSE;

        if (xf >= x0)
            pdg->incx = 1;
        else
            pdg->incx = -1;

        if (yf >= y0)
            pdg->incy = 1;
        else
            pdg->incy = -1;

        pdg->dx = (xf - x0) * pdg->incx;
        pdg->dy = (yf - y0) * pdg->incy;

        if (pdg->dy > pdg->dx) {
            nT = pdg->dy;
            pdg->dy = pdg->dx;
            pdg->dx = nT;
            nT = pdg->incx;
            pdg->incx = pdg->incy;
            pdg->incy = nT;
            pdg->fSwap = TRUE;
        }
        else {
            pdg->fSwap = FALSE;
        }

        pdg->inc1 = pdg->dy * 2;
        pdg->inc2 = (pdg->dy - pdg->dx) * 2;
        pdg->d = pdg->inc1 - pdg->dx;

        pdg->xf = xf;
        pdg->yf = yf;

        *px = x0;
        *py = y0;

        return TRUE;
    }

    /***************************** Public  Function ****************************\
    * DeltaGen
    *
    * This routine generates the next coordinates for px,py assuming that this
    * point is proceeding linearly from x0,y0 to xf, yf.  It returns FALSE only
    * if *px == xf and *py == yf on entry.  (ie returns fContinue)  pdg should
    * have been previously set by DeltaGenInit().
    *
    * Algorithm derived from BRESENHAM line algorighm on p. 435 of Fund. of
    * interactive computer graphics, Foley/VanDam, addison-wesley 1983.
    *
    * History:
    *       3/7/89  sanfords        created
    \***************************************************************************/

    STATICFN BOOL NEAR DeltaGen(
        PDELTAGEN pdg,
        PINT px,
        PINT py)
    {
        PINT pnT;

        if ((*px == pdg->xf) && (*py == pdg->yf))
            return FALSE;

        if (pdg->fSwap) {
            pnT = px;
            px = py;
            py = pnT;
        }

        *px += pdg->incx;
        if (pdg->d < 0) {
            pdg->d += pdg->inc1;
        }
        else {
            *py += pdg->incy;
            pdg->d += pdg->inc2;
        }

        return TRUE;
    }

    /************************************************************************
    * DrawPoint
    *
    * This function is called to draw a point on the image.  It is used
    * by the Pencil and Brush tools.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID DrawPoint(
        HWND hwnd,
        PPOINT ppt,
        BOOL fBrush)
    {
        HDC hDC;
        HBRUSH hbrOld;
        INT wx;
        INT wy;
        INT iStartY;
        INT wStep;
        INT i;
        INT j;
        INT nBrushSize;

        if (ppt->x < 0 || ppt->y < 0)
            return;

        hDC = GetDC(hwnd);

        /*
         * If this is a point from the brush tool, use the current
         * brush width.  Otherwise, draw a single pixel point.
         */
        if (fBrush)
            nBrushSize = gnBrushSize;
        else
            nBrushSize = 1;

        /*
         * Determine some starting factors, then draw the point in
         * the workspace window.
         */
        hbrOld = SelectObject(hDC, ghbrDrawSolid);
        wy = iStartY = ppt->y - (ppt->y % gZoomFactor)
                -(nBrushSize / 2) * gZoomFactor;
        wx = ppt->x - (ppt->x % gZoomFactor)
                -(nBrushSize / 2) * gZoomFactor;
        wStep = gZoomFactor;

        if (gfGrid && gZoomFactor > 1)
            wStep -= 1;

        for (i = 0; i < nBrushSize; i++, wx += gZoomFactor) {
            wy = iStartY;
            for (j = 0; j < nBrushSize; j++, wy += gZoomFactor)
                PatBlt(hDC, wx, wy, wStep, wStep, PATCOPY);
        }

        SelectObject(hDC, hbrOld);
        ReleaseDC(hwnd, hDC);

        /*
         * Set the point in the bitmap directly as an optimization.
         */
        wx = ppt->x / gZoomFactor;
        wy = ppt->y / gZoomFactor;
        if (wx < gcxImage && wy < gcyImage) {
            hbrOld = SelectObject(ghdcImage, ghbrDrawSolid);
            PatBlt(ghdcImage, wx - nBrushSize / 2, wy - nBrushSize / 2,
                    nBrushSize, nBrushSize, PATCOPY);

            if (giType != FT_BITMAP) {
                /*
                 * If in color mode, set the mask bits black.  Otherwise make
                 * them white.
                 */
                PatBlt(ghdcANDMask, wx - nBrushSize / 2, wy - nBrushSize / 2,
                        nBrushSize, nBrushSize,
                        (gfDrawMode == MODE_COLOR) ? BLACKNESS : WHITENESS);
            }

            SelectObject(ghdcImage, hbrOld);

            /*
             * Draw the point in the view window directly as an optimization.
             */
            if (ghwndView)
                ViewSetPixel(wx, wy, nBrushSize);
        }

        /*
         * Mark the image as changed.
         */
        fImageDirty = TRUE;
    }

    /************************************************************************
    * PickDP
    *
    * Drawing proc that selects a rectangular portion of the image.
    * It updates the global picking rectangle.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID PickDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point

        switch (msg) {
            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
                /* erase any previous ghost rectangle */
                WorkUpdate();
                UpdateWindow(ghwndWork);

                /*
                 * Initialize the pick rectangle to cover the entire screen.
                 */
                PickSetRect(0, 0, gcxImage - 1, gcyImage - 1);

                StartRubberBanding(hwnd);
                RubberBandRect(TRUE);
                break;

            case WM_MOUSEMOVE:
                if (fRubberBanding)
                    RubberBandRect(FALSE);

                break;

            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                EndRubberBanding(hwnd);

                /*
                 * Flip the points (if needed) and scale down.
                 */
                ptTL = ptStart;
                ptBR = ptEnd;
                NormalizePoints(&ptTL, &ptBR);
                ptTL.x /= gZoomFactor;
                ptTL.y /= gZoomFactor;
                ptBR.x /= gZoomFactor;
                ptBR.y /= gZoomFactor;

                PickSetRect(ptTL.x, ptTL.y, ptBR.x, ptBR.y);

                break;
        }
    }

    /******************************************************************************
     * VOID LineDP(hwnd, msg, ptNew)
     *
     * PURPOSE: Draw a straight line according to tracking line.
     *
     * PARAMS : HWND   hwnd : handle to dest. DC
     *          unsigned msg  : Upper left corner of rect;
     *          POINT  ptNew   : end pt. of line
     *
     * SIDE EFFECTS: may change bits in image DC
     *
     *****************************************************************************/

    VOID LineDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        INT sx;
        INT sy;
        INT ex;
        INT ey;
        HPEN hpen;
        HPEN hpenOld;
        HBRUSH hbrOld;

        switch (msg) {
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                StartRubberBanding(hwnd);
                RubberBandLine(TRUE);
                break;

            case WM_MOUSEMOVE:
                if (fRubberBanding)
                    RubberBandLine(FALSE);

                break;

            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                EndRubberBanding(hwnd);

                /* transform selected coordinates to those of actual image */
                sx = ptStart.x / gZoomFactor;
                sy = ptStart.y / gZoomFactor;
                ex = ptEnd.x / gZoomFactor;
                ey = ptEnd.y / gZoomFactor;

                hpenOld = SelectObject(ghdcImage, ghpenDraw);
                MoveToEx(ghdcImage, sx, sy, NULL);
                LineTo(ghdcImage, ex, ey);
                SelectObject(ghdcImage, hpenOld);

                if (giType != FT_BITMAP) {
                    /* for icons and cursors draw the line on the AND DC (memory)
                     * in black (if in color mode) or white (otherwise)
                     */
                    hpen = CreatePen(PS_INSIDEFRAME, 1,
                            (gfDrawMode == MODE_COLOR) ? RGB_BLACK : RGB_WHITE);
                    hpenOld = SelectObject(ghdcANDMask, hpen);
                    hbrOld = SelectObject(ghdcANDMask, GetStockObject(NULL_BRUSH));
                    MoveToEx(ghdcANDMask, sx, sy, NULL);
                    LineTo(ghdcANDMask, ex, ey);
                    SelectObject(ghdcANDMask, hbrOld);
                    SelectObject(ghdcANDMask, hpenOld);
                    DeleteObject(hpen);
                }

                /*
                 * Because the LineTo function does not draw the ending
                 * point, we must do it manually here.
                 */
                DrawPoint(hwnd, &ptEnd, FALSE);

                fImageDirty = TRUE;

                ViewUpdate();

                break;
        }
    }

    /************************************************************************
    * RubberBandLine
    *
    * This function erases the old line and draws the new tracking line
    * when using the "Line" tool.
    *
    * Arguments:
    *   BOOL fFirstTime - TRUE if starting to track the line (it doesn't
    *                     need to erase the old line).
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR RubberBandLine(
        BOOL fFirstTime)
    {
        INT nOffset;

        /*
         * Set the raster-op to invert.
         */
        SetROP2(hdcRubberBand, R2_NOT);

        /*
         * If we are magnifying the image at all, the line needs to be
         * slightly offset so that it will be draw exactly in between
         * the grid lines.
         */
        if (gZoomFactor > 1)

nOffset = -1;
        else
            nOffset = 0;

        if (!fFirstTime) {
            /*
             * Erase the old line.
             */

            MoveToEx(hdcRubberBand, ptStart.x + (gZoomFactor / 2) + nOffset,
                    ptStart.y + (gZoomFactor / 2) + nOffset, NULL);
            LineTo(hdcRubberBand, ptPrev.x + (gZoomFactor / 2) + nOffset,
                    ptPrev.y + (gZoomFactor / 2) + nOffset);
        }

        /*
         * Draw the new one.
         */

        MoveToEx(hdcRubberBand, ptStart.x + (gZoomFactor / 2) + nOffset,
                ptStart.y + (gZoomFactor / 2) + nOffset, NULL);
        LineTo(hdcRubberBand, ptEnd.x + (gZoomFactor / 2) + nOffset,
                ptEnd.y + (gZoomFactor / 2) + nOffset);
    }

    /******************************************************************************
     * VOID RectDP(hwnd, msg, ptNew)
     *
     * PURPOSE: Draw a rectangle (filled/hollow) in the area specified
     *
     * PARAMS : HWND   hwnd : handle to dest. DC
     *          WORD   msg  :
     *          POINT  ptNew   : end pt. of line
     *
     * SIDE EFFECTS: may change bits in image DC
     *
     *****************************************************************************/

    VOID RectDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                StartRubberBanding(hwnd);
                RubberBandRect(TRUE);
                break;

            case WM_MOUSEMOVE:
                if (fRubberBanding)
                    RubberBandRect(FALSE);

                break;

            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                RectDPDraw(hwnd);
                break;
        }
    }

    /************************************************************************
    * RectDPDraw
    *
    * Does the final drawing of a rectangle when using the Rectangle tool.
    *
    * Arguments:
    *   HWND hwnd - Window handle to the workspace.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR RectDPDraw(
        HWND hwnd)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point
        HBRUSH hbr;
        HBRUSH hbrOld;
        HPEN hpen;
        HPEN hpenOld;
        INT nOutset;

        EndRubberBanding(hwnd);

        /*
         * Flip the points (if needed) and scale down.
         */
        ptTL = ptStart;
        ptBR = ptEnd;
        NormalizePoints(&ptTL, &ptBR);
        ptTL.x /= gZoomFactor;
        ptTL.y /= gZoomFactor;
        ptBR.x /= gZoomFactor;
        ptBR.y /= gZoomFactor;

        if (gCurTool == TOOL_RECT) {
            hpen = ghpenDraw;
            hbr = GetStockObject(NULL_BRUSH);
            nOutset = 1;
        }
        else {
            hpen = GetStockObject(NULL_PEN);
            hbr = ghbrDraw;
            nOutset = 2;
        }

        hpenOld = SelectObject(ghdcImage, hpen);
        hbrOld = SelectObject(ghdcImage, hbr);
        Rectangle(ghdcImage, ptTL.x, ptTL.y,
                ptBR.x + nOutset, ptBR.y + nOutset);
        SelectObject(ghdcImage, hpenOld);
        SelectObject(ghdcImage, hbrOld);

        if (giType != FT_BITMAP) {
            /* for icons and cursors draw the shape on the AND DC (memory)
             * in black (if in color mode) or white (otherwise)
             */
            if (gCurTool == TOOL_RECT) {
                hpen = CreatePen(PS_INSIDEFRAME, 1,
                        (gfDrawMode == MODE_COLOR) ? RGB_BLACK : RGB_WHITE);
                hbr = GetStockObject(NULL_BRUSH);
            }
            else {
                hpen = GetStockObject(NULL_PEN);
                hbr = GetStockObject((gfDrawMode == MODE_COLOR) ?
                        BLACK_BRUSH : WHITE_BRUSH);
            }

            hpenOld = SelectObject(ghdcANDMask, hpen);
            hbrOld = SelectObject(ghdcANDMask, hbr);
            Rectangle(ghdcANDMask, ptTL.x, ptTL.y,
                    ptBR.x + nOutset, ptBR.y + nOutset);
            SelectObject(ghdcANDMask, hpenOld);
            SelectObject(ghdcANDMask, hbrOld);

            if (gCurTool == TOOL_RECT)
                DeleteObject(hpen);
        }

        fImageDirty = TRUE;

        ViewUpdate();
    }

    /******************************************************************************
     * VOID PASCAL RubberBandRect()
     *
     * PURPOSE: Draw rubberbanding rect.
     *
     * PARAMS : HANDLE hDst : handle to dest. DC
     *
     *****************************************************************************/

    STATICFN VOID NEAR RubberBandRect(
        BOOL fFirstTime)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point

        /*
         * Set the raster-op to invert.
         */
        SetROP2(hdcRubberBand, R2_NOT);

        if (!fFirstTime) {
            /*
             * Erase the old rectangle.
             */
            ptTL = ptStart;
            ptBR = ptPrev;
            NormalizePoints(&ptTL, &ptBR);
            Rectangle(hdcRubberBand, ptTL.x, ptTL.y,
                    ptBR.x + gZoomFactor, ptBR.y + gZoomFactor);
        }

        /*
         * Draw the new one.
         */
        ptTL = ptStart;
        ptBR = ptEnd;
        NormalizePoints(&ptTL, &ptBR);
        Rectangle(hdcRubberBand, ptTL.x, ptTL.y,
                ptBR.x + gZoomFactor, ptBR.y + gZoomFactor);
    }

    /******************************************************************************
     * VOID CircleDP(hwnd, msg, ptNew)
     *
     * PURPOSE: Draw an ellipse (filled/hollow) in the area specified.
     *
     * PARAMS : HWND   hwnd : handle to dest. DC
     *          unsigned msg  : Upper left corner of rect;
     *          POINT  ptNew   : end pt. of line
     *
     * SIDE EFFECTS: may change bits in image DC
     *
     *****************************************************************************/

    VOID CircleDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                StartRubberBanding(hwnd);
                RubberBandCircle(TRUE);
                break;

            case WM_MOUSEMOVE:
                if (fRubberBanding)
                    RubberBandCircle(FALSE);

                break;

            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                CircleDPDraw(hwnd);
                break;
        }
    }

    /************************************************************************
    * CircleDPDraw
    *
    * Does the final drawing of an ellipse when using the Ellipse tool.
    *
    * Arguments:
    *   HWND hwnd - Window handle to the workspace.
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR CircleDPDraw(
        HWND hwnd)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point
        HBRUSH hbr;
        HBRUSH hbrOld;
        HPEN hpen;
        HPEN hpenOld;
        INT nOutset;

        EndRubberBanding(hwnd);

        /*
         * Flip the points (if needed) and scale down.
         */
        ptTL = ptStart;
        ptBR = ptEnd;
        NormalizePoints(&ptTL, &ptBR);
        ptTL.x /= gZoomFactor;
        ptTL.y /= gZoomFactor;
        ptBR.x /= gZoomFactor;
        ptBR.y /= gZoomFactor;

    #ifdef WIN16
        /*
         * The win 3.x code does not properly draw an ellipse if it
         * has a NULL pen selected in (to not draw the border).  For
         * this platform, we must select in the drawing pen.  This is
         * not necessary for NT (we can use a NULL pen to avoid
         * drawing the solid border).
         */

        if (gCurTool == TOOL_CIRCLE)
            hbr = GetStockObject(NULL_BRUSH);
        else
            hbr = ghbrDraw;

        hpenOld = SelectObject(ghdcImage, ghpenDraw);
        hbrOld = SelectObject(ghdcImage, hbr);
        Ellipse(ghdcImage, ptTL.x, ptTL.y, ptBR.x + 1, ptBR.y + 1);
        SelectObject(ghdcImage, hbrOld);
        SelectObject(ghdcImage, hpenOld);

        if (giType != FT_BITMAP) {
            /* for icons and cursors draw the shape on the AND DC (memory)
             * in black (if in color mode) or white (otherwise)
             */
            hpen = CreatePen(PS_INSIDEFRAME, 1,
                    (gfDrawMode == MODE_COLOR) ? RGB_BLACK : RGB_WHITE);

            if (gCurTool == TOOL_CIRCLE)
                hbr = GetStockObject(NULL_BRUSH);
            else
                hbr = GetStockObject((gfDrawMode == MODE_COLOR) ?
                        BLACK_BRUSH : WHITE_BRUSH);

            hpenOld = SelectObject(ghdcANDMask, hpen);
            hbrOld = SelectObject(ghdcANDMask, hbr);
            Ellipse(ghdcANDMask, ptTL.x, ptTL.y, ptBR.x + 1, ptBR.y + 1);
            SelectObject(ghdcANDMask, hpenOld);
            SelectObject(ghdcANDMask, hbrOld);
            DeleteObject(hpen);
        }

    #else

        if (gCurTool == TOOL_CIRCLE) {
            hpen = ghpenDraw;
            hbr = GetStockObject(NULL_BRUSH);
            nOutset = 1;
        }
        else {
            hpen = GetStockObject(NULL_PEN);
            hbr = ghbrDraw;
            nOutset = 2;
        }

        hpenOld = SelectObject(ghdcImage, hpen);
        hbrOld = SelectObject(ghdcImage, hbr);
        Ellipse(ghdcImage, ptTL.x, ptTL.y, ptBR.x + nOutset, ptBR.y + nOutset);
        SelectObject(ghdcImage, hpenOld);
        SelectObject(ghdcImage, hbrOld);

        if (giType != FT_BITMAP) {
            /* for icons and cursors draw the shape on the AND DC (memory)
             * in black (if in color mode) or white (otherwise)
             */
            if (gCurTool == TOOL_CIRCLE) {
                hpen = CreatePen(PS_INSIDEFRAME, 1,
                        (gfDrawMode == MODE_COLOR) ? RGB_BLACK : RGB_WHITE);
                hbr = GetStockObject(NULL_BRUSH);
            }
            else {
                hpen = GetStockObject(NULL_PEN);
                hbr = GetStockObject((gfDrawMode == MODE_COLOR) ?
                        BLACK_BRUSH : WHITE_BRUSH);
            }

            hpenOld = SelectObject(ghdcANDMask, hpen);
            hbrOld = SelectObject(ghdcANDMask, hbr);
            Ellipse(ghdcANDMask, ptTL.x, ptTL.y, ptBR.x + nOutset, ptBR.y + nOutset);
            SelectObject(ghdcANDMask, hpenOld);
            SelectObject(ghdcANDMask, hbrOld);

            if (gCurTool == TOOL_CIRCLE)
                DeleteObject(hpen);
        }

    #endif

        fImageDirty = TRUE;

        ViewUpdate();
    }

    /******************************************************************************
     * VOID PASCAL RubberBandCircle()
     *
     * PURPOSE: Draw rubberbanding circle
     *
     *
     *****************************************************************************/

    STATICFN VOID NEAR RubberBandCircle(
        BOOL fFirstTime)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point

        /*
         * Set the raster-op to invert.
         */
        SetROP2(hdcRubberBand, R2_NOT);

        if (!fFirstTime) {
            /*
             * Erase the old circle.
             */
            ptTL = ptStart;
            ptBR = ptPrev;
            NormalizePoints(&ptTL, &ptBR);
            Ellipse(hdcRubberBand, ptTL.x, ptTL.y,
                    ptBR.x + gZoomFactor, ptBR.y + gZoomFactor);
        }

        /*
         * Draw the new one.
         */
        ptTL = ptStart;
        ptBR = ptEnd;
        NormalizePoints(&ptTL, &ptBR);
        Ellipse(hdcRubberBand, ptTL.x, ptTL.y,
                ptBR.x + gZoomFactor, ptBR.y + gZoomFactor);
    }

    /************************************************************************
    * FloodDP
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    VOID FloodDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        HDC dc;
        HDC bwdc;
        HBRUSH hbrOld;
        HBITMAP bwbit;
        HCURSOR hcurSave;

        switch (msg) {
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                hcurSave = SetCursor(hcurWait);

                dc = GetDC(hwnd);
                /* create temporary DC */
                bwdc = CreateCompatibleDC(dc);

                /* create temporary monochrome bitmap */
                if (!(bwbit = CreateBitmap(gcxImage, gcyImage, 1, 1, NULL))) {
                    DeleteDC(bwdc);
                    ReleaseDC(hwnd, dc);
                    Message(MSG_OUTOFMEMORY);
                    return;
                }
                SelectObject(bwdc, bwbit);

                /*  Set background color of image DC to desired floodfill color.*/
                SetBkColor(ghdcImage,
                        GetPixel(ghdcImage, (ptNew.x / gZoomFactor),
                        (ptNew.y / gZoomFactor)));

                /******* OPERATION 0 ******/
                /* First create a monochrome mask of the image after setting background
                 * color to the floodfill color. This will make the region to be
                 * flooded white(background), and it's boundary black (foreground) in the
                 * mask.
                 */
                BitBlt(bwdc, 0, 0, gcxImage, gcyImage, ghdcImage, 0, 0, SRCCOPY);

                /******* OPERATION 1 ******/
                /* floodfill selected region in mask (which is white bounded by black)
                 * with black.
                 */
                SelectObject(bwdc, GetStockObject(BLACK_BRUSH));
                ExtFloodFill(bwdc, ptNew.x / gZoomFactor,
                        ptNew.y / gZoomFactor, RGB_BLACK, FLOODFILLBORDER);

                /******* OPERATION 2 ******/
                /* Now XOR the original image on the mask , inverting the
                 * flood-filled pixels on mask (black --> white).
                 */
                BitBlt(bwdc, 0, 0, gcxImage, gcyImage, ghdcImage, 0, 0, SRCINVERT);

                /* the AND mask needs to be updated only if in screen or inverse mode */
                if ((giType == FT_CURSOR) || (giType == FT_ICON)) {
                    if (gfDrawMode == MODE_COLOR) {
                        SetBkColor(ghdcANDMask, RGB(0, 0, 0));
                        BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, bwdc,
                                0, 0, ROP_DSna);
                        SelectObject(ghdcANDMask, GetStockObject(BLACK_BRUSH));
                        BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, bwdc,
                                0, 0, ROP_DSPao);
                    }
                    else {
                        SetBkColor(ghdcANDMask, RGB(0xff, 0xff, 0xff));                    BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, bwdc,
                                0, 0, ROP_DSna);
                        SelectObject(ghdcANDMask, GetStockObject(WHITE_BRUSH));
                        BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, bwdc,
                                0, 0, ROP_DSPao);
                    }
                }

                SetBkColor(ghdcImage, RGB_WHITE);
                /****** OPERATION 3 ******/
                /* The following operation turns the flooded area on-screen black,
                 * on the image, preserving the rest of the it.
                 */
                BitBlt(ghdcImage, 0, 0, gcxImage, gcyImage, bwdc, 0, 0, ROP_DSna);

                /****** OPERATION 4 ******/
                /* Rop_DSPao ANDs the pattern (current brush which is the floodfill
                 * color) on the source making flooded area (which was white as a
                 * result of operation 2 ) the current brush color, and keeps the rest
                 * of the source black. The source is then ORed into the original image
                 * (whose flooded area is black as a result of operation 3) to get
                 * the desired end result.
                 */
                hbrOld = SelectObject(ghdcImage, ghbrDraw);
                BitBlt(ghdcImage, 0, 0, gcxImage, gcyImage, bwdc, 0, 0, ROP_DSPao);
                SelectObject(ghdcImage, hbrOld);

                /* clean up */
                DeleteDC(bwdc);
                DeleteObject(bwbit);
                ReleaseDC(hwnd, dc);

                /*
                 * Mark the image as changed.
                 */
                fImageDirty = TRUE;

                ViewUpdate();

                SetCursor(hcurSave);

                break;
        }
    }

    /******************************************************************************
     * VOID HotSpotDP(hwnd, msg, ptNew)
     *
     * PURPOSE: Sets the hotspot.
     *
     * PARAMS : HWND   hwnd : handle to dest. DC
     *          WORD   msg  :
     *          POINT  ptNew   : end pt.
     *
     *****************************************************************************/

    VOID HotSpotDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_LBUTTONDOWN:
                PropBarSetHotSpot(ptNew.x / gZoomFactor, ptNew.y / gZoomFactor);
                break;

            case WM_MOUSEMOVE:
                if (fDrawing && fLeftButtonDown)
                    PropBarSetHotSpot(ptNew.x / gZoomFactor,
                            ptNew.y / gZoomFactor);

                break;

            case WM_LBUTTONUP:
                MarkHotSpotPosition(ptNew.x / gZoomFactor, ptNew.y / gZoomFactor);
                break;
        }
    }

    /************************************************************************
    * MarkHotSpotPosition
    *
    * Updates the hotspot location in the currently selected cursor image.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/

    STATICFN VOID NEAR MarkHotSpotPosition(
        INT x,
        INT y)
    {
        gpImageCur->iHotspotX = x;
        gpImageCur->iHotspotY = y;
        PropBarSetHotSpot(x, y);

        /*
         * Mark the image as changed.
         */
        fImageDirty = TRUE;
    }

    /******************************************************************************
     * NormalizePoints
     *
     * PURPOSE : interchange start and end pts
     *           if start point is > end point.
     *
     *****************************************************************************/

    VOID NormalizePoints(
        PPOINT pptStart,
        PPOINT pptEnd)
    {
        INT n;

        if (pptStart->x > pptEnd->x) {
            n = pptEnd->x;
            pptEnd->x = pptStart->x;
            pptStart->x = n;
        }

        if (pptStart->y > pptEnd->y) {
            n = pptEnd->y;
            pptEnd->y = pptStart->y;
            pptStart->y = n;
        }
    }

    /******************************************************************************
     * HDC PASCAL StartRubberBanding(hwnd)
     *
     * PURPOSE: Sets up rubberbanding for all tools.
     *
     * PARAMS : HANDLE hDst : handle to box DC
     *
     * RETURNS :handle to destination display context
     *
     * SIDE EFFECTS: alters a few global flags for tracking
     *
     *****************************************************************************/

    STATICFN VOID NEAR StartRubberBanding(
        HWND hwnd)
    {
        hdcRubberBand = GetDC(hwnd);

        /*
         * Select a white pen, and a null brush (prevents drawing the
         * interior of rectangles and ellipses).
         */
        SelectObject(hdcRubberBand, GetStockObject(WHITE_PEN));
        SelectObject(hdcRubberBand, GetStockObject(NULL_BRUSH));

        fRubberBanding = TRUE;
    }

    /******************************************************************************
     * VOID PASCAL EndRubberBanding()
     *
     * PURPOSE: Stops rubberbanding rect. and cleans up
     *
     *****************************************************************************/

    STATICFN VOID NEAR EndRubberBanding(
        HWND hwnd)
    {
        ReleaseDC(hwnd, hdcRubberBand);
        fRubberBanding = FALSE;
    }


README.TXT

Message Compiler

SUMMARY
=======

The MC tool takes a message definition script as input, and writes out a
binary message table. It can be useful for obtaining a more complete
understanding of message tables.

MORE INFORMATION
================

There is an additional sample distributed with the Win32 SDK CD that
demonstrates how an application can make use of message tables. It may be
found in the following directory: Q_A\SAMPLES\MSGTABLE.


MC.C

/*++

Copyright 1991-1998 Microsoft Corporation

Module Name:

    mc.c

Abstract:

    This is the main source file for the Win32 Message Compiler (MC)

--*/

#include "mc.h"

#include "windows.h"

void
ConvertAppToOem( unsigned argc, char* argv[] )
/*++

Routine Description:

    Converts the command line from ANSI to OEM, and force the app
    to use OEM APIs

Arguments:

    argc - Standard C argument count.

    argv - Standard C argument strings.

Return Value:

    None.

--*/

{
    unsigned i;

    for( i=0; i<argc; i++ ) {
       CharToOem( argv[i], argv[i] );
    }
    SetFileApisToOEM();
}

void
McPrintUsage( void )
{
    fprintf( stderr, "usage: MC [-?vws] [-h dirspec] [-r dirspec] filename.mc\n" );
    fprintf( stderr, "       -? - displays this message\n" );
    fprintf( stderr, "       -v - gives verbose output.\n" );
    fprintf( stderr, "       -c - sets the Customer bit in all the message Ids.\n" );
    fprintf( stderr, "       -d - numeric values in header file in decimal.\n" );
    fprintf( stderr, "       -w - warns if message text contains non-OS/2 compatible inserts.\n" );
    fprintf( stderr, "       -s - insert symbolic name as first line of each message.\n" );
    fprintf( stderr, "       -h pathspec - gives the path of where to create the C include file\n" );
    fprintf( stderr, "                     Default is .\\\n" );
    fprintf( stderr, "       -r pathspec - gives the path of where to create the RC include file\n" );
    fprintf( stderr, "                     and the binary message resource files it includes.\n" );
    fprintf( stderr, "                     Default is .\\\n" );
    fprintf( stderr, "       filename.mc - gives the names of a message text file\n" );
    fprintf( stderr, "                     to compile.\n" );
}

int
_CRTAPI1 main(
    int argc,
    char *argv[]
    )
{
    char c, *s, *s1;
    int ShowUsage;

    ConvertAppToOem( argc, argv );
    FacilityNames = NULL;
    SeverityNames = NULL;
    LanguageNames = NULL;

    MessageIdTypeName = NULL;

    CurrentFacilityName =
    McAddName( &FacilityNames, "Application",  0x0, NULL );
    CurrentSeverityName =
    McAddName( &SeverityNames, "Success",       0x0, NULL );

    McAddName( &SeverityNames, "Informational", 0x1, NULL );
    McAddName( &SeverityNames, "Warning",       0x2, NULL );
    McAddName( &SeverityNames, "Error",         0x3, NULL );

    McAddName( &LanguageNames,
               "English",
               MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
               "MSG00001"
             );

    strcpy( HeaderFileName, ".\\" );
    strcpy( RcInclFileName, ".\\" );
    strcpy( BinaryMessageFileName, ".\\" );
    MessageFileName[ 0 ] = '\0';

    McInitLexer();

    VerboseOutput = FALSE;
    WarnOs2Compatible = FALSE;
    GenerateDecimalValues = FALSE;
    ShowUsage = FALSE;
    while (--argc) {
        s = *++argv;
        if (*s == '-' || *s == '/') {
            while (c = *++s) {
                switch( c ) {
                case '?':
                    McPrintUsage();
                    exit( 0 );
                    break;

                case 'c':
                    CustomerMsgIdBit = 0x1 << 29;
                    break;

                case 'v':
                    VerboseOutput = TRUE;
                    break;

                case 'd':
                    GenerateDecimalValues = TRUE;
                    break;

                case 'w':
                    WarnOs2Compatible = TRUE;
                    break;

                case 's':
                    InsertSymbolicName = TRUE;
                    break;

                case 'h':
                    if (--argc) {
                        strcpy( s1 = HeaderFileName, *++argv );
s1 += strlen( s1 );
                        s1 = CharPrev( HeaderFileName, s1 );
                        if (*s1 != '\\' && *s1 != '/') {
                            s1 = CharNext( s1 );
                            *s1 = '\\';
                            *++s1 = '\0';
    }
}
                    else {
                        argc++;
                        fprintf( stderr, "MC: missing argument for -%c switch\n", (USHORT)c );
                        ShowUsage = TRUE;
                        }
                    break;

                case 'r':
                    if (--argc) {
strcpy( s1 = RcInclFileName, *++argv );
                        s1 += strlen( s1 );
                        s1 = CharPrev( HeaderFileName, s1 );
                        if (*s1 != '\\' && *s1 != '/') {
                            s1 = CharNext( s1 );
                            *s1 = '\\';
                            *++s1 = '\0';
                        }
strcpy( BinaryMessageFileName, RcInclFileName );
                        }
                    else {
                        argc++;
                        fprintf( stderr, "MC: missing argument for -%c switch\n", (USHORT)c );
                        ShowUsage = TRUE;
                        }
                    break;

                default:
                    fprintf( stderr, "MC: Invalid switch: %c\n", (USHORT)c );
                    ShowUsage = TRUE;
                    break;
                    }
                }
            }
        else
        if (strlen( MessageFileName )) {
            fprintf( stderr, "MC: may only specify one message file to compile.\n" );
            ShowUsage = TRUE;
            }
        else {
            strcpy( MessageFileName, s );
            }
        }

    if (ShowUsage) {
        McPrintUsage();
        exit( 1 );
        }

    ResultCode = 1;
    if (McParseFile() && McBlockMessages() && McWriteBinaryFiles()) {
        ResultCode = 0;
        }
    else {
        McCloseInputFile();
        McCloseOutputFiles();
        }

    return( ResultCode );
}


MC.H

/*++

Copyright 1991-1998 Microsoft Corporation

Module Name:

    mc.h

Abstract:

    This is the main include file for the Win32 Message Compiler (MC)

Author:

    Steve Wood (stevewo) 21-Aug-1991

Revision History:

--*/

#if 0
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#endif

#include <process.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <limits.h>
#include <malloc.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <io.h>
#include <fcntl.h>
#include <conio.h>
#include <sys\types.h>
#include <sys\stat.h>

#ifdef TOOL

#define MAX_PATH 265

#else

#include <windows.h>

#endif

//
// Global constants
//

#define MCCHAR_END_OF_LINE_COMMENT    ';'

#define MCTOK_END_OF_FILE             0x00

#define MCTOK_NUMBER                  0x01
#define MCTOK_NAME                    0x02
#define MCTOK_EQUAL                   0x03
#define MCTOK_LEFT_PAREN              0x04
#define MCTOK_RIGHT_PAREN             0x05
#define MCTOK_COLON                   0x06
#define MCTOK_PLUS                    0x07
#define MCTOK_END_OF_LINE_COMMENT     0x08

#define MCTOK_MSGIDTYPE_KEYWORD       0x11
#define MCTOK_SEVNAMES_KEYWORD        0x12
#define MCTOK_FACILITYNAMES_KEYWORD   0x13
#define MCTOK_LANGNAMES_KEYWORD       0x14
#define MCTOK_MESSAGEID_KEYWORD       0x15
#define MCTOK_SEVERITY_KEYWORD        0x16
#define MCTOK_FACILITY_KEYWORD        0x17
#define MCTOK_SYMBOLNAME_KEYWORD      0x18
#define MCTOK_LANGUAGE_KEYWORD        0x19

//
// Global data types
//

typedef struct _LANGUAGE_INFO {
    struct _LANGUAGE_INFO *Next;
    ULONG Id;
    ULONG Length;
    PCHAR Text;
} LANGUAGE_INFO, *PLANGUAGE_INFO;

typedef struct _MESSAGE_INFO {
    struct _MESSAGE_INFO *Next;
    ULONG Id;
    ULONG Method;
    PCHAR SymbolicName;
    PCHAR EndOfLineComment;
    PLANGUAGE_INFO MessageText;
} MESSAGE_INFO, *PMESSAGE_INFO;

#define MSG_PLUS_ONE 0
#define MSG_PLUS_VALUE 1
#define MSG_ABSOLUTE 2

typedef struct _MESSAGE_BLOCK {
    struct _MESSAGE_BLOCK *Next;
    ULONG LowId;
    ULONG HighId;
    ULONG InfoLength;
    PMESSAGE_INFO LowInfo;
} MESSAGE_BLOCK, *PMESSAGE_BLOCK;

typedef struct _NAME_INFO {
    struct _NAME_INFO *Next;
    ULONG LastId;
    ULONG Id;
    PVOID Value;
    BOOLEAN Used;
    char Name[ 1 ];
} NAME_INFO, *PNAME_INFO;

//
// Global variables
//

int VerboseOutput;
int WarnOs2Compatible;
int InsertSymbolicName;
int GenerateDecimalValues;
int ResultCode;
ULONG CustomerMsgIdBit;

FILE *MessageFile;
char MessageFileName[ MAX_PATH ];
unsigned int MessageFileLineNumber;
unsigned int Token;
char TokenCharValue[ 256 ];
ULONG TokenNumericValue;
PNAME_INFO TokenKeyword;

FILE *HeaderFile;
char HeaderFileName[ MAX_PATH ];
FILE *RcInclFile;
char RcInclFileName[ MAX_PATH ];
FILE *BinaryMessageFile;
char BinaryMessageFileName[ MAX_PATH ];

char *MessageIdTypeName;

PNAME_INFO FacilityNames;
PNAME_INFO CurrentFacilityName;
PNAME_INFO SeverityNames;
PNAME_INFO CurrentSeverityName;
PNAME_INFO LanguageNames;
PNAME_INFO CurrentLanguageName;

PMESSAGE_INFO Messages;
PMESSAGE_INFO CurrentMessage;

//
// Functions defined in mc.c
//

void
McPrintUsage( void );

//
// Functions defined in mcparse.c
//

BOOLEAN
McParseFile( void );

BOOLEAN
McParseMessageDefinition( void );

BOOLEAN
McParseMessageText(
    PMESSAGE_INFO MessageInfo
    );

BOOLEAN
McParseNameList(
    PNAME_INFO *NameListHead,
    BOOLEAN ValueRequired,
    ULONG MaximumValue
    );

BOOLEAN
McParseName(
    PNAME_INFO NameListHead,
    PNAME_INFO *Result
    );

//
// Functions defined in mcout.c
//

BOOLEAN
McBlockMessages( void );

BOOLEAN
McWriteBinaryFiles( void );

//
// Functions defined in mclex.c
//

BOOLEAN
McInitLexer( void );

BOOLEAN
McOpenInputFile( void );

void
McFlushComments( void );

void
McCloseInputFile( void );

void
McCloseOutputFiles( void );

void
McInputError(
    char *Message,
    BOOLEAN Error,
    PVOID Argument
    );

char *
McGetLine( void );

void
McSkipLine( void );

char
McGetChar(
    BOOLEAN SkipWhiteSpace
    );

void
McUnGetChar(
    char c
    );

unsigned int
McGetToken(
    BOOLEAN KeywordExpected
    );

void
McUnGetToken( void );

char *
McSkipWhiteSpace(
    char *s
    );

//
// Functions defined in mcutil.c
//

PNAME_INFO
McAddName(
    PNAME_INFO *NameListHead,
    char *Name,
    ULONG Id,
    PVOID Value
    );

PNAME_INFO
McFindName(
    PNAME_INFO NameListHead,
    char *Name
    );

BOOLEAN
McCharToInteger(
    PCHAR String,
    int Base,
    PULONG Value
    );

char *
McMakeString(
    char *String
    );


MCLEX.C

/*++

Copyright 1991-1998 Microsoft Corporation

Module Name:

    mclex.c

Abstract:

    This file contains the input lexer for the Win32 Message Compiler (MC)

--*/

#include "mc.h"

char LineBuffer[ 256 ];
char *CurrentChar;
BOOLEAN ReturnCurrentToken;

PNAME_INFO KeywordNames;

typedef struct _COMMENT_INFO {
    struct _COMMENT_INFO *Next;
    char Text[ 1 ];
} COMMENT_INFO, *PCOMMENT_INFO;

PCOMMENT_INFO Comments, CurrentComment;

/*++

Routine Description:

    This fills in the Key words associated with the Message file format

Return Value:

    TRUE

--*/

BOOLEAN
McInitLexer( void )
{
    ReturnCurrentToken = FALSE;
    McAddName( &KeywordNames, "MessageIdTypedef",   MCTOK_MSGIDTYPE_KEYWORD,  NULL );
    McAddName( &KeywordNames, "SeverityNames",      MCTOK_SEVNAMES_KEYWORD,   NULL );
    McAddName( &KeywordNames, "FacilityNames",      MCTOK_FACILITYNAMES_KEYWORD,  NULL );
    McAddName( &KeywordNames, "LanguageNames",      MCTOK_LANGNAMES_KEYWORD,  NULL );
    McAddName( &KeywordNames, "MessageId",          MCTOK_MESSAGEID_KEYWORD,  NULL );
    McAddName( &KeywordNames, "Severity",           MCTOK_SEVERITY_KEYWORD,   NULL );
    McAddName( &KeywordNames, "Facility",           MCTOK_FACILITY_KEYWORD,   NULL );
    McAddName( &KeywordNames, "SymbolicName",       MCTOK_SYMBOLNAME_KEYWORD, NULL );
    McAddName( &KeywordNames, "Language",           MCTOK_LANGUAGE_KEYWORD,   NULL );
    return( TRUE );
}

BOOLEAN
McOpenInputFile( void )
{
    char SavedChar, *s, *FileName;
    BOOLEAN Result;

    s = MessageFileName;
    FileName = s;
    SavedChar = '\0';
    while (*s) {
        if (*s == '.') {
            SavedChar = '.';
            *s = '\0';
            break;
            }

        if (*s == ':' || *s == '\\' || *s == '/') {
            FileName = s+1;
            }

        s = CharNext(s);
}

    strcat( HeaderFileName, FileName );
    strcat( HeaderFileName, ".h" );
    strcat( RcInclFileName, FileName );
    strcat( RcInclFileName, ".rc" );

    if (SavedChar == '\0') {
        strcpy( s, ".mc" );
        }
    else {
        *s = SavedChar;
        }

    Result = FALSE;
    MessageFileLineNumber = 0;
    LineBuffer[ 0 ] = '\0';
    CurrentChar = NULL;

    MessageFile = fopen( MessageFileName, "rb" );
    if (MessageFile == NULL) {
        McInputError( "unable to open input file", TRUE, NULL );
        }
    else {
        HeaderFile = fopen( HeaderFileName, "wb" );
        if (HeaderFile == NULL) {
            McInputError( "unable to open output file - %s", TRUE, HeaderFileName );
            }
        else {
            RcInclFile = fopen( RcInclFileName, "wb" );
            if (RcInclFile == NULL) {
                McInputError( "unable to open output file - %s", TRUE, RcInclFileName );
                }
            else {
                Result = TRUE;
                }
            }
        }

    if (!Result) {
        McCloseInputFile();
        McCloseOutputFiles();
        }
    else {
        return( TRUE );
        }
}

void
McCloseInputFile( void )
{
    if (MessageFile != NULL) {
        fclose( MessageFile );
        MessageFile = NULL;
        CurrentChar = NULL;
        LineBuffer[ 0 ] = '\0';
        }
}

void
McCloseOutputFiles( void )
{
    if (HeaderFile != NULL) {
        fclose( HeaderFile );
        }

    if (RcInclFile != NULL) {
        fclose( RcInclFile );
        }
}

void
McInputError(
    char *Message,
    BOOLEAN Error,
    PVOID Argument
    )
{
    fprintf( stderr,
             "%s (%d) : %s: ",
             MessageFileName,
             MessageFileLineNumber,
             Error ? "Error" : "Warning"
           );

    fprintf( stderr, Message, Argument );
    fprintf( stderr, "\n" );
}

/*++

Routine Description:

   This retrieves the current line then moves down to the
   next line in the message file.

Return Value:

   Returns the current line of in the file.

--*/

char *
McGetLine( void )
{
    char *s;

    if (MessageFile == NULL || feof( MessageFile )) {
        return( NULL );
        }

    if (fgets( LineBuffer, sizeof( LineBuffer ), MessageFile ) == NULL) {
        return( NULL );
        }

    s = LineBuffer + strlen( LineBuffer );
    if (s > LineBuffer && *--s == '\n') {
        if (s > LineBuffer && *--s != '\r') {
            *++s = '\r';
            *++s = '\n';
            *++s = '\0';
            }
        }

    MessageFileLineNumber++;
    return( CurrentChar = LineBuffer );
}

void
McSkipLine( void )
{
    CurrentChar = NULL;
}

/*++

Routine Description:

   This retrieves the character at the current position of the line
   buffer then advances to the next position. If the end of the line
   is reached another line is retrieve. If the end of the file is reached
   this returns with a NULL character. One is optionally able to flush
   the white space from the line.

Arguments:

   A boolean specifying whether whitespace should be consider significant.

Return Value:

   Returns the character in the current line.

--*/

char
McGetChar(
    BOOLEAN SkipWhiteSpace
    )
{
    BOOLEAN SawWhiteSpace;
    BOOLEAN SawNewLine;
    PCOMMENT_INFO p;

    SawWhiteSpace = FALSE;

tryagain:
    SawNewLine = FALSE;
    if (CurrentChar == NULL) {
        McGetLine();
        if (CurrentChar == NULL) {
            return( '\0' );
            }

        SawNewLine = TRUE;
        }

    if (SkipWhiteSpace) {
        while (*CurrentChar <= ' ') {
            SawWhiteSpace = TRUE;
            if (!*CurrentChar++) {
                CurrentChar = NULL;
                break;
                }
            }
        }

    if (SawNewLine) {
        if (CurrentChar != NULL && *CurrentChar == MCCHAR_END_OF_LINE_COMMENT) {
            p = malloc( sizeof( *p ) + strlen( ++CurrentChar ) );
            p->Next = NULL;
            strcpy( p->Text, CurrentChar );
            if (CurrentComment == NULL) {
                Comments = p;
                }
            else {
                CurrentComment->Next = p;
                }
            CurrentComment = p;

            CurrentChar = NULL;
            }
        }

    if (CurrentChar == NULL && SkipWhiteSpace) {
        goto tryagain;
        }

    if (SawWhiteSpace) {
        return( ' ' );
        }
    else {
        return( *CurrentChar++ );
        }
}

void
McFlushComments( void )
{
    PCOMMENT_INFO p;

    while (p = Comments) {
        fprintf( HeaderFile, "%s", p->Text );

        Comments = Comments->Next;
        free( p );
        }
    Comments = NULL;
    CurrentComment = NULL;

    fflush( HeaderFile );
    return;
}

void
McUnGetChar(
    char c
    )
{
    if (CurrentChar > LineBuffer) {
        *--CurrentChar = c;
        }
    else {
        LineBuffer[ 0 ] = c;
        LineBuffer[ 1 ] = '\0';
        CurrentChar = LineBuffer;
        }
}

/*++

Routine Description:

    Breaks input line into "tokens values" as defined in MC.H.

Arguments:

    A boolean designating whether keywords are required.

Return Value:

   Returns the the token corresponding to the "token value" For example
   with a token of type MCTOK_NUMBER the value would be a string
   representation of an integer.

--*/

unsigned int
McGetToken(
    BOOLEAN KeywordExpected
    )
{
    char c, *dst;

    if (ReturnCurrentToken) {
        ReturnCurrentToken = FALSE;
        if (Token == MCTOK_NAME && KeywordExpected) {
            TokenKeyword = McFindName( KeywordNames, TokenCharValue );
            if (TokenKeyword == NULL) {
                McInputError( "expected keyword - %s", TRUE, TokenCharValue );
                Token = MCTOK_END_OF_FILE;
                }
            else {
                Token = (unsigned int)TokenKeyword->Id;
                }
            }

        return( Token );
        }

    Token = MCTOK_END_OF_FILE;
    dst = TokenCharValue;
    *dst = '\0';
    TokenNumericValue = 0L;

    while (TRUE) {
        c = McGetChar( (BOOLEAN)(Token == MCTOK_END_OF_FILE) );
        if (Token == MCTOK_NUMBER) {
            if (isdigit( c ) ||
                c == 'x' ||
                (c >= 'a' && c <= 'f') ||
                (c >= 'A' && c <= 'F')
               ) {
                *dst++ = c;
                }
            else {
                McUnGetChar( c );
                *dst = '\0';

                if (!McCharToInteger( TokenCharValue, 0, &TokenNumericValue )) {
                    McInputError( "invalid number - %s", TRUE, TokenCharValue );
                    Token = MCTOK_END_OF_FILE;
                    }
                else {
                    return( Token );
                    }
                }
            }
        else
        if (Token == MCTOK_NAME) {
            if (iscsym( c )) {
                *dst++ = c;
                }
            else {
                McUnGetChar( c );
                *dst = '\0';

                if (KeywordExpected) {
                    TokenKeyword = McFindName( KeywordNames, TokenCharValue );
                    if (TokenKeyword == NULL) {
                        McInputError( "expected keyword - %s", TRUE, TokenCharValue );
                        Token = MCTOK_END_OF_FILE;
                        }
                    else {
                        Token = (unsigned int)TokenKeyword->Id;
                        }
                    }
                return( Token );
                }
            }
        else
        if (isdigit( c )) {
            *dst++ = c;
            Token = MCTOK_NUMBER;
            }
        else
        if (iscsymf( c )) {
            *dst++ = c;
            Token = MCTOK_NAME;
            }
        else
        if (c == '=') {
            *dst++ = c;
            *dst = '\0';
            Token = MCTOK_EQUAL;
            return( Token );
            }
        else
        if (c == '(') {
            *dst++ = c;
            *dst = '\0';
            Token = MCTOK_LEFT_PAREN;
            return( Token );
            }
        else
        if (c == ')') {
            *dst++ = c;
            *dst = '\0';
            Token = MCTOK_RIGHT_PAREN;
            return( Token );
            }
        else
        if (c == ':') {
            *dst++ = c;
            *dst = '\0';
            Token = MCTOK_COLON;
            return( Token );
            }
        else
        if (c == '+') {
            *dst++ = c;
            *dst = '\0';
            Token = MCTOK_PLUS;
            return( Token );
            }
        else
        if (c == ' ') {
            }
        else
        if (c == MCCHAR_END_OF_LINE_COMMENT) {
            Token = MCTOK_END_OF_LINE_COMMENT;
            strcpy( TokenCharValue, CurrentChar );
            CurrentChar = NULL;
            return( Token );
            }
        else
        if (c == '\0') {
            return( Token );
            }
        else {
            McInputError( "invalid character '%c'", TRUE, (PVOID)(ULONG)(UCHAR)c );
            }
        }
}

void
McUnGetToken( void )
{
    ReturnCurrentToken = TRUE;
}

char *
McSkipWhiteSpace(
    char *s
    )
{
    while (*s <= ' ') {
        if (!*s++) {
            s = NULL;
            break;
            }
        }

    return( s );
}


MCOUT.C

/*++

Copyright 1991-1998 Microsoft Corporation

Module Name:

    mcout.c

Abstract:

    This file contains the output functions of the Win32 Message Compiler (MC)

--*/

#include "mc.h"

PMESSAGE_BLOCK MessageBlocks = NULL;
int NumberOfBlocks = 0;

/*++

Routine Description:

Arguments:

Return Value:

--*/

BOOLEAN
McBlockMessages( void )
{
    PMESSAGE_BLOCK p, *pp;
    PMESSAGE_INFO MessageInfo;

    pp = &MessageBlocks;
    p = NULL;

    MessageInfo = Messages;
    while (MessageInfo) {
        if (p) {
            if (p->HighId+1 == MessageInfo->Id) {
                p->HighId += 1;
                }
            else {
                pp = &p->Next;
                }
            }

        if (!*pp) {
            NumberOfBlocks += 1;
            p = malloc( sizeof( *p ) );
            p->Next = NULL;
            p->LowId = MessageInfo->Id;
            p->HighId = MessageInfo->Id;
            p->LowInfo = MessageInfo;
            *pp = p;
            }

        MessageInfo = MessageInfo->Next;
        }

    return( TRUE );
}

/*++

Routine Description:

Arguments:

Return Value:

--*/

BOOLEAN
McWriteBinaryFiles( void )
{
    PNAME_INFO LanguageName, *pp;
    PLANGUAGE_INFO LanguageInfo;
    PMESSAGE_INFO MessageInfo;
    PMESSAGE_BLOCK BlockInfo;
    char *FileName;
    ULONG cb;
    ULONG MessageOffset;
    MESSAGE_RESOURCE_ENTRY MessageEntry;
    MESSAGE_RESOURCE_BLOCK MessageBlock;
    MESSAGE_RESOURCE_DATA  MessageData;
    ULONG Zeroes = 0;
    ULONG NumberOfMessages;

    FileName = BinaryMessageFileName;
    FileName += strlen( FileName );

    pp = &LanguageNames;
    while (LanguageName = *pp) {
        pp = &LanguageName->Next;
        if (!LanguageName->Used) {
            continue;
            }

        strcpy( FileName, LanguageName->Value );
        strcat( FileName, ".bin" );
        if (!(BinaryMessageFile = fopen( BinaryMessageFileName, "wb" ))) {
            McInputError( "unable to open output file - %s", TRUE, BinaryMessageFileName );
            return( FALSE );
            }

        if (VerboseOutput) {
            fprintf( stderr, "Writing %s\n", BinaryMessageFileName );
            }

        fprintf( RcInclFile, "LANGUAGE 0x%x,0x%x\r\n",
                             PRIMARYLANGID( LanguageName->Id ),
                             SUBLANGID( LanguageName->Id )
               );

        fprintf( RcInclFile, "1 11 %s\r\n", FileName );

        NumberOfMessages = 0L;

        MessageData.NumberOfBlocks = NumberOfBlocks;
        MessageOffset = fwrite( &MessageData,
                                1,
                                (size_t)FIELD_OFFSET( MESSAGE_RESOURCE_DATA,
                                                      Blocks[ 0 ]
                                                    ),
                                BinaryMessageFile
                              );
        MessageOffset += NumberOfBlocks * sizeof( MessageBlock );

        BlockInfo = MessageBlocks;
        while (BlockInfo) {
            MessageBlock.LowId = BlockInfo->LowId;
            MessageBlock.HighId = BlockInfo->HighId;
            MessageBlock.OffsetToEntries = MessageOffset;
            fwrite( &MessageBlock, 1, sizeof( MessageBlock ), BinaryMessageFile );

            BlockInfo->InfoLength = 0;
            MessageInfo = BlockInfo->LowInfo;
            while (MessageInfo != NULL && MessageInfo->Id <= BlockInfo->HighId) {
                LanguageInfo = MessageInfo->MessageText;
                while (LanguageInfo) {
                    if (LanguageInfo->Id == LanguageName->Id) {
                        break;
                        }
                    else {
                        LanguageInfo = LanguageInfo->Next;
                        }
                    }

                if (LanguageInfo != NULL) {
                    cb = FIELD_OFFSET( MESSAGE_RESOURCE_ENTRY, Text[ 0 ] ) +
                         LanguageInfo->Length + 1;

                    cb = (cb + 3) & ~3;
                    BlockInfo->InfoLength += cb;
                    }
                else {
                    fprintf( stderr,
                             "MC: No %s language text for %s\n",
                             LanguageName->Name,
                             MessageInfo->SymbolicName
                           );
                    fclose( BinaryMessageFile );
                    return( FALSE );
                    }

                MessageInfo = MessageInfo->Next;
                }

            if (VerboseOutput) {
                fprintf( stderr, "    [%08lx .. %08lx] - %lu bytes\n",
                         BlockInfo->LowId,
                         BlockInfo->HighId,
                         BlockInfo->InfoLength
                       );
                }

            MessageOffset += BlockInfo->InfoLength;
            BlockInfo = BlockInfo->Next;
            }

        BlockInfo = MessageBlocks;
        while (BlockInfo) {
            MessageInfo = BlockInfo->LowInfo;
            while (MessageInfo != NULL && MessageInfo->Id <= BlockInfo->HighId) {
                LanguageInfo = MessageInfo->MessageText;
                while (LanguageInfo) {
                    if (LanguageInfo->Id == LanguageName->Id) {
                        break;
                        }
                    else {
                        LanguageInfo = LanguageInfo->Next;
                        }
                    }

                if (LanguageInfo != NULL) {
                    cb = FIELD_OFFSET( MESSAGE_RESOURCE_ENTRY, Text[ 0 ] ) +
                         LanguageInfo->Length + 1;

                    cb = (cb + 3) & ~3;

                    MessageEntry.Length = (USHORT)cb;
                    MessageEntry.Flags = 0;

                    cb = fwrite( &MessageEntry,
                                 1,
                                 (size_t)FIELD_OFFSET( MESSAGE_RESOURCE_ENTRY,
                                                       Text[ 0 ]
                                                     ),
                                 BinaryMessageFile
                               );
                    cb += fwrite( LanguageInfo->Text,
                                  1,
                                  (size_t)LanguageInfo->Length,
                                  BinaryMessageFile
                                );

                    NumberOfMessages++;

                    cb = MessageEntry.Length - cb;
                    if (cb) {
                        fwrite( &Zeroes,
                                1,
                                (size_t)cb,
                                BinaryMessageFile
                              );
                        }
                    }

                MessageInfo = MessageInfo->Next;
                }

            BlockInfo = BlockInfo->Next;
            }

        if (VerboseOutput) {
            fprintf( stderr, "    Total of %lu messages, %lu bytes\n",
                             NumberOfMessages,
                             ftell( BinaryMessageFile )
                   );
            }

        fclose( BinaryMessageFile );
        }

    return( TRUE );
}


MCPARSE.C

/*++

Copyright 1991-1998 Microsoft Corporation

Module Name:

    mcparse.c

Abstract:

    This file contains the parse logic for the Win32 Message Compiler (MC)

--*/

#include "mc.h"

BOOLEAN
McParseFile( void )
{
    unsigned int t;
    BOOLEAN FirstMessageDefinition = TRUE;
    PNAME_INFO p;

    if (!McOpenInputFile()) {
        fprintf( stderr, "MC: Unable to open %s for input\n", MessageFileName );
        return( FALSE );
        }

    fprintf( stderr, "MC: Compiling %s\n", MessageFileName );
    while ((t = McGetToken( TRUE )) != MCTOK_END_OF_FILE) {
        switch (t) {
        case MCTOK_MSGIDTYPE_KEYWORD:
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if ((t = McGetToken( FALSE )) == MCTOK_NAME) {
                    MessageIdTypeName = McMakeString( TokenCharValue );
                    }
                else {
                    McInputError( "Symbol name must follow %s=", TRUE, TokenKeyword->Name );
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            break;

        case MCTOK_SEVNAMES_KEYWORD:
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if ((t = McGetToken( FALSE )) == MCTOK_LEFT_PAREN) {
                    if (!McParseNameList( &SeverityNames, FALSE, 0x3L )) {
                        return( FALSE );
                        }
                    }
                else {
                    McInputError( "Left parenthesis name must follow %s=", TRUE, TokenKeyword->Name );
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            break;

        case MCTOK_FACILITYNAMES_KEYWORD:
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if ((t = McGetToken( FALSE )) == MCTOK_LEFT_PAREN) {
                    if (!McParseNameList( &FacilityNames, FALSE, 0xFFFL )) {
                        return( FALSE );
                        }
                    }
                else {
                    McInputError( "Left parenthesis name must follow %s=", TRUE, TokenKeyword->Name );
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            break;

        case MCTOK_LANGNAMES_KEYWORD:
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if ((t = McGetToken( FALSE )) == MCTOK_LEFT_PAREN) {
                    if (!McParseNameList( &LanguageNames, TRUE, 0xFFFFL )) {
                        return( FALSE );
                        }
                    }
                else {
                    McInputError( "Left parenthesis name must follow %s=", TRUE, TokenKeyword->Name );
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            break;

        case MCTOK_MESSAGEID_KEYWORD:
            McUnGetToken();
            if (FirstMessageDefinition) {
                FirstMessageDefinition = FALSE;
                McFlushComments();
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//  Values are 32 bit values layed out as follows:\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1\r\n" );
                fprintf( HeaderFile, "//   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0\r\n" );
                fprintf( HeaderFile, "//  +---+-+-+-----------------------+-------------------------------+\r\n" );
                fprintf( HeaderFile, "//  |Sev|C|R|     Facility          |               Code            |\r\n" );
                fprintf( HeaderFile, "//  +---+-+-+-----------------------+-------------------------------+\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//  where\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//      Sev - is the severity code\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//          00 - Success\r\n" );
                fprintf( HeaderFile, "//          01 - Informational\r\n" );
                fprintf( HeaderFile, "//          10 - Warning\r\n" );
                fprintf( HeaderFile, "//          11 - Error\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//      C - is the Customer code flag\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//      R - is a reserved bit\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//      Facility - is the facility code\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "//      Code - is the facility's status code\r\n" );
                fprintf( HeaderFile, "//\r\n" );

                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "// Define the facility codes\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                p = FacilityNames;
                while( p ) {
                    if (p->Value) {
                        fprintf( HeaderFile, GenerateDecimalValues ?
                                             "#define %-32s %ld\r\n" :
                                             "#define %-32s 0x%lX\r\n",
                                 p->Value, p->Id
                               );
                        }

                    p = p->Next;
                    }
                fprintf( HeaderFile, "\r\n" );
                fprintf( HeaderFile, "\r\n" );

                fprintf( HeaderFile, "//\r\n" );
                fprintf( HeaderFile, "// Define the severity codes\r\n" );
                fprintf( HeaderFile, "//\r\n" );
                p = SeverityNames;
                while( p ) {
                    if (p->Value) {
                        fprintf( HeaderFile, GenerateDecimalValues ?
                                             "#define %-32s %ld\r\n" :
                                             "#define %-32s 0x%lX\r\n",
                                 p->Value, p->Id
                               );
                        }

                    p = p->Next;
                    }
                fprintf( HeaderFile, "\r\n" );
                fprintf( HeaderFile, "\r\n" );
                }

            if (!McParseMessageDefinition()) {
                return( FALSE );
                }
            break;

        default:
            McInputError( "Invalid message file token - '%s'", TRUE, TokenCharValue );
            return( FALSE );
            break;
        }
    }

    McFlushComments();
    return( TRUE );
}

BOOLEAN
McParseMessageDefinition( void )
{
    unsigned int t;
    PMESSAGE_INFO MessageInfo;
    BOOLEAN MessageIdSeen;
    PMESSAGE_INFO MessageInfoTemp;

    McFlushComments();

    MessageInfo = malloc( sizeof( *MessageInfo ) );
    MessageInfo->Next = NULL;
    MessageInfo->Id = 0;
    MessageInfo->Method = MSG_PLUS_ONE;
    MessageInfo->SymbolicName = NULL;
    MessageInfo->EndOfLineComment = NULL;
    MessageInfo->MessageText = NULL;
    MessageIdSeen = FALSE;

    while ((t = McGetToken( TRUE )) != MCTOK_END_OF_FILE) {
        switch (t) {
        case MCTOK_MESSAGEID_KEYWORD:
            if (MessageIdSeen) {
                McInputError( "Invalid message definition - text missing.", TRUE, NULL );
                return( FALSE );
                }

            MessageIdSeen = TRUE;
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if ((t = McGetToken( FALSE )) == MCTOK_NUMBER) {
                    MessageInfo->Id = TokenNumericValue;
                    MessageInfo->Method = MSG_ABSOLUTE;
                    }
                else
                if (t == MCTOK_PLUS) {
                    if ((t = McGetToken( FALSE )) == MCTOK_NUMBER) {
                        MessageInfo->Id = TokenNumericValue;
                        MessageInfo->Method = MSG_PLUS_VALUE;
                        }
                    else {
                        McInputError( "Number must follow %s=+", TRUE, TokenKeyword->Name );
                        return( FALSE );
                        }
                    }
                else {
                    McUnGetToken();
                    }

                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            break;

        case MCTOK_SEVERITY_KEYWORD:
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if (!McParseName( SeverityNames, &CurrentSeverityName )) {
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            break;

        case MCTOK_FACILITY_KEYWORD:
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if (!McParseName( FacilityNames, &CurrentFacilityName )) {
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            break;

        case MCTOK_SYMBOLNAME_KEYWORD:
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if ((t = McGetToken( FALSE )) == MCTOK_NAME) {
                    MessageInfo->SymbolicName = McMakeString( TokenCharValue );
                    }
                else {
                    McInputError( "Symbol name must follow %s=+", TRUE, TokenKeyword->Name );
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            break;

        case MCTOK_END_OF_LINE_COMMENT:
            MessageInfo->EndOfLineComment = McMakeString( TokenCharValue );
            break;

        case MCTOK_LANGUAGE_KEYWORD:
            McUnGetToken();

            if (MessageInfo->Method == MSG_PLUS_ONE) {
                MessageInfo->Id = CurrentFacilityName->LastId + 1;
                }
            else
            if (MessageInfo->Method == MSG_PLUS_VALUE) {
                MessageInfo->Id = CurrentFacilityName->LastId + MessageInfo->Id;
                }

            if (MessageInfo->Id > 0xFFFFL) {
                McInputError( "Message Id value (%lx) too large", TRUE, (PVOID)MessageInfo->Id );
                return( FALSE );
                }

            MessageInfo->Id |= (CurrentSeverityName->Id << 30) |
                               CustomerMsgIdBit |
                               (CurrentFacilityName->Id << 16);

            fprintf( HeaderFile, "//\r\n" );
            if (MessageInfo->SymbolicName) {
                fprintf( HeaderFile, "// MessageId: %s\r\n",
                                     MessageInfo->SymbolicName
                       );
                }
            else {
                fprintf( HeaderFile, "// MessageId: 0x%08lXL (No symbolic name defined)\r\n",
                                     MessageInfo->Id
                       );
                }

            fprintf( HeaderFile, "//\r\n" );
            fprintf( HeaderFile, "// MessageText:\r\n" );
            fprintf( HeaderFile, "//\r\n" );

            if (McParseMessageText( MessageInfo )) {
                fprintf( HeaderFile, "//\r\n" );
                if (MessageInfo->SymbolicName) {

                    if (MessageIdTypeName != NULL) {
                        fprintf( HeaderFile, GenerateDecimalValues ?
                                             "#define %-32s ((%s)%ldL)" :
                                             "#define %-32s ((%s)0x%08lXL)",
                                             MessageInfo->SymbolicName,
                                             MessageIdTypeName,
                                             MessageInfo->Id
                               );
                        }
                    else {
                        fprintf( HeaderFile, GenerateDecimalValues ?
                                             "#define %-32s %ldL" :
                                             "#define %-32s 0x%08lXL",
                                             MessageInfo->SymbolicName,
                                             MessageInfo->Id
                               );
                        }
                    }

                if (MessageInfo->EndOfLineComment) {
                    fprintf( HeaderFile, "    %s", MessageInfo->EndOfLineComment );
                    }
                else {
                    fprintf( HeaderFile, "\r\n" );
                    }
                fprintf( HeaderFile, "\r\n" );

                if (Messages == NULL) {
                    Messages = MessageInfo;
                    }
                else {
                    MessageInfoTemp = Messages;

                    //
                    //  Scan the existing messages to see if this message
                    //  exists in the message file.
                    //
                    //  If it does, generate and error for the user.
                    //

                    while (MessageInfoTemp != NULL) {

                        if (MessageInfoTemp->Id == MessageInfo->Id) {
                            McInputError( "Duplicate message ID - 0x%lx", FALSE, (PVOID)MessageInfo->Id );
                            }

                        MessageInfoTemp = MessageInfoTemp->Next;
                        }

                    CurrentMessage->Next = MessageInfo;
                    }

                CurrentMessage = MessageInfo;
                CurrentFacilityName->LastId = MessageInfo->Id & 0xFFFF;
                return( TRUE );
                }
            else {
                return( FALSE );
                }

        default:
            McInputError( "Invalid message definition token - '%s'", TRUE, TokenCharValue );
            return( FALSE );
            }
        }

    return( FALSE );
}

char MessageTextBuffer[ 8192 ];

BOOLEAN
McParseMessageText(
    PMESSAGE_INFO MessageInfo
    )
{
    PLANGUAGE_INFO MessageText, *pp;
    char *src, *dst;
    unsigned int t, n;
    BOOLEAN FirstLanguageProcessed;

    pp = &MessageInfo->MessageText;

    FirstLanguageProcessed = FALSE;
    while ((t = McGetToken( TRUE )) != MCTOK_END_OF_FILE) {
        if (t == MCTOK_LANGUAGE_KEYWORD) {
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if (!McParseName( LanguageNames, &CurrentLanguageName )) {
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign must follow %s", TRUE, TokenKeyword->Name );
                return( FALSE );
                }
            }
        else {
            McUnGetToken();
            break;
            }

        MessageText = malloc( sizeof( *MessageText ) );
        MessageText->Next = NULL;
        MessageText->Id = CurrentLanguageName->Id;
        MessageText->Length = 0;
        MessageText->Text = NULL;

        dst = MessageTextBuffer;
        while (src = McGetLine()) {
            if (!strcmp( src, ".\r\n" )) {
                if (MessageText->Length == 0) {
                    if (MessageInfo->SymbolicName) {
                        strcpy( dst, MessageInfo->SymbolicName );
                        }
                    else {
                        sprintf( dst, "No symbolic name defined for0x%08lXL" );
                        }

                    strcat( dst, "\r\n" );
                    if (!FirstLanguageProcessed) {
                        fprintf( HeaderFile, "//  %s", dst );
                        }

                    n = strlen( dst );
                    dst += n;
                    MessageText->Length += n;
                    }

                McSkipLine();
                break;
                }
            else
            if (!strnicmp( src, "LanguageId=", 11 ) ||
                !strnicmp( src, "MessageId=", 10 )
               ) {
                McInputError( "Unterminated message definition", TRUE, NULL );
                return( FALSE );
                }

            if (!FirstLanguageProcessed) {
                fprintf( HeaderFile, "//  %s", src );
                }

            n = strlen( src );
            if (MessageText->Length + n > sizeof( MessageTextBuffer )) {
                McInputError( "Message text too long - > %ld", TRUE,
                              (PVOID)(ULONG)sizeof( MessageTextBuffer )
                            );
                return( FALSE );
                }

            strcpy( dst, src );
            dst += n;
            MessageText->Length += n;
            }
        *dst = '\0';

        n = ((USHORT)MessageText->Length)+1;
        MessageText->Text = malloc( n );
        memcpy( MessageText->Text, MessageTextBuffer, n );
        *pp = MessageText;
        pp = &MessageText->Next;
        FirstLanguageProcessed = TRUE;
        }

    return( TRUE );
}

BOOLEAN
McParseNameList(
    PNAME_INFO *NameListHead,
    BOOLEAN ValueRequired,
    ULONG MaximumValue
    )
{
    unsigned int t;
    PNAME_INFO p;
    char *Name;
    ULONG Id;
    PVOID Value;

    while ((t = McGetToken( FALSE )) != MCTOK_END_OF_FILE) {
        if (t == MCTOK_RIGHT_PAREN) {
            return( TRUE );
            }

        if (t == MCTOK_NAME) {
            Name = McMakeString( TokenCharValue );
            Id = 0;
            Value = NULL;
            if ((t = McGetToken( FALSE )) == MCTOK_EQUAL) {
                if ((t = McGetToken( FALSE )) == MCTOK_NUMBER) {
                    Id = TokenNumericValue;
                    if ((t = McGetToken( FALSE )) == MCTOK_COLON) {
                        if ((t = McGetToken( FALSE )) == MCTOK_NAME) {
                            Value = McMakeString( TokenCharValue );
                            }
                        else {
                            McInputError( "File name must follow =%ld:", TRUE, (PVOID)Id );
                            return( FALSE );
                            }
                        }
                    else {
                        if (ValueRequired) {
                            McInputError( "Colon must follow =%ld", TRUE, (PVOID)Id );
                            return( FALSE );
                            }

                        McUnGetToken();
                        }
                    }
                else {
                    McInputError( "Number must follow %s=", TRUE, Name );
                    return( FALSE );
                    }
                }
            else {
                McInputError( "Equal sign name must follow %s", TRUE, Name );
                return( FALSE );
                }

            if (Id > MaximumValue) {
                McInputError( "Value is too large (> %lx)", TRUE, (PVOID)MaximumValue );
                return( FALSE );
                }

            p = McAddName( NameListHead, Name, Id, Value );
            free( Name );
            }
        }

    return( FALSE );
}

BOOLEAN
McParseName(
    PNAME_INFO NameListHead,
    PNAME_INFO *Result
    )
{
    unsigned int t;
    PNAME_INFO p;

    if ((t = McGetToken( FALSE )) == MCTOK_NAME) {
        p = McFindName( NameListHead, TokenCharValue );
        if (p != NULL) {
            *Result = p;
            return( TRUE );
            }
        else {
            McInputError( "Invalid name - %s", TRUE, TokenCharValue );
            }
        }
    else {
        McInputError( "Missing name after %s=", TRUE, TokenKeyword->Name );
        }

    return( FALSE );
}


MCUTIL.C

/*++

Copyright 1991-1998 Microsoft Corporation

Module Name:

    mcutil.c

Abstract:

    This file contains utility functions for the Win32 Message Compiler (MC)

--*/

#include "mc.h"

/*++

Routine Description:

    This routine simply writes/overwrites Name, ID and Value
    to a link list of NAME_INFO structures.

Arguments:

    PNAME_INFO
    Keyword such as "Facility", "Serverity", ...
    Lauguage Identifier; a combination of a Primary Language ID,
    Sublanguage ID pair such as LANG_NEUTRAL, SUBLANG_NEUTRAL
    An option value specfic to the keyword.

Return Value:

    The list PNAME_INFO

--*/

PNAME_INFO
McAddName(
    PNAME_INFO *NameListHead,
    char *Name,
    ULONG Id,
    PVOID Value
    )
{
    PNAME_INFO p;
    int n;

    while (p = *NameListHead) {
        if (!(n = stricmp( p->Name, Name ))) {
            if (p->Id != Id) {
                McInputError( "Redefining value of %s", FALSE, Name );
                }

            p->Id = Id;
            p->Value = Value;
            p->Used = FALSE;
            return( p );
            }
        else
        if (n < 0) {
            break;
            }

        NameListHead = &p->Next;
        }

    p = malloc( sizeof( *p ) + strlen( Name ) );
    p->LastId = 0;
    p->Id = Id;
    p->Value = Value;
    p->Used = FALSE;
    strcpy( p->Name, Name );
    p->Next = *NameListHead;
    *NameListHead = p;
    return( p );
}

/*++

Routine Description:

    This routine returns the NAME_INFO structure cooresponding to the
    given Keyword.

Arguments:

    PNAME_INFO
    Keyword such as "Facility", "Serverity", ...

Return Value:

    Either the NAME_INFO structure cooresponding to the keyname or NULL

--*/

PNAME_INFO
McFindName(
    PNAME_INFO NameListHead,
    char *Name
    )
{
    PNAME_INFO p;

    p = NameListHead;
    while (p) {
        if (!stricmp( p->Name, Name )) {
            p->Used = TRUE;
            break;
            }

        p = p->Next;
        }

    return( p );
}

/*++

Routine Description:

    This converts a string value to the corresponding integer value
    in the specified base.

Arguments:

    String image of an integer
    Base of resultant integer
    Pointer to a unsigned long

Return Value:

    A unsigned long

--*/

BOOLEAN
McCharToInteger(
    PCHAR String,
    int Base,
    PULONG Value
    )
{
    CHAR c;
    ULONG Result, Digit, Shift;

    c = *String;
    String = CharNext(String);
    if (!Base) {
        Base = 10;
        Shift = 0;
        if (c == '0') {
            c = *String++;
            if (c == 'x') {
                Base = 16;
                Shift = 4;
                }
            else
            if (c == 'o') {
                Base = 8;
                Shift = 3;
                }
            else
            if (c == 'b') {
                Base = 2;
                Shift = 1;
                }
            else {
                String--;
                }

            c = *String++;
            }
        }
    else {
        switch( Base ) {
            case 16:    Shift = 4;  break;
            case  8:    Shift = 3;  break;
            case  2:    Shift = 1;  break;
            case 10:    Shift = 0;  break;
            default:    return( FALSE );
            }
        }

    Result = 0;
    while (c) {
        if (c >= '0' && c <= '9') {
            Digit = c - '0';
            }
        else
        if (c >= 'A' && c <= 'F') {
            Digit = c - 'A' + 10;
            }
        else
        if (c >= 'a' && c <= 'f') {
            Digit = c - 'a' + 10;
            }
        else {
            break;
            }

        if ((int)Digit >= Base) {
            break;
            }

        if (Shift == 0) {
            Result = (Base * Result) + Digit;
            }
        else {
            Result = (Result << Shift) | Digit;
            }

        c = *String;
        String = CharNext(String);
}

    *Value = Result;
    return( TRUE );
}

/*++

Routine Description:

    Duplicates a string

Arguments:

    A string value

Return Value:

    The duplicated string value

--*/

char *
McMakeString(
    char *String
    )
{
    char *s;

    s = malloc( strlen( String ) + 1 );
    strcpy( s, String );
    return( s );
}


README.TXT

Porting Tool

SUMMARY
=======

The PORTTOOL tool is used to help identify the places in your 16-bit
Windows-based source code that might need to change to make it
Win32-compatible. The tool provides a brief description of the problem
and a possible solution.

MORE INFORMATION
================

The porting tool has two operating modes. It can be run interactively or in
the background. The background mode inserts comments in the locations where
issues are found.

In the file that the porting tool uses to help identify potential issues
(PORT.INI), there is a section available for entering programmer-defined
strings. This allows the porting tool to be updated when new issues arise.


PORT.C

#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include "portpriv.h"
#include "port.h"

// redefine dbcs versions of common string functions
#ifdef strnicmp
#undef strnicmp
#endif
#define strnicmp    My_mbsnicmp
#define strncmp     My_mbsncmp
#define strncpy     My_mbsncpy
#define strtok      My_mbstok

/* globals for this module */
HANDLE  hMMFile = 0;
HANDLE  hDLL;
BOOL  bInit = FALSE;

/* function prototypes for private module functions */
void WINAPI FreePortData ();
int  WINAPI GetFirstToken (LPPORT *);
int  WINAPI GetNextToken (LPPORT *);
void WINAPI IgnoreToken (char *, LPPORT);
BOOL WINAPI LoadSection (char *, char *, DWORD, int *, char *);
BOOL WINAPI GetIniFile (HANDLE, char *, char *);

/****************************************************************************
    My_mbschr:  strchr() DBCS version
****************************************************************************/
unsigned char * _CRTAPI1 My_mbschr(
    unsigned char *psz, unsigned short uiSep)
{
    while (*psz != '\0' && *psz != uiSep) {
        psz = CharNext(psz);
    }
    if (*psz == '\0' && uiSep != '\0') {
        return NULL;
    } else {
        return psz;
    }
}
/****************************************************************************
    My_mbstok:  strtok() DBCS version
****************************************************************************/
unsigned char * _CRTAPI1 My_mbstok(
    unsigned char *pszSrc, unsigned char *pszSep)
{
    static char *pszSave = NULL;
    char *pszHead;
    char *psz;

    if (pszSrc == NULL) {
        if (pszSave == NULL) {
            return NULL;
        } else {
            psz = pszSave;
        }
    } else {
        psz = pszSrc;
    }

    /*********************************************/
    /* Skip delimiters to find a head of a token */
    /*********************************************/
    while (*psz) {
        if (IsDBCSLeadByte(*psz)) {
            break;
        } else if (NULL == My_mbschr(pszSep, *psz)) {
            break;
        }
        psz++;
    }
    if (*psz == '\0') {
        //No more token
        return (pszSave = NULL);
    }
    pszHead = psz;

    /******************************/
    /* Search a Tail of the token */
    /******************************/
    while (*psz) {
        if (IsDBCSLeadByte(*psz)) {
            psz += 2;
            continue;
        } else if (NULL != My_mbschr(pszSep, *psz)) {
            break;
        }
        psz++;
    }
    if (*psz == '\0') {
        pszSave = NULL;
    } else {
        //Found next delimiter
        pszSave = psz + 1;
        *psz = '\0';
    }
    return pszHead;
}
/****************************************************************************
    My_mbsnicmp:strnicmp() DBCS version
                If 'nLen' splits a DBC, this function compares
                the DBC's 2nd byte also.
****************************************************************************/
int _CRTAPI1 My_mbsnicmp(
    const unsigned char *psz1, const unsigned char *psz2, size_t Length)
{
    int nLen = (int)Length;

    while (0 < nLen) {
        if ('\0' == *psz1 || '\0' == *psz2) {
            return *psz1 - *psz2;
        }
        if (IsDBCSLeadByte(*psz1) || IsDBCSLeadByte(*psz2)) {
            if (*psz1 != *psz2 || *(psz1+1) != *(psz2+1)) {
                return *psz1 - *psz2;
            }
            psz1 += 2;
            psz2 += 2;
            nLen -= 2;
        } else {
            if((BYTE)CharUpper((LPSTR)*psz1) != (BYTE)CharUpper((LPSTR)*psz2)){
                return *psz1 - *psz2;
            }
            psz1++;
            psz2++;
            nLen--;
        }
    }
    return 0;
}
/****************************************************************************
    My_mbsncmp: strncmp() DBCS version
                If 'nLen' splits a DBC, this function compares
                the DBC's 2nd byte also.
****************************************************************************/
int _CRTAPI1 My_mbsncmp(
    const unsigned char *psz1, const unsigned char *psz2, size_t Length)
{
    int nLen = (int)Length;

    while (0 < nLen) {
        if ('\0' == *psz1 || '\0' == *psz2) {
            return *psz1 - *psz2;
        }
        if (IsDBCSLeadByte(*psz1) || IsDBCSLeadByte(*psz2)) {
            if (*psz1 != *psz2 || *(psz1+1) != *(psz2+1)) {
                return *psz1 - *psz2;
            }
            psz1 += 2;
            psz2 += 2;
            nLen -= 2;
        } else {
            if (*psz1 != *psz2) {
                return *psz1 - *psz2;
            }
            psz1++;
            psz2++;
            nLen--;
        }
    }
    return 0;
}
/****************************************************************************
    My_mbsncpy:
****************************************************************************/
unsigned char * _CRTAPI1 My_mbsncpy(
    unsigned char *psz1, const unsigned char *psz2, size_t Length)
{
    int nLen = (int)Length;
    unsigned char *pszSv = psz1;

    while (0 < nLen) {
        if (*psz2 == '\0') {
            *psz1++ = '\0';
            nLen--;
        } else if (IsDBCSLeadByte(*psz2)) {
            if (nLen == 1) {
                *psz1 = '\0';
            } else {
                *psz1++ = *psz2++;
                *psz1++ = *psz2++;
            }
            nLen -= 2;
        } else {
            *psz1++ = *psz2++;
            nLen--;
        }
    }
    return pszSv;
}

/* entry point for DLL loading and unloading */
BOOL WINAPI DllMain (
    HANDLE    hModule,
    DWORD     dwFunction,
    LPVOID    lpNot)
{
#ifdef DEBUG
DebugBreak ();
#endif

    switch (dwFunction)
{
case DLL_PROCESS_ATTACH:
hDLL = hModule;
    break;

case DLL_PROCESS_DETACH:
    FreePortData ();
default:
    break;
}

    return TRUE;
}

/* function initializes port structures */
BOOL WINAPI InitPortData (
char  *szIniFileName
)
{
    charszSection[MAX_PATH];
    charszIniFilePath[MAX_PATH];
    charszMapFileName[MAX_PATH];
    OFSTRUCTof;
    HANDLEhFile;
    DWORDnFileSize;
    int nOffset = 0;
    char*lpMMFile;

/* Initialize Flag off */
bInit = FALSE;

    /* load name for global file mapping */
    LoadString (hDLL, IDS_MAPFILENAME, szMapFileName, MAX_PATH);

    /* after first process initializes port data */
    if ((hMMFile = OpenFileMapping (FILE_MAP_WRITE, FALSE, szMapFileName)))
/* exit now since initialization was already performed by another process */
return TRUE;

    /* retrive path and file for ini file */
if (!GetIniFile (hDLL, szIniFileName, szIniFilePath))
return FALSE;

    /* test for ini file existance and get length of file */
    if ((int)(hFile = (HANDLE)OpenFile (szIniFilePath, &of, OF_READ)) == -1)
return FALSE;
    else
{
nFileSize = GetFileSize (hFile, NULL);
CloseHandle (hFile);
}

    /* allocate a segment of the swap file for shared memory 2*Size of ini file */
    if (!(hMMFile = CreateFileMapping ((HANDLE)0xffffffff,
   NULL,
   PAGE_READWRITE,
   0,
   nFileSize * 2,
   szMapFileName)))
return FALSE;

    /* map a view of this file for writing */
    lpMMFile = (char *)MapViewOfFile (hMMFile, FILE_MAP_WRITE, 0, 0, 0);

    /* load tokens for APIS section */
    LoadString (hDLL, IDS_PORTAPIS, szSection, MAX_PATH);
    if (!LoadSection (szIniFilePath, szSection, PT_APIS, &nOffset, lpMMFile))
{
/* clean up memory mapped file */
UnmapViewOfFile (lpMMFile);
CloseHandle (hMMFile);
return FALSE;
}

    /* load tokens for MESSAGES section */
    LoadString (hDLL, IDS_PORTMESSAGES, szSection, MAX_PATH);
    if (!LoadSection (szIniFilePath, szSection, PT_MESSAGES, &nOffset, lpMMFile))
{
/* clean up memory mapped file */
UnmapViewOfFile (lpMMFile);
CloseHandle (hMMFile);
return FALSE;
}

    /* load tokens for STRUCTURES section */
    LoadString (hDLL, IDS_PORTSTRUCTURES, szSection, MAX_PATH);
    if (!LoadSection (szIniFilePath, szSection, PT_STRUCTURES, &nOffset, lpMMFile))
{
/* clean up memory mapped file */
UnmapViewOfFile (lpMMFile);
CloseHandle (hMMFile);
return FALSE;
}

    /* load tokens for TYPES section */
    LoadString (hDLL, IDS_PORTTYPES, szSection, MAX_PATH);
    if (!LoadSection (szIniFilePath, szSection, PT_TYPES, &nOffset, lpMMFile))
{
/* clean up memory mapped file */
UnmapViewOfFile (lpMMFile);
CloseHandle (hMMFile);
return FALSE;
}

    /* load tokens for MACROS section */
    LoadString (hDLL, IDS_PORTMACROS, szSection, MAX_PATH);
    if (!LoadSection (szIniFilePath, szSection, PT_MACROS, &nOffset, lpMMFile))
{
/* clean up memory mapped file */
UnmapViewOfFile (lpMMFile);
CloseHandle (hMMFile);
return FALSE;
}

    /* load tokens for CONSTANTS section */
    LoadString (hDLL, IDS_PORTCONSTANTS, szSection, MAX_PATH);
    if (!LoadSection (szIniFilePath, szSection, PT_CONSTANTS, &nOffset, lpMMFile))
{
/* clean up memory mapped file */
UnmapViewOfFile (lpMMFile);
CloseHandle (hMMFile);
return FALSE;
}

    /* load tokens for CUSTOM section */
    LoadString (hDLL, IDS_PORTCUSTOM, szSection, MAX_PATH);
    if (!LoadSection (szIniFilePath, szSection, PT_CUSTOM, &nOffset, lpMMFile))
{
/* clean up memory mapped file */
UnmapViewOfFile (lpMMFile);
CloseHandle (hMMFile);
return FALSE;
}

    /* release WRITE view of memory mapped file */
    UnmapViewOfFile (lpMMFile);

/* success */
bInit = TRUE;
    return TRUE;
}

/* release memory mapped file view */
void WINAPI FreePortData ()
{
if ( ! bInit )
return;

    /* release memory mapped file */
    CloseHandle (hMMFile);
    }

/* external function to check a string for porting issues */
BOOL WINAPI CheckString (
    char*lpszSrc,
    DWORDdwSearch,
    LPRESULTlprIssue)
{
    BOOL      bRet = FALSE;
    LPPORT    lpToken;
    char      *lpStr = lpszSrc;
    int       nSrcLen = strlen (lpszSrc);
    int       nTokLen;
    char      *lpMMFile = (char *)MapViewOfFile (hMMFile, FILE_MAP_WRITE, 0, 0, 0);

if ( ! bInit )
   return FALSE;

/* if view of file failed */
    if (!lpMMFile)
return FALSE;

    /* if ignore token */
    if (dwSearch & PT_IGNORETOKEN)
/* flag token as ignored */
IgnoreToken (lpszSrc, (LPPORT)lpMMFile);

    else
/* loop through all characters in string */
while ((lpStr-lpszSrc) < nSrcLen)
    {
    /* initialize lpToken to beginning of list */
    lpToken = (LPPORT)lpMMFile;

        /* loop thru all tokens */
    if (nTokLen = GetFirstToken (&lpToken))
    do
        {
        /* filter tokens for search criteria */
        if (!(lpToken->dwType & PT_IGNORED) &&
            !(dwSearch & lpToken->dwType) &&
            ((dwSearch & PT_IGNORECASE &&
              !strnicmp ((char *)lpToken+lpToken->nPosToken, lpStr, nTokLen)) ||
             !strncmp ((char *)lpToken+lpToken->nPosToken, lpStr, nTokLen)))
            {
            /* token found in line, return ISSUE struct */
            strncpy (lprIssue->lpszToken,
                 (char *)lpToken+lpToken->nPosToken,
                 *(WORD *)lprIssue->lpszToken);
            strncpy (lprIssue->lpszHelpStr,
                 (char *)lpToken+lpToken->nPosHelpStr,
                 *(WORD *)lprIssue->lpszHelpStr);
            strncpy (lprIssue->lpszIssue,
                 (char *)lpToken+lpToken->nPosIssue,
                 *(WORD *)lprIssue->lpszIssue);
            strncpy (lprIssue->lpszSuggest,
                 (char *)lpToken+lpToken->nPosSuggest,
                 *(WORD *)lprIssue->lpszSuggest);
            lprIssue->nPosToken = (int)(lpStr - lpszSrc);

    bRet = TRUE;
    goto DONE;
    }
}
    while ((nTokLen = GetNextToken (&lpToken)));

        lpStr = CharNext(lpStr);
        }

DONE:

    /* unmap view of memory mapped file */
    UnmapViewOfFile (lpMMFile);

    return bRet;
}

/* function get's the first token in the list */
int WINAPI GetFirstToken (
    LPPORT    *lpToken)
{
    /* increment to next non-ignored token in list */
    while (((((LPPORT)*lpToken)->dwType) == PT_IGNORED) &&
   ((((LPPORT)*lpToken)->nSize) != 0))
(char *)*lpToken += ((LPPORT)*lpToken)->nSize;

    /* if at end of list, reset list to null */
    if ((((LPPORT)*lpToken)->nSize) == 0)
{
*lpToken = 0;
return 0;
}

    /* return length of token */
    return (strlen ((char *)*lpToken + ((LPPORT)*lpToken)->nPosToken));
}

/* function get's the next token in the list */
int WINAPI GetNextToken (
    LPPORT    *lpToken)
{
    /* increment to next non-ignored token in list */
    do
(char *)*lpToken += ((LPPORT)*lpToken)->nSize;
    while (((((LPPORT)*lpToken)->dwType) == PT_IGNORED) &&
   ((((LPPORT)*lpToken)->nSize) != 0));

    /* if at end of list, reset list to null */
    if ((((LPPORT)*lpToken)->nSize) == 0)
{
*lpToken = 0;
return 0;
}

    /* return length of token */
    return (strlen ((char *)*lpToken + ((LPPORT)*lpToken)->nPosToken));
}

/* function sets the ignore flag on the specified token */
void WINAPI IgnoreToken (
    char    *lpszToken,
    LPPORT  lpToken)
{
    /* search for token in list */
    while (lpToken->nSize != 0)
/* if same token */
if (!strcmp ((char *)((char *)lpToken + lpToken->nPosToken), lpszToken))
    {
    lpToken->dwType |= PT_IGNORED;
    break;
    }
/* increment to  next token */
else
    (char *)lpToken += lpToken->nSize;
}

/* load tokens from a section of ini file */
BOOL WINAPI LoadSection (
    char    *lpszIniFile,
    char    *lpszSection,
    DWORD   dwType,
    int     *nOffset,
    char    *lpMMFile)
{
    char    *lpszKeyNames;
    char    *lpKey;
    char    *lpszValue;
    char    *lpVal;
    char    *lpszToken;
    char    lpszDefault[] = "Default";
    char    *lpMem = lpMMFile + *nOffset;
    int     nList;

    /* allocate lots of memory off heap to save calling applications' stack */
    if (!(lpszKeyNames = (char *)LocalAlloc (LPTR, FIFTY_K_LINE)))
return FALSE;
    if (!(lpszValue = (char *)LocalAlloc (LPTR, TWO_K_LINE)))
{
LocalFree ((HLOCAL)lpszKeyNames);
return FALSE;
}
    if (!(lpszToken = (char *)LocalAlloc (LPTR, MAXTOKENLEN)))
{
LocalFree ((HLOCAL)lpszKeyNames);
LocalFree ((HLOCAL)lpszValue);
return FALSE;
}

    /* get all keynames in section */
    if (((nList = GetPrivateProfileString (lpszSection,
   NULL,
   lpszDefault,
   lpszKeyNames,
   FIFTY_K_LINE,
   lpszIniFile)) == (int)(strlen (lpszDefault))) &&
!strcmp (lpszDefault, lpszKeyNames))
{
LocalFree ((HLOCAL)lpszKeyNames);
LocalFree ((HLOCAL)lpszValue);
LocalFree ((HLOCAL)lpszToken);
return FALSE;
}

    /* initialize token pointer and first token */
    lpKey = lpszKeyNames;

    /* loop through all keynames */
    while (TRUE)
{
/* get next token */
strcpy (lpszToken, lpKey);

/* get value for token */
if ((GetPrivateProfileString (lpszSection,
     lpszToken,
     lpszDefault,
     lpszValue,
     TWO_K_LINE,
     lpszIniFile) == strlen (lpszDefault)) &&
    !strcmp (lpszDefault, lpszValue))
    {
    LocalFree ((HLOCAL)lpszKeyNames);
    LocalFree ((HLOCAL)lpszValue);
    LocalFree ((HLOCAL)lpszToken);
    return FALSE;
    }
else
    {
    /* break line up into components */
    ((LPPORT)lpMem)->nPosToken = sizeof (PORT);
    strcpy ((char *)(lpMem + ((LPPORT)lpMem)->nPosToken), lpszToken);

    ((LPPORT)lpMem)->nPosHelpStr =
    ((LPPORT)lpMem)->nPosToken + strlen ((char *)(lpMem + ((LPPORT)lpMem)->nPosToken)) + 1;
    if (lpVal = strtok (lpszValue, ";"))
strcpy ((char *)(lpMem + ((LPPORT)lpMem)->nPosHelpStr), lpVal);
    else
*(lpMem + ((LPPORT)lpMem)->nPosHelpStr) = 0;

    ((LPPORT)lpMem)->nPosIssue =
    ((LPPORT)lpMem)->nPosHelpStr + strlen ((char *)(lpMem + ((LPPORT)lpMem)->nPosHelpStr)) + 1;
    if (lpVal = strtok (NULL, ";"))
strcpy ((char *)(lpMem + ((LPPORT)lpMem)->nPosIssue), lpVal);
    else
*(lpMem + ((LPPORT)lpMem)->nPosIssue) = 0;

    ((LPPORT)lpMem)->nPosSuggest =
    ((LPPORT)lpMem)->nPosIssue + strlen ((char *)(lpMem + ((LPPORT)lpMem)->nPosIssue)) + 1;
    if (lpVal = strtok (NULL, ";"))
strcpy ((char *)(lpMem + ((LPPORT)lpMem)->nPosSuggest), lpVal);
    else
*(lpMem + ((LPPORT)lpMem)->nPosSuggest) = 0;

    /* set size of dynamic token structure */
    ((LPPORT)lpMem)->nSize = (((LPPORT)lpMem)->nPosSuggest +
 strlen ((char *)(lpMem + ((LPPORT)lpMem)->nPosSuggest)) + 1);

    /* adjust nSize for DWORD alignment */
            ((LPPORT)lpMem)->nSize = ((((LPPORT)lpMem)->nSize) + 3) & ~3;

    /* set token type */
    ((LPPORT)lpMem)->dwType = dwType;

    /* adjust list pointer to point beginning of next list element */
    lpMem += ((LPPORT)lpMem)->nSize;
    }

/* increment token pointer and test for end of list */
if (((lpKey += strlen (lpszToken) + 1) - lpszKeyNames) >= (nList - 1))
    {
    /* indicate end of list by setting size of next element to 0 */
    ((LPPORT)lpMem)->nSize = 0;
    break;
    }
}

    /* got to end of section, clean up and go away */
    LocalFree ((HLOCAL)lpszKeyNames);
    LocalFree ((HLOCAL)lpszValue);
    LocalFree ((HLOCAL)lpszToken);

    /* recalculate offset */
    *nOffset = lpMem - lpMMFile;

    /* return successful load */
    return TRUE;
}

/* retrieve ini file and path */
BOOL WINAPI GetIniFile (
HANDLEhDLL,
char*lpszFileName,
    char    *lpszFile)
{
    char    lpszPath[MAX_PATH];
    char    *lpPath;
// char lpszFileName[MAX_PATH];
OFSTRUCTof;

// Is this a full path ? If so there must be a : or \ in there somewhere.
lpPath = lpszFileName;
while ( *lpPath )
{
   if ( *lpPath == '\\' || *lpPath == ':' )
   {
  if ((OpenFile (lpszFileName, &of, OF_EXIST)))
  {
 // Name passed in is OK.
 strcpy (lpszFile, lpszFileName);
 return TRUE;
  }

   }
   lpPath = CharNext(lpPath);
}

// OK, not a full path, we must build one

    /* get module directory and path */
    GetModuleFileName (hDLL, lpszPath, MAX_PATH);
    lpPath = lpszPath + strlen (lpszPath);

    /* find end of path by searching backwards from end to first '\' or ':' */
    while (lpPath > lpszPath)
    {
    if (*lpPath == '\\' ||
        *lpPath == ':')
        {
        lpPath++;
        break;
        }
    lpPath = CharPrev(lpszPath, lpPath);
    }

    /* terminate at end of path */
    *lpPath = 0;

    /* append ini filename to path */
strcat (lpPath, lpszFileName);

// check for an extension ( look for '.' before '\' )
lpPath = lpszPath+strlen(lpszPath);
while ( lpPath > lpszPath &&
            *lpPath != '\\' &&
            *lpPath != ':')
{
   if ( *lpPath == '.')
  break;
   lpPath = CharPrev(lpszPath, lpPath);
}

if ( *lpPath != '.' )
{
   // No extension, supply one
   strcat (lpszPath, ".INI");
}

    /* test for existance */
    if (!(OpenFile (lpszPath, &of, OF_EXIST)))
{
GetWindowsDirectory (lpszPath, MAX_PATH);
strcat (lpszPath, lpszFileName);
if (!(OpenFile (lpszPath, &of, OF_EXIST)))
{
return FALSE;
}
else
    {
    strcpy (lpszFile, lpszPath);
    return TRUE;
    }
}
    else
{
strcpy (lpszFile, lpszPath);
return TRUE;
}
}


PORT.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 windows.h before including this header file into your source

// define some string length constants
#define    MAXTOKENLEN  100
#define    MAXHELPLEN  100
#define    MAXISSUELEN  1024
#define    MAXSUGGESTLEN  1024

// define option flags
#define    PT_IGNORETOKEN  0x00010000// informs port.dll to ignore this token
#define    PT_DEFAULT  0x00000000// checks for everything
#define    PT_NOAPIS  0x00000002// do not check for APIs
#define    PT_NOMESSAGES  0x00000004// do not check for messages
#define    PT_NOTYPES  0x00000008// do not check for types
#define    PT_NOSTRUCTURES  0x00000010// do not check for structures
#define    PT_NOMACROS  0x00000020// do not check for macros
#define    PT_NOCONSTANTS  0x00000040// do not check for constants
#define    PT_NOCUSTOM  0x00000080// do not check for custom tokens
#define    PT_IGNORECASE  0x00000100// disable case sensitivity
//   (not helpful for APIs and MSGs)

// result structure returns results of line checked
typedef struct tagResult
    {
    char    *lpszToken;
    char    *lpszHelpStr;
    char    *lpszIssue;
    char    *lpszSuggest;
    int     nPosToken;
    }RESULT, * LPRESULT;

// function checks a string for a matching token
BOOL WINAPI CheckString (char *lpszSrc, DWORD fSearch, LPRESULT lpResult);
//    lpszSrc  - source string to search
//    fSearch  - search flags define how to check string
//    lpResult - structure of data passed to/from DLL
//
// When calling this function you must pass a valid LPRESULT structure having
//   allocated memory for each of the string pointer fields in the structure.
//   It is recommended that you allocate enough memory to fulfill the max length
//   for each field as defined by the constants above.Also, you must indicate the
//   actual length of each string allocated in the first WORD of each string.  The
//   CheckString function will copy at most length-1 characters and zero terminate
//   the string for you.
//
// New for version 2.2.  The option to ignore a specific issue for the duration of
//   the DLL is loaded is now provided.  This is done by calling the CheckString
//   function with the PT_IGNORETOKEN bit set in the search flags.  In this case,
//   the CheckString function toggles the ignore state on a token matching the string
//   lpszSrc passed to the function.  The lpResult parameter is ignored in this case.
//   Successive calls with the PT_IGNORETOKEN bit can be used to toggle the state of
//   any token to IGNORED or not IGNORED.
//  NOTE: The PT_IGNORETOKEN flag cannot be reset by the DLL since the search flags
//   parameter is passed by value.  The application that calls CheckString must reset
//   the flag when the function returns.
//


PORTPRIV.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 <windows.h>

// define ini file section names
#define    IDS_PORTAPIS   200
#define    IDS_PORTMESSAGES  201
#define    IDS_PORTSTRUCTURES  202
#define    IDS_PORTTYPES  203
#define    IDS_PORTCONSTANTS  204
#define    IDS_PORTCUSTOM  205
#define    IDS_PORTMACROS  206
#define    IDS_WINDOWSAPPNAME  207
#define    IDS_INIFILE  209
#define    IDS_MAPFILENAME  210

// local constant defintions
#define    FIFTY_K_LINE  51200
#define    TEN_K_LINE  10240
#define    TWO_K_LINE  2056
#define    PT_APIS  0x00000002
#define    PT_MESSAGES  0x00000004
#define    PT_TYPES  0x00000008
#define    PT_STRUCTURES  0x00000010
#define    PT_MACROS  0x00000020
#define    PT_CONSTANTS   0x00000040
#define    PT_CUSTOM  0x00000080
#define    PT_IGNORED  0x00010000

// private port structure
typedef struct tagPort
    {
    int    nSize;
    int    nPosToken;
    int    nPosHelpStr;
    int    nPosIssue;
    int    nPosSuggest;
    DWORD  dwType;
    }PORT, * LPPORT;


PORTTOOL.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright (C) 1993-96 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 "porttool.h"
#include "port.h"

/* forward declarations of helper functions in this module */
HWND   WINAPI StartInteractive (HANDLE, char *, int);
HANDLE WINAPI StartBackground (HANDLE, HWND, char *);
VOID   WINAPI InitializeMenu (HWND, HANDLE);
LONG   WINAPI CommandHandler (HWND, UINT, LONG);
int    WINAPI SetWrap (HWND);
BOOL   WINAPI GetCmdLine (char *, char *, BOOL *, char *);
int    WINAPI Save_YNC (HWND);
VOID   WINAPI SetWindowTitle (HWND, char*);
VOID   WINAPI GetEditSubString (HWND, PUINT, PUINT, char *);

UINT    uSearchMsg;
HWND    hDlgPortStatus;
HWND    hDlgPort;

DWORD   dwVersion=0; // Global variable to store current system version info

/* test case static variables for background porting */
HANDLE              hEvents[nBKPORTEVENTS];
BKPORTFILESTRUCT    BkPort;

/* entry point ot this executable */
int WINAPI WinMain (hInstance, hPrevInstance, lpCmdLine, nCmdShow)
    HINSTANCE hInstance;
    HINSTANCE hPrevInstance;
    LPSTR  lpCmdLine;
    int    nCmdShow;
{
    MSG        msg;
    HWND       hWnd;
    HANDLE     hAccel;
    BOOL       bBkgnd = 0;
    char       *lpszBuff = NULL;
    char       *lpszCmdLine = NULL;
    char       *lpCL;

    /* previous instances do not exist in Win32 */
    if (hPrevInstance)
return 0;

    /* Get OS version info, and store in a global variable */
dwVersion = GetVersion();

lpszPortIniFilePath[0] = 0;

    /* parse and copy command line parameters to local memory */
    lpCL = GetCommandLine ();
    if (lpszCmdLine = (char *)LocalAlloc (LPTR, strlen (lpCL) + 1))
GetCmdLine (lpCL, lpszCmdLine, &bBkgnd, lpszPortIniFilePath);

    /* if /b switch, start background porting session */
    if (bBkgnd)
{
/* invoke background port status dialog */
// MessageBox(NULL, "Backgroud Porting Disabled","Porttool",MB_OK);

if (lpszPortIniFilePath[0] == 0 )
{
   ErrorNotify (NULL, IDS_PORTFILEBACKMSG);
   return FALSE;
}
else if ( ! InitPortData(lpszPortIniFilePath) )
   ErrorNotify (NULL, IDS_PORTINITFAILED);

if (!(hDlgPortStatus = StartBackground (hInstance, NULL, lpszCmdLine)))
return FALSE;
}

    /* start interactive porting session */
    else
{
if (!(hWnd = StartInteractive (hInstance, lpszCmdLine, nCmdShow)))
    return FALSE;
}

    /* free memory allocated for pCmdLine */
    if (lpszCmdLine)
LocalFree ((HLOCAL)lpszCmdLine);

    /* load main accelerator table */
    hAccel = LoadAccelerators (hInstance, MAKEINTRESOURCE (IDA_PORTTOOL));

    /* main window message loop */
    while (GetMessage (&msg, NULL, 0, 0))
{
if ((!hDlgSearch     || !IsDialogMessage (hDlgSearch, &msg))     &&
    (!hDlgPort       || !IsDialogMessage (hDlgPort, &msg))       &&
    (!hDlgPortStatus || !IsDialogMessage (hDlgPortStatus, &msg)) &&
    (!hAccel         || !TranslateAccelerator (hWnd, hAccel, &msg)))
    {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
    }
}

    /* return success of application */
    return TRUE;
}

//
// Used only on FE versions to screen out vertical fonts
//

BOOL APIENTRY ChooseFontHookProc(
    HWND hDlg,              /* window handle of the dialog box */
    UINT message,           /* type of message                 */
    WPARAM wParam,          /* message-specific information    */
    LPARAM lParam)
{
    switch (message) {
    case WM_INITDIALOG:
        {
            LOGFONT lf;
            char szFaceName[LF_FACESIZE];
            UINT uiId, uiCount;

            uiCount = SendDlgItemMessage(hDlg, cmb1, CB_GETCOUNT, 0, 0L);
            /* Delete vertical font */
            for (uiId = 0; uiId < uiCount; uiId++) {
                SendDlgItemMessage(hDlg, cmb1, CB_GETLBTEXT,
                                   uiId, (LPARAM)(LPSTR)szFaceName);
                if (szFaceName[0] == '@') {
                    SendDlgItemMessage(hDlg, cmb1, CB_DELETESTRING, uiId, 0L);
                    uiId--;
                    uiCount--;
                }
            }
            /* Set selection current selected facename */
            SendMessage(hDlg, WM_CHOOSEFONT_GETLOGFONT, 0, (LPARAM)(LPSTR)&lf);
            uiId = SendDlgItemMessage(hDlg, cmb1, CB_FINDSTRING,
                                     0, (LPARAM)(LPSTR)lf.lfFaceName);
            SendDlgItemMessage(hDlg, cmb1, CB_SETCURSEL, uiId, 0L);
        }
        return (TRUE);
        break;
    }
    return (FALSE);
}

/* start background port status dialog */
HANDLE WINAPI StartBackground (
    HANDLE    hModule,
    HWND      hWndParent,
    char      *lpszCmdLine)
{
    return (CreateDialogParam (hModule,
       IDD_BKPORTDIALOG,
       hWndParent,
       BkPortDlgProc,
       (LPARAM)lpszCmdLine));
}

/* start interactive version of app */
HWND WINAPI StartInteractive (
    HANDLE    hInstance,
    char      *lpszCmdLine,
    int       nCmdShow)
    {
    WNDCLASS   wc;
    char       lpszClass[MAX_PATH];
    HWND       hWnd;
char   lpszFileName[MAX_PATH];

    /* load resources strings */
    LoadString (hInstance, IDS_APPNAME, lpszClass, sizeof (lpszClass));

    /* Register the frame class */
    wc.style         = 0;
    wc.lpfnWndProc   = (WNDPROC)MainWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = CBWNDEXTRA;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon (hInstance, IDPortTool);
    wc.hCursor       = LoadCursor (NULL,IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = IDPortTool;
    wc.lpszClassName = lpszClass;

    if (!RegisterClass (&wc) )
return FALSE;

    /* Create the frame */
    hWnd = CreateWindow (lpszClass,
 "Win32 Port",
 WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | DS_LOCALEDIT,
 CW_USEDEFAULT,
 0,
 CW_USEDEFAULT,
 0,
 NULL,
 NULL,
 hInstance,
 (*lpszCmdLine ? lpszCmdLine : 0));

    /* make sure window was created */
    if (!hWnd)
return FALSE;

    /* register search/replace message for common dialog use */
    uSearchMsg = RegisterWindowMessage ((char *)FINDMSGSTRING);

    /* show and update main window */
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);

/* Ask for Port Information File */
if (lpszPortIniFilePath[0] == 0 )
   if ( !GetPortIniFileName (hWnd, lpszFileName, lpszPortIniFilePath) )
  return FALSE;

if ( ! InitPortData(lpszPortIniFilePath) )
   ErrorNotify (hWnd, IDS_PORTINITFAILED);

    return hWnd;
}

/* main window procedure */
LONG WINAPI MainWndProc (
    HWND    hWnd,
    UINT    uMsg,
    UINT    uParam,
    LONG    lParam)
{
LONG    lRet = 1;
int     nResult;
RECT    rc;

    switch (uMsg)
{
case WM_CREATE:
    {
    HWND              hWndEdit;
    LPCREATESTRUCT    lpcs = (LPCREATESTRUCT)lParam;
    char              lpszBuff[MAX_PATH];
    LOGFONT           lfEditFont;
    HFONT             hFont;
    HCURSOR           hOldCursor;

    /* put hourglass cursor up */
    hOldCursor = (HCURSOR)SetClassLong (hWnd, GCL_HCURSOR, 0);
    SetCursor (LoadCursor (NULL, IDC_WAIT));

    /* Create an edit control */
    GetClientRect (hWnd, &rc);
    hWndEdit = CreateWindow ("edit",
     " ",
     WS_CHILD | WS_VISIBLE |
 WS_HSCROLL | WS_VSCROLL |
 ES_AUTOHSCROLL | ES_AUTOVSCROLL |
 ES_MULTILINE,
     rc.left,
     rc.top,
     rc.right-rc.left,
     rc.bottom-rc.top,
     hWnd,
     (HMENU)IDC_EDIT,
     (HANDLE)GetModuleHandle (NULL),
     0);

    /* if edit control failed, abort aplication */
    if (!IsWindow (hWndEdit))
{
ErrorNotify (hWnd, IDS_EDITWNDFAILED);
return FALSE;
}

    /* increase edit control max character limit beyond 30,000 default */
    SendMessage (hWndEdit, EM_LIMITTEXT, 0x0FFFFFFF, 0);

    /* save edit window handle and init state variables */
    SetWindowLong (hWnd, WL_HWNDEDIT, (LONG) hWndEdit);
    SetWindowWord (hWnd, WW_SCROLL, TRUE);
    SetWindowWord (hWnd, WW_UNTITLED, TRUE);
    SetWindowWord (hWnd, WW_SEARCHCASE, TRUE);
    SetWindowWord (hWnd, WW_SEARCHDN, TRUE);
    SetWindowLong (hWnd, WL_HPTRDEVNAMES, 0);

    /* get printer configuration */
    if (!GetPrinterConfig (hWnd))
ErrorNotify (hWnd, IDS_PTRCONFIGFAILED);

    /* if initialization file passed, load file now */
    if (lpcs->lpCreateParams)
{
/* load filename passed at initialization */
if ((nResult = LoadFile (hWnd, lpcs->lpCreateParams)) > 0)
    {
    /* save filename and path in global string */
    strcpy (lpszFilePath, lpcs->lpCreateParams);

    /* The file has a title, so reset the UNTITLED flag. */
    SetWindowWord(hWnd, WW_UNTITLED, FALSE);

    /* extract filename from path */
    GetFileFromPath (lpszFilePath, lpszBuff);

    /* set window text title to be "AppName - filename" */
    SetWindowTitle (hWnd, lpszBuff);
    }
else
    {
    /* notify user of error */
ErrorNotify (hWnd, 0-nResult);

    /* set window title to "AppName - Untitled" */
    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_UNTITLED,
lpszBuff,
sizeof (lpszBuff));
    SetWindowTitle (hWnd, lpszBuff);
    }
}
    else
{
/* set window title to "AppName - Untitled" */
LoadString ((HANDLE)GetModuleHandle (NULL),
    IDS_UNTITLED,
    lpszBuff,
    sizeof (lpszBuff));
SetWindowTitle (hWnd, lpszBuff);

/* set global file and path variables to null */
*lpszFilePath = 0;
}

        //
        // Use system font if Japanese system,
        //  may want to add other DBCS languages later
        //
        if (PRIMARYLANGID(GetUserDefaultLangID ()) == LANG_JAPANESE) {
          GetObject(GetStockObject(SYSTEM_FIXED_FONT),
                        sizeof(LOGFONT), &lfEditFont);

        } else {
          /* create fixed pitch font as default font */
          lfEditFont.lfHeight = 16;
          lfEditFont.lfWidth = 0;
          lfEditFont.lfEscapement = 0;
          lfEditFont.lfOrientation = 0;
          lfEditFont.lfWeight = 400;
          lfEditFont.lfItalic = FALSE;
          lfEditFont.lfUnderline = FALSE;
          lfEditFont.lfStrikeOut = FALSE;
          lfEditFont.lfCharSet = ANSI_CHARSET;
          lfEditFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
          lfEditFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
          lfEditFont.lfQuality = DEFAULT_QUALITY;
          lfEditFont.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
          *lfEditFont.lfFaceName = 0;
        }

    /* make scroll bars initially visible */
    SetScrollRange (hWndEdit, SB_VERT, 0, 100, TRUE);
    SetScrollRange (hWndEdit, SB_HORZ, 0, 100, TRUE);

    /* create the logical font */
    if (hFont = CreateFontIndirect (&lfEditFont))
SendMessage (hWndEdit, WM_SETFONT, (UINT)hFont, 0);
    else
ErrorNotify (hWnd, IDS_FONTFAILEDTOCREATE);

    /* remove hourglass cursor */
    SetClassLong (hWnd, GCL_HCURSOR, (LONG)hOldCursor);
    SetCursor (hOldCursor);

    // Is there anything specific we need to do on different platforms?
#if defined (WIN32)
    if (dwVersion < 0x80000000) {
// Windows NT
    } else if (LOBYTE(LOWORD(dwVersion))<4) {
// Win32s
// Threads aren't available, so disable background porting:
EnableMenuItem (GetMenu(hWnd), IDM_PORTBKGND, MF_GRAYED);
    } else {
// Windows 95
    }
#else
// Win16
#endif

    /* set focus to edit window */
    SetFocus (hWndEdit);
    }
    break;

case WM_SIZE:
    {
    HWND    hWndEdit;

    /* resize the edit control to match the client area */
    hWndEdit = (HWND)GetWindowLong (hWnd, WL_HWNDEDIT);
        InvalidateRect(hWndEdit, (LPRECT)NULL, TRUE);
    MoveWindow (hWndEdit,
0,
0,
LOWORD(lParam),
HIWORD(lParam),
TRUE);
    }
    break;

case WM_SETFOCUS:
    SetFocus ((HWND)GetWindowLong (hWnd, WL_HWNDEDIT));
    break;

case WM_INITMENU:
    /* initialize menuitems */
    InitializeMenu (hWnd, (HMENU)uParam);
    break;

case WM_WININICHANGE:
case WM_DEVMODECHANGE:
    /* get printer configuration */
    GetPrinterConfig (hWnd);
    break;

case WM_COMMAND:
    /* handle all command messages in a localized function */
    lRet = CommandHandler (hWnd, uParam, lParam);
    break;

case WM_CLOSE:
case WM_QUERYENDSESSION:
    {
    HWND    hWndEdit = (HWND)GetWindowLong (hWnd, WL_HWNDEDIT);

    /* check if there are changes to the edit contents, and offer to save first */
    if (SendMessage (hWndEdit, EM_GETMODIFY, 0, 0))
{
switch (Save_YNC (hWnd))
    {
    case IDCANCEL:
/* abort exit */
return FALSE;
    case IDYES:
/* save data then continue */
if (!SendMessage (hWnd, WM_COMMAND, (UINT)UM_SAVEFILE, 0))
    return FALSE;
    case IDNO:
/* just fall through */
break;
    }
}

    /* call destroy window to cleanup and go away */
    DestroyWindow (hWnd);
    }
    break;

case WM_DESTROY:
    {
    HFONT    hFont = (HFONT)SendMessage ((HWND)GetWindowLong (hWnd, WL_HWNDEDIT),
 WM_GETFONT,
 0,
 0);

    /* destroy font handle from edit control if exists */
    if (hFont)
DeleteObject (hFont);

    PostQuitMessage (0);
    }
    break;

default:
    /* process common dialog search message here, then break */
    if (uMsg == uSearchMsg)
{
LPFINDREPLACE    lpfr = (LPFINDREPLACE)lParam;

if (lpfr->Flags & FR_DIALOGTERM)
    {
    /* save case and direction */
    SetWindowWord (hWnd, WW_SEARCHCASE, (WORD)(lpfr->Flags & FR_MATCHCASE));
    SetWindowWord (hWnd, WW_SEARCHDN, (WORD)(lpfr->Flags & FR_DOWN));
    hDlgSearch = NULL;
    }

else if (lpfr->Flags & FR_FINDNEXT)
    if (!LocateText (hWnd,
     (WORD)(lpfr->Flags & FR_MATCHCASE),
     (WORD)(lpfr->Flags & FR_DOWN),
     lpszSearch))
ErrorNotify (hWnd, IDS_STRINGNOTFOUND);
break;
}

    /* pass all unhandled messages to DefWindowProc */
    lRet = DefWindowProc (hWnd, uMsg, uParam, lParam);
    break;
}

    /* return 1 if handled message, 0 if not */
    return lRet;
}

/* about box dialog procedure */
BOOL WINAPI AboutDlgProc (
    HWND    hwnd,
    UINT    uMsg,
    UINT    uParam,
    LONG    lParam)
{
    BOOL    bRet = TRUE;
    static  HFONT hfontDlg;
    char szVersion[80];

    switch (uMsg)
{
case WM_INITDIALOG:  // message: initialize dialog box

    // Create a font to use
    hfontDlg = CreateFont(14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH | FF_SWISS, "");

    SendMessage (GetDlgItem (hwnd, IDC_SYSTEM), WM_SETFONT, (UINT)hfontDlg, TRUE);
    SendMessage (GetDlgItem (hwnd, IDC_APP), WM_SETFONT, (UINT)hfontDlg, TRUE);
    SendMessage (GetDlgItem (hwnd, IDC_VERSION), WM_SETFONT, (UINT)hfontDlg, TRUE);
    SendMessage (GetDlgItem (hwnd, IDC_COPYRIGHT), WM_SETFONT, (UINT)hfontDlg, TRUE);
    //SendMessage (GetDlgItem (hwnd, IDC_OSVERSION), WM_SETFONT, (UINT)hfontDlg, TRUE);

    //dwVersion = GetVersion();
#if defined (WIN32)
    if (dwVersion < 0x80000000) {
// Windows NT
wsprintf (szVersion, "Microsoft WindowsNT %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))));
    }

#else
    wsprintf (szVersion, " Windows %u.%u - DOS %u.%u",
(DWORD)(LOBYTE(LOWORD(dwVersion))),
(DWORD)(HIBYTE(LOWORD(dwVersion))),
(DWORD)(HIBYTE(HIWORD(dwVersion))),
(DWORD)(LOBYTE(HIWORD(dwVersion))));

#endif
    SetWindowText (GetDlgItem(hwnd, IDC_OSVERSION), szVersion);

    return (TRUE);

case WM_COMMAND:
    switch (LOWORD (uParam))
{
/* end dialog with TRUE or FALSE for OK or CANCEL */
case IDOK:
case IDCANCEL:
    EndDialog(hwnd, (LOWORD (uParam) == IDOK));
    break;

default:
    bRet = FALSE;
}
    break;

default:
    bRet =  FALSE;
    break;
}
    return bRet;
}

/* initialize menu function */
VOID WINAPI InitializeMenu (
    HWND    hWnd,
    HANDLE  hMenu)
{
    WORD    mfStatus;
    DWORD   dwSel;
    HWND    hWndEdit = (HWND)GetWindowLong (hWnd, WL_HWNDEDIT);
    WORD    wScroll;

    /* see if edit control can undo last command */
    if (SendMessage (hWndEdit, EM_CANUNDO, 0, 0L))
mfStatus = MF_ENABLED;
    else
mfStatus = MF_GRAYED;
    EnableMenuItem (hMenu, IDM_EDITUNDO, mfStatus);

    /* enable menuitems CUT, COPY and CLEAR if selected text */
    dwSel = SendMessage (hWndEdit, EM_GETSEL, 0, 0);
    mfStatus = (WORD)((LOWORD(dwSel) == HIWORD(dwSel))?MF_GRAYED:MF_ENABLED);
    EnableMenuItem (hMenu, IDM_EDITCUT, mfStatus);
    EnableMenuItem (hMenu, IDM_EDITCOPY, mfStatus);
    EnableMenuItem (hMenu, IDM_EDITCLEAR, mfStatus);

    /* if text allow for new file, enable menuitem */
    if (SendMessage (hWndEdit, WM_GETTEXTLENGTH, 0, 0))
EnableMenuItem (hMenu, IDM_FILENEW, MF_ENABLED);
    else
EnableMenuItem (hMenu, IDM_FILENEW, MF_GRAYED);

    /* if CF_TEXT format data is available in clipboard */
    if (OpenClipboard (hWnd) & IsClipboardFormatAvailable (CF_TEXT))
EnableMenuItem (hMenu, IDM_EDITPASTE, MF_ENABLED);
    else
EnableMenuItem (hMenu, IDM_EDITPASTE, MF_GRAYED);
    CloseClipboard ();

    /* set the scroll bars menuitem */
    wScroll = GetWindowWord (hWnd, WW_SCROLL);
    CheckMenuItem (hMenu, IDM_EDITSCROLL, (UINT)(wScroll ? MF_CHECKED : MF_UNCHECKED));

    /* set the word wrap state for the window */
    dwSel = GetWindowLong (hWndEdit, GWL_STYLE);
    CheckMenuItem (hMenu,
   IDM_EDITWRAP,
   (UINT)((dwSel & ES_AUTOHSCROLL) ? MF_UNCHECKED : MF_CHECKED));

    /* enable search menuitems if a search string exists */
    if (*lpszSearch)
{
EnableMenuItem (hMenu, IDM_SEARCHNEXT, MF_ENABLED);
EnableMenuItem (hMenu, IDM_SEARCHPREV, MF_ENABLED);
}
    else
{
EnableMenuItem (hMenu, IDM_SEARCHNEXT, MF_GRAYED);
EnableMenuItem (hMenu, IDM_SEARCHPREV, MF_GRAYED);
}

    /* enable Print menuitems if a printer is available */
    if (GetWindowLong (hWnd, WL_HPTRDEVNAMES))
{
EnableMenuItem (hMenu, IDM_FILEPRINT, MF_ENABLED);
EnableMenuItem ( hMenu, IDM_FILESETUP, MF_ENABLED);
}
    else
{
EnableMenuItem (hMenu, IDM_FILEPRINT, MF_GRAYED);
EnableMenuItem ( hMenu, IDM_FILESETUP, MF_GRAYED);
}

    /* menuitems that are always enabled */
    EnableMenuItem(hMenu, IDM_EDITSCROLL, MF_ENABLED);
    EnableMenuItem(hMenu, IDM_EDITSELECT, MF_ENABLED);
    EnableMenuItem(hMenu, IDM_EDITWRAP, MF_ENABLED);
    EnableMenuItem(hMenu, IDM_SEARCHFIND, MF_ENABLED);
}

/* handle all WM_COMMAND messages here */
LONG WINAPI CommandHandler (
    HWND    hWnd,
    UINT    uParam,
    LONG    lParam)
{
    int     nResult;
    HWND    hWndEdit = (HWND)GetWindowLong (hWnd, WL_HWNDEDIT);

    switch (LOWORD(uParam))
{
case IDM_FILENEW:
    {
    char    lpszMsg[MAX_PATH];

    /* check if there are changes, and offer to save first */
    if (SendMessage (hWndEdit, EM_GETMODIFY, 0, 0))
{
switch (Save_YNC (hWnd))
    {
    case IDCANCEL:
/* abort operation */
return FALSE;
    case IDYES:
/* save data then continue if successful */
if (!SendMessage (hWnd, WM_COMMAND, (UINT)UM_SAVEFILE, 0))
    return FALSE;
    case IDNO:
/* just fall through */
break;
    }

/* reset edit changes flag */
SendMessage (hWndEdit, EM_SETMODIFY, FALSE, 0);
}

    /* clear edit control */
    SetWindowText (hWndEdit, "");

    /* set window title to "Untitled" */
    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_UNTITLED,
lpszMsg,
sizeof (lpszMsg));
    SetWindowTitle (hWnd, lpszMsg);
    SetWindowWord (hWnd, WW_UNTITLED, TRUE);

    /* reset caret position by sending WM_KILLFOCUS/WM_SETFOCUS messages */
    SetFocus (NULL);
    SetFocus (hWndEdit);
    }
    break;

case IDM_FILEOPEN:
    {
    char    lpszFileName[MAX_PATH];
    int     nResult;

    /* if changes to current file, ask to save first */
    if (SendMessage (hWndEdit, EM_GETMODIFY, 0, 0))
{
switch (Save_YNC (hWnd))
    {
    case IDCANCEL:
/* abort operation */
return FALSE;
    case IDYES:
/* save data then continue */
if (!SendMessage (hWnd, WM_COMMAND, (UINT)UM_SAVEFILE, 0))
    return FALSE;
    case IDNO:
/* just fall through */
break;
    }

/* reset edit changes flag */
SendMessage (hWndEdit, EM_SETMODIFY, FALSE, 0);
}

    /* get new filename and load into edit control */
    if (GetFileName (hWnd, lpszFileName, lpszFilePath))
{
if ((nResult = LoadFile (hWnd, lpszFilePath)) < 0)
    /* notify user of error */
ErrorNotify (hWnd, 0-nResult);
else
    {
    /* update window text */
    SetWindowTitle (hWnd, lpszFileName);
    SetWindowWord (hWnd, WW_UNTITLED, FALSE);
    }
}
    else
return FALSE;
    }
    break;

case IDM_FILESAVEAS:
    {
    char    lpszFileTitle[MAX_PATH];
    int     nResult;

    /* get a valid filename and path */
    if (SaveAsFileName (hWnd, lpszFileTitle, lpszFilePath))
{
/* if file saves okay */
if ((nResult = SaveFile (hWnd, lpszFilePath)) > 0)
    {
    /* reset changed flag, and window title */
    SendMessage (hWndEdit, EM_SETMODIFY, FALSE, 0);
    SetWindowTitle (hWnd, lpszFileTitle);
    SetWindowWord (hWnd, WW_UNTITLED, FALSE);
    }
else
    {
    /* unable to save file, return error */
    ErrorNotify (hWnd, nResult);
    return FALSE;
    }
}
    else
return FALSE;
    }
    break;

case IDM_FILESAVE:
    {
    char    lpszFileTitle[MAX_PATH];
    int     nResult;

        /* if called from menu, check to see if file modified */
        if (!lParam && !SendMessage (hWndEdit, EM_GETMODIFY, 0, 0))
          return FALSE;

/* user defined msg is sent from Exit, Open and New menuitems */
case UM_SAVEFILE:

    /* if untitled, get new name */
    if (GetWindowWord (hWnd, WW_UNTITLED) &&
!(SaveAsFileName(hWnd, lpszFileTitle, lpszFilePath)))
{
/* make sure no name was saved */
*lpszFilePath = 0;
return FALSE;
}

        /* save buffer and reset flag */
        if ((nResult = SaveFile (hWnd, lpszFilePath)) > 0)
        {
        // get lpszFileTitle
        char szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szExt[_MAX_EXT];
        _splitpath(lpszFilePath,szDrive,szDir,lpszFileTitle,szExt);
        lstrcat(lpszFileTitle,szExt);

        /* if new file name, set window title */
        if (*lpszFileTitle)
            {
            SetWindowTitle (hWnd, lpszFileTitle);
            SetWindowWord (hWnd, WW_UNTITLED, FALSE);
            }
        SendMessage (hWndEdit, EM_SETMODIFY, FALSE, 0);
        }
        else
        {
        /* unable to save file, return error */
        ErrorNotify (hWnd, nResult);
        return FALSE;
        }
        }
        break;

case IDM_FILEPRINT:
    {
    int    nRes;

    /* print the file */
    if ((nRes = PrintFile (hWnd)) < 0)
ErrorNotify (hWnd, nRes);
    }
    break;

case IDM_FILESETUP:
    {
    int    nRes;

    /* set up the printer environment */
    if ((nRes = PrinterSetup (hWnd)) < 0)
ErrorNotify (hWnd, nRes);
    }
    break;

case IDM_FILEEXIT:
    /* exit application */
    PostMessage (hWnd, WM_CLOSE, 0, 0L);
    break;

case IDM_FILEABOUT:
    /* call about dialog box */
    DialogBox ((HANDLE)GetModuleHandle (NULL),
       (LPSTR)IDD_ABOUT,
       hWnd,
       AboutDlgProc);
    break;

case IDM_EDITUNDO:
    SendMessage (hWndEdit, EM_UNDO, 0, 0);
    break;

case IDM_EDITCUT:
        SendMessage (hWndEdit, WM_CUT, 0, 0);
        break;

case IDM_EDITCOPY:
        SendMessage (hWndEdit, WM_COPY, 0, 0);
        break;

case IDM_EDITCLEAR:
        SendMessage (hWndEdit, WM_CLEAR, 0, 0);
        break;

case IDM_EDITPASTE:
        SendMessage (hWndEdit, WM_PASTE, 0, 0L);
        break;

case IDM_EDITSELECT:
    /* 0, -1 selects the entire string */
    SendMessage (hWndEdit, EM_SETSEL, 0, (LONG)(int)-1);
    break;

case IDM_EDITSCROLL:
    {
    WORD    wScroll = !GetWindowWord (hWnd, WW_SCROLL);

    /* toggle vertical scroll bar */
    SetScrollRange (hWndEdit, SB_VERT, 0, (int)(wScroll ? 100 : 0), TRUE);

    /* if not word wrap, toggle horizontal scroll */
    if (ES_AUTOHSCROLL & GetWindowLong (hWndEdit, GWL_STYLE))
SetScrollRange (hWndEdit, SB_HORZ, 0, (int)(wScroll ? 100 : 0), TRUE);

    /* set scroll flag */
    SetWindowWord (hWnd, WW_SCROLL, wScroll);
    }
    break;

case IDM_EDITWRAP:
    /* change edit control */
    if ((nResult = SetWrap (hWnd)) < 0)
/* notify user of error */
ErrorNotify (hWnd, nResult);
    break;

case IDM_EDITFONT:
    {
    CHOOSEFONT    cf;
    LOGFONT       lfEditFont;
    HFONT         hEditFont;

    /* get logical font structure from current font */
    if (hEditFont = (HFONT)SendMessage (hWndEdit, WM_GETFONT, 0, 0))
{
GetObject (hEditFont, sizeof (lfEditFont), &lfEditFont);
cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
}
    else
cf.Flags = CF_SCREENFONTS;

        /* call common dialog to select a font */
        cf.lStructSize    = sizeof (CHOOSEFONT);
        cf.hwndOwner      = hWnd;
        cf.hDC            = NULL;
        cf.lpLogFont      = &lfEditFont;
        cf.iPointSize     = 0;
        cf.rgbColors      = 0;
        cf.lCustData      = 0;

        //
        // On Japanese system set font filter proc,
        //  may want to add other DBCS languages later
        //
        if (PRIMARYLANGID(GetUserDefaultLangID ()) == LANG_JAPANESE) {
          cf.lpfnHook = (LPCFHOOKPROC)MakeProcInstance(ChooseFontHookProc, NULL);
          cf.Flags |= CF_ENABLEHOOK;
        } else {
          cf.lpfnHook       = NULL;
        }

        cf.lpTemplateName = NULL;
        cf.hInstance      = NULL;
        cf.lpszStyle      = NULL;
        cf.nFontType      = SCREEN_FONTTYPE;
        cf.nSizeMin       = 0;
        cf.nSizeMax       = 0;

    if (ChooseFont (&cf))
{
/* create new font and put in edit control */
SendMessage (hWndEdit,
     WM_SETFONT,
     (UINT)CreateFontIndirect
     (&lfEditFont),
     TRUE);

if (hEditFont)
    DeleteObject (hEditFont);
}
    }
    break;

case IDM_SEARCHFIND:
    {
    UINT    uBegSel, uEndSel;
    WORD    wCase = GetWindowWord (hWnd, WW_SEARCHCASE);
    WORD    wDir = GetWindowWord (hWnd, WW_SEARCHDN);

    /* if selected text, replace global search string */
    SendMessage (hWndEdit, EM_GETSEL, (UINT)&uBegSel, (UINT)&uEndSel);
    if (uBegSel != uEndSel)
GetEditSubString (hWndEdit, &uBegSel, &uEndSel, lpszSearch);

    /* Put up the find dialog box */
    if (!FindDialog (hWnd, wCase, wDir, lpszSearch))
ErrorNotify (hWnd, IDS_SEARCHDLGFAILED);
    }
    break;

case IDM_SEARCHNEXT:
    /* locate next search string in edit control */
    if (!LocateText (hWnd,
     GetWindowWord (hWnd, WW_SEARCHCASE),
     TRUE,
     lpszSearch))
ErrorNotify (hWnd, IDS_STRINGNOTFOUND);
    break;

case IDM_SEARCHPREV:
    /* locate previous search string in edit control */
    if (!LocateText (hWnd,
     GetWindowWord (hWnd, WW_SEARCHCASE),
     FALSE,

lpszSearch))
ErrorNotify (hWnd, IDS_STRINGNOTFOUND);
    break;

case IDM_PORTCURFILE:
    /* if untitled, save first */
    if (GetWindowWord (hWnd, WW_UNTITLED) &&
!SendMessage (hWnd, WM_COMMAND, IDM_FILEOPEN, 0))
{
ErrorNotify (hWnd, IDS_NOFILETOPORT);
break;
}

    /* start port dialog with flags */
    hDlgPort = CreateDialog (GetModuleHandle (NULL),
     IDD_PORTDIALOG,
     hWnd,
     PortDlgProc);
    break;

case IDM_PORTBKGND:
    if (IsWindow (hDlgPortStatus))
ShowWindow (hDlgPortStatus, SW_SHOWNORMAL);
    else if (!(hDlgPortStatus = StartBackground (GetModuleHandle (NULL),
 hWnd,
 NULL)))
ErrorNotify (hWnd, IDS_BKPORTSTARTFAILED);
    break;

    default:
        return FALSE;
}
    return TRUE;
}

/* change the word wrapping in an edit control */
int WINAPI SetWrap (
    HWND    hWnd)

{
    DWORD   dwStyle;
HANDLEhNewData;
char*lpNewData;
    HWND    hWndOld = (HWND)GetWindowLong (hWnd, WL_HWNDEDIT);
    HWND    hWndNew;
    RECT    rc;
    UINT    uLen;
    UINT    uBegSel, uEndSel;
    WORD    wScroll = GetWindowWord (hWnd, WW_SCROLL);
HFONThEditFont = (HFONT)SendMessage (hWndOld, WM_GETFONT, 0, 0);

#ifndef WIN32
HANDLEhOldData;
char*lpOldData;
#endif

    char sz[40]; // temporary string for error messages

    /* turn off scroll bars if currently present */
    if (wScroll)
{
SetScrollRange (hWndOld, SB_VERT, 0, 0, FALSE);
SetScrollRange (hWndOld, SB_HORZ, 0, 0, FALSE);
}

    /* new edit style = old style XOR ES_AUTOHSCROLL */
    dwStyle = ES_AUTOHSCROLL ^ (DWORD)GetWindowLong (hWndOld, GWL_STYLE);

    /* save text selection */
    SendMessage (hWndOld, EM_GETSEL, (UINT)&uBegSel, (UINT)&uEndSel);

    /* create a new edit control with the new style */
    GetClientRect (hWnd, &rc);
    hWndNew = CreateWindow ("edit",
    "",
    dwStyle,
    rc.left,
    rc.top,
    rc.right-rc.left,
    rc.bottom-rc.top,
    hWnd,
    (HMENU)IDC_EDIT,
    (HANDLE)GetModuleHandle (NULL),
    0);

    /* check for window created */
    if (!IsWindow (hWndNew))
return IDS_EDITWNDFAILED;

    /* reset to no format lines */
    if (dwStyle & ES_AUTOHSCROLL)
        SendMessage (hWndOld, EM_FMTLINES, FALSE, 0);

#if defined (WIN32)
    uLen = GetWindowTextLength (hWndOld);

    hNewData = LocalAlloc (LHND, uLen+1);
    if (hNewData == NULL) {
wsprintf (sz, "Unable to allocate %u bytes", uLen+1);
MessageBox (GetFocus(), sz, "PORTTOOL::SetWrap", MB_OK);
DestroyWindow(hWndNew);
return IDS_GETHANDLEFAILED;
    }

    lpNewData = LocalLock (hNewData);
    if (lpNewData == NULL) {
MessageBox (GetFocus(), "Unable to lock memory", "PORTTOOL::SetWrap", MB_OK);
DestroyWindow(hWndNew);
LocalFree (hNewData);
return IDS_GETHANDLEFAILED;
    }

    GetWindowText (hWndOld, lpNewData, uLen+1);
    SetWindowText (hWndNew, lpNewData);
    LocalUnlock (hNewData);

#else
    /* get the data handle of the old control */
    if (!(hOldData = (HANDLE)SendMessage (hWndOld, EM_GETHANDLE, 0, 0)))
{
/* delete new edit window and return error */
DestroyWindow (hWndNew);
return IDS_GETHANDLEFAILED;
}

    /* increase edit control max character limit beyond 30,000 default */
    SendMessage (hWndNew, EM_LIMITTEXT, 0x0FFFFFFF, 0);

    /* get data handle to new edit control and reallocate to size of old edit text */
    hNewData = (HANDLE)SendMessage (hWndNew, EM_GETHANDLE, 0, 0);
    uLen = GetWindowTextLength (hWndOld);
    if (LocalReAlloc(hNewData, uLen+1, LHND) == NULL)
{
/* abort, clean up and return error */
DestroyWindow (hWndNew);
return IDS_REALLOCFAILED;
}

    lpOldData = LocalLock (hOldData);
    lpNewData = LocalLock (hNewData);
    lpNewData[uLen+1] = 0;

    /* copy from one buffer to the other */
    while (uLen-- > 0)
lpNewData[uLen] = lpOldData[uLen];

    /* unlock and release data buffers */
    LocalUnlock (hOldData);
    LocalUnlock (hNewData);

#endif

    /* swap windows */
    SetWindowLong (hWnd, WL_HWNDEDIT, (LONG)hWndNew);
    DestroyWindow (hWndOld);

#if !defined(WIN32)
    SendMessage (hWndNew, EM_SETHANDLE, (UINT)hNewData, 0);
#endif

    /* set line formatting */
    if (dwStyle & ES_AUTOHSCROLL)
        SendMessage (hWndNew, EM_FMTLINES, FALSE, 0);

    /* replace font in new edit control */
    if (hEditFont)
SendMessage (hWndNew, WM_SETFONT, (UINT)hEditFont, 0);

    /* set scroll bars if appropriate */
    if (wScroll)
{
SetScrollRange (hWndNew, SB_VERT, 0, 100, TRUE);
SetScrollRange (hWndNew, SB_HORZ, 0, (ES_AUTOHSCROLL & dwStyle ? 100 : 0), TRUE);
}

    /* Set limit to greater than 30K */
    SendMessage (hWndNew, EM_LIMITTEXT, 0x0FFFFFFF, 0);

    /* restore text selection, repaint and set focus */
    InvalidateRect (hWndNew, NULL, TRUE);
    UpdateWindow (hWndNew);
    SendMessage (hWndNew, EM_SETSEL, uBegSel, uEndSel);
    SetFocus (hWndNew);

#if defined(WIN32)
    LocalFree (hNewData);
#endif

    /* return success */
    return TRUE;
}

/* get win32 command line parameters */
BOOL WINAPI GetCmdLine(
    char    *lpStr,
    char    *lpszCmdLine,
BOOL*bBkgnd,
char*lpszPortFileName)
{
    if (*lpStr)
    {
    /* skip application name which precedes parameters */
    while (*lpStr != ' ' && *lpStr != 0)
        lpStr = CharNext(lpStr);;

/* skip spaces */
while (*lpStr == ' ' && *lpStr != 0)
    lpStr++;

/* indeed command line parameter(s) present */
if (*lpStr != 0)
    {
    /* if background switch, set flag and remove switch from command line */
    if ((*lpStr == '/' || *lpStr == '-') &&
(*(lpStr+1) == 'b' || *(lpStr+1) == 'B'))
{
*bBkgnd = TRUE;
lpStr += 2;

if (*lpStr == 0)
    *lpszCmdLine = 0;
else
    strcpy (lpszCmdLine, lpStr);
}
    /* maybe switch is embedded in parameter(s) somewhere */
    else
{
char    *pStr = lpStr;
char*pCmdLine  = lpszCmdLine;
char*pPortFile = lpszPortFileName;
int     i, nCnt;

while (*pStr != 0)
    {
    /* background switch is set, so prepare parameters and set flag */
    if ((*pStr == '/' || *pStr == '-') &&
(*(pStr+1) == 'b' || *(pStr+1) == 'B'))
{
*bBkgnd = TRUE;

/* copy from beg. of lpStr to *pStr to lpszCmdLine */
nCnt = pStr - lpStr;
for (i=0; i<nCnt; i++)
    lpszCmdLine[i] = lpStr[i];
lpszCmdLine[i] = 0;
strcat (lpszCmdLine, (pStr+2));

/* break from loop */
break;
}
else if ((*pStr == '/' || *pStr == '-') &&
(*(pStr+1) == 'i' || *(pStr+1) == 'I'))
{
  pStr = lpStr;

  while (*pStr != ' ' && *pStr != 0)
  {
pStr++;
lpStr++;
  }

  while (*pStr == ' ' && *pStr != 0)
  {
pStr++;
lpStr++;
  }

  while (*pStr != ' ' && *pStr != 0)
  {
*pPortFile = *pStr;
pPortFile++;
pStr++;
lpStr++;
  }
  *pPortFile = 0;
}

    pStr++;
    }

/* no switch found, can only edit one file, remove extra parameters */
if (*pStr == 0)
    {
    pStr = lpStr;

    while (*pStr != ' ' && *pStr != 0)
pStr++;

    if (*pStr == ' ')
*pStr = 0;

    strcpy (lpszCmdLine, lpStr);
    }
}
    }
else
    return FALSE;
}
    else
return FALSE;

    return TRUE;
}

/* display a Save, Yes|No|Cancel message box */
int WINAPI Save_YNC (
    HWND    hWnd)
{
    char    lpszMsg[MAX_PATH];
    char    lpszAppName[MAX_PATH];

    /* load string to prompt user */
    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_DATACHANGED,
lpszMsg,
sizeof (lpszMsg));
    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_APPNAME,
lpszAppName,
sizeof (lpszAppName));

    /* return user response */
    return (MessageBox (hWnd,
lpszMsg,
lpszAppName,
MB_YESNOCANCEL | MB_ICONQUESTION));
}

/* error notification routine */
VOID WINAPI ErrorNotify (
    HWND    hWnd,
    int     nErrorString)
{
    char    lpszAppName[MAX_PATH];
    char    lpszErrStr[MAX_PATH];
    char    szNum[10];

    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_APPNAME,
lpszAppName,
sizeof (lpszAppName));
    if (!LoadString ((HANDLE)GetModuleHandle (NULL),
             nErrorString,
             lpszErrStr,
             sizeof (lpszErrStr)))
    {
    LoadString((HANDLE)GetModuleHandle(NULL),
          IDS_UNKNOWN_ERROR,
          lpszErrStr,
          sizeof (lpszErrStr));
    strcat (lpszErrStr, itoa (nErrorString, szNum, 10));
    }

    MessageBox (NULL, lpszErrStr, lpszAppName, MB_OK | MB_ICONINFORMATION | MB_TASKMODAL);
}

VOID WINAPI SetWindowTitle (
    HWND    hWnd,
    char    *lpszFile)
{
    char    lpszAppName[MAX_PATH];

    if (LOBYTE(LOWORD(dwVersion))>=4) {
// On Windows 95, only put up the filename
SetWindowText (hWnd, lpszFile);
    } else {
/* load AppName and Window title string into window text */
LoadString ((HANDLE)GetModuleHandle (NULL),
    IDS_PRINTJOB,
    lpszAppName,
    sizeof (lpszAppName));
strcat (lpszAppName, lpszFile);
SetWindowText (hWnd, lpszAppName);
    }
}

/* extract a substring from the edit controls data buffer */
VOID WINAPI GetEditSubString (
    HWND    hWndEdit,
    PUINT   puStart,
    PUINT   puEnd,
    char    *lpszString)
{
    HANDLE    hEditData;
    char      *lpEditData;
    UINT      i;

    char sz[40]; // temporary string for error reporting

    /* maximum search string length is MAXSEARCHSTRING characters */
    if ((*puEnd - *puStart) > MAXSEARCHSTRING)
*puEnd = *puStart + MAXSEARCHSTRING;

#if defined(WIN32)

    i = GetWindowTextLength(hWndEdit);
    hEditData = LocalAlloc (LHND, i+1);
    if (hEditData == NULL) {
wsprintf (sz, "Unable to allocate %u bytes", i+1);
MessageBox (GetFocus(), sz, "PORTTOOL::GetEditSubString", MB_OK);
    }

    lpEditData = LocalLock (hEditData);
    if (lpEditData == NULL) {
MessageBox (GetFocus(), "Unable to lock memory", "PORTTOOL::GetEditSubString", MB_OK);
    } else {
GetWindowText (hWndEdit, lpEditData, i+1);
    }

#else

    /* get edit data handle and lock */
    hEditData = (HANDLE)SendMessage (hWndEdit, EM_GETHANDLE, 0, 0);
    lpEditData = (char *)LocalLock (hEditData);

#endif

    if (lpEditData != NULL) {
/* copy characters to string and terminate */
for (i=*puStart; i<*puEnd; i++)
    lpszString[i-*puStart] = lpEditData[i];
lpszString[i-*puStart] = 0;
    }

    /* unlock and return edit handle */
    LocalUnlock (hEditData);

#if defined(WIN32)

    LocalFree (hEditData);

#endif

}

/****************************************************************************
    My_mbschr:  strchr() DBCS version
****************************************************************************/
unsigned char * _CRTAPI1 My_mbschr(
    unsigned char *psz, unsigned short uiSep)
{
    while (*psz != '\0' && *psz != uiSep) {
        psz = CharNext(psz);
    }
    if (*psz == '\0' && uiSep != '\0') {
        return NULL;
    } else {
        return psz;
    }
}
/****************************************************************************
    My_mbstok:  strtok() DBCS version
****************************************************************************/
unsigned char * _CRTAPI1 My_mbstok(
    unsigned char *pszSrc, unsigned char *pszSep)
{
    static char *pszSave = NULL;
    char *pszHead;
    char *psz;

    if (pszSrc == NULL) {
        if (pszSave == NULL) {
            return NULL;
        } else {
            psz = pszSave;
        }
    } else {
        psz = pszSrc;
    }

    /*********************************************/
    /* Skip delimiters to find a head of a token */
    /*********************************************/
    while (*psz) {
        if (IsDBCSLeadByte(*psz)) {
            break;
        } else if (NULL == My_mbschr(pszSep, *psz)) {
            break;
        }
        psz++;
    }
    if (*psz == '\0') {
        //No more token
        return (pszSave = NULL);
    }
    pszHead = psz;

    /******************************/
    /* Search a Tail of the token */
    /******************************/
    while (*psz) {
        if (IsDBCSLeadByte(*psz)) {
            psz += 2;
            continue;
        } else if (NULL != My_mbschr(pszSep, *psz)) {
            break;
        }
        psz++;
    }
    if (*psz == '\0') {
        pszSave = NULL;
    } else {
        //Found next delimiter
        pszSave = psz + 1;
        *psz = '\0';
    }
    return pszHead;
}
/****************************************************************************
    My_mbsncpy:
****************************************************************************/
unsigned char * _CRTAPI1 My_mbsncpy(
    unsigned char *psz1, const unsigned char *psz2, size_t nLength)
{
    int nLen = (int)nLength;
    unsigned char *pszSv = psz1;

    while (0 < nLen) {
        if (*psz2 == '\0') {
            *psz1++ = '\0';
            nLen--;
        } else if (IsDBCSLeadByte(*psz2)) {
            if (nLen == 1) {
                *psz1 = '\0';
            } else {
                *psz1++ = *psz2++;
                *psz1++ = *psz2++;
            }
            nLen -= 2;
        } else {
            *psz1++ = *psz2++;
            nLen--;
        }
    }
    return pszSv;
}


PORTTOOL.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 <windows.h>
#include <commdlg.h>
#include <string.h>
#include <stdlib.h>

#define strncpy     My_mbsncpy
#define strtok      My_mbstok
unsigned char * _cdecl My_mbsncpy(
                        unsigned char *, const unsigned char *, size_t);
unsigned char * _cdecl My_mbstok(unsigned char *, unsigned char *);

#ifdef RC_INVOKED
#define ID(id) id
#else
#define ID(id) MAKEINTRESOURCE(id)
#endif

#undef EDIT_FIXED

// resource ID's
#define IDPortTool ID(1)
#define IDBkPort ID(2)

#define IDA_PORTTOOL 1

// edit control child window ID
#define IDC_EDIT 1000

// max string lengths
#define MAXSEARCHSTRING  80
#define MAXPTRDEFINITION 200
#define MAXPTRDEVICE 50
#define MAXPTRDRIVER 50
#define MAXPTRPORT 50

// extra byte window word and long definitions
#define WL_HWNDEDIT 0
#define WW_SCROLL 4
#define WW_UNTITLED 6
#define WW_SEARCHCASE 8
#define WW_SEARCHDN 10
#define WL_HPTRDEVNAMES  12
#define CBWNDEXTRA 16

// menuitem IDs, file submenu
#define IDM_FILENEW  1001
#define IDM_FILEOPEN 1002
#define IDM_FILESAVE 1003
#define IDM_FILESAVEAS 1004
#define IDM_FILEPRINT 1005
#define IDM_FILESETUP 1006
#define IDM_FILEEXIT 1007
#define IDM_FILEABOUT 1008
// edit submenu
#define IDM_EDITUNDO 2000
#define IDM_EDITCUT  2001
#define IDM_EDITCOPY 2002
#define IDM_EDITPASTE 2003
#define IDM_EDITCLEAR 2004
#define IDM_EDITSELECT 2005
#define IDM_EDITWRAP 2006
#define IDM_EDITSCROLL 2007
#define IDM_EDITFONT 2008
// search submenu
#define IDM_SEARCHFIND 3001
#define IDM_SEARCHNEXT 3002
#define IDM_SEARCHPREV 3003
// port submenu
#define IDM_PORTCURFILE  4000
#define IDM_PORTBKGND 4001

#define IDM_FUNCTION1 5000
#define IDM_FUNCTION2 5001
#define IDM_FUNCTION3 5002
#define IDM_FUNCTION4 5003
#define IDM_FUNCTION5 5004

// dialog & control IDs

// about dialog
#define IDD_ABOUT ID(300)
#define IDC_SYSTEM       301
#define IDC_APP          302
#define IDC_VERSION      303
#define IDC_COPYRIGHT    304
#define IDC_OSVERSION    305

// cancel print dialog
#define IDD_CANCELDLG ID(400)
#define IDC_PRINTDEVICE  401
#define IDC_PRINTPORT 402
#define IDC_PRINTTITLE 403

// port options dialog
#define IDD_OPTIONSDLG ID(500)
#define IDC_NOAPIS 501
#define IDC_NOMESSAGES 502
#define IDC_NOSTRUCTURES 503
#define IDC_NOMACROS 504
#define IDC_NOCONSTANTS  505
#define IDC_NOTYPES 506
#define IDC_NOCUSTOM 507
#define IDC_IGNORECASE 508
#define IDC_CURTOKEN 509
#define IDC_IGNORETOKEN  510

// port current file dialog
#define IDD_PORTDIALOG ID(600)
#define IDC_SEARCHFOUND  601
#define IDC_ISSUECNT 602
#define IDC_LINENO 603
#define IDC_LINE 604
#define IDC_ISSUE 605
#define IDC_CONTINUE 606
#define IDC_SUGGESTION 608
#define IDC_SUGGESTLABEL 609
#define IDC_OPTIONS 610
#define IDC_DONE 611
#define IDC_HELPM 612
#define IDC_RESTART 613

// background port status dialog
#define IDD_BKPORTDIALOG ID(700)
#define IDC_BKNOAPIS 702
#define IDC_BKNOMESSAGES 703
#define IDC_BKNOSTRUCTURES 704
#define IDC_BKNOMACROS 705
#define IDC_BKNOCONSTANTS 706
#define IDC_BKNOTYPES 707
#define IDC_BKNOCUSTOM 708
#define IDC_BKIGNORECASE 709
#define IDC_CHANGEOPTIONS 710
#define IDC_ADDFILE 711
#define IDC_COMPLETE 712
#define IDC_LINES 713
#define IDC_ISSUES 714
#define IDC_ABORTFILE 715
#define IDC_FILEPATH 716
#define IDC_FILELIST 717
#define IDC_BKDONE 718

// file I/O error stringtable entries
#define IDS_OPENFAILED  WM_USER+101
#define IDS_NOSIZE  WM_USER+102
#define IDS_READFAILED  WM_USER+103
#define IDS_REALLOCFAILED  WM_USER+104
#define IDS_EDITWNDFAILED  WM_USER+105
#define IDS_GETHANDLEFAILED  WM_USER+106
#define IDS_WRITEOPENFAILED  WM_USER+107
#define IDS_WRITEFAILED   WM_USER+108
#define IDS_PTRCOMMDLGFAILED  WM_USER+109
#define IDS_PRINTABORTED  WM_USER+110
#define IDS_STARTDOCFAILED  WM_USER+111
#define IDS_SETABORTPROCFAILED  WM_USER+112
#define IDS_CANCELDLGFAILED  WM_USER+113
#define IDS_PTRCONFIGFAILED  WM_USER+114
#define IDS_NOFILETOPORT  WM_USER+115
#define IDS_PORTINITFAILED  WM_USER+116
#define IDS_MEMORYFAILED  WM_USER+117
#define IDS_FONTFAILEDTOCREATE  WM_USER+118
#define IDS_LOADPORTFAILED  WM_USER+119
#define IDS_PROCADDRESSFAILED  WM_USER+120
#define IDS_UNTITLED  WM_USER+201
#define IDS_APPNAME  WM_USER+202
#define IDS_PRINTJOB  WM_USER+203
#define IDS_PRINTERROR  WM_USER+204
#define IDS_DATACHANGED   WM_USER+205
#define IDS_DEFAULTFILEEXT  WM_USER+206
#define IDS_SAVEASTITLE   WM_USER+207
#define IDS_FILEOPENTITLE  WM_USER+208
#define IDS_STRINGNOTFOUND  WM_USER+209
#define IDS_SEARCHDLGFAILED  WM_USER+210
#define IDS_WINDOWSAPPNAME  WM_USER+211
#define IDS_DEVICENAME  WM_USER+212
#define IDS_PORTFILE  WM_USER+213
#define IDS_SEARCHING  WM_USER+214
#define IDS_FOUND  WM_USER+215
#define IDS_NOMOREPORTISSUES  WM_USER+216
#define IDS_INIFILE  WM_USER+217
#define IDS_WINHELP  WM_USER+218
#define IDS_NOTYETIMPLEMENTED  WM_USER+219
#define IDS_NOT   WM_USER+220
#define IDS_PORTDLLFILE   WM_USER+221
#define IDS_CHECKSTRING   WM_USER+222
#define IDS_BKPORTNEWLINE  WM_USER+223
#define IDS_BKPORTHEADER  WM_USER+224
#define IDS_BKPORTTOKEN   WM_USER+225
#define IDS_BKPORTISSUE   WM_USER+226
#define IDS_BKPORTSUGGEST  WM_USER+227
#define IDS_BKPORTHELP  WM_USER+228
#define IDS_BKPORTHELPFILE  WM_USER+229
#define IDS_BKPORTEOL  WM_USER+230
#define IDS_BKPORTABORT   WM_USER+231
#define IDS_BKPORTSTATUS  WM_USER+232
#define IDS_BKPORTCANCEL  WM_USER+233
#define IDS_BKPORTSTARTFAILED  WM_USER+234
#define IDS_CANTBUILDFILELIST  WM_USER+235
#define IDS_PORTFILEBACKMSG  WM_USER+236
#define IDS_DEFAULTPORTEXT    WM_USER+237
#define IDS_PORTFILEOPENTITLE    WM_USER+238
#define IDS_UNKNOWN_ERROR         WM_USER+239
#define IDS_ERR_INVALID_THREADHND WM_USER+240
#define IDS_CANCEL_BKGND_PROCESS  WM_USER+241
#define IDS_ABORT_TITLE           WM_USER+242
#define IDS_FILE_FILTER_SPEC1     WM_USER+243
#define IDS_EXT_FILTER_SPEC1      WM_USER+244
#define IDS_FILE_FILTER_SPEC2     WM_USER+245
#define IDS_EXT_FILTER_SPEC2      WM_USER+246
#define IDS_FILE_FILTER_SPEC3     WM_USER+247
#define IDS_EXT_FILTER_SPEC3      WM_USER+248
#define IDS_FILE_FILTER_SPEC4     WM_USER+249
#define IDS_EXT_FILTER_SPEC4      WM_USER+250
#define IDS_FILE_FILTER_SPEC5     WM_USER+251
#define IDS_EXT_FILTER_SPEC5      WM_USER+252

// define background porting handle array indexes
#define BKPORT_ABORT0
#define BKPORT_STATUS1
#define nBKPORTEVENTS2

#define MAXBKTHREADS20

// define structure used in background porting
typedef struct tagBkPortFile
    {
    char    szFile[MAX_PATH];
    char    szFilePath[MAX_PATH];
    DWORD   dwPTFlags;
    HANDLE  hThread;
    HWND    hDlg;
    }BKPORTFILESTRUCT, * LPBKPORTFILESTRUCT;

// linked list structure defines background files being ported
typedef struct _BKFILELIST
    {
    BKPORTFILESTRUCTbkFile;
    HANDLEhEvents[nBKPORTEVENTS];
    LPVOIDNext;
    }BKFILELIST, * LPBKFILELIST;

// function prototypes defined in PORTTOOL.C
#define UM_SAVEFILE WM_USER+1
VOIDWINAPI ErrorNotify (HWND, int);
LONGWINAPI MainWndProc (HWND, UINT, UINT, LONG);
BOOLWINAPI AboutDlgProc (HWND, UINT, UINT, LONG);

// globals and function prototypes defined in PTFILE.C
externcharlpszFilePath[MAX_PATH];
BOOLWINAPI GetFileName(HWND, char *, char *);
BOOLWINAPI GetFileFromPath (char *, char *);
BOOLWINAPI GetIniFile (char *);
intWINAPI SaveFile (HWND, char *);
BOOLWINAPI SaveAsFileName (HWND, char *, char *);
intWINAPI LoadFile (HWND, char *);
BOOL WINAPI GetPortIniFileName ( HWND, char *, char *);

// globals and function prototypes defined in PTFIND.C
externcharlpszSearch[MAXSEARCHSTRING+1];
externHWNDhDlgSearch;
BOOLWINAPI FindDialog (HWND, WORD, WORD, char *);
BOOLWINAPI LocateText (HWND, WORD, WORD, char *);

// globals and function prototypes defined in PTPRINT.C
#define UM_CANCELPRINT  WM_USER+2
intWINAPI PrintFile (HWND);
intWINAPI PrinterSetup (HWND);
BOOLWINAPI GetPrinterConfig (HWND);

// globals and function prototypes defined in PTDLGS.C
BOOLWINAPI OptionsDlgProc (HWND, UINT, UINT, LONG);
BOOLWINAPI PortDlgProc (HWND, UINT, UINT, LONG);
BOOLWINAPI BkPortDlgProc (HWND, UINT, UINT, LONG);

// globals and function prototypes defined in PTBKPORT.C
#define UM_THREADCOMPLETE WM_USER+2
#define UM_STATUSUPDATE  WM_USER+3
HANDLEWINAPI StartBkPortThread (LPBKPORTFILESTRUCT);
BOOLWINAPI CreateEvents (HANDLE *, LPBKPORTFILESTRUCT);
voidWINAPI DestroyEvents (HANDLE *);

BOOL WINAPI InitPortData(CHAR *);

CHAR                lpszPortIniFilePath[MAX_PATH];


PTBKPORT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright (C) 1993-96 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 "porttool.h"
#include "port.h"

/* define line types returned from GetNextLine */
#define VALID_LINE 0
#define COMMENT_LINE 1
#define EOF_LINE 2

#define CARRIAGE_RETURN  13
#define LINE_FEED 10

/* define worker functions for this module */
int  WINAPI GetNextLine (char *, int, char *, char *, BOOL *);
int  WINAPI NextLineLength (char *, char *, int);
BOOL WINAPI BkFilePortThread (LPBKPORTFILESTRUCT);
void WINAPI DateTimeStamp (char *);

/* function creates a background porting thread */
HANDLE WINAPI StartBkPortThread (LPBKPORTFILESTRUCT    lpBkPort)
{
    DWORD    id;

    /* create thread with initial structure */
    return (lpBkPort->hThread = CreateThread ((LPSECURITY_ATTRIBUTES)NULL,
      4096,
      (LPTHREAD_START_ROUTINE)BkFilePortThread,
      (LPVOID)lpBkPort,
      0,
      &id));
}

/* independent thread function that performs background file porting */
BOOL WINAPI BkFilePortThread (
    LPBKPORTFILESTRUCT  lpBkPort)
{
    HANDLEhEvents[nBKPORTEVENTS];
    HANDLEhFile;
    OFSTRUCTof;
    DWORDnFileSize;
    DWORD       nBytes;
    HANDLEhFileBuffer;
    char*lpFile, *lpFilePtr;
    WORDwComplete, wIssues = 0;
    int nLines = 0;
    BOOLbCommentOn;
    HANDLEhLine;
    HANDLEhToken, hIssue, hSuggest, hHelp;
    RESULTrIssue;
    char*lpLine;
    charszHeader[MAX_PATH], szToken[50], szIssue[50], szdt[50],
szSuggest[50], szHelp[50], szHelpFile[50], szEOL[50], szNL[5];

    /* adjust our priority to below normal */
    SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_BELOW_NORMAL);

    /* attach our thread input to the parent thread so we can post messages directly */
    AttachThreadInput (GetCurrentThreadId (),
       GetWindowThreadProcessId (lpBkPort->hDlg, NULL),
       TRUE);

    /* load file comment strings */
    LoadString (GetModuleHandle (NULL), IDS_BKPORTNEWLINE, szNL, 5);
    LoadString (GetModuleHandle (NULL), IDS_BKPORTHEADER, szHeader, MAX_PATH);
    LoadString (GetModuleHandle (NULL), IDS_BKPORTTOKEN, szToken, 50);
    LoadString (GetModuleHandle (NULL), IDS_BKPORTISSUE, szIssue, 50);
    LoadString (GetModuleHandle (NULL), IDS_BKPORTSUGGEST, szSuggest, 50);
    LoadString (GetModuleHandle (NULL), IDS_BKPORTHELP, szHelp, 50);
    LoadString (GetModuleHandle (NULL), IDS_BKPORTHELPFILE, szHelpFile, 50);
    LoadString (GetModuleHandle (NULL), IDS_BKPORTEOL, szEOL, 50);

    /* initialize wait events for communication between threads */
    if (!CreateEvents (hEvents, lpBkPort))
return FALSE;

    /* open file for porting and read into buffer */
    if ((int)(hFile = (HANDLE)OpenFile (lpBkPort->szFilePath, &of, OF_READWRITE)) == -1)
{
DestroyEvents (hEvents);
return FALSE;
}

    /* global allocate buffer for file */
    if (!(hFileBuffer = GlobalAlloc (GPTR, (nFileSize = GetFileSize (hFile, NULL))+1)) ||
!(lpFile = (char *)GlobalLock (hFileBuffer)))
{
CloseHandle (hFile);
DestroyEvents (hEvents);
return FALSE;
}

    /* allocate initial line buffer of reasonable size */
    hLine = GlobalAlloc (GMEM_MOVEABLE, 1024);

    /* allocate local memory segments for porttool RESULT strings */
    if (!(rIssue.lpszToken = LocalLock (hToken = LocalAlloc (LHND, MAXTOKENLEN))) ||
!(rIssue.lpszHelpStr = LocalLock (hHelp = LocalAlloc (LHND, MAXHELPLEN))) ||
!(rIssue.lpszIssue = LocalLock (hIssue = LocalAlloc (LHND, MAXISSUELEN))) ||
!(rIssue.lpszSuggest = LocalLock (hSuggest = LocalAlloc (LHND, MAXSUGGESTLEN))))
{
CloseHandle (hFile);
GlobalUnlock (lpFile);
GlobalFree (hFileBuffer);
DestroyEvents (hEvents);
GlobalFree (hLine);
return FALSE;
}

    /* read entire file into buffer and zero terminate */
    ReadFile (hFile, lpFile, nFileSize, &nBytes, NULL);
    lpFile[nFileSize] = 0;
    lpFilePtr = lpFile;

    /* if bytes read not equal to size of file, abort */
    if (nBytes != nFileSize)
{
CloseHandle (hFile);
GlobalUnlock (lpFile);
GlobalFree (hFileBuffer);
DestroyEvents (hEvents);
GlobalFree (hLine);
return FALSE;
}

    /* reset file to size zero before beginning porting */
    SetFilePointer (hFile, 0, NULL, FILE_BEGIN);
    SetEndOfFile (hFile);
    WriteFile (hFile, szHeader, strlen (szHeader), &nBytes, NULL);
    WriteFile (hFile, lpBkPort->szFile, strlen (lpBkPort->szFile), &nBytes, NULL);
    WriteFile (hFile, szEOL, strlen (szEOL), &nBytes, NULL);
    WriteFile (hFile, szNL, strlen (szNL), &nBytes, NULL);

    while (TRUE)
{
/* wait 1ms for either abort or status events to signal */
switch (WaitForMultipleObjects (nBKPORTEVENTS, hEvents, FALSE, 1))
    {
    case BKPORT_ABORT:
{
charszAbort[MAX_PATH];

/* create time and date */
DateTimeStamp (szdt);

/* write header line */
WriteFile (hFile, szHeader, strlen (szHeader), &nBytes, NULL);
WriteFile (hFile, szdt, strlen (szdt), &nBytes, NULL);
WriteFile (hFile, szEOL, strlen (szEOL), &nBytes, NULL);
WriteFile (hFile, szNL, strlen (szNL), &nBytes, NULL);

/* load abort string and write to file */
LoadString (GetModuleHandle (NULL),
    IDS_BKPORTABORT,
    szAbort,
    MAX_PATH);
WriteFile (hFile, szAbort, strlen (szAbort), (LPDWORD)&nBytes, NULL);

/* write rest of file to disk */
WriteFile (hFile,
   (VOID *)lpFilePtr,
   (nFileSize - (lpFilePtr-lpFile)),
   (LPDWORD)&nBytes,
   NULL);

/* clean up */
CloseHandle (hFile);
GlobalUnlock (lpFile);
GlobalFree (hFileBuffer);
GlobalFree (hLine);
DestroyEvents (hEvents);

/* free RESULT strings */
LocalUnlock (hToken); LocalFree (hToken);
LocalUnlock (hHelp); LocalFree (hHelp);
LocalUnlock (hIssue); LocalFree (hIssue);
LocalUnlock (hSuggest); LocalFree (hSuggest);

/* exit thread */
return FALSE;
}
break;

    case BKPORT_STATUS:
/* post message to parent thread with status info */
PostMessage (lpBkPort->hDlg,
     UM_STATUSUPDATE,
     MAKELONG (wIssues, wComplete),
     nLines);
break;

    case WAIT_TIMEOUT:
/* if we timed out ignore */
break;

    default:
/* anything else is an error */
ErrorNotify (lpBkPort->hDlg, GetLastError ());
goto DONE;
break;
    }

/* reset line buffer */
GlobalReAlloc (hLine,
       NextLineLength (lpFilePtr, lpFile, nFileSize),
       GMEM_MOVEABLE);
lpLine = (char *)GlobalLock (hLine);
*lpLine = 0;

/* get next line from  file buffer */
switch (GetNextLine (lpFile, nFileSize, lpFilePtr, lpLine, &bCommentOn))
    {
    /* check valid strings for porting issues */
    case VALID_LINE:
/* initialize rIssue string lengths */
*(WORD *)rIssue.lpszToken = MAXTOKENLEN;
*(WORD *)rIssue.lpszHelpStr = MAXHELPLEN;
*(WORD *)rIssue.lpszIssue = MAXISSUELEN;
*(WORD *)rIssue.lpszSuggest = MAXSUGGESTLEN;

if (CheckString (lpLine, lpBkPort->dwPTFlags, &rIssue))
    {
    /* create time and date */
    DateTimeStamp (szdt);

    /* write header line */
    WriteFile (hFile, szNL, strlen (szNL), &nBytes, NULL);
    WriteFile (hFile, szHeader, strlen (szHeader), &nBytes, NULL);
    WriteFile (hFile, szdt, strlen (szdt), &nBytes, NULL);
    WriteFile (hFile, szEOL, strlen (szEOL), &nBytes, NULL);

    /* write token line */
    WriteFile (hFile, szToken, strlen (szToken), &nBytes, NULL);
    WriteFile (hFile,
       rIssue.lpszToken,
       strlen (rIssue.lpszToken),
       &nBytes,
       NULL);
    WriteFile (hFile, szEOL, strlen (szEOL), &nBytes, NULL);

    /* write issue line */
    WriteFile (hFile, szIssue, strlen (szIssue), &nBytes, NULL);
    WriteFile (hFile,
       rIssue.lpszIssue,
       strlen (rIssue.lpszIssue),
       &nBytes,
       NULL);
    WriteFile (hFile, szEOL, strlen (szEOL), &nBytes, NULL);

    /* if suggestion */
    if (*(rIssue.lpszSuggest))
{
WriteFile (hFile, szSuggest, strlen (szSuggest), &nBytes, NULL);
WriteFile (hFile,
   rIssue.lpszSuggest,
   strlen (rIssue.lpszSuggest),
   &nBytes,
   NULL);
WriteFile (hFile, szEOL, strlen (szEOL), &nBytes, NULL);
}

    /* if help string */
    if (*(rIssue.lpszSuggest))
{
WriteFile (hFile, szHelp, strlen (szHelp), &nBytes, NULL);
WriteFile (hFile,
   rIssue.lpszHelpStr,
   strlen (rIssue.lpszHelpStr),
   &nBytes,
   NULL);
WriteFile (hFile, szHelpFile, strlen (szHelpFile), &nBytes, NULL);
WriteFile (hFile, szEOL, strlen (szEOL), &nBytes, NULL);
}

    wIssues++;
    }

    case COMMENT_LINE:
/* write line to file whether comment or not */
WriteFile (hFile, lpLine, strlen (lpLine), &nBytes, NULL);
break;

    case EOF_LINE:
if (*lpLine)
    WriteFile (hFile, lpLine, strlen (lpLine), &nBytes, NULL);
goto DONE;
break;
    }

/* unlock line buffer */
GlobalUnlock (hLine);

/* update status counts */
lpFilePtr += strlen (lpLine);
nLines++;
wComplete = (WORD)(((lpFilePtr-lpFile)*100)/nFileSize);
}

DONE:
    /* clean up */
    CloseHandle (hFile);
    GlobalUnlock (lpFile);
    GlobalFree (hFileBuffer);
    GlobalFree (hLine);
    DestroyEvents (hEvents);

    /* free RESULT strings */
    LocalUnlock (hToken); LocalFree (hToken);
    LocalUnlock (hHelp); LocalFree (hHelp);
    LocalUnlock (hIssue); LocalFree (hIssue);
    LocalUnlock (hSuggest); LocalFree (hSuggest);

    /* send message to parent that thread is dead */
    PostMessage (lpBkPort->hDlg,
 UM_THREADCOMPLETE,
 (WPARAM)lpBkPort->hThread,
 0);

    /* exit thread */
    return TRUE;
}

void WINAPI DateTimeStamp (
    char    *lpszDT)
{
    SYSTEMTIME  dt;
    char  Buff[10];

    /* create time and date stamp */
    GetSystemTime (&dt);

    //
    // On Japanese system change the date display order,
    //  we should do this right w/ locales eventually.
    //
    if (PRIMARYLANGID(GetUserDefaultLangID ()) == LANG_JAPANESE) {

      strcpy (lpszDT, itoa (dt.wYear, Buff, 10));
      strcat (lpszDT, "/");
      strcat (lpszDT, itoa (dt.wMonth, Buff, 10));
      strcat (lpszDT, "/");
      strcat (lpszDT, itoa (dt.wDay, Buff, 10));
    } else {
      strcpy (lpszDT, itoa (dt.wMonth, Buff, 10));
      strcat (lpszDT, "/");
      strcat (lpszDT, itoa (dt.wDay, Buff, 10));
      strcat (lpszDT, "/");
      strcat (lpszDT, itoa (dt.wYear, Buff, 10));
    }

    strcat (lpszDT, "    ");
    strcat (lpszDT, itoa (dt.wHour, Buff, 10));
    strcat (lpszDT, ":");
    strcat (lpszDT, itoa (dt.wMinute, Buff, 10));
}

BOOL WINAPI CreateEvents (
    HANDLE  *lphEvents,
    LPBKPORTFILESTRUCT  lpBkPort)
{
    char    szEvent[MAX_PATH];

    LoadString (GetModuleHandle (NULL), IDS_BKPORTABORT, szEvent, MAX_PATH);
    strcat (szEvent, lpBkPort->szFile);
    if (!(lphEvents[BKPORT_ABORT] = CreateEvent (NULL, TRUE, FALSE, szEvent)))
return FALSE;

    LoadString (GetModuleHandle (NULL), IDS_BKPORTSTATUS, szEvent, MAX_PATH);
    strcat (szEvent, lpBkPort->szFile);
    if (!(lphEvents[BKPORT_STATUS] = CreateEvent (NULL, TRUE, FALSE, szEvent)))
{
CloseHandle (lphEvents[BKPORT_ABORT]);
return FALSE;
}

    /* return success */
    return TRUE;
}

void WINAPI DestroyEvents (
    HANDLE    *lphEvents)
{
    /* close event handles */
    CloseHandle (lphEvents[BKPORT_ABORT]);
    CloseHandle (lphEvents[BKPORT_STATUS]);
}

int WINAPI NextLineLength (
    char    *lpFilePtr,
    char    *lpFile,
    int     nFileSize)
{
    int    nCnt=0;
    char   *lpf = lpFilePtr;

    /* count all characters up to end of file or CR/LF sequence */
    while (lpf &&
   lpf-lpFile < nFileSize &&
   *lpf       != CARRIAGE_RETURN &&
   *(lpf+1)   != LINE_FEED)
{
lpf++;
nCnt++;
}

    /* length plus 3 for CR/LF/0 terminator sequence */
    return nCnt + 3;
}

int WINAPI GetNextLine (
    char    *lpFile,
    int     nFileSize,
    char    *lpFilePtr,
    char    *lpLine,
    BOOL    *bCommentOn)
{
    char    *lpf = lpFilePtr;
    char    *lpl = lpLine;

    /* copy all characters up to end of file or CR/LF sequence */
    while (lpf &&
   lpf-lpFile < nFileSize &&
   *lpf       != CARRIAGE_RETURN &&
   *(lpf+1)   != LINE_FEED)
*lpl++ = *lpf++;

    /* check for end of buffer */
    if (lpf-lpFile >= nFileSize)
return EOF_LINE;

    /* copy carriage return and line feed to line and terminate */
    *lpl++ = *lpf++;
    *lpl++ = *lpf++;
    *lpl = 0;

    /* increment lpl to first non space character in line */
    lpl = lpLine;
    while (*lpl == ' ')
lpl++;

    /* see if single line comments exist */
    if (*lpl == '/' &&
*(lpl+1) == '/')
return COMMENT_LINE;

    /* see if comments begin */
    if (*lpl == '/'  &&
*(lpl+1) == '*')
*bCommentOn = TRUE;

    /* if comment on, see if it terminates yet */
    if (*bCommentOn)
{
lpl = lpLine;
while (*lpl)
    {
    if (*lpl == '*'  &&
*(lpl+1) == '/')
{
*bCommentOn = FALSE;
break;
}
    lpl = CharNext(lpl);
    }

/* if more text on line, valid line */
while (*lpl)
    if (*lpl != '*' &&
*(lpl+1) != '/')
return VALID_LINE;
    else
return COMMENT_LINE;
}

    /* if haven't returned yet, must be valid line */
    return VALID_LINE;
}


PTDLGS.C

#include "porttool.h"
#include "port.h"

int DLGOFFSET = 100;

RESULTrIssue;

HANDLE    hBkFileHeap;
extern HWND    hDlgPort;
extern HWND    hDlgPortStatus;

/* function prototypes for helper functions */
void WINAPI GrowDialog (HWND, BOOL);
BOOL WINAPI GetHelpFileName (char *);
BOOL WINAPI BuildFileList (char *, LPBKFILELIST *);
BOOL WINAPI AddFile (char *, char *, BKFILELIST *);
BOOL WINAPI RemoveFile (char *, LPBKFILELIST *);
BOOL WINAPI FreeFileList (BKFILELIST *);

BOOL MySetEvent (HWND hWnd, HANDLE hEvent);
BOOL MyResetEvent (HWND hWnd, HANDLE hEvent);

/* port options dialog */
BOOL WINAPI OptionsDlgProc (
    HWND    hDlg,
    UINT    uMsg,
    UINT    uParam,
    LONG    lParam)
{
BOOL       bRet = TRUE;
static  DWORD      *dwPTFlags;
static  HFONT      hStrikeoutFont;
static  HFONT      hSystemFont;
LOGFONT    lf;

    switch (uMsg)
{
case WM_INITDIALOG:
    /* create strikeout font for ignored tokens */
    hSystemFont = GetStockObject (SYSTEM_FONT);
    GetObject (hSystemFont, sizeof (LOGFONT), &lf);
    lf.lfStrikeOut = TRUE;
    hStrikeoutFont = CreateFontIndirect (&lf);

    /* initialize token control with stock system font */
    SendMessage (GetDlgItem (hDlg, IDC_CURTOKEN),
 WM_SETFONT,
 (UINT)hSystemFont,
 FALSE);

    /* save dwPTFlags from lParam */
    dwPTFlags = (DWORD *)lParam;

    /* initialize current token if any */
    if (*(WORD *)rIssue.lpszToken != MAXTOKENLEN)
SetDlgItemText (hDlg, IDC_CURTOKEN, rIssue.lpszToken);
    else
EnableWindow (GetDlgItem (hDlg, IDC_IGNORETOKEN), FALSE);

    /* initialize search flag check boxes */
    CheckDlgButton (hDlg, IDC_NOAPIS, (*dwPTFlags & PT_NOAPIS));
    CheckDlgButton (hDlg, IDC_NOMESSAGES, (*dwPTFlags & PT_NOMESSAGES));
    CheckDlgButton (hDlg, IDC_NOSTRUCTURES, (*dwPTFlags & PT_NOSTRUCTURES));
    CheckDlgButton (hDlg, IDC_NOMACROS, (*dwPTFlags & PT_NOMACROS));
    CheckDlgButton (hDlg, IDC_NOCONSTANTS, (*dwPTFlags & PT_NOCONSTANTS));
    CheckDlgButton (hDlg, IDC_NOTYPES, (*dwPTFlags & PT_NOTYPES));
    CheckDlgButton (hDlg, IDC_NOCUSTOM, (*dwPTFlags & PT_NOCUSTOM));
    CheckDlgButton (hDlg, IDC_IGNORECASE, (*dwPTFlags & PT_IGNORECASE));

    /* set focus to first check box, return FALSE */
    SetFocus (GetDlgItem (hDlg, IDC_NOAPIS));
    bRet = FALSE;
    break;

case WM_COMMAND:
    switch (LOWORD (uParam))
{
case IDOK:
    /* get check box states and return as FLAGS in UM_PORT message */
    *dwPTFlags = (*dwPTFlags & ~PT_IGNORECASE) ^
(IsDlgButtonChecked (hDlg, IDC_IGNORECASE) ? PT_IGNORECASE : 0);
    *dwPTFlags = (*dwPTFlags & ~PT_NOAPIS) ^
(IsDlgButtonChecked (hDlg, IDC_NOAPIS) ? PT_NOAPIS : 0);
    *dwPTFlags = (*dwPTFlags & ~PT_NOMESSAGES) ^
(IsDlgButtonChecked (hDlg, IDC_NOMESSAGES) ? PT_NOMESSAGES : 0);
    *dwPTFlags = (*dwPTFlags & ~PT_NOSTRUCTURES) ^
(IsDlgButtonChecked (hDlg, IDC_NOSTRUCTURES) ? PT_NOSTRUCTURES : 0);
    *dwPTFlags = (*dwPTFlags & ~PT_NOMACROS) ^
(IsDlgButtonChecked (hDlg, IDC_NOMACROS) ? PT_NOMACROS : 0);
    *dwPTFlags = (*dwPTFlags & ~PT_NOCONSTANTS) ^
(IsDlgButtonChecked (hDlg, IDC_NOCONSTANTS) ? PT_NOCONSTANTS : 0);
    *dwPTFlags = (*dwPTFlags & ~PT_NOTYPES) ^
(IsDlgButtonChecked (hDlg, IDC_NOTYPES) ? PT_NOTYPES : 0);
    *dwPTFlags = (*dwPTFlags & ~PT_NOCUSTOM) ^
(IsDlgButtonChecked (hDlg, IDC_NOCUSTOM) ? PT_NOCUSTOM : 0);

case IDCANCEL:
    SendMessage (GetDlgItem (hDlg, IDC_CURTOKEN), WM_SETFONT, 0, FALSE);
    DeleteObject (hStrikeoutFont);
    EndDialog (hDlg, LOWORD (uParam) == IDOK);
    break;

case IDC_IGNORETOKEN:
    /* toggle ignore bit */
    *dwPTFlags ^= PT_IGNORETOKEN;

    /* have control draw in strikeout if ignored */
    if (*dwPTFlags & PT_IGNORETOKEN)
SendMessage (GetDlgItem (hDlg, IDC_CURTOKEN),
     WM_SETFONT,
     (UINT)hStrikeoutFont,
     TRUE);
    /* else draw in system font */
    else
SendMessage (GetDlgItem (hDlg, IDC_CURTOKEN),
     WM_SETFONT,
     (UINT)hSystemFont,
     TRUE);
    break;
}
    break;

default:
    bRet = FALSE;
    break;
}

    /* return (message was processed); */
    return bRet;
}

/* port options dialog */
BOOL WINAPI PortDlgProc (
    HWND    hDlg,
    UINT    uMsg,
    UINT    uParam,
    LONG    lParam)
{
BOOL    bRet = TRUE;
static  DWORD   dwPTFlags;
static  BOOL    bSearching = TRUE;
static  BOOL    bHelpActive = FALSE, bIsHelpFile = FALSE;
static  HBRUSH  hBkBrush;
static  HWND    hWndEdit;
static  HANDLE  hEditData = NULL;
static  int     nIssues = 0;
static  int     iLineNo, iStartPos;
static  HLOCAL  hToken, hHelp, hIssue, hSuggest;
static  HLOCAL  hEditLine;

static  UINT    uLen = 0;

    switch (uMsg)
{
case WM_INITDIALOG:
    {
    char    lpszTitle[MAX_PATH];
    char    lpszFilename[MAX_PATH];
    RECT    rc;

    HDC     hDC;
    HFONT   hFont;
    TEXTMETRIC tm;

    hFont = GetStockObject(SYSTEM_FONT);
    hDC = GetDC(NULL);
    hFont = SelectObject(hDC, hFont);
    GetTextMetrics(hDC, &tm);
    SelectObject(hDC, hFont);
    ReleaseDC(NULL, hDC);
    DLGOFFSET = tm.tmHeight * 5;

    /* reposition self on bottom of screen */
    GetWindowRect (hDlg, &rc);
    SetWindowPos (hDlg,
  NULL,
  rc.left,
  GetSystemMetrics (SM_CYSCREEN) -
   (rc.bottom - rc.top + DLGOFFSET),
  rc.right-rc.left,
  rc.bottom-rc.top,
  SWP_NOZORDER | SWP_NOSIZE);

    /* set help available flag */
    if (GetHelpFileName (lpszTitle))
{
EnableWindow (GetDlgItem (hDlg, IDC_HELPM), TRUE);
bIsHelpFile = TRUE;
}

    /* allocate strings for Issue struct from local heap to reduce stack overhead */
    if (!(rIssue.lpszToken = LocalLock (hToken = LocalAlloc (LHND, MAXTOKENLEN))) ||
!(rIssue.lpszHelpStr = LocalLock (hHelp = LocalAlloc (LHND, MAXHELPLEN))) ||
!(rIssue.lpszIssue = LocalLock (hIssue = LocalAlloc (LHND, MAXISSUELEN))) ||
!(rIssue.lpszSuggest = LocalLock (hSuggest = LocalAlloc (LHND, MAXSUGGESTLEN))))
{
ErrorNotify (hDlg, IDS_MEMORYFAILED);
PostMessage (hDlg, WM_COMMAND, IDC_DONE, 0);
break;
}
    /* initialize line and token offset position stuff */
    iLineNo = 0;
    iStartPos = 0;
    rIssue.nPosToken = 0;

    /* initialize background brush for use in WM_CTLCOLOR message */
    hBkBrush = (HBRUSH)GetClassLong (hDlg, GCL_HBRBACKGROUND);

    /* set initial search flags to default */
    dwPTFlags = 0;

    /* initialize filename in caption */
    LoadString (GetModuleHandle (NULL), IDS_PORTFILE, lpszTitle, MAX_PATH);
    GetFileFromPath (lpszFilePath, lpszFilename);
    strcat (lpszTitle, lpszFilename);
    SetWindowText (hDlg, lpszTitle);

    /* IDC_SUGGESTION to SW_HIDE */
    ShowWindow (GetDlgItem (hDlg, IDC_SUGGESTION), SW_HIDE);
    ShowWindow (GetDlgItem (hDlg, IDC_SUGGESTLABEL), SW_HIDE);

    /* get edit window and data handle */
    hWndEdit = (HWND)GetWindowLong (GetParent (hDlg), WL_HWNDEDIT);

#if !defined (WIN32)

    hEditData = (HANDLE)SendMessage (hWndEdit, EM_GETHANDLE, 0, 0);

#else
    {
char    *lpEditData;
char    sz[80];
MEMORYSTATUS memstat;

uLen = GetWindowTextLength (hWndEdit);
hEditData = LocalAlloc (LHND, uLen+1);
if (hEditData == NULL) {
    memstat.dwLength = sizeof(MEMORYSTATUS);
    GlobalMemoryStatus (&memstat);
    wsprintf (sz, "Failed Allocation: %u | %u (%u%%)", (UINT)uLen+1, (UINT)memstat.dwAvailPhys, memstat.dwMemoryLoad);
    //MessageBox (GetFocus(), sz, "PortTool", MB_OK);
    uLen = 500000;
    while (hEditData == NULL && uLen > 1000) {
uLen -= 1000;
hEditData = LocalAlloc (LHND, uLen+1);
    }
    wsprintf (sz, "hEditData = %u : uLen = %u", (UINT)hEditData, (UINT)uLen);
    //MessageBox (GetFocus(), sz, "PortTool", MB_OK);
}
lpEditData = LocalLock (hEditData);
if (lpEditData == NULL) {
    MessageBox (GetFocus(), "Unalble To Lock Memory!", "PortTool", MB_OK);
} else {
    UINT i;
    i = GetWindowText (hWndEdit, lpEditData, uLen+1);
    wsprintf (sz, "length: %u", i);
    //MessageBox (GetFocus(), sz, "PortTool", MB_OK);
}
LocalUnlock (hEditData);
    }
#endif

    /* allocate here, reallocate later when needed */
    hEditLine = LocalAlloc (LHND, 1);

    /* post message to start ball rolling */
    PostMessage (hDlg, WM_COMMAND, (UINT)IDC_CONTINUE, 0);

    /* don't worry about focus here since were going to drive the search anyway */
    bRet = TRUE;
    }
    //MessageBox (GetFocus(), "End Of Init", "PortTool", MB_OK);
    break;

case WM_COMMAND:
    switch (LOWORD (uParam))
{
case IDC_CONTINUE:
    {
    int     iLastLine, nCharOffset, nLineLen;
    MSG     msg;
    char    lpszBuff[MAXTOKENLEN];
    char    *lpszLine;
    char    *lpLine;
    char    *lpEditData;

    /* disable continue button */
    EnableWindow (GetDlgItem (hDlg, IDC_CONTINUE), FALSE);
    EnableWindow (GetDlgItem (hDlg, IDC_OPTIONS), FALSE);
    EnableWindow (GetDlgItem (hDlg, IDCANCEL), TRUE);

    /* set IDC_SEARCHFOUND to green searching */
    LoadString (GetModuleHandle (NULL), IDS_SEARCHING,
lpszBuff,
sizeof (lpszBuff));
    SetWindowText (GetDlgItem (hDlg, IDC_SEARCHFOUND), lpszBuff);
    bSearching = TRUE;

    /* set last line */
    iLastLine = (int)SendMessage (hWndEdit, EM_GETLINECOUNT, 0, 0);

    /* find next port issue */
    while (TRUE)
{
if (iLineNo >= iLastLine)
    {
    /* no more issues found, so clean up and go away */
    ErrorNotify (hDlg, IDS_NOMOREPORTISSUES);

    /* nullify any selection in line edit control */
    SendMessage (GetDlgItem (hDlg, IDC_LINE), EM_SETSEL, 0, 0);
    break;
    }

/* increment line no */
SetWindowText (GetDlgItem (hDlg, IDC_LINENO),
       itoa (iLineNo, lpszBuff, 10));

/* get length and number of edit line */
nCharOffset = SendMessage (hWndEdit, EM_LINEINDEX, iLineNo, 0);
if ((nLineLen = SendMessage (hWndEdit, EM_LINELENGTH, nCharOffset, 0)) <= 2)
    goto NEXT_LINE;

/* allocate enough memory for edit line */
if (!(hEditLine = LocalReAlloc (hEditLine,
nLineLen+1,
LHND)))
    {
    /* no more issues found, so clean up and go away */
    ErrorNotify (hDlg, IDS_MEMORYFAILED);
    PostMessage (hDlg, WM_COMMAND, IDC_DONE, 0);
    break;
    }

/* get line from edit control, and null terminate */

/* get edit window and data handle */

lpEditData = LocalLock (hEditData);

lpLine = lpszLine = LocalLock (hEditLine);

strncpy (lpszLine, lpEditData+nCharOffset, nLineLen);
lpszLine[nLineLen] = 0;

/* increment the token position for multiple errors in a line */
lpLine += iStartPos;
LocalUnlock (hEditData);

/* initialize line and hilight token */
SetWindowText (GetDlgItem (hDlg, IDC_LINE), lpszLine);

/* reinitialize rIssue strings lengths */
*(WORD *)rIssue.lpszToken = MAXTOKENLEN;
*(WORD *)rIssue.lpszHelpStr = MAXHELPLEN;
*(WORD *)rIssue.lpszIssue = MAXISSUELEN;
*(WORD *)rIssue.lpszSuggest = MAXSUGGESTLEN;

/* search next line */
if (CheckString (lpLine, dwPTFlags, &rIssue))
    {
    /* set SEARCHFOUND string to found */
    LoadString (GetModuleHandle (NULL),
IDS_FOUND,
lpszBuff,
sizeof (lpszBuff));
    strcat (lpszBuff, rIssue.lpszToken);
    SetWindowText (GetDlgItem (hDlg, IDC_SEARCHFOUND), lpszBuff);

    /* reenable options button */
    EnableWindow (GetDlgItem (hDlg, IDC_OPTIONS), TRUE);

    /* set searching flag off */
    bSearching = FALSE;

    /* increment issue cnt */
    SetWindowText (GetDlgItem (hDlg, IDC_ISSUECNT),
   itoa (++nIssues, lpszBuff, 10));

    /* initialize Issue */
    SetWindowText (GetDlgItem (hDlg, IDC_ISSUE), rIssue.lpszIssue);

    /* if help, enble button */
    EnableWindow (GetDlgItem (hDlg, IDC_HELPM),
  ((*(rIssue.lpszSuggest) != 0) && bIsHelpFile));

    /* if suggest, show suggestion */
    if (*(rIssue.lpszSuggest))
{
SetWindowText (GetDlgItem (hDlg, IDC_SUGGESTION),
       rIssue.lpszSuggest);
if (!IsWindowVisible (GetDlgItem (hDlg, IDC_SUGGESTION)))
    GrowDialog (hDlg, TRUE);
}

    else if (IsWindowVisible (GetDlgItem (hDlg, IDC_SUGGESTION)))
GrowDialog (hDlg, FALSE);

    /* scroll parent edit control and select offending text */
    SendMessage (hWndEdit, EM_LINESCROLL, 0, iLineNo -
    SendMessage (hWndEdit, EM_GETFIRSTVISIBLELINE, 0, 0));
    SendMessage (hWndEdit,
 EM_SETSEL,
 iStartPos + nCharOffset + rIssue.nPosToken,
 iStartPos + nCharOffset + rIssue.nPosToken +
     strlen (rIssue.lpszToken));

    /* select text in line edit control */
    SendMessage (GetDlgItem (hDlg, IDC_LINE),
 EM_SETSEL,
 iStartPos + rIssue.nPosToken,
 iStartPos + rIssue.nPosToken +
     strlen (rIssue.lpszToken));

    /* reset nPosToken to check rest of line */
    iStartPos += (rIssue.nPosToken + strlen (rIssue.lpszToken));
    LocalUnlock (hEditLine);
    break;
    }

/* call peek message to let user cancel if they choose */
if (PeekMessage (&msg,
 GetDlgItem (hDlg, IDCANCEL),
 WM_LBUTTONDOWN,
 WM_LBUTTONDOWN,
 PM_REMOVE))
    {
    /* reset appropriate buttons */
    EnableWindow (GetDlgItem (hDlg, IDCANCEL), FALSE);
    EnableWindow (GetDlgItem (hDlg, IDC_HELPM), FALSE);
    EnableWindow (GetDlgItem (hDlg, IDC_OPTIONS), TRUE);

    /* break to let message get delivered */
    break;
    }

/* also let the user exit from searching */
if (PeekMessage (&msg,
 GetDlgItem (hDlg, IDC_DONE),
 WM_LBUTTONDOWN,
 WM_LBUTTONDOWN,
 PM_REMOVE))
    {
    PostMessage (hDlg, WM_COMMAND, IDC_DONE, 0);
    break;
    }

/* unlock local edit line */
LocalUnlock (hEditLine);

/* reset token position */
rIssue.nPosToken = 0;
iStartPos = 0;
NEXT_LINE:
/* increment line and continue */
iLineNo++;
}

    /* enable continue button unless at end of file */
    if (iLineNo < iLastLine)
{
EnableWindow (GetDlgItem (hDlg, IDC_CONTINUE), TRUE);
SetFocus (GetDlgItem (hDlg, IDC_CONTINUE));
}
    else
{
EnableWindow (GetDlgItem (hDlg, IDC_CONTINUE), FALSE);
EnableWindow (GetDlgItem (hDlg, IDCANCEL), FALSE);
SetFocus (GetDlgItem (hDlg, IDC_DONE));
}
    }
    break;

case WM_CLOSE:
case IDC_DONE:
    {
    char    lpszFile[MAX_PATH];

    if (bHelpActive &&
GetHelpFileName (lpszFile))
WinHelp (hDlg, lpszFile, HELP_QUIT, 0);

    /* clean up and go away */
    LocalUnlock (hToken); LocalFree (hToken);
    LocalUnlock (hHelp); LocalFree (hHelp);
    LocalUnlock (hIssue); LocalFree (hIssue);
    LocalUnlock (hSuggest); LocalFree (hSuggest);
    LocalFree (hEditLine);
DestroyWindow (hDlg);
hDlgPort = NULL;

    }
    break;

case IDC_OPTIONS:
    {
    DWORD    dwOptions = dwPTFlags;

    /* call dialog to start port process */
    if (DialogBoxParam (GetModuleHandle (NULL),
IDD_OPTIONSDLG,
hDlg,
OptionsDlgProc,
(LPARAM)&dwOptions))
{
dwPTFlags = dwOptions;

/* if PT_IGNORETOKEN, call CheckString */
if (dwOptions & PT_IGNORETOKEN)
    {
    CheckString (rIssue.lpszToken, dwPTFlags, NULL);
    dwPTFlags ^= PT_IGNORETOKEN;
    }
}

    }
    break;

case IDC_HELPM:
    {
    char    lpszFile[MAX_PATH];

    if (bIsHelpFile && GetHelpFileName (lpszFile))
{
WinHelp (hDlg, lpszFile, HELP_KEY, (DWORD)rIssue.lpszHelpStr);
bHelpActive = TRUE;
}
    }
    break;

case IDC_RESTART:
    iLineNo = 0;
    rIssue.nPosToken = 0;
    iStartPos = 0;
    PostMessage (hDlg, WM_COMMAND, IDC_CONTINUE, 0);
    break;
}
    break;

default:
    bRet = FALSE;
    break;
}

    /* return (message was processed); */
    return bRet;
}

/* background porting status dialog */
BOOL WINAPI BkPortDlgProc (
    HWND    hDlg,
    UINT    uMsg,
    UINT    uParam,
    LONG    lParam)
{
  BOOL          bRet = TRUE;
  char          szFileName[MAX_PATH];
  char          szFilePath[MAX_PATH];
static    BKFILELIST    *lpbkFiles;
static    int           iCurThread;
static    BOOL          bStarted = FALSE;
  BKFILELIST    *lpNode;

//char szDebug[80];

// wsprintf (szDebug, "%i : [0x%X - 0x%X]", uMsg, uParam, lParam);
// OutputDebugString (szDebug);

    switch (uMsg)
{
case WM_INITDIALOG:
    {
    HWND          hIssues = GetDlgItem (hDlg, IDC_ISSUES);
    HWND          hLines = GetDlgItem (hDlg, IDC_LINES);
    HWND          hComplete = GetDlgItem (hDlg, IDC_COMPLETE);

    /* set background icon to porttool background icon and start minimized */
    SetClassLong (hDlg,
  GCL_HICON,
  (LONG)LoadIcon (GetModuleHandle (NULL), IDBkPort));

    lpbkFiles = NULL;
    iCurThread = -1;

    /* build list of files to port from lParam */
    if (lParam)
{
if (!BuildFileList ((char *)lParam, &lpbkFiles))
    {
    lpbkFiles = NULL;
    break;
    }
}

    else
{
/* get file from user first */
*szFileName = 0;
*szFilePath = 0;

GetFileName (hDlg, szFileName, szFilePath);
if (!BuildFileList (szFilePath, &lpbkFiles))
    {
    lpbkFiles = NULL;
    break;
    }
}

    lpNode = lpbkFiles;
    /* initialize each file in list */
    while (lpNode)
{
/* add filename to listbox */
SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
     LB_ADDSTRING,
     0,
     (LPARAM)lpNode->bkFile.szFile);

/* initialize some stuff */
lpNode->bkFile.hDlg = hDlg;
lpNode->bkFile.dwPTFlags = PT_DEFAULT;

/* start background thread on each file */
if (!StartBkPortThread (&lpNode->bkFile)) {
    MessageBox(GetFocus(), "Failed to spawn background porting thread", "PortTool", MB_OK);
}

/* traverse list */
lpNode = lpNode->Next;
}

    /* select first thread in listbox */
    SendMessage (GetDlgItem (hDlg, IDC_FILELIST), LB_SETCURSEL, 0, 0);
    SetDlgItemText (hDlg, IDC_FILEPATH, lpbkFiles->bkFile.szFilePath);

    /* if started with /b switch */
    if (!GetParent (hDlg))
ShowWindow (hDlg, SW_SHOWMINIMIZED);
    else
MySetEvent (hDlg, lpbkFiles->hEvents[BKPORT_STATUS]);

    iCurThread = 0;
    bStarted = TRUE;
}
    break;

case WM_SHOWWINDOW:
    if (bStarted)
{
int    i = 0;

lpNode = lpbkFiles;
while (i++ < iCurThread)
    lpNode = lpNode->Next;

if (!uParam && lpNode)
    MyResetEvent (hDlg, lpNode->hEvents[BKPORT_STATUS]);
else if(lpNode)
    MySetEvent (hDlg, lpNode->hEvents[BKPORT_STATUS]);
}
    break;

case WM_SIZE:
    if (bStarted) {
    int    i = 0;

    lpNode = lpbkFiles;

    while (i++ < iCurThread) {
        lpNode = lpNode->Next;
    }

    if (uParam == SIZEICONIC && lpNode) {
        MyResetEvent (hDlg, lpNode->hEvents[BKPORT_STATUS]);
    } else if(lpNode) {
        MySetEvent (hDlg, lpNode->hEvents[BKPORT_STATUS]);
    }
    }
    break;

case UM_STATUSUPDATE:
    {
    char    Buff[10];

    /* update status info controls */
    SetDlgItemText (hDlg, IDC_ISSUES, itoa (LOWORD (uParam), Buff, 10));
    SetDlgItemText (hDlg, IDC_COMPLETE, itoa (HIWORD (uParam), Buff, 10));
    SetDlgItemText (hDlg, IDC_LINES, itoa (lParam, Buff, 10));
    }
    break;

case UM_THREADCOMPLETE:
    {
    int    iThread = 0;

    /* find handle in list */
    lpNode = lpbkFiles;
    while (lpNode)
{
if ((HANDLE)uParam == lpNode->bkFile.hThread)
    break;
lpNode = lpNode->Next;
iThread++;
}

    if (lpNode)
{
/* remove file list item */
RemoveFile (lpNode->bkFile.szFilePath, &lpbkFiles);

/* remove item from list box */
SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
     LB_DELETESTRING,
     iThread,
     0);

/* if current thread ended and more threads exist */
if (iThread == iCurThread &&
    lpbkFiles             &&
    SendMessage (GetDlgItem (hDlg, IDC_FILELIST), LB_GETCOUNT, 0, 0))
    {
    SendMessage (GetDlgItem (hDlg, IDC_FILELIST), LB_SETCURSEL, 0, 0);
    MySetEvent (hDlg, lpbkFiles->hEvents[BKPORT_STATUS]);
    iCurThread = 0;
    }

else if (iThread == iCurThread)
    {
    iCurThread = -1;
    PostMessage (hDlg, WM_COMMAND, IDC_BKDONE, 0);
    }

/* clean up controls */
SetDlgItemText (hDlg, IDC_FILEPATH, "");
PostMessage (hDlg, UM_STATUSUPDATE, 0, 0);
}

    else
{
                CHAR szBuf[MAX_PATH];
                LoadString((HANDLE)GetModuleHandle(NULL),
                    IDS_ERR_INVALID_THREADHND,
                    szBuf,sizeof(szBuf));
MessageBox (hDlg, szBuf, NULL, MB_ICONSTOP | MB_OK);
ExitProcess (FALSE);
}

    }
    break;

case WM_COMMAND:
    switch (LOWORD (uParam))
{
case IDC_FILELIST:
    /* if new file selected change update signal to active thread */
    if (HIWORD (uParam) == LBN_SELCHANGE)
{
int    i = 0;
int    iNewThread = SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
 LB_GETCURSEL,
 0,
 0);

/* reset current thread */
lpNode = lpbkFiles;
while (lpNode)
    {
    if (i == iCurThread)
MyResetEvent (hDlg, lpNode->hEvents[BKPORT_STATUS]);

    if (i == iNewThread)
{
MySetEvent (hDlg, lpNode->hEvents[BKPORT_STATUS]);
SetDlgItemText (hDlg,
IDC_FILEPATH,
lpNode->bkFile.szFilePath);
}

    lpNode = lpNode->Next;
    i++;
    }

iCurThread = iNewThread;
}
    break;

case IDC_ABORTFILE:
    {
    int        i = 0;
    HCURSOR    hOldCursor;

    /* reset current thread */
    lpNode = lpbkFiles;
    while (lpNode)
{
if (i == iCurThread)
    {
    /* put hourglass cursor up */
    hOldCursor = (HCURSOR)SetClassLong (hDlg, GCL_HCURSOR, 0);
    SetCursor (LoadCursor (NULL, IDC_WAIT));

    /* abort porting file where it is */
    MySetEvent (hDlg, lpNode->hEvents[BKPORT_ABORT]);

    /* remove file from list when thread is dead */
    WaitForSingleObject (lpNode->bkFile.hThread, INFINITE);
    RemoveFile (lpNode->bkFile.szFilePath, &lpbkFiles);

    /* replace original cursor */
    SetClassLong (hDlg, GCL_HCURSOR, (LONG)hOldCursor);
    SetCursor (hOldCursor);

    /* update listbox */
    SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
 LB_DELETESTRING,
 iCurThread,
 0);

    /* select new event if any in listbox */
    if (SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
     LB_GETCOUNT,
     0,
     0))
{
SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
     LB_SETCURSEL,
     0,
     0);
MySetEvent (hDlg, lpbkFiles->hEvents[BKPORT_STATUS]);
iCurThread = 0;
}

    else
{
iCurThread = -1;
PostMessage (hDlg, WM_COMMAND, IDC_DONE, 0);
}

    /* clean up controls */
    SetDlgItemText (hDlg, IDC_FILEPATH, "");
    PostMessage (hDlg, UM_STATUSUPDATE, 0, 0);
    break;
    }

lpNode = lpNode->Next;
i++;
}
    }
    break;

case IDC_ADDFILE:
    {
    /* get file from user first */
    *szFileName = 0;
    *szFilePath = 0;

    /* add a file to the list */
    if (GetFileName (hDlg, szFileName, szFilePath))
{
/* if new list */
            if (!lpbkFiles)
            {
                if (!BuildFileList (szFilePath, &lpbkFiles))
                {
                    lpbkFiles = NULL;
                    break;
                }
            }
else if (!AddFile (szFilePath, szFileName, lpbkFiles))
    break;

/* find node in list */
lpNode = lpbkFiles;
while (lpNode)
    {
    if (!strcmp (lpNode->bkFile.szFilePath, szFilePath))
break;
    lpNode = lpNode->Next;
    }

if (lpNode)
    {
    /* add filename to listbox */
    SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
 LB_ADDSTRING,
 0,
 (LPARAM)lpNode->bkFile.szFile);

    /* initialize some stuff */
    lpNode->bkFile.hDlg = hDlg;
    lpNode->bkFile.dwPTFlags = PT_DEFAULT;

    /* start background thread on this file */
    StartBkPortThread (&lpNode->bkFile);

    /* if first thread */
    if (iCurThread == -1 ||
SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
     LB_GETCOUNT, 0, 0) == 1)
{
iCurThread = 0;
SendMessage (GetDlgItem (hDlg, IDC_FILELIST),
     LB_SETCURSEL,
     0,
     0);
SendMessage (hDlg,
     WM_COMMAND,
     MAKELONG (IDC_FILELIST, LBN_SELCHANGE),
     0);
}
    }
}
    }
    break;

case IDC_CHANGEOPTIONS:
    {
    DWORD    dwFlags;

dwFlags = 0L;
    dwFlags = (dwFlags & ~PT_IGNORECASE) ^
(IsDlgButtonChecked (hDlg, IDC_BKIGNORECASE) ? PT_IGNORECASE : 0);
    dwFlags = (dwFlags & ~PT_NOAPIS) ^
(IsDlgButtonChecked (hDlg, IDC_BKNOAPIS) ? PT_NOAPIS : 0);
    dwFlags = (dwFlags & ~PT_NOMESSAGES) ^
(IsDlgButtonChecked (hDlg, IDC_BKNOMESSAGES) ? PT_NOMESSAGES : 0);
    dwFlags = (dwFlags & ~PT_NOSTRUCTURES) ^
(IsDlgButtonChecked (hDlg, IDC_BKNOSTRUCTURES) ? PT_NOSTRUCTURES : 0);
    dwFlags = (dwFlags & ~PT_NOMACROS) ^
(IsDlgButtonChecked (hDlg, IDC_BKNOMACROS) ? PT_NOMACROS : 0);
    dwFlags = (dwFlags & ~PT_NOCONSTANTS) ^
(IsDlgButtonChecked (hDlg, IDC_BKNOCONSTANTS) ? PT_NOCONSTANTS : 0);
    dwFlags = (dwFlags & ~PT_NOTYPES) ^
(IsDlgButtonChecked (hDlg, IDC_BKNOTYPES) ? PT_NOTYPES : 0);
    dwFlags = (dwFlags & ~PT_NOCUSTOM) ^
(IsDlgButtonChecked (hDlg, IDC_BKNOCUSTOM) ? PT_NOCUSTOM : 0);

    /* change the options for the file being ported */
    lpbkFiles->bkFile.dwPTFlags = dwFlags;
    }
    break;

case IDCANCEL:
    {
    HCURSOR    hOldCursor;
    HANDLE     hThreads[MAXBKTHREADS];
    int        i = 0;
                    CHAR       szText[MAX_PATH]; // for LoadString()
                    CHAR       szTitle[MAX_PATH]; // for LoadString()
                    HMODULE    hModule = GetModuleHandle(NULL);

    /* put up confirm message */
                    LoadString((HANDLE)hModule,
                        IDS_CANCEL_BKGND_PROCESS,
                        szText,sizeof(szText));
                    LoadString((HANDLE)hModule,
                        IDS_ABORT_TITLE,
                        szTitle,sizeof(szTitle));
    if (MessageBox (hDlg,
    szText, szTitle,
    MB_ICONQUESTION | MB_OKCANCEL) == IDOK)
{
/* put hourglass cursor up */
hOldCursor = (HCURSOR)SetClassLong (hDlg, GCL_HCURSOR, 0);
SetCursor (LoadCursor (NULL, IDC_WAIT));

/* if any files in list */
if (lpbkFiles)
    {
    /* abort all background threads and build thread handle array */
    lpNode = lpbkFiles;
    while (lpNode)
{
MySetEvent (hDlg, lpNode->hEvents[BKPORT_ABORT]);
hThreads[i++] = lpNode->bkFile.hThread;
lpNode = lpNode->Next;
}

    /* wait on completion of background threads */
    WaitForMultipleObjects (i, hThreads, TRUE, INFINITE);

    /* free background port resources */
    FreeFileList (lpbkFiles);
    }

SetClassLong (hDlg, GCL_HCURSOR, (LONG)hOldCursor);
SetCursor (hOldCursor);
DestroyWindow (hDlg);
hDlgPortStatus = NULL;

}
    }
    break;

case IDC_BKDONE:
    /* if file list post message to cancel */
    if (lpbkFiles)
PostMessage (hDlg, WM_COMMAND, IDCANCEL, 0);
    else
DestroyWindow (hDlg);
hDlgPortStatus = NULL;
    break;

default:
    bRet = FALSE;
    break;
}
    break;

case WM_DESTROY:
    /* if no parent, post quit message */
    if (GetParent (hDlg) == NULL)
PostQuitMessage (1);
    break;

default:
    bRet = FALSE;
    break;
}

    return bRet;
}

/* funtion retrieves the help filename from the ini file */

BOOL WINAPI GetHelpFileName (
    char    *lpszFile)
{
    char        szAppName[30];
    char        szWinHelp[30];
    char        szDefault[] = "Default";
    OFSTRUCT    of;

    /* get help filename from ini file */
    LoadString (GetModuleHandle (NULL), IDS_APPNAME, szAppName, 30);
    LoadString (GetModuleHandle (NULL), IDS_WINHELP, szWinHelp, 30);
    GetPrivateProfileString (szAppName,
     szWinHelp,
     szDefault,
     lpszFile,
     MAX_PATH,
 lpszPortIniFilePath);

    /* test to see if help file exists */
    return (OpenFile (lpszFile, &of, OF_EXIST) != -1);
}

/* rearrange dialog and controls */
void WINAPI GrowDialog (
    HWND    hDlg,
    BOOL    bBigger)
{
    RECT    rc;
    int     nChange = (bBigger ? DLGOFFSET : -DLGOFFSET);

    /* grow main dialog */
    GetWindowRect (hDlg, &rc);
    SetWindowPos (hDlg,
  NULL,
  rc.left,
  rc.top,
  rc.right-rc.left,
  rc.bottom-rc.top + nChange,
  SWP_NOMOVE | SWP_NOZORDER);

    /* move stop button down */
    GetWindowRect (GetDlgItem (hDlg, IDCANCEL), &rc);
    ScreenToClient (hDlg, (LPPOINT)&rc);
    ScreenToClient (hDlg, (LPPOINT)&rc.right);
    SetWindowPos (GetDlgItem (hDlg, IDCANCEL),
  NULL,
  rc.left,
  rc.top + nChange,
  rc.right-rc.left,
  rc.bottom-rc.top,
  SWP_NOSIZE | SWP_NOZORDER);

    /* move CONTINUE button down */
    GetWindowRect (GetDlgItem (hDlg, IDC_CONTINUE), &rc);
    ScreenToClient (hDlg, (LPPOINT)&rc);
    ScreenToClient (hDlg, (LPPOINT)&rc.right);
    SetWindowPos (GetDlgItem (hDlg, IDC_CONTINUE),
  NULL,
  rc.left,
  rc.top + nChange,
  rc.right-rc.left,
  rc.bottom-rc.top,
  SWP_NOSIZE | SWP_NOZORDER);

    /* move restart button down */
    GetWindowRect (GetDlgItem (hDlg, IDC_RESTART), &rc);
    ScreenToClient (hDlg, (LPPOINT)&rc);
    ScreenToClient (hDlg, (LPPOINT)&rc.right);
    SetWindowPos (GetDlgItem (hDlg, IDC_RESTART),
  NULL,
  rc.left,
  rc.top + nChange,
  rc.right-rc.left,
  rc.bottom-rc.top,
  SWP_NOSIZE | SWP_NOZORDER);

    /* move options button down */
    GetWindowRect (GetDlgItem (hDlg, IDC_OPTIONS), &rc);
    ScreenToClient (hDlg, (LPPOINT)&rc);
    ScreenToClient (hDlg, (LPPOINT)&rc.right);
    SetWindowPos (GetDlgItem (hDlg, IDC_OPTIONS),
  NULL,
  rc.left,
  rc.top + nChange,
  rc.right-rc.left,
  rc.bottom-rc.top,
  SWP_NOSIZE | SWP_NOZORDER);

    /* move help button down */
    GetWindowRect (GetDlgItem (hDlg, IDC_HELPM), &rc);
    ScreenToClient (hDlg, (LPPOINT)&rc);
    ScreenToClient (hDlg, (LPPOINT)&rc.right);
    SetWindowPos (GetDlgItem (hDlg, IDC_HELPM),
  NULL,
  rc.left,
  rc.top + nChange,
  rc.right-rc.left,
  rc.bottom-rc.top,
  SWP_NOSIZE | SWP_NOZORDER);

    /* move done button down */
    GetWindowRect (GetDlgItem (hDlg, IDC_DONE), &rc);
    ScreenToClient (hDlg, (LPPOINT)&rc);
    ScreenToClient (hDlg, (LPPOINT)&rc.right);
    SetWindowPos (GetDlgItem (hDlg, IDC_DONE),
  NULL,
  rc.left,
  rc.top + nChange,
  rc.right-rc.left,
  rc.bottom-rc.top,
  SWP_NOSIZE | SWP_NOZORDER);

    /* show suggestion edit control and label when appropriate */
    ShowWindow (GetDlgItem (hDlg, IDC_SUGGESTION), (bBigger ? SW_SHOW : SW_HIDE));
    ShowWindow (GetDlgItem (hDlg, IDC_SUGGESTLABEL), (bBigger ? SW_SHOW : SW_HIDE));
}

BOOL WINAPI BuildFileList (
    char          *lpFileList,
    LPBKFILELIST  *lpList)
{
    char        *lpFile;
    char        szFilePath[MAX_PATH];
    char        szFile[MAX_PATH];
    HFILE       hFile;
    OFSTRUCT    of;
    BOOL        bList = FALSE;

    /* create heap for up to 50 files at a time */
    if (!(hBkFileHeap = HeapCreate (HEAP_NO_SERIALIZE,
    sizeof (BKFILELIST),
    MAXBKTHREADS * sizeof (BKFILELIST))))
return FALSE;

    /* allocate first node in list */
    *lpList = (BKFILELIST *)HeapAlloc (hBkFileHeap, 0, sizeof (BKFILELIST));
    (*lpList)->hEvents[BKPORT_ABORT] = NULL;

    /* parse first file in list */
    lpFile = strtok (lpFileList, " ");

    /* loop through all files in list */
    while (lpFile)
{
strcpy (szFilePath, lpFile);

/* if no path, add current directory as path */
if (!GetFileFromPath (szFilePath, szFile))
    {
    strcpy (szFile, szFilePath);
    GetCurrentDirectory (MAX_PATH, szFilePath);
    strcat (szFilePath, "\\");
    strcat (szFilePath, szFile);
    }

/* verify file is available */
hFile = OpenFile (szFilePath, &of, OF_READWRITE);
if (hFile != -1)
    {
    /* added at least one file */
    bList = TRUE;

    /* close file */
    CloseHandle ((HANDLE)hFile);

    /* add file to list */
    AddFile (szFilePath, szFile, *lpList);
    }

/* get next file in list */
lpFile = strtok (NULL, " ");
}

    /* if no valid files, cleanup */
    if (!bList)
{
HeapDestroy (hBkFileHeap);
return FALSE;
}

    return TRUE;
}

BOOL WINAPI AddFile (
    char        *lpFilePath,
    char        *lpFile,
    BKFILELIST  *lpbkFiles)
{
    BKFILELIST    *lpNode;

    /* if first item in list don't need to allocate */
    if (!lpbkFiles->hEvents[BKPORT_ABORT])
lpNode = lpbkFiles;
    else
{
lpNode = (BKFILELIST *)HeapAlloc (hBkFileHeap, 0, sizeof (BKFILELIST));
if (!lpNode)
    return FALSE;

/* find end of list then add new node */
while (lpbkFiles->Next)
    lpbkFiles = lpbkFiles->Next;
lpbkFiles->Next = lpNode;
}

    /* initialize node structure */
    strcpy (lpNode->bkFile.szFile, lpFile);
    strcpy (lpNode->bkFile.szFilePath, lpFilePath);
    CreateEvents (lpNode->hEvents, &lpNode->bkFile);
    lpNode->Next = NULL;

    return TRUE;
}

BOOL WINAPI RemoveFile (
    char          *lpFilePath,
    LPBKFILELIST  *lpbkFiles)
{
    BKFILELIST    *pHead = *lpbkFiles;
    BKFILELIST    *pTail = *lpbkFiles;

    /* loop thru list until file name matches */
    while (pHead)
{
if (!strcmp (lpFilePath, pHead->bkFile.szFilePath))
    {
    /* special case remove first node */
    if (pTail == pHead)
{
*lpbkFiles = pHead->Next;
DestroyEvents (pHead->hEvents);
HeapFree (hBkFileHeap, 0, (char *)(pHead));

/* if no more nodes, destroy heap */
if (!*lpbkFiles)
    HeapDestroy (hBkFileHeap);
}

    else
{
pTail->Next = pHead->Next;
DestroyEvents (pHead->hEvents);
HeapFree (hBkFileHeap, 0, (char *)pHead);
}

    return TRUE;
    }

pTail = pHead;
pHead = pHead->Next;
}

    return FALSE;
}

BOOL WINAPI FreeFileList (
    BKFILELIST  *lpbkFiles)
{
    /* loop thru each list item */
    while (lpbkFiles)
{
/* destroy event handles */
DestroyEvents (lpbkFiles->hEvents);

lpbkFiles = lpbkFiles->Next;
}

    /* release entire heap */
    HeapDestroy (hBkFileHeap);

    return TRUE;
}

BOOL MySetEvent (HWND hWnd, HANDLE hEvent)
{
    if (SetEvent(hEvent)) {
return TRUE;
    } else {
return PostMessage (hWnd, (UINT)hEvent, 0, 0L);
    }
}

BOOL MyResetEvent (HWND hWnd, HANDLE hEvent)
{
    MSG msg;

    if (ResetEvent(hEvent)) {
return TRUE;
    } else {
PeekMessage (&msg, hWnd, (UINT)hEvent, (UINT)hEvent, TRUE);
return TRUE;
    }
}


PTFILE.C

#include "PortTool.h"
#include <string.h>

/* global file name variables */
char    lpszFilePath[MAX_PATH];
charlpszFilterSpec[128] = "";    // pickup eventually from string table

charlpszPortIniFilePath[MAX_PATH];
charlpszPortIniFilterSpec[128] = "";  // pickup eventually from string table

/* call the OpenFile common dialog to get a porttool Information filename */

/* load file filter spec strings from resource */
VOID LoadFilterSpecString(LPTSTR lpDest, UINT StartID, UINT EndID)
{
   CHAR    szBuf[MAX_PATH];
   UINT    wID;
   INT     nOffset;
   HMODULE hModule = GetModuleHandle(NULL);

    if(!*lpDest) {
        for(wID = StartID, nOffset = 0; wID <= EndID; wID++) {
            LoadString((HANDLE)hModule, wID, szBuf, sizeof(szBuf));
            lstrcpy(lpDest + nOffset, szBuf);
            nOffset += lstrlen(szBuf) + 1;
        }
    }
    return;
}

/* call the OpenFile common dialog to get a filename */
BOOL WINAPI GetFileName (
    HWND    hWnd,
    char    *lpszFileTitle,
    char    *lpszFilePath)
{
    OPENFILENAME    ofn;
    char        lpszFileOpen[25];
    char        lpszExt[10];

memset(&ofn, 0, sizeof(OPENFILENAME));
strcpy (lpszFileTitle, "");

    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_DEFAULTFILEEXT,
lpszExt,
sizeof (lpszExt));

    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_FILEOPENTITLE,
lpszFileOpen,
sizeof (lpszFileOpen));
    LoadFilterSpecString(lpszFilterSpec, IDS_FILE_FILTER_SPEC1, IDS_EXT_FILTER_SPEC3);

    /* fill in non-variant fields of OPENFILENAME struct. */
    ofn.lStructSize       = sizeof(OPENFILENAME);
ofn.hwndOwner  = hWnd;
ofn.lpstrFilter   = lpszFilterSpec;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 0;
ofn.lpstrFile  = lpszFilePath;
ofn.nMaxFile  = MAX_PATH;
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrFileTitle    = lpszFileTitle;
    ofn.nMaxFileTitle     = MAX_PATH;
ofn.lpstrTitle  = lpszFileOpen;
ofn.lpstrDefExt   = lpszExt;
ofn.Flags  = OFN_FILEMUSTEXIST;

    /* call common open dialog and return result */
return (GetOpenFileName ((LPOPENFILENAME)&ofn));
}

BOOL WINAPI GetPortIniFileName (
    HWND    hWnd,
    char    *lpszFileTitle,
    char    *lpszFilePath)
{
    OPENFILENAME    ofn;
charlpszFileOpen[64];
    char        lpszExt[10];

memset(&ofn, 0, sizeof(OPENFILENAME));
strcpy (lpszFileTitle, "");

/* load strings from resource string table */
    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_DEFAULTPORTEXT,
lpszExt,
sizeof (lpszExt));

    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_PORTFILEOPENTITLE,
lpszFileOpen,
sizeof (lpszFileOpen));

    LoadFilterSpecString(lpszPortIniFilterSpec, IDS_FILE_FILTER_SPEC4, IDS_EXT_FILTER_SPEC5);

    /* fill in non-variant fields of OPENFILENAME struct. */
    ofn.lStructSize       = sizeof(OPENFILENAME);
ofn.hwndOwner  = hWnd;
ofn.lpstrFilter   = lpszPortIniFilterSpec;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 0;
ofn.lpstrFile  = lpszFilePath;
ofn.nMaxFile  = MAX_PATH;
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrFileTitle    = lpszFileTitle;
    ofn.nMaxFileTitle     = MAX_PATH;
ofn.lpstrTitle  = lpszFileOpen;
ofn.lpstrDefExt   = lpszExt;
ofn.Flags  = 0;

    /* call common open dialog and return result */
return (GetOpenFileName ((LPOPENFILENAME)&ofn));
}

/* function retrieves the filename from the path */
BOOL WINAPI GetFileFromPath (
    char    *lpszFullPath,
    char    *lpszFile)
{
    char    *lpPtr = lpszFullPath + strlen (lpszFullPath);

    /* file is at end of path, so search backwards to first \ or : char */
    while (lpPtr > lpszFullPath)
{
if (*lpPtr == '\\' ||
    *lpPtr == ':')
    {
    lpPtr++;
    break;
    }
lpPtr = CharPrev(lpszFullPath, lpPtr);
}

    /* return filename if found, or full path passed in */
    strcpy (lpszFile, lpPtr);

    return (lpPtr > lpszFullPath);
}

/* open a file and load into edit control */
int WINAPI LoadFile (
    HWND    hWnd,
    char    *lpszName)
{
    LONG    lLength;
    HWND    hWndEdit = (HWND)GetWindowLong (hWnd, WL_HWNDEDIT);
    HANDLE  hEditData;
    char    *lpEditData;
HFILEhFile;

    /* open file for read & write */
if ((hFile = (HFILE)CreateFile (lpszName, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL))
   == (HFILE)INVALID_HANDLE_VALUE) {
/* return error */
return 0-IDS_OPENFAILED;
    }

    /* get file length */
    if (lLength = _llseek(hFile, 0L, 2))
       _llseek(hFile, 0L, 0);
    else
    {
    /* close file and return error */
    CloseHandle ((HANDLE)hFile);
return 0-IDS_NOSIZE;
    }

#if !defined (WIN32)

    /* get the edit control's memory handle */
    if (!(hEditData = (HANDLE)SendMessage (hWndEdit, EM_GETHANDLE, 0, 0L)))
    {
    /* close file and return error */
    CloseHandle ((HANDLE)hFile);
return 0-IDS_GETHANDLEFAILED;
    }

    /* realloc the memory to fit the new file size */
    if (((hEditData = LocalReAlloc(hEditData, lLength+1, LHND)) == NULL) ||

#else

    if (((hEditData = LocalAlloc (LHND, lLength+1)) == NULL) ||

#endif

    (!(lpEditData = (char *)LocalLock (hEditData))))
    {
    /* close file and return error */
    CloseHandle ((HANDLE)hFile);
return 0-IDS_REALLOCFAILED;
    }

    /* read the file into hEditData buffer */
    if (_lread(hFile, lpEditData, lLength) == -1)
    {
    /* close file and return error */
    CloseHandle ((HANDLE)hFile);
return 0-IDS_READFAILED;
    }

    /* null terminate edit buffer */
    lpEditData[lLength] = 0;
    LocalUnlock (hEditData);

    /* load buffer into edit control and close file */

#if !defined(WIN32)

    SendMessage (hWndEdit, EM_SETHANDLE, (UINT)hEditData, 0L);

#else

    lpEditData = LocalLock (hEditData);
    SendMessage (hWndEdit, WM_SETTEXT, 0, (LPARAM)lpEditData);
    GetLastError();
    LocalUnlock (hEditData);
    // LocalFree (hEditData); // Isn't there a synchronization issue with this?

#endif

    CloseHandle ((HANDLE)hFile);

    /* return success */
    return TRUE;
}

/* save file to disk */
int WINAPI SaveFile (
    HWND    hWnd,
    char    *lpszFile)
{
    HANDLE   hEditData;
    int      nLength;
    DWORD    dwWritten;
    HANDLE   hFile;
    HWND     hWndEdit = (HWND)GetWindowLong (hWnd, WL_HWNDEDIT);
    char     *lpEditData;

    /* open the file for writing */
    hFile = CreateFile (lpszFile,
    GENERIC_WRITE,
    0,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL);

    /* validate file handle */
    if (!hFile)
    return IDS_WRITEOPENFAILED;

    /* find out the length of the text in the edit control */
    nLength = GetWindowTextLength (hWndEdit);

#if !defined (WIN32)

    /* get handle to Edit text and lock pointer */
    hEditData  = (HANDLE)SendMessage (hWndEdit, EM_GETHANDLE, 0, 0);
    lpEditData = (char *)LocalLock (hEditData);

#else

    hEditData = LocalAlloc (LHND, nLength+1);
    lpEditData = (char *) LocalLock (hEditData);
    GetWindowText (hWndEdit, lpEditData, nLength+1);

#endif

    /* write edit data to file. */
    if (!WriteFile(hFile, lpEditData, nLength, &dwWritten, NULL))
    {
    /* unlock memory, restore edit handle, close file and return error */
    LocalUnlock (hEditData);
    CloseHandle (hFile);
    return IDS_WRITEFAILED;
    }

    /* clean up and go away */
    LocalUnlock (hEditData);
    CloseHandle (hFile);

    return TRUE;
}

/* invokes the saveas common dialog to retrieve a file name */
BOOL WINAPI SaveAsFileName (
    HWND    hWnd,
    char    *lpszFileTitle,
    char    *lpszFilePath)
{
    OPENFILENAME    ofn;
    char        lpszSaveAs[25];
    char        lpszExt[10];
    BOOL        nResult;

memset(&ofn, 0, sizeof(OPENFILENAME));
    *lpszFileTitle = 0;
    *lpszFilePath = 0;

    /* load strings from resource string table */
    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_DEFAULTFILEEXT,
lpszExt,
sizeof (lpszExt));
    LoadString ((HANDLE)GetModuleHandle (NULL),
IDS_SAVEASTITLE,
lpszSaveAs,
sizeof (lpszSaveAs));
    LoadFilterSpecString(lpszFilterSpec, IDS_FILE_FILTER_SPEC1, IDS_EXT_FILTER_SPEC3);

    /* fill in non-variant fields of OPENFILENAME struct. */
    ofn.lStructSize   = sizeof (OPENFILENAME);
    ofn.hwndOwner     = hWnd;
    ofn.lpstrFilter   = lpszFilterSpec;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 0;
    ofn.lpstrFile     = lpszFilePath;
    ofn.nMaxFile      = MAX_PATH;
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrFileTitle    = lpszFileTitle;
    ofn.nMaxFileTitle     = MAX_PATH;
    ofn.lpstrTitle    = lpszSaveAs;
    ofn.lpstrDefExt   = lpszExt;
    ofn.Flags         = 0;

    /* call common saveas dialog and return success */
    if(nResult = GetSaveFileName ((LPOPENFILENAME)&ofn)) {
        lstrcpy(lpszFileTitle,ofn.lpstrFileTitle); // save File Title
    }
    return nResult;
}


PTFIND.C

#include "PortTool.h"

/* global search string */
char    lpszSearch[MAXSEARCHSTRING+1] = "";
HWND    hDlgSearch;

FINDREPLACE    frSearch;

/* compare two substrings */
BOOL WINAPI RealSlowCompare (WORD, char *, char *);
BOOL WINAPI RealSlowCompare (
    WORD    wCase,
    char    *lpszSubject,
    char    *lpszTarget)
{
    if (wCase)
{
while (*lpszTarget)
    if (*lpszTarget++ != *lpszSubject++)
return FALSE;
}
    else
{
while (*lpszTarget) {
    if (IsDBCSLeadByte(*lpszSubject) ||
IsDBCSLeadByte(*lpszTarget)) {
if (*lpszTarget++ != *lpszSubject++ ||
    *lpszTarget++ != *lpszSubject++) {
    return FALSE;
}
    } else {
if ((CHAR)(DWORD)CharLower ((char *)(DWORD)(BYTE)*lpszTarget++)
 != (CHAR)(DWORD)CharLower ((char *)(DWORD)(BYTE)*lpszSubject++)) {
    return FALSE;
}
    }
}
}
    return TRUE;
}

/* invoke the common search/replace dialog */
BOOL WINAPI FindDialog (
    HWND    hWnd,
    WORD    wCase,
    WORD    wDir,
    char    *lpszInit)
{
    frSearch.lStructSize      = sizeof (FINDREPLACE);
    frSearch.hwndOwner        = hWnd;
    frSearch.hInstance        = (HANDLE)GetWindowLong (hWnd, GWL_HINSTANCE);
    frSearch.Flags            = FR_HIDEWHOLEWORD;

    /* if wCase, case sensitive */
    if (wCase)
frSearch.Flags        |= FR_MATCHCASE;
    /* if wDir, search forward */
    if (wDir)
frSearch.Flags        |= FR_DOWN;

    frSearch.lpstrFindWhat    = lpszInit;
    frSearch.lpstrReplaceWith = NULL;
    frSearch.wFindWhatLen     = MAXSEARCHSTRING+1;
    frSearch.wReplaceWithLen  = 0;
    frSearch.lCustData        = 0;
    frSearch.lpfnHook         = NULL;
    frSearch.lpTemplateName   = NULL;

    /* call common search dialog */
    if (hDlgSearch = FindText (&frSearch))
return TRUE;
    else
return FALSE;
}

/* perform the actual text searching in the edit control data */
BOOL WINAPI LocateText (
    HWND        hWnd,
    WORD        wCase,
    WORD        wDir,
    char        *lpszStr)
{
    UINT    uBegSel, uEndSel, uOrgBegSel, uOrgEndSel;
    HANDLE  hEditData;
    HWND    hWndEdit = (HANDLE)GetWindowLong (hWnd, WL_HWNDEDIT);
    char    *lpEditData;
    char    *lpEditHead;
    UINT    uLen;
    int     nStrLen = strlen (lpszStr);
    int     nChars;

    /* test for valid string */
    if (!*lpszStr)
return FALSE;

    /* locate beginning of selected text */
    SendMessage (hWndEdit, EM_GETSEL, (UINT)&uBegSel, (UINT)&uEndSel);
    uOrgBegSel = uBegSel;
    uOrgEndSel = uEndSel;

    /* get length of the text */
    uLen = (UINT)SendMessage (hWndEdit, WM_GETTEXTLENGTH, 0, 0);

    /* Get handle to edit text data and lock it */

#if !defined (WIN32)

    hEditData = (HANDLE)SendMessage (hWndEdit, EM_GETHANDLE, 0, 0);
    lpEditData = LocalLock (hEditData);
    lpEditHead = lpEditData;

#else

    hEditData = LocalAlloc (LHND, uLen);
    lpEditData = LocalLock (hEditData);
    lpEditHead = lpEditData;
    GetWindowText (hWndEdit, lpEditData, uLen);

#endif

    /* advance starting point past selection one char */
    if (wDir) {
        if (IsDBCSLeadByte(lpEditData[uBegSel])) {
            uBegSel += 2;
        } else {
            uBegSel += 1;
        }
    } else {
        if (IsDBCSLeadByte(*CharPrev(lpEditData, lpEditData + uBegSel))) {
            uBegSel -= 2;
        } else {
            uBegSel -= 1;
        }
    }
    lpEditData += uBegSel;

    /* count characters to search (either forward to end of file or back to beginning) */
    if (wDir)
nChars = (int)(uLen - uBegSel + 1 - nStrLen);
    else
nChars = (int)uBegSel;

    /* compare character by character for a substring match */
    //DBCS_FIX
    while ((wDir && nChars >= nStrLen) || (!wDir && nChars >= 0))
{
/* compare this substring for a match */
if (RealSlowCompare (wCase, lpEditData, lpszStr))
    {
    /* string found, cleanup and go away */
    LocalUnlock(hEditData);

    /* scroll parent edit control and select offending text */
    SendMessage (hWndEdit, EM_LINESCROLL, 0,
SendMessage (hWndEdit, EM_LINEFROMCHAR, uBegSel, 0) -
SendMessage (hWndEdit, EM_GETFIRSTVISIBLELINE, 0, 0));

    /* Select the located string */
    uEndSel = uBegSel + nStrLen;
    SendMessage(hWndEdit, EM_SETSEL, uBegSel, uEndSel);

    /* return success */
    return TRUE;
    }

if (wDir) {
    if (IsDBCSLeadByte(*lpEditData)) {
nChars -= 2;
lpEditData += 2;
uBegSel += 2;
    } else {
nChars--;
lpEditData++;
uBegSel++;
    }
} else {
    lpEditData = CharPrev(lpEditHead, lpEditData);
    if (IsDBCSLeadByte(*lpEditData)) {
nChars -= 2;
uBegSel -= 2;
    } else {
nChars--;
uBegSel--;
    }
}
}

    LocalUnlock (hEditData);
    SendMessage (hWndEdit, EM_SETSEL, uOrgBegSel, uOrgEndSel);

    /* return failed search  */
    return FALSE;
}


PTPRINT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright (C) 1993-96 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 "PortTool.h"

HWNDhCancelDlg = 0;
PRINTDLGpdPrint;

/* get default printer configuration and save in hWnd extra bytes for use later */
BOOL WINAPI GetPrinterConfig (
    HWND    hWnd)
{
    pdPrint.lStructSize = sizeof (PRINTDLG);
pdPrint.Flags= PD_RETURNDEFAULT;
pdPrint.hwndOwner= hWnd;
pdPrint.hDevMode= NULL;
pdPrint.hDevNames= NULL;
pdPrint.hDC = NULL;

PrintDlg (&pdPrint);

SetWindowLong (hWnd, WL_HPTRDEVNAMES, (LONG) pdPrint.hDevNames);

return TRUE;
}

/* abort proc called by gdi during print download process */
int WINAPI AbortProc (
    HDC     hdc,
    int     nErr)
{
    BOOL    fContinue = TRUE;
    MSG     msg;

    /* process messages for cancel dialog and other apps */
    while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == UM_CANCELPRINT)
    {
    fContinue = FALSE;
    break;
    }

else if (!hCancelDlg || !IsDialogMessage (hCancelDlg, &msg))
    {
    TranslateMessage (&msg);
    DispatchMessage  (&msg);
    }
}

    return fContinue;
}

BOOL WINAPI CancelDlgProc (
    HWND    hWnd,
    UINT    uMsg,
    UINT    uParam,
    LONG    lParam)
{

    switch (uMsg)
{
case WM_INITDIALOG:
    {
    char    *lpdn;
    char    lpszTitle[MAX_PATH];
        HWND    hWndOwner = GetWindow(hWnd, GW_OWNER);

    /* initialize dialog control information */
    lpdn = LocalLock (pdPrint.hDevNames);

    SetDlgItemText (hWnd,
    IDC_PRINTDEVICE,
    lpdn + sizeof (DEVNAMES) +
((DEVNAMES *)lpdn)->wDeviceOffset);
    SetDlgItemText (hWnd,
    IDC_PRINTPORT,
    lpdn + sizeof (DEVNAMES) +
((DEVNAMES *)lpdn)->wOutputOffset);
    LocalUnlock (pdPrint.hDevNames);
    GetWindowText (hWndOwner, lpszTitle, sizeof (lpszTitle));
    SetDlgItemText (hWnd, IDC_PRINTTITLE, lpszTitle);
    }
    break;

case WM_COMMAND:
    /* if cancel button selected, post message to cancel print job */
    if (LOWORD (uParam) == IDCANCEL)
{
PostMessage (GetParent (hWnd), UM_CANCELPRINT, 0, 0);
DestroyWindow (hWnd);
}
    break;

default:
    return FALSE;
    }
    return TRUE;
}

/* put up the print common dialog, and print */
int WINAPI PrintFile (
    HWND    hWnd)
{
    char*lpEditData;
    HANDLEhEditData;
    SIZEsLine;
    int yLineExt;
    int yExt;
    int yPageExt;
    UINTuLine;
    UINTuNumLines;
    UINTuOffset;
    UINTuLineLen;
    UINTuTitleLen;
    HWNDhWndEdit;
// PRINTDLGpdPrint;
    DOCINFOdiPrint;
    charlpszJobName[MAX_PATH];
    HFONThFont;
    HFONThFontPrt = NULL;
    intnPageCnt = 1;

    /* call common print dialog to get initialized printer DC */
    pdPrint.hwndOwner = hWnd;
    pdPrint.hDC = NULL;
    pdPrint.Flags = PD_RETURNDC ;

    /* call common print dialog */
    if (!PrintDlg (&pdPrint))
return IDS_PTRCOMMDLGFAILED;

    /* start cancel dialog box */
    hCancelDlg = CreateDialog ((HANDLE)GetModuleHandle (NULL),
       IDD_CANCELDLG,
       hWnd,
       CancelDlgProc);

    if (!hCancelDlg)
return IDS_CANCELDLGFAILED;

    ShowWindow (hCancelDlg, SW_SHOW);
    UpdateWindow (hCancelDlg);

    /* set AbortProc callback */
    if (SetAbortProc (pdPrint.hDC, AbortProc) < 0)
{
/* on error, clean up and go away */
DestroyWindow (hCancelDlg);
DeleteDC (pdPrint.hDC);
return IDS_SETABORTPROCFAILED;
}

    hWndEdit = (HWND)GetWindowLong (hWnd, WL_HWNDEDIT);
    if (NULL == (hFont = (HFONT)SendMessage(hWndEdit, WM_GETFONT, 0, 0L))) {
        hFont = GetStockObject(SYSTEM_FONT);
    } else {
        HDC     hdcScreen;
        LOGFONT lf;

        if (NULL != (hdcScreen = GetDC(hWnd))) {
            if(GetObject(hFont, sizeof(LOGFONT), &lf)) {
                lf.lfWidth = 0;
                lf.lfHeight = lf.lfHeight
                            * GetDeviceCaps(pdPrint.hDC, LOGPIXELSX)
                            / GetDeviceCaps(hdcScreen, LOGPIXELSX);
                if (NULL != (hFontPrt = CreateFontIndirect(&lf))) {
                    hFont = hFontPrt;
                }
            }
            ReleaseDC(hWnd, hdcScreen);
        }
    }
    hFont = SelectObject(pdPrint.hDC, hFont);

    /* initialize printer for job */
    GetWindowText (hWnd, lpszJobName, sizeof (lpszJobName));
    diPrint.cbSize = sizeof (DOCINFO);
    diPrint.lpszDocName = lpszJobName;
    diPrint.lpszOutput = NULL;
    if (StartDoc (pdPrint.hDC, &diPrint) == SP_ERROR ||  StartPage (pdPrint.hDC) < 0)
{
/* on error, clean up and go away */
DestroyWindow (hCancelDlg);
SelectObject(pdPrint.hDC, hFont);
        if (hFontPrt) {
    DeleteObject(hFontPrt);
        }
DeleteDC (pdPrint.hDC);
return IDS_STARTDOCFAILED;
}

    /* job started, so display cancel dialog */
    ShowWindow (hCancelDlg, SW_SHOW);
    UpdateWindow (hCancelDlg);

    /* retrieve dimensions for printing and init loop variables */
    /* 'hWndEdit' has already been got. */
    hWndEdit = (HWND)GetWindowLong(hWnd, WL_HWNDEDIT);
    hEditData = (HANDLE)SendMessage (hWndEdit, EM_GETHANDLE, 0, 0L);
    uNumLines = (WORD)SendMessage (hWndEdit, EM_GETLINECOUNT, 0, 0L);
    GetTextExtentPoint (pdPrint.hDC, "CC", 2, &sLine);
    yLineExt = sLine.cy;
    yPageExt = GetDeviceCaps (pdPrint.hDC, VERTRES);
    yExt = 0;
    uLine = 0;
    if (pdPrint.Flags & PD_PAGENUMS) {
uLine = (pdPrint.nFromPage - 1) * (yPageExt / yLineExt);
    }

    /* print text line by line from top to bottom */
    while (uLine < uNumLines)
{
if ((pdPrint.Flags & PD_PAGENUMS) &&
    (uLine >= (UINT)(pdPrint.nToPage * (yPageExt / yLineExt)))) {
    break;
}
/* if at end of page, start a new page */
if ((yExt + yLineExt) > yPageExt)
    {
    if (!EndPage (pdPrint.hDC) || StartPage (pdPrint.hDC) < 0)
{
DestroyWindow (hCancelDlg);
SelectObject(pdPrint.hDC, hFont);
                if (hFontPrt) {
                    DeleteObject(hFontPrt);
                }
DeleteDC (pdPrint.hDC);
return IDS_PRINTABORTED;
}
    yExt = 0;
    }

/* determine buffer offset for current line and line length */
uOffset = SendMessage (hWndEdit, EM_LINEINDEX, uLine, 0);
uLineLen = SendMessage (hWndEdit, EM_LINELENGTH, uOffset, 0);

#if defined (WIN32)
    uTitleLen=GetWindowTextLength(hWndEdit);
    hEditData=LocalAlloc(LHND,uTitleLen+1);
    lpEditData=LocalLock(hEditData);
    GetWindowText(hWndEdit, lpEditData, uTitleLen+1);
#else
    hEditData = (HANDLE)SendMessage (hWndEdit, EM_GETHANDLE, 0, 0L);
lpEditData = LocalLock (hEditData);
#endif

/* print current the line and unlock the text handle */
    TextOut (pdPrint.hDC, 0, yExt, (char *)lpEditData+uOffset, uLineLen);
LocalUnlock (hEditData);

/* increment page position */
yExt += yLineExt;
uLine++;
}

    /* end the last page and document */
    EndPage (pdPrint.hDC);
    EndDoc (pdPrint.hDC);

    SelectObject(pdPrint.hDC, hFont);
    if (hFontPrt) {
        DeleteObject(hFontPrt);
    }
    /* end cancel dialog box, clean up and exit */
    DestroyWindow (hCancelDlg);
    DeleteDC(pdPrint.hDC);
    return TRUE;
}

/* printer setup common dialog */
int WINAPI PrinterSetup (
    HWND    hWnd)
{
// PRINTDLGpdPrint;

pdPrint.Flags= PD_PRINTSETUP;
pdPrint.hwndOwner= hWnd;

    /* call common print dialog */
if (!PrintDlg (&pdPrint) && CommDlgExtendedError ())
return IDS_PTRCOMMDLGFAILED;
else
return 0;
}


README.TXT

Screen Saver

The SCRNSAVE library contains control panel utility library routines for
managing cpArrow spinner controls used in applet dialog boxes.


CPARROW.C

/** FILE: arrow.c ********** Module Header ********************************
 *
 * Control panel utility library routines for managing "cpArrow" window
 * class/spinner controls used in applet dialogs.
 *
 * History:
 *  15:30 on Thur  25 Apr 1991  -by-  Steve Cathcart   [stevecat]
 *        Took base code from Win 3.1 source
 *  10:30 on Tues  04 Feb 1992  -by-  Steve Cathcart   [stevecat]
 *        Updated code to latest Win 3.1 sources
 *  12:00 on Fri   07 Aug 1992  -by-  Steve Cathcart   [stevecat]
 *        Implemented new drawing scheme for spinner/arrow control
 *
 *  Copyright 1990 - 1998 Microsoft Corporation
 *
 *************************************************************************/
//==========================================================================
//                        Include files
//==========================================================================
// C Runtime
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include <windows.h>

// Application specific
#include "cparrow.h"

//==========================================================================
//                        Local Definitions
//==========================================================================

//  Offsets to use with GetWindowLong
#define GWL_SPINNERSTATE    0

//  Control state flags.
#define SPINNERSTATE_GRAYED      0x0001
#define SPINNERSTATE_HIDDEN      0x0002
#define SPINNERSTATE_MOUSEOUT    0x0004
#define SPINNERSTATE_UPCLICK     0x0008
#define SPINNERSTATE_DOWNCLICK   0x0010

//  Combination of click states.
#define SPINNERSTATE_CLICKED   (SPINNERSTATE_UPCLICK | SPINNERSTATE_DOWNCLICK)

//  Combination of state flags.
#define SPINNERSTATE_ALL         0x001F

//  Sinner Control color indices
#define SPINNERCOLOR_FACE        0
#define SPINNERCOLOR_ARROW       1
#define SPINNERCOLOR_SHADOW      2
#define SPINNERCOLOR_HIGHLIGHT   3
#define SPINNERCOLOR_FRAME       4

#define CCOLORS                  5

//==========================================================================
//                        External Declarations
//==========================================================================

//==========================================================================
//                        Local Data Declarations
//==========================================================================

/*
 * Macros to change the control state given the state flag(s)
 */
#define StateSet(dwState, wFlags)    (dwState |=  (wFlags))
#define StateClear(dwState, wFlags)  (dwState &= ~(wFlags))
#define StateTest(dwState, wFlags)   (dwState &   (wFlags))

//Array of default colors, matching the order of SPINNERCOLOR_* values.
DWORD rgColorDef[CCOLORS]={
                         COLOR_BTNFACE,             //  SPINNERCOLOR_FACE
                         COLOR_BTNTEXT,             //  SPINNERCOLOR_ARROW
                         COLOR_BTNSHADOW,           //  SPINNERCOLOR_SHADOW
                         COLOR_BTNHIGHLIGHT,        //  SPINNERCOLOR_HIGHLIGHT
                         COLOR_WINDOWFRAME          //  SPINNERCOLOR_FRAME
                         };

BOOL   bArrowTimed = FALSE;
BOOL   bRight;
HANDLE hParent;

//==========================================================================
//                        Local Function Prototypes
//==========================================================================
void Draw3DButtonRect (HDC hDC, HPEN hPenHigh, HPEN hPenShadow, int x1,
                       int y1, int x2, int y2, BOOL fClicked);
LONG SpinnerPaint (HWND hWnd, DWORD dwSpinnerState);

//==========================================================================
//                            Functions
//==========================================================================

BOOL OddArrowWindow(HWND hArrowWnd)
{

#ifdef  OLD_CODE
    HWND hParent;
    RECT rResize;
    BOOL bResize;

    GetWindowRect(hArrowWnd, (LPRECT) &rResize);
    if (!(bResize = (rResize.right - rResize.left) % 2))
    {
        rResize.right++;
        ScreenToClient(hParent = GetParent(hArrowWnd), (LPPOINT) & rResize.left);
        ScreenToClient(hParent, (LPPOINT) & rResize.right);
        MoveWindow(hArrowWnd, rResize.left, rResize.top,
                             (rResize.right - rResize.left),
                             (rResize.bottom - rResize.top), FALSE);
    }
    return(bResize);
#endif  //  OLD_CODE

    return(TRUE);
}

VOID ArrowTimerProc(HWND hWnd, UINT wMsg, UINT nID, DWORD dwTime)
{
    WORD  wScroll;
    DWORD dwSpinnerState;

    dwSpinnerState = (DWORD) GetWindowLong (hWnd, GWL_SPINNERSTATE);

    if (StateTest(dwSpinnerState, SPINNERSTATE_CLICKED))
    {
        wScroll = (StateTest(dwSpinnerState, SPINNERSTATE_DOWNCLICK)) ?
                                                    SB_LINEDOWN : SB_LINEUP;
        if (bRight == WM_RBUTTONDOWN)
            wScroll += SB_PAGEUP - SB_LINEUP;

            SendMessage(hParent, WM_VSCROLL,
                        MAKELONG(wScroll, GetWindowLong(hWnd, GWL_ID)),
                        (LONG) hWnd);
    }

    //  Don't need to call KillTimer(), because SetTimer will
    //  reset the right one

    SetTimer(hWnd, nID, 50, (TIMERPROC) ArrowTimerProc);

    return ;

    wMsg = wMsg;
    dwTime = dwTime;
}

/*
 * ClickedRectCalc
 *
 * Description:
 *  Calculates the rectangle of the clicked region based on the
 *  state flags SPINNERSTATE_UPCLICK and SPINNERSTATE_DOWNCLICK.
 *
 * Parameter:
 *  hWnd            HWND handle to the control window.
 *  lpRect          LPRECT rectangle structure to fill.
 *
 * Return Value:
 *  void
 *
 */

void ClickedRectCalc(HWND hWnd, DWORD dwState, LPRECT lpRect)
{
    int  cx, cy;

    GetClientRect (hWnd, lpRect);

    cx = lpRect->right  >> 1;
    cy = lpRect->bottom >> 1;

    if (StateTest(dwState, SPINNERSTATE_DOWNCLICK))
        lpRect->top = cy;
    else
        lpRect->bottom = 1+cy;

    return;
}

/*
 * ArrowControlProc
 *
 * Description:
 *
 *  Window Procedure for the Spinner/Arrow custom control.  Handles all
 *  messages like WM_PAINT just as a normal application window would.
 *  State information about the control is maintained ALL drawing is
 *  handled during WM_PAINT message processing.
 *
 */
LRESULT APIENTRY ArrowControlProc(HWND hArrow, UINT message, WPARAM wParam, LONG lParam)
{
    WORD    wScroll;
    POINT   pt;
    RECT    rect;
    int     x, y;
    int     cy;
    DWORD   dwSpinnerState, dwState;

    dwSpinnerState = (DWORD) GetWindowLong (hArrow, GWL_SPINNERSTATE);

    switch (message)
    {
    case WM_CREATE:
        dwSpinnerState = 0;
        SetWindowLong (hArrow, GWL_SPINNERSTATE, (LONG) dwSpinnerState);
        break;

    case WM_ENABLE:
        //  Handles disabling/enabling case.  Example of a
        //  change-state-and-repaint strategy since we let the
        //  painting code take care of the visuals.

        if (wParam)
            StateClear(dwSpinnerState, SPINNERSTATE_GRAYED);
        else
            StateSet(dwSpinnerState, SPINNERSTATE_GRAYED);

        SetWindowLong (hArrow, GWL_SPINNERSTATE, (LONG) dwSpinnerState);

        //  Force a repaint since the control will look different.

        InvalidateRect (hArrow, NULL, TRUE);
        UpdateWindow (hArrow);
        break;

    case WM_SHOWWINDOW:
        //  Set or clear the hidden flag. Windows will
        //  automatically force a repaint if we become visible.

        if (wParam)
            StateClear(dwSpinnerState, SPINNERSTATE_HIDDEN);
        else
            StateSet(dwSpinnerState, SPINNERSTATE_HIDDEN);

        SetWindowLong (hArrow, GWL_SPINNERSTATE, (LONG) dwSpinnerState);
        break;

    case WM_CANCELMODE:
        //  IMPORTANT MESSAGE!  WM_CANCELMODE means that a
        //  dialog or some other modal process has started.
        //  we must make sure that we cancel any clicked state
        //  we are in, kill the timers, and release the capture.

        StateClear(dwSpinnerState, SPINNERSTATE_CLICKED);
        if (bArrowTimed)
        {
            SendMessage (hParent, WM_VSCROLL, MAKELONG(SB_ENDSCROLL,
                           GetWindowLong (hArrow, GWL_ID)), (LONG) hArrow);
            KillTimer (hArrow, GetWindowLong (hArrow, GWL_ID));
            bArrowTimed = FALSE;
        }
        ReleaseCapture();
        break;

    case WM_RBUTTONDOWN:
    case WM_LBUTTONDOWN:
        //  When we get a mouse down message, we know that the mouse
        //  is over the control.  We then do the following steps
        //  to set up the new state:
        //   1.  Hit-test the coordinates of the click to
        //       determine in which half the click occurred.
        //   2.  Set the appropriate SPINNERSTATE_*CLICK state
        //       and repaint that clicked half.  This is another
        //       example of a change-state-and-repaint strategy.
        //   3.  Send an initial scroll message.
        //   4.  Set the mouse capture.
        //   5.  Set the initial delay timer before repeating
        //       the scroll message.

        if (bRight)
            break;

        bRight = message;

        hParent = GetParent (hArrow);

        //  Get the mouse coordinates.
        x = (int) LOWORD(lParam);
        y = (int) HIWORD(lParam);

        //  Only need to hit-test the upper half
        //  Then change-state-and-repaint

        GetClientRect (hArrow, &rect);
        cy = rect.bottom >> 1;

        if (y > cy)
        {
            StateSet(dwSpinnerState, SPINNERSTATE_DOWNCLICK);
            rect.top = cy;
            wScroll = SB_LINEDOWN;
        }
        else
        {
            StateSet(dwSpinnerState, SPINNERSTATE_UPCLICK);
            rect.bottom = cy + 1;
            wScroll = SB_LINEUP;
        }

        SetWindowLong (hArrow, GWL_SPINNERSTATE, (LONG) dwSpinnerState);

        InvalidateRect (hArrow, &rect, TRUE);
        UpdateWindow (hArrow);

        SetCapture (hArrow);

        //  Process SHIFT key state along with button message

        if (wParam & MK_SHIFT)
        {
            if (message != WM_RBUTTONDOWN)
                wScroll += (WORD) (SB_TOP - SB_LINEUP);
            else
                wScroll += (WORD) (SB_THUMBPOSITION - SB_LINEUP);
        }
        else
        {
            if (message == WM_RBUTTONDOWN)
                wScroll += SB_PAGEUP - SB_LINEUP;

            bArrowTimed = SetTimer (hArrow, GetWindowLong (hArrow, GWL_ID),
                                             200, (TIMERPROC) ArrowTimerProc);
        }
        SendMessage (hParent, WM_VSCROLL, MAKELONG(wScroll,
                              GetWindowLong (hArrow, GWL_ID)), (LONG) hArrow);
        break;

    case WM_MOUSEMOVE:
        //  On WM_MOUSEMOVE messages we want to know if the mouse
        //  has moved out of the control when the control is in
        //  a clicked state.  If the control has not been clicked,
        //  then we have nothing to do.  Otherwise we want to set
        //  the SPINNERSTATE_MOUSEOUT flag and repaint so the button
        //  visually comes up.

        if (!StateTest(dwSpinnerState, SPINNERSTATE_CLICKED))
            break;

        //  Save copy of original state
        dwState = dwSpinnerState;

        //  Get the mouse coordinates.
        pt.x = (int) LOWORD(lParam);
        pt.y = (int) HIWORD(lParam);

        //  Get the area we originally clicked and the new POINT
        ClickedRectCalc (hArrow, dwSpinnerState, &rect);

        //  Hit-Test the rectange and change the state if necessary.
        if (PtInRect(&rect, pt))
            StateClear(dwSpinnerState, SPINNERSTATE_MOUSEOUT);
        else
            StateSet(dwSpinnerState, SPINNERSTATE_MOUSEOUT);

        SetWindowLong (hArrow, GWL_SPINNERSTATE, (LONG) dwSpinnerState);

        //  If the state changed, repaint the appropriate part of
        //  the control.
        if (dwState != dwSpinnerState)
        {
            InvalidateRect (hArrow, &rect, TRUE);
            UpdateWindow (hArrow);
        }

        break;

    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
        //  A mouse button up event is much like WM_CANCELMODE since
        //  we have to clean out whatever state the control is in:
        //   1.  Kill any repeat timers we might have created.
        //   2.  Release the mouse capture.
        //   3.  Clear the clicked states and repaint, another example
        //       of a change-state-and-repaint strategy.

        if ((UINT) (bRight - WM_LBUTTONDOWN + WM_LBUTTONUP) == message)
        {
            bRight = 0;
            ReleaseCapture();

            if (bArrowTimed)
            {
                SendMessage (hParent, WM_VSCROLL, MAKELONG(SB_ENDSCROLL,
                               GetWindowLong (hArrow, GWL_ID)), (LONG) hArrow);
                KillTimer (hArrow, GetWindowLong (hArrow, GWL_ID));
                bArrowTimed = FALSE;
            }

            //  Repaint if necessary, only if we are clicked AND the mouse
            //  is still in the boundaries of the control.

            if (StateTest(dwSpinnerState, SPINNERSTATE_CLICKED) &&
                StateTest(dwSpinnerState, ~SPINNERSTATE_MOUSEOUT))
            {
                //  Calculate the rectangle before clearing states.
                ClickedRectCalc (hArrow, dwSpinnerState, &rect);

                //  Clear the states so we repaint properly.
                StateClear(dwSpinnerState, SPINNERSTATE_CLICKED | SPINNERSTATE_MOUSEOUT);

                SetWindowLong (hArrow, GWL_SPINNERSTATE, (LONG) dwSpinnerState);
                InvalidateRect (hArrow, &rect, TRUE);
                UpdateWindow (hArrow);
            }
        }
        break;

    case WM_PAINT:
        return SpinnerPaint (hArrow, dwSpinnerState);

    default:
        return (DefWindowProc (hArrow, message, wParam, lParam));
        break;
    }
    return(0L);
}

/*
 * SpinnerPaint
 *
 * Description:
 *
 *  Handles all WM_PAINT messages for the control and paints
 *  the control for the current state, whether it be clicked
 *  or disabled.
 *
 * Parameters:
 *  hWnd            HWND Handle to the control.
 *  dwSpinnerState  DWORD Spinner control status flags
 *
 * Return Value:
 *  LONG            0L.
 */

LONG SpinnerPaint (HWND hWnd, DWORD dwSpinnerState)
{
    PAINTSTRUCT ps;
    LPRECT      lpRect;
    RECT        rect;
    HDC         hDC;
    COLORREF    rgCr[CCOLORS];
    HPEN        rgHPen[CCOLORS];
    int         iColor;

    HBRUSH      hBrushArrow;
    HBRUSH      hBrushFace;
    HBRUSH      hBrushBlack;

    POINT       rgpt1[3];
    POINT       rgpt2[3];

    int         xAdd1=0, yAdd1=0;
    int         xAdd2=0, yAdd2=0;

    int         cx,  cy;        //  Whole dimensions
    int         cx2, cy2;       //  Half dimensions
    int         cx4, cy4;       //  Quarter dimensions

    lpRect = &rect;

    hDC = BeginPaint (hWnd, &ps);
    GetClientRect (hWnd, lpRect);

    //  Get colors that we'll need.  We do not want to cache these
    //  items since we may our top-level parent window may have
    //  received a WM_WININICHANGE message at which time the control
    //  is repainted.  Since this control never sees that message,
    //  we cannot assume that colors will remain the same throughout
    //  the life of the control.

    for (iColor = 0; iColor < CCOLORS; iColor++)
    {
        rgCr[iColor] = GetSysColor (rgColorDef[iColor]);

        rgHPen[iColor] = CreatePen (PS_SOLID, 1, rgCr[iColor]);
    }

    hBrushFace  = CreateSolidBrush (rgCr[SPINNERCOLOR_FACE]);
    hBrushArrow = CreateSolidBrush (rgCr[SPINNERCOLOR_ARROW]);
    hBrushBlack = GetStockObject (BLACK_BRUSH);

    //  These values are extremely cheap to calculate for the amount
    //  we are going to use them.

    cx  = lpRect->right  - lpRect->left;
    cy  = lpRect->bottom - lpRect->top;
    cx2 = cx  >> 1;
    cy2 = cy  >> 1;
    cx4 = cx2 >> 1;
    cy4 = cy2 >> 1;

    //  If one half is depressed, set the x/yAdd varaibles that we use
    //  to shift the small arrow image down and right.

    if (!StateTest(dwSpinnerState, SPINNERSTATE_MOUSEOUT))
    {
        if (StateTest(dwSpinnerState, SPINNERSTATE_UPCLICK))
        {
            xAdd1 = 1;
            yAdd1 = 1;
        }
        else if (StateTest(dwSpinnerState, SPINNERSTATE_DOWNCLICK))
        {
            xAdd2 = 1;
            yAdd2 = 1;
        }
    }

    //  Draw the face color and the outer frame
    SelectObject (hDC, hBrushFace);
    SelectObject (hDC, rgHPen[SPINNERCOLOR_FRAME]);

    Rectangle (hDC, lpRect->left, lpRect->top, lpRect->right, lpRect->bottom);

    //  Draw the horizontal center line.
    MoveToEx (hDC, 0, cy2, NULL);
    LineTo (hDC, cx, cy2);

    //  We do one of three modifications for drawing the borders:
    //   1) Both halves un-clicked.
    //   2) Top clicked,   bottom unclicked.
    //   3) Top unclicked, bottom clicked.
    //
    //  Case 1 is xAdd1==xAdd2==0
    //  Case 2 is xAdd1==1, xAdd2=0
    //  Case 3 is xAdd1==0, xAdd2==1

    //  Draw top and bottom buttons borders.
    Draw3DButtonRect (hDC, rgHPen[SPINNERCOLOR_HIGHLIGHT],
                      rgHPen[SPINNERCOLOR_SHADOW],
                      0,  0,  cx-1, cy2,  (BOOL) xAdd1);

    Draw3DButtonRect (hDC, rgHPen[SPINNERCOLOR_HIGHLIGHT],
                      rgHPen[SPINNERCOLOR_SHADOW],
                      0, cy2, cx-1, cy-1, (BOOL) xAdd2);

    //  Select default line color.
    SelectObject (hDC, rgHPen[SPINNERCOLOR_ARROW]);

    //  Draw the arrows depending on the enable state.
    if (StateTest (dwSpinnerState, SPINNERSTATE_GRAYED))
    {
        //  Draw arrow color lines in the upper left of the
        //  top arrow and on the top of the bottom arrow.
        //  Pen was already selected as a default.

        MoveToEx (hDC, cx2,   cy4-2, NULL);      //Top arrow
        LineTo   (hDC, cx2-3, cy4+1);
        MoveToEx (hDC, cx2-3, cy2+cy4-2, NULL);  //Bottom arrow
        LineTo   (hDC, cx2+3, cy2+cy4-2);

        //  Draw highlight color lines in the bottom of the
        //  top arrow and on the lower right of the bottom arrow.

        SelectObject (hDC, rgHPen[SPINNERCOLOR_HIGHLIGHT]);
        MoveToEx (hDC, cx2-3, cy4+1, NULL);      //Top arrow
        LineTo   (hDC, cx2+3, cy4+1);
        MoveToEx (hDC, cx2+3, cy2+cy4-2, NULL);  //Bottom arrow
        LineTo   (hDC, cx2,   cy2+cy4+1);
        SetPixel (hDC, cx2,   cy2+cy4+1, rgCr[SPINNERCOLOR_HIGHLIGHT]);
    }
    else
    {
        //  Top arrow polygon
        rgpt1[0].x = xAdd1 + cx2;
        rgpt1[0].y = yAdd1 + cy4 - 2;
        rgpt1[1].x = xAdd1 + cx2 - 3;
        rgpt1[1].y = yAdd1 + cy4 + 1;
        rgpt1[2].x = xAdd1 + cx2 + 3;
        rgpt1[2].y = yAdd1 + cy4 + 1;

        //  Bottom arrow polygon
        rgpt2[0].x = xAdd2 + cx2;
        rgpt2[0].y = yAdd2 + cy2 + cy4 + 1;
        rgpt2[1].x = xAdd2 + cx2 - 3;
        rgpt2[1].y = yAdd2 + cy2 + cy4 - 2;
        rgpt2[2].x = xAdd2 + cx2 + 3;
        rgpt2[2].y = yAdd2 + cy2 + cy4 - 2;

        //  Draw the arrows
        SelectObject (hDC, hBrushArrow);
        Polygon (hDC, (LPPOINT)rgpt1, 3);
        Polygon (hDC, (LPPOINT)rgpt2, 3);
    }

    //  Clean up
    EndPaint(hWnd, &ps);

    DeleteObject (hBrushFace);
    DeleteObject (hBrushArrow);

    for (iColor = 0; iColor < CCOLORS; iColor++)
    {
        if (rgHPen[iColor])
            DeleteObject (rgHPen[iColor]);
    }

    return 0L;
}

/*
 * Draw3DButtonRect
 *
 * Description:
 *  Draws the 3D button look within a given rectangle.  This rectangle
 *  is assumed to be bounded by a one pixel black border, so everything
 *  is bumped in by one.
 *
 * Parameters:
 *  hDC         DC to draw to.
 *  hPenHigh    HPEN highlight color pen.
 *  hPenShadow  HPEN shadow color pen.
 *  x1          int Upper left corner x.
 *  y1          int Upper left corner y.
 *  x2          int Lower right corner x.
 *  y2          int Lower right corner y.
 *  fClicked    BOOL specifies if the button is down or not (TRUE==DOWN)
 *
 * Return Value:
 *  void
 *
 */

void Draw3DButtonRect (HDC hDC, HPEN hPenHigh, HPEN hPenShadow, int x1,
                       int y1, int x2, int y2, BOOL fClicked)
{
    HPEN  hPenOrg;

    //  Shrink the rectangle to account for borders.
    x1+=1;
    x2-=1;
    y1+=1;
    y2-=1;

    hPenOrg = SelectObject (hDC, hPenShadow);

    if (fClicked)
    {
        //  Shadow on left and top edge when clicked.
        MoveToEx (hDC, x1, y2, NULL);
        LineTo (hDC, x1, y1);
        LineTo (hDC, x2+1, y1);
    }
    else
    {
        //  Lowest shadow line.
        MoveToEx (hDC, x1, y2, NULL);
        LineTo (hDC, x2, y2);
        LineTo (hDC, x2, y1-1);

        //  Upper shadow line.
        MoveToEx (hDC, x1+1, y2-1, NULL);
        LineTo (hDC, x2-1, y2-1);
        LineTo (hDC, x2-1, y1);

        SelectObject (hDC, hPenHigh);

        //  Upper highlight line.
        MoveToEx (hDC, x1, y2-1, NULL);
        LineTo (hDC, x1, y1);
        LineTo (hDC, x2, y1);
    }

    if (hPenOrg)
        SelectObject (hDC, hPenOrg);

    return;
}

BOOL RegisterArrowClass (HANDLE hModule)
{
    WNDCLASS wcArrow;

    wcArrow.lpszClassName = TEXT("cpArrow");
    wcArrow.hInstance     = hModule;
    wcArrow.lpfnWndProc   = ArrowControlProc;
    wcArrow.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wcArrow.hIcon         = NULL;
    wcArrow.lpszMenuName  = NULL;
    wcArrow.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wcArrow.style         = CS_HREDRAW | CS_VREDRAW;
    wcArrow.cbClsExtra    = 0;
    wcArrow.cbWndExtra    = sizeof(DWORD);

    return(RegisterClass((LPWNDCLASS) &wcArrow));
}

/*
short ArrowVScrollProc(wScroll, nCurrent, lpAVS)

wScroll is an SB_* message
nCurrent is the base value to change
lpAVS is a far pointer to the structure containing change amounts
      and limits to be used, along with a flags location for errors

returns a short value of the final amount
        the flags element in the lpAVS struct is
                0 if no problems found
         OVERFLOW set if the change exceeded upper limit (limit is returned)
        UNDERFLOW set if the change exceeded lower limit (limit is returned)
   UNKNOWNCOMMAND set if wScroll is not a known SB_* message

NOTE: Only one of OVERFLOW or UNDERFLOW may be set.  If you send in values
      that would allow both to be set, that's your problem.  Either can
      be set in combination with UNKNOWNCOMMAND (when the command is not
      known and the input value is out of bounds).
*/

short ArrowVScrollProc(short wScroll, short nCurrent, LPARROWVSCROLL lpAVS)
{
    short    nDelta;

/* Find the message and put the relative change in nDelta.  If the
   message is an absolute change, put 0 in nDelta and set nCurrent
   to the value specified.  If the command is unknown, set error
   flag, set nDelta to 0, and proceed through checks.
*/

    switch (wScroll)
    {
    case SB_LINEUP:
        nDelta = lpAVS->lineup;
        break;
    case SB_LINEDOWN:
        nDelta = lpAVS->linedown;
        break;
    case SB_PAGEUP:
        nDelta = lpAVS->pageup;
        break;
    case SB_PAGEDOWN:
        nDelta = lpAVS->pagedown;
        break;
    case SB_TOP:
        nCurrent = lpAVS->top;
        nDelta = 0;
        break;
    case SB_BOTTOM:
        nCurrent = lpAVS->bottom;
        nDelta = 0;
        break;
    case SB_THUMBTRACK:
        nCurrent = lpAVS->thumbtrack;
        nDelta = 0;
        break;
    case SB_THUMBPOSITION:
        nCurrent = lpAVS->thumbpos;
        nDelta = 0;
        break;
    case SB_ENDSCROLL:
        nDelta = 0;
        break;
    default:
        lpAVS->flags = UNKNOWNCOMMAND;
        nDelta = 0;
        break;
    }
    if (nCurrent + nDelta > lpAVS->top)
    {
        nCurrent = lpAVS->top;
        nDelta = 0;
        lpAVS->flags = OVERFLOW;
    }
    else if (nCurrent + nDelta < lpAVS->bottom)
    {
        nCurrent = lpAVS->bottom;
        nDelta = 0;
        lpAVS->flags = UNDERFLOW;
    }
    else
        lpAVS->flags = 0;
    return(nCurrent + nDelta);
}


CPARROW.H

typedef struct
  {
    short lineup;             /* lineup/down, pageup/down are relative */
    short linedown;           /* changes.  top/bottom and the thumb    */
    short pageup;             /* elements are absolute locations, with */
    short pagedown;           /* top & bottom used as limits.          */
    short top;
    short bottom;
    short thumbpos;
    short thumbtrack;
    BYTE  flags;              /* flags set on return                   */
  } ARROWVSCROLL;
typedef ARROWVSCROLL NEAR     *NPARROWVSCROLL;
typedef ARROWVSCROLL FAR      *LPARROWVSCROLL;

#define UNKNOWNCOMMAND 1
#define OVERFLOW       2
#define UNDERFLOW      4

BOOL RegisterArrowClass (HANDLE hModule);


CPHELP.H

//
//  NOTE:  The NetUI group is responsible for a number of applets
//         that are shipped with the base system.  This includes the
//         Network Control Panel Applet (NCPA.CPL), the Server Manager
//         and Services Applets (SRVMGR.CPL), and the UPS Applet (UPS.CPL).
//
//         To prevent help context conflicts between the NetUI applets
//         and the "standard" system applets, the NetUI group hereby
//         reserves the help contexts in the range 40000 - 59999.  This
//         will provide plenty of breathing room for future NetUI applets.
//

#define IDH_NETUI_FIRST 40000
#define IDH_NETUI_LAST  59999

#define IDH_HELPFIRST        5000
#define IDH_SYSMENU     (IDH_HELPFIRST + 2000)
#define IDH_MBFIRST     (IDH_HELPFIRST + 2001)
#define IDH_DLG_FONT2   (IDH_HELPFIRST + 2002)
#define IDH_MBLAST      (IDH_HELPFIRST + 2099)
#define IDH_DLGFIRST    (IDH_HELPFIRST + 3000)
#define IDH_SCRNSAVE    (IDH_HELPFIRST + 4000)

#define IDH_SPOOLER_OFFSET 5000
#define IDH_DISPLAY_OFFSET 6000

#define IDH_MENU_SCHHELP    (IDH_HELPFIRST + MENU_SCHHELP)
#define IDH_MENU_INDHELP    (IDH_HELPFIRST + MENU_INDHELP)
#define IDH_MENU_USEHELP    (IDH_HELPFIRST + MENU_USEHELP)
#define IDH_MENU_ABOUT      (IDH_HELPFIRST + MENU_ABOUT )
#define IDH_MENU_EXIT       (IDH_HELPFIRST + MENU_EXIT)
#define IDH_CHILD_COLOR     (IDH_HELPFIRST + CHILD_COLOR)
#define IDH_CHILD_PRINTER   (IDH_HELPFIRST + CHILD_PRINTER)
#define IDH_CHILD_FONT      (IDH_HELPFIRST + CHILD_FONT )
#define IDH_CHILD_INTL      (IDH_HELPFIRST + CHILD_INTL )
#define IDH_CHILD_PORTS     (IDH_HELPFIRST + CHILD_PORTS)
#define IDH_CHILD_KEYBOARD  (IDH_HELPFIRST + CHILD_KEYBOARD )
#define IDH_CHILD_MOUSE     (IDH_HELPFIRST + CHILD_MOUSE)
#define IDH_CHILD_DATETIME  (IDH_HELPFIRST + CHILD_DATETIME )
#define IDH_CHILD_DESKTOP   (IDH_HELPFIRST + CHILD_DESKTOP)
#define IDH_CHILD_SOUND     (IDH_HELPFIRST + CHILD_SOUND)
#define IDH_CHILD_NETWORK   (IDH_HELPFIRST + CHILD_NETWORK)
#define IDH_CHILD_SYSTEM    (IDH_HELPFIRST + CHILD_SYSTEM)
#define IDH_CHILD_MIDI      (IDH_HELPFIRST + 122)           // MM midi
#define IDH_CHILD_SND       (IDH_HELPFIRST + 121)           // MM sound
#define IDH_CHILD_DRIVERS   (IDH_HELPFIRST + 120)           // MM drivers
#define IDH_CHILD_CURSORS   (IDH_HELPFIRST + 119)
#define IDH_DLG_CURBROWSE   (IDH_HELPFIRST + 118)

#define IDH_DLG_CONFLICT    (IDH_DLGFIRST + DLG_CONFLICT)
#define IDH_DLG_ADDFILE     (IDH_DLGFIRST + DLG_ADDFILE)
#define IDH_DLG_INTLDATE    (IDH_DLGFIRST + DLG_INTLDATE)
#define IDH_DLG_INTLTIME    (IDH_DLGFIRST + DLG_INTLTIME)
#define IDH_DLG_INTLNUM     (IDH_DLGFIRST + DLG_INTLNUM)
#define IDH_DLG_INTLCUR     (IDH_DLGFIRST + DLG_INTLCUR)
#define IDH_DLG_NETPRINT    (IDH_DLGFIRST + DLG_NETPRN)

#define IDH_DLG_PATTERN     (IDH_DLGFIRST + DLG_PATTERN)
#define IDH_DLG_PORTS2      (IDH_DLGFIRST + DLG_PORTS2)
#define IDH_DLG_PORTS3      (IDH_DLGFIRST + DLG_PORTS3)
#define IDH_DLG_MOUSE       (IDH_DLGFIRST + DLG_MOUSE)

#define IDH_DLG_COLORSAVE   (IDH_DLGFIRST + DLG_COLORSAVE)
#define IDH_DLG_COLORDEFINE (IDH_DLGFIRST + DLG_COLORDEFINE)
#define IDH_DLG_CONFIGURE   (IDH_DLGFIRST + DLG_CONFIGURE)
#define IDH_DLG_INSTALL     (IDH_DLGFIRST + DLG_INSTALL)
#define IDH_DLG_UNLIST      (IDH_DLGFIRST + DLG_UNLIST)
#define IDH_DLG_REMOVEFONT  (IDH_DLGFIRST + DLG_REMOVEFONT)
#define IDH_DLG_TRUETYPE    (IDH_DLGFIRST + DLG_TRUETYPE)
#define IDH_DLG_BROWSE      (IDH_DLGFIRST + DLG_BROWSE)

#define IDH_DLG_INSTALL_PS     (IDH_DLGFIRST + DLG_INSTALL_PS)
#define IDH_DLG_REMOVEFONT_PS  (IDH_DLGFIRST + DLG_REMOVEFONT_PS)

#define IDH_DLG_SYSTEM      (IDH_DLGFIRST + DLG_SYSTEM)
#define IDH_DLG_ADDOS       (IDH_DLGFIRST + DLG_ADDOS)

#define IDH_DLG_VIRTUALMEM  (IDH_DLGFIRST + DLG_VIRTUALMEM)

#define IDH_DLG_PREVIOUSCON (IDH_DLGFIRST + DLG_PREVIOUSCON)
#define IDH_DLG_TASKING     (IDH_DLGFIRST + DLG_TASKING)

#define IDH_DLG_COREDUMP    (IDH_DLGFIRST + DLG_COREDUMP)

// pen win help constants

#define IDH_DLG_ROT              (IDH_DLGFIRST + 200)
#define IDH_DLG_CAL              (IDH_DLGFIRST + 201)
#define IDH_DLG_CAL_ACCEPT       (IDH_DLGFIRST + 202)

// MM midi  dialogs
#define IDH_DLG_MIDI_SETUPEDIT  (IDH_DLGFIRST + 404)
#define IDH_DLG_MIDI_PATCHEDIT  (IDH_DLGFIRST + 504)
#define IDH_DLG_MIDI_KEYEDIT    (IDH_DLGFIRST + 604)
#define IDH_DLG_MIDI_SETUPNEW   (IDH_DLGFIRST + 704)
#define IDH_DLG_MIDI_PATCHNEW   (IDH_DLGFIRST + 804)
#define IDH_DLG_MIDI_KEYNEW     (IDH_DLGFIRST + 904)

// MM sound dialogs
#define IDH_DLG_NEWSND          (IDH_DLGFIRST + 503)

// MM drivers dialogs
#define IDH_DLG_ADD_DRIVERS     (IDH_DLGFIRST + 403)
#define IDH_DLG_INSERT_DISK     (IDH_DLGFIRST + 703)
#define IDH_DLG_ADD_UNKNOWN     (IDH_DLGFIRST + 803)
#define IDH_DLG_FILE_ERROR      (IDH_DLGFIRST + 903)

// Screen Saver dialogs
#define IDH_DLG_CHANGEPASS      (IDH_SCRNSAVE)
#define IDH_DLG_MARQUE          (IDH_SCRNSAVE + 1)
#define IDH_DLG_MYSTIFY         (IDH_SCRNSAVE + 2)
#define IDH_DLG_STARSIM         (IDH_SCRNSAVE + 3)
#define IDH_DLG_MARQUEE_FONT    (IDH_SCRNSAVE + 4)
#define IDH_DLG_FLYWIN          (IDH_SCRNSAVE + 5)
#define IDH_DLG_BEZIER          (IDH_SCRNSAVE + 6)

#define IDH_DLG_PRINTER         (IDH_DLGFIRST + DLG_PRINTER)

// We need to change the help stuff when called by the spooler.
#define IDH_PRINTER               (IDH_DLG_PRINTER + IDH_SPOOLER_OFFSET)
#define IDH_DLG_CONNECT           (IDH_DLG_CONFIGURE + IDH_SPOOLER_OFFSET)
#define IDH_DLG_NETCONNECT        (IDH_DLG_NETPRINT + IDH_SPOOLER_OFFSET)
#define IDH_DLG_PREVCON           (IDH_DLG_PREVIOUSCON + IDH_SPOOLER_OFFSET)
#define IDH_DLG_UNLISTED_PRINTER  (IDH_DLG_UNLIST + IDH_SPOOLER_OFFSET)
// #define IDH_DLG_INSTALL_PRINTER   (IDH_DLG_INSTALL + IDH_SPOOLER_OFFSET)
#define IDH_DLG_COM_SET           (IDH_DLG_PORTS2 + IDH_SPOOLER_OFFSET)
#define IDH_DLG_ADVANCED_COMSET   (IDH_DLG_PORTS3 + IDH_SPOOLER_OFFSET)
#define IDH_DLG_PRINTER_BROWSE    (IDH_DLG_BROWSE + IDH_SPOOLER_OFFSET)
#define IDH_DLG_INS_PRINTER_DISK  (IDH_DLG_INSERT_DISK + IDH_SPOOLER_OFFSET)


COMSTRIN.H

#define idsIniFile              1001
#define idsScreenSaver          1002
#define idsAppName              1007
#define idsNoHelpMemory         1008
#define idsHelpFile             1009

#define DLG_SCRNSAVECONFIGURE   2003

#include "cphelp.h"


SKELETON.C

#define WIN31
#include <windows.h>
#include <scrnsave.h>
#include "cparrow.h"
#include "comstrin.h"
#include "uniconv.h"

/* Global variables... */
HANDLE  hMainInst;
HWND    hMainWindow;

TCHAR   szName[TITLEBARNAMELEN];
TCHAR   szAppName[APPNAMEBUFFERLEN];       // Section name in CONTROL.INI
TCHAR   szIniFile[MAXFILELEN];
TCHAR   szScreenSaver[22];
TCHAR   szHelpFile[MAXFILELEN];
TCHAR   szNoHelpMemory[BUFFLEN];
UINT    MyHelpMessage;

/* Local Function definitions... */
BOOL AppInit           (HANDLE hInst, HANDLE hPrev, WORD sw, LPTSTR szCmdLine);
int  DoConfigureDialog (HANDLE hInst, BOOL fParent);

HCURSOR hcurOld;

HHOOK hhkNextMsgFilterHookFunc = NULL;

LRESULT CALLBACK HelpMessageFilterHookFunction (int nCode, WPARAM wParam, LPMSG lpMsg);

#define THRESHOLD   3

//***************************************************************************

BOOL AppInit (HANDLE hInst, HANDLE hPrev, WORD sw, LPTSTR szCmdLine)
{
    WNDCLASS cls;
    int    dx, dy;

    if (hPrev != NULL)
        return FALSE;

    /*
     *  Register a class for the main application window
     */
    cls.hCursor        = NULL;
    cls.hIcon          = LoadIcon (hInst, MAKEINTATOM (ID_APP));
    cls.lpszMenuName   = NULL;
    cls.lpszClassName  = TEXT("WindowsScreenSaverClass");
    cls.hbrBackground  = GetStockObject (BLACK_BRUSH);
    cls.hInstance      = hInst;
    cls.style          = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
    cls.lpfnWndProc    = ScreenSaverProc;
    cls.cbWndExtra     = 0;
    cls.cbClsExtra     = 0;

    if (!RegisterClass (&cls))
        return FALSE;

    dx = GetSystemMetrics (SM_CXSCREEN);
    dy = GetSystemMetrics (SM_CYSCREEN);
    hMainWindow = CreateWindowEx (WS_EX_TOPMOST,
                            TEXT("WindowsScreenSaverClass"), // Class name
                            TEXT("\0"),                      // Caption
                            WS_POPUP | WS_VISIBLE |          // Style bits
                            WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                            0, 0,                            // Position
                            dx, dy,                          // Size
                            (HWND)NULL,                      // Parent window (no parent)
                            (HMENU)NULL,                     // use class menu
                            (HANDLE)hInst,                   // handle to window instance
                            (LPVOID)NULL                     // no params to pass on
                               );
    if (hMainWindow)
        return TRUE;

    return FALSE;
}

//***************************************************************************

int _CRTAPI1 main (USHORT argc, CHAR **argv)
{
    HANDLE   hInst;
    HANDLE   hPrev     = NULL;
    LPTSTR   szCmdLine = GetCommandLine();
    WORD     sw        = SW_SHOWNORMAL;
    MSG      msg;
    LPTSTR   lpT;

    hInst = GetModuleHandle (NULL);

    /* Save instance handle for DialogBoxs */
    hMainInst = hInst;

    // This has to be loaded first so we know who we are and to find if we
    // already around.

    LoadString (hInst, idsAppName, szAppName, CharSizeOf(szAppName));

    //=================================================================
    // on NT, szCmdLine's first string includes its own name, remove this
    // to make it exactly like the windows command line.

    if (*szCmdLine)
    {
        lpT = _tcschr (szCmdLine, TEXT(' '));   // skip self name
        if (lpT)
        {
            szCmdLine = lpT;
            while (*szCmdLine == TEXT(' '))
                szCmdLine++;            // skip spaces to end or first cmd
        }
        else
        {
            szCmdLine += lstrlen(szCmdLine);   // point to NULL
        }
    }
    //=====================================================================

    MyHelpMessage = RegisterWindowMessage (szAppName);

    if (MyHelpMessage)
        hhkNextMsgFilterHookFunc = SetWindowsHook (WH_MSGFILTER,
                                      (HOOKPROC)HelpMessageFilterHookFunction);
    /* Parse through the command line.  If the parameter is -s, /s, or s, then
       bring up a configure dialog box.  Otherwise, bring up the normal
       window...*/
    if (!((!_tcsnicmp (szCmdLine, TEXT("/s"), 2) || !_tcsicmp (szCmdLine, TEXT("-s"))) ||
        !_tcsicmp (szCmdLine, TEXT("s"))))
    {
        if (!((!_tcsicmp (szCmdLine, TEXT("/c")) || !_tcsicmp (szCmdLine, TEXT("-c"))) ||
            !_tcsicmp (szCmdLine, TEXT("c"))))
            return DoConfigureDialog (hInst, FALSE);
        else
            return DoConfigureDialog (hInst, TRUE);
    }

    /* Call initialization procedure */
    if (!AppInit (hInst, hPrev, sw, szCmdLine))
        return FALSE;

    /* Message Loop for the program... */
    while (GetMessage (&msg, NULL, 0, 0))
    {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }

    if (MyHelpMessage)
        UnhookWindowsHook (WH_MSGFILTER, (HOOKPROC)HelpMessageFilterHookFunction);

    return msg.wParam;
}

//***************************************************************************

LONG DefScreenSaverProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static BOOL     fHere = FALSE;
    static POINT    ptLast;
    static BOOL     bDialogUp = FALSE;
    POINT           ptCursor, ptCheck;

    switch (msg)
    {
    case WM_SYSCOMMAND:
        if ((wParam==SC_SCREENSAVE) || (wParam==SC_CLOSE))
            return FALSE;
        break;

    case WM_DESTROY:
        PostQuitMessage (0);
        break;

    case WM_SETCURSOR:
        SetCursor (NULL);
        break;

    case WM_NCACTIVATE:
        if (wParam==FALSE && !bDialogUp)
            return FALSE;
        break;

    case WM_ACTIVATE:
    case WM_ACTIVATEAPP:
        if (wParam != FALSE)        // only fall through if we are
            break;                  // losing the focus...

    case WM_MOUSEMOVE:
        if (!fHere)
        {
            GetCursorPos (&ptLast);
            fHere = TRUE;
        }
        else
        {
            GetCursorPos (&ptCheck);
            if (ptCursor.x = ptCheck.x - ptLast.x)
            {
                if (ptCursor.x < 0)
                    ptCursor.x *= -1;
            }
            if (ptCursor.y = ptCheck.y - ptLast.y)
            {
                if (ptCursor.y < 0)
                    ptCursor.y *= -1;
            }
            if ((ptCursor.x + ptCursor.y) > THRESHOLD)
                goto SHOWDIALOG;
        }
        break;

    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
        GetCursorPos (&ptCursor);
        ptCursor.x ++;
        ptCursor.y ++;
        SetCursorPos (ptCursor.x, ptCursor.y);
        GetCursorPos (&ptCheck);
        if (ptCheck.x != ptCursor.x && ptCheck.y != ptCursor.y)
            ptCursor.x -= 2;
        ptCursor.y -= 2;
        SetCursorPos (ptCursor.x, ptCursor.y);

        // fall thru

    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
SHOWDIALOG:
        PostMessage (hWnd, WM_CLOSE, 0, 0l);
        break;
    }
    return DefWindowProc (hWnd, msg, wParam, lParam);
}

//***************************************************************************

int    DoConfigureDialog (HANDLE hInst, BOOL fParent )
{
    HWND    hWndParent;

    if (fParent)
        hWndParent = GetActiveWindow ();
    else
        hWndParent = NULL;

    if (RegisterDialogClasses (hInst))
    {
        hMainWindow = NULL;
        DialogBox (hInst, MAKEINTRESOURCE (DLG_SCRNSAVECONFIGURE),
                          hWndParent, (WNDPROC) ScreenSaverConfigureDialog);
    }

    return TRUE;
}

//***************************************************************************

LRESULT CALLBACK HelpMessageFilterHookFunction (int nCode, WPARAM wParam, LPMSG lpMsg)
{

    if (nCode < 0)
        goto DefHook;

    if (nCode == MSGF_DIALOGBOX)
        if (lpMsg->message == WM_KEYDOWN && lpMsg->wParam == VK_F1)
        {
            HWND hTemp;
            HWND hParent = lpMsg->hwnd;

            while (hParent != NULL)
            {
                hTemp = hParent;
                if (!(GetWindowLong(hTemp, GWL_STYLE) & WS_CHILD))
                    break;
                hParent = (HWND) GetWindowLong (hParent, GWL_HWNDPARENT);
            }

            PostMessage (hTemp, MyHelpMessage, 0, 0L);
            return TRUE;
        }
        else
DefHook:
        return (int)DefHookProc(nCode, wParam, (LONG)lpMsg, &hhkNextMsgFilterHookFunc);

    return 0;
}


UNICONV.H

/****************************************************************************/
/*                                                                          */
/*  UNICONV.H -                                                             */
/*                                                                          */
/*       My special Unicode workaround file                                 */
/*                                                                          */
/*       Created by      :       Diane K. Oh                                */
/*       On Date         :       June 11, 1992                              */
/*                                                                          */
/*       File was created to Unicode enable special function calls of       */
/*       WIN32 Shell applications.                                          */
/*                                                                          */
/****************************************************************************/

#include <string.h>
#include <tchar.h>
#ifdef UNICODE
#include <wchar.h>
#else
#include <ctype.h>
#endif

/*--------------------------------------------------------------------------*/
/*  Macros                                                                  */
/*--------------------------------------------------------------------------*/

#define CharSizeOf(x)   (sizeof(x) / sizeof(*x))
#define ByteCountOf(x)  ((x) * sizeof(TCHAR))

/*--------------------------------------------------------------------------*/
/*  Constants                                                               */
/*--------------------------------------------------------------------------*/

#define   UNICODE_FONT_NAME        TEXT("Lucida Sans Unicode")
#define   UNICODE_FIXED_FONT_NAME  TEXT("Lucida Console")

/*--------------------------------------------------------------------------*/
/*  Function Templates                                                      */
/*--------------------------------------------------------------------------*/

INT      MyAtoi          (LPTSTR string);
LONG     MyAtol          (LPTSTR  string);
LPTSTR   MyItoa          (INT value, LPTSTR string, INT radix);
LPTSTR   MyUltoa         (unsigned long value, LPTSTR string, INT radix);
VOID     MySplitpath     (LPTSTR path, LPTSTR drive, LPTSTR dir,
                          LPTSTR fname, LPTSTR ext);

LPTSTR   SkipProgramName (LPTSTR lpCmdLine);

HANDLE   MyOpenFile      (LPTSTR lpszFile, TCHAR * lpszPath, DWORD fuMode);
BOOL     MyCloseFile     (HANDLE hFile);
BOOL     MyAnsiReadFile  (HANDLE hFile, UINT uCodePage, LPVOID lpBuffer, DWORD nChars);
BOOL     MyByteReadFile  (HANDLE hFile, LPVOID lpBuffer, DWORD nBytes);
BOOL     MyAnsiWriteFile (HANDLE hFile, UINT uCodePage, LPVOID lpBuffer, DWORD nChars);
BOOL     MyByteWriteFile (HANDLE hFile, LPVOID lpBuffer, DWORD nBytes);
LONG     MyFileSeek      (HANDLE hFile, LONG lDistanceToMove, DWORD dwMoveMethod);


README.TXT

Monitoring Messages with SPY

SUMMARY
=======

The SPY tool demonstrates how to monitor system messages sent to a specified
window. SPY records the messages and displays them on the specified device.
SPY is also useful in debugging, to verify that the messages you think a
window is receiving are actually being received.

SPY demonstrates the following techniques:

  - Using a system message hook.
  - Using the WM_COPYDATA message to pass data to another application.
  - Reading and writing the registry.
  - Creating a thread.
  - Creating a DLL for the hook.

MORE INFORMATION
================

To use SPY to monitor messages, do the following:

 1. Choose the Options menu to select the following:

      - Messages: Selects the messages you wish to monitor.
      - Font: Selects the font you wish to have displayed.
      - Output: Selects the output device to which you want messages to go.

 2. Select the window whose messages you want to watch by choosing the
    Select Window command from the Spy menu. This brings up a dialog box
    listing the current windows to watch. The default is all windows.

 3. The Edit menu allows you to use the clipboard from the SPY window.

 4. Choose Start! to start monitoring. Once this option is chosen, the menu
    item changes to Stop!. Choose Stop! to cease monitoring messages.


HOOK.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.
\******************************************************************************/

/*----------------------------------------------------------------------------
*|   hook.h    - Windows message spy application                               |
|                                                                              |
\*----------------------------------------------------------------------------*/

#define PUBLIC
#if !DBG
#define PRIVATE static
#else
#define PRIVATE
#endif

// special hwnds for the hooks to recognize
#define HWND_ALL  ((HWND)0xffffffff)
#define HWND_NULL ((HWND)0L)

#define SPYAPPNAME      "Spy"
#define SPYCLASSNAME    "Spy Class"
#define HOOKWINDOWNAME  "SpyHookWindow"
#define HOOKWINDOWCLASS "SpyHookClass"

//
// Structure that contains the spy message data sent from the hook
// to the spy app to display.  The hwnd is sent using wParam of
// the WM_COPYDATA message, and the message number is in the dwData
// field of the COPYDATASTRUCT.  This structure allows the wParam,
// lParam and any optional extra data to be passed across.
//
typedef struct
{
    WPARAM wParam;
    LPARAM lParam;
    BYTE ExtraData[64];
} SPYMSGDATA, *PSPYMSGDATA;

LRESULT CALLBACK SpyGetMsgProc(INT hc, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK SpyCallWndProc(INT hc, WPARAM wParam, LPARAM lParam);


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.
\******************************************************************************/

/*****************************************************************************\
*
* Module: dialogs.c
*
*   Contains dialog procs for the Windows debugging Spy SDK applet.
*
* Functions:
*
*   MyDialogBox()
*   AboutDlgProc()
*   SelectWindowDlgProc()
*   OutputDlgProc()
*   SelectFont()
*   MessagesDlgProc()
*   SelectWindowCommand()
*   FillListBox()
*   AddOneWindow()
*   MakeWindowName()
*   FindHwndInListBox()
*   HighlightWindow()
*   SelectWindowUpdateInfo()
*   SelectWindowEnableFields()
*   OutputCommand()
*   MessagesInit()
*   MessagesCommand()
*   MessagesUpdateCheckBoxes()
*
* Comments:
*
\*****************************************************************************/

#include "spy.h"

#include <commdlg.h>

#define DINV                3

PRIVATE HWND ghwndSpyingOnTemp;     // Temp when selecting hwnd to spy on.
PRIVATE HWND ghwndDlgBeingFilled = NULL;
PRIVATE BOOL bBorderOn = FALSE;
PRIVATE INT gcItemsSave;

PRIVATE BOOL SelectWindowCommand(HWND hwnd, INT nCmd, INT nNotifyCode);
PRIVATE VOID FillListBox(HWND hDlg, HWND hwndList, HWND hwnd);
BOOL CALLBACK AddOneWindow(HWND hwnd, LPARAM hwndListLP);
PRIVATE VOID MakeWindowName(HWND hwnd, LPSTR lpString, INT nStringLen);
PRIVATE INT FindHwndInListBox(HWND hwndList, HWND hSpyWnd);
PRIVATE VOID HighlightWindow(HWND hwnd, BOOL fDraw);
PRIVATE VOID SelectWindowUpdateInfo(HWND hDlg, HWND hwnd);
PRIVATE VOID SelectWindowEnableFields(HWND hwnd, BOOL fEnable);
PRIVATE BOOL OutputCommand(HWND hwnd, INT nCmd, INT nNotifyCode);
PRIVATE VOID MessagesInit(HWND hwnd);
PRIVATE BOOL MessagesCommand(HWND hwnd, INT nCmd, INT nNotifyCode);
PRIVATE VOID MessagesUpdateCheckBoxes(HWND hwnd);

CHAR szConsoleWindowClass[] = "ConsoleWindowClass";

/*****************************************************************************\
* MyDialogBox
*
* Puts up the specified dialog.
*
* Arguments:
*   INT idDlg          - The resource id of the dialog to display.
*   DLGPROC pfnDlgProc - The dialog proc to use.
*
* Returns:
*   The return value from DialogBox (whatever the dialog proc passes
*   to EndDialog).
\*****************************************************************************/

BOOL
MyDialogBox(
    INT idDlg,
    DLGPROC pfnDlgProc
    )
{
    return DialogBox(ghInst, MAKEINTRESOURCE(idDlg), ghwndSpyApp, pfnDlgProc);
}

/*****************************************************************************\
* AboutDlgProc
*
*   Dialog proc for the About box.
*
\*****************************************************************************/

BOOL CALLBACK
AboutDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch (msg) {
        case WM_INITDIALOG:
            return TRUE;

        case WM_COMMAND:
            EndDialog(hwnd, IDOK);
            break;
    }

    return FALSE;
}

/*****************************************************************************\
* SelectWindowDlgProc
*
* Dialog proc for the Select Window dialog.  This dialog allows the user
* to select which window they want to spy on.
*
* Arguments:
*   HWND hwnd       - Window handle of the dialog.
*   UINT msg        - Message sent to window.
*   WPARAM wParam   - Message parameter.
*   LPARAM lParam   - Message parameter.
*
* Returns:
*   The value that the dialog proc should return, based on the processing
*   of the specific WM_COMMAND message received.
\*****************************************************************************/

BOOL CALLBACK
SelectWindowDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    HWND hwndList;
    INT nIndex;

    UNREFERENCED_PARAMETER(lParam);

    switch (msg)
    {
        case WM_INITDIALOG:
            hwndList = GetDlgItem(hwnd, DID_SELWINLIST);
            ghwndSpyingOnTemp = ghwndSpyingOn == HWND_ALL ? NULL : (ghwndSpyingOn == NULL ? NULL
                : GetParent(ghwndSpyingOn));
            FillListBox(hwnd, hwndList, ghwndSpyingOnTemp);
            nIndex = FindHwndInListBox(hwndList, ghwndSpyingOn == HWND_ALL ? NULL
                : ghwndSpyingOn);
            SendMessage(hwndList, LB_SETCURSEL, nIndex, 0);
            ghwndSpyingOnTemp = (HWND)SendMessage(hwndList, LB_GETITEMDATA, nIndex, 0);
            SelectWindowUpdateInfo(hwnd, ghwndSpyingOnTemp);
            CheckDlgButton(hwnd, DID_SELWINALLWINDOWS, gfSpyAll);
            SelectWindowEnableFields(hwnd, !gfSpyAll);
            SetFocus(hwnd);
            HighlightWindow(ghwndSpyingOnTemp, TRUE);

            return TRUE;

        case WM_NCLBUTTONDOWN:
            if (wParam == HTCAPTION)
            {
                //
                // The mouse is down for a move of the dialog, so clean up the
                // border stuff.
                //
                if (bBorderOn)
                    HighlightWindow(ghwndSpyingOnTemp, FALSE);
            }

            return FALSE;

        case WM_KEYDOWN:
        case WM_LBUTTONUP:
        case WM_NCLBUTTONUP:
            //
            // The mouse is up from a move of the dialog, so put up the
            // border stuff again.
            //
            if (!bBorderOn)
                HighlightWindow(ghwndSpyingOnTemp, TRUE);

            return FALSE;

        case WM_CANCELMODE:
            return FALSE;

        case WM_COMMAND:
            return SelectWindowCommand(hwnd, LOWORD(wParam), HIWORD(wParam));
    }

    return FALSE;
}

/*****************************************************************************\
* SelectWindowCommand
*
* Handles thw WM_COMMAND messages for the Select Window dialog.
*
* Arguments:
*   HWND hwnd       - Window handle of the dialog.
*   INT nCmd        - Command value.
*   INT nNotifyCode - The notify code.
*
* Returns:
*   The value that the dialog proc should return, based on the processing
*   of the specific WM_COMMAND message received.
\*****************************************************************************/

PRIVATE BOOL
SelectWindowCommand(
    HWND hwnd,
    INT nCmd,
    INT nNotifyCode
    )
{
    INT nIndex;
    HWND hwndList;
    CHAR rgString[32];

    switch (nCmd)
    {
        case IDOK:
            SetWindowToSpyOn(IsDlgButtonChecked(hwnd, DID_SELWINALLWINDOWS) ?
                HWND_ALL : ghwndSpyingOnTemp);

            if (bBorderOn)
                HighlightWindow(ghwndSpyingOnTemp, FALSE);

            EndDialog(hwnd, IDOK);
            return TRUE;

        case IDCANCEL:
            if (bBorderOn)
                HighlightWindow(ghwndSpyingOnTemp, FALSE);

            EndDialog(hwnd, IDCANCEL);
            return TRUE;

        case DID_SELWINLIST:
            //
            // User single clicked or doubled clicked in listbox -
            //   Single click means select a window to spy on
            //   Double click means enumerate all the children of that window.
            //
            hwndList = GetDlgItem(hwnd, DID_SELWINLIST);
            switch (nNotifyCode)
            {
                case LBN_SELCHANGE:
                    //
                    // Single click case. Select a window to spy upon.
                    //
                    // Get the window handle, set it as the window to spy on.
                    //

                    if (bBorderOn)
                        HighlightWindow(ghwndSpyingOnTemp, FALSE);

                    nIndex = (INT)SendMessage(hwndList, LB_GETCURSEL, 0, 0);
                    ghwndSpyingOnTemp = (HWND)SendMessage(hwndList, LB_GETITEMDATA,
                        nIndex, 0);
                    SelectWindowUpdateInfo(hwnd, ghwndSpyingOnTemp);

                    HighlightWindow(ghwndSpyingOnTemp, TRUE);

                    break;

                case LBN_DBLCLK:
                    //
                    // Double click case - first click has already been
                    // processed as single click. In this case, the user has
                    // requested to look at all the children of a given
                    // selection.
                    //
                    // Get the current selection, and check to see if it is the
                    // " [ parent.. ]" entry. If so, go up one level first.
                    //

                    SetCursor(LoadCursor(NULL, IDC_WAIT));

                    if (bBorderOn)
                        HighlightWindow(ghwndSpyingOnTemp, FALSE);

                    nIndex = (INT)SendMessage(hwndList, LB_GETCURSEL, 0, 0);
                    ghwndSpyingOnTemp = (HWND)SendMessage(hwndList, LB_GETITEMDATA,
                        nIndex, 0);
                    SendMessage(hwndList, LB_GETTEXT, nIndex, (LPARAM)rgString);

                    if (rgString[0] == ' ')
                    {
                        // At top?  If so, we are done.
                        if (ghwndSpyingOnTemp == NULL)
                        {
                            SetCursor(LoadCursor(NULL, IDC_ARROW));
                            break;
                        }

                        ghwndSpyingOnTemp = GetParent(ghwndSpyingOnTemp);
                    }

                    SendMessage(hwndList, LB_RESETCONTENT, 0, 0);
                    FillListBox(hwnd, hwndList, ghwndSpyingOnTemp);

                    nIndex = FindHwndInListBox(hwndList, ghwndSpyingOnTemp);
                    SendMessage(hwndList, LB_SETCURSEL, nIndex, 0);
                    ghwndSpyingOnTemp = (HWND)SendMessage(hwndList, LB_GETITEMDATA,
                        nIndex, 0);
                    HighlightWindow(ghwndSpyingOnTemp,TRUE);
                    SelectWindowUpdateInfo(hwnd, ghwndSpyingOnTemp);
                    SetCursor(LoadCursor(NULL, IDC_ARROW));
                    break;
            }

            break;

        case DID_SELWINALLWINDOWS:
            SelectWindowEnableFields(hwnd,
                !IsDlgButtonChecked(hwnd, DID_SELWINALLWINDOWS));

            break;
    }

    return FALSE;
}

/*****************************************************************************\
* FillListBox
*
* Fills the listbox in the Select Window dialog with the names of
* the child windows of the given window.
*
* Arguments:
*   hDlg     - Window handle of the dialog window
*   hwndList - Handle to the listbox within the dialog.
*   hwnd     - Parent whose children to enumerate.
*
* Returns:
*   VOID
\*****************************************************************************/

PRIVATE VOID
FillListBox(
    HWND hDlg,
    HWND hwndList,
    HWND hwnd
    )
{
    INT nIndex;

    //
    // First fill the list box with child windows
    //
    // Make sure we display the list box after things are added.
    //

    SendMessage(hwndList, WM_SETREDRAW, 0, 0);

    //
    // remember which dialog we are processing
    //

    ghwndDlgBeingFilled = hDlg;

    if (hwnd == NULL)
    {
        //
        // Enumerate the top level separately... gross unsymmetry, but
        // hey.
        //
        EnumWindows(AddOneWindow, (LPARAM)hwndList);
    }
    else
    {
        EnumChildWindows(hwnd, AddOneWindow, (LPARAM)hwndList);
    }

    //
    // Now give the user a method of getting back to the parent. The space at
    // the beginning of the " [parent]" string identifies the entry as the
    // parent entry and makes it different from all the other entries since
    // the others start with a handle number of some sort.
    //

    nIndex = SendMessage(hwndList, LB_ADDSTRING, 0,
                        (LPARAM)(LPCTSTR)LoadResourceString(IDS_PARENT));
    SendMessage(hwndList, LB_SETITEMDATA, nIndex, (LONG)hwnd);

    //
    // Now do the redraw...
    //

    SendMessage(hwndList, WM_SETREDRAW, 1, 0);
}

/*****************************************************************************\
* AddOneWindow
*
* Gets the windows to add to the list of windows to spy on.
*
* Arguments:
*     HWND hwnd - handle of the window to add.
*     HWND hwndList - handle to the listbox.
*
* Returns:
*     TRUE - if a window was created.
\*****************************************************************************/
#define CCH_RGBUF   32

BOOL CALLBACK
AddOneWindow(
    HWND hwnd,
    LPARAM hwndListLP
    )
{
    CHAR rgBuf[CCH_RGBUF];
    INT nIndex;
    HWND htemp;
    HWND hwndList = (HWND)hwndListLP;
    //
    // Make sure we don't add any window that has anything to do with
    // the dialog or any other spy window
    //

    htemp = GetParent(hwnd);

    // Don't put console windows in the list since they can not be hooked
    if (GetClassName(hwnd, rgBuf, CCH_RGBUF) != 0 &&
        strcmp(rgBuf, szConsoleWindowClass) == 0 )
    {
        return 1;
    }

    // Don't put windows that belong to spy in the list
    if (hwnd == ghwndDlgBeingFilled || htemp == ghwndDlgBeingFilled
        || hwnd == ghwndSpyApp
        || htemp == ghwndSpyApp || hwnd == ghwndPrintf || htemp == ghwndPrintf
        || hwnd == ghwndSpyHook || htemp == ghwndSpyHook)
    {
        return 1;
    }

    MakeWindowName(hwnd, rgBuf, CCH_RGBUF);

    nIndex = SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)rgBuf);

    if (nIndex == LB_ERR || nIndex == LB_ERRSPACE)
        return 0;

    if (SendMessage(hwndList, LB_SETITEMDATA, nIndex, (LPARAM)hwnd) == LB_ERR)
        return 0;

    return 1;
}

/*****************************************************************************\
* MakeWindowName
*
* Builds the window name from the window handle passed in.
*
* Arguments:
*     HWND hwnd - handle to the window.
*     LPSTR lpString - String to put window name into.
*     INT nSTringLen - Length of window string.
*
* Returns:
*     VOID
\*****************************************************************************/

PRIVATE VOID
MakeWindowName(
    HWND hwnd,
    LPSTR lpString,
    INT nStringLen
    )
{
    wsprintf(lpString, "%8.8lX:", hwnd);

    if (hwnd == NULL || !IsWindow(hwnd))
    {
        lstrcat(lpString, "!!!");
    }
    else
    {
        GetWindowText(hwnd, lpString + 9, nStringLen - 9);
    }
}

/*****************************************************************************\
* FindHwndInListBox
*
* Gets the window from the list of windows in the listbox.
*
* Arguments:
*     HWND hwndList - handle to the listbox.
*     HWND hSpyWnd - handle to the spy window.
*
* Returns:
*     INT - Index to the window in the listbox.
\*****************************************************************************/

PRIVATE INT
FindHwndInListBox(
    HWND hwndList,
    HWND hSpyWnd
    )
{
    CHAR rgBuf[9];
    INT nIndex;

    wsprintf(rgBuf, "%08lX", (LONG)hSpyWnd);

    nIndex = SendMessage(hwndList, LB_FINDSTRING, (WPARAM)-1, (LPARAM)rgBuf);

    if (nIndex == LB_ERR)
        nIndex = 0;

    return nIndex;
}

/*****************************************************************************\
* HighlightWindow
*
* Used to temporarily highlight the window that the user has selected from
* the Select Window dialog.  It does this by inverting a border around the
* window.
*
* Arguments:
*     HWND hwnd - handle to the selected window.
*     BOOL fdraw - whether to draw the window inverted on non-inverted.
*
* Returns:
*     VOID
\*****************************************************************************/

PRIVATE VOID
HighlightWindow(
    HWND hwnd,
    BOOL fDraw
    )
{
    HDC hdc;
    RECT rc;

    bBorderOn = fDraw;

    if (hwnd == NULL || !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);
}

/*****************************************************************************\
* SelectWindowUpdateInfo
*
* Updates the informational fields in the Select Window dialog when
* a new window is selected from the hwnd listbox.
*
* Arguments:
*     HWND hDlg - handle to the select window dialog box.
*     HWND hwnd - handle to the new window selected.
*
* Returns:
*     VOID
\*****************************************************************************/

PRIVATE VOID
SelectWindowUpdateInfo(
    HWND hDlg,
    HWND hwnd
    )
{
    HWND hParent;
    DWORD dwStyle;
    RECT rc;
    CHAR szTemp[MAXSTRING];

    if (hwnd)
    {
        hParent = GetParent(hwnd);
        dwStyle = GetWindowLong(hwnd, GWL_STYLE);

        MakeWindowName(hwnd, szTemp, MAXSTRING);
        SetDlgItemText(hDlg, DID_SELWINWINDOW, szTemp);

        GetClassName(hwnd, szTemp, MAXSTRING);
        SetDlgItemText(hDlg, DID_SELWINCLASS, szTemp);

        if (hParent)
        {
            MakeWindowName(hParent, szTemp, MAXSTRING);
            SetDlgItemText(hDlg, DID_SELWINPARENT, szTemp);
        }
        else
        {
            SetDlgItemText(hDlg, DID_SELWINPARENT,
                           LoadResourceString(IDS_NOPARENT));
        }

        GetWindowRect(hwnd, &rc);
        wsprintf(szTemp, "(%d,%d)-(%d,%d) %dx%d", rc,
            rc.right-rc.left, rc.bottom-rc.top);
        SetDlgItemText(hDlg, DID_SELWINRECT, szTemp);

        if (dwStyle & WS_POPUP)
            wsprintf (szTemp, "%08lX: WS_POPUP", dwStyle);
        else if (dwStyle & WS_CHILD)
            wsprintf (szTemp, "%08lX: WS_CHILD, ID: %lX", dwStyle,
            GetWindowLong(hwnd, GWL_ID));
        else if (dwStyle & WS_ICONIC)
            wsprintf (szTemp, "%08lX: WS_ICONIC", dwStyle);
        else
            wsprintf (szTemp, "%08lX: WS_OVERLAPPED", dwStyle);

        SetDlgItemText(hDlg, DID_SELWINSTYLE, szTemp);
    }
    else
    {
        TCHAR lpBuf[256];
        LoadString(GetModuleHandle(NULL), IDS_UNDEFINED, lpBuf, sizeof(lpBuf));
        SetDlgItemText(hDlg, DID_SELWINWINDOW, lpBuf);
        SetDlgItemText(hDlg, DID_SELWINCLASS,  lpBuf);
        SetDlgItemText(hDlg, DID_SELWINPARENT, lpBuf);
        SetDlgItemText(hDlg, DID_SELWINRECT,   lpBuf);
        SetDlgItemText(hDlg, DID_SELWINSTYLE,  lpBuf);
    }
}

/*****************************************************************************\
* SelectWindowEnableFields
*
* Enables/disables the different fields in the Select Window dialog
* based on whether the user wants to spy on all windows or individually
* select one.
*
* Arguments:
*   HWND hwnd    - Dialog window handle.
*   BOOL fEnable - TRUE to enable the fields, FALSE to disable them.
*
* Returns:
*   VOID
\*****************************************************************************/

PRIVATE VOID
SelectWindowEnableFields(
    HWND hwnd,
    BOOL fEnable
    )
{
    EnableWindow(GetDlgItem(hwnd, DID_SELWINLIST), fEnable);
    EnableWindow(GetDlgItem(hwnd, DID_SELWINWINDOW), fEnable);
    EnableWindow(GetDlgItem(hwnd, DID_SELWINCLASS), fEnable);
    EnableWindow(GetDlgItem(hwnd, DID_SELWINPARENT), fEnable);
    EnableWindow(GetDlgItem(hwnd, DID_SELWINRECT), fEnable);
    EnableWindow(GetDlgItem(hwnd, DID_SELWINSTYLE), fEnable);
}

/*****************************************************************************\
* OutputDlgProc
*
* Dialog proc for the Output dialog.
*
* Arguments:
*   HWND hwnd - handle to the output dialog
*   UINT msg - message sent to output dialog
*   WPARAM wParam - message parameter.
*   LPARAM lParam - message parameter.
*
* Returns:
*   The value that the dialog proc should return, based on the processing
*   of the specific WM_COMMAND message received.
*
\*****************************************************************************/

BOOL CALLBACK
OutputDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch (msg)
    {
        case WM_INITDIALOG:
            CheckDlgButton(hwnd, DID_OUTPUTWINDOW, gfOutputWin);
            CheckDlgButton(hwnd, DID_OUTPUTCOM1, gfOutputCom1);
            CheckDlgButton(hwnd, DID_OUTPUTFILE, gfOutputFile);

            SetDlgItemText(hwnd, DID_OUTPUTFILENAME, gszFile);
            SetDlgItemInt(hwnd, DID_OUTPUTLINES, gnLines, FALSE);

            return TRUE;

        case WM_COMMAND:
            return OutputCommand(hwnd, LOWORD(wParam), HIWORD(wParam));
    }

    return FALSE;
}

/*****************************************************************************\
* OutputCommand
*
* Handles the WM_COMMAND messages for the Output dialog.
*
* Arguments:
*   HWND hwnd       - Window handle of the dialog.
*   INT nCmd        - Command value.
*   INT nNotifyCode - The notify code.
*
* Returns:
*   The value that the dialog proc should return, based on the processing
*   of the specific WM_COMMAND message received.
*
\*****************************************************************************/

PRIVATE BOOL
OutputCommand(
    HWND hwnd,
    INT nCmd,
    INT nNotifyCode
    )
{
    HFILE fh;
    INT i;
    CHAR szTemp[MAXSTRING];

    switch (nCmd)
    {
        case IDOK:
            i = GetDlgItemInt(hwnd, DID_OUTPUTLINES, &i, FALSE);
            if (i != gnLines)
            {
                if ( i > 0 && i <= LINES_MAX)
                {
                    gnLines = i;
                    MyCreatePrintfWin(ghwndSpyApp);
                }
                else
                {
                    Message(MB_OK | MB_ICONEXCLAMATION,
                        LoadResourceString(IDS_ERROR_WND_LINE), LINES_MAX);
                    SetFocus(GetDlgItem(hwnd, DID_OUTPUTLINES));
                    break;
                }
            }

            gfOutputWin = IsDlgButtonChecked(hwnd, DID_OUTPUTWINDOW);
            gfOutputCom1 = IsDlgButtonChecked(hwnd, DID_OUTPUTCOM1);
            gfOutputFile = IsDlgButtonChecked(hwnd, DID_OUTPUTFILE);

            if (gfOutputFile)
            {
                GetDlgItemText(hwnd, DID_OUTPUTFILENAME, szTemp, MAXSTRING);
                /*
                 * If they changed the filename, or the file is not open
                 * then open it.
                 */
                if (lstrcmp(gszFile, szTemp) != 0 || gfhFile == 0)
                {
                    fh = _lcreat(szTemp, 0);
                    if (fh == (HFILE)(-1))
                    {
                        if (Message(MB_OKCANCEL | MB_ICONEXCLAMATION,
                            LoadResourceString(IDS_ERROR_CANT_OPEN_FILE), szTemp) == IDCANCEL)
                        {
                            EndDialog(hwnd, FALSE);
                        }

                        return TRUE;
                    }

                    lstrcpy(gszFile, szTemp);
                    if (gfhFile)
                        _lclose(gfhFile);
                    gfhFile = fh;
                }
            }
            else
            {
                if (gfhFile)
                {
                    _lclose(gfhFile);
                    gfhFile = 0;
                }
            }

            if (gfOutputCom1)
            {
                if (gfhCom1 != INVALID_HANDLE_VALUE)
                    CloseHandle(gfhCom1);

                gfhCom1 = CreateFile(
                        "com1",
                        GENERIC_WRITE,
                        0,                    // exclusive access
                        NULL,                 // no security attrs
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);
                if (gfhCom1 == INVALID_HANDLE_VALUE)
                {
                    if (Message(MB_OKCANCEL | MB_ICONEXCLAMATION,
                        LoadResourceString(IDS_ERROR_CANT_OPEN_COM1)) == IDCANCEL)
                    {
                        EndDialog(hwnd, FALSE);
                    }

                    return TRUE;
                }
            }
            else
            {
                if (gfhCom1 != INVALID_HANDLE_VALUE)
                {
                    CloseHandle(gfhCom1);
                }
            }

            EndDialog(hwnd, TRUE);
            return TRUE;

        case IDCANCEL:
            EndDialog(hwnd, FALSE);
            return TRUE;
    }

    return FALSE;
}

/*****************************************************************************\
* SelectFont
*
* Allows the user to select a new font for the display.
*
* Arguments:
*    none
*
* Returns:
*    VOID
\*****************************************************************************/

VOID
SelectFont(
    VOID
    )
{
    CHOOSEFONT cf;
    LOGFONT lf;
    HFONT hfontNew;

    GetObject(ghfontPrintf, sizeof(LOGFONT), (LPVOID)&lf);

    cf.lStructSize = sizeof(cf);
    cf.hwndOwner = ghwndSpyApp;
    cf.hDC = NULL;
    cf.lpLogFont = &lf;
    cf.iPointSize = 0;
    cf.Flags = CF_ANSIONLY | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT
        | CF_SCREENFONTS;
    cf.rgbColors = 0;
    cf.lCustData = 0;
    cf.lpfnHook = NULL;
    cf.lpTemplateName = NULL;
    cf.hInstance = NULL;
    cf.lpszStyle = NULL;
    cf.nFontType = 0;
    cf.nSizeMin = 0;
    cf.nSizeMax = 0;

    if (ChooseFont(&cf))
    {
        if (hfontNew = CreateFontIndirect(&lf))
        {
            SetPrintfFont(ghwndPrintf, hfontNew);
            ghfontPrintf = hfontNew;
        }
    }
}

/*****************************************************************************\
* MessagesDlgProc
*
* Dialog proc for the Messages dialog.  This dialog allows the user
* to select which messages they want to spy on.
*
* Arguments:
*    HWND hwnd - handle to the dialog window.
*    UINT msg - message to the window
*    WPARAM wParam - message parameter
*    LPARAM lParam - message parameter
*
* Returns:
*   The value that the dialog proc should return, based on the processing
*   of the specific WM_COMMAND message received.
\*****************************************************************************/

BOOL CALLBACK
MessagesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch (msg)
    {
        case WM_INITDIALOG:
            MessagesInit(hwnd);
            return TRUE;

        case WM_COMMAND:
            return MessagesCommand(hwnd, LOWORD(wParam), HIWORD(wParam));
    }

    return FALSE;
}

/*****************************************************************************\
* MessagesInit
*
* Initializes the messages dialog.
*
* Arguments:
*   HWND hwnd - Dialog window handle.
*
* Returns:
*   VOID
\*****************************************************************************/

PRIVATE VOID
MessagesInit(
    HWND hwnd
    )
{
    HWND hwndList;
    INT i;
    INT j;
    INT iSel;
    PMSGDESC pmd;

    for (j = 0; j < gcMsgGroups; j++)
        gaMsgGroup[j].cUseCount = 0;

    hwndList = GetDlgItem(hwnd, DID_MSGSLIST);

    for (i = 0, pmd = gaMsgs; i < gcMessages; i++, pmd++)
    {
        iSel = (INT)SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)pmd->pszMsg);
        SendMessage(hwndList, LB_SETITEMDATA, iSel, (LPARAM)pmd);

        if (pmd->Flags & MTF_SELECTED)
        {
            SendMessage(hwndList, LB_SETSEL, TRUE, iSel);

            for (j = 0; j < gcMsgGroups; j++)
            {
                if (gaMsgGroup[j].flMask & pmd->Flags)
                    gaMsgGroup[j].cUseCount++;
            }
        }
    }

    //
    // Set the selection rectangle to the first item in the listbox.

//
    SendMessage(hwndList, LB_SETCARETINDEX, 0, FALSE);

    //
    // Loop through all the message groups.
    //
    for (j = 0; j < gcMsgGroups; j++)
    {
        //
        // Is at least one message in the group selected?
        //
        if (gaMsgGroup[j].cUseCount)
        {
            //
            // Check the corresponding checkbox.  If all messages
            // in the group are selected, the checkbox is checked.
            // If only some are selected, the checkbox is set to
            // grayed (3-state).
            //
            CheckDlgButton(hwnd, gaMsgGroup[j].idCheckBox,
                (gaMsgGroup[j].cUseCount == gaMsgGroup[j].cMsgs) ? 1 : 2);
        }
    }

    if (gfMsgsUser)
        CheckDlgButton(hwnd, DID_MSGSUSER, 1);

    if (gfMsgsUnknown)
        CheckDlgButton(hwnd, DID_MSGSUNKNOWN, 1);

    gcItemsSave = SendMessage(hwndList, LB_GETSELITEMS,
        gcMessages, (LPARAM)gaiSelected);
}

/*****************************************************************************\
* MessagesCommand
*
* Handles the WM_COMMAND messages for the Messages dialog.
*
* Arguments:
*   HWND hwnd       - Window handle of the dialog.
*   INT nCmd        - Command value.
*   INT nNotifyCode - The notify code.
*
* Returns:
*   The value that the dialog proc should return, based on the processing
*   of the specific WM_COMMAND message received.
*
\*****************************************************************************/

PRIVATE BOOL
MessagesCommand(
    HWND hwnd,
    INT nCmd,
    INT nNotifyCode
    )
{
    INT i;
    INT j;
    PMSGGROUP pmg;
    PMSGDESC pmd;
    BOOL fChecked;
    HWND hwndList;
    INT cItems;
    BOOL fSel;
    INT iSel;
    INT cSelItemsMax;
    INT iTopIndex;

    switch (nCmd)
    {
        case DID_MSGSLIST:
            if (nNotifyCode == LBN_SELCHANGE)
            {
                hwndList = GetDlgItem(hwnd, DID_MSGSLIST);
                cItems = SendMessage(hwndList, LB_GETSELITEMS,
                    gcMessages, (LPARAM)gaiSelected2);
                if (cItems == gcItemsSave)
                {
                    //
                    // Nothing changed except for the selection
                    // rectangle moving.  We are done.
                    //
                    break;
                }

                if (cItems > gcItemsSave)
                {
                    //
                    // A message was selected.  Look for it.
                    //
                    for (i = 0; i < gcItemsSave &&
                        gaiSelected[i] == gaiSelected2[i]; i++)
                        ;

                    iSel = gaiSelected2[i];
                    fSel = TRUE;
                }
                else
                {
                    //
                    // A message was unselected.  Look for it.
                    //
                    for (i = 0; i < cItems &&
                        gaiSelected[i] == gaiSelected2[i]; i++)
                        ;

                    iSel = gaiSelected[i];
                    fSel = FALSE;
                }

                //
                // Get the currently selected item.  It was either
                // just turned on or off.
                //
                pmd = (PMSGDESC)SendMessage(hwndList, LB_GETITEMDATA, iSel, 0);

                //
                // Loop through the message groups and update the use
                // counts for all groups that contain this message.
                //
                for (i = 0; i < gcMsgGroups; i++)
                {
                    if (pmd->Flags & gaMsgGroup[i].flMask)
                    {
                        gaMsgGroup[i].cUseCount += fSel ? 1 : -1;
                    }
                }

                //
                // Be sure that the checkboxes reflect the updated
                // status of the message group use counts.
                //
                MessagesUpdateCheckBoxes(hwnd);

                //
                // Save away the new selected item array.
                //
                cSelItemsMax = max(cItems, gcItemsSave);
                for (i = 0; i < cSelItemsMax; i++)
                {
                    gaiSelected[i] = gaiSelected2[i];
                }

                gcItemsSave = cItems;
            }

            break;

        case DID_MSGSALL:
            hwndList = GetDlgItem(hwnd, DID_MSGSLIST);
            SendMessage(hwndList, LB_SETSEL, TRUE, (LPARAM)-1);

            for (i = 0; i < gcMsgGroups; i++)
            {
                gaMsgGroup[i].cUseCount = gaMsgGroup[i].cMsgs;
                CheckDlgButton(hwnd, gaMsgGroup[i].idCheckBox, 1);
            }

            gcItemsSave = SendMessage(hwndList, LB_GETSELITEMS, gcMessages,
                (LPARAM)gaiSelected);

            CheckDlgButton(hwnd, DID_MSGSUSER, 1);
            CheckDlgButton(hwnd, DID_MSGSUNKNOWN, 1);

            break;

        case DID_MSGSNONE:
            hwndList = GetDlgItem(hwnd, DID_MSGSLIST);
            SendMessage(hwndList, LB_SETSEL, FALSE, (LPARAM)-1);

            for (i = 0; i < gcMsgGroups; i++)
            {
                gaMsgGroup[i].cUseCount = 0;
                CheckDlgButton(hwnd, gaMsgGroup[i].idCheckBox, 0);
            }

            gcItemsSave = 0;

            CheckDlgButton(hwnd, DID_MSGSUSER, 0);
            CheckDlgButton(hwnd, DID_MSGSUNKNOWN, 0);

            break;

        case DID_MSGSDDE:
        case DID_MSGSCLIP:
        case DID_MSGSMOUSE:
        case DID_MSGSNC:
        case DID_MSGSKEYBD:
#ifdef FE_IME
        case DID_MSGSIME:
#endif
        case DID_MSGSBM:
        case DID_MSGSCB:
        case DID_MSGSEM:
        case DID_MSGSLB:
        case DID_MSGSSTM:
            for (i = 0; i < gcMsgGroups; i++)
            {
                if (gaMsgGroup[i].idCheckBox == nCmd)
                {
                    pmg = &gaMsgGroup[i];
                    break;
                }
            }

            fChecked = IsDlgButtonChecked(hwnd, pmg->idCheckBox);
            if (fChecked == 1)
                fChecked = FALSE;
            else
                fChecked = TRUE;

            hwndList = GetDlgItem(hwnd, DID_MSGSLIST);

            if (fChecked)
            {
                SendMessage(hwndList, WM_SETREDRAW, FALSE, 0);
                iTopIndex = SendMessage(hwndList, LB_GETTOPINDEX, 0, 0);
            }

            //
            // Get the list of currently selected items.
            //
            cItems = SendMessage(hwndList, LB_GETSELITEMS,
                gcMessages, (LPARAM)gaiSelected);

            //
            // Look for all the messages in this group.
            //
            for (i = 0, iSel = 0; i < gcMessages; i++)
            {
                pmd = (PMSGDESC)SendMessage(hwndList, LB_GETITEMDATA, i, 0);
                if (pmd->Flags & pmg->flMask)
                {
                    //
                    // Bump up through the list of selected items, looking
                    // to see if this item is currently selected.
                    //
                    for (fSel = FALSE; iSel < cItems &&
                        gaiSelected[iSel] <= i; iSel++)
                    {
                        //
                        // A match was found.  The item is selected.
                        //
                        if(gaiSelected[iSel] == i)
                        {
                            fSel = TRUE;
                            break;
                        }
                    }

                    //
                    // Is the current selection state of the item
                    // different from the desired selection state?
                    //
                    if (fSel != fChecked)
                    {
                        //
                        // Update the use counts of all groups that contain
                        // this message.
                        //
                        for (j = 0; j < gcMsgGroups; j++)
                        {
                            if (pmd->Flags & gaMsgGroup[j].flMask)
                            {
                                gaMsgGroup[j].cUseCount += fChecked ? 1 : -1;
                            }
                        }

                        //
                        // Select/deselect the message in the list box.
                        //
                        SendMessage(hwndList, LB_SETSEL, fChecked, i);
                    }
                }
            }

            //
            // Be sure that the checkboxes reflect the updated
            // status of the message group use counts.
            //
            MessagesUpdateCheckBoxes(hwnd);

            if (fChecked)
            {
                SendMessage(hwndList, LB_SETTOPINDEX, iTopIndex, 0);
                SendMessage(hwndList, WM_SETREDRAW, TRUE, 0);
                InvalidateRect(hwndList, NULL, FALSE);
            }

            gcItemsSave = SendMessage(hwndList, LB_GETSELITEMS,
                gcMessages, (LPARAM)gaiSelected);

            break;

        case IDOK:
            hwndList = GetDlgItem(hwnd, DID_MSGSLIST);
            cItems = SendMessage(hwndList, LB_GETSELITEMS,
                gcMessages, (LPARAM)gaiSelected);

            //
            // Unselect all messages.
            //
            for (i = 0; i < gcMessages; i++)
                gaMsgs[i].Flags &= ~MTF_SELECTED;

            //
            // Mark all the messages that are selected.
            //
            for (i = 0; i < cItems; i++)
            {
                pmd = (PMSGDESC)SendMessage(hwndList, LB_GETITEMDATA,
                    gaiSelected[i], 0);
                pmd->Flags |= MTF_SELECTED;
            }

            if (IsDlgButtonChecked(hwnd, DID_MSGSUSER))
                gfMsgsUser = TRUE;
            else
                gfMsgsUser = FALSE;

            if (IsDlgButtonChecked(hwnd, DID_MSGSUNKNOWN))
                gfMsgsUnknown = TRUE;
            else
                gfMsgsUnknown = FALSE;

#if 0  //Debug code!
for (i = 0; i < gcMsgGroups; i++)
{
    iSel = 0;
    for (j = 0; j < cItems; j++)
    {
        pmd = (PMSGDESC)SendMessage(hwndList, LB_GETITEMDATA,
            gaiSelected[j], 0);
        if (pmd->Flags & gaMsgGroup[i].flMask)
            iSel++;
    }

    if (iSel != gaMsgGroup[i].cUseCount)
    {
        DbgPrintf("Use counts are wrong!!!");
        for (j = 0; j < gcMsgGroups; j++)
        {
            DbgPrintf("cMsgs:%d Use:%d", gaMsgGroup[j].cMsgs, gaMsgGroup[j].cUseCount);
        }
    }
}
#endif // end debug code

            EndDialog(hwnd, IDOK);
            return TRUE;

        case IDCANCEL:
            EndDialog(hwnd, IDCANCEL);
            return TRUE;
    }

    return FALSE;
}

/*****************************************************************************\
* MessagesUpdateCheckBoxes
*
* Updates the message group checkboxes in the Messages dialog.
* This routine should be called when the use counts in the
* message group table are changed, so that the state of the
* checkboxes will get updated also.
*
* Arguments:
*   HWND hwnd - Dialog window handle.
*
* Returns:
*   VOID
\*****************************************************************************/

PRIVATE VOID
MessagesUpdateCheckBoxes(
    HWND hwnd
    )
{
    INT i;
    INT fState;

    for (i = 0; i < gcMsgGroups; i++)
    {
        if (gaMsgGroup[i].cUseCount == gaMsgGroup[i].cMsgs)
            fState = 1;
        else if (gaMsgGroup[i].cUseCount == 0)
            fState = 0;
        else
            fState = 2;

        CheckDlgButton(hwnd, gaMsgGroup[i].idCheckBox, fState);
    }
}


DIALOGS.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 DID_ABOUT                   100

#define DID_SELECTWINDOW            200
#define DID_SELWINLIST              201
#define DID_SELWINWINDOW            202
#define DID_SELWINCLASS             203
#define DID_SELWINPARENT            204
#define DID_SELWINRECT              205
#define DID_SELWINSTYLE             206
#define DID_SELWINALLWINDOWS        207

#define DID_OUTPUT                  300
#define DID_OUTPUTWINDOW            301
#define DID_OUTPUTLINES             302
#define DID_OUTPUTCOM1              303
#define DID_OUTPUTFILE              304
#define DID_OUTPUTFILENAME          305

#define DID_MESSAGES                400
#define DID_MSGSLIST                401
#define DID_MSGSALL                 402
#define DID_MSGSNONE                403
#define DID_MSGSDDE                 404
#define DID_MSGSCLIP                405
#define DID_MSGSMOUSE               406
#define DID_MSGSNC                  407
#define DID_MSGSKEYBD               408
#define DID_MSGSBM                  409
#define DID_MSGSCB                  410
#define DID_MSGSEM                  411
#define DID_MSGSLB                  412
#define DID_MSGSSTM                 413
#define DID_MSGSUSER                414
#define DID_MSGSUNKNOWN             415
#define DID_MSGSIME                 416


HOOK.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: hook.c
*
*   Contains the message hooking functions for the Windows debugging Spy
*   SDK applet.
*
* Functions:
*
*   CreateHookThread()
*   HookMain()
*   HookWndProc()
*   SetSpyHook()
*   SetWindowToSpyOn()
*   DbgPrintf()
*
* Comments:
*
\*****************************************************************************/

#include "spy.h"

PRIVATE BOOL gfProcessHooks = TRUE;

/*****************************************************************************\
* CreateHookThread
*
*    Creates the hook thread.
*
* Arguments:
*    none
*
* Returns:
*    BOOL - Whether or not hook create succeeeded.
\*****************************************************************************/

BOOL
CreateHookThread(
    VOID
    )
{
    WNDCLASS wc;
    DWORD Id;

    //
    // Register a class for the hook stuff to forward its messages to.
    //
    wc.hCursor        = NULL;    // this window never shown, so no
    wc.hIcon          = NULL;    // cursor or icon are necessary
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = HOOKWINDOWCLASS;
    wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hInstance      = ghInst;
    wc.style          = 0;
    wc.lpfnWndProc    = HookWndProc;
    wc.cbWndExtra     = sizeof(HWND) + sizeof(HWND);
    wc.cbClsExtra     = 0;

    if (!RegisterClass(&wc))
        return FALSE;

    //
    // Now create another thread to handle the new queue
    //
    if (!(ghHookThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)HookMain,
        0L, STANDARD_RIGHTS_REQUIRED, &Id)))
        return FALSE;

    return TRUE;
}

/*****************************************************************************\
* HookMain
*
*    Main window procedure for the Hook window
*
\*****************************************************************************/

DWORD
HookMain(
    LPVOID lpv
    )
{
    MSG msg;

    //
    // Create a hidden window for all to find, but not to see
    //
    ghwndSpyHook = CreateWindow(HOOKWINDOWCLASS, HOOKWINDOWNAME,
        WS_OVERLAPPEDWINDOW,
        0, 0, 0, 0,
        (HWND) NULL,        /* no parent */
        (HMENU) NULL,       /* use class menu */
        (HANDLE) ghInst,    /* handle to window instance */
        (LPSTR) NULL        /* no params to pass on */
        );

    if (!ghwndSpyHook)
    {
        ExitThread(0);
    }

    SetWindowToSpyOn(HWND_ALL);

    //
    // Polling forwarded messages from hook's event queue
    //
    while (IsWindow(ghwndSpyHook) && GetMessage(&msg, ghwndSpyHook, 0, 0))
    {
        if (gfProcessHooks)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    ghwndSpyHook = NULL;

    return 0; // not reached
}

/*****************************************************************************\
* HookWndProc
*
* Window procedure for the spy hook.
*
* Arguments:
*   HWND hwnd - handle to the hook window.
*   UINT msg - message sent to hook window.
*   WPARAM wParam - message parameter.
*   LPARAM lParam - message parameter.
*
* Returns:
*   The value that the proc should return, based on the processing
*   of the specific WM_COMMAND message received.
\*****************************************************************************/

LRESULT CALLBACK
HookWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch (msg)
    {
        //
        // New message for Win32 - allows the application to pass data to another application.
        //
        case WM_COPYDATA:
            {
                MSG msgT;

                msgT.hwnd = (HWND)wParam;
                msgT.message = ((PCOPYDATASTRUCT)lParam)->dwData;
                msgT.wParam = ((PSPYMSGDATA)((PCOPYDATASTRUCT)lParam)->lpData)->wParam;
                msgT.lParam = ((PSPYMSGDATA)((PCOPYDATASTRUCT)lParam)->lpData)->lParam;
//DbgPrintf("S Received Message hwnd:%8.8x msg:%d", msgT.hwnd, msgT.message);
                PrintMsg(&msgT);
//DbgPrintf("S Printed Message hwnd:%8.8x msg:%d", msgT.hwnd, msgT.message);
            }

            return TRUE;

        case WM_CREATE:
            //
            // Initialize the second HWND in the window words to be the
            // window handle of the spy app.  This will be queried by
            // the hook DLL.
            //
            SetWindowLong(hwnd, sizeof(HWND), (LONG)ghwndSpyApp);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        case WM_NCDESTROY:
            gfProcessHooks = FALSE;
            break;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

/*****************************************************************************\
* SetSpyHook
*
* Sets the windows hooks used to trap the messages.  After this
* is called with a TRUE for fSet, the messages will start flowing
* through the hook DLL.
*
* Arguments:
*   BOOL fSet - TRUE to hook, FALSE to unhook.
*
* Returns:
*   TRUE if successful.
*
\*****************************************************************************/

BOOL
SetSpyHook(
    BOOL fSet
    )
{
    static HHOOK hhkGetMessage = NULL;
    static HHOOK hhkCallWndProc = NULL;
    static HANDLE hmodHook;

    if (fSet)
    {
        if (!hmodHook)
        {
            if (!(hmodHook = LoadLibrary("hook")))
            {
                Message(MB_OK | MB_ICONEXCLAMATION,
                        LoadResourceString(IDS_ERROR_CANT_LOAD_DLL));
                return FALSE;
            }
        }

        if (!hhkGetMessage)
        {
            if (!(hhkGetMessage = SetWindowsHookEx(WH_GETMESSAGE,
                (HOOKPROC)GetProcAddress(hmodHook, "SpyGetMsgProc"), hmodHook, 0)))
            {
                return FALSE;
            }
        }

        if (!hhkCallWndProc)
        {
            if (!(hhkCallWndProc = SetWindowsHookEx(WH_CALLWNDPROC,
                (HOOKPROC)GetProcAddress(hmodHook, "SpyCallWndProc"), hmodHook, 0)))
            {
                UnhookWindowsHookEx(hhkGetMessage);
                return FALSE;
            }
        }
    }
    else
    {
        if (hhkGetMessage)
        {
            UnhookWindowsHookEx(hhkGetMessage);
            hhkGetMessage = NULL;
        }

        if (hhkCallWndProc)
        {
            UnhookWindowsHookEx(hhkCallWndProc);
            hhkCallWndProc = NULL;
        }
    }

    return TRUE;
}

/*****************************************************************************\
* SetWindowToSpyOn
*
* Sets the current window to spy on to the specified hwnd.  This hwnd can
* also be the special value HWND_ALL to specify that all windows should
* be spy'd upon.
*
* Arguments:
*   HWND hwndSpyingOn - Window handle to spy on, or HWND_ALL for all windows.
*
* Returns:
*   VOID
\*****************************************************************************/

VOID
SetWindowToSpyOn(
    HWND hwndSpyingOn
    )
{
    ghwndSpyingOn = hwndSpyingOn;
    gfSpyAll = (ghwndSpyingOn == HWND_ALL) ? TRUE : FALSE;
    SetWindowLong(ghwndSpyHook, 0, (LONG)ghwndSpyingOn);
    SetSpyCaption();
}

#ifdef DBG
/****************************************************************************
* DBGprintf
*
* This debugging function prints out a string to the debug output.
* An optional set of substitutional parameters can be specified,
* and the final output will be the processed result of these combined
* with the format string, just like printf.  A newline is always
* output after every call to this function.
*
* Arguments:
*   LPTSTR fmt - Format string (printf style).
*   ...        - Variable number of arguments.
*
* Returns:
*    VOID
****************************************************************************/

VOID DbgPrintf(
    LPTSTR fmt,
    ...
    )
{
    va_list marker;
    TCHAR szBuf[256];

    va_start(marker, fmt);
    wvsprintf(szBuf, fmt, marker);
    va_end(marker);

    OutputDebugString(szBuf);
    OutputDebugString(TEXT("\r\n"));
}
#endif


MISC.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: misc.c
*
*   Contains miscellaneous routines for the Windows debugging Spy SDK applet.
*
* Functions:
*
*   ReadRegistry()
*   WriteRegistry()
*   Message()
*   SetSpyCaption()
*   GetWindowName()
*   StripExtension()
*
* Comments:
*
\*****************************************************************************/

#include "spy.h"
#include <string.h>

//
// Registry flags for the "Flags" value.
//
#define REGFLAG_OUTPUTWIN           0x00000001
#define REGFLAG_OUTPUTCOM1          0x00000002
#define REGFLAG_OUTPUTFILE          0x00000004
#define REGFLAG_MSGSUSER            0x00000010
#define REGFLAG_MSGSUNKNOWN         0x00000020

PRIVATE HKEY ghkeySpy = NULL;
PRIVATE CHAR gszSpyAppKey[] = "Software\\Microsoft\\Spy";
PRIVATE CHAR gszKeyPosition[] = "Position";
PRIVATE CHAR gszKeyFont[] = "Font";
PRIVATE CHAR gszKeyMessages[] = "Messages";
PRIVATE CHAR gszKeyFileName[] = "FileName";
PRIVATE CHAR gszKeyLines[] = "Lines";
PRIVATE CHAR gszKeyFlags[] = "Flags";
PRIVATE CHAR gszDefFileName[] = "spy.log";
PRIVATE BYTE BitTable[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };

PRIVATE VOID GetWindowName(HWND hwnd, PSTR sz);
PRIVATE LPSTR StripExtension(LPSTR pszFileName);

/*****************************************************************************\
* ReadRegistry
*
* Opens (creates if necessary) the registry key for spy preferences and then
* reads the last saved values.
*
* Arguments:
*    none
*
* Returns:
*    VOID
\*****************************************************************************/

VOID
ReadRegistry(
    VOID
    )
{
    LOGFONT lf;
    BYTE abMsgs[128];
    DWORD fFlags;
    HDC hdc;
    INT i;
    DWORD dwType;
    DWORD cbData;

    RegCreateKey(HKEY_CURRENT_USER, gszSpyAppKey, &ghkeySpy);

    cbData = gwndpl.length = sizeof(gwndpl);
    if (!ghkeySpy || RegQueryValueEx(ghkeySpy, gszKeyPosition, NULL, &dwType,
        (LPVOID)&gwndpl, &cbData) != ERROR_SUCCESS)
    {
        gwndpl.length = 0;
    }

    //
    // If the spy process is killed while the registry key is open, the
    //  position data can be indeterminant...  Here we catch the case that
    //  the reg. data is not good, and we set sensible defaults.
    //

    if (gwndpl.length != sizeof(gwndpl))
    {

        gwndpl.length = sizeof(gwndpl);
        gwndpl.flags = 0;
        gwndpl.showCmd = SW_SHOWNORMAL;
        gwndpl.ptMinPosition.x = 0;
        gwndpl.ptMinPosition.y = 0;
        gwndpl.ptMaxPosition.x = 0;
        gwndpl.ptMaxPosition.y = 0;
        gwndpl.rcNormalPosition.left = 10;
        gwndpl.rcNormalPosition.top = 10;
        gwndpl.rcNormalPosition.right =
            10 + (GetSystemMetrics(SM_CXSCREEN) / 3);
        gwndpl.rcNormalPosition.bottom =
            10 + (GetSystemMetrics(SM_CYSCREEN) / 3);
    }

    cbData = sizeof(lf);
    if (!ghkeySpy || RegQueryValueEx(ghkeySpy, gszKeyFont, NULL, &dwType,
        (LPVOID)&lf, &cbData) != ERROR_SUCCESS)
    {
        hdc = GetDC(NULL);
        GetObject(GetStockObject(SYSTEM_FONT), sizeof(lf), &lf);
        ReleaseDC(NULL, hdc);
    }

    ghfontPrintf = CreateFontIndirect(&lf);

    cbData = sizeof(abMsgs);
    if (!ghkeySpy || RegQueryValueEx(ghkeySpy, gszKeyMessages, NULL, &dwType,
        (LPVOID)abMsgs, &cbData) != ERROR_SUCCESS)
    {
        //
        // Select all messages by default
        //
        for (i = 0; i < gcMessages; i++)
        {
            gaMsgs[i].Flags |= MTF_SELECTED;
        }
    }
    else
    {
        for (i = 0; i < gcMessages; i++)
        {
            if (abMsgs[gaMsgs[i].msg >> 3] & BitTable[gaMsgs[i].msg & 0x07])
                gaMsgs[i].Flags |= MTF_SELECTED;
        }
    }

    cbData = MAXSTRING * sizeof(TCHAR);
    if (!ghkeySpy || RegQueryValueEx(ghkeySpy, gszKeyFileName, NULL, &dwType,
        (LPVOID)gszFile, &cbData) != ERROR_SUCCESS)
    {
        lstrcpy(gszFile, gszDefFileName);
    }

    cbData = sizeof(DWORD);
    if (!ghkeySpy || RegQueryValueEx(ghkeySpy, gszKeyLines, NULL, &dwType,
        (LPVOID)&gnLines, &cbData) != ERROR_SUCCESS ||
        gnLines > LINES_MAX)
    {
        gnLines = LINES_MAX;
    }

    cbData = sizeof(DWORD);
    if (!ghkeySpy || RegQueryValueEx(ghkeySpy, gszKeyFlags, NULL, &dwType,
        (LPVOID)&fFlags, &cbData) != ERROR_SUCCESS)
    {
        gfOutputWin = TRUE;
        gfOutputCom1 = FALSE;
        gfOutputFile = FALSE;
        gfMsgsUser = TRUE;
        gfMsgsUnknown = TRUE;
    }
    else
    {
        if (fFlags & REGFLAG_OUTPUTWIN)
            gfOutputWin = TRUE;

        if (fFlags & REGFLAG_OUTPUTCOM1)
            gfOutputCom1 = TRUE;

        if (fFlags & REGFLAG_OUTPUTFILE)
            gfOutputFile = TRUE;

        if (fFlags & REGFLAG_MSGSUSER)
            gfMsgsUser = TRUE;

        if (fFlags & REGFLAG_MSGSUNKNOWN)
            gfMsgsUnknown = TRUE;
    }

    if (gfOutputFile)
    {
        gfhFile = _lcreat(gszFile, 0);
        if (gfhFile == (HFILE)-1)        //BUGBUG put up a message here.
            gfhFile = 0;
    }

    if (gfOutputCom1)
    {
        gfhCom1 = CreateFile(
                "com1",
                GENERIC_WRITE,
                0,                    // exclusive access
                NULL,                 // no security attrs
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);
    }
}

/*****************************************************************************\
* WriteRegistry
*
* Writes out preference data to the registry when the app exits, then
* closes the registry key.
*
* Arguments:
*    none
*
* Returns:
*    VOID
\*****************************************************************************/

VOID
WriteRegistry(
    VOID
    )
{
    LOGFONT lf;
    BYTE abMsgs[128];
    INT i;
    DWORD fFlags;
    WINDOWPLACEMENT wndpl;

    if (ghkeySpy)
    {
        wndpl.length = sizeof(WINDOWPLACEMENT);
        GetWindowPlacement(ghwndSpyApp, &wndpl);
        RegSetValueEx(ghkeySpy, gszKeyPosition, 0, REG_BINARY,
            (LPBYTE)&wndpl, sizeof(wndpl));

        GetObject(ghfontPrintf, sizeof(lf), &lf);
        RegSetValueEx(ghkeySpy, gszKeyFont, 0, REG_BINARY,
            (LPBYTE)&lf, sizeof(lf));

        memset(abMsgs, 0, sizeof(abMsgs));
        for (i = 0; i < gcMessages; i++)
        {
            if (gaMsgs[i].Flags & MTF_SELECTED)
                abMsgs[gaMsgs[i].msg >> 3] |= BitTable[gaMsgs[i].msg & 0x07];
        }

        RegSetValueEx(ghkeySpy, gszKeyMessages, 0, REG_BINARY,
            (LPBYTE)&abMsgs, sizeof(abMsgs));

        RegSetValueEx(ghkeySpy, gszKeyFileName, 0, REG_SZ,
            (LPBYTE)gszFile, (lstrlen(gszFile) + 1) * sizeof(TCHAR));

        RegSetValueEx(ghkeySpy, gszKeyLines, 0, REG_DWORD,
            (LPBYTE)&gnLines, sizeof(DWORD));

        fFlags = 0;
        if (gfOutputWin)
            fFlags |= REGFLAG_OUTPUTWIN;

        if (gfOutputCom1)
            fFlags |= REGFLAG_OUTPUTCOM1;

        if (gfOutputFile)
            fFlags |= REGFLAG_OUTPUTFILE;

        if (gfMsgsUser)
            fFlags |= REGFLAG_MSGSUSER;

        if (gfMsgsUnknown)
            fFlags |= REGFLAG_MSGSUNKNOWN;

        RegSetValueEx(ghkeySpy, gszKeyFlags, 0, REG_DWORD,
            (LPBYTE)&fFlags, sizeof(DWORD));

        RegCloseKey(ghkeySpy);
    }
}

/*****************************************************************************\
* Message
*
* Puts up a message box.
*
* Arguments:
*   UINT fuStyle    - Flags for MessageBox (MB_YESNOCANCEL, etc).
*   LPSTR pszFormat - Format string for the message.
*
* Returns:
*   Whatever MessageBox returns.
*
\*****************************************************************************/

INT
Message(
    UINT fuStyle,
    LPSTR pszFormat,
    ...
    )
{
    va_list marker;
    INT RetCode;
    TCHAR szT[MAXSTRING];

    va_start(marker, pszFormat);
    wvsprintf(szT, pszFormat, marker);
    RetCode = MessageBox(ghwndSpyApp, szT, gszWindowName, fuStyle|MB_TASKMODAL);
    va_end(marker);

    return RetCode;
}

/*****************************************************************************\
* SetSpyCaption
*
* This routine sets the Spy app's caption bar to display info on the window
* that is currently being spy'ed upon.
*
* Arguments:
*    none
*
* Returns:
*    VOID
\*****************************************************************************/

VOID
SetSpyCaption(
    VOID
    )
{
    CHAR szText[MAXSTRING];
    CHAR szTemp[MAXSTRING];

    if (ghwndSpyingOn != NULL && ghwndSpyingOn != HWND_ALL)
    {
        GetWindowName(ghwndSpyingOn, szTemp);

        if (lstrlen(gszWindowName) + lstrlen(szTemp) + 3 > MAXSTRING)
            szTemp[MAXSTRING - 3 - lstrlen(szTemp)] = 0;

        if (gfSpyOn)
            wsprintf(szText, "%s - %s", gszWindowName, szTemp);
        else
            wsprintf(szText, "<%s - %s>", gszWindowName, szTemp);
    }
    else
    {
        lstrcpy(szText, gszWindowName);
    }

    SetWindowText(ghwndSpyApp, szText);
}

/*****************************************************************************\
* GetWindowName
*
* Builds the name of the window being spy'd on in the specified buffer.
* This will be something like "EXENAME!WindowText" or "EXENAME!Class".
*
* Arguments:
*
*    HWND hwnd - handle to the window being spy'd on.
*    PSTR pstr - pointer to string to return.
*
* Returns:
*    VOID
\*****************************************************************************/

PRIVATE VOID
GetWindowName(
    HWND hwnd,
    PSTR sz
    )
{
    PSTR szSave = sz;

    if (hwnd != NULL && IsWindow(hwnd))
    {
#if 0
        // THIS DOES NOT WORK ON NT SINCE HINST'S ARE NOT GLOBAL

        HINSTANCE hinst;

        SetLastError(0);
        hinst = (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE);
        GetLastError();

        /*
         * Get the module name
         */
#ifdef JAPAN
        //DBCS_FIX
        if (GetModuleFileName((HANDLE)GetWindowLong(hwnd, GWL_HINSTANCE),
            sz, MAXSTRING)) {
            //Probably this function will fail on NT
#else
        SetLastError(0);
        GetModuleFileName(hinst, sz, MAXSTRING);
        GetLastError();
#endif
        lstrcpy(sz, StripExtension(sz));

        sz += lstrlen(sz);
        *sz++ = '!';
#ifdef Japan
        }
#endif
        *sz = 0;

        GetWindowText(hwnd, sz, MAXSTRING - (sz - szSave));
#else // !0
        GetWindowText(hwnd, sz, MAXSTRING);
#endif

        /*
         * If the window has no caption string then use the Class name
         */
        if (*sz == 0)
            GetClassName(hwnd, sz, MAXSTRING - (sz - szSave));
    }
    else
    {
        *sz = 0;
    }
}

/*****************************************************************************\
* StripExtension
*
*   Strips the extension off of a filename.
*
* Arguments:
*   LPSTR pszFileName - File name to process.
*
* Returns:
*   Returns a pointer to the beginning of the filename.  The extension
*   will have been stripped off.
*
\*****************************************************************************/

PRIVATE LPSTR
StripExtension(
    LPSTR pszFileName
    )
{
    LPSTR p = pszFileName;

    while (*p)
        p++;

    while (p > pszFileName && *p != '\\' && *p != ':') {
        p = CharPrev(pszFileName, p);
        if (*p == '.') {
            *p = 0;
        }
    }
    if (*p == '\\' || *p == ':') {
        p++;
    }
    return p;
}

/*****************************************************************************\
* LoadResourceString
*
*   Loads a resource string from SPY and returns a pointer to the string.
*
* Arguments:
*   wId        - resource string id
*
* Returns:
*   Returns a pointer to the string.
*
\*****************************************************************************/
LPTSTR
LoadResourceString( UINT wId )
{
    static TCHAR lpBuf[1024];

    LoadString( GetModuleHandle(NULL), wId, lpBuf, sizeof(lpBuf) );

    return lpBuf;
}


SPY.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: spy.c
*
*   Main module for the Windows debugging Spy SDK applet.
*
* Functions:
*
*   WinMain()
*   SpyWndProc()
*   SpyInit()
*   PutOptions()
*   InitMenu()
*   SpyCommand()
*
* Comments:
*
\*****************************************************************************/

#include "spy.h"
#include <stdlib.h>

#define WM_EXECINSTANCE     (WM_USER+100)

/*
 * Macros to simplify working with menus.
 */
#define MyEnableMenuItem(hMenu, wIDEnableItem, fEnable) \
    EnableMenuItem((hMenu),(wIDEnableItem),(fEnable)?MF_ENABLED:MF_GRAYED)

#define MyCheckMenuItem(hMenu, wIDCheckItem, fCheck) \
    CheckMenuItem((hMenu),(wIDCheckItem),(fCheck)?MF_CHECKED:MF_UNCHECKED)

HANDLE ghInst;
HWND ghwndSpyApp;
HWND ghwndPrintf = NULL;
HANDLE ghHookThread = NULL;
HWND ghwndSpyHook = NULL;
HWND ghwndSpyingOn = NULL;              // The window we are spying on.
HFONT ghfontPrintf;
INT gnLines;
BOOL gfSpyOn = FALSE;
BOOL gfSpyAll;
BOOL gfOutputWin;
BOOL gfOutputCom1;
BOOL gfOutputFile;
HFILE gfhFile;
HANDLE gfhCom1;
CHAR gszFile[MAXSTRING];
INT gcxBorder;
INT gcyBorder;
BOOL gfMsgsUser;                        // TRUE to spy on all WM_USER messages.
BOOL gfMsgsUnknown;                     // TRUE to spy on all unknown msgs.
CHAR gszAppName[] = SPYAPPNAME;
UCHAR gszWindowName[40];
WINDOWPLACEMENT gwndpl;

PRIVATE HANDLE ghaccelTbl;              // Accelerator table handle.
PRIVATE CHAR gszSpyClassName[] = SPYCLASSNAME;

PRIVATE BOOL SpyInit(HANDLE hInstance, INT nCmdShow);
PRIVATE VOID PutOptions(VOID);
PRIVATE VOID InitMenu(HMENU hmenu);
PRIVATE LRESULT SpyCommand(HWND hwnd, INT nCmd, INT nNotifyCode);

/*****************************************************************************\
* WinMain
*
* Main entry point for the Spy app.
*
\*****************************************************************************/

INT WINAPI
WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    INT nCmdShow
    )
{
    MSG msg;

    if (!SpyInit(hInstance, nCmdShow))
        return FALSE;

    if (!CreateHookThread())
        goto closespy;

    /*
     * Polling messages from event queue
     */
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(ghwndSpyApp, ghaccelTbl, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

closespy:
    if (IsWindow(ghwndSpyApp))
    {
        if (DestroyWindow(ghwndSpyApp))
        {
            ghwndSpyApp = NULL;
        }
    }

    if (IsWindow(ghwndPrintf))
    {
        if (DestroyWindow(ghwndPrintf))
        {
            ghwndPrintf = NULL;
        }
    }

    return (INT)msg.wParam;
}

/*****************************************************************************\
* SpyInit
*
* Initializes the Spy application.
*
* Arguments:
*   HANDLE hInstance - handle to the instance of SPY.
*   INT nCmdShow - show the window?
*
* Returns:
*   TRUE if successful, FALSE otherwise.
*
\*****************************************************************************/

PRIVATE BOOL
SpyInit(
    HANDLE hInstance,
    INT nCmdShow
    )
{
    WNDCLASS wc;
    HWND hwndT;
    CHAR szClassName[40];
    BOOL bFoundPrevSpy = FALSE;
    INT i;
    INT j;

    ghInst = hInstance;

    /*
     * Loop through windows to find one of the spy class.
     */
    for (hwndT = GetWindow(GetDesktopWindow(), GW_CHILD); hwndT;
        hwndT = GetWindow(hwndT, GW_HWNDNEXT))
    {
        if (GetClassName(hwndT, szClassName, 40))
        {
            if (!lstrcmpi(szClassName, gszSpyClassName))
            {
                bFoundPrevSpy = TRUE;
                break;
            }
        }
    }

    if (bFoundPrevSpy)
    {
        if (hwndT)
            SendMessage(hwndT, WM_EXECINSTANCE, 0, 0);

        return FALSE;
    }

    if (!(ghaccelTbl = LoadAccelerators(ghInst, "spy")))
        return FALSE;

    ReadRegistry();

    gcxBorder = GetSystemMetrics(SM_CXBORDER);
    gcyBorder = GetSystemMetrics(SM_CYBORDER);

    //
    // Calculate the counts in the message groups.  This is best
    // done at run time to be safe.
    //
    for (i = 0; i < gcMessages; i++)
    {
        //
        // If this message belongs to a message group,
        // increment the total for that group.
        //
        for (j = 0; j < gcMsgGroups; j++)
        {
            if (gaMsgGroup[j].flMask & gaMsgs[i].Flags)
                gaMsgGroup[j].cMsgs++;
        }
    }

    lstrcpy(gszWindowName,LoadResourceString(IDS_APPLICATION_NAME));

    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon          = LoadIcon(hInstance, gszAppName);
    wc.lpszMenuName   = gszAppName;
    wc.lpszClassName  = gszSpyClassName;
    wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hInstance      = hInstance;
    wc.style          = CS_BYTEALIGNCLIENT;
    wc.lpfnWndProc    = SpyWndProc;
    wc.cbWndExtra     = 0;
    wc.cbClsExtra     = 0;

    if (!RegisterClass(&wc))
        return FALSE;

    ghwndSpyApp = CreateWindow(gszSpyClassName, gszWindowName,
        WS_OVERLAPPEDWINDOW, 0, 0, 0, 0,
        NULL, NULL, hInstance, NULL);

    if (!ghwndSpyApp)
        return FALSE;

    if (nCmdShow != SW_SHOWNORMAL)
        gwndpl.showCmd = nCmdShow;

    SetWindowPlacement(ghwndSpyApp, &gwndpl);

    return TRUE;
}

/*****************************************************************************\
* SpyWndProc
*
* Main window procedure for the spy app.
*
* Arguments:
*    HWND hwnd - handle to the spy window
*    UINT msg - message
*    WPARAM wParam - message parameter
*    LPARAM lParam - message parameter
*
* Returns:
*   The value that the window proc should return, based on the processing
*   of the specific WM_COMMAND message received.
\*****************************************************************************/

LRESULT CALLBACK
SpyWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch (msg)
    {
        case WM_CREATE:
            MyCreatePrintfWin(hwnd);
            return 0;

        case WM_INITMENU:
            if (GetMenu(ghwndSpyApp) == (HMENU)wParam)
                InitMenu((HMENU)wParam);

            break;

        case WM_COMMAND:
            return SpyCommand(hwnd, LOWORD(wParam), HIWORD(wParam));

        case WM_ACTIVATE:
            /*
             * Set the focus to the printf window if we are being activated.
             */
            if (LOWORD(wParam))
                SetFocus(ghwndPrintf);

            break;

        case WM_SIZE:
            /*
             * Size the printf window to fit into the new client area size.
             */
            MoveWindow(ghwndPrintf, -gcxBorder, -gcyBorder,
                LOWORD(lParam) + (2 * gcxBorder),
                HIWORD(lParam) + (2 * gcyBorder), TRUE);
            break;

        case WM_CLOSE:
            SetSpyHook(FALSE);

            if (gfhCom1 != INVALID_HANDLE_VALUE)
                CloseHandle(gfhCom1);
            if (gfhFile)
                _lclose(gfhFile);

            SendMessage(ghwndSpyHook, WM_CLOSE, 0, 0);
            WriteRegistry();
            WaitForSingleObject(ghHookThread, INFINITE);
            DestroyWindow(ghwndSpyApp);
            break;

        case WM_DESTROY:
            PostQuitMessage(0);   /* Kill the main window */
            ghwndSpyApp = NULL;
            ghwndPrintf = NULL;
            break;

        case WM_EXECINSTANCE:
            /*
             * another instance of spy has been started.
             */
            if (IsIconic(hwnd))
                ShowWindow(hwnd,SW_SHOWNORMAL);

            SetForegroundWindow(hwnd);
            BringWindowToTop(hwnd);

            break;

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

    return 0;
}

/*****************************************************************************\
* InitMenu
*
* This function grays/enables and checks/unchecks the menu items
* appropriately for the given state.
*
* Arguments:
*   HMENU hmenu - The menu handle.
*
* Returns:
*   VOID
\*****************************************************************************/

PRIVATE VOID
InitMenu(
    HMENU hmenu
    )
{
    BOOL fEnable = !IsPrintfEmpty();

    MyEnableMenuItem(hmenu, MENU_EDIT_CUT, fEnable);
    MyEnableMenuItem(hmenu, MENU_EDIT_COPY, fEnable);
    MyEnableMenuItem(hmenu, MENU_EDIT_CLEAR, fEnable);
}

/*****************************************************************************\
* SpyCommand
*
* Handles thw WM_COMMAND messages for the Spy app.
*
* Arguments:
*   HWND hwnd       - Window handle of the main app window.
*   INT nCmd        - Command value.
*   INT nNotifyCode - The notify code.
*
* Returns:
*   The value that the window proc should return, based on the processing
*   of the specific WM_COMMAND message received.
\*****************************************************************************/

PRIVATE LRESULT
SpyCommand(
    HWND hwnd,
    INT nCmd,
    INT nNotifyCode
    )
{
    HMENU hmenu;

    switch (nCmd)
    {
        case MENU_SPY_SELECTWINDOW:
            MyDialogBox(DID_SELECTWINDOW, SelectWindowDlgProc);
            break;

        case MENU_SPY_ABOUT:
            MyDialogBox(DID_ABOUT, AboutDlgProc);
            break;

        case MENU_SPY_EXIT:
            PostMessage(hwnd, WM_CLOSE, 0, 0);
            break;

        case MENU_EDIT_CUT:
            if (CopyToClipboard())
                ClearPrintfWindow(ghwndPrintf);

            break;

        case MENU_EDIT_COPY:
            CopyToClipboard();
            break;

        case MENU_EDIT_CLEAR:
            ClearPrintfWindow(ghwndPrintf);
            break;

        case MENU_OPTIONS_MESSAGES:
            MyDialogBox(DID_MESSAGES, MessagesDlgProc);
            break;

        case MENU_OPTIONS_FONT:
            SelectFont();
            break;

        case MENU_OPTIONS_OUTPUT:
            MyDialogBox(DID_OUTPUT, OutputDlgProc);
            break;

        case MENU_START:
            if (SetSpyHook(TRUE))
            {
                hmenu = GetMenu(hwnd);
                ModifyMenu(hmenu, MENUPOS_STARTSTOP, MF_BYPOSITION | MF_STRING,
                    MENU_STOP, LoadResourceString(IDS_MENU_STOP));
                DrawMenuBar(hwnd);

                SetSpyCaption();
            }

            break;

        case MENU_STOP:
            if (SetSpyHook(FALSE))
            {
                hmenu = GetMenu(hwnd);
                ModifyMenu(hmenu, MENUPOS_STARTSTOP, MF_BYPOSITION | MF_STRING,
                    MENU_START, LoadResourceString(IDS_MENU_START));
                DrawMenuBar(hwnd);

                SetSpyCaption();
            }

            break;
    }

    return 0;
}


SPY.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.
\******************************************************************************/

/*****************************************************************************\
*
* Module: spy.h
*
*   Main include file for the Windows debugging Spy SDK applet.
*
\*****************************************************************************/

#include <windows.h>

#ifdef  FE_IME
#include <ime.h>
#endif

#include "dialogs.h"
#include "..\hook.h"

#define MAXSTRING                   MAX_PATH

#define LINES_MAX                   250         // Max lines allowed in window.

//
// Message Table Flags
//
#define MTF_SELECTED                0x00000001

#define MTF_TYPE_MOUSE              0x00010000
#define MTF_TYPE_DDE                0x00020000
#define MTF_TYPE_CLIP               0x00040000
#define MTF_TYPE_NC                 0x00080000
#define MTF_TYPE_KEYBD              0x00100000
#define MTF_TYPE_LB                 0x00200000
#define MTF_TYPE_BM                 0x00400000
#define MTF_TYPE_STM                0x00800000
#define MTF_TYPE_CB                 0x01000000
#define MTF_TYPE_EM                 0x02000000
#ifdef FE_IME
#define MTF_TYPE_IME                0x04000000
#endif

//
// Menu ids.
//

#define MENU_SPY_SELECTWINDOW       1000
#define MENU_SPY_ABOUT              1001
#define MENU_SPY_EXIT               1002

#define MENU_EDIT_CUT               1100
#define MENU_EDIT_COPY              1101
#define MENU_EDIT_CLEAR             1102

#define MENU_OPTIONS_MESSAGES       1200
#define MENU_OPTIONS_FONT           1201
#define MENU_OPTIONS_OUTPUT         1202

#define MENU_START                  1300
#define MENU_STOP                   1301

//
// Resource file constants
//
#define IDS_APPLICATION_NAME        2000
#define IDS_MENU_START              2001
#define IDS_MENU_STOP               2002
#define IDS_PARENT                  2003
#define IDS_NOPARENT                2004
#define IDS_UNDEFINED               2005
#define IDS_ERROR_WND_LINE          2006
#define IDS_ERROR_CANT_OPEN_FILE    2007
#define IDS_ERROR_CANT_OPEN_COM1    2008
#define IDS_ERROR_CANT_LOAD_DLL     2009

//
// Position of the top level Start/Stop menu item.
//
#define MENUPOS_STARTSTOP           3

//
// Prototype for the Message Decode functions.
//
typedef VOID (*LPFNDECODE)(HWND, UINT, PSPYMSGDATA);

//
// The Message Description Table structure that describes each message
// that Spy understands.
//
typedef struct
{
    LPTSTR pszMsg;
    UINT msg;
    UINT Flags;
    LPFNDECODE pfnDecode;
} MSGDESC, *PMSGDESC;

//
// The message group structure.  A table of these structures describes
// each different group of messages that can be selected/deselected
// for spying on using the Messages dialog.
//
typedef struct
{
    INT idCheckBox;
    DWORD flMask;
    INT cMsgs;
    INT cUseCount;
} MSGGROUP, *PMSGGROUP;

extern HANDLE ghInst;
extern HWND ghwndSpyApp;
extern HWND ghwndPrintf;
extern HANDLE ghHookThread;
extern HWND ghwndSpyHook;
extern HWND ghwndSpyingOn;              // The window we are spying on.
extern HFONT ghfontPrintf;
extern INT gnLines;
extern BOOL gfSpyOn;
extern BOOL gfSpyAll;
extern BOOL gfOutputWin;
extern BOOL gfOutputCom1;
extern BOOL gfOutputFile;
extern HFILE gfhFile;
extern HANDLE gfhCom1;
extern CHAR gszFile[];
extern INT gcxBorder;
extern INT gcyBorder;
extern BOOL gfMsgsUser;
extern BOOL gfMsgsUnknown;
extern CHAR gszAppName[];
extern UCHAR gszWindowName[];
extern WINDOWPLACEMENT gwndpl;

extern MSGDESC gaMsgs[];
extern INT gcMessages;
extern MSGGROUP gaMsgGroup[];
extern INT gcMsgGroups;
extern INT gaiSelected[];
extern INT gaiSelected2[];

#include "spyfuncs.h"
#include "wprintf.h"

#ifdef FE_IME
#include <ime.h>               //  For WM_IME_REPORT
#include <winuser.h>           //  For WM_IMEKEYDOWN, WM_IMEKEYUP
#endif


SPYFUNCS.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.
\******************************************************************************/

/*****************************************************************************\
*
* Module: spyfuncs.h
*
*   External function declarations for the Windows debugging Spy SDK applet.
*
\*****************************************************************************/

//
// dialogs.c
//

BOOL MyDialogBox(INT idDlg, DLGPROC pfnDlgProc);
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK SelectWindowDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
BOOL CALLBACK OutputDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
VOID SelectFont(VOID);
BOOL CALLBACK MessagesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);

//
// hook.c
//

BOOL CreateHookThread(VOID);
DWORD HookMain(LPVOID lpv);
LRESULT CALLBACK HookWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL SetSpyHook(BOOL fSet);
VOID SetWindowToSpyOn(HWND hwndSpyingOn);

#ifdef DBG
VOID DbgPrintf(LPTSTR fmt, ...);
#endif

//
// misc.c
//

VOID ReadRegistry(VOID);
VOID WriteRegistry(VOID);
INT Message(UINT fuStyle, LPSTR pszFormat, ...);
VOID SetSpyCaption(VOID);
LPTSTR LoadResourceString(UINT wId);

//
// spy.c
//

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, INT nCmdShow);
LRESULT CALLBACK SpyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

//
// wm.c
//

VOID CalculatePrintfTabs(HFONT hfont);
VOID PrintMsg(LPMSG lpMsg);

//
// wprintf.c
//

VOID MyCreatePrintfWin(HWND hwnd);
VOID SetPrintfFont(HWND hwnd, HFONT hfont);
VOID SetPrintfTabs(INT n, LPINT pTabs);
VOID ClearPrintfWindow(HWND hwnd);
BOOL CopyToClipboard(VOID);
BOOL IsPrintfEmpty(VOID);


WM.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: wm.c
*
* Contains functions that lookup and format messages received by the
* Windows debugging Spy SDK applet.
*
* Functions:
*
*    CalculatePrintfTabs()
*    PrintMsg()
*    DecodeGeneric()
*    MsgCmp()
*    mprintf()
*
* Comments:
*
\*****************************************************************************/

#include "spy.h"
#include <stdlib.h>
#include <dde.h>

PRIVATE CHAR szFormatName[] = "%08X\t%s\t%08X\t%08X\r\n";
PRIVATE CHAR szFormatUSER[] = "%08X\tWM_USER+%d\t%08X\t%08X\r\n";
PRIVATE CHAR szFormatUnknown[] = "%08X\tWM_%04X\t%08X\t%08X\r\n";

VOID DecodeGeneric(HWND hwnd, UINT msg, PSPYMSGDATA psmd) {}

//
// Message description table.  Describes each message that can be spied on.
// This table must be kept in sorted order.
//
MSGDESC gaMsgs[] =
{
    { "WM_NULL", WM_NULL,                                   // 0x0000
        0, DecodeGeneric },
    { "WM_CREATE", WM_CREATE,                               // 0x0001
        0, DecodeGeneric },
    { "WM_DESTROY", WM_DESTROY,                             // 0x0002
        0, DecodeGeneric },
    { "WM_MOVE", WM_MOVE,                                   // 0x0003
        0, DecodeGeneric },
    { "WM_SIZE", WM_SIZE,                                   // 0x0005
        0, DecodeGeneric },
    { "WM_ACTIVATE", WM_ACTIVATE,                           // 0x0006
        0, DecodeGeneric },
    { "WM_SETFOCUS", WM_SETFOCUS,                           // 0x0007
        0, DecodeGeneric },
    { "WM_KILLFOCUS", WM_KILLFOCUS,                         // 0x0008
        0, DecodeGeneric },
    { "WM_ENABLE", WM_ENABLE,                               // 0x000A
        0, DecodeGeneric },
    { "WM_SETREDRAW", WM_SETREDRAW,                         // 0x000B
        0, DecodeGeneric },
    { "WM_SETTEXT", WM_SETTEXT,                             // 0x000C
        0, DecodeGeneric },
    { "WM_GETTEXT", WM_GETTEXT,                             // 0x000D
        0, DecodeGeneric },
    { "WM_GETTEXTLENGTH", WM_GETTEXTLENGTH,                 // 0x000E
        0, DecodeGeneric },
    { "WM_PAINT", WM_PAINT,                                 // 0x000F
        0, DecodeGeneric },
    { "WM_CLOSE", WM_CLOSE,                                 // 0x0010
        0, DecodeGeneric },
    { "WM_QUERYENDSESSION", WM_QUERYENDSESSION,             // 0x0011
        0, DecodeGeneric },
    { "WM_QUIT", WM_QUIT,                                   // 0x0012
        0, DecodeGeneric },
    { "WM_QUERYOPEN", WM_QUERYOPEN,                         // 0x0013
        0, DecodeGeneric },
    { "WM_ERASEBKGND", WM_ERASEBKGND,                       // 0x0014
        0, DecodeGeneric },
    { "WM_SYSCOLORCHANGE", WM_SYSCOLORCHANGE,               // 0x0015
        0, DecodeGeneric },
    { "WM_ENDSESSION", WM_ENDSESSION,                       // 0x0016
        0, DecodeGeneric },
    { "WM_SHOWWINDOW", WM_SHOWWINDOW,                       // 0x0018
        0, DecodeGeneric },
    { "WM_WININICHANGE", WM_WININICHANGE,                   // 0x001A
        0, DecodeGeneric },
    { "WM_DEVMODECHANGE", WM_DEVMODECHANGE,                 // 0x001B
        0, DecodeGeneric },
    { "WM_ACTIVATEAPP", WM_ACTIVATEAPP,                     // 0x001C
        0, DecodeGeneric },
    { "WM_FONTCHANGE", WM_FONTCHANGE,                       // 0x001D
        0, DecodeGeneric },
    { "WM_TIMECHANGE", WM_TIMECHANGE,                       // 0x001E
        0, DecodeGeneric },
    { "WM_CANCELMODE", WM_CANCELMODE,                       // 0x001F
        0, DecodeGeneric },
    { "WM_SETCURSOR", WM_SETCURSOR,                         // 0x0020
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_MOUSEACTIVATE", WM_MOUSEACTIVATE,                 // 0x0021
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_CHILDACTIVATE", WM_CHILDACTIVATE,                 // 0x0022
        0, DecodeGeneric },
    { "WM_QUEUESYNC", WM_QUEUESYNC,                         // 0x0023
        0, DecodeGeneric },
    { "WM_GETMINMAXINFO", WM_GETMINMAXINFO,                 // 0x0024
        0, DecodeGeneric },
    { "WM_PAINTICON", WM_PAINTICON,                         // 0x0026
        0, DecodeGeneric },
    { "WM_ICONERASEBKGND", WM_ICONERASEBKGND,               // 0x0027
        0, DecodeGeneric },
    { "WM_NEXTDLGCTL", WM_NEXTDLGCTL,                       // 0x0028
        0, DecodeGeneric },
    { "WM_SPOOLERSTATUS", WM_SPOOLERSTATUS,                 // 0x002A
        0, DecodeGeneric },
    { "WM_DRAWITEM", WM_DRAWITEM,                           // 0x002B
        0, DecodeGeneric },
    { "WM_MEASUREITEM", WM_MEASUREITEM,                     // 0x002C
        0, DecodeGeneric },
    { "WM_DELETEITEM", WM_DELETEITEM,                       // 0x002D
        0, DecodeGeneric },
    { "WM_VKEYTOITEM", WM_VKEYTOITEM,                       // 0x002E
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_CHARTOITEM", WM_CHARTOITEM,                       // 0x002F
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_SETFONT", WM_SETFONT,                             // 0x0030
        0, DecodeGeneric },
    { "WM_GETFONT", WM_GETFONT,                             // 0x0031
        0, DecodeGeneric },
    { "WM_SETHOTKEY", WM_SETHOTKEY,                         // 0x0032
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_GETHOTKEY", WM_GETHOTKEY,                         // 0x0033
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_QUERYDRAGICON", WM_QUERYDRAGICON,                 // 0x0037
        0, DecodeGeneric },
    { "WM_COMPAREITEM", WM_COMPAREITEM,                     // 0x0039
        0, DecodeGeneric },
    { "WM_COMPACTING", WM_COMPACTING,                       // 0x0041
        0, DecodeGeneric },
    { "WM_WINDOWPOSCHANGING", WM_WINDOWPOSCHANGING,         // 0x0046
        0, DecodeGeneric },
    { "WM_WINDOWPOSCHANGED", WM_WINDOWPOSCHANGED,           // 0x0047
        0, DecodeGeneric },
    { "WM_POWER", WM_POWER,                                 // 0x0048
        0, DecodeGeneric },
    { "WM_COPYDATA", WM_COPYDATA,                           // 0x004A
        0, DecodeGeneric },
    { "WM_CANCELJOURNAL", WM_CANCELJOURNAL,                 // 0x004B
        0, DecodeGeneric },
    { "WM_NCCREATE", WM_NCCREATE,                           // 0x0081
        MTF_TYPE_NC, DecodeGeneric },
    { "WM_NCDESTROY", WM_NCDESTROY,                         // 0x0082
        MTF_TYPE_NC, DecodeGeneric },
    { "WM_NCCALCSIZE", WM_NCCALCSIZE,                       // 0x0083
        MTF_TYPE_NC, DecodeGeneric },
    { "WM_NCHITTEST", WM_NCHITTEST,                         // 0x0084
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCPAINT", WM_NCPAINT,                             // 0x0085
        MTF_TYPE_NC, DecodeGeneric },
    { "WM_NCACTIVATE", WM_NCACTIVATE,                       // 0x0086
        MTF_TYPE_NC, DecodeGeneric },
    { "WM_GETDLGCODE", WM_GETDLGCODE,                       // 0x0087
        0, DecodeGeneric },
    { "WM_NCMOUSEMOVE", WM_NCMOUSEMOVE,                     // 0x00A0
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCLBUTTONDOWN", WM_NCLBUTTONDOWN,                 // 0x00A1
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCLBUTTONUP", WM_NCLBUTTONUP,                     // 0x00A2
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCLBUTTONDBLCLK", WM_NCLBUTTONDBLCLK,             // 0x00A3
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCRBUTTONDOWN", WM_NCRBUTTONDOWN,                 // 0x00A4
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCRBUTTONUP", WM_NCRBUTTONUP,                     // 0x00A5
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCRBUTTONDBLCLK", WM_NCRBUTTONDBLCLK,             // 0x00A6
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCMBUTTONDOWN", WM_NCMBUTTONDOWN,                 // 0x00A7
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCMBUTTONUP", WM_NCMBUTTONUP,                     // 0x00A8
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_NCMBUTTONDBLCLK", WM_NCMBUTTONDBLCLK,             // 0x00A9
        MTF_TYPE_NC | MTF_TYPE_MOUSE, DecodeGeneric },
    { "EM_GETSEL", EM_GETSEL,                               // 0x00B0
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETSEL", EM_SETSEL,                               // 0x00B1
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETRECT", EM_GETRECT,                             // 0x00B2
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETRECT", EM_SETRECT,                             // 0x00B3
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETRECTNP", EM_SETRECTNP,                         // 0x00B4
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SCROLL", EM_SCROLL,                               // 0x00B5
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_LINESCROLL", EM_LINESCROLL,                       // 0x00B6
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SCROLLCARET", EM_SCROLLCARET,                     // 0x00B7
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETMODIFY", EM_GETMODIFY,                         // 0x00B8
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETMODIFY", EM_SETMODIFY,                         // 0x00B9
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETLINECOUNT", EM_GETLINECOUNT,                   // 0x00BA
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_LINEINDEX", EM_LINEINDEX,                         // 0x00BB
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETHANDLE", EM_SETHANDLE,                         // 0x00BC
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETHANDLE", EM_GETHANDLE,                         // 0x00BD
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETTHUMB", EM_GETTHUMB,                           // 0x00BE
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_LINELENGTH", EM_LINELENGTH,                       // 0x00C1
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_REPLACESEL", EM_REPLACESEL,                       // 0x00C2
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETLINE", EM_GETLINE,                             // 0x00C4
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_LIMITTEXT", EM_LIMITTEXT,                         // 0x00C5
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_CANUNDO", EM_CANUNDO,                             // 0x00C6
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_UNDO", EM_UNDO,                                   // 0x00C7
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_FMTLINES", EM_FMTLINES,                           // 0x00C8
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_LINEFROMCHAR", EM_LINEFROMCHAR,                   // 0x00C9
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETTABSTOPS", EM_SETTABSTOPS,                     // 0x00CB
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETPASSWORDCHAR", EM_SETPASSWORDCHAR,             // 0x00CC
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_EMPTYUNDOBUFFER", EM_EMPTYUNDOBUFFER,             // 0x00CD
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETFIRSTVISIBLELINE", EM_GETFIRSTVISIBLELINE,     // 0x00CE
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETREADONLY", EM_SETREADONLY,                     // 0x00CF
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_SETWORDBREAKPROC", EM_SETWORDBREAKPROC,           // 0x00D0
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETWORDBREAKPROC", EM_GETWORDBREAKPROC,           // 0x00D1
        MTF_TYPE_EM, DecodeGeneric },
    { "EM_GETPASSWORDCHAR", EM_GETPASSWORDCHAR,             // 0x00D2
        MTF_TYPE_EM, DecodeGeneric },
    { "SBM_SETPOS", SBM_SETPOS,                             // 0x00E0
        0, DecodeGeneric },
    { "SBM_GETPOS", SBM_GETPOS,                             // 0x00E1
        0, DecodeGeneric },
    { "SBM_SETRANGE", SBM_SETRANGE,                         // 0x00E2
        0, DecodeGeneric },
    { "SBM_GETRANGE", SBM_GETRANGE,                         // 0x00E3
        0, DecodeGeneric },
    { "SBM_ENABLE_ARROWS", SBM_ENABLE_ARROWS,               // 0x00E4
        0, DecodeGeneric },
    { "SBM_SETRANGEREDRAW", SBM_SETRANGEREDRAW,             // 0x00E6
        0, DecodeGeneric },
    { "BM_GETCHECK", BM_GETCHECK,                           // 0x00F0
        MTF_TYPE_BM, DecodeGeneric },
    { "BM_SETCHECK", BM_SETCHECK,                           // 0x00F1
        MTF_TYPE_BM, DecodeGeneric },
    { "BM_GETSTATE", BM_GETSTATE,                           // 0x00F2
        MTF_TYPE_BM, DecodeGeneric },
    { "BM_SETSTATE", BM_SETSTATE,                           // 0x00F3
        MTF_TYPE_BM, DecodeGeneric },
    { "BM_SETSTYLE", BM_SETSTYLE,                           // 0x00F4
        MTF_TYPE_BM, DecodeGeneric },
    { "WM_KEYDOWN", WM_KEYDOWN,                             // 0x0100
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_KEYUP", WM_KEYUP,                                 // 0x0101
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_CHAR", WM_CHAR,                                   // 0x0102
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_DEADCHAR", WM_DEADCHAR,                           // 0x0103
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_SYSKEYDOWN", WM_SYSKEYDOWN,                       // 0x0104
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_SYSKEYUP", WM_SYSKEYUP,                           // 0x0105
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_SYSCHAR", WM_SYSCHAR,                             // 0x0106
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_SYSDEADCHAR", WM_SYSDEADCHAR,                     // 0x0107
        MTF_TYPE_KEYBD, DecodeGeneric },
#ifdef  FE_IME
    { "WM_WNT_CONVERTREQUESTEX", WM_WNT_CONVERTREQUESTEX,   // 0x0109
        MTF_TYPE_IME, DecodeGeneric },
    { "WM_CONVERTREQUEST", WM_CONVERTREQUEST,               // 0x010A
        MTF_TYPE_IME, DecodeGeneric },
    { "WM_CONVERTRESULT", WM_CONVERTRESULT,                 // 0x010B
        MTF_TYPE_IME, DecodeGeneric },
    { "WM_INTERIM", WM_INTERIM,                             // 0x010C
        MTF_TYPE_IME, DecodeGeneric },
#   define TMP_MTF_TYPE_IME     MTF_TYPE_IME
#else // not FE_IME
#   define TMP_MTF_TYPE_IME     0
#endif
    { "WM_IME_STARTCOMPOSITION", WM_IME_STARTCOMPOSITION,   // 0x010D
        TMP_MTF_TYPE_IME, DecodeGeneric },
    { "WM_IME_ENDCOMPOSITION",   WM_IME_ENDCOMPOSITION,     // 0x010E
        TMP_MTF_TYPE_IME, DecodeGeneric },
    { "WM_IME_COMPOSITION",      WM_IME_COMPOSITION,        // 0x010F
        TMP_MTF_TYPE_IME, DecodeGeneric },
    { "WM_INITDIALOG", WM_INITDIALOG,                       // 0x0110
        0, DecodeGeneric },
    { "WM_COMMAND", WM_COMMAND,                             // 0x0111
        0, DecodeGeneric },
    { "WM_SYSCOMMAND", WM_SYSCOMMAND,                       // 0x0112
        0, DecodeGeneric },
    { "WM_TIMER", WM_TIMER,                                 // 0x0113
        0, DecodeGeneric },
    { "WM_HSCROLL", WM_HSCROLL,                             // 0x0114
        0, DecodeGeneric },
    { "WM_VSCROLL", WM_VSCROLL,                             // 0x0115
        0, DecodeGeneric },
    { "WM_INITMENU", WM_INITMENU,                           // 0x0116
        0, DecodeGeneric },
    { "WM_INITMENUPOPUP", WM_INITMENUPOPUP,                 // 0x0117
        0, DecodeGeneric },
    { "WM_MENUSELECT", WM_MENUSELECT,                       // 0x011F
        0, DecodeGeneric },
    { "WM_MENUCHAR", WM_MENUCHAR,                           // 0x0120
        0, DecodeGeneric },
    { "WM_ENTERIDLE", WM_ENTERIDLE,                         // 0x0121
        0, DecodeGeneric },
    { "WM_CTLCOLORMSGBOX", WM_CTLCOLORMSGBOX,               // 0x0132
        0, DecodeGeneric },
    { "WM_CTLCOLOREDIT", WM_CTLCOLOREDIT,                   // 0x0133
        0, DecodeGeneric },
    { "WM_CTLCOLORLISTBOX", WM_CTLCOLORLISTBOX,             // 0x0134
        0, DecodeGeneric },
    { "WM_CTLCOLORBTN", WM_CTLCOLORBTN,                     // 0x0135
        0, DecodeGeneric },
    { "WM_CTLCOLORDLG", WM_CTLCOLORDLG,                     // 0x0136
        0, DecodeGeneric },
    { "WM_CTLCOLORSCROLLBAR", WM_CTLCOLORSCROLLBAR,         // 0x0137
        0, DecodeGeneric },
    { "WM_CTLCOLORSTATIC", WM_CTLCOLORSTATIC,               // 0x0138
        0, DecodeGeneric },
    { "CB_GETEDITSEL", CB_GETEDITSEL,                       // 0x0140
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_LIMITTEXT", CB_LIMITTEXT,                         // 0x0141
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_SETEDITSEL", CB_SETEDITSEL,                       // 0x0142
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_ADDSTRING", CB_ADDSTRING,                         // 0x0143
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_DELETESTRING", CB_DELETESTRING,                   // 0x0144
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_DIR", CB_DIR,                                     // 0x0145
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETCOUNT", CB_GETCOUNT,                           // 0x0146
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETCURSEL", CB_GETCURSEL,                         // 0x0147
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETLBTEXT", CB_GETLBTEXT,                         // 0x0148
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETLBTEXTLEN", CB_GETLBTEXTLEN,                   // 0x0149
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_INSERTSTRING", CB_INSERTSTRING,                   // 0x014A
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_RESETCONTENT", CB_RESETCONTENT,                   // 0x014B
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_FINDSTRING", CB_FINDSTRING,                       // 0x014C
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_SELECTSTRING", CB_SELECTSTRING,                   // 0x014D
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_SETCURSEL", CB_SETCURSEL,                         // 0x014E
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_SHOWDROPDOWN", CB_SHOWDROPDOWN,                   // 0x014F
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETITEMDATA", CB_GETITEMDATA,                     // 0x0150
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_SETITEMDATA", CB_SETITEMDATA,                     // 0x0151
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETDROPPEDCONTROLRECT", CB_GETDROPPEDCONTROLRECT, // 0x0152
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_SETITEMHEIGHT", CB_SETITEMHEIGHT,                 // 0x0153
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETITEMHEIGHT", CB_GETITEMHEIGHT,                 // 0x0154
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_SETEXTENDEDUI", CB_SETEXTENDEDUI,                 // 0x0155
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETEXTENDEDUI", CB_GETEXTENDEDUI,                 // 0x0156
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETDROPPEDSTATE", CB_GETDROPPEDSTATE,             // 0x0157
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_FINDSTRINGEXACT", CB_FINDSTRINGEXACT,             // 0x0158
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_SETLOCALE", CB_SETLOCALE,                         // 0x0159
        MTF_TYPE_CB, DecodeGeneric },
    { "CB_GETLOCALE", CB_GETLOCALE,                         // 0x015A
        MTF_TYPE_CB, DecodeGeneric },
    { "STM_SETICON", STM_SETICON,                           // 0x0170
        MTF_TYPE_STM, DecodeGeneric },
    { "STM_GETICON", STM_GETICON,                           // 0x0171
        MTF_TYPE_STM, DecodeGeneric },
    { "LB_ADDSTRING", LB_ADDSTRING,                         // 0x0180
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_INSERTSTRING", LB_INSERTSTRING,                   // 0x0181
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_DELETESTRING", LB_DELETESTRING,                   // 0x0182
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SELITEMRANGEEX", LB_SELITEMRANGEEX,               // 0x0183
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_RESETCONTENT", LB_RESETCONTENT,                   // 0x0184
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETSEL", LB_SETSEL,                               // 0x0185
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETCURSEL", LB_SETCURSEL,                         // 0x0186
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETSEL", LB_GETSEL,                               // 0x0187
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETCURSEL", LB_GETCURSEL,                         // 0x0188
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETTEXT", LB_GETTEXT,                             // 0x0189
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETTEXTLEN", LB_GETTEXTLEN,                       // 0x018A
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETCOUNT", LB_GETCOUNT,                           // 0x018B
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SELECTSTRING", LB_SELECTSTRING,                   // 0x018C
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_DIR", LB_DIR,                                     // 0x018D
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETTOPINDEX", LB_GETTOPINDEX,                     // 0x018E
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_FINDSTRING", LB_FINDSTRING,                       // 0x018F
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETSELCOUNT", LB_GETSELCOUNT,                     // 0x0190
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETSELITEMS", LB_GETSELITEMS,                     // 0x0191
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETTABSTOPS", LB_SETTABSTOPS,                     // 0x0192
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETHORIZONTALEXTENT", LB_GETHORIZONTALEXTENT,     // 0x0193
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETHORIZONTALEXTENT", LB_SETHORIZONTALEXTENT,     // 0x0194
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETCOLUMNWIDTH", LB_SETCOLUMNWIDTH,               // 0x0195
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_ADDFILE", LB_ADDFILE,                             // 0x0196
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETTOPINDEX", LB_SETTOPINDEX,                     // 0x0197
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETITEMRECT", LB_GETITEMRECT,                     // 0x0198
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETITEMDATA", LB_GETITEMDATA,                     // 0x0199
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETITEMDATA", LB_SETITEMDATA,                     // 0x019A
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SELITEMRANGE", LB_SELITEMRANGE,                   // 0x019B
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETANCHORINDEX", LB_SETANCHORINDEX,               // 0x019C
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETANCHORINDEX", LB_GETANCHORINDEX,               // 0x019D
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETCARETINDEX", LB_SETCARETINDEX,                 // 0x019E
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETCARETINDEX", LB_GETCARETINDEX,                 // 0x019F
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETITEMHEIGHT", LB_SETITEMHEIGHT,                 // 0x01A0
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETITEMHEIGHT", LB_GETITEMHEIGHT,                 // 0x01A1
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_FINDSTRINGEXACT", LB_FINDSTRINGEXACT,             // 0x01A2
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETLOCALE", LB_SETLOCALE,                         // 0x01A5
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_GETLOCALE", LB_GETLOCALE,                         // 0x01A6
        MTF_TYPE_LB, DecodeGeneric },
    { "LB_SETCOUNT", LB_SETCOUNT,                           // 0x01A7
        MTF_TYPE_LB, DecodeGeneric },
    { "WM_MOUSEMOVE", WM_MOUSEMOVE,                         // 0x0200
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_LBUTTONDOWN", WM_LBUTTONDOWN,                     // 0x0201
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_LBUTTONUP", WM_LBUTTONUP,                         // 0x0202
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_LBUTTONDBLCLK", WM_LBUTTONDBLCLK,                 // 0x0203
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_RBUTTONDOWN", WM_RBUTTONDOWN,                     // 0x0204
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_RBUTTONUP", WM_RBUTTONUP,                         // 0x0205
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_RBUTTONDBLCLK", WM_RBUTTONDBLCLK,                 // 0x0206
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_MBUTTONDOWN", WM_MBUTTONDOWN,                     // 0x0207
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_MBUTTONUP", WM_MBUTTONUP,                         // 0x0208
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_MBUTTONDBLCLK", WM_MBUTTONDBLCLK,                 // 0x0209
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_PARENTNOTIFY", WM_PARENTNOTIFY,                   // 0x0210
        MTF_TYPE_MOUSE, DecodeGeneric },
    { "WM_ENTERMENULOOP", WM_ENTERMENULOOP,                 // 0x0211
        0, DecodeGeneric },
    { "WM_EXITMENULOOP", WM_EXITMENULOOP,                   // 0x0212
        0, DecodeGeneric },
    { "WM_MDICREATE", WM_MDICREATE,                         // 0x0220
        0, DecodeGeneric },
    { "WM_MDIDESTROY", WM_MDIDESTROY,                       // 0x0221
        0, DecodeGeneric },
    { "WM_MDIACTIVATE", WM_MDIACTIVATE,                     // 0x0222
        0, DecodeGeneric },
    { "WM_MDIRESTORE", WM_MDIRESTORE,                       // 0x0223
        0, DecodeGeneric },
    { "WM_MDINEXT", WM_MDINEXT,                             // 0x0224
        0, DecodeGeneric },
    { "WM_MDIMAXIMIZE", WM_MDIMAXIMIZE,                     // 0x0225
        0, DecodeGeneric },
    { "WM_MDITILE", WM_MDITILE,                             // 0x0226
        0, DecodeGeneric },
    { "WM_MDICASCADE", WM_MDICASCADE,                       // 0x0227
        0, DecodeGeneric },
    { "WM_MDIICONARRANGE", WM_MDIICONARRANGE,               // 0x0228
        0, DecodeGeneric },
    { "WM_MDIGETACTIVE", WM_MDIGETACTIVE,                   // 0x0229
        0, DecodeGeneric },
    { "WM_MDISETMENU", WM_MDISETMENU,                       // 0x0230
        0, DecodeGeneric },
    { "WM_ENTERSIZEMOVE", WM_ENTERSIZEMOVE,                 // 0x0231
        0, DecodeGeneric },
    { "WM_EXITSIZEMOVE", WM_EXITSIZEMOVE,                   // 0x0232
        0, DecodeGeneric },
    { "WM_DROPFILES", WM_DROPFILES,                         // 0x0233
        0, DecodeGeneric },
    { "WM_MDIREFRESHMENU", WM_MDIREFRESHMENU,               // 0x0234
        0, DecodeGeneric },
#ifdef  FE_IME
    { "WM_IME_REPORT", WM_IME_REPORT,                       // 0x0280
        MTF_TYPE_IME, DecodeGeneric },
#endif
    { "WM_IME_SETCONTEXT",      WM_IME_SETCONTEXT,          // 0x0281
        TMP_MTF_TYPE_IME, DecodeGeneric },
    { "WM_IME_NOTIFY",          WM_IME_NOTIFY,              // 0x0282
        TMP_MTF_TYPE_IME, DecodeGeneric },
    { "WM_IME_CONTROL",         WM_IME_CONTROL,             // 0x0283
        TMP_MTF_TYPE_IME, DecodeGeneric },
    { "WM_IME_COMPOSITIONFULL", WM_IME_COMPOSITIONFULL,     // 0x0284
        TMP_MTF_TYPE_IME, DecodeGeneric },
    { "WM_IME_SELECT",          WM_IME_SELECT,              // 0x0285
        TMP_MTF_TYPE_IME, DecodeGeneric },
    { "WM_IME_CHAR",            WM_IME_CHAR,                // 0x0286
        TMP_MTF_TYPE_IME, DecodeGeneric },
#ifdef  FE_IME
    { "WM_IMEKEYDOWN", WM_IMEKEYDOWN,                       // 0x0290
        MTF_TYPE_IME, DecodeGeneric },
    { "WM_IMEKEYUP", WM_IMEKEYUP,                           // 0x0291
        MTF_TYPE_IME, DecodeGeneric },
#endif
    { "WM_CUT", WM_CUT,                                     // 0x0300
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_COPY", WM_COPY,                                   // 0x0301
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_PASTE", WM_PASTE,                                 // 0x0302
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_CLEAR", WM_CLEAR,                                 // 0x0303
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_UNDO", WM_UNDO,                                   // 0x0304
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_RENDERFORMAT", WM_RENDERFORMAT,                   // 0x0305
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_RENDERALLFORMATS", WM_RENDERALLFORMATS,           // 0x0306
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_DESTROYCLIPBOARD", WM_DESTROYCLIPBOARD,           // 0x0307
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_DRAWCLIPBOARD", WM_DRAWCLIPBOARD,                 // 0x0308
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_PAINTCLIPBOARD", WM_PAINTCLIPBOARD,               // 0x0309
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_VSCROLLCLIPBOARD", WM_VSCROLLCLIPBOARD,           // 0x030A

MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_SIZECLIPBOARD", WM_SIZECLIPBOARD,                 // 0x030B
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_ASKCBFORMATNAME", WM_ASKCBFORMATNAME,             // 0x030C
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_CHANGECBCHAIN", WM_CHANGECBCHAIN,                 // 0x030D
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_HSCROLLCLIPBOARD", WM_HSCROLLCLIPBOARD,           // 0x030E
        MTF_TYPE_CLIP, DecodeGeneric },
    { "WM_QUERYNEWPALETTE", WM_QUERYNEWPALETTE,             // 0x030F
        0, DecodeGeneric },
    { "WM_PALETTEISCHANGING", WM_PALETTEISCHANGING,         // 0x0310
        0, DecodeGeneric },
    { "WM_PALETTECHANGED", WM_PALETTECHANGED,               // 0x0311
        0, DecodeGeneric },
    { "WM_HOTKEY", WM_HOTKEY,                               // 0x0312
        MTF_TYPE_KEYBD, DecodeGeneric },
    { "WM_DDE_INITIATE", WM_DDE_INITIATE,                   // 0x03E0
        MTF_TYPE_DDE, DecodeGeneric },
    { "WM_DDE_TERMINATE", WM_DDE_TERMINATE,                 // 0x03E1
        MTF_TYPE_DDE, DecodeGeneric },
    { "WM_DDE_ADVISE", WM_DDE_ADVISE,                       // 0x03E2
        MTF_TYPE_DDE, DecodeGeneric },
    { "WM_DDE_UNADVISE", WM_DDE_UNADVISE,                   // 0x03E3
        MTF_TYPE_DDE, DecodeGeneric },
    { "WM_DDE_ACK", WM_DDE_ACK,                             // 0x03E4
        MTF_TYPE_DDE, DecodeGeneric },
    { "WM_DDE_DATA", WM_DDE_DATA,                           // 0x03E5
        MTF_TYPE_DDE, DecodeGeneric },
    { "WM_DDE_REQUEST", WM_DDE_REQUEST,                     // 0x03E6
        MTF_TYPE_DDE, DecodeGeneric },
    { "WM_DDE_POKE", WM_DDE_POKE,                           // 0x03E7
        MTF_TYPE_DDE, DecodeGeneric },
    { "WM_DDE_EXECUTE", WM_DDE_EXECUTE,                     // 0x03E8
        MTF_TYPE_DDE, DecodeGeneric }
};

INT gcMessages = sizeof(gaMsgs) / sizeof(MSGDESC);

//
// Arrays of integers large enough to hold the indexes of all
// the messages in the message table.  This is used when
// selecting messages with the Messages dialog.
//
INT gaiSelected[sizeof(gaMsgs) / sizeof(MSGDESC)];
INT gaiSelected2[sizeof(gaMsgs) / sizeof(MSGDESC)];

MSGGROUP gaMsgGroup[] =
{
    { DID_MSGSDDE,      MTF_TYPE_DDE,   0,  0 },
    { DID_MSGSCLIP,     MTF_TYPE_CLIP,  0,  0 },
    { DID_MSGSMOUSE,    MTF_TYPE_MOUSE, 0,  0 },
    { DID_MSGSNC,       MTF_TYPE_NC,    0,  0 },
    { DID_MSGSKEYBD,    MTF_TYPE_KEYBD, 0,  0 },
#ifdef FE_IME
    { DID_MSGSIME,      MTF_TYPE_IME,   0,  0 },
#endif
    { DID_MSGSBM,       MTF_TYPE_BM,    0,  0 },
    { DID_MSGSCB,       MTF_TYPE_CB,    0,  0 },
    { DID_MSGSEM,       MTF_TYPE_EM,    0,  0 },
    { DID_MSGSLB,       MTF_TYPE_LB,    0,  0 },
    { DID_MSGSSTM,      MTF_TYPE_STM,   0,  0 }
};

INT gcMsgGroups = sizeof(gaMsgGroup) / sizeof(MSGGROUP);

INT __cdecl  MsgCmp(const void *p1, const void *p2);
PRIVATE VOID mprintf(LPSTR format, ...);

/*****************************************************************************\
* MsgCmp
*
* Callback function for qsort that sorts messages.
*
* Arguments:
*    void *p1 - pointer to first item
*    void *p2 - pointer to second item
*
* Returns:
*    -1 if first input less than second input
*    1  if second input is less than first input
*    0  if inputs are equal
*
\*****************************************************************************/

INT __cdecl
MsgCmp(
    const void *p1,
    const void *p2
    )
{
    MSGDESC *pmd1 = (PMSGDESC)p1;
    MSGDESC *pmd2 = (PMSGDESC)p2;

    return pmd1->msg < pmd2->msg ? -1 : (pmd1->msg > pmd2->msg ? 1 : 0);
}

/*****************************************************************************\
* CalculatePrintfTabs
*
* Calculates the tabs needed for each field in the printf window.  This
* is based on the largest string in the message table and the specified
* font.
*
* Arguments:
*   HFONT hfont - Font to use in the calculations.
*
* Returns:
*   VOID
\*****************************************************************************/

VOID
CalculatePrintfTabs(
    HFONT hfont
    )
{
    MSGDESC *pmd;
    HDC hdc;
    INT dxMsg;
    INT dxSpace;
    INT dxHwnd;
    INT tabs[3];
    SIZE siz;
    HFONT hfontOld;
    INT i;

    hdc = GetDC(NULL);
    hfontOld = SelectObject(hdc, hfont);
    dxMsg = 0;
    for (pmd = gaMsgs, i = 0; i < gcMessages; pmd++, i++)
    {
        GetTextExtentPoint(hdc, pmd->pszMsg, lstrlen(pmd->pszMsg), &siz);
        dxMsg = max(dxMsg, siz.cx);
    }

    //
    // Calculate the widest possible hwnd value.  It is assumed that the
    // font will have the same width for all digits (or that '0' would
    // be the widest).
    //
    GetTextExtentPoint(hdc, "00000000", 8, &siz);
    dxHwnd = siz.cx;
    GetTextExtentPoint(hdc, "AAAAAAAA", 8, &siz);
    dxHwnd = max(dxHwnd, siz.cx);
    GetTextExtentPoint(hdc, "BBBBBBBB", 8, &siz);
    dxHwnd = max(dxHwnd, siz.cx);
    GetTextExtentPoint(hdc, "CCCCCCCC", 8, &siz);
    dxHwnd = max(dxHwnd, siz.cx);
    GetTextExtentPoint(hdc, "DDDDDDDD", 8, &siz);
    dxHwnd = max(dxHwnd, siz.cx);
    GetTextExtentPoint(hdc, "EEEEEEEE", 8, &siz);
    dxHwnd = max(dxHwnd, siz.cx);
    GetTextExtentPoint(hdc, "FFFFFFFF", 8, &siz);
    dxHwnd = max(dxHwnd, siz.cx);

    GetTextExtentPoint(hdc, " ", 1, &siz);
    dxSpace = siz.cx;

    SelectObject(hdc, hfontOld);
    ReleaseDC(NULL, hdc);

    tabs[0] = dxHwnd + dxSpace;
    tabs[1] = tabs[0] + dxMsg + dxSpace;
    tabs[2] = tabs[1] + dxHwnd + dxSpace;

    SetPrintfTabs(3, tabs);
}

/*****************************************************************************\
* PrintMsg
*
* Writes out a specified message.
*
* Arguments:
*    LPMSG lpMsg - message to print out
*
* Returns:
*    VOID
\*****************************************************************************/

VOID
PrintMsg(
    LPMSG lpMsg
    )
{
    static MSGDESC md;
    PMSGDESC pmd;

    md.msg = lpMsg->message;
    pmd = (PMSGDESC)bsearch(&md, gaMsgs, gcMessages, sizeof(MSGDESC), MsgCmp);

    if (pmd)
    {
        if (pmd->Flags & MTF_SELECTED)
        {
            mprintf(szFormatName, lpMsg->hwnd, pmd->pszMsg, lpMsg->wParam,
                lpMsg->lParam);
        }
    }
    else
    {
        if (lpMsg->message >= WM_USER)
        {
            if (gfMsgsUser)
            {
                mprintf(szFormatUSER, lpMsg->hwnd, lpMsg->message - WM_USER,
                    lpMsg->wParam, lpMsg->lParam);
            }
        }
        else
        {
            if (gfMsgsUnknown)
            {
                mprintf(szFormatUnknown, lpMsg->hwnd, lpMsg->message,
                    lpMsg->wParam, lpMsg->lParam);
            }
        }
    }
}

/*****************************************************************************\
* mprintf
*
* Writes out a message to the specified device.
*
* Arguments:
*   LPSTR format - Format string.
*   ...          - Optional arguments.
*
* Returns:
*
*
\*****************************************************************************/
PUBLIC INT FAR cdecl
ITvwprintf(
HWND  hwnd,
LPSTR format,
va_list marker
)
{
    LPSTR p;
    TCHAR szBuffer[512];
    for( p = format; *p != '\0'; p++ ) {
        if (*p == '\r')
            *p = ' ';
    }

    wvsprintf(szBuffer, format, marker );
    return SendMessage( hwnd, WM_VWPRINTF, (WPARAM)szBuffer, (LPARAM)0 );
}

PRIVATE VOID
mprintf(
    LPSTR format,
    ...
    )
{
    va_list marker;
    CHAR szTemp[MAXSTRING];
    INT iLen;

    va_start(marker, format);

    if (gfOutputWin)
    {
        ITvwprintf(ghwndPrintf, format, marker);
    }

#if 0
    if (gfOutputCom1)
    {
        iLen = wvsprintf(szTemp, format, marker);
        M_lwrite(INT2HFILE(FH_COM1), szTemp, iLen);
    }
#else //BUGBUG this doesn't work under NT.  The com device needs to be explicitly opened.
    if (gfOutputCom1 && gfhCom1 != INVALID_HANDLE_VALUE)
    {
        DWORD   dwBytesWritten;

        iLen = wvsprintf(szTemp, format, marker);
        WriteFile(gfhCom1, szTemp, iLen, &dwBytesWritten, NULL);
        WriteFile(gfhCom1, "\r\n", 2,    &dwBytesWritten, NULL);
    }
#endif// fix by Japanese team

    if (gfOutputFile && gfhFile)
    {
        iLen = wvsprintf(szTemp, format, marker);
        _lwrite(gfhFile, szTemp, iLen);
//BUGBUG        _lclose(DUPHFILE(gfhFile));    /* flush the file buffer */
    }

    va_end(marker);
}


WPRINTF.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: wprintf.c
*
*   Contains the routines for using printf windows
*
* Functions:
*
*   MyCreatePrintfWin()
*   SetPrintFont()
*   SetPrintfTabs()
*   ClearPrintfWindow()
*   CopyToClipboard()
*   IsPrintfEmpty()
*   PrintfWndProc()
*   DebugPaint()
*   InsertString()
*   DebugHScroll()
*   DebugVScroll()
*   SetWindowClass()
*   LinesInDebugWindow()
*   CharsInDebugWindow()
*   wprintfSetScrollRange()
*   NewLine()
*
* Comments:
*
\*****************************************************************************/

#include "spy.h"

/*****************************************************************************\
*
*  g e n e r a l   c o n s t a n t s
*
\*****************************************************************************/

#define MAXBUFLEN 200         /* Maximum string length for wprintf */

#define FIRST(pTxt) ((pTxt)->iFirst)
#define TOP(pTxt)   (((pTxt)->iFirst + (pTxt)->iTop) % (pTxt)->iMaxLines)
#define LAST(pTxt)  (((pTxt)->iFirst + (pTxt)->iCount-1) % (pTxt)->iMaxLines)
#define INC(pTxt,x) ((x) = ++(x) % (pTxt)->iMaxLines)
#define DEC(pTxt,x) ((x) = --(x) % (pTxt)->iMaxLines)
#define OFFSETX (pTxt->Tdx/2)
#define OFFSETY 1
#define VARSIZE 1

#define BOUND(x,mn,mx) ((x) < (mn) ? (mn) : ((x) > (mx) ? (mx) : (x)))

#define FTwixtI3(l,x,h) ((x)>=(l) && (x<=h))

#define EnterCrit(p)
#define LeaveCrit(p)

/*****************************************************************************\
*
*   g l o b a l   v a r i a b l e s
*
\*****************************************************************************/

typedef struct {
    INT     iLen;
    CHAR    * *hText;
}   LINE;

struct TEXT_STRUCT {
    CRITICAL_SECTION csSync;      // CritSect to sync the threads

    INT     iFirst;               /* First line in queue */
    INT     iCount;               /* Number of lines in queue */
    INT     iTop;                 /* Line at top of window */
    INT     iLeft;                /* X offset of the window */
    INT     MaxLen;               /* Max String Length */
    INT     iMaxLines;            /* max number of LINEs */
    HFONT   hFont;                /* Font to draw with */
    DWORD   Tdx, Tdy;             /* Font Size */
    LINE    arLines[VARSIZE];     /* array of iMaxLines LINEs */
};

typedef struct TEXT_STRUCT *PTXT; /* pointer to a text struct */
typedef PTXT               *HTXT; /* Handle to a text struct */

PRIVATE INT iSem = 0;

INT tabs[20];
INT nTabs = 0;

/*****************************************************************************\
*
*   f u n c t i o n   d e f i n i t i o n s
*
\*****************************************************************************/

LONG APIENTRY PrintfWndProc(HWND, UINT, WPARAM, LONG);

PRIVATE VOID DebugPaint(HWND hwnd, LPPAINTSTRUCT pps);
PRIVATE INT  InsertString (PTXT, CHAR *);
PRIVATE VOID DebugHScroll(HWND, PTXT, INT);
PRIVATE VOID DebugVScroll(HWND, PTXT, INT);
PRIVATE BOOL SetWindowClass (HANDLE, LPSTR);
PRIVATE INT  LinesInDebugWindow (HWND);
PRIVATE INT  CharsInDebugWindow (HWND);
PRIVATE VOID wprintfSetScrollRange (HWND, BOOL);
PRIVATE VOID NewLine (PTXT pTxt);
PRIVATE INT mwprintf( HWND hwnd, LPSTR format, ... );

/*****************************************************************************\
* MyCreatePrintfWin
*
* Creates a window to printf to.
*
* Arguments:
*    HWND hwnd - handle to the parent window
*
* Returns:
*    VOID
\*****************************************************************************/

VOID
MyCreatePrintfWin(
    HWND hwnd
    )
{
    RECT rc;

    if (ghwndPrintf)
        DestroyWindow(ghwndPrintf);

    GetClientRect(hwnd, &rc);

    ghwndPrintf = CreatePrintfWin(hwnd, ghInst, "", WS_CHILD | WS_VSCROLL |
        WS_HSCROLL, -gcxBorder, -gcyBorder, rc.right + (2 *gcxBorder),
        rc.bottom + (2 * gcyBorder), gnLines);
}

/*****************************************************************************\
* DebugPaint(hwnd, pps)
*
* The paint function.
*
* Arguments:
*    HWND hwnd - Window to paint to.
*    LPPAINTSTRUCT - pps
*
*   Returns:
*       nothing
*
\*****************************************************************************/

PRIVATE VOID
DebugPaint(
    HWND hwnd,
    LPPAINTSTRUCT pps
    )
{
    PTXT pTxt;
    HTXT hTxt;
    INT iQueue;
    INT xco;
    INT yco;
    INT iLast;
    HBRUSH hb;
    COLORREF c;

    hTxt = (HTXT)GetWindowLong(hwnd, 0);
    pTxt = *hTxt;

    SetTextColor(pps->hdc, GetSysColor(COLOR_WINDOWTEXT));
    c = GetSysColor(COLOR_WINDOW);
    SetBkColor(pps->hdc, c);
    hb = CreateSolidBrush(c);
    if (pTxt->hFont)
        SelectObject(pps->hdc, pTxt->hFont);

    iLast  = LAST(pTxt);
    iQueue = TOP(pTxt);

    xco = OFFSETX - pTxt->iLeft * pTxt->Tdx;
    yco = OFFSETY;

    for (;;)
    {
        if (yco <= pps->rcPaint.bottom &&
                (yco + (LONG)(pTxt->Tdy)) >= pps->rcPaint.top)
        {
            if (pTxt->arLines[iQueue].hText == NULL
                || (LPSTR)*(pTxt->arLines[iQueue].hText) == NULL)
            {
                RECT rcT;

                rcT.top = yco;
                rcT.bottom = yco+pTxt->Tdy;
                rcT.left = pps->rcPaint.left;
                rcT.right = pps->rcPaint.right;
                FillRect(pps->hdc, &rcT, hb);
            }
            else
            {
                TabbedTextOut(pps->hdc, xco, yco,
                    (LPSTR)*(pTxt->arLines[iQueue].hText),
                    pTxt->arLines[iQueue].iLen, nTabs, tabs, xco);
            }
        }

        if (iQueue == iLast)
            break;

        yco += pTxt->Tdy;
        INC(pTxt, iQueue);
    }

    DeleteObject((HANDLE)hb);
}

/*****************************************************************************\
* SetWindowClass (hInstance)
*
* Registers the window class of the printf window
*
* Arguments:
*   HANDLE hInstance - instance handle of current instance
*   LPSTR lpch - pointer to class name
*
* Returns:
*   TRUE if successful, FALSE if not
\*****************************************************************************/

PRIVATE BOOL
SetWindowClass(
    HANDLE hInstance,
    LPSTR lpch
    )
{
    WNDCLASS wc;

    wc.style          = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc    = PrintfWndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = sizeof(HANDLE);
    wc.hInstance      = hInstance;
    wc.hIcon          = NULL;
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = lpch;

    return RegisterClass(&wc);
}

/*****************************************************************************\
* CreatePrintfWin (hParent, lpchName, dwStyle, x, y, dx, dy, iMaxLines)
*
* Creates a window for the depositing of debuging messages.
*
* Arguments:
*   HWND hParent - Window handle of the parent window.
*   HANDLE hInstance - Module instance handle.
*   lPSTR lpchName - String to appear in the caption bar of the debuging window
*   DWORD dwStyle - Window style
*   INT x,y - Location of window
*   INT dx,dy - Size of the window
*   INT iMaxLines - The maximum number of text lines to display in the window
*
* Returns:
*   A window handle of the debuging window, or NULL if a error occured.
\*****************************************************************************/

PUBLIC HWND APIENTRY
CreatePrintfWin (
HWND   hParent,
HANDLE hInstance,
LPSTR  lpchName,
DWORD  dwStyle,
INT   x,
INT   y,
INT   dx,
INT   dy,
INT    iMaxLines
)
{
    static BOOL bClass = FALSE;   /* Is the class registered */

    HWND   hwnd;
    HTXT   hTxt;      /* handle to a debuging window struct */
    PTXT   pTxt;
    static CHAR achClass[40];

    /*
        *  Make a Class name that is unique across instances
        */
    if (!bClass++) {
        wsprintf(achClass, "WPRINTF_%4.4X", hInstance);
        SetWindowClass(hInstance, achClass);
    }

    /* Allocate the window long before create the window, such that the
           window proc can find the window long during the create. */

    hTxt = (HTXT)LocalAlloc(LHND, sizeof(struct TEXT_STRUCT) + (iMaxLines
        - VARSIZE) * sizeof(LINE));

    if (!hTxt) {
        return FALSE;
    }

    pTxt = *hTxt;

    //InitializeCriticalSection(&pTxt->csSync);

    pTxt->iFirst            = 0;    /* Set the queue up to have 1 NULL line */
    pTxt->iCount            = 1;
    pTxt->iTop              = 0;
    pTxt->iLeft             = 0;
    pTxt->MaxLen            = 0;
    pTxt->iMaxLines         = iMaxLines;
    pTxt->arLines[0].hText  = NULL;
    pTxt->arLines[0].iLen   = 0;

    hwnd = CreateWindow((LPSTR)achClass, (LPSTR)lpchName, dwStyle, x, y,
        dx, dy, (HWND)hParent,     /* parent window */
    (HMENU)NULL,       /* use class menu */
    (HANDLE)hInstance, /* handle to window instance */
    (LPSTR)hTxt        /* used by WM_CREATE to set the window long */
    );

    if (!hwnd) {
        return FALSE;
    }

    wprintfSetScrollRange(hwnd, FALSE);

    /* Make window visible */
    ShowWindow(hwnd, SHOW_OPENWINDOW);
    return hwnd;
}

/*****************************************************************************\
* SetPrintfFont (hwnd,hFont)
*
* Sets the font for the printf window.
*
* Arguments:
*   HWND hwnd - Window handle of the printf window.
*   HFONT hFont - Font handle
*
* Returns:
*
\*****************************************************************************/

VOID
SetPrintfFont(
    HWND hwnd,
    HFONT hfont
    )
{
    PTXT pTxt;
    HDC hDC;
    TEXTMETRIC tm;
    HFONT hfontOld;

    pTxt = *(HTXT)GetWindowLong(hwnd, 0);
    pTxt->hFont = hfont;

    /* Find out the size of a Char in the font */
    hDC = GetDC(hwnd);
    hfontOld = SelectObject(hDC, hfont);
    DeleteObject(hfontOld);
    GetTextMetrics(hDC, &tm);
    pTxt->Tdy = tm.tmHeight;
    pTxt->Tdx = tm.tmAveCharWidth;
    ReleaseDC(hwnd, hDC);

    CalculatePrintfTabs(hfont);

    InvalidateRect(hwnd, NULL, TRUE);
    UpdateWindow(hwnd);
}

/*****************************************************************************\
* SetPrintfTabs
*
* Sets the Tabstops in the printf window.
*
* Arguments:
*    INT n - number of tabs to set.
*    LPINT pTabs - arrays of tabstops
*
* Returns:
*    VOID
\*****************************************************************************/

VOID
SetPrintfTabs(
    INT n,
    LPINT pTabs
    )
{
    INT i;

    nTabs = n;
    for (i = 0; i < nTabs; i++)
        tabs[i] = *pTabs++;
}

/*****************************************************************************\
* ClearPrintfWindow
*
* Clears all text from the window
*
* Arguments:
*    HWND hwnd - window handle for the Degubing window
*
* Returns:
*    VOID
\*****************************************************************************/

VOID
ClearPrintfWindow(
    HWND hwnd
    )
{
    INT   i, iQueue;
    PTXT  pTxt;
    HTXT  hTxt;

    if (hwnd != NULL && IsWindow(hwnd)) {
        hTxt  = (HTXT)GetWindowLong(hwnd, 0);
        pTxt = *hTxt;

        EnterCrit(pTxt);

        iQueue = TOP(pTxt);
        for (i = 0; i < (pTxt)->iCount; i++, INC(pTxt, iQueue))
            if ((pTxt)->arLines[iQueue].hText != NULL) {
                LocalFree ((HANDLE)pTxt->arLines[iQueue].hText);
                pTxt->arLines[iQueue].hText = NULL;
            }

        pTxt->iFirst            = 0;  /* Set the queue up to have 1 NULL line */
        pTxt->iCount            = 1;
        pTxt->iTop              = 0;
        pTxt->iLeft             = 0;
        pTxt->MaxLen            = 0;
        pTxt->arLines[0].hText  = NULL;
        pTxt->arLines[0].iLen   = 0;

        wprintfSetScrollRange(hwnd, FALSE);
        InvalidateRect(hwnd, NULL, TRUE);

        LeaveCrit(pTxt);
    }
}

/*****************************************************************************\
* PrintfWndProc( hwnd, uiMessage, wParam, lParam )
*
* The window proc for the debugging window.  This processes all
* of the window's messages.
*
* Arguments:
*   HWND hwnd - window handle for the parent window
*   UINT uiMessage -message number
*   WPARAM wParam - message-dependent
*   LPARAM lParam - message-dependent
*
* Returns:
*   0 if processed, nonzero if ignored
\*****************************************************************************/

PUBLIC LONG APIENTRY
PrintfWndProc(
HWND   hwnd,
UINT   uiMessage,
WPARAM wParam,
LONG   lParam
)
{
    PAINTSTRUCT rPS;
    HTXT        hTxt;
    PTXT        pTxt;

    hTxt  = (HTXT)GetWindowLong(hwnd, 0);

    if ( hTxt ) {
        pTxt = *hTxt;
    }

    switch (uiMessage) {
    case WM_CREATE:
        {
            /* set the WindowLong before any other message tries to
                         * reference it during the create of a window
                         */
            LPCREATESTRUCT csWindowLong = (LPCREATESTRUCT) lParam;

            hTxt = (HTXT)csWindowLong->lpCreateParams;

            SetWindowLong(hwnd, 0, (LONG)hTxt);
            SetPrintfFont(hwnd, ghfontPrintf);
            wprintfSetScrollRange(hwnd, FALSE);
        }
        break;

    case WM_DESTROY:
        {
            INT i, iQueue;

            EnterCrit(pTxt);

            iQueue = TOP(pTxt);
            for (i = 0; i < (pTxt)->iCount; i++, INC(pTxt, iQueue))
                if ((pTxt)->arLines[iQueue].hText != NULL) {
                    LocalFree ((HANDLE)(pTxt)->arLines[iQueue].hText);
                    pTxt->arLines[iQueue].hText = NULL;
                }

            LeaveCrit(pTxt);
            // DeleteCriticalSection(&pTxt->csSync);

            LocalFree((HANDLE)hTxt);
            break;
        }

    case WM_SIZE:
        EnterCrit(pTxt);
        if (!iSem) {
            wprintfSetScrollRange(hwnd, TRUE);
        }
        DebugVScroll(hwnd, pTxt, 0);
        LeaveCrit(pTxt);
        break;

    case WM_VSCROLL:
        EnterCrit(pTxt);

        switch (LOWORD(wParam)) {
        case SB_LINEDOWN:
            DebugVScroll(hwnd, pTxt, 1);
            break;
        case SB_LINEUP:
            DebugVScroll(hwnd, pTxt, -1);
            break;
        case SB_PAGEUP:
            DebugVScroll(hwnd, pTxt, -LinesInDebugWindow(hwnd));
            break;
        case SB_PAGEDOWN:
            DebugVScroll(hwnd, pTxt, LinesInDebugWindow(hwnd));
            break;
        case SB_THUMBTRACK:
        case SB_THUMBPOSITION:
            DebugVScroll(hwnd, pTxt, HIWORD(wParam) - pTxt->iTop);
            break;
        case SB_ENDSCROLL:
            break;
        }

        LeaveCrit(pTxt);
        break;

    case WM_HSCROLL:
        EnterCrit(pTxt);

        switch (LOWORD(wParam)) {
        case SB_LINEDOWN:
            DebugHScroll (hwnd, pTxt, 1);
            break;
        case SB_LINEUP:
            DebugHScroll (hwnd, pTxt, -1);
            break;
        case SB_PAGEUP:
            DebugHScroll (hwnd, pTxt, -CharsInDebugWindow(hwnd));
            break;
        case SB_PAGEDOWN:
            DebugHScroll (hwnd, pTxt, CharsInDebugWindow(hwnd));
            break;
        case SB_THUMBTRACK:
        case SB_THUMBPOSITION:
            DebugHScroll(hwnd, pTxt, HIWORD(wParam) - pTxt->iLeft);
            break;
        case SB_ENDSCROLL:
            break;
        }

        LeaveCrit(pTxt);
        break;

    case WM_PAINT:
        EnterCrit(pTxt);

        BeginPaint(hwnd, (LPPAINTSTRUCT) & rPS);
        DebugPaint(hwnd, &rPS);
        EndPaint(hwnd, (LPPAINTSTRUCT) & rPS);

        LeaveCrit(pTxt);
        break;

    case WM_KEYDOWN:
        EnterCrit(pTxt);

        switch (wParam) {
        case VK_UP:
            DebugVScroll(hwnd, pTxt, -1);
            break;
        case VK_DOWN:
            DebugVScroll(hwnd, pTxt, 1);
            break;
        case VK_PRIOR:
            DebugVScroll(hwnd, pTxt, -LinesInDebugWindow(hwnd));
            break;
        case VK_NEXT:
            DebugVScroll(hwnd, pTxt, LinesInDebugWindow(hwnd));
            break;
        case VK_LEFT:
            DebugHScroll(hwnd, pTxt, -1);
            break;
        case VK_RIGHT:
            DebugHScroll(hwnd, pTxt, 1);
            break;
        }

        LeaveCrit(pTxt);
        break;

    case WM_KEYUP:
        break;

    case WM_VWPRINTF:
        return mwprintf( hwnd, (LPSTR)"%s", (LPSTR)wParam );

    default:
        return DefWindowProc(hwnd, uiMessage, wParam, lParam);
    }
    return 0L;
}

/*****************************************************************************\
* DebugScroll
*
* Scrolls the debug window vertically
*
* Arguments:
*    HWND hwnd - handle to the debug window
*    PTXT pTxt - pointer to the text to scroll
*    INT n - number of lines to scroll
*
* Returns:
*    VOID
\*****************************************************************************/

PRIVATE VOID
DebugVScroll(
HWND hwnd,
PTXT pTxt,
INT  n
)
{
    RECT rect;
    INT  iMinPos, iMaxPos;

    GetScrollRange(hwnd, SB_VERT, (LPINT) &iMinPos, (LPINT) &iMaxPos);
    GetClientRect(hwnd, (LPRECT) &rect);
    rect.left += OFFSETX;
    rect.top  += OFFSETY;

    n = BOUND(pTxt->iTop + n, iMinPos, iMaxPos) - pTxt->iTop;
    if (n == 0)
        return;

    pTxt->iTop += n;
    ScrollWindow(hwnd, 0, -n * pTxt->Tdy, (LPRECT) &rect, (LPRECT) &rect);
    SetScrollPos(hwnd, SB_VERT, pTxt->iTop, TRUE);
}

/*****************************************************************************\
* DebugHScroll
*
* Performs the horizontal scroll calculations
*
* Arguments:
*    HWND hwnd - handle to the debug window
*    PTXT pTxt - pointer to the text to scroll
*    INT n - number of characters to scroll
*
* Returns:
*    VOID
\*****************************************************************************/

PRIVATE VOID
DebugHScroll(
HWND hwnd,
PTXT pTxt,
INT  n
)
{
    RECT rect;
    INT  iMinPos, iMaxPos;

    GetScrollRange (hwnd, SB_HORZ, (LPINT) &iMinPos, (LPINT) &iMaxPos);
    GetClientRect (hwnd, (LPRECT) & rect);
    rect.left += OFFSETX;
    rect.top  += OFFSETY;

    n = BOUND(pTxt->iLeft + n, iMinPos, iMaxPos) - pTxt->iLeft;
    if (n == 0)
        return;

    pTxt->iLeft += n;
    ScrollWindow(hwnd, -n * pTxt->Tdx, 0, (LPRECT) & rect, (LPRECT) & rect);
    SetScrollPos(hwnd, SB_HORZ, pTxt->iLeft, TRUE);
}

/*****************************************************************************\
* LinesInDebugWindow
*
* Calculates the number of lines in the debug window
*
* Arguments:
*    HWND hwnd - handle to the debug window
*
* Returns:
*    INT - number of lines in the debug window
\*****************************************************************************/

PRIVATE INT
LinesInDebugWindow (
HWND hwnd
)
{
    RECT CRect;
    PTXT pTxt;

    pTxt = *(HTXT)GetWindowLong(hwnd, 0);
    GetClientRect(hwnd, &CRect);
    if ( pTxt->Tdy == 0 ) {
        return 0;
    }
    return pTxt ? (CRect.bottom - CRect.top - OFFSETY) / pTxt->Tdy : 0;
}

/*****************************************************************************\
* CharsInDebugWindow
*
* Calculates the number of characters horizontally in the debug window
*
* Arguments:
*    HWND hwnd - handle to the debug window
*
* Returns:
*    INT - number of horizontal characters in the debug window
\*****************************************************************************/

PRIVATE INT
CharsInDebugWindow (
HWND hwnd
)
{
    RECT CRect;
    PTXT pTxt;

    pTxt = *(HTXT)GetWindowLong (hwnd, 0);
    GetClientRect(hwnd, (LPRECT) & CRect);
    if ( pTxt->Tdx == 0 ) {
        return 0;
    }
    return pTxt ? (CRect.right - CRect.left - OFFSETX) / pTxt->Tdx : 0;
}

PRIVATE INT
mwprintf(
    HWND hwnd,
    LPSTR format,
    ...
    )
{
    va_list marker;
    INT i;

    va_start(marker, format);

    i = vwprintf(hwnd, format, marker);

    va_end(marker);

    return i;
}

/*****************************************************************************\
* vwprintf
*
* variable printf - works like the C runtime printf
*
* Arguments:
*    HWND hwnd - handle to the debug window
*    LPSTR format - pointer to the format string
*    va_list marker - pointer to marker
*
* Returns:
*    INT - number of arguments printed
\*****************************************************************************/
PUBLIC INT FAR cdecl
vwprintf(
HWND  hwnd,
LPSTR format,
va_list marker
)
{
    static HWND hwndLast = NULL;
    static CHAR  pchBuf[MAXBUFLEN];
    RECT  rect, rcClient;
    INT   iRet;
    INT   cLinesDisplayed;       // lines of output to show
    INT   cLinesFitInWindow;    // lines that can fit in the current window
    INT   cLinesNew;  // how much left to scroll
    PTXT  pTxt;
    HTXT  hTxt;
    BOOL  fNoScrollB;

    if (hwnd == NULL)
        hwnd = hwndLast;

    if (hwnd == NULL || !IsWindow(hwnd))
        return 0;  /* exit if bad window handle */

    hwndLast = hwnd;

    //
    // First format the line and wait until we can play with the Txt structure
    //
    iRet = wvsprintf((LPSTR)pchBuf, format, marker);
    hTxt = (HTXT)GetWindowLong(hwnd, 0);
    pTxt = (PTXT)LocalLock((HANDLE)hTxt);
    if(pTxt == NULL)
        return 0;  // exit if bad memory block

    EnterCrit(pTxt);

    //
    // Number of lines that we can display stuff in
    //
    cLinesFitInWindow   = LinesInDebugWindow(hwnd);

    if (cLinesFitInWindow > pTxt->iMaxLines) {
        fNoScrollB = TRUE;
        cLinesFitInWindow = pTxt->iMaxLines;
    } else {
        fNoScrollB = FALSE;
    }

    //
    // Number of lines actually displayed in the current window
    //
    cLinesDisplayed   = min(pTxt->iCount, cLinesFitInWindow);

    //
    // Return value is number of new lines to display
    //
    cLinesNew = InsertString(pTxt, pchBuf);

    //
    // Now make sure the new text is painted only if visable
    //
    GetClientRect(hwnd, (LPRECT) & rect);
    rcClient = rect;

    //
    // Calculate how much of the window to invalidate
    //
    rect.top += (cLinesDisplayed - 1) * pTxt->Tdy;

    InvalidateRect(hwnd, (LPRECT)&rect, TRUE);

    //
    // If we have more lines than we can display, scroll the window
    // such that the last line printed is now at the bottom
    //
    if (cLinesDisplayed + cLinesNew > cLinesFitInWindow) {
        cLinesNew = cLinesDisplayed + cLinesNew - cLinesFitInWindow;

        if (fNoScrollB) {
            rcClient.bottom = cLinesDisplayed * pTxt->Tdy;
            ScrollWindow(hwnd, 0, -cLinesNew * pTxt->Tdy, (LPRECT) &rcClient, (LPRECT) &rcClient);
        } else {
            wprintfSetScrollRange(hwnd, FALSE);
            DebugVScroll(hwnd, pTxt, cLinesNew);
        }
        LeaveCrit(pTxt);
    } else {
        LeaveCrit(pTxt);
    }

    LocalUnlock((HANDLE)hTxt);

    return(iRet);       /* return the count of arguments printed */
}

/*****************************************************************************\
* wprintfSetScrollRange
*
* Sets the scroll range of the debug window
*
* Arguments:
*    HWND hwnd - handle to the debug window
*    BOOL fRedraw - whether or not to redraw the window
*
* Returns:
*    VOID
\*****************************************************************************/

PRIVATE VOID
wprintfSetScrollRange (
HWND hwnd,
BOOL bRedraw
)
{
    PTXT pTxt;
    INT  iRange;
    INT  iLeftCritSect = 0;

    iSem++;
    pTxt = *(HTXT)GetWindowLong(hwnd, 0);

    /* Update the scroll bars */
    iRange = pTxt->iCount - 1 - LinesInDebugWindow(hwnd);

    if (iRange < 0) {
        iRange = 0;
        DebugVScroll(hwnd, pTxt, -pTxt->iTop);
    }

    while (GetCurrentThreadId() == (DWORD)pTxt->csSync.OwningThread){
LeaveCrit(pTxt);
iLeftCritSect++;
    }

    SetScrollRange(hwnd, SB_VERT, 0, iRange, FALSE);
    SetScrollPos(hwnd, SB_VERT, pTxt->iTop, bRedraw);

    if(iLeftCritSect) {
EnterCrit(pTxt);
    }

    iRange = pTxt->MaxLen - CharsInDebugWindow(hwnd) + 1;

if (iRange < 0) {
        iRange = 0;
        DebugHScroll(hwnd, pTxt, -pTxt->iLeft);
    }

    if(iLeftCritSect)
LeaveCrit(pTxt);

    SetScrollRange(hwnd, SB_HORZ, 0, iRange, FALSE);
    SetScrollPos(hwnd, SB_HORZ, pTxt->iLeft, bRedraw);

    while (iLeftCritSect--) {
EnterCrit(pTxt);
    }

    iSem--;
}

/*****************************************************************************\
* NewLine
*
* Calculates when a new line is needed in the debug window
*
* Arguments:
*    PTXT pTxt - pointer to the text
*
* Returns:
*    VOID
\*****************************************************************************/

PRIVATE VOID
NewLine (
PTXT pTxt
)
{
    INT iLast = LAST(pTxt);

    if (pTxt->iCount == pTxt->iMaxLines) {
        LocalFree ((HANDLE)pTxt->arLines[pTxt->iFirst].hText);
        pTxt->arLines[pTxt->iFirst].hText = NULL;
        INC (pTxt, pTxt->iFirst);
        if (pTxt->iTop > 0) {
            pTxt->iTop--;
        }
    } else {
        pTxt->iCount++;
    }
    iLast = LAST(pTxt);
    pTxt->arLines[iLast].hText = NULL;
    pTxt->arLines[iLast].iLen  = 0;
}

/*****************************************************************************\
* InsertString
*
* Inserts a string into the debug window
*
* Arguments:
*    PTXT pTxt - pointer to the text
*    CHAR *str - pointer to insertion string
*
* Returns:
*    INT - Line number at which string was inserted
\*****************************************************************************/

PRIVATE INT
InsertString (
PTXT  pTxt,
CHAR  *str
)
{
    CHAR   pchBuf[MAXBUFLEN];        /* intermediate buffer */
    INT    iBuf;
    INT    iLast = LAST(pTxt);
    INT    cLine = 0;

    for (iBuf = 0; iBuf < pTxt->arLines[iLast].iLen; iBuf++)
        pchBuf[iBuf] = (*pTxt->arLines[iLast].hText)[iBuf];

    while (*str != '\0') {
        while ((*str != '\n') && (*str != '\0'))
            pchBuf[iBuf++] = *str++;

        if (pTxt->arLines[iLast].hText != NULL)
            LocalFree((HANDLE)pTxt->arLines[iLast].hText);

        /* Test for the case of a zero length line, Only brian would do this */

        if (iBuf == 0)
            pTxt->arLines[iLast].hText == NULL;
        else {
            if ((pTxt->arLines[iLast].hText = (CHAR **)LocalAlloc(LHND, iBuf))
                == NULL) {
                return 0;
            }
        }

        pTxt->arLines[iLast].iLen = iBuf;
        while (--iBuf >= 0 )
            (*pTxt->arLines[iLast].hText)[iBuf] = pchBuf[iBuf];

        if (*str == '\n') {   /* Now do the next string after the \n */
            str++;
            cLine++;
            iBuf = 0;
            NewLine(pTxt);
            INC(pTxt, iLast);
        }
    }

    return cLine;
}

/*****************************************************************************\
* CopyToClipboard
*
* Copies all lines to the clipboard in text format.
*
* Arguments:
*   none
*
* Returns:
*   TRUE if successful, FALSE if not.
*
\*****************************************************************************/

BOOL
CopyToClipboard(
    VOID
    )
{
    PTXT pTxt;
    INT iQueue;
    INT cch;
    INT i;
    BOOL fSuccess = FALSE;
    LPSTR pBuf = NULL;
    LPSTR pb;

    pTxt = *(HTXT)GetWindowLong(ghwndPrintf, 0);

    EnterCrit(pTxt);

    iQueue = FIRST(pTxt);
    cch = 0;
    for (i = 0; i < pTxt->iCount; i++, INC(pTxt, iQueue))
    {
        if (pTxt->arLines[iQueue].hText != NULL)
        {
            //
            // Count the characters in the line, plus room for the
            // carriage return and newline.
            //
            cch += pTxt->arLines[iQueue].iLen;
            cch += 2;
        }
    }

    //
    // Add one for the terminating null.
    //
    cch++;

    if (!(pBuf = (LPSTR)GlobalAlloc(GMEM_DDESHARE, cch * sizeof(TCHAR))))
    {
        LeaveCrit(pTxt);
        return FALSE;
    }

    pb = pBuf;
    iQueue = FIRST(pTxt);
    for (i = 0; i < pTxt->iCount; i++, INC(pTxt, iQueue))
    {
        if (pTxt->arLines[iQueue].hText != NULL)
        {
            lstrcpy(pb, *pTxt->arLines[iQueue].hText);
            pb += pTxt->arLines[iQueue].iLen;
            *pb++ = '\r';
            *pb++ = '\n';
        }
    }

    LeaveCrit(pTxt);

    if (OpenClipboard(ghwndSpyApp))
    {
        EmptyClipboard();
        fSuccess = SetClipboardData(CF_TEXT, pBuf) ? TRUE : FALSE;
        CloseClipboard();
    }

    return fSuccess;
}

/*****************************************************************************\
* IsPrintfEmpty
*
* Used to determine if the printf window is empty or not.
*
* Arguments:
*   none
*
* Returns:
*   TRUE if the printf window is empty, FALSE if there is at least
*   one line in the window.
*
\*****************************************************************************/

BOOL
IsPrintfEmpty(
    VOID
    )
{
    PTXT pTxt;

    pTxt = *(HTXT)GetWindowLong(ghwndPrintf, 0);

    //
    // It is empty if the line count is zero (doesn't currently happen)
    // or if there is only one line and it is NULL.
    //
    return (pTxt->iCount == 0 ||
        (pTxt->iCount == 1 && pTxt->arLines[FIRST(pTxt)].hText == NULL))
        ? TRUE : FALSE;
}


WPRINTF.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.
\******************************************************************************/

/* These functions are used to debug apps quickly */
/* NOTE windows.h must be included prior to this file */

HWND APIENTRY CreatePrintfWindow (HWND,LPSTR,BOOL,INT);
HWND APIENTRY CreatePrintfWin (HWND,HANDLE,LPSTR,DWORD,INT,INT,INT,INT,INT);
INT FAR cdecl vwprintf(HWND hWnd, LPSTR format, va_list marker);

#define WM_VWPRINTF (WM_USER + 123)
#define WM_WPRINTF  (WM_USER + 124)


HOOK.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.
\******************************************************************************/

/*****************************************************************************\
* hook.c - Windows message spy application dll
*
* Functions:
*
* DllMain()
* FindSpyWindow()
* HookProc()
* SpyGetMsgProc()
* SpyCallWndProc()
* DbgPrintf()
*
* Comments:
*
\*****************************************************************************/

#include <windows.h>
#include "..\hook.h"

PRIVATE HWND ghwndSpyHook = NULL;   // the handle back to the spy executable
PRIVATE SPYMSGDATA gsmd;
PRIVATE COPYDATASTRUCT gcds = { 0, sizeof(SPYMSGDATA), &gsmd };

PRIVATE VOID FindSpyWindow(VOID);

#ifdef DBG
VOID DbgPrintf(LPTSTR fmt, ...);
#endif

/*****************************************************************************\
* DllMain (hModule,cbHeap,lpchCmdLine)
*
* Called when the libary is loaded
*
* Arguments:
*    PVOID hModule - Module handle for the libary.
*    ULONG ulReason - DLL purpose
*    PCONTEXT pctx - not used
*
* Returns:
*    TRUE - Everything is ok
*    FALSE- Error.
\*****************************************************************************/

BOOL
APIENTRY DllMain(
    PVOID hModule,
    ULONG ulReason,
    PCONTEXT pctx
    )
{
    UNREFERENCED_PARAMETER(hModule);
    UNREFERENCED_PARAMETER(pctx);

    //
    // This function is called for every instance of the DLL. We must find
    // and store the handle to the spy window every time an instance of the
    // DLL is instantiated.
    //
    if ( ulReason == DLL_PROCESS_ATTACH ) {
        FindSpyWindow();
    }

    return TRUE;
}

/*****************************************************************************\
* FindSpyWindow
*
* Finds the spy window and store a local copy in this instances data.
* This must be called everytime that a new instance of the DLL is
* created.
*
* Arguments:
*    none
*
* Returns:
*    VOID
\*****************************************************************************/

PRIVATE VOID
FindSpyWindow(
    VOID
    )
{
    ghwndSpyHook = FindWindow(HOOKWINDOWCLASS, HOOKWINDOWNAME);
}

/*****************************************************************************\
* HookProc( hWnd, uiMessage, wParam, lParam )
*
* The hook proc for the windows hook being spied on
*
* Arguments:
*    HWND hWnd - window handle for the parent window
*    UINT uiMessage - message number
*    WPARAM wParam - message-dependent
*    LPARAM lParam - message-dependent
*
* Returns:
*    0 if processed, nonzero if ignored
\*****************************************************************************/

BOOL WINAPI
HookProc(
    HWND hwnd,
    UINT uiMessage,
    WPARAM wParam,
    LPARAM lParam
    )
{
    HWND hwndSpyingOn;
    HWND hwndSpyApp;

    if (ghwndSpyHook == NULL || !IsWindow(ghwndSpyHook))
    {
        //
        // Spy has terminated. Find the new window.
        //
        FindSpyWindow();
    }

    if (ghwndSpyHook != NULL && hwnd != ghwndSpyHook)
    {
        hwndSpyingOn = (HWND)GetWindowLong(ghwndSpyHook, 0);
        hwndSpyApp = (HWND)GetWindowLong(ghwndSpyHook, sizeof(HWND));
//DbgPrintf("H ghwndSpyHook:%8.8x", ghwndSpyHook);
//DbgPrintf("H hwndSpyingOn:%8.8x", hwndSpyingOn);
//DbgPrintf("H hwndSpyApp:%8.8x", hwndSpyApp);

        //
        // Send the message on asynchronously for Spy to deal with if
        // it is the appropriate hwndSpyingOn window to spy on.
        //

        if (hwndSpyingOn == hwnd
            || (hwndSpyingOn == HWND_ALL && hwnd != hwndSpyApp
            && !IsChild(hwndSpyApp, hwnd)))
        {
            gsmd.wParam = wParam;
            gsmd.lParam = lParam;

            gcds.dwData = uiMessage;

//DbgPrintf("H Sending Message hwnd:%8.8x msg:%d", hwnd, uiMessage);
            SendMessage(ghwndSpyHook, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&gcds);
//DbgPrintf("H Sent Message hwnd:%8.8x msg:%d", hwnd, uiMessage);

//DbgPrintf("");
            return TRUE;
        }
//DbgPrintf("");
    }

    return FALSE;
}

/*****************************************************************************\
* SpyGetMsgProc
*
* The Get Message hook function.
*
\*****************************************************************************/

LRESULT CALLBACK
SpyGetMsgProc(
    INT hc,
    WPARAM wParam,
    LPARAM lParam
    )
{
    PMSG pmsg;

    pmsg = (PMSG)lParam;

    if (hc >= 0 && pmsg && pmsg->hwnd)
    {
        return HookProc(pmsg->hwnd, pmsg->message, pmsg->wParam, pmsg->lParam);
    }

    //
    // Note that CallNextHookEx ignores the first parameter (hhook) so
    // it is acceptable (barely) to pass in a NULL.
    //
    return CallNextHookEx(NULL, hc, wParam, lParam);
}

/*****************************************************************************\
* SpyCallWndProc
*
* The Call Window Proc (Send Message) hook function.
*
\*****************************************************************************/

LRESULT CALLBACK
SpyCallWndProc(
    INT hc,
    WPARAM wParam,
    LPARAM lParam
    )
{
    PCWPSTRUCT pcwps;

    pcwps = (PCWPSTRUCT)lParam;

    if (hc >= 0 && pcwps && pcwps->hwnd)
    {
        return HookProc(pcwps->hwnd, pcwps->message, pcwps->wParam, pcwps->lParam);
    }

    //
    // Note that CallNextHookEx ignores the first parameter (hhook) so
    // it is acceptable (barely) to pass in a NULL.
    //
    return CallNextHookEx(NULL, hc, wParam, lParam);
}

#ifdef DBG
/****************************************************************************
* DBGprintf
*
* This debugging function prints out a string to the debug output.
* An optional set of substitutional parameters can be specified,
* and the final output will be the processed result of these combined
* with the format string, just like printf.  A newline is always
* output after every call to this function.
*
* Arguments:
*   LPTSTR fmt - Format string (printf style).
*   ...        - Variable number of arguments.
* Returns:
*    VOID
\****************************************************************************/

VOID DbgPrintf(
    LPTSTR fmt,
    ...
    )
{
    va_list marker;
    TCHAR szBuf[256];

    va_start(marker, fmt);
    wvsprintf(szBuf, fmt, marker);
    va_end(marker);

    OutputDebugString(szBuf);
    OutputDebugString(TEXT("\r\n"));
}
#endif


README.TXT

Task List

The TList tool is a task list application.


COMMON.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*  Copyright (C) 1994-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 TITLE_SIZE          64
#define PROCESS_SIZE        MAX_PATH

//
// task list structure
//
typedef struct _TASK_LIST {
    DWORD       dwProcessId;
    DWORD       dwInheritedFromProcessId;
    BOOL        flags;
    HANDLE      hwnd;
    CHAR        ProcessName[PROCESS_SIZE];
    CHAR        WindowTitle[TITLE_SIZE];
} TASK_LIST, *PTASK_LIST;

typedef struct _TASK_LIST_ENUM {
    PTASK_LIST  tlist;
    DWORD       numtasks;
} TASK_LIST_ENUM, *PTASK_LIST_ENUM;

//
// Function pointer types for accessing platform-specific functions
//
typedef DWORD (*LPGetTaskList)(PTASK_LIST, DWORD);
typedef BOOL  (*LPEnableDebugPriv)(VOID);

//
// Function prototypes
//
DWORD
GetTaskList95(
    PTASK_LIST  pTask,
    DWORD       dwNumTasks
    );

DWORD
GetTaskListNT(
    PTASK_LIST  pTask,
    DWORD       dwNumTasks
    );

BOOL
EnableDebugPriv95(
    VOID
    );

BOOL
EnableDebugPrivNT(
    VOID
    );

BOOL
KillProcess(
    PTASK_LIST tlist,
    BOOL       fForce
    );

VOID
GetWindowTitles(
    PTASK_LIST_ENUM te
    );

BOOL
MatchPattern(
    PUCHAR String,
    PUCHAR Pattern
    );


KILL.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright (C) 1994-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 Name:

    kill.c

Abstract:

    This module implements a task killer application.

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"

#define MAX_TASKS           256

BOOL        ForceKill;
DWORD       pid;
CHAR        pname[MAX_PATH];
TASK_LIST   tlist[MAX_TASKS];

VOID GetCommandLineArgs(VOID);
VOID Usage(VOID);

int _cdecl
main(
    int argc,
    char *argv[]
    )
{
    DWORD             i;
    DWORD             numTasks;
    TASK_LIST_ENUM    te;
    int               rval = 0;
    char              tname[PROCESS_SIZE];
    LPSTR             p;
    DWORD             ThisPid;
    OSVERSIONINFO     verInfo = {0};
    LPGetTaskList     GetTaskList;
    LPEnableDebugPriv EnableDebugPriv;

    GetCommandLineArgs();

    if (pid == 0 && pname[0] == 0) {
        printf( "missing pid or task name\n"
                "type kill /? for help\n");
        return 1;
    }

    //
    // Determine what system we're on and do the right thing
    //
    verInfo.dwOSVersionInfoSize = sizeof (verInfo);
    GetVersionEx(&verInfo);

    switch (verInfo.dwPlatformId)
    {
    case VER_PLATFORM_WIN32_NT:
       GetTaskList     = GetTaskListNT;
       EnableDebugPriv = EnableDebugPrivNT;
       break;

    case VER_PLATFORM_WIN32_WINDOWS:
       GetTaskList = GetTaskList95;
       EnableDebugPriv = EnableDebugPriv95;
       break;

    default:
       printf ("tlist requires Windows NT or Windows 95\n");
       return 1;
    }

    //
    // Obtain the ability to manipulate other processes
    //
    EnableDebugPriv();

    if (pid) {
        tlist[0].dwProcessId = pid;
        if (KillProcess( tlist, TRUE )) {
            printf( "process #%d killed\n", pid );
            return 0;
        } else {
            printf( "process #%d could not be killed\n", pid );
            return 1;
        }
    }

    //
    // get the task list for the system
    //
    numTasks = GetTaskList( tlist, MAX_TASKS );

    //
    // enumerate all windows and try to get the window
    // titles for each task
    //
    te.tlist = tlist;
    te.numtasks = numTasks;
    GetWindowTitles( &te );

    ThisPid = GetCurrentProcessId();

    for (i=0; i<numTasks; i++) {
        //
        // this prevents the user from killing KILL.EXE and
        // it's parent cmd window too
        //
        if (ThisPid == tlist[i].dwProcessId) {
            continue;
        }
        if (MatchPattern( tlist[i].WindowTitle, "*KILL*" )) {
            continue;
        }

        tname[0] = 0;
        strcpy( tname, tlist[i].ProcessName );
        p = strchr( tname, '.' );
        if (p) {
            p[0] = '\0';
        }
        if (MatchPattern( tname, pname )) {
            tlist[i].flags = TRUE;
        } else if (MatchPattern( tlist[i].ProcessName, pname )) {
            tlist[i].flags = TRUE;
        } else if (MatchPattern( tlist[i].WindowTitle, pname )) {
            tlist[i].flags = TRUE;
        }
    }

    for (i=0; i<numTasks; i++) {
        if (tlist[i].flags) {
            if (KillProcess( &tlist[i], ForceKill )) {
                printf( "process #%d [%s] killed\n", tlist[i].dwProcessId, tlist[i].ProcessName );
            } else {
                printf( "process #%d [%s] could not be killed\n", tlist[i].dwProcessId, tlist[i].ProcessName );
                rval = 1;
            }
        }
    }

    return rval;
}

VOID
GetCommandLineArgs(
    VOID
    )
{
    char        *lpstrCmd;
    UCHAR       ch;
    char        *p = pname;

    pid = 0;
    *p = '\0';

    lpstrCmd = GetCommandLine();

    // skip over program name
    do
    {
       ch = *lpstrCmd++;
    }
    while (!isspace(ch));

    //  skip over any white space following the program name
    while (isspace(ch))
    {
       ch = *lpstrCmd++;
    }

    // Follow this pattern strictly:
    //  KILL [options] <<pid> | <pattern>>\n\n"
    //  where options is /f,
    //  and PID is a positive or negative decimal number, and
    //  pattern is a string of characters

    // Look for an option, which will be prefaced with '/'
    if (ch == '/')
    {
       ch = *lpstrCmd++;

       switch (ch)
       {
       case 'F':     // found "kill /f" so far
       case 'f':
          ForceKill = TRUE;
          ch = *lpstrCmd++;
          break;

       case '?':    // found "kill /?"
          Usage();
          return;   // don't do anything after printing usage
          break;

       default:
          printf ("%c is an invalid switch\n", ch);  // invalid switch
          return;
       }

       // eat whitepace after option switch
       while (isspace(ch))
       {
          ch = *lpstrCmd++;
       }
    }

    // In the 3 cases below, look for either a PID or a pattern
    // a PID could be a positive or negative decimal integer

    // Look for a negative PID
    if (ch == '-')
    {
       ch = *lpstrCmd++;

       while (isdigit(ch))
       {
          pid = pid * 10 - ( ch - '0' );
          ch = *lpstrCmd++;
       }
       return; // found "kill -pid" or "kill /f -pid" so we're done
    }

    // Look for a postive PID
    if (isdigit(ch))
    {
       while (isdigit(ch))
       {
          pid = pid * 10 + ch - '0';
          ch = *lpstrCmd++;
       }
       return;  // found "kill pid" or "kill /f pid" so we're done
    }

    // Look for a pattern
    while (ch)
    {
       *p++ = ch;
       ch = *lpstrCmd++;
    }
    *p = '\0';
    strupr(pname);

    return;  // found "kill pattern" or "kill /f pattern" so we're done
}

VOID
Usage(
    VOID
    )

/*++

Routine Description:

    Prints usage text for this tool.

Arguments:

    None.

Return Value:

    None.

--*/

{
    fprintf( stderr, "Microsoft (R) Windows (TM) KILL\n" );
    fprintf( stderr, "Copyright (C) 1994-1996 Microsoft Corp. All rights reserved\n\n" );
    fprintf( stderr, "usage: KILL [options] <<pid> | <pattern>>\n\n" );
    fprintf( stderr, "           [options]:\n" );
    fprintf( stderr, "               /?     This help\n\n" );
    fprintf( stderr, "               /f     Force process kill\n\n" );
    fprintf( stderr, "           <pid>\n" );
    fprintf( stderr, "              This is the process id for the task\n" );
    fprintf( stderr, "               to be killed.  Use TLIST to get a\n" );
    fprintf( stderr, "               valid pid\n\n" );
    fprintf( stderr, "           <pattern>\n" );
    fprintf( stderr, "              The pattern can be a complete task\n" );
    fprintf( stderr, "              name or a regular expression pattern\n" );
    fprintf( stderr, "              to use as a match.  Kill matches the\n" );
    fprintf( stderr, "              supplied pattern against the task names\n" );
    fprintf( stderr, "              and the window titles.\n" );
    ExitProcess(0);
}


COMMON.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright (C) 1994-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 Name:

    common.c

Abstract:

    This module contains common apis used by tlist & kill.

--*/

#include <windows.h>
#include <winperf.h>   // for Windows NT
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>  // for Windows 95

#include "common.h"

//
// manafest constants
//
#define INITIAL_SIZE        51200
#define EXTEND_SIZE         25600
#define REGKEY_PERF         "software\\microsoft\\windows nt\\currentversion\\perflib"
#define REGSUBKEY_COUNTERS  "Counters"
#define PROCESS_COUNTER     "process"
#define PROCESSID_COUNTER   "id process"
#define UNKNOWN_TASK        "unknown"

//
// Function pointer types for accessing Toolhelp32 functions dynamically.
// By dynamically accessing these functions, we can do so only on Windows
// 95 and still run on Windows NT, which does not have these functions.
//
typedef BOOL (WINAPI *PROCESSWALK)(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
typedef HANDLE (WINAPI *CREATESNAPSHOT)(DWORD dwFlags, DWORD th32ProcessID);

//
// prototypes
//
BOOL CALLBACK
EnumWindowsProc(
    HWND    hwnd,
    DWORD   lParam
    );

DWORD
GetTaskList95(
    PTASK_LIST  pTask,
    DWORD       dwNumTasks
    )

/*++

Routine Description:

    Provides an API for getting a list of tasks running at the time of the
    API call.  This function uses Toolhelp32to get the task list and is
    therefore straight WIN32 calls that anyone can call.

Arguments:

    dwNumTasks       - maximum number of tasks that the pTask array can hold

Return Value:

    Number of tasks placed into the pTask array.

--*/

{
   CREATESNAPSHOT pCreateToolhelp32Snapshot = NULL;
   PROCESSWALK    pProcess32First           = NULL;
   PROCESSWALK    pProcess32Next            = NULL;

   HANDLE         hKernel        = NULL;
   HANDLE         hProcessSnap   = NULL;
   PROCESSENTRY32 pe32           = {0};
   DWORD          dwTaskCount    = 0;

   // Guarantee to the code later on that we'll enum at least one task.
   if (dwNumTasks == 0)
      return 0;

    // Obtain a module handle to KERNEL so that we can get the addresses of
    // the 32-bit Toolhelp functions we need.
    hKernel = GetModuleHandle("KERNEL32.DLL");

    if (hKernel)
    {
        pCreateToolhelp32Snapshot =
          (CREATESNAPSHOT)GetProcAddress(hKernel, "CreateToolhelp32Snapshot");

        pProcess32First = (PROCESSWALK)GetProcAddress(hKernel,
                                                      "Process32First");
        pProcess32Next  = (PROCESSWALK)GetProcAddress(hKernel,
                                                      "Process32Next");
    }

    // make sure we got addresses of all needed Toolhelp functions.
    if (!(pProcess32First && pProcess32Next && pCreateToolhelp32Snapshot))
       return 0;

    // Take a snapshot of all processes currently in the system.
    hProcessSnap = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == (HANDLE)-1)
        return 0;

    // Walk the snapshot of processes and for each process, get information
    // to display.
    dwTaskCount = 0;
    pe32.dwSize = sizeof(PROCESSENTRY32);   // must be filled out before use
    if (pProcess32First(hProcessSnap, &pe32))
    {
        do
        {
            LPSTR pCurChar;

            // strip path and leave executabe filename splitpath
            for (pCurChar = (pe32.szExeFile + lstrlen (pe32.szExeFile));
                 *pCurChar != '\\' && pCurChar != pe32.szExeFile;
                 --pCurChar)

            lstrcpy(pTask -> ProcessName, pCurChar);
            pTask -> flags = 0;
            pTask -> dwProcessId = pe32.th32ProcessID;

            ++dwTaskCount;   // keep track of how many tasks we've got so far
            ++pTask;         // get to next task info block.
        }
        while (dwTaskCount < dwNumTasks && pProcess32Next(hProcessSnap, &pe32));
    }
    else
        dwTaskCount = 0;    // Couldn't walk the list of processes.

    // Don't forget to clean up the snapshot object...
    CloseHandle (hProcessSnap);

    return dwTaskCount;
}

DWORD
GetTaskListNT(
    PTASK_LIST  pTask,
    DWORD       dwNumTasks
    )

/*++

Routine Description:

    Provides an API for getting a list of tasks running at the time of the
    API call.  This function uses the registry performance data to get the
    task list and is therefore straight WIN32 calls that anyone can call.

Arguments:

    dwNumTasks       - maximum number of tasks that the pTask array can hold

Return Value:

    Number of tasks placed into the pTask array.

--*/

{
    DWORD                        rc;
    HKEY                         hKeyNames;
    DWORD                        dwType;
    DWORD                        dwSize;
    LPBYTE                       buf = NULL;
    CHAR                         szSubKey[1024];
    LANGID                       lid;
    LPSTR                        p;
    LPSTR                        p2;
    PPERF_DATA_BLOCK             pPerf;
    PPERF_OBJECT_TYPE            pObj;
    PPERF_INSTANCE_DEFINITION    pInst;
    PPERF_COUNTER_BLOCK          pCounter;
    PPERF_COUNTER_DEFINITION     pCounterDef;
    DWORD                        i;
    DWORD                        dwProcessIdTitle;
    DWORD                        dwProcessIdCounter;
    CHAR                         szProcessName[MAX_PATH];
    DWORD                        dwLimit = dwNumTasks - 1;

    //
    // Look for the list of counters.  Always use the neutral
    // English version, regardless of the local language.  We
    // are looking for some particular keys, and we are always
    // going to do our looking in English.  We are not going
    // to show the user the counter names, so there is no need
    // to go find the corresponding name in the local language.
    //
    lid = MAKELANGID( LANG_ENGLISH, SUBLANG_NEUTRAL );
    sprintf( szSubKey, "%s\\%03x", REGKEY_PERF, lid );
    rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                       szSubKey,
                       0,
                       KEY_READ,
                       &hKeyNames
                     );
    if (rc != ERROR_SUCCESS) {
        goto exit;
    }

    //
    // get the buffer size for the counter names
    //
    rc = RegQueryValueEx( hKeyNames,
                          REGSUBKEY_COUNTERS,
                          NULL,
                          &dwType,
                          NULL,
                          &dwSize
                        );

    if (rc != ERROR_SUCCESS) {
        goto exit;
    }

    //
    // allocate the counter names buffer
    //
    buf = (LPBYTE) malloc( dwSize );
    if (buf == NULL) {
        goto exit;
    }
    memset( buf, 0, dwSize );

    //
    // read the counter names from the registry
    //
    rc = RegQueryValueEx( hKeyNames,
                          REGSUBKEY_COUNTERS,
                          NULL,
                          &dwType,
                          buf,
                          &dwSize
                        );

    if (rc != ERROR_SUCCESS) {
        goto exit;
    }

    //
    // now loop thru the counter names looking for the following counters:
    //
    //      1.  "Process"           process name
    //      2.  "ID Process"        process id
    //
    // the buffer contains multiple null terminated strings and then
    // finally null terminated at the end.  the strings are in pairs of
    // counter number and counter name.
    //

    p = buf;
    while (*p) {
        if (p > buf) {
            for( p2=p-2; isdigit(*p2); p2--) ;
            }
        if (stricmp(p, PROCESS_COUNTER) == 0) {
            //
            // look backwards for the counter number
            //
            for( p2=p-2; isdigit(*p2); p2--) ;
            strcpy( szSubKey, p2+1 );
        }
        else
        if (stricmp(p, PROCESSID_COUNTER) == 0) {
            //
            // look backwards for the counter number
            //
            for( p2=p-2; isdigit(*p2); p2--) ;
            dwProcessIdTitle = atol( p2+1 );
        }
        //
        // next string
        //
        p += (strlen(p) + 1);
    }

    //
    // free the counter names buffer
    //
    free( buf );

    //
    // allocate the initial buffer for the performance data
    //
    dwSize = INITIAL_SIZE;
    buf = malloc( dwSize );
    if (buf == NULL) {
        goto exit;
    }
    memset( buf, 0, dwSize );

    while (TRUE) {

        rc = RegQueryValueEx( HKEY_PERFORMANCE_DATA,
                              szSubKey,
                              NULL,
                              &dwType,
                              buf,
                              &dwSize
                            );

        pPerf = (PPERF_DATA_BLOCK) buf;

        //
        // check for success and valid perf data block signature
        //
        if ((rc == ERROR_SUCCESS) &&
            (dwSize > 0) &&
            (pPerf)->Signature[0] == (WCHAR)'P' &&
            (pPerf)->Signature[1] == (WCHAR)'E' &&
            (pPerf)->Signature[2] == (WCHAR)'R' &&
            (pPerf)->Signature[3] == (WCHAR)'F' ) {
            break;
        }

        //
        // if buffer is not big enough, reallocate and try again
        //
        if (rc == ERROR_MORE_DATA) {
            dwSize += EXTEND_SIZE;
            buf = realloc( buf, dwSize );
            memset( buf, 0, dwSize );
        }
        else {
            goto exit;
        }
    }

    //
    // set the perf_object_type pointer
    //
    pObj = (PPERF_OBJECT_TYPE) ((DWORD)pPerf + pPerf->HeaderLength);

    //
    // loop thru the performance counter definition records looking
    // for the process id counter and then save its offset
    //
    pCounterDef = (PPERF_COUNTER_DEFINITION) ((DWORD)pObj + pObj->HeaderLength);
    for (i=0; i<(DWORD)pObj->NumCounters; i++) {
        if (pCounterDef->CounterNameTitleIndex == dwProcessIdTitle) {
            dwProcessIdCounter = pCounterDef->CounterOffset;
            break;
        }
        pCounterDef++;
    }

    dwNumTasks = min( dwLimit, (DWORD)pObj->NumInstances );

    pInst = (PPERF_INSTANCE_DEFINITION) ((DWORD)pObj + pObj->DefinitionLength);

    //
    // loop thru the performance instance data extracting each process name
    // and process id
    //
    for (i=0; i<dwNumTasks; i++) {
        //
        // pointer to the process name
        //
        p = (LPSTR) ((DWORD)pInst + pInst->NameOffset);

        //
        // convert it to ascii
        //
        rc = WideCharToMultiByte( CP_ACP,
                                  0,
                                  (LPCWSTR)p,
                                  -1,
                                  szProcessName,
                                  sizeof(szProcessName),
                                  NULL,
                                  NULL
                                );

        if (!rc) {
            //
    // if we cant convert the string then use a default value
            //
            strcpy( pTask->ProcessName, UNKNOWN_TASK );
        }

        if (strlen(szProcessName)+4 <= sizeof(pTask->ProcessName)) {
            strcpy( pTask->ProcessName, szProcessName );
            strcat( pTask->ProcessName, ".exe" );
        }

        //
        // get the process id
        //
        pCounter = (PPERF_COUNTER_BLOCK) ((DWORD)pInst + pInst->ByteLength);
        pTask->flags = 0;
        pTask->dwProcessId = *((LPDWORD) ((DWORD)pCounter + dwProcessIdCounter));
        if (pTask->dwProcessId == 0) {
            pTask->dwProcessId = (DWORD)-2;
        }

        //
        // next process
        //
        pTask++;
        pInst = (PPERF_INSTANCE_DEFINITION) ((DWORD)pCounter + pCounter->ByteLength);
    }

exit:
    if (buf) {
        free( buf );
    }

    RegCloseKey( hKeyNames );
    RegCloseKey( HKEY_PERFORMANCE_DATA );

    return dwNumTasks;
}

BOOL
EnableDebugPriv95(
    VOID
    )

/*++

Routine Description:

    Changes the process's privilege so that kill works properly.

Arguments:

Return Value:

    TRUE             - success
    FALSE            - failure

Comments:
    Always returns TRUE

--*/

{
   return TRUE;
}

BOOL
EnableDebugPrivNT(
    VOID
    )

/*++

Routine Description:

    Changes the process's privilege so that kill works properly.

Arguments:

Return Value:

    TRUE             - success
    FALSE            - failure

--*/

{
    HANDLE hToken;
    LUID DebugValue;
    TOKEN_PRIVILEGES tkp;

    //
    // Retrieve a handle of the access token
    //
    if (!OpenProcessToken(GetCurrentProcess(),
            TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
            &hToken)) {
        printf("OpenProcessToken failed with %d\n", GetLastError());
        return FALSE;
    }

    //
    // Enable the SE_DEBUG_NAME privilege
    //
    if (!LookupPrivilegeValue((LPSTR) NULL,
            SE_DEBUG_NAME,
            &DebugValue)) {
        printf("LookupPrivilegeValue failed with %d\n", GetLastError());
        return FALSE;
    }

    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Luid = DebugValue;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    AdjustTokenPrivileges(hToken,
        FALSE,
        &tkp,
        sizeof(TOKEN_PRIVILEGES),
        (PTOKEN_PRIVILEGES) NULL,
        (PDWORD) NULL);

    //
    // The return value of AdjustTokenPrivileges can't be tested
    //
    if (GetLastError() != ERROR_SUCCESS) {
        printf("AdjustTokenPrivileges failed with %d\n", GetLastError());
        return FALSE;
    }

    return TRUE;
}

BOOL
KillProcess(
    PTASK_LIST tlist,
    BOOL       fForce
    )
{
    HANDLE            hProcess;

    if (fForce || !tlist->hwnd) {
        hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, tlist->dwProcessId );
        if (hProcess) {
            hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, tlist->dwProcessId );
            if (hProcess == NULL) {
                return FALSE;
            }

            if (!TerminateProcess( hProcess, 1 )) {
                CloseHandle( hProcess );
                return FALSE;
            }

            CloseHandle( hProcess );
            return TRUE;
        }
    }

    //
    // kill the process
    //
    PostMessage( tlist->hwnd, WM_CLOSE, 0, 0 );

    return TRUE;
}

VOID
GetWindowTitles(
    PTASK_LIST_ENUM te
    )
{
    //
    // enumerate all windows
    //
    EnumWindows( EnumWindowsProc, (LPARAM) te );
}

BOOL CALLBACK
EnumWindowsProc(
    HWND    hwnd,
    DWORD   lParam
    )

/*++

Routine Description:

    Callback function for window enumeration.

Arguments:

    hwnd             - window handle
    lParam           - ** not used **

Return Value:

    TRUE  - continues the enumeration

--*/

{
    DWORD             pid = 0;
    DWORD             i;
    CHAR              buf[TITLE_SIZE];
    PTASK_LIST_ENUM   te = (PTASK_LIST_ENUM)lParam;
    PTASK_LIST        tlist = te->tlist;
    DWORD             numTasks = te->numtasks;

    //
    // get the processid for this window
    //
    if (!GetWindowThreadProcessId( hwnd, &pid )) {
        return TRUE;
    }

    //
    // look for the task in the task list for this window
    //
    for (i=0; i<numTasks; i++) {
        if (tlist[i].dwProcessId == pid) {
            tlist[i].hwnd = hwnd;
            //
    // we found the task so lets try to get the
            // window text
            //
            if (GetWindowText( tlist[i].hwnd, buf, sizeof(buf) )) {
                //
// got it, so lets save it
                //
                strcpy( tlist[i].WindowTitle, buf );
            }
            break;
        }
    }

    //
    // continue the enumeration
    //
    return TRUE;
}

BOOL
MatchPattern(
    PUCHAR String,
    PUCHAR Pattern
    )
{
    UCHAR   c, p, l;

    for (; ;) {
        switch (p = *Pattern++) {
            case 0:                             // end of pattern
                return *String ? FALSE : TRUE;  // if end of string TRUE

            case '*':
                while (*String) {               // match zero or more char
                    if (MatchPattern (String++, Pattern))
                        return TRUE;
                }
                return MatchPattern (String, Pattern);

            case '?':
                if (*String++ == 0)             // match any one char
                    return FALSE;                   // not end of string
                break;

            case '[':
                if ( (c = *String++) == 0)      // match char set
                    return FALSE;                   // syntax

                c = toupper(c);
                l = 0;
                while (p = *Pattern++) {
                    if (p == ']')               // if end of char set, then
                        return FALSE;           // no match found

                    if (p == '-') {             // check a range of chars?
                        p = *Pattern;           // get high limit of range
                        if (p == 0  ||  p == ']')
                            return FALSE;           // syntax

                        if (c >= l  &&  c <= p)
                            break;              // if in range, move on
                    }

                    l = p;
                    if (c == p)                 // if char matches this element
                        break;                  // move on
                }

                while (p  &&  p != ']')         // got a match in char set
                    p = *Pattern++;             // skip to end of set

                break;

            default:
                c = *String++;
                if (toupper(c) != p)            // check for exact char
                    return FALSE;                   // not a match

                break;
        }
    }
}


TLIST.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*  Copyright (C) 1994-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.
\******************************************************************************/

/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

    tlist.c

Abstract:

    This module implements a task list application.

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"

#define MAX_TASKS           256

#define PrintTask(idx) \
        printf( "%4d %-16s", tlist[idx].dwProcessId, tlist[idx].ProcessName ); \
        if (tlist[idx].hwnd) { \
            printf( "  %s", tlist[idx].WindowTitle ); \
        } \
        printf( "\n" );

DWORD numTasks;
TASK_LIST tlist[MAX_TASKS];

VOID Usage(VOID);

int _cdecl
main(
    int argc,
    char *argv[]
    )

/*++

Routine Description:

    Main entrypoint for the TLIST application.  This app prints
    a task list to stdout.  The task list include the process id,
    task name, ant the window title.

Arguments:

    argc             - argument count
    argv             - array of pointers to arguments

Return Value:

    0                - success

--*/

{
    DWORD             i;
    TASK_LIST_ENUM    te;
    BOOL              fTree;
    OSVERSIONINFO     verInfo = {0};
    LPGetTaskList     GetTaskList;
    LPEnableDebugPriv EnableDebugPriv;

    if (argc > 1 && (argv[1][0] == '-' || argv[1][0] == '/') && argv[1][1] == '?') {
        Usage();
    }

    //
    // Determine what system we're on and do the right thing
    //

    verInfo.dwOSVersionInfoSize = sizeof (verInfo);
    GetVersionEx(&verInfo);

    switch (verInfo.dwPlatformId)
    {
    case VER_PLATFORM_WIN32_NT:
       GetTaskList     = GetTaskListNT;
       EnableDebugPriv = EnableDebugPrivNT;
       break;

    case VER_PLATFORM_WIN32_WINDOWS:
       GetTaskList = GetTaskList95;
       EnableDebugPriv = EnableDebugPriv95;
       break;

    default:
       printf ("tlist requires Windows NT or Windows 95\n");
       return 1;
    }

    fTree = FALSE;

    //
    // Obtain the ability to manipulate other processes
    //
    EnableDebugPriv();

    //
    // get the task list for the system
    //
    numTasks = GetTaskList( tlist, MAX_TASKS );

    //
    // enumerate all windows and try to get the window
    // titles for each task
    //
    te.tlist = tlist;
    te.numtasks = numTasks;
    GetWindowTitles( &te );

    //
    // print the task list
    //
    for (i=0; i<numTasks; i++) {
PrintTask( i );
    }

    //
    // end of program
    //
    return 0;
}

VOID
Usage(
    VOID
    )

/*++

Routine Description:

    Prints usage text for this tool.

Arguments:

    None.

Return Value:

    None.

--*/

{
    fprintf( stderr, "Microsoft (R) Windows NT (TM) Version 3.5 TLIST\n" );
    fprintf( stderr, "Copyright (C) 1994-1995 Microsoft Corp. All rights reserved\n\n" );
    fprintf( stderr, "usage: TLIST\n" );
    ExitProcess(0);
}


README.TXT

A Graphical Difference Program

SUMMARY
=======

The WINDIFF tool takes two files, or two directories, and does a
byte-for-byte file comparison. It then displays the results in a scrollable
window, lining up the identical parts, and marking with color the parts of
the files that differ. This application can be useful for doing source level
management of code.

This multithread tool allows you to interact with the window while the file
comparisons occur in the background.

MORE INFORMATION
================

Usage:

WINDIFF [paths] [saveoption]

paths

Can be either of the following forms:

    path
        Compare what's in the path with what's in the current directory.

    path1 path2
        Compare what's in path1 with what's in path2.

saveoption

Can be any combination of the following flags:

    -s: Write the names of files that appear in both paths.
    -l: Write the names of files that appear in only the left-hand path.
    -r: Write the names of files that appear in only the right-hand path.
    -d: Write the names of files that appear in both paths, but the files
        are different.

Colors:

RED designates text from the left-hand file.
YELLOW designates text from the right-hand file.
BLUE designates a moved line.
BLACK is used for everything else.


COMPITEM.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 Header *******************************
* Module Name: COMPITEM.C
*
* Module which does the comparison between two files.
*
* Functions:
*
* ci_copytext()
* ci_makecomposite()
* ci_compare()
* ci_onesection()
* compitem_new()
* compitem_delete()
* compitem_discardsections()
* compitem_getcomposite()
* compitem_getleftsections()
* compitem_getrightsections()
* compitem_getleftfile()
* compitem_getrightfile()
* compitem_getstate()
* compitem_gettext_tag()
* compitem_gettext_result()
* compitem_getfilename()
* compitem_frefilename()
*
* Comments:
*
* This module uses the structure compitem which is a data type that knows
* about two files, and can compare them. The result of the comparison
* is a list of sections for each file, and a composite list of sections
* representing the comparison of the two files.
*
* A compitem has a state (one of the integer values defined in state.h)
* representing the result of the comparison. It can also be
* queried for the text result (text equivalent of the state) as well
* as the tag - or title for this compitem (usually a text string containing
* the name(s) of the files being compared).
*
* A compitem will supply a composite section list even if the files are
* the same, or if there is only one file. The composite section list will
* only be built (and the files read in) when the compitem_getcomposite()
* call is made (and not at compitem_new time).
*
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <string.h>

#include "gutils.h"
#include "state.h"
#include "windiff.h"
#include "wdiffrc.h"
#include "list.h"
#include "line.h"
#include "scandir.h"
#include "file.h"
#include "section.h"
#include "compitem.h"

struct compitem {

        FILEDATA left;          /* handle for left-hand file */
        FILEDATA right;         /* handle for right-hand file */

        LIST secs_composite;    /* list of sections (composite file)*/
        LIST secs_left;         /* list of sections (left file) */
        LIST secs_right;        /* list of sections (right file) */

        int state;              /* compitem state - result of compare */
        BOOL bDiscard;          /* true if not alloc-ed on list */
        LPSTR tag;              /* text for tag (title of compitem) */
        LPSTR result;           /* text equivalent of state */

};

LPSTR ci_copytext(LPSTR in);
void ci_makecomposite(COMPITEM ci);
void ci_compare(COMPITEM ci);

/***************************************************************************
 * Function: compitem_new
 *
 * Purpose:
 *
 * Returns a handle to a new compitem - given the filenames for the
 * left and right files to be compared. Either left or right or neither
 * (but not both) may be null. In this case we set the state accordingly.
 *
 * The parameters are handles to DIRITEM objects: these allow us to get the
 * the name of the file relative to the compare roots (needed for the tag)
 * and the absolute name of the file (needed for opening the file).
 *
 * Comments:
 *
 * If the list parameter is not null, the List_New* functions are used to
 * allocate memory for the compitem. We remember this (in the bDiscard flag)
 * so we do not delete the compitem if it was allocated on the list.
 *
 * If the list parameter is null, the memory
 * for the compitem is allocated from the gmem_* heap initialised by the app.
 *
 ****************************************************************************/
COMPITEM
compitem_new(DIRITEM leftname, DIRITEM rightname, LIST list, BOOL fExact)
{
        COMPITEM ci;
        LPSTR str1, str2;
        char buf[2*MAX_PATH+20];

        /*
         * Allocate the memory for the compitem, either at the end of the
         * list or in the gmem_* heap.
         */
        if (list == NULL) {
                /* no list passed */
                ci = (COMPITEM) gmem_get(hHeap, sizeof(struct compitem));
                memset(ci, 0, sizeof(struct compitem));
                ci->bDiscard = TRUE;
        } else {
                /* add to end of list */
                ci = (COMPITEM) List_NewLast(list, sizeof(struct compitem));
                ci->bDiscard = FALSE;
        }

        ci->secs_composite = NULL;
        ci->secs_left = NULL;
        ci->secs_right = NULL;

        /*
         * Make a filedata for each of the files that are non-null.
         * Filedata objects are responsible for reading the file and
         * accessing the lines in it. Don't read in the files until we need to.
         */
        if (leftname != NULL) {
                ci->left = file_new(leftname, FALSE);
                if (ci->left == NULL) {
                        return(NULL);
                }
        } else {
                ci->left = NULL;
        }
        if ( rightname != NULL) {
                ci->right = file_new(rightname, FALSE);
                if (ci->right == NULL) {
                        return(NULL);
                }
        } else {
                ci->right = NULL;
        }

        /*
         * See if we have one or two files, and set the state accordingly
         */
        if ( ! ci->left && !ci->right) {
                /* two NULL files - this is wrong */
                return(NULL);
        }

        /* Set the tag (title field) for this item. If the
         * two files have names that match, we use just that name -
         * otherwise we use both names separated by a colon 'left : right'.
         *
         * In both cases, use the names relative to compare root (the
         * names will certainly be different if we compare the abs paths)
         */
        str1 = dir_getrelname(leftname);
        str2 = dir_getrelname(rightname);

        /* If only one file - set name to that */
        if (ci->left == NULL) {
                ci->tag = ci_copytext(str2);
        } else if (ci->right == NULL) {
                ci->tag = ci_copytext(str1);
        } else {
                if (lstrcmpi(str1, str2) == 0) {
                        ci->tag = ci_copytext(str2);
                } else {
                        wsprintf(buf, "%s : %s", str1, str2);
                        ci->tag = ci_copytext(buf);
                }
        }

        dir_freerelname(leftname, str1);
        dir_freerelname(leftname, str2);

        if (ci->left == NULL) {
                str1 = dir_getroot_item(rightname);
                wsprintf(buf, LoadRcString(IDS_ONLY_IN), str1);
                dir_freeroot_item(rightname, str1);

                ci->result = ci_copytext(buf);
                ci->state = STATE_FILERIGHTONLY;
        } else if (ci->right == NULL) {
                str1 = dir_getroot_item(leftname);
                wsprintf(buf, LoadRcString(IDS_ONLY_IN), str1);
                dir_freeroot_item(leftname, str1);

                ci->result = ci_copytext(buf);
                ci->state = STATE_FILELEFTONLY;
        } else {
                /* two files - are they the same ? compare
                 * the file sizes
                 */

                if (dir_getfilesize(leftname) != dir_getfilesize(rightname))
                {
                    ci->state = STATE_DIFFER;
                    ci->result = ci_copytext(LoadRcString(IDS_DIFFERENT_SIZES));
                } else if (!fExact){
                    ci->result = ci_copytext(LoadRcString(IDS_SAME_SIZE));
                    ci->state = STATE_SAME;
                } else {
                    ci->result =  ci_copytext(LoadRcString(IDS_IDENTICAL));
                    ci->state = STATE_SAME;
                }
        }

#if FALSE
                if (dir_getfilesize(leftname) == dir_getfilesize(rightname)) {
                    if (  !fExact )
                       {
                        ci->result = ci_copytext("same size etc.");
                        ci->state = STATE_SAME;
                    } else {
                        ci->result = ci_copytext("files differ");
                        ci->state = STATE_DIFFER;
                        ci->result = ci_copytext("files differ");
                     }
                } else {
                        ci->result = ci_copytext("files differ");
                        ci->state = STATE_DIFFER;
                }
        }
#endif

        /*
         * Building the section lists and composite lists can wait
         * until needed.
         */
        return(ci);
} /* compitem_new */

/***************************************************************************
 * Function: compitem_delete
 *
 * Purpose:
 *
 * Deletes a compitem and free all associated data.
 *
 * Comments:
 *
 * If the ci->bDiscard flag is set, the compitem was alloc-ed on a list,
 * and should not be discarded (the list itself will be deleted).
 *
 * The DIRDATA we were passed are not deleted. the filedata, lines
 * and sections are.
 ***************************************************************************/
void
compitem_delete(COMPITEM ci)
{
        if (ci == NULL) {
                return;
        }

        compitem_discardsections(ci);

        /* Delete the two filedatas (and associated line lists) */
        file_delete(ci->left);
        file_delete(ci->right);

        /* text we allocated */
        gmem_free(hHeap, ci->tag, lstrlen(ci->tag) + 1);
        gmem_free(hHeap, ci->result, lstrlen(ci->result) + 1);

        /* Free the compitem struct itself if not alloced on a list */
        if (ci->bDiscard) {
                gmem_free(hHeap, (LPSTR) ci, sizeof(struct compitem));
        }
}

/*
/***************************************************************************
 * Function: compitem_discardsections
 *
 * Purpose:
 *
 * To discard sections - throw away cached information relating to the
 * comparison (but not the files if they are read into memory). This
 * is used to force a re-compare if changes in the comparison options
 * are made
 *
 ***************************************************************************/
void
compitem_discardsections(COMPITEM ci)
{
        /* delete the lists of sections we built */
        if (ci == NULL) {
                return;
        }
        if (ci->secs_composite) {
                section_deletelist(ci->secs_composite);
                ci->secs_composite = NULL;
        }
        if (ci->secs_left) {
                section_deletelist(ci->secs_left);
                ci->secs_left = NULL;
        }
        if (ci->secs_right) {
                section_deletelist(ci->secs_right);
                ci->secs_right = NULL;
        }

        /* reset the line lists to throw away cached hash codes and links */
        if (ci->left != NULL) {
                file_reset(ci->left);
        }
        if (ci->right != NULL) {
                file_reset(ci->right);
        }

}

/***************************************************************************
 * Function: compitem_getcomposite
 *
 * Purpose:
 *
 * To get the handle for the composite section list
 *
 ***************************************************************************/
LIST
compitem_getcomposite(COMPITEM ci)
{
        if (ci == NULL) {
                return NULL;
        }
        /*
         * do the comparison if we haven't already done it
         */
        if (ci->secs_composite == NULL) {
                ci_makecomposite(ci);
        }

        return(ci->secs_composite);
}

/***************************************************************************
 * Function: compitem_getleftsections
 *
 * Purpose:
 *
 * To get the handle for the list of sections in the left file
 *
 ***************************************************************************/
LIST
compitem_getleftsections(COMPITEM ci)
{
        if (ci == NULL) {
                return NULL;
        }
        /*
         * do the comparison if we haven't already done it
         */
        if (ci->secs_composite == NULL) {
                ci_makecomposite(ci);
        }

        return(ci->secs_left);
}

/***************************************************************************
 * Function: compitem_getrightsections
 *
 * Purpose:
 *
 * To get the handle for the list of sections in the right file
 *
 ***************************************************************************/
LIST
compitem_getrightsections(COMPITEM ci)
{
        if (ci == NULL) {
                return NULL;
        }
        /*
         * do the comparison if we haven't already done it
         */
        if (ci->secs_composite == NULL) {
                ci_makecomposite(ci);
        }

        return(ci->secs_right);
}

/***************************************************************************
 * Function: compitem_getleftfile
 *
 * Purpose:
 *
 * To get the handle to the left file itself
 *
 ***************************************************************************/
FILEDATA
compitem_getleftfile(COMPITEM ci)
{
        if (ci == NULL) {
                return(NULL);
        }
        return(ci->left);
}

/***************************************************************************
 * Function: compitem_getrightfile
 *
 * Purpose:
 *
 * To get the handle to the right file itself
 *
 ***************************************************************************/
FILEDATA
compitem_getrightfile(COMPITEM ci)
{
        if (ci == NULL) {
                return(NULL);
        }
        return(ci->right);
}

/***************************************************************************
 * Function: compitem_getstate
 *
 * Purpose:
 *
 * To get the state (compare result) of this compitem
 *
 ***************************************************************************/
int
compitem_getstate(COMPITEM ci)
{
        if (ci == NULL) {
                return(0);
        }
        return(ci->state);
}

/***************************************************************************
 * Function: compitem_gettext_tag
 *
 * Purpose:
 *
 * To get the tag (text for the compitem title)
 *
 ***************************************************************************/
LPSTR
compitem_gettext_tag(COMPITEM ci)
{
        if (ci == NULL) {
                return(NULL);
        }
        return(ci->tag);
}

/***************************************************************************
 * Function: compitem_gettext_result
 *
 * Purpose:
 *
 * To get the result text (text equiv of state)
 *
 ***************************************************************************/
LPSTR
compitem_gettext_result(COMPITEM ci)
{
        if (ci == NULL) {
                return(NULL);
        }
        return(ci->result);
}

/***************************************************************************
 * Function: compitem_getfilename
 *
 * Purpose:
 *
 * To return the name of the file associated with this compitem. The option
 * argument (one of CI_LEFT, CI_RIGHT, CI_COMP) indicates which file
 * is required.
 *
 * Comments:
 *
 * CI_LEFT and CI_RIGHT just result in calls to dir_getopenname to get
 * an open-able filename.
 *
 * For CI_COMP, we create a temporary file, write out all the text in the
 * composite section list to this file, and then pass the name of the
 * temporary file to the caller. This file will be deleted on
 * the call to compitem_freefilename().
 *
 ***************************************************************************/
LPSTR
compitem_getfilename(COMPITEM item, int option)
{
        LPSTR fname;
        LINE line;
        LPSTR tag, text;
        SECTION sec;
        OFSTRUCT os;
        int fh;

        if (item == NULL) {
                return(NULL);
        }

        switch(option) {
        case CI_LEFT:
                if (item->left != NULL) {
                        return(dir_getopenname(file_getdiritem(item->left)));
                } else {
                        return(NULL);
                }

        case CI_RIGHT:
                if (item->right != NULL) {
                        return(dir_getopenname(file_getdiritem(item->right)));
                } else {
                        return(NULL);
                }

        case CI_COMP:

                /* caller has asked for the filename of the composite file.
                 * we need to create a temporary file and write the
                 * lines in the composite section list out to it.
                 */
                fname = gmem_get(hHeap, MAX_PATH);
                GetTempPath(MAX_PATH, fname);
                GetTempFileName(fname, "wdf", 0, fname);

                fh = OpenFile(fname, &os, OF_READWRITE|OF_SHARE_DENY_NONE);
                if (fh < 0) {
                        MessageBox(NULL, LoadRcString(IDS_CANT_OPEN_TMP_FILE), NULL, MB_OK | MB_ICONSTOP);
                        return(NULL);
                }

                /* make sure the composite list has been built */

                if (item->secs_composite == NULL) {
                        ci_makecomposite(item);
                }

                /* write out every line in every section on the composite
                 * list to the temp file.
                 */
                List_TRAVERSE(item->secs_composite, sec) {

                        /* get the tag field based on the section state*/
                        switch(section_getstate(sec)) {
                        case STATE_SAME:
                                tag = "    ";
                                break;

                        case STATE_LEFTONLY:
                                tag = " <! ";
                                break;
                        case STATE_RIGHTONLY:
                                tag = " !> ";
                                break;

                        case STATE_MOVEDLEFT:
                                tag = " <- ";
                                break;

                        case STATE_MOVEDRIGHT:
                                tag = " -> ";
                                break;
                        }

                        /* write out each line in this section.
                         * non-standard traverse of list as we only
                         * want to go from section first to section last
                         * inclusive.
                         */
                        for (line = section_getfirstline(sec);
                             line != NULL;
                             line = List_Next(line) ) {

                                text = line_gettext(line);

                                /* write out to file */
                                _lwrite(fh, tag, lstrlen(tag));
                                _lwrite(fh, text, lstrlen(text));

                                if (line == section_getlastline(sec)) {
                                        break;
                                }
                        }
                }

                /* now close the file and return its name */
                _lclose(fh);
                return(fname);

        default:
                MessageBox(NULL, LoadRcString(IDS_BAD_ARGUMENT), NULL, MB_OK | MB_ICONSTOP);
                return(NULL);
        }
}

/***************************************************************************
 * Function: compitem_freefilename
 *
 * Purpose:
 *
 * Free memory created by a call to compitem_getfilename. If a temporary
 * file was created, this may cause it to be deleted. The option argument must
 * be the same as passed to the original compitem_getfilename call.
 *
 * If we created a temporary file for CI_COMP, then delete it; otherwise,
 * just pass the name to dir_freeopenname.
 *
 ***************************************************************************/
void
compitem_freefilename(COMPITEM item, int option, LPSTR filename)
{
        OFSTRUCT os;

        if ((item == NULL) || (filename == NULL)) {
                return;
        }

        switch(option) {

        case CI_LEFT:
                dir_freeopenname(file_getdiritem(item->left), filename);
                break;

        case CI_RIGHT:
                dir_freeopenname(file_getdiritem(item->right), filename);
                break;

        case CI_COMP:

                /* this is a temporary file we created. Delete it. */
                OpenFile(filename, &os, OF_DELETE);

                gmem_free(hHeap, filename, MAX_PATH);
                break;
        }
}

/***************************************************************************
 * Function: ci_copytext
 *
 * Purpose:
 *
 * To alloc a buffer large enough for the text string and copy the text into
 * it and return a pointer to the string.
 *
 ***************************************************************************/
LPSTR
ci_copytext(LPSTR in)
{
        LPSTR out;

        if (in == NULL) {
                out = gmem_get(hHeap, 1);
                out[0] = '\0';
        } else {
                out = gmem_get(hHeap, lstrlen(in) + 1);
                lstrcpy(out, in);
        }
        return(out);
}

/***************************************************************************
 * Function: ci_onesection
 *
 * Purpose:
 *
 * To make a list containing a single section from the whole list of lines
 *
 ***************************************************************************/
LIST
ci_onesection(FILEDATA file)
{
        LIST lines;
        LIST sections;
        SECTION section;

        lines = file_getlinelist(file);

        /* create a null list */
        sections = List_Create();

        /* tell the section to create itself on the end of this list. */
        section = section_new(List_First(lines), List_Last(lines), sections);
        section_setstate(section, STATE_SAME);

        return(sections);
}

/***************************************************************************
 * Function: ci_makecomposite
 *
 * Purpose:
 *
 * Compare the two files and build the composite list. This function is
 * called whenever we need one of the section lists and only does the
 * comparison if the composite list does not already exist.
 *
 ***************************************************************************/
void
ci_makecomposite(COMPITEM ci)
{
        if (ci->secs_composite != NULL) {
                return;
        }

        /* if there is only one file, make a single item list
         * of sections
         */
        if (ci->left == NULL) {
                ci->secs_left = NULL;
                ci->secs_right = ci_onesection(ci->right);

                /* make a second list, not a pointer to the first
                 * or we will get confused when deleting
                 */
                ci->secs_composite = ci_onesection(ci->right);
                return;
        } else if (ci->right == NULL) {
                ci->secs_right = NULL;
                ci->secs_left = ci_onesection(ci->left);

                /* make a second list, not a pointer to the first
                 * or we will get confused when deleting
                 */
                ci->secs_composite = ci_onesection(ci->left);
                return;
        }

        /* we have two files - we need to compare them fully */
        ci_compare(ci);
}

/***************************************************************************
 * Function: ci_compare
 *
 * Purpose:
 *
 * Compare files and build a composite list.
 *
 * Comments:
 *
 * Comparison method:
 *
 *    0   Break each file into lines and hash each line.  Lines which
 *        don't match can be rapidly eliminated by comparing the hash code.
 *
 *        Store the hash codes in a binary search tree that
 *        will give for each hash code the number of times that it
 *        occurred in each file and one of the lines where it occurred
 *        in each file.  The tree is used to rapidly find the partner
 *        of a line which occurs exactly once in each file.
 *
 *    1   Make a section covering the whole file (for both)
 *        and link unique lines between these sections (i.e. link lines
 *        which occur exactly once in each file as they must match each other).
 *        These are referred to as anchor points.
 *
 *    2   Build section lists for both files by traversing line lists and
 *        making a section for each set of adjacent lines that are unmatched
 *        and for each set of adjacent lines that match a set of adjacent
 *        lines in the other file.  In making a section we start from a
 *        known matching line and work both forwards and backwards through
 *        the file including lines which match, whether they are unique or not.
 *
 *    3   Establish links between sections that match
 *        and also between sections that don't match but do
 *        correspond (by position in file between matching sections)
 *
 *    4   For each section pair that don't match but do correspond,
 *        link up matching lines unique within that section.  (i.e. do
 *        the whole algorithm again on just this section).
 *
 *    There may be some lines which occur many times over in each file.
 *    As these occurrences are matched up, so the number left to match
 *    reduces, and may reach one in each file.  At this point these two
 *    can be matched.  Therefore we...
 *
 *    Repeat steps 0-4 until no more new links are added, but (especially
 *    in step 0) we only bother with lines which have not yet been matched.
 *    This means that a line which has only one unmatched instance in each
 *    file gets a count of one and so is a new anchor point.
 *
 *    Finally build a composite list from the two lists of sections.
 *
 ***************************************************************************/
void
ci_compare(COMPITEM ci)
{
        LIST lines_left, lines_right;
        SECTION whole_left, whole_right;
        BOOL bChanges;

        /* get the list of lines for each file */
        lines_left = file_getlinelist(ci->left);
        lines_right = file_getlinelist(ci->right);

        if ((lines_left == NULL) || (lines_right == NULL)) {
                ci->secs_left = NULL;
                ci->secs_right = NULL;
                ci->secs_composite = NULL;
                return;
        }

        do {

                /* we have made no changes so far this time round the
                 * loop
                 */
                bChanges = FALSE;

                /* make a section covering the whole file */
                whole_left = section_new(List_First(lines_left),
                                         List_Last(lines_left), NULL);

                whole_right = section_new(List_First(lines_right),
                                         List_Last(lines_right), NULL);

                /* link up matching unique lines between these sections */
                if (section_match(whole_left, whole_right)) {
                        bChanges = TRUE;
                }

                /* delete the two temp sections */
                section_delete(whole_left);
                section_delete(whole_right);

                /* discard previous section lists if made */
                if (ci->secs_left) {
                        section_deletelist(ci->secs_left);
                        ci->secs_left = NULL;
                }
                if (ci->secs_right) {
                        section_deletelist(ci->secs_right);
                        ci->secs_right = NULL;
                }
                /* build new section lists for both files */
                ci->secs_left = section_makelist(lines_left, TRUE);
                ci->secs_right = section_makelist(lines_right, FALSE);

                /* match up sections - make links and corresponds between
                 * sections. Attempts to section_match corresponding
                 * sections that are not matched. returns true if any
                 * further links were made

*/
                if (section_matchlists(ci->secs_left, ci->secs_right)) {
                        bChanges = TRUE;
                }

        /* repeat as long as we keep adding new links */

        } while (bChanges);

        /* all possible lines linked, and section lists made .
         * combine the two section lists to get a view of the
         * whole comparison - the composite section list. This also
         * sets the state of each section in the composite list.
         */
        ci->secs_composite = section_makecomposite(ci->secs_left, ci->secs_right);

}


COMPITEM.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.
\******************************************************************************/

/*
 * COMPITEM.H
 */

/* handle to a CompItem */
typedef struct compitem FAR* COMPITEM;

COMPITEM compitem_new(DIRITEM left, DIRITEM right, LIST list, BOOL fExact);
void compitem_delete(COMPITEM item);
LIST compitem_getcomposite(COMPITEM item);
void compitem_discardsections(COMPITEM item);
LIST compitem_getleftsections(COMPITEM item);
LIST compitem_getrightsections(COMPITEM item);
FILEDATA compitem_getleftfile(COMPITEM item);
FILEDATA compitem_getrightfile(COMPITEM item);
int compitem_getstate(COMPITEM item);
LPSTR compitem_gettext_tag(COMPITEM item);
LPSTR compitem_gettext_result(COMPITEM item);
LPSTR compitem_getfilename(COMPITEM item, int option);
void compitem_freefilename(COMPITEM item, int option, LPSTR filename);

/*
 * options for compitem_getfilename, indicating which name is desired
 */
#define CI_LEFT         1       /* name of left file */
#define CI_RIGHT        2       /* name of right file */
#define CI_COMP         3       /* name of composite file */


COMPLIST.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 Header *******************************
* Module Name: COMPLIST.C
*
* Supports a list of compitems, where each compitem represents
* a pair of matching files, or an unmatched file.
*
* Functions:
*
* complist_filedialog()
* complist_dirdialog()
* complist_args()
* complist_getitems()
* complist_delete()
* complist_savelist()
* complist_copyfiles()
* complist_dodlg_savelist()
* complist_dodlg_copyfiles()
* complist_match()
* complist_new()
* complist_dodlg_dir()
*
* Comments:
*
* We build lists of filenames from two pathnames (using the
* scandir module) and then traverse the two lists comparing names.
* Where the names match, we create a CompItem from the matching
* names. Where there is an unmatched name, we create a compitem for it.
*
* We may also be asked to create a complist for two individual files:
* here we create a single compitem for them as a matched pair even if
* the names don't match.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include <direct.h>

#include "gutils.h"
#include "state.h"
#include "windiff.h"
#include "wdiffrc.h"
#include "list.h"
#include "line.h"
#include "scandir.h"
#include "file.h"
#include "section.h"
#include "compitem.h"
#include "complist.h"
#include "view.h"

extern BOOL bAbort;             /* defined in windiff.c  Read only here */

/*
 * The COMPLIST handle is typedef-ed to be a pointer to one
 * of these struct complist
 */
struct complist {
        DIRLIST left;           /* left list of files */
        DIRLIST right;          /* right list of files */
        LIST items;             /* list of COMPITEMs */
};

/* ---- module-wide data -------------------------------------*/

/* data for communicating between the SaveList dlg and complist_savelist() */

char dlg_file[256];                /* filename to save to */

/* checkbox options */
BOOL dlg_identical, dlg_differ, dlg_left, dlg_right;
BOOL dlg_recursive = FALSE;

/* data for Directory and SaveList */
char dialog_leftname[256];
char dialog_rightname[256];

/*
 * data used by dodlg_copyfiles
 */
UINT dlg_options;
char dlg_root[256];

/*------------------------timing for performance measurements-----------------*/

static DWORD TickCount;         /* time operation started, then time taken*/

int FAR PASCAL complist_dodlg_savelist(HWND hDlg, UINT message,
        UINT wParam, long lParam);
int FAR PASCAL complist_dodlg_copyfiles(HWND hDlg, UINT message,
        UINT wParam, long lParam);
BOOL complist_match(COMPLIST cl, VIEW view, BOOL fDeep, BOOL fExact);
COMPLIST complist_new(void);
int FAR PASCAL complist_dodlg_dir(HWND hDlg, unsigned message,
        WORD wParam, LONG lParam);

/***************************************************************************
 * Function: complist_filedialog
 *
 * Purpose:
 *
 * Builds a complist by putting up two dialogs to allow the user to
 * select two files. This will build a Complist with one CompItem (even
 * if the names don't match).
 *
 ***************************************************************************/
COMPLIST
complist_filedialog(VIEW view)
{
        COMPLIST cl;
        OFSTRUCT os1, os2;
        char fname[256], FileExt[256], FileOpenSpec[256];

        /* ask for the filenames */
        lstrcpy(FileExt, ".c");
        lstrcpy(FileOpenSpec, "*.*");
        lstrcpy(fname,"");

        if (!complist_open(LoadRcString(IDS_SELECT_FIRST_FILE), FileExt, FileOpenSpec,
                        &os1, fname) )
                return(NULL);

        lstrcpy(FileExt, ".c");
        lstrcpy(FileOpenSpec, "*.*");
        lstrcpy(fname,"");

        if (!complist_open(LoadRcString(IDS_SELECT_SECOND_FILE), FileExt, FileOpenSpec,
                        &os2, fname) )
                return(NULL);

        /* alloc a new structure */
        cl = complist_new();

        cl->left = dir_buildlist(os1.szPathName, TRUE);
        cl->right = dir_buildlist(os2.szPathName, TRUE);

        /* register with the view (must be done after the list is non-null) */
        view_setcomplist(view, cl);

        complist_match(cl, view, FALSE, TRUE);

        return(cl);
}/* complist_filedialog */

/***************************************************************************
 * Function: complist_dirdialog
 *
 * Purpose:
 *
 * Builds a new complist by querying the user for two directory
 * names and scanning those in parallel.
 *
 * Names that match in the same directory will be paired - unmatched
 * names will go in a compitem on their own.
 *
 ***************************************************************************/
COMPLIST
complist_dirdialog(VIEW view)
{
        DLGPROC lpProc;
        BOOL fOK;

        /* put up a dialog for the two pathnames */
        lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_dir, hInst);
        windiff_UI(TRUE);
        fOK = DialogBox(hInst, "Directory", hwndClient, lpProc);
        windiff_UI(FALSE);
        FreeProcInstance(lpProc);

        if (!fOK) {
                return(NULL);
        }

        return complist_args( dialog_leftname, dialog_rightname
                            , view, dlg_recursive);
} /* complist_dirdialog */

/***************************************************************************
 * Function: complist_args
 *
 * Purpose:
 *
 * Given two pathname strings, scan the directories and traverse them
 * in parallel comparing matching names.
 *
 ***************************************************************************/
COMPLIST
complist_args(LPSTR p1, LPSTR p2, VIEW view, BOOL fDeep)
{
        COMPLIST cl;
        char msg[256];

        /* alloc a new complist */
        cl = complist_new();

        cl->left = dir_buildlist(p1, TRUE);
        /* check that we could find the paths, and report if not */
        if (cl->left == NULL) {
                wsprintf((LPTSTR)msg, LoadRcString(IDS_COULDNT_FIND), p1);
                MessageBox(NULL, msg, NULL, MB_OK | MB_ICONSTOP);
                return(NULL);
        }

        cl->right = dir_buildlist(p2, TRUE);
        if (cl->right == NULL) {
                wsprintf((LPTSTR)msg, LoadRcString(IDS_COULDNT_FIND), p2);
                MessageBox(NULL, msg, NULL, MB_OK | MB_ICONSTOP);
                return(NULL);
        }

        /* register with the view (must be done after building lists) */
        view_setcomplist(view, cl);

        complist_match(cl, view, fDeep, TRUE);

        return(cl);
} /* complist_args */

/***************************************************************************
 * Function: complist_getitems
 *
 * Purpose:
 *
 * Gets the handle to the list of COMPITEMs. The list continues to be
 * owned by the COMPLIST, so don't delete except by calling complist_delete.
 *
 ***************************************************************************/
LIST
complist_getitems(COMPLIST cl)
{
        if (cl == NULL) {
                return(NULL);
        }

        return(cl->items);
}

/***************************************************************************
 * Function: complist_delete
 *
 * Purpose:
 *
 * Deletes a complist and all associated CompItems and DIRLISTs. Note this
 * does not delete any VIEW - the VIEW owns the COMPLIST and not the other
 * way around.
 *
 **************************************************************************/
void
complist_delete(COMPLIST cl)
{
        COMPITEM item;

        if (cl == NULL) {
                return;
        }

        /* delete the two directory scan lists */
        dir_delete(cl->left);
        dir_delete(cl->right);

        /* delete the compitems in the list */
        List_TRAVERSE(cl->items, item) {
                        compitem_delete(item);
        }

        /* delete the list itself */
        List_Destroy(&cl->items);

        gmem_free(hHeap, (LPSTR) cl, sizeof(struct complist));

}

/***************************************************************************
 * Function: complist_savelist
 *
 * Purpose:
 *
 * Writes out to a text file the list of compitems as relative filenames
 * one per line.
 *
 * If savename is non-null, use this as the filename for output; otherwise,
 * query the user via a dialog for the filename and include options.
 *
 **************************************************************************/
void
complist_savelist(COMPLIST cl, LPSTR savename, UINT options)
{
        DLGPROC lpProc;
        static BOOL done_init = FALSE;
        BOOL bOK;
        int fh, state;
        OFSTRUCT os;
        char msg[256];
        HCURSOR hcurs;
        COMPITEM ci;
        LPSTR pstr, lhead, rhead;
        int nFiles = 0;

        if (!done_init) {
                /* init the options once round - but keep the same options
                 * for the rest of the session.
                 */

                /* first init default options */
                dlg_identical = FALSE;
                dlg_differ = TRUE;
                dlg_left = TRUE;
                dlg_right = FALSE;

                dlg_file[0] = '\0';

                done_init = TRUE;
        }

        if (cl == NULL) {
                return;
        }

        if (savename == NULL) {

                /* store the left and right rootnames so that dodlg_savelist
                 * can display them in the dialog.
                 */
                pstr = dir_getroot_list(cl->left);
                lstrcpy(dialog_leftname, pstr);
                dir_freeroot_list(cl->left, pstr);

                pstr = dir_getroot_list(cl->right);
                lstrcpy(dialog_rightname, pstr);
                dir_freeroot_list(cl->right, pstr);

                lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_savelist, hInst);
                windiff_UI(TRUE);
                bOK = DialogBox(hInst, "SaveList", hwndClient, lpProc);
                windiff_UI(FALSE);
                FreeProcInstance(lpProc);

                if (!bOK) {
                        /* user cancelled from dialog box */
                        return;
                }
                savename = dlg_file;

        } else {
                dlg_identical = (options & INCLUDE_SAME);
                dlg_differ = (options & INCLUDE_DIFFER);
                dlg_left = (options & INCLUDE_LEFTONLY);
                dlg_right = (options & INCLUDE_RIGHTONLY);
        }

        /* try to open the file */
        fh = OpenFile(savename, &os, OF_CREATE|OF_READWRITE|OF_SHARE_DENY_WRITE);
        if (fh < 0) {
                wsprintf((LPTSTR)msg, LoadRcString(IDS_CANT_OPEN), savename);
                windiff_UI(TRUE);
                MessageBox(NULL, msg, "Windiff", MB_ICONSTOP|MB_OK);
                windiff_UI(FALSE);
                return;
        }

        hcurs = SetCursor(LoadCursor(NULL, IDC_WAIT));

        /* write out the header line */
        lhead = dir_getroot_list(cl->left);
        rhead = dir_getroot_list(cl->right);
{
        TCHAR szBuf1[20],szBuf2[20],szBuf3[20],szBuf4[20];
        lstrcpy(szBuf1,(LPSTR)(dlg_identical ? LoadRcString(IDS_IDENTICAL_COMMA) : ""));
        lstrcpy(szBuf2,(LPSTR)(dlg_left ? LoadRcString(IDS_LEFT_ONLY_COMMA) : ""));
        lstrcpy(szBuf3,(LPSTR)(dlg_right ? LoadRcString(IDS_RIGHT_ONLY_COMMA) : ""));
        lstrcpy(szBuf4,(LPSTR)(dlg_differ ? LoadRcString(IDS_DIFFERING) : ""));
        wsprintf(msg, LoadRcString(IDS_HEADER_LINE_STR),
                lhead, rhead, szBuf1, szBuf2, szBuf3, szBuf4);
}
        _lwrite(fh, msg, lstrlen(msg));
        dir_freeroot_list(cl->left, lhead);
        dir_freeroot_list(cl->right, rhead);

        /* traverse the list of compitems looking for the
         * ones we are supposed to include
         */
        List_TRAVERSE(cl->items, ci) {

                /* check if files of this type are to be listed */
                state = compitem_getstate(ci);

                if ((state == STATE_SAME) && (!dlg_identical)) {
                        continue;
                } else if ((state == STATE_DIFFER) && (!dlg_differ)) {
                        continue;
                } else if ((state == STATE_FILELEFTONLY) && (!dlg_left)) {
                        continue;
                } else if ((state == STATE_FILERIGHTONLY) && (!dlg_right)) {
                        continue;
                }

                nFiles++;

                /* output the list line */
                wsprintf((LPTSTR)msg, "%s\r\n", compitem_gettext_tag(ci));
                _lwrite(fh, msg, lstrlen(msg));
        }

        /* write tail line */
        wsprintf((LPTSTR)msg, LoadRcString(IDS_FILES_LISTED), nFiles);
        _lwrite(fh, msg, lstrlen(msg));

        /* - close file and we are finished */
        _lclose(fh);

        SetCursor(hcurs);
} /* complist_savelist */

/***************************************************************************
 * Function: complist_copyfiles
 *
 * Purpose:
 *
 * To copy files to a new directory newroot. if newroot is NULL, query the user
 * via a dialog to get the new dir name and options.
 *
 * Options are either COPY_FROMLEFT or COPY_FROMRIGHT (indicating which
 * tree is to be the source of the files, plus any or all of
 * INCLUDE_SAME, INCLUDE_DIFFER and INCLUDE_LEFT (INCLUDE_LEFT
 * and INCLUDE_RIGHT are treated the same here since the COPY_FROM* option
 * indicates which side to copy from).
 *
 ***************************************************************************/
void
complist_copyfiles(COMPLIST cl, LPSTR newroot, UINT options)
{
        int nFiles = 0;
        int nFails = 0;
        static BOOL done_init = FALSE;
        LPSTR pstr;
        char buffer[64];
        DIRITEM diritem;
        DLGPROC lpProc;
        BOOL bOK;
        COMPITEM ci;
        int state;

        if (!done_init) {
                /*
                 * one-time initialisation of dialog defaults
                 */
                dlg_options = COPY_FROMLEFT|INCLUDE_LEFTONLY|INCLUDE_DIFFER;
                dlg_root[0] = '\0';
                done_init = TRUE;
        }

        if (cl == NULL) {
                return;
        }

        if (newroot == NULL) {
                /*
                 * put up dialog to query rootname and options
                 */

                /* store the left and right rootnames so that the dlg proc
                 * can display them in the dialog.
                 */
                pstr = dir_getroot_list(cl->left);
                lstrcpy(dialog_leftname, pstr);
                dir_freeroot_list(cl->left, pstr);

                pstr = dir_getroot_list(cl->right);
                lstrcpy(dialog_rightname, pstr);
                dir_freeroot_list(cl->right, pstr);

                do {
                        lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_copyfiles, hInst);
                        windiff_UI(TRUE);
                        bOK = DialogBox(hInst, "CopyFiles", hwndClient, lpProc);
                        windiff_UI(FALSE);
                        FreeProcInstance(lpProc);

                        if (!bOK) {
                                /* user cancelled from dialog box */
                                return;
                        }
                        if (lstrlen(dlg_root) == 0) {
                                windiff_UI(TRUE);
                                MessageBox(NULL, LoadRcString(IDS_ENTER_DIR_NAME),
                                                "Windiff", MB_ICONSTOP|MB_OK);
                                windiff_UI(FALSE);
                        }
                } while (lstrlen(dlg_root) == 0);

        } else {
                dlg_options = options;
                lstrcpy(dlg_root, newroot);
        }

        TickCount = GetTickCount();

        if (dlg_options & COPY_FROMLEFT) {
                if (!dir_startcopy(cl->left))
                        return;
        } else {
                if (!dir_startcopy(cl->right))
                        return;
        }

        /*
         * traverse the list of compitems copying files as necessary
         */
        List_TRAVERSE(cl->items, ci) {

                if (bAbort){
                        break;  /* fall into end_copy processing */
                }
                /* check if files of this type are to be copied */
                state = compitem_getstate(ci);

                if ((state == STATE_SAME) && !(dlg_options & INCLUDE_SAME)) {
                        continue;
                } else if ((state == STATE_DIFFER) && !(dlg_options & INCLUDE_DIFFER)) {
                        continue;
                } else if (state == STATE_FILELEFTONLY) {
                        if (dlg_options & COPY_FROMRIGHT) {
                                continue;
                        }
                        if ((dlg_options & (INCLUDE_LEFTONLY | INCLUDE_RIGHTONLY)) == 0) {
                                continue;
                        }
                } else if (state == STATE_FILERIGHTONLY) {
                        if (dlg_options & COPY_FROMLEFT) {
                                continue;
                        }
                        if ((dlg_options & (INCLUDE_LEFTONLY | INCLUDE_RIGHTONLY)) == 0) {
                                continue;
                        }
                }

                if (dlg_options & COPY_FROMLEFT) {
                        diritem = file_getdiritem(compitem_getleftfile(ci));
                } else {
                        diritem = file_getdiritem(compitem_getrightfile(ci));
                }

                /*
                 * copy the file to the new root directory
                 */
                if (dir_copy(diritem, dlg_root) == FALSE) {
                        nFails++;
                        pstr = dir_getrelname(diritem);
                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FAILED_TO_COPY), pstr);
                        dir_freerelname(diritem, pstr);

                        if (MessageBox(NULL, buffer, NULL, MB_OKCANCEL | MB_ICONSTOP) == IDCANCEL)
                            /* user pressed cancel - abort current operation*/
                            /* fall through to end-copy processing */
                            break;

                } else {
                        nFiles++;
                }

                wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPYING), nFiles);
                SetStatus(buffer);

                /*
                 * allow user interface to continue
                 */
                if (Poll()) {
                        /* abort requested */
                        TickCount = GetTickCount()-TickCount;
                        windiff_UI(TRUE);
                        MessageBox(hwndClient, LoadRcString(IDS_COPY_ABORTED),
                                "WinDiff", MB_OK|MB_ICONINFORMATION);
                        windiff_UI(FALSE);
                        break;
                }

        } /* traverse */
        if (dlg_options & COPY_FROMLEFT) {
                nFails = dir_endcopy(cl->left);
        } else {
                nFails = dir_endcopy(cl->right);
        }
        TickCount = GetTickCount()-TickCount;

        if (nFails<0) {
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPY_FAILED), -nFails);
        } else {
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPY_COMPLETE), nFails);
        }
        windiff_UI(TRUE);
        MessageBox(hwndClient, buffer, "WinDiff", MB_OK|MB_ICONINFORMATION);
        windiff_UI(FALSE);

        buffer[0] = '\0';
        SetStatus(buffer);
} /* complist_copyfiles */

/***************************************************************************
 * Function: complist_match
 *
 * Purpose:
 *
 * Matches up two lists of filenames
 *
 * Commentsz:
 *
 * We can find out from the DIRLIST handle whether the original list
 * was a file or a directory name.
 * If the user typed:
 *      two file names  - match these two item even if the names differ
 *
 *      two dirs        - match only those items whose names match
 *
 *      one file and one dir
 *                      - try to find a file of that name in the dir.
 *
 * This function returns TRUE if the complist_match was ok, or FALSE if it was
 * aborted in some way.
 *
 ***************************************************************************/
BOOL
complist_match(COMPLIST cl, VIEW view, BOOL fDeep, BOOL fExact)
{
        LPSTR lname;
        LPSTR rname;
        DIRITEM leftitem, rightitem;
        int cmpvalue;

        TickCount = GetTickCount();

        if (dir_isfile(cl->left) ) {

                if (dir_isfile(cl->right)) {
                        /* two files */

                        /* there should be one item in each list - make
                         * a compitem by matching these two and append it to the
                         * list
                         */
                        compitem_new(dir_firstitem(cl->left),
                                       dir_firstitem(cl->right), cl->items, fExact);

                        view_newitem(view);

                        TickCount = GetTickCount() - TickCount;
                        return TRUE;
                }
                /* left is file, right is dir */
                leftitem = dir_firstitem(cl->left);
                rightitem = dir_firstitem(cl->right);
                lname = dir_getrelname(leftitem);
                while (rightitem != NULL) {
                        rname = dir_getrelname(rightitem);
                        cmpvalue = lstrcmpi(lname, rname);
                        dir_freerelname(rightitem, rname);

                        if (cmpvalue == 0) {
                                /* this is the match */
                                compitem_new(leftitem, rightitem, cl->items, fExact);
                                view_newitem(view);

                                dir_freerelname(leftitem, lname);

                                TickCount = GetTickCount() - TickCount;
                                return(TRUE);
                        }

                        rightitem = dir_nextitem(cl->right, rightitem, fDeep);
                }
                /* not found */
                dir_freerelname(leftitem, lname);
                compitem_new(leftitem, NULL, cl->items, fExact);
                view_newitem(view);
                TickCount = GetTickCount() - TickCount;
                return(TRUE);

        } else if (dir_isfile(cl->right)) {

                /* left is dir, right is file */

                /* loop through the left dir, looking for
                 * a file that has the same name as rightitem
                 */

                leftitem = dir_firstitem(cl->left);
                rightitem = dir_firstitem(cl->right);
                rname = dir_getrelname(rightitem);
                while (leftitem != NULL) {
                        lname = dir_getrelname(leftitem);
                        cmpvalue = lstrcmpi(lname, rname);
                        dir_freerelname(leftitem, lname);

                        if (cmpvalue == 0) {
                                /* this is the match */
                                compitem_new(leftitem, rightitem, cl->items, fExact);
                                view_newitem(view);

                                dir_freerelname(rightitem, rname);

                                TickCount = GetTickCount() - TickCount;
                                return(TRUE);
                        }

                        leftitem = dir_nextitem(cl->left, leftitem, fDeep);
                }
                /* not found */
                dir_freerelname(rightitem, rname);
                compitem_new(NULL, rightitem, cl->items, fExact);
                view_newitem(view);
                TickCount = GetTickCount() - TickCount;
                return(TRUE);
        }

        /* two directories */

        /* traverse the two lists in parallel comparing the relative names*/

        leftitem = dir_firstitem(cl->left);
        rightitem = dir_firstitem(cl->right);
        while ((leftitem != NULL) && (rightitem != NULL)) {

                lname = dir_getrelname(leftitem);
                rname = dir_getrelname(rightitem);
                cmpvalue = utils_CompPath(lname, rname);
                dir_freerelname(leftitem, lname);
                dir_freerelname(rightitem, rname);

                if (cmpvalue == 0) {
                        compitem_new(leftitem, rightitem, cl->items, fExact);
                        if (view_newitem(view)) {
                                TickCount = GetTickCount() - TickCount;
                                return(FALSE);
                        }
                        leftitem = dir_nextitem(cl->left, leftitem, fDeep);
                        rightitem = dir_nextitem(cl->right, rightitem, fDeep);

                } else if (cmpvalue < 0) {
                        compitem_new(leftitem, NULL, cl->items, fExact);
                        if (view_newitem(view)) {
                                TickCount = GetTickCount() - TickCount;
                                return(FALSE);
                        }
                        leftitem = dir_nextitem(cl->left, leftitem, fDeep);
                }  else {
                        compitem_new(NULL, rightitem, cl->items, fExact);
                        if (view_newitem(view)) {
                                TickCount = GetTickCount() - TickCount;
                                return(FALSE);
                        }
                        rightitem = dir_nextitem(cl->right, rightitem, fDeep);
                }
        }

        /* any left over are unmatched */
        while (leftitem != NULL) {
                compitem_new(leftitem, NULL, cl->items, fExact);
                if (view_newitem(view)) {
                        TickCount = GetTickCount() - TickCount;
                        return(FALSE);
                }
                leftitem = dir_nextitem(cl->left, leftitem, fDeep);
        }
        while (rightitem != NULL) {
                compitem_new(NULL, rightitem, cl->items, fExact);
                if (view_newitem(view)) {
                        TickCount = GetTickCount() - TickCount;
                        return(FALSE);
                }
                rightitem = dir_nextitem(cl->right, rightitem, fDeep);
        }
        TickCount = GetTickCount() - TickCount;
        return(TRUE);
} /* complist_match */

/* return time last operation took in milliseconds */
DWORD complist_querytime(void)
{       return TickCount;
}

/***************************************************************************
 * Function: complist_dodlg_savelist
 *
 * Purpose:
 *
 * Dialog to query about filename and types of files. Init dlg fields from
 * the dlg_* variables, and save state to the dlg_* variables on dialog
 * close. return TRUE for OK, or FALSE for cancel (from the dialogbox()
 * using EndDialog).
 *
 **************************************************************************/
int FAR PASCAL
complist_dodlg_savelist(HWND hDlg, UINT message, UINT wParam, long lParam)
{
        static char buffer[256];

        switch(message) {

        case WM_INITDIALOG:
                SendDlgItemMessage(hDlg, IDD_IDENTICAL, BM_SETCHECK,
                        dlg_identical ? 1 : 0, 0);
                SendDlgItemMessage(hDlg, IDD_DIFFER, BM_SETCHECK,
                        dlg_differ ? 1 : 0, 0);
                SendDlgItemMessage(hDlg, IDD_LEFT, BM_SETCHECK,
                        dlg_left ? 1 : 0, 0);
                SendDlgItemMessage(hDlg, IDD_RIGHT, BM_SETCHECK,
                        dlg_right ? 1 : 0, 0);

                SetDlgItemText(hDlg, IDD_FILE, dlg_file);

                /* convert 'left tree' into the right name */
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname);
                SendDlgItemMessage(hDlg, IDD_LEFT, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);

/* convert 'right tree' msg into correct path */
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname);
                SendDlgItemMessage(hDlg, IDD_RIGHT, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);

                return(TRUE);

        case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {

                case IDOK:
                        dlg_identical = (SendDlgItemMessage(hDlg, IDD_IDENTICAL,
                                        BM_GETCHECK, 0, 0) == 1);
                        dlg_differ = (SendDlgItemMessage(hDlg, IDD_DIFFER,
                                        BM_GETCHECK, 0, 0) == 1);
                        dlg_left = (SendDlgItemMessage(hDlg, IDD_LEFT,
                                        BM_GETCHECK, 0, 0) == 1);
                        dlg_right = (SendDlgItemMessage(hDlg, IDD_RIGHT,
                                        BM_GETCHECK, 0, 0) == 1);
                        GetDlgItemText(hDlg, IDD_FILE, dlg_file, sizeof(dlg_file));

                        EndDialog(hDlg, TRUE);
                        break;

                case IDCANCEL:
                        EndDialog(hDlg, FALSE);
                        break;
                }
        }
        return(FALSE);
} /* complist_dodlg_savelist */

/***************************************************************************
 * Function: complist_dodlg_copyfiles
 *
 * Purpose:
 *
 * dialog to get directory name and inclusion options. Init dlg fields from
 * the dlg_* variables, and save state to the dlg_* variables on dialog
 * close. return TRUE for OK, or FALSE for cancel (from the dialogbox()
 * using EndDialog).
 *
 **************************************************************************/
int FAR PASCAL
complist_dodlg_copyfiles(HWND hDlg, UINT message, UINT wParam, long lParam)
{
        static char buffer[256];

        switch(message) {

        case WM_INITDIALOG:
                /*
                 * set checkboxes and directory field to defaults
                 */
                CheckDlgButton(hDlg, IDD_IDENTICAL,
                        (dlg_options & INCLUDE_SAME) ? 1 : 0);

                CheckDlgButton(hDlg, IDD_DIFFER,
                        (dlg_options & INCLUDE_DIFFER) ? 1 : 0);

                CheckDlgButton(hDlg, IDD_LEFT,
                        (dlg_options & (INCLUDE_LEFTONLY|INCLUDE_RIGHTONLY)) ? 1 : 0);

                SetDlgItemText(hDlg, IDD_DIR1, dlg_root);

                /*
                 * set 'copy from' buttons to have the full pathname
                 */
                SetDlgItemText(hDlg, IDD_FROMLEFT, dialog_leftname);
                SetDlgItemText(hDlg, IDD_FROMRIGHT, dialog_rightname);

                /*
                 * set default radio button for copy from, and set
                 * the text on the 'files only in...' checkbox to
                 * indicate which path is being selected
                 */
                if (dlg_options & COPY_FROMLEFT) {
                        CheckRadioButton(hDlg, IDD_FROMLEFT, IDD_FROMRIGHT,
                                        IDD_FROMLEFT);

                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname);
                        SetDlgItemText(hDlg, IDD_LEFT, buffer);
                } else {
                        CheckRadioButton(hDlg, IDD_FROMLEFT, IDD_FROMRIGHT,
                                        IDD_FROMRIGHT);

                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname);
                        SetDlgItemText(hDlg, IDD_LEFT, buffer);
                }

                return(TRUE);

        case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {

                case IDD_FROMLEFT:
                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname);
                        SetDlgItemText(hDlg, IDD_LEFT, buffer);

                        dlg_options &= ~(COPY_FROMRIGHT);
                        dlg_options |= COPY_FROMLEFT;
                        break;

                case IDD_FROMRIGHT:
                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname);
                        SetDlgItemText(hDlg, IDD_LEFT, buffer);

                        dlg_options &= ~(COPY_FROMLEFT);
                        dlg_options |= COPY_FROMRIGHT;
                        break;

                case IDOK:
                        if (SendDlgItemMessage(hDlg, IDD_IDENTICAL,
                            BM_GETCHECK, 0, 0) == 1) {
                                dlg_options |= INCLUDE_SAME;
                        } else {
                                dlg_options &= ~INCLUDE_SAME;
                        }
                        if (SendDlgItemMessage(hDlg, IDD_DIFFER,
                            BM_GETCHECK, 0, 0) == 1) {
                                dlg_options |= INCLUDE_DIFFER;
                        } else {
                                dlg_options &= ~INCLUDE_DIFFER;
                        }
                        if (SendDlgItemMessage(hDlg, IDD_LEFT,
                            BM_GETCHECK, 0, 0) == 1) {
                                dlg_options |= INCLUDE_LEFTONLY;
                        } else {
                                dlg_options &= ~INCLUDE_LEFTONLY;
                        }
                        GetDlgItemText(hDlg, IDD_DIR1, dlg_root, sizeof(dlg_root));

                        EndDialog(hDlg, TRUE);
                        break;

                case IDCANCEL:
                        EndDialog(hDlg, FALSE);
                        break;
                }
        }
        return(FALSE);
} /* complist_dodlg_copyfiles */

/***************************************************************************
 * Function: complist_new
 *
 * Purpose:
 *
 * Allocates a new complist and initialise it
 *
 **************************************************************************/
COMPLIST
complist_new(void)
{
        COMPLIST cl;

        cl = (COMPLIST) gmem_get(hHeap, sizeof(struct complist));
        cl->left = NULL;
        cl->right = NULL;
        cl->items = List_Create();

        return(cl);
} /* complist_new */

/***************************************************************************
 * Function: complist_dodlg_dir
 *
 * Purpose:
 *
 * Dialog box function to ask for two directory names.
 * no listing of files etc - just two edit fields  in which the
 * user can type a file or a directory name.
 *
 * Initialises the names from win.ini, and stores them to win.ini first.
 *
 **************************************************************************/
int FAR PASCAL
complist_dodlg_dir(HWND hDlg, unsigned message, WORD wParam, LONG lParam)
{
        static char path[256];
        static char buffer[256];

        switch (message) {

        case WM_INITDIALOG:

                /* fill the edit fields with the current
                 * directory as a good starting point
                 */
                _getcwd(path, sizeof(path));
                AnsiLowerBuff(path, strlen(path));
                GetProfileString(APPNAME, "NameLeft", path, buffer, 256);
                SetDlgItemText(hDlg, IDD_DIR1, buffer);
                GetProfileString(APPNAME, "NameRight", path, buffer, 256);
                SetDlgItemText(hDlg, IDD_DIR2, buffer);
                /* set recursive option to most recent value */
                CheckDlgButton(hDlg, IDD_RECURSIVE, dlg_recursive);
                return(TRUE);

        case WM_COMMAND:
                switch (LOWORD(wParam)) {
                case IDCANCEL:
                        EndDialog(hDlg, FALSE);
                        return(TRUE);

                case IDOK:
                        /* fetch the text from the dialog, and remember
                         * it in win.ini
                         */

                        GetDlgItemText(hDlg, IDD_DIR1,
                                dialog_leftname, sizeof(dialog_leftname));
                        WriteProfileString(APPNAME, "NameLeft", dialog_leftname);

                        GetDlgItemText(hDlg, IDD_DIR2,
                                dialog_rightname, sizeof(dialog_rightname));
                        WriteProfileString(APPNAME, "NameRight", dialog_rightname);

                        /* fetch recursive option */
                        dlg_recursive = SendDlgItemMessage(hDlg, IDD_RECURSIVE,
                                BM_GETCHECK, 0, 0);

                        EndDialog(hDlg, TRUE);
                        return(TRUE);
                }
                break;
        }
        return(FALSE);
} /* complist_dodlg_dir */

/***************************************************************************
 * Function: complist_open
 *
 * Purpose:
 *
 * Puts up dialog asking the user to select an existing file to open.
 *
 * Parameters:
 *
 *      prompt - message to user indicating purpose of file
 *               (to be displayed somewhere in dialog box.
 *
 *      ext    - default file extension if user enters file without
 *               extension.
 *
 *      spec   - default file spec (eg *.*)
 *
 *      osp    - OFSTRUCT representing file, if successfully open.
 *
 *      fn     - buffer where filename (just final element) is returned.
 *
 * Returns:
 *
 * TRUE - if file selected and exists (tested with OF_EXIST).
 *
 * FALSE - if dialog cancelled. If user selects a file that we cannot
 *           open, we complain and restart the dialog.
 *
 * Comments:
 *
 *           if TRUE is returned, the file will have been successfully opened,
 *           for reading and then closed again.
 *
 **************************************************************************/

BOOL FAR PASCAL
complist_open(LPSTR prompt, LPSTR ext, LPSTR spec, OFSTRUCT FAR *osp, LPSTR fn)
{
    OPENFILENAME ofn;
    char achFilters[256];
    char achPath[256];
    LPSTR chp;
    int fh;

    /* build filter-pair buffer to contain one pair - the spec filter,
     * twice (one of the pair should be the filter, the second should be
     * the title of the filter - we don't have a title so we use the
     * filter both times. remember double null at end of list of strings.
     */
    lstrcpy(achFilters, spec);             // filter + null
    chp = &achFilters[lstrlen(achFilters)+1];      //2nd string just after null
    lstrcpy(chp, spec);                    // filter name (+null)
    chp[lstrlen(chp)+1] = '\0';            // double null at end of list
    /*
     * initialise arguments to dialog proc
     */
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = NULL;
    ofn.hInstance = NULL;
    ofn.lpstrFilter = achFilters;
    ofn.lpstrCustomFilter = (LPSTR)NULL;
    ofn.nMaxCustFilter = 0L;
    ofn.nFilterIndex = 1L;              // first filter pair in list
    achPath[0] = '\0';
    ofn.lpstrFile = achPath;            // we need to get the full path to open
    ofn.nMaxFile = sizeof(achPath);
    ofn.lpstrFileTitle = fn;            // return final elem of name here
    ofn.nMaxFileTitle = sizeof(fn);
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = prompt;            // dialog title is good place for prompt text
    ofn.Flags = OFN_FILEMUSTEXIST |
                OFN_HIDEREADONLY |
                OFN_PATHMUSTEXIST;
    ofn.lpstrDefExt = ext;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lCustData = 0;

    /*
     * loop until the user cancels, or selects a file that we can open
     */
    do {
        if (!GetOpenFileName(&ofn)) {
            return(FALSE);
        }

        fh = OpenFile(achPath, osp, OF_READ);

        if (fh == HFILE_ERROR) {
            if (MessageBox(NULL, LoadRcString(IDS_COULDNT_BE_OPENED), LoadRcString2(IDS_FILEOPEN),
                            MB_OKCANCEL|MB_ICONSTOP) == IDCANCEL) {
                return(FALSE);
            }
        }
    } while (fh == HFILE_ERROR);

    _lclose(fh);

    return(TRUE);
}

/***************************************************************************
 * Function: complist_getroot_left
 *
 * Purpose:
 *
 * Gets the root names of the left tree used to build this complist.
 *
 **************************************************************************/
LPSTR
complist_getroot_left(COMPLIST cl)
{
        return( dir_getroot_list(cl->left));
}

/***************************************************************************
 * Function: complist_getroot_right
 *
 * Purpose:
 *
 * Gets the root names of the right tree used to build this complist.
 *
 **************************************************************************/
LPSTR
complist_getroot_right(COMPLIST cl)
{
        return( dir_getroot_list(cl->right));
}
/***************************************************************************
 * Function: complist_freeroot_*
 *
 * Purpose:
 *
 * Frees up memory allocated in a call to complist_getroot*()
 *
 **************************************************************************/
void
complist_freeroot_left(COMPLIST cl, LPSTR path)
{
        dir_freeroot_list(cl->left, path);
}

void
complist_freeroot_right(COMPLIST cl, LPSTR path)
{
        dir_freeroot_list(cl->right, path);
}


COMPLIST.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.
\******************************************************************************/

/*
 * COMPLIST.H
 */

/* The file view.h includes the term COMPLIST: we need to use the term VIEW.
 * Due to MIPS limitations,the module which declares it first does
 * the real declares and the second one gets no-ops.
 */
#ifndef INC_VIEW_COMPLIST
#define INC_VIEW_COMPLIST
typedef struct view FAR * VIEW;                 /* handle to a VIEW     */
typedef struct complist FAR * COMPLIST;         /* handle to a complist */
#endif // INC_VIEW_COMPLIST

COMPLIST complist_filedialog(VIEW view);
COMPLIST complist_dirdialog(VIEW view);
COMPLIST complist_args(LPSTR path1, LPSTR path2, VIEW view, BOOL fDeep);
void complist_delete(COMPLIST cl);
LIST complist_getitems(COMPLIST cl);
void complist_savelist(COMPLIST cl, LPSTR savename, UINT saveopts);
void complist_copyfiles(COMPLIST cl, LPSTR newroot, UINT options);
DWORD complist_querytime(void);
BOOL APIENTRY complist_open(LPSTR prompt, LPSTR ext, LPSTR spec,
        OFSTRUCT FAR *osp, LPSTR fn);
LPSTR complist_getroot_left(COMPLIST cl);
LPSTR complist_getroot_right(COMPLIST cl);
void complist_freeroot_left(COMPLIST cl, LPSTR path);
void complist_freeroot_right(COMPLIST cl, LPSTR path);

/*
 * Copy file options are either COPY_FROMLEFT or COPY_FROMRIGHT
 * (indicating which
 * tree is to be the source of the files, plus any or all of
 * INCLUDE_SAME, INCLUDE_DIFFER and INCLUDE_LEFT (INCLUDE_LEFT
 * and INCLUDE_RIGHT are treated the same here since the COPY_FROM* option
 * indicates which side to copy from).
 */

#define COPY_FROMLEFT   0x100           /* copy files from left tree */
#define COPY_FROMRIGHT  0x200           /* copy files from right tree */


FILE.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 Header *******************************
* Module Name: FILE.C
*
* An object representing a file and the lines of text it contains.
*
* Functions:
*
* file_new()
* file_getdiritem()
* file_delete()
* file_getlinelist()
* file_discardlines()
* file_reset()
* file_readlines()
*
* Comments:
*
* A FILEDATA object is initialised with a DIRITEM handle from which it
* can get a filename. It knows how to supply a list of LINE handles for the
* lines of text in the file.
*
* The file is read into memory optionally on creation of the FILEDATA object:
* otherwise, at the first call to file_getlinelist. It can be discarded
* by calling file_discardlines: in this case, it will be re-read next time
* file_getlinelist is called.
*
* Calling file_reset will cause line_reset to be called for all lines
* in the list. This clears any links.
*
* We allocate all memory from a gmem* heap hHeap, assumed to be declared and
* initialised elsewhere.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <string.h>

#include "gutils.h"
#include "windiff.h"
#include "list.h"
#include "line.h"
#include "scandir.h"
#include "file.h"

extern HANDLE hHeap;

struct filedata {

        DIRITEM diritem;        /* handle to file name information */
        LIST lines;             /* NULL if lines not read in */
};

void file_readlines(FILEDATA fd);

/****************************************************************************
 * Function: file_new
 *
 * Purpose:
 *
 * Creates a new FILEDATA, based on a DIRITEM. the filedata will retain
 * the diritem handle for use in fetching filenames and handles.
 *
 * If the bRead is set, the file will be read into memory. If not, this
 * will be done during the first call to file_getlines.
 *
 ***************************************************************************/
FILEDATA
file_new(DIRITEM fiName, BOOL bRead)
{
        FILEDATA fd;

        fd = (FILEDATA) gmem_get(hHeap, sizeof(struct filedata));
        if (fd == NULL) {
                return(NULL);
        }

        fd->diritem = fiName;
        fd->lines = NULL;

        if (bRead) {
                file_readlines(fd);
        }

        return(fd);
}

/****************************************************************************
 * Function: file_getdiritem
 *
 * Purpose:
 *
 * Returns a handle to the DIRITEM used to create this FILEDATA
 *
 ***************************************************************************/
DIRITEM
file_getdiritem(FILEDATA fd)
{
        if (fd == NULL) {
                return(NULL);
        }

        return(fd->diritem);
}

/****************************************************************************
 * Function: file_delete
 *
 * Purpose:
 *
 * Deletes a filedata and its associated list of lines. Note that the diritem
 * is not deleted (this is owned by the DIRLIST, and will be deleted
 * when the DIRLIST is deleted)
 *
 ***************************************************************************/
void
file_delete(FILEDATA fd)
{
        if (fd == NULL) {
                return;
        }

        /* throw away the line list, if there is one */
        file_discardlines(fd);

        gmem_free(hHeap, (LPSTR) fd, sizeof(struct filedata));
}

/****************************************************************************
 * Function: file_getlinelist
 *
 * Purpose:
 *
 * Returns a handle to a list of lines in this file. The items in the
 * list are LINE handles.
 *
 * The first call to this function will cause the file to be read into
 * memory if bRead was FALSE on the call to file_new, or if file_discardlines
 * has since been called.
 *
 * The list of lines returned should not be deleted except by calls to
 * file_delete or file_discardlines.
 *
 ***************************************************************************/
LIST
file_getlinelist(FILEDATA fd)
{
        if (fd == NULL) {
                return NULL;
        }

        if (fd->lines == NULL) {
                file_readlines(fd);
        }
        return(fd->lines);
}

/****************************************************************************
 * Function: file_discardlines
 *
 * Purpose:
 *
 * Discards the list of lines associated with a file. This will cause
 * the file to be re-read next time file_getlinelist is called.
 *
 ***************************************************************************/
void
file_discardlines(FILEDATA fd)
{
        LINE line;

        if (fd == NULL) {
                return;
        }

        if (fd->lines != NULL) {

                /* clear each line to free any memory associated
                 * with them, then discard the entire list
                 */
                List_TRAVERSE(fd->lines, line) {
                        line_delete(line);
                }
                List_Destroy(&fd->lines);
        }

        /* this is probably done in List_Destroy, but better do it anyway*/
        fd->lines = NULL;
}

/****************************************************************************
 * Function: file_reset
 *
 * Purpose:
 *
 * Forces a reset of each line in the list. The function line_reset discards
 * links between lines, and any hashcode information. This would be used if
 * the compare options or hashcode options have changed.
 *
 ***************************************************************************/
void
file_reset(FILEDATA fd)
{
        LINE line;

        if (fd == NULL) {
                return;
        }

        if (fd->lines != NULL) {

                List_TRAVERSE(fd->lines, line)  {
                        line_reset(line);
                }
        }
}

/****************************************************************************
 * Function: file_readlines
 *
 * Purpose:
 *
 * Reads the file into a list of lines.
 *
 * Comments:
 *
 * We use the buffered read functions to read a block at a time, and
 * return us a pointer to a line within the block. The line we are
 * pointed to is not null terminated. from this we do a line_new: this
 * will make a copy of the text (since we want to re-use the buffer), and
 * will null-terminate its copy.
 *
 *
 ***************************************************************************/
void
file_readlines(FILEDATA fd)
{
        LPSTR textp;
        int fh;
        FILEBUFFER fbuf;
        int linelen;
        int linenr = 1;
        HCURSOR hcurs;

        hcurs = SetCursor(LoadCursor(NULL, IDC_WAIT));

        /* open the file */
        fh = dir_openfile(fd->diritem);

        if (fh < 0) {
                SetCursor(hcurs);
                return;
        }
        /* initialise the file buffering */
        fbuf = readfile_new(fh);

        /* make an empty list for the files */
        fd->lines = List_Create();

        while ( (textp = readfile_next(fbuf, &linelen)) != NULL) {

                line_new(textp, linelen, linenr++, fd->lines);

        }

        /* close filehandle and free buffer */
        readfile_delete(fbuf);

        dir_closefile(fd->diritem, fh);

        SetCursor(hcurs);
}


FILE.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.
\******************************************************************************/

/*
 * FILE.H
 */

/* handle to filedata */
typedef struct filedata FAR * FILEDATA;

FILEDATA file_new(DIRITEM fiName, BOOL bRead);
DIRITEM file_getdiritem(FILEDATA fi);
void file_delete(FILEDATA fi);
LIST file_getlinelist(FILEDATA fi);
void file_discardlines(FILEDATA fi);
void file_reset(FILEDATA fi);


GBIT.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 Header *******************************
* Module Name: GBIT.C
*
* Bitmap allocation routines to manage a bit-mapped free list, and find
* free sections.
*
* Functions:
*
* gbit_set()
* gbit_init()
* gbit_alloc()
* gbit_free()
* gbit_findfree()
*
* Comments:
*
* Each map is an array of unsigned longs where bit 0 of the first
* long represents block 1.
*
****************************************************************************/

#include <windows.h>
#include "gutils.h"

BOOL gbit_set(DWORD FAR * map, long blknr, long nblks, BOOL op_set);

/***************************************************************************
 * Function: gbit_init
 *
 * Purpose:
 *
 * Initialise a pre-allocated map of ulongs to represent a free
 * area of nblks
 */
void APIENTRY
gbit_init(DWORD FAR * map, long nblks)
{
        long i;
        long leftover = nblks % 32;
        long blks = nblks / 32;
        DWORD last = 0;

        for (i=0; i < blks; i++) {
                map[i] = 0xffffffff;
        }
        for (i = 0; i < leftover; i++) {
                last = (last << 1) | 1;
        }
        if(leftover)
                map[blks] = last;
}

/***************************************************************************
 * Function: gbit_alloc
 *
 * Purpose:
 *
 * Mark a region starting at blknr for nblks, as busy (ie 0)
 */
BOOL APIENTRY
gbit_alloc(DWORD FAR * map, long blknr, long nblks)
{
        return(gbit_set(map, blknr, nblks, FALSE));
}

/***************************************************************************
 * Function: gbit_set
 *
 * Purpose:
 *
 * Mark region - if op_set, to 1s, otherwise to 0s
 */
BOOL
gbit_set(DWORD FAR * map, long blknr, long nblks, BOOL op_set)
{
        long first;
        long last;
        long fullwords;
        long startbit, startword;
        long i;
        DWORD dword = 0;

        blknr--;
        first = min(32 - (blknr % 32), nblks);
        nblks -= first;
        last = nblks % 32;
        fullwords = (nblks - last) / 32;

        startword = blknr / 32;
        startbit = blknr % 32;
        for (i = 0; i < first; i++) {
                dword = (dword << 1) | 1;
        }
        dword <<= startbit;
        if (op_set) {
                map[startword] |= dword;
                dword = 0xffffffff;
        } else {
                map[startword] &= ~dword;
                dword = 0;
        }
        startword++;
        for (i = 0; i < fullwords; i++) {
                map[startword+i] = dword;
        }
        startword += fullwords;
        for(i = 0, dword = 0; i < last; i++) {
                dword = (dword << 1) | 1;
        }
        if (last) {
                if (op_set) {
                        map[startword] |= dword;
                } else {
                        map[startword] &= ~dword;
                }
        }

        return(TRUE);
}

/***************************************************************************
 * Function: gbit_free
 *
 * Purpose:
 *
 * Mark region of nblks starting at blknr to 0s - ie not busy
 */
BOOL APIENTRY
gbit_free(DWORD FAR * map, long blknr, long nblks)
{
        return(gbit_set(map, blknr, nblks, TRUE));
}

/***************************************************************************
 * Function: gbit_findfree
 *
 * Purpose:
 *
 * Find a free segment (ie contiguous sequence of 1s) of nblks in length.
 * If not found, find longest sequence. Store address of segment in *blknr.
 *
 * Return value is nr of blks in sequence found. Region is *not* marked busy.
 */
long APIENTRY
gbit_findfree(DWORD FAR* map, long nblks, long mapsize, long FAR * blknr)
{
        long curblk, startblk, len, i;
        long startbit, nfull, nlast, nbitsleft;
        DWORD mask;
        long mapblks = (mapsize + 31) / 32;
        long aubegin = 0, aulen = 0;
        long curbit = 0;

        /* main loop looking at segments */
        for (curblk = 0; curblk < mapblks; ) {
loop:
                /* loop finding first 1 */
                for (; curblk < mapblks; curblk++, curbit = 0) {
                        if (map[curblk] > 0) {
                                break;
                        }
                }
                if (curblk >= mapblks)
                        break;

                /* find first 1 in this long */
                startblk = curblk;
                for (mask = 1, i = 0; i < curbit; i++) {
                        mask <<= 1;
                }
                for(; curbit < 32; curbit++, mask <<= 1) {
                        if (map[curblk] & mask) {
                                break;
                        }
                }
                if (curbit >= 32) {
                        /* abandon this word - start again with next word */
                        curblk++;
                        curbit = 0;
                        goto loop;
                }

                /* we've now found a 1 - calc remaining
                 * bits in this word, complete words etc required.
                 */
                startbit = curbit;
                nbitsleft = min( (32 - curbit), nblks);
                nfull = (nblks - nbitsleft) / 32;
                nlast = (nblks - nbitsleft) % 32;

                /* check for required sequence within this word */

                for (i = 0; i < nbitsleft; i++, curbit++, mask <<= 1) {
                        if ((map[curblk] & mask) == 0) {
                                /* abandon and start again - start
                                 * next pass at curbit in same word
                                 */
                                /* store free region if longest yet */
                                if (i > aulen) {
                                        aulen = i;
                                        aubegin = curblk * 32 + startbit +1;
                                }
                                goto loop;
                        }
                }

                /* check for nfull full words */
                for (curblk++; curblk <= startblk + nfull; curblk++) {
                        if (curblk >= mapblks) {
                                /* end of map - abandon here and exit at top
                                 * of loop
                                 */
                                len = nbitsleft +
                                        ((curblk - (startblk + 1)) * 32);
                                if (len > aulen) {
                                        aubegin = startblk * 32 + startbit + 1;
                                        aulen = len;
                                }
                                goto loop;
                        }
                        if (map[curblk] != 0xffffffff) {
                                /* not a full word - start again at this bit */
                                len = 0;
                                curbit = 0;
                                for (mask = 1; mask & map[curblk]; mask <<= 1) {
                                        len++;
                                        curbit++;
                                }
                                len += nbitsleft +
                                        (curblk - (startblk+ 1)) * 32;
                                if (len > aulen) {
                                        aulen = len;
                                        aubegin = startblk * 32 + startbit + 1;
                                }
                                /* continue with current blk, bit */
                                goto loop;
                        }
                }

                /* left-over bits required in last word */
                mask = 1;
                for (curbit = 0; curbit < nlast;  curbit++, mask <<= 1) {
                        if ((map[curblk] & mask) == 0) {
                                len = nbitsleft + (nfull * 32);
                                len += curbit;
                                if (len > aulen) {
                                        aulen = len;
                                        aubegin = startblk * 32 + startbit + 1;
                                }
                                goto loop;
                        }
                }
                /* ok - found a block big enough! */
                aubegin = startblk * 32 + startbit + 1;
                *blknr = aubegin;
                return(nblks);
        }

        /* end of map - return longest sequence */
        *blknr = aubegin;
        return(aulen);
}


GDATE.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 Header *******************************
* Module Name: GDATE.C
*
* Contains date conversion functions.
*
* Functions:
*
* gdi_isleap()
* gdate_daytodmy()
* gdate_dmytoday()
* gdate_monthdays()
* gdate_weeklyday()
*
* Comments:
*
****************************************************************************/

#include <windows.h>
#include <string.h>

#include "gutils.h"

BOOL gdi_isleap(LONG year);

/*---static data--------------------------------------------*/

int monthdays[] = {
        31,
        28,
        31,
        30,
        31,
        30,
        31,
        31,
        30,
        31,
        30,
        31
};

/***************************************************************************
 * Function: gdate_daytomy
 *
 * Purpose:
 *
 * converts day to d/m/y
 */
void APIENTRY
gdate_daytodmy(LONG days, int FAR* yrp, int FAR* monthp, int FAR* dayp)
{
        int years;
        int nleaps;
        int month;
        int mdays;

        /* get number of completed years and calc leap days */
        years = (int) (days / 365);
        days = days % 365;
        nleaps = (years / 4) - (years / 100) + (years / 400);
        while (nleaps > days) {
                days += 365;
                years--;
                nleaps = (years / 4) - (years / 100) + (years / 400);
        }
        days -= nleaps;

        /* add one year for current (non-complete) year */
        years++;

        /* current month */
        for (month = 0; month < 12; month++) {
                mdays = monthdays[month];
                if (gdi_isleap(years) && (month == 1)) {
                        mdays++;
                }
                if (days == mdays) {
                        days = 0;
                        month++;
                        break;
                } else if (days < mdays) {
                        break;
                } else {
                        days -= mdays;
                }
        }
        /* conv month from 0-11 to 1-12 */
        if (monthp != NULL) {
                *monthp = month+1;
        }
        if (dayp != NULL) {
                *dayp = (int) days + 1;
        }
        if (yrp != NULL) {
                *yrp = years;
        }
}

/***************************************************************************
 * Function: gdate_dmytoday
 *
 * Purpose:
 *
 * converts d/m/y to a day
 */
LONG APIENTRY
gdate_dmytoday(int yr, int month, int day)
{
        int nleaps;
        int i;
        long ndays;

        /* exclude the current year */
        yr--;
        nleaps = (yr / 4) - (yr / 100) + (yr / 400);

        /* in any given year, day 0 is jan1 */
        month--;
        day--;
        ndays = 0;
        for (i = 0; i < month ; i++) {
                ndays += monthdays[i];
                if (gdi_isleap(yr+1) && (i == 1)) {
                        ndays++;
                }
        }
        ndays = ndays + day + nleaps + (yr * 365L);
        return(ndays);
}

/***************************************************************************
 * Function: gdate_monthdays
 *
 * Purpose:
 *
 * Gets number of days in month
 */
int APIENTRY
gdate_monthdays(int month, int year)
{
        int ndays;

        ndays = monthdays[month - 1];
        if (gdi_isleap(year) && (month == 2)) {
                ndays++;
        }
        return(ndays);
}

/***************************************************************************
 * Function: gdate_weekday
 *
 * Purpose:
 *
 * Gets the day of the week
 */
int APIENTRY
gdate_weekday(long daynr)
{
        return((int) ((daynr + 1) % 7));
}

/***************************************************************************
 * Function: gdi_isleap
 *
 * Purpose:
 *
 * Determines whether the year is a leap year
 */
BOOL
gdi_isleap(LONG year)
{
        if ( ((year % 4) == 0) &&
                (((year % 100) != 0) ||
                ((year % 400) == 0))) {
                        return TRUE;
        } else {
                return FALSE;
        }
}


GMEM.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 Header *******************************
* Module Name: GMEM.C
*
* Memory utility functions.
*
* Functions:
*
* gmem_panic()
* gmem_init()
* gmem_get()
* gmem_free()
* gmem_freeall()
*
* Comments:
*
* Global heap functions - allocate and free many small
* pieces of memory by calling global alloc for large pieces
* and breaking them up. A heap contains a critical section, so
* multiple simultaneous calls to gmem_get and gmem_free will be
* protected.
*
* gmem_freeall should not be called until all other users have finished
* with the heap.
*
* Out-of-memory is not something we regard as normal.
* If we cannot allocate memory - we put up an abort-retry-ignore
* error, and only return from the function if the user selects ignore.
*
****************************************************************************/

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

#include "gutils.h"
#include "gutilsrc.h"

int gmem_panic(void);

/* ensure BLKSIZE is multiple of sizeof(DWORD) */
#define BLKSIZE         64               /* blk size in bytes */
#define ALLOCSIZE       32768
#define NBLKS           (ALLOCSIZE / BLKSIZE)
#define MAPSIZE         (NBLKS / 8)
#define MAPLONGS        (MAPSIZE / sizeof(DWORD))
#define TO_BLKS(x)      (((x) + BLKSIZE - 1) / BLKSIZE)

typedef struct seghdr {
        HANDLE hseg;
        CRITICAL_SECTION critsec;
        struct seghdr FAR * pnext;
        long nblocks;
        DWORD segmap[MAPLONGS];
} SEGHDR, FAR * SEGHDRP;

/* anything above this size, we alloc directly from global heap */
#define MAXGALLOC       20000

/***************************************************************************
 * Function: gmem_init
 *
 * Purpose:
 *
 * init heap - create first segment
 */
HANDLE APIENTRY
gmem_init(void)
{
        HANDLE hNew;
        SEGHDRP hp;

        /* retry all memory allocations after calling gmem_panic */
        do {
                hNew = GlobalAlloc(GHND, ALLOCSIZE);
                if (hNew == NULL) {
                        if (gmem_panic() == IDIGNORE) {
                                return(NULL);
                        }
                }
        } while  (hNew == NULL);

        hp = (SEGHDRP) GlobalLock(hNew);
        if (hp == NULL) {
                return(NULL);
        }
        hp->hseg = hNew;
        InitializeCriticalSection(&hp->critsec);
        hp->pnext = NULL;
        gbit_init(hp->segmap, NBLKS);
        gbit_alloc(hp->segmap, 1, TO_BLKS(sizeof(SEGHDR)));
        hp->nblocks = NBLKS - TO_BLKS(sizeof(SEGHDR));

        return(hNew);
}

/***************************************************************************
 * Function: gmem_get
 *
 * Purpose:
 *
 * Get memory from heap
 */
LPSTR APIENTRY
gmem_get(HANDLE hHeap, int len)
{
        SEGHDRP chainp;
        HANDLE hNew;
        SEGHDRP hp;
        LPSTR chp;
        long nblks;
        long start;
        long nfound;

        /* the heap is always locked (in gmem_init)- so having got the
         * pointer, we can always safely unlock it
         */
        chainp = (SEGHDRP) GlobalLock(hHeap);
        GlobalUnlock(hHeap);

        if (len < 1) {
                return(NULL);
        }

        /*
         * too big to be worth allocing from heap - get from globalalloc
         */
        if (len > MAXGALLOC) {
                /* retry all memory allocations after calling gmem_panic */
                do {
                        hNew = GlobalAlloc(GHND, len);
                        if (hNew == NULL) {
                                if (gmem_panic() == IDIGNORE) {
                                        return(NULL);
                                }
                        }
                } while  (hNew == NULL);

                chp = GlobalLock(hNew);
                if (chp == NULL) {
                        return(NULL);
                }
                return(chp);
        }

        /*
         * get critical section during all access to the heap itself
         */
        EnterCriticalSection(&chainp->critsec);

        nblks = TO_BLKS(len + sizeof(HANDLE));

        for (hp = chainp; hp !=NULL; hp = hp->pnext) {
                if (hp->nblocks >= nblks) {
                        nfound = gbit_findfree(hp->segmap, nblks,NBLKS, &start);
                        if (nfound >= nblks) {
                                gbit_alloc(hp->segmap, start, nblks);
                                hp->nblocks -= nblks;

                                /* convert blocknr to pointer
                                 * store seg handle in block
                                 */
                                chp = (LPSTR) hp;
                                chp = &chp[ (start-1) * BLKSIZE];
                                * ( (HANDLE FAR *) chp) = hp->hseg;
                                chp += sizeof(HANDLE);

                                break;
                        }
                }
        }
        if (hp == NULL) {
                /* retry all memory allocations after calling gmem_panic */
                do {
                        hNew = GlobalAlloc(GHND, ALLOCSIZE);
                        if (hNew == NULL) {
                                if (gmem_panic() == IDIGNORE) {
                                        LeaveCriticalSection(&chainp->critsec);
                                        return(NULL);
                                }
                        }
                } while  (hNew == NULL);

                hp = (SEGHDRP) GlobalLock(hNew);
                if (hp == NULL) {
                        LeaveCriticalSection(&chainp->critsec);
                        return(NULL);
                }
                hp->pnext = chainp->pnext;
                hp->hseg = hNew;
                chainp->pnext = hp;
                gbit_init(hp->segmap, NBLKS);
                gbit_alloc(hp->segmap, 1, TO_BLKS(sizeof(SEGHDR)));
                hp->nblocks = NBLKS - TO_BLKS(sizeof(SEGHDR));
                nfound = gbit_findfree(hp->segmap, nblks, NBLKS, &start);
                if (nfound >= nblks) {
                        gbit_alloc(hp->segmap, start, nblks);
                        hp->nblocks -= nblks;

                        /* convert block nr to pointer */
                        chp = (LPSTR) hp;
                        chp = &chp[ (start-1) * BLKSIZE];
                        /* add a handle into the block and skip past */
                        * ( (HANDLE FAR *) chp) = hp->hseg;
                        chp += sizeof(HANDLE);
                }
        }
        LeaveCriticalSection(&chainp->critsec);
        memset(chp, 0, len);
        return(chp);
}

/***************************************************************************
 * Function: gmem_free
 *
 * Purpose:
 *
 * Free memory alloced
 */
void APIENTRY
gmem_free(HANDLE hHeap, LPSTR ptr, int len)
{
        SEGHDRP chainp;
        SEGHDRP hp;
        HANDLE hmem;
        long nblks, blknr;
        LPSTR chp;

        if (len < 1) {
                return;
        }

        /*
         * allocs greater than MAXGALLOC are too big to be worth
         * allocing from the heap - they will have been allocated
         * directly from globalalloc
         */
        if (len > MAXGALLOC) {
                hmem = GlobalHandle( (LPSTR) ptr);
                GlobalUnlock(hmem);
                GlobalFree(hmem);
                return;
        }

        chainp = (SEGHDRP) GlobalLock(hHeap);
        EnterCriticalSection(&chainp->critsec);

        /* just before the ptr we gave the user, is the handle to
         * the block
         */
        chp = (LPSTR) ptr;
        chp -= sizeof(HANDLE);
        hmem = * ((HANDLE FAR *) chp);
        hp = (SEGHDRP) GlobalLock(hmem);

        nblks = TO_BLKS(len + sizeof(HANDLE));

        /* convert ptr to block nr */
        blknr = TO_BLKS( (unsigned) (chp - (LPSTR) hp) ) + 1;

        gbit_free(hp->segmap, blknr, nblks);
        hp->nblocks += nblks;

        GlobalUnlock(hmem);

   LeaveCriticalSection(&chainp->critsec);
        GlobalUnlock(hHeap);

}

/***************************************************************************
 * Function: gmem_freeall
 *
 * Purpose:
 *
 * Free heap
 */
void APIENTRY
gmem_freeall(HANDLE hHeap)
{
        SEGHDRP chainp;
        HANDLE hSeg;

        chainp = (SEGHDRP) GlobalLock(hHeap);
        /* this segment is always locked - so we need to unlock
         * it here as well as below
         */
        GlobalUnlock(hHeap);

        /* finished with the critical section  -
         * caller must ensure that at this point there is no
         * longer any contention
         */
        DeleteCriticalSection(&chainp->critsec);

        while (chainp != NULL) {
                hSeg = chainp->hseg;
                chainp = chainp->pnext;
                GlobalUnlock(hSeg);
                GlobalFree(hSeg);
        }
}

/***************************************************************************
 * Function: gmem_panic
 *
 * Purpose:
 *
 * A memory allocation attempt has failed. Return IDIGNORE to ignore the
 * error and return NULL to the caller, and IDRETRY to retry the allocation
 * attempt.
 */
int
gmem_panic(void)
{
        int code;
    extern HANDLE hLibInst;
TCHAR szBuf1[512];
    TCHAR szBuf2[512];

        LoadString(hLibInst, IDS_MEMORY_ALLOC_FAIL, szBuf1, sizeof(szBuf1));
        LoadString(hLibInst, IDS_OUT_OF_MEMORY, szBuf2, sizeof(szBuf2));

        code = MessageBox(NULL, szBuf1, szBuf2,
                        MB_ICONSTOP|MB_ABORTRETRYIGNORE);
        if (code == IDABORT) {
                /* abort this whole process */
                ExitProcess(1);
        } else {
                return(code);
        }
}


GUTILS.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 Header *******************************
* Module Name: GUTILS.C
*
* Entry point for GUTILS.DLL
*
* Functions:
*
* DllMain()
*
****************************************************************************/

#include <windows.h>

#include "gutils.h"
#include "gutilsrc.h"

/* dll global data */
HANDLE hLibInst;
extern void gtab_init(void);
extern BOOL StatusInit(HANDLE);

BOOL APIENTRY DllMain(HANDLE hInstance, DWORD dwReason, LPVOID reserved)
{
        if (dwReason == DLL_PROCESS_ATTACH) {
                hLibInst = hInstance;
                gtab_init();
                StatusInit(hLibInst);
        }
        return(TRUE);
}


GUTILS.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.
\******************************************************************************/

/*
 * GUTILS.H
 */

/* win32 msg crackers */
#define GET_WM_COMMAND_ID(w, l) (LOWORD(w))
#define GET_WM_COMMAND_CMD(w, l) (HIWORD(w))
#define GET_WM_COMMAND_HWND(w, l) (l)
#define GET_SCROLL_OPCODE(w, l)     (LOWORD(w))
#define GET_SCROLL_POS(w, l)        (HIWORD(w))

/* ------- memory allocator ------------------------------------------*/
HANDLE APIENTRY gmem_init(void);
LPSTR APIENTRY gmem_get(HANDLE hHeap, int len);
void APIENTRY gmem_free(HANDLE hHeap, LPSTR ptr, int len);
void APIENTRY gmem_freeall(HANDLE hHeap);

/* --------- date conversion functions    -----------------------*/
void APIENTRY gdate_daytodmy(LONG days,
        int FAR* yrp, int FAR* monthp, int FAR* dayp);
LONG APIENTRY gdate_dmytoday(int yr, int month, int day);
int APIENTRY gdate_monthdays(int month, int year);
int APIENTRY gdate_weekday(long daynr);

/* --- status line window class ---------------------------------- */
/* The status line is a bar across the top or bottom of the window.
 * It can hold a number of fields which can be either static text
 * or buttons.  The so called "static" text can be changed at any time.
 * The fields can be left or right aligned (default is RIGHT).
 * If the text is marked as VAR then the screen real estate allocated
 * for it will be adjusted whenever the text changes.  VAR fields
 * can be given minimum or maximum sizes (but not both).
 *
 * STATIC text fields can be drawn as raised or lowered rectangles (using
 * shades of grey), or (default) without a border. BUTTON fields will
 * always be drawn as raised rectangles, and will lower when pressed.
 *
 * Button fields will send WM_COMMAND messages when clicked including the
 * field id and the WM_LBUTTONUP notification code. Note that that this
 * is not a full implementation of the button class, and no other messages
 * will be sent. In general, none of the fields of a status bar are
 * implemented as separate windows, so GetDlgItem() and similar calls will not
 * work. Buttons only respond to mouse down events, and there is no handling
 * of the focus or of keyboard events.
 *
 * To use:
 *    call StatusAlloc giving the number of items you are going to add to the
 *    status bar. This returns a handle to use in subsequent calls.
 *
 *    Then call StatusAddItem to define each item in turn.
 *    Buttons are placed in order of definition along the bar starting from
 *    the left (SF_LEFT) and from the right (SF_RIGHT) until the two
 *    sides meet.
 *
 *    Call StatusHeight to find the expected height of this status bar, and
 *    set its position within the parent window, then call StatusCreate to
 *    create the window.
 *
 * Having created the window, send SM_SETTEXT messages to set the new
 * text of a field (static or button), or SM_NEW with a handle (obtained from
 * StatusAlloc) to change the contents of the status line.
 */

/* values for type argument to StatusAddItem */
#define SF_BUTTON       1
#define SF_STATIC       2

/* bits in flags argument to StatusAddItem */
#define SF_RAISE        1       /* paint static as raised 3D rectangle */
#define SF_LOWER        2       /* paint static as lowered 3D rectangle */
#define SF_LEFT         4       /* align field on left of status bar */
#define SF_RIGHT        8       /* align field on right (DEFAULT) */
#define SF_VAR          0x10    /* size of field depends on actual text extent*/
#define SF_SZMAX        0x20    /* (with SF_VAR): width argument is maximum */
#define SF_SZMIN        0x40    /* (with SF_VAR) width arg is minimum size */

HWND APIENTRY StatusCreate(HANDLE hInst, HWND hParent, int id,
                LPRECT rcp, HANDLE hmem);
int APIENTRY StatusHeight(HANDLE hmem);
HANDLE APIENTRY StatusAlloc(int nitems);
BOOL APIENTRY StatusAddItem(HANDLE hmem, int itemnr, int type, int flags,
        int id, int width, LPSTR text);

/* send these window messages to the class */

#define SM_NEW          (WM_USER+1)     /* wParam handle for new status line */
#define SM_SETTEXT      (WM_USER+2)     /* wparam: item id, lparam new label*/

void APIENTRY gbit_init(DWORD FAR * map, long nblks);
BOOL APIENTRY gbit_alloc(DWORD FAR * map, long blknr, long nblks);
BOOL APIENTRY gbit_free(DWORD FAR * map, long blknr, long nblks);
long APIENTRY gbit_findfree(DWORD FAR* map, long nblks,
                long mapsize, long FAR * blknr);

/* ----- buffered line input ----------------------------------*/
 /* handle to a file buffer */
typedef struct filebuffer * FILEBUFFER;

FILEBUFFER APIENTRY readfile_new(int fh);
LPSTR APIENTRY readfile_next(FILEBUFFER fb, int FAR * plen);
void APIENTRY readfile_delete(FILEBUFFER fb);

LPTSTR APIENTRY LoadRcString(UINT);
LPTSTR APIENTRY LoadRcString2(UINT);

/* ------ hashing  ------------------------------------------- */
DWORD APIENTRY hash_string(LPSTR string, BOOL bIgnoreBlanks);
BOOL APIENTRY utils_isblank(LPSTR string);
int APIENTRY utils_CompPath(LPSTR left, LPSTR right);

/* --- simple input ------------------------------------------------------*/
int APIENTRY StringInput(LPSTR result, int resultsize, LPSTR prompt,
                         LPSTR caption, LPSTR def_input);

// DBCS friendly versions of string library functions
// These are for both WINDIFF.EXE and GUTILS.DLL.
#define strchr          My_mbschr
#define Old_strncpystrncpy
#define strncpy         My_mbsncpy
unsigned char * _CRTAPI1 My_mbschr(unsigned char *, unsigned short);
unsigned char * _CRTAPI1 My_mbsncpy(
                unsigned char *, const unsigned char *, size_t);

// These are for WINDIFF.EXE.
#define strrchr         My_mbsrchr
#define strncmp         My_mbsncmp
unsigned char * _CRTAPI1 My_mbsrchr(unsigned char *, unsigned short);
int _CRTAPI1 My_mbsncmp(const unsigned char *, const unsigned char *, size_t);


GUTILSRC.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 IDC_LFNAME    201
#define IDC_FNAME     202
#define IDC_PROMPT    203
#define IDC_LFILES    204
#define IDC_LSDIR     205
#define IDC_SDIR      206
#define IDC_LDIRS     207
#define IDC_FILES     208
#define IDC_DIRS      209
#define IDC_OPEN      210
#define IDC_CANCEL    211

#define IDC_LPAGENR                 102

#define IDD_LABEL     301
#define IDD_FILE      302

// string id
#define IDS_MEMORY_ALLOC_FAIL   401
#define IDS_OUT_OF_MEMORY       402
#define IDS_PAGE_STR            403
#define IDS_LIST_ALLOC_NEGATIVE 404


LINE.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 Header *******************************
* Module Name: LINE.C
*
* Functions that handle lines of text to be output.
*
* Functions:
*
* line_new()
* line_delete()
* line_reset()
* line_gettext()
* line_gettabbedlength()
* line_getlink()
* line_getlinenr()
* line_compare()
* line_link()
* line_isblank()
*
* Comments:
*
* LINE is a data type representing a string of ascii text along with
* a line number.
*
* A LINE can compare itself to another line, and maintain a link if the
* lines are similar.
*
* Comparisons between lines take note of the global option flag
* ignore_blanks, defined elsewhere. If this is true, we ignore
* differences in spaces and tabs when comparing lines, and when
* generating hashcodes.
*
* Links and are only generated once. To clear the link call line_reset.
*
* Lines can be allocated on a list. If a null list handle is passed, the
* line will be allocated using gmem_get() from the hHeap defined and
* initialised elsewhere.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <string.h>

#include "gutils.h"
#include "windiff.h"    /* defines hHeap and ignore_blanks */
#include "list.h"
#include "line.h"

struct fileline {

        UINT flags;     /* see below */

        LPSTR text;     /* null-terminated copy of line text */
        DWORD hash;     /* hashcode for line */
        LINE link;      /* handle for linked line */
        UINT linenr;    /* line number (any arbitrary value) */
};

/* flag values (or-ed) */
#define LF_DISCARD      1       /* if true, alloced from gmem heap */
#define LF_HASHVALID    2       /* if true, hashcode need not be recalced */

/***************************************************************************
 * Function: line_new
 *
 * Purpose:
 *
 * Creates a new line and makes a copy of the text.
 *
 * If the list is non-null, allocate on the list. If null, alloc from
 * gmem_get.
 *
 ***************************************************************************/
LINE
line_new(LPSTR text, int linelength, UINT linenr, LIST list)
{
        LINE line;

        /* alloc a line. from the list if there is a list */
        if (list) {
                line = List_NewLast(list, sizeof(struct fileline));
                if (line == NULL) {
                        return(NULL);
                }
                line->flags = 0;
        } else {
                line = (LINE) gmem_get(hHeap, sizeof(struct fileline));
                if (line == NULL) {
                        return(NULL);
                }
                line->flags = LF_DISCARD;
        }

        /* alloc space for the text. remember the null character */
        line->text = gmem_get(hHeap, linelength + 1);
        strncpy(line->text, text, linelength);
        line->text[linelength] = '\0';

        line->link = NULL;
        line->linenr = linenr;

        return(line);
}

/***************************************************************************
 * Function: line_delete
 *
 * Purpose:
 *
 * Deletes a line and frees up all associated memory and if the line
 * was not alloc-ed from a list, frees up the line struct itself
 *
 ***************************************************************************/
void
line_delete(LINE line)
{
        if (line == NULL) {
                return;
        }

        /* free up text space */
        gmem_free(hHeap, line->text, lstrlen(line->text)+1);

        /* free up line itself only if not on list */
        if (line->flags & LF_DISCARD) {
                gmem_free(hHeap, (LPSTR) line, sizeof(struct fileline));
        }
}

/***************************************************************************
 * Function: line_reset
 *
 * Purpose:
 *
 * Clears the link and force recalc of the hash code.
 *
 ***************************************************************************/
void
line_reset(LINE line)
{
        if (line == NULL) {
                return;
        }

        line->link = NULL;

        line->flags &= ~LF_HASHVALID;
}

/***************************************************************************
 * Function: line_gettext
 *
 * Purpose:
 *
 * Returns a pointer to the line text
 *
 ***************************************************************************/
LPSTR
line_gettext(LINE line)
{
        if (line == NULL) {
                return(NULL);
        }

        return (line->text);
}

/***************************************************************************
 * Function: line_gettabbedlength
 *
 * Purpose:
 *
 * Returns the length of line in characters, expanding tabs.
 *
 ***************************************************************************/
int
line_gettabbedlength(LINE line, int tabstops)
{
        int length;
        LPSTR chp;

        if (line == NULL) {
                return(0);
        }

        for (length = 0, chp = line->text; *chp != '\0'; chp++) {
                if (*chp == '\t') {
                         length = (length + tabstops) / tabstops * tabstops;
                } else {
                        length++;
                }
        }
        return(length);
}

/***************************************************************************
 * Function: line_gethashcode
 *
 * Purpose:
 *
 * Returns the hashcode for this line
 *
 ***************************************************************************/
DWORD
line_gethashcode(LINE line)
{
        if (line == NULL) {
                return(0);
        }

        if (! (line->flags & LF_HASHVALID)) {

                /* hashcode needs to be recalced */
                line->hash = hash_string(line->text, ignore_blanks);
                line->flags |= LF_HASHVALID;
        }
        return (line->hash);
}

/***************************************************************************
 * Function: line_getlink
 *
 * Purpose:
 *
 * Returns the handle for the line that is linked to this line (the
 * result of a successful line_link() operation). This line is
 * identical in text to the linked line (allowing for ignore_blanks).
 *
 ***************************************************************************/
LINE
line_getlink(LINE line)
{
        if (line == NULL) {
                return(NULL);
        }

        return(line->link);
}

/***************************************************************************
 * Function: line_getlinenr
 *
 * Purpose:
 *
 * Returns the line number associated with this line
 *
 ***************************************************************************/
UINT
line_getlinenr(LINE line)
{
        if (line == NULL) {
                return(0);
        }

        return(line->linenr);
}

/***************************************************************************
 * Function: line_compare
 *
 * Purpose:
 *
 * Compares two lines and returns TRUE if they are the same.
 *
 ***************************************************************************/
BOOL
line_compare(LINE line1, LINE line2)
{
        LPSTR p1, p2;

        /* Assert: At least one of them is not null ??? */

        if ((line1 == NULL) || (line2 == NULL)) {
                /* null line handles do not compare */
                return(FALSE);
        }

        /* check that the hashcodes match */
        if (line_gethashcode(line1) != line_gethashcode(line2)) {
                return(FALSE);
        }

        /* hashcodes match - are the lines really the same ? */
        /* note that this is coupled to gutils\utils.c in definition of blank */
        p1 = line_gettext(line1);
        p2 = line_gettext(line2);

// Japanese friendy
        do {
                if (ignore_blanks) {
                        while ( (*p1 == ' ') || (*p1 == '\t')) {
                                p1 = CharNext(p1);
                        }
                        while ( (*p2 == ' ') || (*p2 == '\t')) {
                                p2 = CharNext(p2);
                        }
                }
                if (IsDBCSLeadByte(*p1) && *(p1+1) != '\0'
                &&  IsDBCSLeadByte(*p2) && *(p2+1) != '\0') {
                        if (*p1 != *p2 || *(p1+1) != *(p2+1)) {
                                return(FALSE);
                        }
                        p1 += 2;
                        p2 += 2;
                } else {
                        if (*p1 != *p2) {
                                return(FALSE);
                        }
                        p1++;
                        p2++;
                }
        } while ( (*p1 != '\0') && (*p2 != '\0'));

        return(TRUE);
}

/***************************************************************************
 * Function: line_link
 *
 * Purpose:
 *
 * Attempts to link two lines and returns TRUE if succesful.
 *
 * This will fail if either line is NULL, or already linked, or if
 * they differ.
 *
 ***************************************************************************/
BOOL
line_link(LINE line1, LINE line2)
{
        if ( (line1 == NULL) || (line2 == NULL)) {
                return(FALSE);
        }

        if ( (line1->link != NULL) || (line2->link != NULL)) {
                return(FALSE);
        }

        if (line_compare(line1, line2)) {
                line1->link = line2;
                line2->link = line1;
                return(TRUE);
        } else {
                return(FALSE);
        }
}

/***************************************************************************
 * Function: line_isblank
 *
 * Purpose:
 *
 * Returns TRUE iff line is blank.  NULL => return FALSE
 *
 ***************************************************************************/
BOOL line_isblank(LINE line)
{
        return line!=NULL && utils_isblank(line->text);
}


LINE.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.
\******************************************************************************/

/*
 * LINE.H
 */

typedef struct fileline FAR * LINE;

LINE line_new(LPSTR text, int linelength, UINT linenr, LIST list);
void line_delete(LINE line);
void line_reset(LINE line);
BOOL line_compare(LINE line1, LINE line2);
BOOL line_link(LINE line1, LINE line2);
LPSTR line_gettext(LINE line);
int line_gettabbedlength(LINE line, int tabstops);
DWORD line_gethashcode(LINE line);
LINE line_getlink(LINE line);
UINT line_getlinenr(LINE line);
BOOL line_isblank(LINE line);


LIST.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 Header *******************************
* Module Name: LIST.C
*
*
* Functions:
*
* Alloc()
* Free()
* List_Init()
* List_Dump()
* List_Show()
* List_Create()
* List_Destroy()
* List_AddFirst()
* List_NewFirst()
* List_DeleteFirst()
* List_AddLast()
* List_NewLast()
* LIst_DeleteLast()
* List_AddAfter()
* List_NewAfter()
* List_AddBefore()
* List_NewBefore()
* List_Delete()
* List_DeleteForwards()
* List_DeleteBackwards()
* List_ItemLength()
* List_First()
* List_Last()
* List_Next()
* List_Prev()
* List_Clear()
* List_IsEmpty()
* SwitchLists()
* List_Join()
* List_InsertListAfter()
* List_InsertListBefore()
* List_SplitAfter()
* List_SplitBefore()
* List_Card()
* List_IsOK()
* LIst_MakeOK()
* List_Check()
* List_Recover()
*
* Comments:
*
*
****************************************************************************/

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

#include "gutils.h"
#include "list.h"
#include "gutilsrc.h"

#define memcpy  memcpy

char msg[80];  /* a temp for building up wsprintf messages in */

#define BLOCKSIZE 25000
 typedef struct
 { HANDLE hMem;     /* memory handle for this block */
   int iInUse;    /* number of allocations taken out of it.  0 => free it */
   int iNext;     /* next byte to use */
   char chData[BLOCKSIZE];
 } BLOCK, FAR *PBLOCK;

static CRITICAL_SECTION CritSec;  /* to protect pCurrent */
#define List_Enter_Crit(x)      EnterCriticalSection(x)
#define List_Leave_Crit(x)      LeaveCriticalSection(x)

static PBLOCK pCurrent = NULL;  /* block currently in use */
                          /* must always be either NULL or valid */

 /* Allocate storage for List elements.  n.b. after a call to this
    you MUST record the value of pCurrent as you need to hand that in
    to Free.  You don't hand in the value of the actual storage.
    See screed above.
    This function Enters the critical section.  The caller must Leave it.
 */
static LPVOID Alloc(int size)
 { HANDLE hMem;
   LPVOID pRet;
   List_Enter_Crit(&CritSec);
   if ((pCurrent==NULL)||(pCurrent->iNext+size>BLOCKSIZE+1))
   { hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE,(DWORD)(sizeof(BLOCK)));
     if (hMem==NULL)
       { pCurrent = NULL;
         OutputDebugString("GlobalAlloc failed!!\n");
         return NULL;
       }
     pCurrent = (PBLOCK)GlobalLock(hMem);
     if (pCurrent==NULL)
       { OutputDebugString("GlobalLock failed!!\n");
         return NULL;
       }
     pCurrent->hMem = hMem;
     pCurrent->iInUse = 0;
     pCurrent->iNext = 0;
   }
   pRet = &(pCurrent->chData[pCurrent->iNext]);
   ++(pCurrent->iInUse);
   pCurrent->iNext += size;

   /* for MIPS we must also ensure that the data is aligned 4 byte*/
   pCurrent->iNext += 3;
   pCurrent->iNext -= pCurrent->iNext % 4;

   return pRet;
 } /* Alloc */

static void Free(PBLOCK pBlock, LPVOID p)
 { HANDLE hMem;
   List_Enter_Crit(&CritSec);
    --pBlock->iInUse;
   if (pBlock->iInUse<=0)
   { if (pBlock->iInUse<0)
     {
        extern HANDLE hLibInst;
        TCHAR szBuf[512];
        LoadString(hLibInst, IDS_LIST_ALLOC_NEGATIVE, szBuf, sizeof(szBuf));

        wsprintf(msg, szBuf, pBlock->iInUse);
        MessageBox(NULL, msg, NULL, MB_OK | MB_ICONSTOP);
      }

     hMem = pBlock->hMem;
     GlobalUnlock(hMem);
     GlobalFree(hMem);
     if (pCurrent==pBlock) pCurrent = NULL; /* defend the invariant */
   }
   List_Leave_Crit(&CritSec);
 } /* Free */

  /* The following definition tells the truth about what an ITEM is.  The
  |  header file says only that there's a structure with the tag item_tag and
  |  that a LIST is a pointer to one.  Here we spell out what that structure
  |  is (and a LIST is still a pointer to one).  A PLIST is defined as a
  |  pointer to one of those, but is only really used because the C
  |  parameter mechanism demands an extra level of indirection for a
  |  parameter that can be updated.  (Modula-2 VAR parameter).
  */
  typedef struct item_tag
  { struct item_tag FAR *pitNext;    /* to next in circular list */
    struct item_tag FAR *pitPrev;    /* to prev in circular list */
    PBLOCK pBlock;               /* to memory block */
    BOOL bAnchor;                /* TRUE iff an anchor block */
    BOOL bOK;                    /* true unless a list op has failed */
    int iLen;                    /* length of data only */
    char Data[1];                /* the caller's data.  The '1' is a lie */
  } ITEM;

  /* For an anchor block, only the fields pitNext thru bAnchor are allocated.
  |  For a normal list element, Data may well be longer than 1 byte.
  |  The bOK flag is to support a style of programming where several
  |  successive operations can be done without having to check the return
  |  code at each stage.  At the end, the list can be examined to see if
  |  the data in it is valid or if it has been made invalid by the failure
  |  of any of the previous operations.  Certain operations may result in
  |  having no list at all if they fail (e.g. create) and for these, you'd
  |  better check the result at once!
  |  ??? Some of this screed belongs in the header!!!
  */

  static int iAnchorSize;      /* Size of anchor block (no data, no dummy) */
  static int iHeaderSize;      /* Size of data block not counting Data
                                  and offset from cursor back to item.
                               */
  static BOOL bInited = FALSE; /* TRUE <=> iAnchorSize and iHeaderSize are OK*/

#define MOVEBACK(Curs)                                               \
   { Curs = ((char FAR *)Curs-iHeaderSize); } /*move from Data to pitNext*/

  /*==================================================================
  || Lists are circular, doubly linked with an anchor block which holds
  || pointers to both ends.  Every block has a flag which shows whether
  || it's an anchor or not.
  ||
  || Empty list:
  ||
  ||      -------------
  ||     |             |
  ||     |   Anchor    |
  ||     v   -------   |
  ||  Ul--->| Next--+--|
  ||        |-------|  |
  ||        | Prev--+--
  ||         -------
  ||
  || One entry list:
  ||
  ||      ------------------------------------
  ||     |                                    |
  ||     |   Anchor                           |
  ||     v   -------                ------    |
  ||  Ul--->| Next--+------------->| Next-+---|
  ||        |-------|    |         |------|   |
  ||        | Prev--+----          | Prev-+---
  ||         -------               |------|
  ||                               | Len  |
  ||                               |------|
  ||                               | Data |
  ||                                ------
  || Two entry list:
  ||
  ||      -------------------------------------------------
  ||     | ---------------    ---------------              |
  ||     ||               |  |               |             |
  ||     ||  Anchor       |  |               |             |
  ||     vv  --------     |  v    ------     |    ------   |
  ||  Ul--->| Next--+-----+----->| Next-+----+-->| Next-+--
  ||        |-------|     |      |------|  | |   |------|
  ||        | Prev--+--    ------+-Prev |  |  ---+-Prev |
  ||         -------   |         |------|  |     |------|
  ||                   |         | Len  |  |     | Len  |
  ||                   |         |------|  |     |------|<----Cursor
  ||                   |         | Data |  |     | Data |
  ||                   |          ------   |      ------
  ||                   |                   |
  ||                    -------------------
  ||
  || etc.
  ||
  || Note that an external cursor (i.e one which is seen by the caller)
  || points to the Data field, not to the start of the structure.
  || This allows easy access to the data by the user at the cost of a
  || slightly slower traverse.
  || Within this module, we may sometimes traverse a list with  a cursor
  || that points to the start of an item.  This is called an item cursor.
   ===================================================================*/

  /*------------------------------------------------------------------
  | Set iAnchorSize and iHeaderSize.  Implementation independent!
   -------------------------------------------------------------------*/
void APIENTRY List_Init(void)
  {  LIST P;
     P = (LIST)&P;                  /* really any old address will do */
     iAnchorSize = (char FAR *)&(P->iLen) - (char FAR *)&(P->pitNext);
     iHeaderSize = (char FAR *)&(P->Data) - (char FAR *)&(P->pitNext);
     InitializeCriticalSection(&CritSec);
     /* assumes layout in storage is linear */
  }

  /* Dump the internals to the debugger. */
void APIENTRY List_Dump(LPSTR Header, LIST lst)
  {  LIST pit;
     char msg[250];

     OutputDebugString(Header);  OutputDebugString("\n");
     pit = lst;
     do
     { wsprintf(msg,"%8x %8x %8x %ld %s "
               , pit, pit->pitNext, pit->pitPrev, pit->iLen
               , (pit->bAnchor ? "Anchor" : "Data")
               );
       OutputDebugString(msg);
       if (pit->pitNext->pitPrev != pit)
         OutputDebugString(" Next Prev error!!");
       if (pit->pitPrev->pitNext != pit)
         OutputDebugString(" Prev Next error!!");
       OutputDebugString("\n");
       pit = pit->pitNext;
     } while (pit!=lst);
     OutputDebugString("End of list dump\n");
  } /* List_Dump */

  /* Dump hex representation of handle to debugger */
void APIENTRY List_Show(LIST lst)
  { char msg[50];
    wsprintf(msg, "%8x", lst);
    OutputDebugString(msg);
  } /* List_Show */

  /*------------------------------------------------------------------
  | Create a list.  It will be initially empty
   -------------------------------------------------------------------*/
LIST APIENTRY List_Create(void)
  {  LIST lst;
     if (!bInited) {List_Init(); }          /* prevent some silly errors */
     lst = Alloc(iAnchorSize);
     if (lst==NULL) { return NULL; }
     lst->pBlock = pCurrent;
     List_Leave_Crit(&CritSec);
     lst->bOK = TRUE;
     lst->pitNext = lst;
     lst->pitPrev = lst;
     lst->bAnchor = TRUE;
     /* no length field set in an anchor block */
     return lst;
  } /* List_Create */

  /*------------------------------------------------------------------
  | Destroy *plst.  It does not need to be empty first
   -------------------------------------------------------------------*/
  void APIENTRY List_Destroy(PLIST plst)
  {  LIST pitP;    /* item cursor on * plst */
     LIST pitQ;    /* item cursor runs one step ahead of pitQ */

     if (plst==NULL)
       return;
     /* There is at least an anchor block to destroy */
     pitP = *plst;
     do
     {  pitQ = pitP->pitNext;
        Free(pitP->pBlock, pitP);
        pitP = pitQ;
     }while(pitP != *plst);
     *plst = NULL;
  } /* List_Destroy */

  /*------------------------------------------------------------------
  | Add an item holding Object to the beginning of * plst
   -------------------------------------------------------------------*/
  void APIENTRY List_AddFirst(LIST lst, LPVOID pObject, UINT uLen)
  {  LIST pit;      /* newly allocated item */

     if (lst==NULL)
       return;
     pit = Alloc(iHeaderSize+uLen);
     if (pit==NULL) { lst->bOK = FALSE; return; }
     pit->pBlock = pCurrent;
     List_Leave_Crit(&CritSec);
     pit->iLen = uLen;
     pit->pitPrev = lst;
     pit->pitNext = lst->pitNext;
     lst->pitNext->pitPrev = pit; /* for empty list that set lst->pitPrev */
     lst->pitNext = pit;
     pit->bAnchor = FALSE;
     memcpy( &(pit->Data), pObject, uLen );
  } /* List_AddFirst */

  /*------------------------------------------------------------------
  | Return the address of the place for Len bytes of data in a new
  | item at the start of lst
   -------------------------------------------------------------------*/
  LPVOID APIENTRY List_NewFirst(LIST lst, UINT uLen)
  {  LIST pit;

     if (lst==NULL)
       return NULL;
     pit = Alloc(iHeaderSize+uLen);
     if (pit==NULL) { lst->bOK = FALSE; return NULL; }
     pit->pBlock = pCurrent;
     List_Leave_Crit(&CritSec);
     pit->iLen = uLen;
     pit->pitPrev = lst;
     pit->pitNext = lst->pitNext;
     lst->pitNext->pitPrev = pit; /* for empty list that set lst->pitPrev */
     lst->pitNext = pit;
     pit->bAnchor = FALSE;
     return (char FAR *)&(pit->Data);
  } /* List_NewFirst */

  /*------------------------------------------------------------------
  | Delete the first item in lst.  Error if lst is empty
   -------------------------------------------------------------------*/
  void APIENTRY List_DeleteFirst(LIST lst)
  {  LIST pit;

     if (lst==NULL)
       return;
                               /* attempting to delete the anchor block! */
     if (lst->pitNext==lst) {lst->bOK = FALSE; }
     else
        {  pit = lst->pitNext;
           pit->pitNext->pitPrev = pit->pitPrev;
           pit->pitPrev->pitNext = pit->pitNext;
           Free(pit->pBlock, pit);
        }
  } /* List_DeleteFirst */

  /*------------------------------------------------------------------
  | Add an item holding Object to the end of lst
   -------------------------------------------------------------------*/
  void APIENTRY List_AddLast(LIST lst, LPVOID pObject, UINT uLen)
  {  LIST pit;

     if (lst==NULL)
       return;
     pit = Alloc(iHeaderSize+uLen);
     if (pit==NULL) { lst->bOK = FALSE; return; }
     pit->pBlock = pCurrent;
     List_Leave_Crit(&CritSec);
     pit->iLen = uLen;
     pit->pitNext = lst;
     pit->pitPrev = lst->pitPrev;
     lst->pitPrev->pitNext = pit; /* for empty list that set lst->pitNext */
     lst->pitPrev = pit;
     pit->bAnchor = FALSE;
     memcpy( &(pit->Data), pObject, uLen );
  } /* ListAddLast */

  /*------------------------------------------------------------------
  | Return the address of the place for uLen bytes of data in a new
  |  item at the end of lst
   -------------------------------------------------------------------*/
  LPVOID APIENTRY List_NewLast(LIST lst, UINT uLen)
  {  LIST pit;

     if (lst==NULL)
       return NULL;
     pit = Alloc(iHeaderSize+uLen);
     if (pit==NULL) { lst->bOK = FALSE; return NULL; }
     pit->pBlock = pCurrent;
     List_Leave_Crit(&CritSec);
     pit->iLen = uLen;
     pit->pitNext = lst;
     pit->pitPrev = lst->pitPrev;
     lst->pitPrev->pitNext = pit; /* for empty list that set lst->pitNext */
     lst->pitPrev = pit;
     pit->bAnchor = FALSE;
     return (char FAR *)&(pit->Data);
  } /* ListNewLast */

  /*------------------------------------------------------------------
  | Delete the last item in lst.  Error if lst is empty
   -------------------------------------------------------------------*/
  void APIENTRY List_DeleteLast(LIST lst)
  {  LIST pit;

     if (lst==NULL)
       return;
                               /* attempting to delete the anchor block! */
     if (lst->pitNext==lst) {lst->bOK = FALSE; }
     else
        {  pit = lst->pitPrev;
           pit->pitNext->pitPrev = pit->pitPrev;
           pit->pitPrev->pitNext = pit->pitNext;
           Free(pit->pBlock, pit);
        }
  } /* List_DeleteLast */

  /*--------------------------------------------------------------------
  | Add an item holding * pObject to lst immediately after Curs.
  | List_AddAfter(lst,NULL,pObject,Len) adds it to the start of the lst
   ---------------------------------------------------------------------*/
  void APIENTRY List_AddAfter( LIST lst
                    , LPVOID Curs
                    , LPVOID pObject
                    , UINT uLen
                    )
  {  LIST pitNew;
     LIST pitAfter;

     if (lst==NULL)
       return;
     if (Curs==NULL){ List_AddFirst(lst, pObject, uLen);}
     else
        {  MOVEBACK(Curs);
           pitAfter = (LIST)Curs;
           pitNew = Alloc(iHeaderSize+uLen);
           if (pitNew==NULL) { lst->bOK = FALSE; return; }
           pitNew->pBlock = pCurrent;
           List_Leave_Crit(&CritSec);
           pitNew->iLen = uLen;
           pitNew->pitPrev = pitAfter;
           pitNew->pitNext = pitAfter->pitNext;
           pitAfter->pitNext->pitPrev = pitNew;
           pitAfter->pitNext = pitNew;
           pitNew->bAnchor = FALSE;
           memcpy( &(pitNew->Data), pObject, uLen );
        }
  } /* List_AddAfter */

  /*--------------------------------------------------------------------
  | Return the address of the place for uLen bytes of data in a new
  | item immediately after Curs.
  | List_NewAfter(Lst,NULL,uLen) returns a pointer
  | to space for uLen bytes in a new first element.
   ---------------------------------------------------------------------*/
  LPVOID APIENTRY List_NewAfter(LIST lst, LPVOID Curs, UINT uLen)
  {  LIST pitNew;
     LIST pitAfter;

     if (lst==NULL)
       return NULL;
     if (Curs==NULL){ return List_NewFirst(lst, uLen);}
     else
        {  MOVEBACK(Curs);
           pitAfter = (LIST)Curs;
           pitNew = Alloc(iHeaderSize+uLen);
           if (pitNew==NULL) { lst->bOK = FALSE; return NULL; }
           pitNew->pBlock = pCurrent;
           List_Leave_Crit(&CritSec);
           pitNew->iLen = uLen;
           pitNew->pitPrev = pitAfter;
           pitNew->pitNext = pitAfter->pitNext;
           pitAfter->pitNext->pitPrev = pitNew;
           pitAfter->pitNext = pitNew;
           pitNew->bAnchor = FALSE;
           return (char FAR *)&(pitNew->Data);
        }
  } /* List_NewAfter */

  /*--------------------------------------------------------------------
  | Add an item holding Object to lst immediately before Curs.
  | List_AddBefore(Lst,NULL,Object,uLen) adds it to the end of the list
   ---------------------------------------------------------------------*/
  void APIENTRY List_AddBefore( LIST lst
                     , LPVOID Curs
                     , LPVOID pObject
                     , UINT uLen
                     )
  {  LIST pitNew;
     LIST pitBefore;

     if (lst==NULL)
       return;
     if (Curs==NULL){ List_AddLast(lst, pObject, uLen);}
     else
        {  MOVEBACK(Curs);
           pitBefore = (LIST)Curs;
           pitNew = Alloc(iHeaderSize+uLen);
           if (pitNew==NULL) { lst->bOK = FALSE; return; }
           pitNew->pBlock = pCurrent;
           List_Leave_Crit(&CritSec);
           pitNew->iLen = uLen;
           pitNew->pitNext = pitBefore;
           pitNew->pitPrev = pitBefore->pitPrev;
           pitBefore->pitPrev->pitNext = pitNew;
           pitBefore->pitPrev = pitNew;
           pitNew->bAnchor = FALSE;
           memcpy( &(pitNew->Data), pObject, uLen );
        }
  } /* List_AddBefore */

  /*--------------------------------------------------------------------
  | Return the address of the place for uLen bytes of data in a new
  | item immediately before Curs.
  | List_NewBefore(Lst,NULL,uLen) returns a pointer
  | to space for uLen bytes in a new last element.
   ---------------------------------------------------------------------*/
  LPVOID APIENTRY List_NewBefore(LIST lst, LPVOID Curs, UINT uLen )
  {  LIST pitNew;
     LIST pitBefore;

     if (lst==NULL)
       return NULL;
     if (Curs==NULL){ return List_NewLast(lst, uLen);}
     else
        {  MOVEBACK(Curs);
           pitBefore = (LIST)Curs;
           pitNew = Alloc(iHeaderSize+uLen);
           if (pitNew==NULL) { lst->bOK = FALSE; return NULL; }
           pitNew->pBlock = pCurrent;
           List_Leave_Crit(&CritSec);
           pitNew->iLen = uLen;
           pitNew->pitNext = pitBefore;
           pitNew->pitPrev = pitBefore->pitPrev;
           pitBefore->pitPrev->pitNext = pitNew;
           pitBefore->pitPrev = pitNew;
           pitNew->bAnchor = FALSE;
           return (char FAR *) &(pitNew->Data);
        }
  } /* List_NewBefore */

  /*------------------------------------------------------------------
  | Delete the item that Curs identifies.
  | This will be only a few (maybe as little as 3) machine instructions
  | quicker than DeleteForwards or DeleteBackwards but leaves Curs dangling.
  | It is therefore NOT usually to be preferred.
  | It may be useful when you have a function which returns an LPVOID
  | since the argument does not need to be a variable.
  |     Trivial example: List_Delete(List_First(L));
   -------------------------------------------------------------------*/
  void APIENTRY List_Delete(LPVOID Curs)
  {  LIST pit;

     if(Curs==NULL)
       return;
     MOVEBACK(Curs)
     pit = (LIST)Curs;
     pit->pitNext->pitPrev = pit->pitPrev;
     pit->pitPrev->pitNext = pit->pitNext;
     Free(pit->pBlock, pit);
  } /* List_Delete */

  /*-----------------------------------------------------------------------
  | Delete the item that Curs identifies and return a cursor that
  | identifies the next item (NULL if already on last)
   ------------------------------------------------------------------------*/
  LPVOID APIENTRY List_DeleteForwards(LPVOID Curs)
  {  LIST pitDel;  /* the item to delete */
     LIST pitN;    /* the item after (could be anchor) */

     if(Curs==NULL)
       return NULL;
     MOVEBACK(Curs)
     pitDel = (LIST)Curs;
     pitN = pitDel->pitNext;

     pitN->pitPrev = pitDel->pitPrev;
     pitDel->pitPrev->pitNext = pitN;
     Free(pitDel->pBlock, pitDel);
     if (pitN->bAnchor) return NULL;
     else return (char FAR *)&(pitN->Data);
  } /* List_DeleteForwards */

  /*-----------------------------------------------------------------------
  | Delete the item that Curs identifies and return a cursor that
  | identifies the previous item (NULL if already on first)
   ------------------------------------------------------------------------*/
  LPVOID APIENTRY List_DeleteBackwards(LPVOID Curs)
  {  LIST pitDel;  /* the one to delete */
     LIST pitB;    /* the one before */

     if(Curs==NULL)
       return NULL;
     MOVEBACK(Curs)
     pitDel = (LIST)Curs;
     pitB = pitDel->pitPrev;
     pitDel->pitNext->pitPrev = pitB;
     pitB->pitNext = pitDel->pitNext;
     Free(pitDel->pBlock, pitDel);
     if (pitB->bAnchor) return NULL;
     else return (char FAR *)&(pitB->Data);
  } /* List_DeleteBackwards */

  /*-------------------------------------------------------------------
  | Return the length of the object identified by the cursor Curs
   -------------------------------------------------------------------*/
  int APIENTRY List_ItemLength(LPVOID Curs)
  {  LIST pit;

     if(Curs==NULL)
       return 0;
     MOVEBACK(Curs)
     pit = (LIST)Curs;
     return pit->iLen;
  } /* List_ItemLength */

  /*------------------------------------------------------------------
  | Return the address of the first object in lst
  |  If lst is empty then Return NULL.
   -------------------------------------------------------------------*/
  LPVOID APIENTRY List_First(LIST lst)
  {
     if (lst==NULL)
       return NULL;
     if (lst->pitNext==lst) { return NULL; }
     return &(lst->pitNext->Data);
  } /* List_First */

  /*------------------------------------------------------------------
  | Return the address of the last object in lst
  | If lst is empty then return NULL.
   -------------------------------------------------------------------*/
  LPVOID APIENTRY List_Last(LIST lst)
  {
     if (lst==NULL)
       return NULL;
     if (lst->pitNext==lst) { return NULL; }
     return &(lst->pitPrev->Data);
  } /* List_Last */

  /*------------------------------------------------------------------
  | Return the address of the object after Curs^.
  | List_Next(List_Last(lst)) == NULL;  List_Next(NULL) is an error.
   -------------------------------------------------------------------*/
  LPVOID APIENTRY List_Next(LPVOID Curs)
  {  LIST pit;

     if(Curs==NULL)
       return NULL;
     MOVEBACK(Curs)
     pit = (LIST)Curs;
     pit = pit->pitNext;
     if (pit->bAnchor) {return NULL;} else {return &(pit->Data);}
  } /* List_Next */

  /*------------------------------------------------------------------
  | Return the address of the object after Curs^.
  | List_Prev(List_First(L)) == NULL;  List_Prev(NULL) is an error.
   -------------------------------------------------------------------*/
  LPVOID APIENTRY List_Prev(LPVOID Curs)
  {  LIST pit;

     if(Curs==NULL)
       return NULL;
     MOVEBACK(Curs)
     pit = (LIST)Curs;
     pit = pit->pitPrev;
     if (pit->bAnchor) {return NULL;} else {return &(pit->Data);}
  } /* List_Prev */

  /*-------------------------------------------------------------------
  | Arrange that lst is empty after this call
   --------------------------------------------------------------------*/
  void APIENTRY List_Clear(LIST lst)
  {  LIST pitP;   /* item cursor on List, points to element starts */
     LIST pitQ;   /* runs one step ahead of pitP                   */

     if (lst==NULL)
       return;
     pitP = lst->pitNext;   /* first element of list proper */
     while (pitP!=lst)      /* while not wrapped onto anchor */
        {  pitQ = pitP->pitNext;
           Free(pitP->pBlock, pitP);
           pitP = pitQ;
        }
     lst->bOK = TRUE;
     lst->pitNext = lst;
     lst->pitPrev = lst;
  } /* List Clear */

  /*---------------------------------------------------------------------
  | Return TRUE if and only if lst is empty
   ----------------------------------------------------------------------*/
  BOOL APIENTRY List_IsEmpty(LIST lst)
  {  if (lst==NULL)
       return TRUE;   /* well it's sort of true isn't it? */
     return lst->pitNext ==lst;
  } /* List_IsEmpty */

  /*------------------------------------------------------------------
  | l1 had better be empty.  l1 then acquires all the elements from l2
   -------------------------------------------------------------------*/
  void APIENTRY SwitchLists(LIST l1, LIST l2)
  {  /* connect l1 to l2's elements, l1 had better be initially empty */
     l1->pitPrev = l2->pitPrev;
     l1->pitNext = l2->pitNext;
     /* connect the elements to l1 anchor block. */
     l1->pitPrev->pitNext = l1;
     l1->pitNext->pitPrev = l1;
     /* make l2 empty */
     l2->pitPrev = l2;
     l2->pitNext = l2;
  } /* SwitchLists */

  /*-----------------------------------------------------------------------
  | l1 := l1||l2; l2 := empty
  | The elements themselves are not moved, so pointers to them remain valid.
  |
  | l1 gets all the elements of l1 in their original order followed by
  | all the elements of l2 in the order they were in in l2.
  | l2 becomes empty.
   ------------------------------------------------------------------------*/
  void APIENTRY List_Join(LIST l1, LIST l2)
  {  if((l1==NULL)||(l2==NULL))
       return;
     l1->bOK = l1->bOK &&l2->bOK;  /* result OK if both inputs OK */
     l2->bOK = TRUE;               /* as l2 always becomes empty */
     if (l2->pitNext==l2) { /* no elements need moving */ }
     else if (l2->pitNext==l2) { SwitchLists(l1,l2); return; }
     else
        {  l2->pitNext->pitPrev = l1->pitPrev;
           l1->pitPrev->pitNext = l2->pitNext;
           l1->pitPrev = l2->pitPrev;
           l1->pitPrev->pitNext = l1;
           l2->pitNext = l2;
           l2->pitPrev = l2;
        }
  } /* List_Join */

  /*-----------------------------------------------------------------------
  | Let L1 be *pl1 and L2 be *pl2
  | L1 := L1[...Curs] || L2 || L1[Curs+1...]; L2 := empty
  | Curs=NULL means insert L2 at the start of L1
  | The elements themselves are not moved, so pointers to them remain valid.
  |
  | L1 gets the elements of L1 from the start up to and including the element
  | that Curs points at, in their original order,
  | followed by all the elements that were in L2, in their original order,
  | followed by the rest of L1
   ------------------------------------------------------------------------*/
  void APIENTRY List_InsertListAfter(LIST l1, LIST l2, LPVOID Curs)
  {  LIST pitA;     /* The element after Curs, could be anchor */
     LIST pit;      /* The start of the element that Curs points at
                    |  or the anchor block if Curs==NULL
                    */

     if ( (l1==NULL) || (l2==NULL))
       return;
     l1->bOK = l1->bOK && l2->bOK;
     l2->bOK = TRUE;
     if (l2->pitNext==l2) { /* no elements need moving */ }
     else if ( l1->pitNext==l1)

{  /* the easy way to code this would be simply to switch the two
        |  pointers l1 and l2, but they are value parameters and we don't
        |  want to change that.
        */
        SwitchLists(l1,l2);
        return;
     }
     else
     {  if(Curs==NULL){ pit = l1;}
        else
        {  MOVEBACK(Curs)
           pit = (LIST)Curs;
        }
        /* pit points to a block to insert after, could be anchor */
        pitA = pit->pitNext;      /* Cannot be same as P, already checked */
        l2->pitNext->pitPrev = pit;    /*  P<-- elems-of-l2    A */
        l2->pitPrev->pitNext = pitA;   /*  P<-- elems-of-l2 -->A */
        pit->pitNext = l2->pitNext;    /*  P<-->elems-of-l2 -->A */
        pitA->pitPrev = l2->pitPrev;   /*  P<-->elems-of-l2<-->A */

        l2->pitNext = l2;
        l2->pitPrev = l2;
     }
  }  /* List_InsertListAfter */

  /*-----------------------------------------------------------------------
  | l1 := l1[...Curs-1] || l2 || l1[Curs...]; l2 := empty
  | Curs=NULL means insert l2 at the end of l1
  | The elements themselves are not moved, so pointers to them remain valid.
  |
  | l1 gets the elements of l1 from the start up to but not including the
  | element that Curs points at, in their original order,
  | followed by all the elements that were in l2, in their original order,
  | followed by the rest of l1.
   ------------------------------------------------------------------------*/
  void APIENTRY List_InsertListBefore(LIST l1, LIST l2, LPVOID Curs)
  {  LIST pitB;     /* The element before Curs, could be anchor */
     LIST pit;      /* The start of the element that Curs points at
                    |  or the anchor block if Curs==NULL
                    */

     if ((l1==NULL) || (l2==NULL))
       return;
     l1->bOK = l1->bOK && l2->bOK;
     l2 ->bOK = TRUE;
     if (l2->pitNext==l2) { /* no action needed */ }
     else if (l1->pitNext==l1)
     {  /* the easy way to code this would be simply to switch the two
        |  pointers l1 and l2, but they are value parameters and we don't
        |  want to change that.
        */
        SwitchLists(l1,l2);
        return;
     }
     else
     {  if(Curs==NULL) { pit = l1; }
        else
        {  MOVEBACK(Curs)
           pit = (LIST)Curs;
        }

        /* P points to a block to insert before, could be anchor */
        pitB = pit->pitPrev;       /* Cannot be same as P, already checked */
        l2->pitNext->pitPrev = pitB; /*  B<-- elems-of-L2    P */
        l2->pitPrev->pitNext = pit;  /*  B<-- elems-of-L2 -->P */
        pitB->pitNext = l2->pitNext; /*  B<-->elems-of-L2 -->P */
        pit->pitPrev = l2->pitPrev;  /*  B<-->elems-of-L2<-->P */
        l2->pitNext = l2;
        l2->pitPrev = l2;
     }
  } /* List_InsertListBefore */

  /*-----------------------------------------------------------------------
  | Let l1 be l1 and l2 be l2
  | Split l2 off from the front of l1:    final l2,l1 = original l1
  |
  | Split l1 into l2: objects of l1 up to and including Curs object
  |               l1: objects of l1 after Curs
  | Any original contents of l2 are freed.
  | List_Spilt(l1, l2, NULL) splits l1 before the first object so l1 gets all.
  | The elements themselves are not moved.
   ------------------------------------------------------------------------*/
  void APIENTRY List_SplitAfter(LIST l1, LIST l2, LPVOID Curs)
  {  LIST pit;

     if ((l1==NULL) || (l2==NULL))
       return;
     if (l2->pitNext!=l2){ List_Clear(l2); };
     if (Curs!=NULL)
     {  MOVEBACK(Curs)
        pit = (LIST)Curs;
        /* Curs had better be an item in l1! l2 had better be created! */
        if (pit==l1) { l1->bOK = FALSE; l2->bOK = FALSE; return; }
        if (pit->pitNext==l1)
        {  /* transfer whole of l2 to l1 */
           SwitchLists(l2,l1);
           return;
        }
        l2->pitPrev = pit;
        l2->pitNext = l1->pitNext;
        l1->pitNext = pit->pitNext;
        pit->pitNext = l2;
        l2->pitNext->pitPrev = l2;
        l1->pitNext->pitPrev = l1;
     }
  } /* List_SplitAfter */

  /*----------------------------------------------------------------------
  | Split l2 off from the back of l1:  final l1,l2 = original l1
  |
  | Split l1 into l1: objects of l1 up to but not including Curs object
  |               l2: objects of l1 from Curs onwards
  | Any original contants of l2 are freed.
  | List_Spilt(l1, l2, NULL) splits l1 after the last object so l1 gets all.
  | The elements themselves are not moved.
   -----------------------------------------------------------------------*/
  void APIENTRY List_SplitBefore(LIST l1, LIST l2, LPVOID Curs)
  {  LIST pit;

     if ((l1==NULL) || (l2==NULL))
       return;
     if (l2->pitNext!=l2){ List_Clear(l2); }
     if (Curs!=NULL)
     {  MOVEBACK(Curs)
        pit = (LIST)Curs;
        /* Curs had better be an item in L1! L2 had better be created! */
        if (pit==l1){ l1->bOK = FALSE; l2->bOK = FALSE; return; }
        if (pit->pitPrev==l1) { SwitchLists(l2,l1); return; }
        l2->pitNext = pit;
        l2->pitPrev = l1->pitPrev;
        l1->pitPrev = pit->pitPrev;
        pit->pitPrev = l2;
        l2->pitPrev->pitNext = l2;
        l1->pitPrev->pitNext = l1;
     }
  } /* List_SplitBefore */

  /*------------------------------------------------------------------
  | Return the number of items in L
   -------------------------------------------------------------------*/
  int APIENTRY List_Card(LIST lst)
  {  LIST pit;     /* item cursor on lst */
     int cit;

     if (lst==NULL)
       return 0;    /* well it is sort of 0 */
     pit = lst->pitNext;
     cit = 0;
     while(pit!=lst)
        {  cit++;
           pit = pit->pitNext;
        }
     return cit;
  } /* List_Card */

  /*------------------------------------------------------------------
  | Check return code
   -------------------------------------------------------------------*/
  BOOL APIENTRY List_IsOK(LIST lst)
  {  if(lst==NULL)
       return FALSE;       /* well it is sick ain't it! */
     return lst->bOK;
  } /* List_IsOK */

  /*------------------------------------------------------------------
  | Set return code to good
   -------------------------------------------------------------------*/
  void APIENTRY List_MakeOK(LIST lst)
  {  if(lst==NULL)
       return;
     lst->bOK = TRUE;
  } /* List_MakeOK */

  BOOL APIENTRY List_Check(LIST lst)
  { LIST pel;
    BOOL bOK;
    /*-----------------------------------------------------------------
    | Check the anchor block has the Anchor flag set.
    | Run through the LIST using the Anchor flag (which should be FALSE)
    | to mark where we have been (to test for loops in the chain)
    | and carry on until we see the Anchor flag again.  Check that this
    | is the anchor block that we started from.  Now do another pass
    | turning the Anchor flags off again and checking the Prev pointers.
     -------------------------------------------------------------------*/
    if(lst==NULL) return FALSE;  /* Should we trap?  Arguable */
    bOK = lst->bAnchor;
    pel = lst->pitNext;
    while(! pel->bAnchor)
    { pel->bAnchor = TRUE;
      pel = pel->pitNext;
    }
    bOK = bOK && (pel==lst);
    if(bOK)
    { /* Turn all the bAnchor flags off */
      pel = lst;
      do
      { pel->bAnchor = FALSE;
        bOK = bOK & (pel->pitNext->pitPrev==pel);
        pel = pel->pitNext;
      } while (pel!=lst);
      lst->bAnchor = TRUE;  /* except the real one */
    }
    else
    { /* just turn off those that we set on */
      pel = lst->pitNext;
      while (pel->bAnchor)
      { pel->bAnchor = FALSE;
        pel = pel->pitNext;
      }
      lst->bAnchor = TRUE;
    }
    return bOK;
  } /* List_Check */

  void APIENTRY List_Recover(PLIST plst)
  {  LIST Last, P,Q;
     BOOL OK;
    /* For no particular reason we presume that the forward chain
       is good and reconstruct the back chain from it.  A better
       algorithm would do the kind of things that List_Check does
       to figure out where the problems lie.  This just steps along
       until it sees either an address that it has already seen or
       else the anchor block.  (It's an n-squared algorithm).
       It links the last good block found back to the anchor and
       fixes all the Anchor flags.
    */
    if (plst==NULL) return;
    if (*plst==NULL)
    {  *plst = List_Create();
       return;
    }
    (*plst)->bAnchor = TRUE;
    P = (*plst)->pitNext;
    Last = *plst;
    for (; ; )
    {  if (P==*plst) break;
       Last = P;
       if (P->pitNext!=*plst)
       {   OK = TRUE;
           Q = *plst;
           for (; ; )
           {   OK &= (P->pitNext!=Q);
               if (Q==P) break;
               Q = Q->pitNext;
           }
           if (!OK) break;
       }
       P = P->pitNext;
    }
    P = *plst;
    while (P!=Last)
    {  P->pitNext->pitPrev = P;
       P->bAnchor = FALSE;
       P = P->pitNext;
    }
    Last->pitNext = *plst;
    (*plst)->pitPrev = Last;
    (*plst)->bAnchor = TRUE;
    (*plst)->bOK = TRUE;   /* Here's hoping! */
  } /* List_Recover */


LIST.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.
\******************************************************************************/

/*
 * LIST.H
 */
/*------------------------------------------------------------------------
| Abstract data type LIST OF (*untyped*) object.
| Different lists can have different types of object in them
| Different items in a list can have different types of object in them.
| The price of this lack of typing is that you have a slightly more
| awkward syntax and you get no help from the compiler if you try to
| put the wrong type of data into the list.
|
| The list is implemented as a collection of items.  Within the item
| somewhere is the object.
|
| Objects are stored UNALIGNED within items.
|
| Use:
|
|   #include <list.h>
|   . . .
|   LIST MyList; (* or LIST liMyList for Hungarians *)
|   . . .
|   MyList = List_Create();
|   List_AddLast(MyList,&MyObject,sizeof(OBJECT));
|
| In the abstract a LIST is a list of objects.  The representation
| is a linked collection of items.  The manner of the linking is
| implementation dependent (as I write this it's linear but when you
| read it it might be a tree (See Knuth for why a tree)).
|
| A LIST is a "handle" for a list which may be thought of as a POINTER
| (whether it is really a pointer or not is implementation dependent)
| so that it can be copied at the risk of creating an alias. e.g.
|
|   L = List_Create();
|   L1 = L;             (* L and L1 are both healthy and empty *)
|   List_AddFirst(L, &elem, sizeof(elem));
|   (* L1 may also appear to have one object, there again it may be sick *)
|   L1 = L;               (* Now they both surely see the one element *)
|   List_Destroy(&L1);    (* L is almost certainly sick now too *)
|   L1 = List_Create();   (* All bets off as to what L is like now
|                            but L1 is empty and healthy
|                         *)
|
| If two handles compare equal then the lists must be equal, but
| unequal handles could address two similar lists i.e. the same list
| of objects held in two different LISTs of items (like pointers).
|
| A LIST can be transferred from one variable to another like this:
|
|   NewList = OldList;           (* copy the handle *)
|   OldList = List_Create();     (* kill the old alias *)
|
| and the Create statement can be omitted if OldList is never touched again.
|
| Items are identified by Cursors.  A cursor is the address of an object
| within an item in the list. i.e. it is the address of the piece of your
| data that you had inserted.  (It is probably NOT the address of the item).
| It is typed as pointer to void here, but you should declare it as a pointer
| to whatever sort of object you are putting in the LIST.
|
| The operations AddFirst, AddLast, AddAfter and AddBefore
| all copy elements by direct assignment.  If an element is itself
| a complex structure (say a tree) then this will only copy a pointer
| or an anchor block or whatever and give all the usual problems of
| aliases.  Clear will make the list empty, but will only free the
| storage that it can "see" directly.  SplitBefore or Split After may
| also perform a Clear operation.  To deal with fancy data structures
| use New rather than Add calls and copy the data yourself
|   e.g.  P = List_NewLast(MyList, sizeof(MyArray[14])*(23-14+1));
|         CopyArraySlice(P, MyArray, 14, 23);
|
| The operations NewFirst, NewLast, NewAfter, NewBefore, First and Last
| all return pointers to elements and thus allow you to do any copying.
| This is how you might copy a whole list of fancy structures:
|
|    void CopyFancyList(LIST * To, LIST From)
|             (* Assumes that To has been Created and is empty *)
|    { PELEMENT Cursor;
|      PELEMENT P;
|
|      List_TRAVERSE(From, Cursor);
|      { P = List_NewLast(To, sizeof(element) );
|        FancyCopy(P, Cursor);    (* Copy so that *Cursor==*P afterwords *)
|      }
|    }
 --------------------------------------------------------------------*/

  typedef struct item_tag FAR * LIST;
  typedef LIST FAR * PLIST;

  void APIENTRY List_Init(void);
  /* MUST BE CALLED BEFORE ANY OF THE OTHER FUNCTIONS. */

  void APIENTRY List_Dump(LPSTR Header, LIST lst);
  /* Dump the internals to current output stream -- debug only */

  void APIENTRY List_Show(LIST lst);
  /* Dump hex representation of handle to current out stream -- debug only */

  LIST APIENTRY List_Create(void);
  /* Create a list.  It will be initially empty */

  void APIENTRY List_Destroy(PLIST plst);
  /* Destroy *plst.  It does not need to be empty first.
  |  All storage directly in the list wil be freed.
  */

  void APIENTRY List_AddFirst(LIST lst, LPVOID pObject, UINT uLen);
  /* Add an item holding Object to the beginning of * plst */

  LPVOID APIENTRY List_NewFirst(LIST lst, UINT uLen);
  /* Return the address of the place for Len bytes of data in a new
  |  item at the start of *plst
  */

  void APIENTRY List_DeleteFirst(LIST lst);
  /* Delete the first item in lst.  Error if lst is empty */

  void APIENTRY List_AddLast(LIST lst, LPVOID pObject, UINT uLen);
  /* Add an item holding Object to the end of lst */

  LPVOID APIENTRY List_NewLast(LIST lst, UINT uLen);
  /* Return the address of the place for uLen bytes of data in a new
  |  item at the end of lst
  */

  void APIENTRY List_DeleteLast(LIST lst);
  /* Delete the last item in lst.  Error if lst is empty */

  void APIENTRY List_AddAfter( LIST lst
                    , LPVOID Curs
                    , LPVOID pObject
                    , UINT uLen
                    );
  /*--------------------------------------------------------------------
  | Add an item holding *pObject to lst immediately after Curs.
  | List_AddAfter(lst, NULL, pObject, Len) adds it to the start of the lst
   ---------------------------------------------------------------------*/

  LPVOID APIENTRY List_NewAfter(LIST lst, LPVOID Curs, UINT uLen);
  /*--------------------------------------------------------------------
  | Return the address of the place for uLen bytes of data in a new
  | item immediately after Curs.
  | List_NewAfter(Lst, NULL, uLen) returns a pointer
  | to space for uLen bytes in a new first element.
   ---------------------------------------------------------------------*/

  void APIENTRY List_AddBefore( LIST lst
                     , LPVOID Curs
                     , LPVOID pObject
                     , UINT uLen
                     );
  /*--------------------------------------------------------------------
  | Add an item holding Object to lst immediately before Curs.
  | List_AddBefore(Lst, NULL, Object, uLen) adds it to the end of the list
   ---------------------------------------------------------------------*/

  LPVOID APIENTRY List_NewBefore(LIST lst, LPVOID Curs, UINT uLen );
  /*--------------------------------------------------------------------
  | Return the address of the place for uLen bytes of data in a new
  | item immediately before Curs.
  | List_NewBefore(Lst, NULL, uLen) returns a pointer
  | to space for uLen bytes in a new last element.
   ---------------------------------------------------------------------*/

  void APIENTRY List_Delete(LPVOID Curs);
  /*------------------------------------------------------------------
  | Delete the item that Curs identifies.
  | This will be only a few (maybe as little as 3) machine instructions
  | quicker than DeleteAndNext or DeleteAndPrev but leaves Curs dangling.
  | It is therefore NOT usually to be preferred.
  | It may be useful when you have a function which returns an LPVOID
  | since the argument does not need to be a variable.
  |     Trivial example: List_Delete(List_First(L));
   -------------------------------------------------------------------*/

  int APIENTRY List_ItemLength(LPVOID Curs);
  /* Return the length of the object identified by the cursor Curs */

  /*------------------------------------------------------------------
  | TRAVERSING THE ULIST
  |
  | LIST lst;
  | object * Curs;
  | . . .
  | Curs = List_First(lst);
  | while (Curs!=NULL)
  | {  DoSomething(*Curs);   (* Curs points to YOUR data not to chain ptrs *)
  |    Curs = List_Next(Curs);
  | }
  |
  | This is identically equal to
  | List_TRAVERSE(lst, Curs)  // note NO SEMI COLON!
  | {  DoSomething(*Curs); }
   -------------------------------------------------------------------*/

  #define List_TRAVERSE(lst, curs)  for(  curs=List_First(lst)            \
                                       ;  curs!=NULL                      \
                                       ;  curs = List_Next((LPVOID)curs)  \
                                       )

  LPVOID APIENTRY List_First(LIST lst);
  /*------------------------------------------------------------------
  | Return the address of the first object in lst
  |  If lst is empty then Return NULL.
  --------------------------------------------------------------------*/

  LPVOID APIENTRY List_Last(LIST lst);
  /*------------------------------------------------------------------
  | Return the address of the last object in lst
  | If lst is empty then return NULL.
  --------------------------------------------------------------------*/

  LPVOID APIENTRY List_Next(LPVOID Curs);
  /*------------------------------------------------------------------
  | Return the address of the object after Curs^.
  | List_Next(List_Last(lst)) == NULL;  List_Next(NULL) is an error.
  | List_Next(List_Prev(curs)) is illegal if curs identifies first el
  --------------------------------------------------------------------*/

  LPVOID APIENTRY List_Prev(LPVOID Curs);
  /*------------------------------------------------------------------
  | Return the address of the object after Curs^.
  | List_Prev(List_First(L)) == NULL;  List_Prev(NULL) is an error.
  | List_Prev(List_Next(curs)) is illegal if curs identifies last el
  --------------------------------------------------------------------*/

  /*------------------------------------------------------------------
  |  Whole list operations
   -----------------------------------------------------------------*/
  void APIENTRY List_Clear(LIST lst);
  /* arrange that lst is empty after this */

  BOOL APIENTRY List_IsEmpty(LIST lst);
  /* Return TRUE if and only if lst is empty */

  void APIENTRY List_Join(LIST l1, LIST l2);
  /*-----------------------------------------------------------------------
  | l1 := l1||l2; l2 := empty
  | The elements themselves are not moved, so pointers to them remain valid.
  |
  | l1 gets all the elements of l1 in their original order followed by
  | all the elements of l2 in the order they were in in l2.
  | l2 becomes empty.
   ------------------------------------------------------------------------*/

  void APIENTRY List_InsertListAfter(LIST l1, LIST l2, LPVOID Curs);
  /*-----------------------------------------------------------------------
  | l1 := l1[...Curs] || l2 || l1[Curs+1...]; l2 := empty
  | Curs=NULL means insert l2 at the start of l1
  | The elements themselves are not moved, so pointers to them remain valid.
  |
  | l1 gets the elements of l1 from the start up to and including the element
  | that Curs points at, in their original order,
  | followed by all the elements that were in l2, in their original order,
  | followed by the rest of l1
   ------------------------------------------------------------------------*/

  void APIENTRY List_InsertListBefore(LIST l1, LIST l2, LPVOID Curs);
  /*-----------------------------------------------------------------------
  | l1 := l1[...Curs-1] || l2 || l1[Curs...]; l2 := empty
  | Curs=NULL means insert l2 at the end of l1
  | The elements themselves are not moved, so pointers to them remain valid.
  |
  | l1 gets the elements of l1 from the start up to but not including the
  | element that Curs points at, in their original order,
  | followed by all the elements that were in l2, in their original order,
  | followed by the rest of l1.
   ------------------------------------------------------------------------*/

  void APIENTRY List_SplitAfter(LIST l1, LIST l2, LPVOID Curs);
  /*-----------------------------------------------------------------------
  | Let l1 be l1 and l2 be l2
  | Split l2 off from the front of l1:    final l2,l1 = original l1
  |
  | Split l1 into l2: objects of l1 up to and including Curs object
  |               l1: objects of l1 after Curs
  | Any original contents of l2 are freed.
  | List_Spilt(l1, l2, NULL) splits l1 before the first object so l1 gets all.
  | The elements themselves are not moved.
   ------------------------------------------------------------------------*/

  void APIENTRY List_SplitBefore(LIST l1, LIST l2, LPVOID Curs);
  /*----------------------------------------------------------------------
  | Split l2 off from the back of l1:  final l1,l2 = original l1
  |
  | Split l1 into l1: objects of l1 up to but not including Curs object
  |               l2: objects of l1 from Curs onwards
  | Any original contants of l2 are freed.
  | List_Spilt(l1, l2, NULL) splits l1 after the last object so l1 gets all.
  | The elements themselves are not moved.
   -----------------------------------------------------------------------*/

  int APIENTRY List_Card(LIST lst);
  /* Return the number of items in L */

  /*------------------------------------------------------------------
  | Error handling.
  |
  | Each list has within it a flag which indicates whether any illegal
  | operation has been detected (e.g. DeleteFirst when empty).
  | Rather than have a flag on every operation, there is a flag held
  | within the list that can be queried when convenient.  Many operations
  | do not have enough redundancy to allow any meaningful check.  This
  | is a design compromise (for instance to allow P = List_Next(P);
  | rather than P = List_Next(L, P); which is more awkward, especially
  | if L is actually a lengthy phrase).
  |
  | List_IsOK tests this flag (so is a very simple, quick operation).
  | MakeOK sets the flag to TRUE, in other words to accept the current
  | state of the list.
  |
  | It is possible for a list to be damaged (whether or not the flag
  | says OK) for instance by the storage being overwritten.
  |
  | List_Check attempts to verify that the list is sound (for instance where
  | there are both forward and backward pointers they should agree).
  |
  | List_Recover attempts to make a sound list out of whatever debris is left.
  | If the list is damaged, Recover may trap (e.g. address error) but
  | if the list was damaged then ANY operation on it may trap.
  | If Check succeeds without trapping then so will Recover.
   -----------------------------------------------------------------*/

  BOOL APIENTRY List_IsOK(LIST lst);
  /* Check return code */

  void APIENTRY List_MakeOK(LIST lst);
  /* Set return code to good */

  BOOL APIENTRY List_Check(LIST lst);
  /* Attempt to validate the chains */

  void APIENTRY List_Recover(PLIST plst);
  /* Desperate stuff.  Attempt to reconstruct something */

/*------------------------------------------------------------------
|  It is designed to be as easy to USE as possible, consistent
|  only with being an opaque type.
|
|  In particular, the decision to use the address of an object a list cursor
|  means that there is a small amount of extra arithmetic (in the
|  IMPLEMENTATION) in cursor operations (e.g. Next and Prev).
|  and spurious arguments are avoided whenever possible, even though
|  it would allow greater error checking.
|
| Of the "whole list" operations, Clear is given because it seems to be
| a common operation, even though the caller can implement it with almost
| the same efficiency as the List implementation module.
| Join, Split and InsertListXxx cannot be implemented efficiently without
| knowing the representation.
 --------------------------------------------------------------------*/


BAR.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 Header *******************************
* Module Name: BAR.C
*
* This module contains functions for bar window
* graphically showing two lists of sections and showing
* colored vertical bars for the sections of text,
* with linking lines for the sections that are the same.
*
* Functions:
*
* BarWndProc()
* BarPaint()
* DrawSection()
* DrawLink()
* BarClick()
* InitBarClass()
* BarDrawPosition()
*
* Comments:
*
****************************************************************************/

#include <windows.h>
#include <commdlg.h>

#include "gutils.h"
#include "table.h"
#include "state.h"
#include "wdiffrc.h"
#include "windiff.h"
#include "list.h"
#include "line.h"
#include "scandir.h"
#include "file.h"
#include "section.h"
#include "compitem.h"
#include "complist.h"
#include "view.h"

long APIENTRY BarWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam);
void BarPaint(HWND hwnd);
void DrawSection(HDC hdc, int cx, int cy, int lines, SECTION sec, int sidecode);
void DrawLink(HDC hdc, int cx, int cy, int lines, SECTION sec);
void BarClick(HWND hwnd, int x, int y);

HPEN hpenSame, hpenLeft, hpenRight;
HBRUSH hbrSame, hbrLeft, hbrRight;
HBRUSH hbrSideBar;

/***************************************************************************
 * Function: InitBarClass
 *
 * Purpose:
 *
 * Create bar window class
 */
BOOL
InitBarClass(HINSTANCE hInstance)
{
        WNDCLASS    wc;
        BOOL resp;

        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = BarWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = GetStockObject(WHITE_BRUSH);
        wc.lpszClassName = "BarClass";
        wc.lpszMenuName = NULL;

        resp = RegisterClass(&wc);

        return(resp);
}

/***************************************************************************
 * Function: BarWndProc
 *
 * Purpose:
 *
 * Window procedure supporting bar window
 *
 */

long APIENTRY
BarWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam)
{

        switch(message) {

        case WM_CREATE:

                hpenSame = CreatePen(PS_SOLID, 1, RGB(0,0,0));
                hbrSame = CreateSolidBrush(RGB(255,255,255));

                hpenLeft = CreatePen(PS_SOLID, 1, rgb_barleft);
                hbrLeft = CreateSolidBrush(rgb_barleft);

                hpenRight = CreatePen(PS_SOLID, 1, rgb_barright);
                hbrRight = CreateSolidBrush(rgb_barright);

                hbrSideBar = CreateSolidBrush(rgb_barcurrent);
                break;

        case WM_DESTROY:
                DeleteObject(hpenSame);
                DeleteObject(hpenLeft);
                DeleteObject(hpenRight);
                DeleteObject(hbrSame);
                DeleteObject(hbrLeft);
                DeleteObject(hbrRight);
                DeleteObject(hbrSideBar);
                break;

        case WM_PAINT:
                BarPaint(hWnd);
                break;

        case WM_LBUTTONDOWN:
                BarClick(hWnd, LOWORD(lParam), HIWORD(lParam));
                break;

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

/***************************************************************************
 * Function: BarDrawPosition
 *
 * Purpose:
 *
 * Draw the current position as side-bars down the bar window,
 * showing which lines from each file are currently in view. HDC can be
 * NULL (we get one ourselves if so). If bErase is true, we clear
 * the previous side-bars first.
 *
 * This is called from BarPaint when we paint the whole window, and
 * from TableServer() whenever it receives a TQ_SCROLL notification that
 * the table window has been scrolled.
 */
void
BarDrawPosition(HWND hwndBar, HDC hdcIn, BOOL bErase)
{
        HDC hdc;
        int total_lines, cy, cx;
        RECT rc, rcLeft, rcRight;
        VIEW view;
        COMPITEM item;
        LIST listleft, listright;
        long toprow, endrow, i;
        int left_first, left_last, right_first, right_last, linenr;

        /* get a hdc if we weren't given one */
        if (hdcIn == NULL) {
                hdc = GetDC(hwndBar);
        } else {
                hdc = hdcIn;
        }

        /* set horz position of bars */
        GetClientRect(hwndBar, &rc);
        cx = (int)(rc.right - rc.left);
        cy = (int)(rc.bottom - rc.top);

        /* layout constants are defined as percentages of window width */
        rcLeft.left = cx * L_POS_START / 100;
        rcRight.left = cx * R_POS_START / 100;
        rcLeft.right = rcLeft.left +  (cx * L_POS_WIDTH / 100);
        rcRight.right = rcRight.left +  (cx * R_POS_WIDTH / 100);

        /* erase the whole marker section if requested */
        if (bErase) {
                rcLeft.top = rc.top;
                rcLeft.bottom = rc.bottom;
                rcRight.top = rc.top;
                rcRight.bottom = rc.bottom;

                FillRect(hdc, &rcLeft, GetStockObject(WHITE_BRUSH));

                FillRect(hdc, &rcRight, GetStockObject(WHITE_BRUSH));
        }

        /*
         * calculate the vertical scaling - depends on the
         * total number of lines shown
         */

        /* get the handles to the two lists of sections */
        view = (VIEW) SendMessage(hwndClient, TM_CURRENTVIEW, 0, 0);
        /* make sure we are in expand mode */
        if (view_isexpanded(view) == FALSE) {
                /* get rid of the dc if we made it ourselves */
                if (hdcIn == NULL) {
                        ReleaseDC(hwndBar, hdc);
                }
                return;
        }

        item = view_getitem(view, 0);

        listleft = compitem_getleftsections(item);
        listright = compitem_getrightsections(item);

        /* if there is only one list of sections, draw nothing. The
         * picture for a single file is not very exciting.
         */

        if ((listleft == NULL) || (listright == NULL)) {
                /* get rid of the dc if we made it ourselves */
                if (hdcIn == NULL) {
                        ReleaseDC(hwndBar, hdc);
                }
                return;
        }

        /* take the longest of the two files and use this
         * for vertical scaling. the scale is such that the longest file
         * *just fits*.
         */
        total_lines = line_getlinenr(section_getlastline(List_Last(listleft)));
        total_lines = max(total_lines,
                       (int) line_getlinenr(section_getlastline(List_Last(listright))));

        /* get the current top row and nr of rows visible */
        toprow = SendMessage(hwndRCD, TM_TOPROW, FALSE, 0);
        endrow = SendMessage(hwndRCD, TM_ENDROW, FALSE, 0);
        endrow = min(endrow, view_getrowcount(view)-1);

        /*
         * find the first and last line nrs from each file currently visible.
         *
         */
        left_first = left_last = right_first = right_last = 0;

        for (i = toprow; i <= endrow; i++) {
                linenr = view_getlinenr_left(view, i);

                if (linenr > 0) {

                        if (left_first == 0) {
                                left_first = linenr;
                        }
                        left_first = min(left_first, linenr);
                        left_last = max(left_last, linenr);
                }

                linenr = view_getlinenr_right(view, i);
                if (linenr > 0) {
                        if (right_first == 0) {
                                right_first = linenr;
                        }
                        right_first = min(right_first, linenr);
                        right_last = max(right_last, linenr);
                }

        }

        /* draw the two markers as thick bars -> elongated rectangles */
        rcLeft.top = MulDiv(left_first-1, cy, total_lines);
        rcLeft.bottom = MulDiv(left_last, cy, total_lines);
        FillRect(hdc, &rcLeft, hbrSideBar);

        rcRight.top = MulDiv(right_first-1, cy, total_lines);
        rcRight.bottom = MulDiv(right_last, cy, total_lines);
        FillRect(hdc, &rcRight, hbrSideBar);

        /* get rid of the dc if we made it ourselves */
        if (hdcIn == NULL) {
                ReleaseDC(hwndBar, hdc);
        }

}

/***************************************************************************
 * Function: BarPaint
 *
 * Purpose:
 *
 * Paint the bar window
 */
void
BarPaint(HWND hwnd)
{
        PAINTSTRUCT ps;
        HDC hdc;
        VIEW view;
        COMPITEM item;
        LIST listleft, listright;
        SECTION sec;
        int total_lines, cx, cy;
        RECT rc;

        hdc = BeginPaint(hwnd, &ps);

        /* draw a separator line at the very edge of the window */
        GetClientRect(hwnd, &rc);
        MoveToEx(hdc, (int)(rc.right-1), rc.top, NULL);
        LineTo(hdc, (int)(rc.right-1), rc.bottom);

        /* first gather information about what is to be displayed */

        /* find the total lines (for horz. scaling) */

        /* get the handles to the two lists of sections */
        view = (VIEW) SendMessage(hwndClient, TM_CURRENTVIEW, 0, 0);

        /* make sure we are in expand mode */
        if (view_isexpanded(view) == FALSE) {
                return;
        }

        item = view_getitem(view, 0);

        listleft = compitem_getleftsections(item);
        listright = compitem_getrightsections(item);

        /*
         * don't bother if there is only one list - not very interesting
         */
        if ((listleft == NULL) || (listright == NULL)) {
                EndPaint(hwnd, &ps);
                return;
        }

        /* take the longest of the two files and use this
         * for vertical scaling. the scale is such that the longest file
         * *just fits*.
         */
        total_lines = (int) line_getlinenr(section_getlastline(List_Last(listleft)));
        total_lines = max(total_lines,
                       (int) line_getlinenr(section_getlastline(List_Last(listright))));

        /* horizontal spacing:
         *
         * there are two columns, for the left and right files, and a gap
         * between them criss-crossed by lines marking the links.
         *
         * Each of the columns then has three sections, for the
         * position marker, the different sections
         * and the linked sections. The width and positions of these items
         * are defined (in windiff.h) as percentages of the window width.
         */

        cx = (int)(rc.right - rc.left);
        cy = (int)(rc.bottom - rc.top);

        /* draw all the left sections and links */
        List_TRAVERSE(listleft, sec) {
                DrawSection(hdc, cx, cy, total_lines, sec, STATE_LEFTONLY);

                if (section_getlink(sec) != NULL) {
                        DrawLink(hdc, cx, cy, total_lines, sec);
                }
        }

        /* draw all the right sections */
        List_TRAVERSE(listright, sec) {
                DrawSection(hdc, cx, cy, total_lines, sec, STATE_RIGHTONLY);
        }

        /* now draw current position markers */
        BarDrawPosition(hwnd, hdc, FALSE);

        EndPaint(hwnd, &ps);
}

/***************************************************************************
 * Function: DrawSection
 *
 * Purpose:
 *
 * Paint a single section
 */
void
DrawSection(HDC hdc, int cx, int cy, int lines, SECTION sec, int sidecode)
{
        int x1, y1, x2, y2;
        HPEN hpenOld;
        HBRUSH hbrOld;

        /* calculate the vertical position from the scaling. the scaling
         * is such that the longest file just fits
         */
        y1 = MulDiv(line_getlinenr(section_getfirstline(sec))- 1, cy, lines);
        y2 = MulDiv(line_getlinenr(section_getlastline(sec)), cy, lines);

        /* left or right  - set bar position and width*/
        if (sidecode == STATE_LEFTONLY) {
                if (section_getlink(sec) != NULL) {
                        x1 = L_MATCH_START;
                        x2 = L_MATCH_WIDTH;
                } else {
                        x1 = L_UNMATCH_START;
                        x2 = L_UNMATCH_WIDTH;
                }
        } else {
                if (section_getlink(sec) != NULL) {
                        x1 = R_MATCH_START;
                        x2 = R_MATCH_WIDTH;
                } else {
                        x1 = R_UNMATCH_START;
                        x2 = R_UNMATCH_WIDTH;
                }
        }
        /* bar position defines are in percentages of the win width (cx) */
        x1 = cx * x1 / 100;
        x2 = (cx * x2 / 100) + x1;

        /* select pens and brushes */
        if (section_getlink(sec) != NULL) {
                hpenOld = SelectObject(hdc, hpenSame);
                hbrOld = SelectObject(hdc, hbrSame);
        } else if (sidecode == STATE_LEFTONLY) {
                hpenOld = SelectObject(hdc, hpenLeft);
                hbrOld = SelectObject(hdc, hbrLeft);
        } else {
                hpenOld = SelectObject(hdc, hpenRight);
                hbrOld = SelectObject(hdc, hbrRight);
        }

        /* draw the section as a coloured elongated rectangle */
        Rectangle(hdc, x1, y1, x2, y2);

        /* de-select the pen and brush in favour of the default */
        SelectObject(hdc, hpenOld);
        SelectObject(hdc, hbrOld);

}

/***************************************************************************
 * Function: DrawLink
 *
 * Purpose:
 *
 * Draw a line linking two sections. Indicates a section from each
 * file that match each other. psec points to the section in the
 * left file.
 */
void
DrawLink(HDC hdc, int cx, int cy, int lines, SECTION sec)
{
        int x1, y1, x2, y2;
        int ybase, yrange;
        SECTION other;

        other = section_getlink(sec);

        /* position the link line halfway down the section
         * - allow for the case where
         * the section is one line (ie halve the co-ords, not the line nr)
         */
        ybase = MulDiv(line_getlinenr(section_getfirstline(sec)) - 1, cy, lines);
        yrange = MulDiv(line_getlinenr(section_getlastline(sec)), cy, lines);
        y1 = ((yrange - ybase) / 2) + ybase;

        ybase = MulDiv(line_getlinenr(section_getfirstline(other)) - 1, cy, lines);
        yrange = MulDiv(line_getlinenr(section_getlastline(other)), cy, lines);
        y2 = ((yrange - ybase) / 2) + ybase;

        /* horizontal layout constants are defined as percentages of the
         * window width
         */
        x1 = cx * (L_MATCH_START + L_MATCH_WIDTH) / 100;
        x2 = cx * R_UNMATCH_START / 100;

        MoveToEx(hdc, x1, y1, NULL);
        LineTo(hdc, x2, y2);
}

/***************************************************************************
 * Function: BarClick
 *
 * Purpose:
 *
 * The user has clicked on the bar window. Translate the clicked position into
 * a line in one of the files if possible, and scroll the table window to
 * show that line.
 */
void
BarClick(HWND hwnd, int x, int y)
{
        RECT rc;
        int xleft, xright;
        int linenr, i, this;
        BOOL bIsLeft;
        int tot_left, tot_right, total_lines;
        LIST listleft, listright;
        VIEW view;
        COMPITEM item;
        TableSelection select;

        /* find size of the window to get horz scaling, and see
         * where click was
         */
        GetClientRect(hwnd, &rc);

        /* was it near either of the bars ? */

        /* horz positioning is in percentages of window width */
        xleft = max(L_UNMATCH_START + L_UNMATCH_WIDTH,
                     L_MATCH_START + L_MATCH_WIDTH);
        xright = min(R_UNMATCH_START, R_MATCH_START);
        xleft = xleft * (rc.right - rc.left) / 100;
        xright = xright * (rc.right - rc.left) / 100;

        if (x < xleft) {
                bIsLeft = TRUE;
        } else if (x > xright) {
                bIsLeft = FALSE;
        } else {
                /* click was between the two bars - ignore it */
                return;
        }

        /* calculate the vertical scaling (based on total lines displayed)
         * so that we can convert the y position into a line nr
         */

        /* get the handles to the two lists of sections */
        view = (VIEW) SendMessage(hwndClient, TM_CURRENTVIEW, 0, 0);

        /* make sure we are in expand mode */
        if (view_isexpanded(view) == FALSE) {
                return;
        }

        item = view_getitem(view, 0);

        listleft = compitem_getleftsections(item);
        listright = compitem_getrightsections(item);

        /* ignore the click if only one list of sections, since in
         * this case there is nothing drawn for him to click on.
         */
        if ((listleft == NULL) || (listright == NULL)) {
                return;
        }

        /* take the longest of the two files and use this
         * for vertical scaling. the scale is such that the longest file
         * *just fits*.
         */
        tot_left = line_getlinenr(section_getlastline(List_Last(listleft)));
        tot_right = line_getlinenr(section_getlastline(List_Last(listright)));

        total_lines = max(tot_left, tot_right);

        /* convert vertical position into a line nr. The vertical scaling
         * can be calculated from knowing that the longest list of
         * lines just fits in the window.
         */
        linenr = (int) (((long) total_lines * y) / (rc.bottom - rc.top)) + 1;

        /* check that the line is valid */
        if (bIsLeft) {
                if (linenr > tot_left) {
                        return;
                }
        } else {
                if (linenr > tot_right) {
                        return;
                }
        }

        /* search the current view, looking for a row with this
         * line nr on the correct side
         */
        for (i = 0; i < view_getrowcount(view); i++) {
                if (bIsLeft) {
                        this = view_getlinenr_left(view,i);
                } else {
                        this = view_getlinenr_right(view,i);
                }

                if (linenr == this) {
                        /* found the matching line- select it in the
                         * table window
                         */
                        select.startrow = i;
                        select.startcell = 0;
                        select.nrows = 1;
                        select.ncells = 1;
                        SendMessage(hwndRCD, TM_SELECT, 0, (long) (LPSTR)&select);
                        return;
                }
        }

        windiff_UI(TRUE);
        MessageBox(hwndClient, LoadRcString(IDS_LINE_NOT_VISIBLE),
                "WinDiff", MB_ICONSTOP|MB_OK);
        windiff_UI(FALSE);
}


SCANDIR.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 Header *******************************
* Module Name: SCANDIR.C
*
* Scan a directory tree and build a sorted list of filenames within that
* tree.
*
* Functions:
*
* dir_buildlist()
* dir_delete()
* dir_isfile()
* dir_firstitem()
* dir_nextitem()
* dir_findnextfile()
* dir_getrelname()
* dir_getfullname()
* dir_getroot_list()
* dir_getroot_item()
* dir_freerelname()
* dir_freefullname()
* dir_freeroot_list()
* dir_freerootitem()
* dir_getopenname()
* dir_freeopenname()
* dir_openfile()
* dir_closefile()
* dir_filesize()
* dir_startcopy()
* dir_endcopy()
* dir_copy()
* dir_finalelem()
* dir_cleardirect()
* dir_adddirect()
* dir_addfile()
* dir_scan()
* dir_isvaliddir()
* dir_isvalidfile()
* dir_fileinit()
* dir_dirinit()
* dir_getpathsize()
* dir_findnextfile()
*
* Comments:
*
* The call dir_buildlist takes a pathname and returns a handle. Subsequent
* calls to dir_firstitem and dir_nextitem return handles to
* items within the list, from which you can get the name of the
* file (relative to the original pathname, or complete), and filesize.
*
* The list can be either built entirely during the build call, or
* built one directory at a time as required by dir_nextitem calls. This
* option affects only relative performance, and is taken as a
* recommendation only (ie some of the time we will ignore the flag).
*
* The list is ordered alphabetically (case-insensitive using lstrcmpi).
* within any one directory, we list filenames before going on
* to subdirectory contents.
*
* All memory is allocated from a gmem_* heap hHeap declared
* and initialised elsewhere.
*
* The caller gets handles to two things: a DIRLIST, representing the
* entire list of filenames, and a DIRITEM: one item within the list.
*
* From the DIRITEM he can get the filename (including or excluding the
* tree root passed to dir_build*) - and also he can get to the next
* DIRITEM.
*
* We permit lazy building of the tree (usually so the caller can keep
* the user-interface up-to-date as we go along). In this case,
* we need to store information about how far we have scanned and
* what is next to do. We need to scan an entire directory at a time and then
* sort it so we can return files in the correct order.
*
* We scan an entire directory and store it in a DIRECT struct. This contains
* a list of DIRITEMs for the files in the current directory, and a list of
* DIRECTs for the subdirectories (possible un-scanned).
*
* dir_nextitem will use the list functions to get the next DIRITEM on the list.
* When the end of the list is reached, it will use the backpointer back to the
* DIRECT struct to find the next directory to scan.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include <direct.h>

#include "gutils.h"
#include "list.h"
#include "scandir.h"
#include "windiff.h"
#include "wdiffrc.h"

/*
 * Hold name and information about a given file (one ITEM in a DIRectory)
 * caller's DIRITEM handle is a pointer to one of these structures
 */
struct diritem {
        LPSTR name;             /* ptr to filename (final element only) */
        long size;              /* filesize */
        struct direct FAR * direct; /* containing directory */
        LPSTR localname;        /* name of temp copy of file */
        BOOL bLocalIsTemp;      /* true if localname is tempfile.
                                 */
};

/* DIRECT: Hold state about directory and current position in list of filenames.
 */
typedef struct direct {
        LPSTR relname;          /* name of dir relative to DIRLIST root */
        DIRLIST head;           /* back ptr (to get fullname) */
        struct direct FAR * parent; /* parent directory (NULL if above tree root)*/

        BOOL bScanned;          /* TRUE if scanned */
        LIST diritems;          /* list of DIRITEMs for files in cur. dir */
        LIST directs;           /* list of DIRECTs for child dirs */

        int pos;                /* where are we begin, files, dirs */
        struct direct FAR * curdir; /* subdir being scanned (ptr to list element)*/
} FAR * DIRECT;

/* Values for direct.pos */
#define DL_FILES        1       /* reading files from the diritems */
#define DL_DIRS         2       /* in the dirs: List_Next on curdir */

/*
 * The DIRLIST handle returned from a build function is in fact
 * a pointer to one of these
 */
struct dirlist {

        char rootname[256];        /* name of root of tree */
        BOOL bFile;             /* TRUE if root of tree is file, not dir */
        DIRECT dot;             /* dir  for '.' - for tree root dir */
};

extern BOOL bAbort;             /* from windiff.c (read only here). */

/* ------ memory allocation ---------------------------------------------*/

/* All memory is allocated from a heap created by the application */
extern HANDLE hHeap;

/*-- forward declaration of internal functions ---------------------------*/

LPSTR dir_finalelem(LPSTR path);
void dir_cleardirect(DIRECT dir);
void dir_adddirect(DIRECT dir, LPSTR path);
void dir_addfile(DIRECT dir, LPSTR path, DWORD size);
void dir_scan(DIRECT dir, BOOL bRecurse);
BOOL dir_isvaliddir(LPSTR path);
BOOL dir_isvalidfile(LPSTR path);
void dir_fileinit(DIRITEM pfile, DIRECT dir, LPSTR path, long size);
void dir_dirinit(DIRECT dir, DIRLIST head, DIRECT parent, LPSTR name);
long dir_getpathsize(LPSTR path);
DIRITEM dir_findnextfile(DIRLIST dl, DIRECT curdir);

/***************************************************************************
 * Function: dir_buildlist
 *
 * Purpose:
 *
 * Build a list of filenames
 *
 * Optionally build the list on demand, in which case we scan the
 * entire directory but don't recurse into subdirs until needed
 *
 */

DIRLIST
dir_buildlist(LPSTR path, BOOL bOnDemand)
{
        DIRLIST dl;
        BOOL bFile;

        /* first check if the path is valid */
        if (dir_isvaliddir(path)) {
                bFile = FALSE;
        } else if (dir_isvalidfile(path)) {
                bFile = TRUE;
        } else {
                /* not valid */
                return(NULL);
        }

        /* alloc and init the DIRLIST head */

        dl = (DIRLIST) gmem_get(hHeap, sizeof(struct dirlist));
        memset(dl, 0, sizeof(struct dirlist));

        /* convert the pathname to an absolute path */

        _fullpath(dl->rootname, path, sizeof(dl->rootname));

        dl->bFile = bFile;
        /* make a '.' directory for the current directory -
         * all files and subdirs will be listed from here
         */
        dl->dot = (DIRECT) gmem_get(hHeap, sizeof(struct direct));
        dir_dirinit(dl->dot, dl, NULL, ".");

        /* were we given a file or a directory ? */
        if (bFile) {
                /* its a file. create a single file entry
                 * and set the state accordingly
                 */
                dl->dot->bScanned = TRUE;

                dir_addfile(dl->dot, dir_finalelem(path),
                                dir_getpathsize(path));

                return(dl);
        }

        /* scan the root directory and return. if we are asked
         * to scan the whole thing, this will cause a recursive
         * scan all the way down the tree
         */
        dir_scan(dl->dot, (!bOnDemand) );

        return(dl);
} /* dir_buildlist */

/***************************************************************************
 * Function: dir_delete
 *
 * Purpose:
 *
 * Free up the DIRLIST and all associated memory
 */
void
dir_delete(DIRLIST dl)
{
        if (dl == NULL) {
                return;
        }
        dir_cleardirect(dl->dot);
        gmem_free(hHeap, (LPSTR) dl->dot, sizeof(struct direct));

        gmem_free(hHeap, (LPSTR) dl, sizeof(struct dirlist));
}

/***************************************************************************
 * Function: dir_isfile
 *
 * Purpose:
 *
 * Was the original build request a file or a directory ?
 */
BOOL
dir_isfile(DIRLIST dl)
{
        if (dl == NULL) {
                return(FALSE);
        }

        return(dl->bFile);
}

/***************************************************************************
 * Function: dir_firstitem
 *
 * Purpose:
 *
 * Return the first file in the list, or NULL if no files found.
 * Returns a DIRITEM. This can be used to get filename, size and chcksum.
 * If there are no files in the root, we recurse down until we find a file.
 */
DIRITEM
dir_firstitem(DIRLIST dl)
{
        if (dl == NULL) {
                return(NULL);
        }
        /*
         * reset the state to indicate that no files have been read yet
         */
        dl->dot->pos = DL_FILES;
        dl->dot->curdir = NULL;

        /* now get the next filename */
        return(dir_findnextfile(dl, dl->dot));
} /* dir_firstitem */

/***************************************************************************
 * Function:dir_nextitem
 *
 * Purpose:
 *
 * Get the next filename after the one given.
 *
 * The List_Next function can give us the next element on the list of files.
 * If this is null, we need to go back to the DIRECT and find the
 * next list of files to traverse (in the next subdir).
 *
 * After scanning all the subdirs, return to the parent to scan further
 * dirs that are peers of this, if there are any. If we have reached the end of
 * the tree (no more dirs in dl->dot to scan), return NULL.
 *
 * Don't recurse to lower levels unless fDeep is TRUE
 */
DIRITEM
dir_nextitem(DIRLIST dl, DIRITEM cur, BOOL fDeep)
{
        DIRITEM next;

        if ((dl == NULL) || (cur == NULL)) {
                return(NULL);
        }
        if (bAbort) return NULL;  /* user requested abort */

        if ( (next = List_Next(cur)) != NULL) {
                /* there was another file on this list */
                return(next);
        }
        if (!fDeep) return NULL;

        /* get the head of the next list of filenames from the directory */
        cur->direct->pos = DL_DIRS;
        cur->direct->curdir = NULL;
        return(dir_findnextfile(dl, cur->direct));
} /* dir_nextitem */

/***************************************************************************
 * Function: dir_findnextfile
 *
 * Purpose:
 *
 * Gets the next file in the directory
 */
DIRITEM
dir_findnextfile(DIRLIST dl, DIRECT curdir)
{
        DIRITEM curfile;

        if ((dl == NULL) || (curdir == NULL)) {
                return(NULL);
        }

        /* scan the subdir if necessary */
        if (!curdir->bScanned) {
                dir_scan(curdir, FALSE);
        }

        /* have we already read the files in this directory ? */
        if (curdir->pos == DL_FILES) {
                /* no - return head of file list */
                curfile = (DIRITEM) List_First(curdir->diritems);
                if (curfile != NULL) {
                        return(curfile);
                }

                /* no more files - try the subdirs */
                curdir->pos = DL_DIRS;
        }

        /* try the next subdir on the list, if any */
        /* is this the first or the next */
        if (curdir->curdir == NULL) {
                curdir->curdir = (DIRECT) List_First(curdir->directs);
        } else {
                curdir->curdir = (DIRECT) List_Next(curdir->curdir);
        }
        /* did we find a subdir ? */
        if (curdir->curdir == NULL) {

                /* no more dirs - go back to parent if there is one */
                if (curdir->parent == NULL) {
                        /* no parent - we have exhausted the tree */
                        return(NULL);
                }

                /* reset parent state to indicate this is the current
                 * directory - so that next gets the next after this.
                 * this ensures that multiple callers of dir_nextitem()
                 * to the same tree work.
                 */
                curdir->parent->pos = DL_DIRS;
                curdir->parent->curdir = curdir;

                return(dir_findnextfile(dl, curdir->parent));
        }

        /* there is a next directory - set it to the
         * beginning and get the first file from it
         */
        curdir->curdir->pos = DL_FILES;
        curdir->curdir->curdir = NULL;
        return(dir_findnextfile(dl, curdir->curdir));

} /* dir_findnextfile */

/*-- pathnames ----
 *
 * This module supports two types of pathnames, called relative and full.
 * Relative names are relative to the root passed in the initial call
 * to dir_build*, and full names include the tree root.
 *
 * Note that this is a different distinction to relative vs absolute
 * pathnames, since the tree root may still be either relative or absolute.
 *
 * Examples:
 *
 *  - if you called dir_buildlist("c:\")
 *              getrelname gives:               ".\config.sys"
 *              getfullname gives:              "c:\config.sys"
 *
 * - if you called dir_buildlist(".\geraintd")
 *              getrelname gives:               ".\source\scandir.h"
 *              getfullname gives either
 *                      ".\geraintd\source\scandir.h"
 *                    or "c:\geraintd\source\scandir.h"
 *                   (depending on the implementation).
 *
 * To support this, we maintain the tree root name in the DIRLIST head, and
 * in each directory, the name of that directory relative to tree root.
 * Files just have the filename, so we need to prepend the directory name,
 * and (for getfullname) the tree root name as well
 *
 * We store the directory name with a trailing
 * slash to make concatenation easier
 *
 * -----
 */

/***************************************************************************
 * Function: dir_getrelname
 *
 * Purpose:
 *
 * Return the name of the current file relative to tree root
 */
LPSTR
dir_getrelname(DIRITEM cur)
{
        LPSTR name;
        int size;

        /* check this is a valid item */
        if (cur == NULL) {
                return(NULL);
        }
        /* remember to include the NULL when sizing */
        size = lstrlen(cur->direct->relname) + lstrlen(cur->name) + 1;
        name = gmem_get(hHeap, size);
        lstrcpy(name, cur->direct->relname);
        lstrcat(name, cur->name);

        return(name);
} /* dir_getrelname */

/***************************************************************************
 * Function: dir_getfullname
 *
 * Purpose:
 *
 * Return the fullname of the file (including the tree root passed in)
 */
LPSTR
dir_getfullname(DIRITEM cur)
{
        LPSTR name;
        int size;
        LPSTR head;

        /* check this is a valid item */
        if (cur == NULL)  {
                return(NULL);
        }

        if (cur->direct->head->bFile) {
                return(cur->direct->head->rootname);
        }

        /* remember to include the NULL when sizing */
        size = lstrlen(cur->name) + 1;

        size += lstrlen(cur->direct->relname);

        /* add on root name */
        head = cur->direct->head->rootname;
        size += lstrlen(head);

        /* root names may not end in a slash. we need to
         * insert one in this case. Also, relnames always begin .\, so
         * we skip the . always, and the .\ if we don't need to
         * append a slash
         *
         */
        size--;         /* omit the '.' */
        if (*CharPrev(head, head+lstrlen(head)) == '\\') {
                size--;                         /* omit the .\ */
        }

        name = gmem_get(hHeap, size);

        lstrcpy(name, cur->direct->head->rootname);

        /* add relname and then name, omiting the .\ */

                /* skip . or .\ before relname */
                if (*CharPrev(head, head+lstrlen(head)) == '\\') {
                        lstrcat(name, &cur->direct->relname[2]);
                } else {
                        lstrcat(name, &cur->direct->relname[1]);
                }
                lstrcat(name, cur->name);
        return(name);
} /* dir_getfullname */

/***************************************************************************
 * Function: dir_getroot_list
 *
 * Purpose:
 *
 * Return the name of the tree root given a handle to the DIRLIST.
 */
LPSTR
dir_getroot_list(DIRLIST dl)
{
        if (dl == NULL)
                return(NULL);
        return(dl->rootname);
} /* dir_getroot_list */

/***************************************************************************
 * Function: dir_getroot_item
 *
 * Purpose:
 *
 * Return the root name of this tree given a handle to a DIRITEM in the
 * list.
 */
LPSTR dir_getroot_item(DIRITEM item)
{
        if (item == NULL)
                return(NULL);

        return(dir_getroot_list(item->direct->head));
}

/***************************************************************************
 * Function: dir_freerelname
 *
 * Purpose:
 *
 * Free up a relname that we allocated. This interface allows us
 * some flexibility in how we store relative and complete names
 *
 */
void
dir_freerelname(DIRITEM cur, LPSTR name)
{
        if((cur != NULL) && (name != NULL))
                        gmem_free(hHeap, name, lstrlen(name) +1);
} /* dir_freerelname */

/***************************************************************************
 * Function: dir_freefullname
 *
 * Purpose:
 *
 */
void
dir_freefullname(DIRITEM cur, LPSTR name)
{
        if (cur->direct->head->bFile)
                return;

        if (name != NULL)
                gmem_free(hHeap, name, lstrlen(name) + 1);
} /* dir_freefullname            */

/***************************************************************************
 * Function: dir_freeroot_list
 *
 * Purpose:
 *
 * Free up rootname allocated by dir_getroot_list.
 * We just gave a pointer to the rootname, so do nothing.
 */
void
dir_freeroot_list(DIRLIST dl, LPSTR name)
{
        if ((dl == NULL) || (name == NULL)) {
                return;
        }
        return;
} /* dir_freeroot_list */

/***************************************************************************
 * Function: dir_freeroot_item
 *
 * Purpose:
 *
 * Free up memory alloc-ed by a call to dir_getroot_item.
 */
void
dir_freeroot_item(DIRITEM item, LPSTR name)
{
        if ((item == NULL) || (name == NULL))
                return;
        dir_freeroot_list(item->direct->head, name);
}

/***************************************************************************
 * Function: dir_getopenname
 *
 * Purpose:
 *
 * Get an open-able name for the file. This will be the same as the fullname.
 */
LPSTR
dir_getopenname(DIRITEM item)
{
        LPSTR fname;

        if (item == NULL)
                return(NULL);

        fname = dir_getfullname(item);

                return(fname);
} /* dir_getopenname */

/***************************************************************************
 * Function: dir_freeopenname
 *
 * Purpose:
 *
 * Free up memory created by a call to dir_getopenname(). This *may*
 * cause the file to be deleted if it was a temporary copy.
 */
void
dir_freeopenname(DIRITEM item, LPSTR openname)
{
        if ((item == NULL) || (openname == NULL))
                return;

        dir_freefullname(item, openname);
} /* dir_freeopenname */

/***************************************************************************
 * Function: dir_openfile
 *
 * Purpose:
 *
 * Return an open file handle to the file.
 */
int
dir_openfile(DIRITEM item)
{
        LPSTR fname;
        int fh;
        OFSTRUCT os;

        fname = dir_getfullname(item);
        fh = OpenFile(fname, &os, OF_READ|OF_SHARE_DENY_NONE);
        dir_freefullname(item, fname);
        return(fh);
} /* dir_openfile */

/***************************************************************************
 * Function: dir_closefile
 *
 * Purpose:
 *
 * Close a file opened with dir_openfile.
 */
void
dir_closefile(DIRITEM item, int fh)
{
        _lclose(fh);

} /* dir_closefile */

/***************************************************************************
 * Function: dir_getfilesize
 *
 * Purpose:
 *
 * Return the file size (set during scanning)
 */
long
dir_getfilesize(DIRITEM cur)
{
        /* check this is a valid item */
        if (cur == NULL)
                return(0);

        return(cur->size);
} /* dir_getfilesize */

/* ss_endcopy returns a number indicating the number of files copied,
   but we may have some local copies too.  We need to count these
   ourselves and add them in
*/

int nLocalCopies;        /* cleared in startcopy, ++d in copy
                                ** inspected in endcopy
                                */

/***************************************************************************
 * Function: dir_startcopy
 *
 * Purpose:
 *
 * Start a bulk copy
 */
BOOL dir_startcopy(DIRLIST dl)
{
        nLocalCopies = 0;
        return(TRUE);

} /* dir_startcopy */
/***************************************************************************
 * Function: dir_endcopy
 *
 */

int dir_endcopy(DIRLIST dl)
{
        return(nLocalCopies);

} /* dir_endcopy */

/***************************************************************************
 * Function: dir_copy
 *
 * Purpose:
 *
 * Create a copy of the file, in the new root directory. Creates sub-dirs as
 * necessary.
 *
 * Returns TRUE for success and FALSE for failure.
 */
BOOL dir_copy(DIRITEM item, LPSTR newroot)
{
        static char newpath[256];
        LPSTR relname, fullname;
        LPSTR pstart, pdest, pel;
        BOOL bOK;

        BY_HANDLE_FILE_INFORMATION bhfi;
        HANDLE hfile;

        /*
         * check that the newroot directory itself exists
         */
        if ((item == NULL) || !dir_isvaliddir(newroot)) {
                return(FALSE);
        }

        /*
         * name of file relative to the tree root
         */
        relname = dir_getrelname(item);

        /*
         * build the new pathname by concatenating the new root and
         * the old relative name. add one path element at a time and
         * ensure that the directory exists, creating it if necessary.
         */
        lstrcpy(newpath, newroot);

        /* add separating slash if not already there */
        if (*CharPrev(newpath, newpath+lstrlen(newpath)) != '\\') {
                lstrcat(newpath, "\\");
        }

        pstart = relname;
        while ( (pel = strchr(pstart, '\\')) != NULL) {

                /* found another element ending in slash. incr past the \\ */
                pel++;

                /*
                 * ignore .
                 */
                if (strncmp(pstart, ".\\", 2) != 0) {

                        pdest = &newpath[lstrlen(newpath)];
                        strncpy(pdest, pstart, pel - pstart);
                        pdest[pel - pstart] = '\0';

                        /* create subdir if necessary */
                        if (!dir_isvaliddir(newpath)) {
                                if (_mkdir(newpath) != 0) {
                                        return(FALSE);
                                }
                        }
                }

                pstart = pel;
        }

        /*
         * there are no more slashes, so pstart points at the final
         * element
         */
        lstrcat(newpath, pstart);

        fullname = dir_getfullname(item);

                bOK = CopyFile(fullname, newpath, FALSE);

                /* having copied the file, now copy the times, attributes */
                hfile = CreateFile(fullname, GENERIC_READ, 0, NULL,
                                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                bhfi.dwFileAttributes = GetFileAttributes(fullname);
                GetFileTime(hfile, &bhfi.ftCreationTime,
                                &bhfi.ftLastAccessTime, &bhfi.ftLastWriteTime);
                CloseHandle(hfile);

                hfile = CreateFile(newpath, GENERIC_WRITE, 0, NULL,
                                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                SetFileTime(hfile, &bhfi.ftCreationTime,
                                   &bhfi.ftLastAccessTime,
                                   &bhfi.ftLastWriteTime);
                CloseHandle(hfile);
                SetFileAttributes(newpath, bhfi.dwFileAttributes);

                if (bOK) ++nLocalCopies;

        dir_freerelname(item, relname);
        dir_freefullname(item, fullname);

        return(bOK);
} /* dir_copy */

/***************************************************************************
 * Function: dir_dirinit
 *
 * Purpose:
 *
 * Fill out a new DIRECT for a subdirectory (pre-allocated).
 * Init files and dirs lists to empty (List_Create). Set the relname
 * of the directory by pre-pending the parent relname if there
 * is a parent, and appending a trailing slash (if there isn't one).
 */
void
dir_dirinit(DIRECT dir, DIRLIST head, DIRECT parent, LPSTR name)
{
        int size;

        dir->head = head;
        dir->parent = parent;

        /* add on one for the null and one for the trailing slash */
        size = lstrlen(name) + 2;
        if (parent != NULL) {
                size += lstrlen(parent->relname);
        }

        /* build the relname from the parent and the current name
         * with a terminating slash
         */
        dir->relname = gmem_get(hHeap, size);
        if (parent != NULL) {
                lstrcpy(dir->relname, parent->relname);
        } else{
                dir->relname[0] = '\0';
        }

        lstrcat(dir->relname, name);

        if (*CharPrev(dir->relname,
                        dir->relname+lstrlen(dir->relname)) != '\\') {
                lstrcat(dir->relname, "\\");
        }

        /* force name to lowercase */
        AnsiLowerBuff(dir->relname, lstrlen(dir->relname));

        dir->diritems = List_Create();
        dir->directs = List_Create();
        dir->bScanned = FALSE;
        dir->pos = DL_FILES;

} /* dir_dirinit */

/***************************************************************************
 * Function: dir_fileinit
 *
 * Purpose:
 *
 * Initialise the contents of an (allocated) DIRITEM struct.
 */
void
dir_fileinit(DIRITEM pfile, DIRECT dir, LPSTR path, long size)
{

        pfile->name = gmem_get(hHeap, lstrlen(path) + 1);
        lstrcpy(pfile->name, path);

        /* force name to lower case */
        AnsiLowerBuff(pfile->name, lstrlen(path));

        pfile->direct = dir;
        pfile->size = size;

        pfile->localname = NULL;

} /* dir_fileinit */

/***************************************************************************
 * Function: dir_isfilevalid
 *
 * Purpose:
 *
 * Is this a valid file or not
 */
BOOL
dir_isvalidfile(LPSTR path)
{
        DWORD dwAttrib;

        dwAttrib = GetFileAttributes(path);
        if (dwAttrib == -1) {
                return(FALSE);
        }
        if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
                return(FALSE);
        }
        return(TRUE);
} /* dir_isvalidfile */

/***************************************************************************
 * Function: dir_isvaliddir
 *
 * Purpose:
 *
 * Is this a valid directory ?
 */
BOOL
dir_isvaliddir(LPSTR path)
{
        DWORD dwAttrib;

        dwAttrib = GetFileAttributes(path);
        if (dwAttrib == -1) {
                return(FALSE);
        }
        if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
                return(TRUE);
        }
        return(FALSE);
} /* dir_isvaliddir */

/***************************************************************************
 * Function: dir_scan
 *
 * Purpose:
 *
 * Scan the directory given. Add all files to the list
 * in alphabetic order, and add all directories in alphabetic
 * order to the list of child DIRITEMs. If bRecurse is true, go on to
 * recursive call dir_scan for each of the child DIRITEMs
 */
void
dir_scan(DIRECT dir, BOOL bRecurse)
{
        PSTR path;
        int size;
        DIRECT child;
        BOOL bMore;
        long filesize;
        BOOL bIsDir;
        LPSTR name;

        HANDLE hFind;
        WIN32_FIND_DATA finddata;

        char debugmsg[200];
        wsprintf(debugmsg, "scandir: %s %s\n",
                 dir->relname, bRecurse?"recursive":"non-recursive"
                );

        /* make the complete search string including *.* */
        size = lstrlen(dir->head->rootname);
        size += lstrlen(dir->relname);

        /* add on one null and *.* */
        size += 4;

        path = LocalLock(LocalAlloc(LHND, size));

        lstrcpy(path, dir->head->rootname);

        /* omit the . at the beginning of the relname, and the
         * .\ if there is a trailing \ on the rootname
         */
        if (*CharPrev(path, path+lstrlen(path)) == '\\') {
                lstrcat(path, &dir->relname[2]);
        } else {
                lstrcat(path, &dir->relname[1]);
        }
        lstrcat(path, "*.*");

        /* read all entries in the directory */
        hFind = FindFirstFile(path, &finddata);
        bMore = (hFind != (HANDLE) -1);
        LocalUnlock(LocalHandle ( (PSTR) path));
        LocalFree(LocalHandle ( (PSTR) path));

        while (bMore) {

                bIsDir = (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
                name = (LPSTR) &finddata.cFileName;
                filesize = finddata.nFileSizeLow;
                if (!bIsDir) {

                        dir_addfile(dir, name, filesize);

                } else if ( (lstrcmp(name, ".") != 0) &&
                           ( lstrcmp(name, "..") != 0) ) {

                        dir_adddirect(dir, name);
                }

                bMore = FindNextFile(hFind, &finddata);
        }

        FindClose(hFind);

        dir->bScanned = TRUE;
        dir->pos = DL_FILES;

        if (bRecurse) {
                List_TRAVERSE(dir->directs, child) {
                        dir_scan(child, TRUE);
                }
        }

} /* dir_scan */

/***************************************************************************
 * Function: dir_addfile
 *
 * Purpose:
 *
 * Add the file 'path' to the list of files in dir, in order.
 */
void
dir_addfile(DIRECT dir, LPSTR path, DWORD size)
{
        DIRITEM pfile;

        AnsiLowerBuff(path, lstrlen(path));  // needless?

        List_TRAVERSE(dir->diritems, pfile) {
                /////if (lstrcmpi(pfile->name, path) > 0) {
                if (utils_CompPath(pfile->name, path) > 0) {

                        /* goes before this one */
                        pfile = List_NewBefore(dir->diritems, pfile, sizeof(struct diritem));
                        dir_fileinit(pfile, dir, path, size);
                        return;
                }
        }
        /* goes at end */
        pfile = List_NewLast(dir->diritems, sizeof(struct diritem));
        dir_fileinit(pfile, dir, path, size);
} /* dir_addfile */

/***************************************************************************
 * Function: dir_addirect
 *
 * Purpose:
 *
 * Add a new directory in alphabetic order on
 * the list dir->directs
 *
 */
void
dir_adddirect(DIRECT dir, LPSTR path)
{
        DIRECT child;
        LPSTR finalel;
        char achTempName[256];

        AnsiLowerBuff(path, lstrlen(path));
        List_TRAVERSE(dir->directs, child) {

                int cmpval;

                /* we need to compare the child name with the new name.
                 * the child name is a relname with a trailing
                 * slash - so compare only the name up to but
                 * not including the final slash.
                 */
                finalel = dir_finalelem(child->relname);

                /*
                 * we cannot use strnicmp since this uses a different
                 * collating sequence to lstrcmpi. So copy the portion
                 * we are interested in to a null-term. buffer.
                 */
                strncpy(achTempName, finalel, lstrlen(finalel)-1);
                achTempName[lstrlen(finalel)-1] = '\0';

                cmpval = utils_CompPath(achTempName, path);

                if (cmpval > 0) {

                        /* goes before this one */
                        child = List_NewBefore(dir->directs, child, sizeof(struct direct));
                        dir_dirinit(child, dir->head, dir, path);
                        return;
                }
        }
        /* goes at end */
        child = List_NewLast(dir->directs, sizeof(struct direct));
        dir_dirinit(child, dir->head, dir, path);
} /* dir_adddirect */

/***************************************************************************
 * Function: dir_cleardirect
 *
 * Purpose:
 *
 * Free all memory associated with a DIRECT (including freeing
 * child lists). Don't de-alloc the direct itself (allocated on a list)
 */
void
dir_cleardirect(DIRECT dir)
{
        DIRITEM pfile;
        DIRECT child;

        /* clear contents of files list */
        List_TRAVERSE(dir->diritems, pfile) {
                gmem_free(hHeap, pfile->name, lstrlen(pfile->name));
                if ((pfile->localname) && (pfile->bLocalIsTemp)) {

                        /*
                         * the copy will have copied the attributes,
                         * including read-only. We should unset this bit
                         * so we can delete the temp file.
                         */
                        SetFileAttributes(pfile->localname,
                                GetFileAttributes(pfile->localname)
                                        & ~FILE_ATTRIBUTE_READONLY);
                        DeleteFile(pfile->localname);
                        gmem_free(hHeap, pfile->localname, 256);
                        pfile->localname = NULL;
                }

        }
        List_Destroy(&dir->diritems);

        /* clear contents of dirs list (recursively) */
        List_TRAVERSE(dir->directs, child) {
                dir_cleardirect(child);
        }
        List_Destroy(&dir->directs);

        gmem_free(hHeap, dir->relname, lstrlen(dir->relname) + 1);

} /* dir_cleardirect */

/***************************************************************************
 * Function: dir_finalelem
 *
 * Purpose:
 *
 * Return a pointer to the final element in a path. Note that
 * we may be passed relnames with a trailing final slash - ignore this
 * and return the element before that final slash.
 */
LPSTR
dir_finalelem(LPSTR path)
{
        LPSTR chp;
        int size;

        /* is the final character a slash ? */
        size = lstrlen(path) - 1;
        if (*(chp = CharPrev(path, path+lstrlen(path))) == '\\') {
                /* find the slash before this */
                while (chp > path) {
                        if (*(chp = CharPrev(path, chp)) == '\\') {
                                /* skip the slash itself */
                                chp++;
                                break;
                        }
                }
                return(chp);
        }
        /* look for final slash */
        chp = strrchr(path, '\\');
        if (chp != NULL) {
                return(chp+1);
        }

        /* no slash - is there a drive letter ? */
        chp = strrchr(path, ':');
        if (chp != NULL) {
                return(chp+1);
        }

        /* this is a final-element anyway */
        return(path);

} /* dir_finalelem */

/***************************************************************************
 * Function: dir_getpathsize
 *
 * Purpose:
 *
 * Find the size of a file given a pathname to it
 */
long
dir_getpathsize(LPSTR path)
{
        int fh;
        OFSTRUCT os;
        long size;
        fh = OpenFile(path, &os, OF_READ|OF_SHARE_DENY_NONE);
        if (fh == -1) {
                return(0);
        }

        size = GetFileSize( (HANDLE) fh, NULL);
        _lclose(fh);
        return(size);
} /* dir_getpathsize */


SCANDIR.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.
\******************************************************************************/

/*
 * SCANDIR.H
 *
 */

/* Handle to the list of files scanned */
typedef struct dirlist FAR * DIRLIST;

/* Handle to one item within the list of files */
typedef struct diritem FAR * DIRITEM;

DIRLIST dir_buildlist(LPSTR pathname, BOOL bOnDemand);
void dir_delete(DIRLIST list);
BOOL dir_isfile(DIRLIST list);
DIRITEM dir_firstitem(DIRLIST list);
DIRITEM dir_nextitem(DIRLIST list, DIRITEM previtem, BOOL fDeep);

/* Filenames
 *
 * From a DIRITEM, you can query either the relative or the full name.
 *
 * The relative name does not include the tree root that was originally
 * passed to dir_buildlist. The full name does include this. Note however
 * that if you passed a relative name to dir_buildlist, the full
 * name you get back will not be an *absolute* pathname.
 *
 * Thus, if you call dir_buildlist with "c:\",
 * we will return:
 *      relative name:  ".\config.sys"
 *      full name:      "c:\config.sys"
 *
 * If you call dir_buildlist with ".\geraintd",
 * we will return:
 *      relative name:  ".\source\scandir.h"
 *      full name:      ".\geraintd\source\scandir.h"
 *
 * In both cases, we return a pointer to a filename string: you must
 * call dir_freefullname or dir_freerelname to free this memory when you
 * have finished with it. Depending on the implementation, one or other
 * (or possibly both) of these names will have been built specially
 * when you called the query function.
 *
 * You can also return a pointer to the tree root name. (In the above
 * examples this would be c:\ and .\geraintd). Depending on the implementation,
 * this may have been forced to an absolute path.
 *
 */

LPSTR dir_getfullname(DIRITEM item);
LPSTR dir_getrelname(DIRITEM item);
LPSTR dir_getroot_item(DIRITEM item);
LPSTR dir_getroot_list(DIRLIST dl);
void dir_freefullname(DIRITEM item, LPSTR fullname);
void dir_freerelname(DIRITEM item, LPSTR relname);
void dir_freeroot_item(DIRITEM item, LPSTR rootname);
void dir_freeroot_list(DIRLIST dl, LPSTR rootname);
LPSTR dir_getopenname(DIRITEM item);
void dir_freeopenname(DIRITEM item, LPSTR openname);
int dir_openfile(DIRITEM item);
void dir_closefile(DIRITEM item, int fh);
long dir_getfilesize(DIRITEM item);
BOOL dir_copy(DIRITEM item, LPSTR newroot);
BOOL dir_startcopy(DIRLIST dl);
int dir_endcopy(DIRLIST dl);


SECTION.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 Header *******************************
* Module Name: SECTION.C
*
* Manages sections of lines, and lists of sections.
*
* Functions:
*
* section_new()
* section_delete()
* section_match()
* section_getfirstline()
* section_getlastline()
* section_getlink()
* section_getcorrespond()
* section_getstate()
* section_setstate()
* section_getlinecount()
* section_getleftbasenr()
* section_setleftbasenr()
* section_getrightbasenr()
* section_setrightbasenr()
* FindEndOfUnmatched()
* NextNonIngnorable()
* FinEndOfMatched()
* section_makelist()
* section_deletelist()
* FindFirstWithLink()
* section_matchlists()
* section_takesection()
* section_makecomposite()
* AbsorbAnyBlanks()
* section_makectree()
* section_expandanchor()
*
* Comments:
*
* A section is a data type that represents a contiguous block of lines
* of the same state (all unmatched, or all matched to a contiguous block of
* lines). A section can link up matching lines within the section.
*
* Section list functions can make and match lists of sections from lists of
* lines, and create a composite list by combining sections from two lists
* to create a list that 'best represents' the similarities and differences
* between the two lists of lines.
*
* Assumptions: the lines passed in are on a list (can be traversed with
* List_Next() etc. Line numbering using the section_get*basenr()
* functions work only if lines are numbered sequentially in ascending order.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <string.h>

#include "gutils.h"
#include "tree.h"
#include "state.h"
#include "windiff.h"
#include "wdiffrc.h"
#include "list.h"
#include "line.h"
#include "section.h"

/*
 * a section handle (SECTION) is a pointer to one of these structures
 */
struct section {
        LINE first;             /* first line in section */
        LINE last;              /* last line in section */

        BOOL bDiscard;          /* true if not alloc-ed on list */

        SECTION link;           /* we match this section */
        SECTION correspond;     /* we correspond to this section, but
                                 * don't match it
                                 */

        int state;              /* compare state for section */

        int leftbase;           /* nr in original left list of first line*/
        int rightbase;          /* nr in original right list of first line*/
};

/* --- function prototypes ------------------------------------------*/

TREE section_makectree(SECTION sec);
BOOL section_expandanchor(SECTION sec1, LINE line1, SECTION sec2, LINE line2);

/***************************************************************************
 * Function: section_new
 *
 * Purpose:
 *
 * Makes a new section, given handles to a first and last line.
 *
 * A section must be at least one line long. The lines passed in must be
 * on a list in order.
 *
 * If the list parameter is non-null, we will allocate the section struct
 * on the list. otherwise we will alloc it from gmem_get(hHeap). We remember
 * this in the bDiscard flag for section_delete, so that we only
 * hand back to gmem_free memory that we got.
 **************************************************************************/
SECTION
section_new(LINE first, LINE last, LIST list)
{
        SECTION sec;

        /* alloc the sec and remember where we alloc-ed it */
        if (list) {
                sec = (SECTION) List_NewLast(list, sizeof(struct section));
                sec->bDiscard = TRUE;
        } else {
                sec = (SECTION) gmem_get(hHeap, sizeof(struct section));
                sec->bDiscard = FALSE;
        }

        sec->first = first;
        sec->last = last;
        sec->link = NULL;
        sec->correspond = NULL;
        sec->state = 0;
        sec->leftbase = 1;
        sec->rightbase = 1;

        return(sec);
}

/***************************************************************************
 * Function: section_delete
 *
 * Purpose:
 *
 * Discard a section. Free all associated memory (not the line list).
 * Free up the section itself if it was not alloc-ed on a list.
 */
void
section_delete(SECTION section)
{
        if (section->bDiscard) {
                gmem_free(hHeap, (LPSTR) section, sizeof(struct section));
        }
}

/***************************************************************************
 * Function: section_match
 *
 * Purpose:
 *
 * Match up two sections: match all lines that
 * are unique and identical between the two sections.
 *
 * We use a tree of line handles, keyed by the line hash code. We use a
 * ctree, which keeps a count for multiple identical keys. This allows
 * us to rapidly find lines that are unique within this section.
 * We build two of these trees (one for each line list). For each line
 * that is unique in both trees, we attempt to link the lines.
 *
 * We also attempt to link the first and last line of the section.
 *
 * For each line we successfully link, we spread up and down from
 * this anchor point attempting to link lines.
 *
 * We return true if we linked any lines
 *
 * This routine may be called more than once on the same list of lines.
 * In matching lines we want to find unique, *unmatched* lines: so we only
 * insert lines into the ctree if they are currently unlinked.
 */
BOOL
section_match(SECTION sec1, SECTION sec2)
{
        TREE ctleft, ctright;
        LINE line, line2;
        BOOL bLinked = FALSE;

        if ((sec1 == NULL) || (sec2 == NULL)) {
                return(FALSE);
        }

        if ((sec1->first == NULL) || (sec2->first == NULL)) {
                return(FALSE);
        }
        /* ASSERT if first is non-null, so is last */

        /* attempt to link the first line of each file, and
         * if matched, expand as long as we keep matching
         */
        bLinked |= section_expandanchor(sec1, sec1->first, sec2, sec2->first);

        /* attempt to link the last lines of each file and
         * expand upwards
         */
        bLinked |= section_expandanchor(sec1, sec1->last, sec2, sec2->last);

        /* build a tree of lines, indexed by the line hashcode.
         * a ctree will hold only the first value of any given key, but
         * it will keep track of the number of items inserted on this key.
         * thus we can keep count of the number of times this line
         * (or at least this hashcode) appears.
         */
        ctleft = section_makectree(sec1);
        ctright = section_makectree(sec2);

        /* for each unlinked line in one list (doesnt matter which), find if
         * appears once only in each list. if so, link, and expand
         * the link to link lines before and after the matching line
         * as long as they continue to match.
         */
        for (line = sec1->first; line != NULL; line = List_Next(line)) {

                if ((line_getlink(line) == NULL) &&
                   (ctree_getcount(ctleft, line_gethashcode(line)) == 1) &&
                   (ctree_getcount(ctright, line_gethashcode(line)) == 1)) {

                        /* line appears exactly once in each list */
                        line2 = * ((LINE FAR *)ctree_find(ctright,
                                        line_gethashcode(line)));
                        bLinked |= section_expandanchor(sec1, line, sec2, line2);
                }

                if (line == sec1->last) {
                        break;
                }
        }

        /* delete the ctrees */
        ctree_delete(ctleft);
        ctree_delete(ctright);

        return(bLinked);
} /* section_match */

/***************************************************************************
 * Function: section_getfirstline
 *
 * Purpose:
 *
 * Gets a handle to the first line in this section
 */
LINE
section_getfirstline(SECTION section)
{
        if (section == NULL) {
                return(NULL);
        }
        return(section->first);
}

/***************************************************************************
 * Function: section_getlastline
 *
 * Purpose:
 *
 * Returns a handle to the last line in a section
 */
LINE
section_getlastline(SECTION section)
{
        if (section == NULL) {
                return(NULL);
        }
        return(section->last);
}

/***************************************************************************
 * Function: section_getlink
 *
 * Purpose:
 *
 * Returns a handle to the linked section, if any. A linked section
 * is a section whose lines all match the lines in this section
 */
SECTION
section_getlink(SECTION section)
{
        if (section == NULL) {
                return NULL;
        }
        return(section->link);
}

/***************************************************************************
 * Function: section_getcorrespond
 *
 * Purpose:
 *
 * Returns a handle to the corresponding section (a section which
 * corresponds in position to this one, but whose lines do not match).
 */
SECTION
section_getcorrespond(SECTION section)
{
        if (section == NULL) {
                return(NULL);
        }
        return(section->correspond);
}
/***************************************************************************
 * Function: section_getstate
 *
 * Purpose:
 *
 * Gets the state for this section */
int
section_getstate(SECTION section)
{
        if (section == NULL)
                return(0);
        return(section->state);
}

/***************************************************************************
 * Function: section_setstate
 *
 * Purpose:
 *
 * Sets the state for this section */
void
section_setstate(SECTION section, int state)
{
        section->state = state;
}

/***************************************************************************
 * Function: section_getlinecount
 *
 * Purpose:
 *
 * Returns the number of lines in the section. Here we assume that
 * lines in the section are number sequentially in ascending order, and we
 * simply look at the first and last line numbers.
 */
int
section_getlinecount(SECTION section)
{
        return(line_getlinenr(section->last) -
                        line_getlinenr(section->first)) + 1;
}

/*
 * -- base line numbers --
 *
 * These functions only apply to sections in the composite list. When creating
 * a composite section, we record the line number of the first line in each
 * of the two sections we built it from. Thus we can calculate the
 * line number of any line in the section in either file it appeared in,
 * by adding the index of the line within the section to the base line
 * number.
 */
int
section_getleftbasenr(SECTION section)
{
        return(section->leftbase);
}

void
section_setleftbasenr(SECTION section, int base)
{
        section->leftbase = base;
}

int
section_getrightbasenr(SECTION section)
{
        return(section->rightbase);
}

void
section_setrightbasenr(SECTION section, int base)
{
        section->rightbase = base;
}

/* --- section list functions -------------------------------------*/

/* Theory of handling blank lines:
|
|  If ignore_blanks is FALSE then a blank is just another character.
|  If it is TRUE then we will normally include unmatched blanks in whatever
|  section is surrounding them.  It would be nice if we could arrange to
|  never have a section that is only unmatched blanks, but (at least at
|  the start of the file) it can happen.
|
|  Note that there are two DIFFERENT blank handling techniques:
|  In the first phase of the comparison when we are just trying to match up
|  lines, we skip over blank lines both forwards and backwards from an anchor.
|  When we are making real sections for display we only go forwards.
|  This results in a possible anomaly at the top of the whole file where
|  there could be some blanks which do not match and which can only possibly
|  be described as the start of a section.
|  For this reason, we label the sections with their state as early as possible
|  and go by that rather than by the presence or absence of link fields.
|  (It takes some scanning to find a link.  The first line in the section
|  could be a blank).
*/

/***************************************************************************
 * Function: FindEndOfUnmatched
 *
 * Purpose:
 *
 * Returns a LINE which is the last line in an unmatched section
 * containing (probably starting with) Line.
 * Note that it does not necessarily make progress.
 *
 * As noted above, even if blank lines are being ignored, we don't
 * mind tagging them onto the end of an already unmatching section.
 * This means we carry on until we find the first real link
 */
LINE FindEndOfUnmatched(LINE line)
{
        LINE next;

        for (; ; )
        {       next = List_Next(line);
                if (next==NULL) return line;
                if (line_getlink(next)!=NULL) return line;
                line = next;
        }
} /* FindEndOfUnmatched */

/***************************************************************************
 * Function: NextNonIgnorable
 *
 * Purpose:
 *
 * An ignorable line is a blank line with no link and ignore_blanks set
 *
 * Given that line is initially not NULL and not ignorable:
 * If line is the last line in the list then return NULL
 * Else If ignore_blanks is FALSE then return the next line after line
 * else return next line which has a link or which is non-blank.
 * If there is no such line then return the last line in the list.
 *
 * Note that this does always make progress (at the cost of
 * sometimes returning NULL).
 */
LINE NextNonIgnorable(LINE line)
{       LINE next;

        next = List_Next(line);
        if (next==NULL) return NULL;
        for (; ; ) {
                line = next;
                if (  line_getlink(line)!=NULL) return line;
                if (! ignore_blanks)            return line;
                if (! line_isblank(line))       return line;
                next = List_Next(line);
                if (next==NULL) return line;
        }
} /* NextNonIgnorable */

/***************************************************************************
 * Function: FindEndOfMatched
 *
 * Purpose:
 *
 * Given that line is either linked or an ignorable blank:
 * Return a LINE which is the last line in a matched section
 * containing (probably starting with) line.
 * This could mean returning the line we were given.
 *
 * If the lines linked to are not consecutive then the section ends.
 * If blanks are being ignored, then any blank line is deemed
 * to match (even if it doesn't match).  In this case we need the
 * links of the lines before and after the blanks to be consecutive
 * in order to carry on.  There could be blank lines on either or both
 * ends of the links.
 */
LINE FindEndOfMatched(LINE line)
{
        LINE next;              /* next non-ignored or linked line */
        LINE nextlink;          /* next in other file */

        /* The basic algorithm is to set up next and nextlink to point to
           candidate lines.  Examine them.  If they are good then step
           on to them, else return the line one before.
           There are confusion factors associated with the beginning and
           end of the file.
        */

        /* ASSERT( line is either an ignorable blank or else is linked) */

        /* As a section (at least at the start of the file) might start
           with an ignored non-linked blank line, first step over any such
        */
        if( line_getlink(line)==NULL && line_isblank(line) ) {
                next = NextNonIgnorable(line);

                /* There are unfortunately 6 cases to deal with
                   * marks where next will be. * against eof means next==NULL
                   blank(s) refer to ignorable unlinked blanks.
                          A         B        C        D        E        F
                   line-> xxxxx     xxxxx    xxxxx    xxxxx    xxxxx    xxxxx
                         *unlinked  blanks  *linked   blanks  *eof     *blanks
                                   *unlinked         *linked            eof

                   next could be:

                      null - case E => return line
                      unlinked ignorable blank - case F => return that blank line
                      unlinked other - cases A,B return prev(that unlinked line)
                      linked - cases C,D continue from that linked line
                */
                if (next==NULL) return line;
                if (line_getlink(next)==NULL) {
                        if (ignore_blanks && line_isblank(next)) {
                                return next;
                        }
                        return List_Prev(next);
                }

                line = next;
        }

        /* we have stepped over inital blanks and now do have a link */

        for ( ; ; ) {

                next = NextNonIgnorable(line);
                /* Same 6 cases - basically same again */
                if (next==NULL) return line;
                if (line_getlink(next)==NULL) {
                        if (ignore_blanks && line_isblank(next)) {
                                return next;
                        }
                        return List_Prev(next);
                }

                nextlink = NextNonIgnorable(line_getlink(line));

                /* WEAK LOOP INVARIANT
                   line is linked.
                   next is the next non-ignorable line in this list after line.
                   nextlink is the next non-ignorable line after link(line)
                                        in the other list (could be NULL etc).
                */
                if (line_getlink(next) != nextlink) return List_Prev(next);

                line = next;
        }
        return line;
} /* FindEndOfMatched */

/***************************************************************************
 * Function: section_makelist
 *
 * Purpose:
 *
 * Make a list of sections by traversing a list of lines. Consecutive
 * linked lines that are linked to consecutive lines are put in a single
 * section. Blocks of unlinked lines are placed in a section.
 * If ignore_blanks is set then we first try to link them as normal.
 * but if they won't link then we just skip over them and keep them
 * in the same section.
 *
 * Left must be set TRUE iff the list of lines is a left hand section.
 * Returns a handle to a list of sections
 */
LIST
section_makelist(LIST linelist, BOOL left)
{
        LINE line1, line2;
        LIST sections;
        BOOL matched;
        SECTION sect;

        /* make an empty list of sections */
        sections = List_Create();

        /* for each line in the list */

        List_TRAVERSE(linelist, line1) {

                /* is it linked ? */

                if( line_getlink(line1) != NULL
                  || ( ignore_blanks && line_isblank(line1))
                  ) {
                        line2 = FindEndOfMatched(line1);
                        matched = TRUE;
                } else {
                        line2 = FindEndOfUnmatched(line1);
                        matched = FALSE;
                }

                /* create the section and add to list */
                sect = section_new(line1, line2, sections);
                sect->state = (matched ? STATE_SAME
                                       : left ? STATE_LEFTONLY
                                              : STATE_RIGHTONLY
                              );

                /* advance to end of section (no-op if 1 line section) */
                line1 = line2;
        }

        return(sections);
} /* section_makelist */

/***************************************************************************
 * Function: section_deletelist
 *
 * Purpose:
 *
 * Delete a list of sections
 *
 * Sections have no dangling pointers, so all we do is delete the list
 */
void
section_deletelist(LIST sections)
{
        List_Destroy(&sections);
}

/***************************************************************************
 * Function: FindFirstWithLink
 *
 * Purpose:
 *
 * Return the first line in the range first..last
 * which has a link.  Return last if none of them have a link.
 * List_Next must lead from first to last eventually.
 * It is legit for last to be NULL.
 */
LINE FindFirstWithLink(LINE first, LINE last)
{
        /* The strategy of including blanks on the ENDS of sections rather
           than the start of new sections will mean that this function
           usually strikes gold immediately.  A file with a leading
           blank section is its raison d'etre.
        */
        while (line_getlink(first)==NULL && first!=last)
                first = List_Next(first);

        if (line_getlink(first)==NULL) {
        }
        return first;
} /* FindFirstWithLink */

/***************************************************************************
 * Function: section_matchlists
 *
 * Purpose:
 *
 * Match up two lists of sections. Establish links between sections
 * that match, and establish 'correspondence' between sections that
 * are in the same place, but don't match.
 *
 * For each pair of corresponding sections, we also call section_match
 * to try and link up more lines.
 *
 * We return TRUE if we made any more links between lines, or false
 * otherwise.
 *
 */
BOOL
section_matchlists(LIST secsleft, LIST secsright)
{
        BOOL bLinked = FALSE;
        SECTION sec1, sec2;

        /* match up linked sections - We know whether a section is
           supposed to link from its state, but we don't know what section
           it links to.  Also we can have sections which are defined to
           be matching but actually contain nothing but ignorable
           blank lines
        */

        /*  for each linked section try to find the section  linked to it. */
        List_TRAVERSE(secsleft, sec1) {
                if (sec1->state==STATE_SAME) {
                        LINE FirstWithLink = FindFirstWithLink(sec1->first, sec1->last);
                        List_TRAVERSE(secsright, sec2) {
                                if ( sec2->state==STATE_SAME
                                   && line_getlink(FirstWithLink)
                                        == FindFirstWithLink(sec2->first, sec2->last)) {
                                            break;
                                }
                        }
                        /* sec2 could be NULL if sec1 is all allowable blanks */
                        if (sec2!=NULL) {
                                sec1->link = sec2;
                                sec2->link = sec1;
                        }
                }
        }

        /* go through all unmatched sections. Note that we need to complete
         * the link-up of matching sections before this, since we need
         * all the links in place for this to work.
         */

        List_TRAVERSE(secsleft, sec1) {
                SECTION secTemp;

                if (sec1->state == STATE_SAME) {
                        /* skip the linked sections */
                        continue;
                }

                /* check that the previous and next sections, if
                 * they exist, are linked. this should not fail since
                 * two consecutive unlinked sections should be made into
                 * one section
                 */
                secTemp = List_Prev(sec1);
                if (secTemp && secTemp->state!= STATE_SAME) {
                        continue;
                }
                secTemp = List_Next(sec1);
                if (secTemp && secTemp->state!= STATE_SAME) {
                        continue;
                }

                /* find the section that corresponds to this - that is, the
                 * section following the section linked to our previous section.
                 * we could be at beginning or end of list.
                 */
                if (List_Prev(sec1) != NULL) {
                        SECTION secOther;
                        secOther = section_getlink(List_Prev(sec1));
                        if (secOther==NULL)
                                continue;

                        sec2 = List_Next(secOther);

                        /* check this section is not linked */
                        if ((sec2 == NULL) || (section_getlink(sec2) != NULL)) {
                                continue;
                        }

                        /* check that the section after these are linked
                         * to each other (or both are at end of list).
                         */
                        if (List_Next(sec1) != NULL) {

                                if (section_getlink(List_Next(sec1)) !=
                                    List_Next(sec2)) {
                                        continue;
                                }
                        } else {
                                if (List_Next(sec2) == NULL) {
                                        continue;
                                }
                        }

                } else if (List_Next(sec1) != NULL) {
                        SECTION secOther;
                        secOther = section_getlink(List_Next(sec1));
                        if (secOther==NULL)
                                continue;

                        sec2 = List_Prev(secOther);

                        /* check this section is not linked */
                        if ((sec2 == NULL) || (section_getlink(sec2) != NULL)) {
                                continue;
                        }

                        /* check that the section before these are linked
                         * to each other (or both are at start of list).
                         */
                        if (List_Prev(sec1) != NULL) {

                                if (section_getlink(List_Prev(sec1)) !=
                                    List_Prev(sec2)) {
                                        continue;
                                }
                        } else {
                                if (List_Prev(sec2) == NULL) {
                                        continue;
                                }
                        }
                } else {
                        /* there must be at most one section in each
                         * file, and they are unmatched. make these correspond.
                         */
                        sec2 = List_First(secsright);
                }

                /* make the correspondence links
                 */
                if ((sec1 != NULL) && (sec2 != NULL)) {
                        sec1->correspond = sec2;
                        sec2->correspond = sec1;
                }

                /* attempt to link up lines */
                if (section_match(sec1, sec2)) {
                        bLinked = TRUE;
                }
        }

        return(bLinked);
} /* section_matchlists */

/***************************************************************************
 * Function: section_takesection
 *
 * Purpose:
 *
 * Add a section to the composite list. Called from make_composites
 * to copy a section, add it to the composite list and set the state,
 * leftbase and rightbase.   Note that the state could be STATE_SAME
 * with a NULL section on the left.  May NOT call with STATE_SAME and
 * a NULL right section!
 *
 */
void
section_takesection(LIST compo, SECTION left, SECTION right, int state)
{
        SECTION newsec;
        SECTION sec;

        /* select which section is being output, and change the state
         * to indicate it has been output
         */
        switch(state) {
        case STATE_SAME:
                /* both the same. we mark both as output, and
                 * take the right one.  It is possible that the
                 * left one could be NULL (an ignorable blank section)

*/
                if (left!=NULL) left->state = STATE_MARKED;
                right->state = STATE_MARKED;
                sec = right;
                break;

        case STATE_LEFTONLY:
        case STATE_MOVEDLEFT:
                sec = left;
                left->state = STATE_MARKED;
                break;

        case STATE_RIGHTONLY:
        case STATE_MOVEDRIGHT:
                sec = right;
                right->state = STATE_MARKED;
                break;
        }

        /* create a new section on the list */
        newsec = section_new(sec->first, sec->last, compo);

        newsec->state = state;

        if (left != NULL) {
                newsec->leftbase = line_getlinenr(left->first);
        } else {
                newsec->leftbase = 0;
        }

        if (right != NULL) {
                newsec->rightbase = line_getlinenr(right->first);
        } else {
                newsec->rightbase = 0;
        }

} /* section_takesection */

/***************************************************************************
 * Function: section_makecomposite
 *
 * Purpose:
 *
 * Make a composite list of sections by traversing a list of sections.
 *
 * Return a handle to a list of sections.
 *
 * During this, set state, leftbase and rightbase for sections.
 *
 * Comments:
 *
 * This function creates a list that corresponds to the 'best' view
 * of the differences between the two lists. We place sections from the
 * two lists into one composite list. Sections that match each other are only
 * inserted once (from the right list). Sections that match, but in different
 * positions in the two lists are inserted twice, once in each position, with
 * status to indicate this. Unmatched sections are inserted in the correct
 * position.
 *
 * - Take sections from the left list until the section is linked to one not
 *   already taken.
 * - Then take sections from right until we find a section linked to one not
 *   already taken.
 * - If the two sections waiting are linked to each other, take them both
 *   (once- we take the right one and advance past both).
 *
 * - Now we have to decide which to take in place and which to declare
 *   'moved'. Consider the case where the only change is that the first line
 *   has been moved to the end. We should take the first line (as a move),
 *   then the bulk of the file (SAME) then the last line (as a move). Hence,
 *   in difficult cases, we take the smaller section first, to ensure that
 *   the larger section is taken as SAME.
 *
 *   To indicate which section has been output, we set the state field
 *   to STATE_MARKED once we have taken it.   States in left and right
 *   lists are of no further interest once we have built the composite.
 *
 *   Up to this point we have worked off the STATE of a section.  By now
 *   all the section links are in place, so we can use them too.
 */
LIST
section_makecomposite(LIST secsleft, LIST secsright)
{
        SECTION left, right;
        LIST compo;

        /* make an empty list for the composite */
        compo = List_Create();

        left = List_First(secsleft);
        right = List_First(secsright);

        while ( (left != NULL) || (right != NULL)) {

                if (left == NULL) {
                        /* no more in left list - take right section */
                        /* is it moved or just unmatched ? */
                        if (right->link == NULL) {
                                section_takesection(compo, NULL, right, STATE_RIGHTONLY);
                                right = List_Next(right);
                        } else {
                                section_takesection(compo, right->link, right, STATE_MOVEDRIGHT);
                                right = List_Next(right);
                        }
                } else if (right == NULL) {
                        /* right list empty - must be left next */

                        /* is it moved or just unmatched ? */
                        if (left->link == NULL) {
                                section_takesection(compo, left, NULL, STATE_LEFTONLY);
                                left = List_Next(left);
                        } else {
                                section_takesection(compo, left, left->link, STATE_MOVEDLEFT);
                                left = List_Next(left);
                        }

                } else if (left->state == STATE_LEFTONLY) {
                        /* unlinked section on left */
                        section_takesection(compo, left, NULL, STATE_LEFTONLY);
                        left = List_Next(left);

                } else if (left->link==NULL) {
                        /* This is an ignorable blank section on the left.
                         * We ignore it. (We will take any such from the right)
                         */
                        left = List_Next(left);

                } else if (left->link->state==STATE_MARKED) {
                        /* left is linked to section that is already taken*/
                        section_takesection(compo, left, left->link, STATE_MOVEDLEFT);
                        left = List_Next(left);

                } else  if (right->link == NULL) {
                        /* take unlinked section on right
                         * Either unmatched or ignorable blanks
                         */
                        section_takesection(compo, NULL, right, right->state);
                        right = List_Next(right);

                } else if (right->link->state==STATE_MARKED) {
                        /* right is linked to section that's already taken */
                        section_takesection(compo, right->link, right, STATE_MOVEDRIGHT);
                        right = List_Next(right);

                } else if (left->link == right) {
                        /* sections match */
                        section_takesection(compo, left, right, STATE_SAME);
                        right = List_Next(right);
                        left = List_Next(left);
                } else {
                        /* both sections linked to forward sections
                         * decide first based on size of sections
                         * - smallest first as a move so that largest
                         * is an unchanged.
                         */
                        if (section_getlinecount(right) > section_getlinecount(left)) {
                                section_takesection(compo, left, left->link, STATE_MOVEDLEFT);
                                left = List_Next(left);
                        } else {
                                section_takesection(compo, right->link, right, STATE_MOVEDRIGHT);
                                right = List_Next(right);
                        }
                }
        }

        return(compo);
} /* section_makecomposite */

typedef LINE (APIENTRY * MOVEPROC)(LINE);

/***************************************************************************
 * Function: AbsorbAnyBlanks
 *
 * Purpose:
 *
 * Update PLINE by making it point to the first non-blank
 * at-or-after from but not after limit.
 * If they are all blank then make it point to limit
 * If from is non-blank then leave it alone.
 * Return TRUE iff PLINE was updated.
 * It is legit for limit to be NULL (meaning end of file).
 */
BOOL AbsorbAnyBlanks(LINE * from, LINE limit, MOVEPROC Move)
{       BOOL progress = FALSE;

        while ( (from!=NULL)
              && (line_isblank(*from))
              && (*from!=limit)
              ) {
                *from = Move(*from);
                progress = TRUE;
        }
        return progress;
} /* AbsorbAnyBlanks */

/***************************************************************************
 * Function: section_expandanchor
 *
 * Purpose:
 *
 * Given an anchor point (two lines that we think should match),
 * try to link them, and the lines above and below them for as long
 * as the lines can be linked (are the same, are unlinked).
 *
 * Return TRUE if we make any links.
 *
 */
BOOL
section_expandanchor(SECTION sec1, LINE line1, SECTION sec2, LINE line2)
{
        /* when a line is matched we set bChanges.  If we notice some
         * blank lines, but do NOT link any new non-blank lines, we
         * do NOT set bChanges.  (If we did it would cause a closed
         * loop as they would get noticed again next time.  line_link
         * only returns TRUE if it is a NEW link).
         * At this stage we are only interested in making links, not in
         * the size of the section that results (that fun comes later).
         * therefore trailing blanks at the end of a section are not
         * interesting and we don't look for them.
         */
        BOOL bChanges = FALSE;
        LINE left, right;

        /* We handle the section limits by using a sentinel which is one
         * past the end of the section.  (If the section ends at the end
         * of the list then the sentinel is NULL).
         */
        LINE leftend, rightend;
        leftend = List_Next(sec1->last);
        rightend = List_Next(sec2->last);

        /* null lines shall not match */
        if ((line1 == NULL) || (line2 == NULL)) {
                return(FALSE);
        }

        /* check all lines forward until fail to link (because null,
         * not matching, or already linked).
         * include the passed in anchor point since this has not
         * yet been linked.
         * If blanks are ignorable then skip over any number of whole
         * blank lines.
         */
        left = line1;
        right = line2;
        for (; ; ) {
                if (line_link(left, right) ) {

                        bChanges = TRUE;
                        left = List_Next(left);
                        right = List_Next(right);
                        if (left==leftend || right==rightend) break;
                }
                else if (ignore_blanks){
                        /* even though no match, maybe an ignorable blank? */

                        BOOL moved = FALSE;
                        moved |= AbsorbAnyBlanks(&left, leftend, (MOVEPROC)List_Next);
                        moved |= AbsorbAnyBlanks(&right, rightend, (MOVEPROC)List_Next);
                        if (!moved) break; /* it didn't match and we didn't move on */
                        if (left==leftend || right==rightend) break;
                }
                else break;
        }

        /* check all matches going backwards from anchor point
           but only if it was a real anchor  (could have been
           end-of-section/end-of-file and non-matching).
        */
        if (line_getlink(line1)==NULL) return bChanges;

        left = List_Prev(line1);
        right = List_Prev(line2);
        if (left==NULL || right==NULL) return bChanges;

        leftend = List_Prev(sec1->first);
        rightend = List_Prev(sec2->first);

        for (; ; ) {
                if (line_link(left, right)) {

                        bChanges = TRUE;
                        left = List_Prev(left);
                        right = List_Prev(right);
                        if (left == leftend || right == rightend) break;

                }
                else if (ignore_blanks){
                        /* even though no match, maybe an ignorable blank? */

                        BOOL moved = FALSE;
                        moved |= AbsorbAnyBlanks(&left, leftend, (MOVEPROC)List_Prev);
                        moved |= AbsorbAnyBlanks(&right, rightend, (MOVEPROC)List_Prev);
                        if (!moved) break; /* it didn't match and we didn't move on */
                        if (left==leftend || right==rightend) break;

                }
                else break;
        }

        return(bChanges);
}

/***************************************************************************
 * Function: section_makectree
 *
 * Purpose:
 *
 * Build a ctree from the lines in the section given
 *
 * Remember that we are only interested in the lines that are
 * not already linked.
 *
 * The value we store in the tree is the handle of the line. the key
 * is the line hash code
 */
TREE
section_makectree(SECTION sec)
{
        TREE tree;
        LINE line;

        /* make an empty tree */
        tree = ctree_create(hHeap);

        for (line = sec->first; line != NULL; line = List_Next(line)) {
                if (line_getlink(line) == NULL) {
                        ctree_update(tree, line_gethashcode(line),
                                        &line, sizeof(LINE));
                }
                if (line == sec->last) {
                        break;
                }
        }
        return(tree);
}


SECTION.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.
\******************************************************************************/

/*
 * SECTION.H
 */

/* handle to a section */
typedef struct section FAR * SECTION;

SECTION section_new(LINE first, LINE last, LIST list);
void section_delete(SECTION section);
BOOL section_match(SECTION section1, SECTION section2);
LINE section_getfirstline(SECTION section);
LINE section_getlastline(SECTION section);
SECTION section_getlink(SECTION section);
SECTION section_getcorrespond(SECTION section);
void section_setstate(SECTION section, int state);
int section_getstate(SECTION section);
int section_getlinecount(SECTION section);
int section_getleftbasenr(SECTION section);
int section_getrightbasenr(SECTION section);
LIST section_makelist(LIST linelist, BOOL left);
void section_deletelist(LIST sections);
LIST section_makecomposite(LIST secsleft, LIST secsright);
BOOL section_matchlists(LIST secsleft, LIST secsright);


STATE.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.
\******************************************************************************/

/*
 * STATE.H
 *
 * Definition of the results of comparisons for files and for lines
 * within files.
 *
 * These need to be globally declared so that the UI code in windiff.c can
 * map states to the colour scheme (to correctly highlight changed lines).
 *
 * They apply to files (compitem_getstate() ) and to sections in the
 * composite list (section_getstate). All lines within a section have the
 * same state. The UI code will use the view_getstate() function to find the
 * state for a given line on the screen.
 *
 */

/* Applies to both lines or files: they are the same */
#define STATE_SAME              1

/* Applies to files.  Same size, date, time */
#define STATE_COMPARABLE        2

/* Applies to files.  Different, but only in blanks
 * This state only turns up after the file has been expanded.
 */
#define STATE_SIMILAR           3

/* Applies only to files */

/* - Files differ (and can be expanded) */
#define STATE_DIFFER            4

/* They are only in the left or right tree */
#define STATE_FILELEFTONLY      5
#define STATE_FILERIGHTONLY     6

/* Applies to lines only */

/* the line only exists in one of the lists */
#define STATE_LEFTONLY          7       /* line only in left file */
#define STATE_RIGHTONLY         8       /* line only in right file */

/* The line is the same in both files, but in
 * different places (thus the line will appear twice in the composite list,
 * once with each of these two states
 */
#define STATE_MOVEDLEFT         9       /* this is the left file version */
#define STATE_MOVEDRIGHT        10      /* this is the right file version*/

/* In processing the sections to build the composite list, we need to
 * track which sections have been processed.  After this the left and
 * right lists of sections are of no further interest
 */
#define STATE_MARKED            99


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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: STATUS.C
*
* Status line handler.
*
* Functions:
*
* StatusInit()
* StatusCreate()
* StatusHeight()
* StatusAlloc()
* StatusAddItem()
* StatusCreateTools()
* StatusDeleteTools()
* StatusWndProc()
* StatusResize()
* StatusCalcHeight()
* StatusCalcWidth()
* StatusGetItem()
* LowerRect()
* RaiseRect()
* StatusPaint()
* BottomRight()
* TopLeft()
* StatusButtonDown()
* StatusButtonUp()
* InitDC()
*
* Comments:
*
****************************************************************************/

#include <windows.h>
#include <string.h>

#include "gutils.h"

/* --- data structures ------------------------------------------------- */
#define SF_MAXLABEL     80   /* no more than 80 in an item within the bar */
                             /* Is this adequate for long pathnames on a
                                hi-res screen?
                             */

typedef struct statel {
        int type;                       /* SF_BUTTON or SF_STATIC */
        int flags;                      /* SF_VAR => variable width
                                           SF_LEFT=> left aligned (else right)
                                           SF_RAISE=> paint as 'raised' 3d rect
                                           SF_LOWER=> paint as lowered 3D rect
                                           SF_SZMIN=>together with SF_VAR
                                                     allows minimum size for
                                                     var sized item
                                           SF_SZMAX=>see SZMIN and use nouse
                                        */
        int id;                         /* control id */
        int width;                      /* width of control in chars */
        char text[SF_MAXLABEL+1];       /* null-term string for label */

        RECT rc;                        /* used by status.c */
} STATEL, FAR * PSTATEL;

typedef struct itemlist {
        int nitems;
        PSTATEL statels;

        int selitem;                    /* used by status.c */
        BOOL isselected;                /* used by status.c */
} ILIST, FAR * PILIST;

/* prototypes of routines in this module */

void StatusCreateTools(void);
void StatusDeleteTools(void);
long APIENTRY StatusWndProc(HWND, UINT, UINT, LONG);
void StatusResize(HWND hWnd, PILIST pilist);
int StatusCalcHeight(HWND hWnd, PSTATEL ip);
int StatusCalcWidth(HWND hWnd, PSTATEL ip);
PSTATEL StatusGetItem(PILIST plist, int id);
void LowerRect(HDC hDC, LPRECT rcp);
void RaiseRect(HDC hDC, LPRECT rcp);
void StatusPaint(HWND hWnd, PILIST iplistp);
void BottomRight(HDC hDC, LPRECT rcp, HPEN hpen, BOOL bCorners);
void TopLeft(HDC hDC, LPRECT rcp, HPEN hpen, BOOL bCorners);
void StatusButtonDown(HDC hDC, PSTATEL ip);
void StatusButtonUp(HDC hDC, PSTATEL ip);
void InitDC(HDC hdc);

/*--global data---------------------------------------------------------*/

HPEN hpenHilight, hpenLowlight;
HPEN hpenBlack, hpenNeutral;
HBRUSH hbrBackground; /* pieces and board */
HFONT hFont;
int status_charheight, status_charwidth;

/* default pt size for font (tenths of a pt) */
#define         DEF_PTSIZE      80

/***************************************************************************
 * Function: StatusInit
 *
 * Purpose:
 *
 * Create window class
 */
BOOL
StatusInit(HANDLE hInstance)
{
        WNDCLASS    wc;
        BOOL resp;
        TEXTMETRIC tm;
        HDC hDC;

        StatusCreateTools();

        wc.style = CS_HREDRAW|CS_VREDRAW|CS_GLOBALCLASS;
        wc.lpfnWndProc = (WNDPROC) StatusWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = sizeof(HANDLE);
        wc.hInstance = hInstance;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = hbrBackground;
        wc.lpszClassName = (LPSTR) "gdstatusclass";
        wc.lpszMenuName = NULL;

        resp = RegisterClass(&wc);

        hDC = GetDC(NULL);
        InitDC(hDC);
        GetTextMetrics(hDC, &tm);
        status_charheight = (int)(tm.tmHeight + tm.tmExternalLeading);
        status_charwidth = (int)tm.tmAveCharWidth;
        ReleaseDC(NULL, hDC);

        return(resp);
}

/*
/***************************************************************************
 * Function: StatusCreate
 *
 * Purpose:
 *
 * Create and show the window
 */
HWND APIENTRY
StatusCreate(HANDLE hInst, HWND hParent, int id, LPRECT rcp, HANDLE hmem)
{

        HWND hWnd;

        /* create a child window of status class */

        hWnd = CreateWindow("gdstatusclass",
                        NULL,
                        WS_CHILD | WS_VISIBLE,
                        rcp->left,
                        rcp->top,
                        (rcp->right - rcp->left),
                        (rcp->bottom - rcp->top),
                        hParent,
                        (HANDLE) id,
                        hInst,
                        (LPVOID) hmem);

        return(hWnd);
}

/***************************************************************************
 * Function: StatusHeight
 *
 * Purpose:
 *
 * Return default height of this window
 */
int APIENTRY
StatusHeight(HANDLE hmem)
/* The window has a number of items which are arranged horizontally,
   so the window height is the maximum of the individual heights
*/
{
        PILIST plist;
        int i;
        int sz;
        int maxsize = 0;

        plist = (PILIST) GlobalLock(hmem);
        if (plist != NULL) {
                for (i = 0; i<plist->nitems; i++) {
                        sz = StatusCalcHeight(NULL, &plist->statels[i]);
                        maxsize = max(sz, maxsize);
                }
        }
        GlobalUnlock(hmem);
        if (maxsize > 0) {
                return(maxsize + 4);
        } else {
                return(status_charheight + 4);
        }
}

/***************************************************************************
 * Function: StatusAlloc
 *
 * Purpose:
 *
 * Alloc the plist struct and return handle to caller
 */
HANDLE FAR PASCAL
StatusAlloc(int nitems)
{
        HANDLE hmem;
        PILIST pilist;
        LPSTR chp;

        hmem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT,
                sizeof(ILIST) + (sizeof(STATEL) * nitems));
        chp = GlobalLock(hmem);
        if (chp == NULL) {
                return(NULL);
        }

        pilist = (PILIST) chp;
        pilist->nitems = nitems;
        pilist->statels = (PSTATEL) &chp[sizeof(ILIST)];
        GlobalUnlock(hmem);

        return(hmem);
}

/***************************************************************************
 * Function: StatusAddItem
 *
 * Purpose:
 *
 * Insert an item into the plist
 */
BOOL FAR PASCAL
StatusAddItem(HANDLE hmem, int itemnr, int type, int flags, int id,
        int width, LPSTR text)
{
        PILIST pilist;
        PSTATEL pel;

        pilist = (PILIST) GlobalLock(hmem);
        if ((pilist == NULL) || (itemnr >= pilist->nitems)) {
                GlobalUnlock(hmem);
                return(FALSE);
        }
        pel = &pilist->statels[itemnr];
        pel->type = type;
        pel->flags = flags;
        pel->id = id;
        pel->width = width;
        if (text == NULL) {
                pel->text[0] = '\0';
        } else {
                lstrcpy(pel->text, text);
        }

        GlobalUnlock(hmem);
        return(TRUE);
}

/***************************************************************************
 * Function: InitDC
 *
 * Purpose:
 *
 * Initialize colors and font
 */
void
InitDC(HDC hdc)
{
        SetBkColor(hdc, RGB(192,192,192));
        SelectObject(hdc, hbrBackground);
        SelectObject(hdc, hFont);
}

/***************************************************************************
 * Function: StatusCreateTools
 *
 * Purpose:
 *
 * Create Pens and brushes
 */
void
StatusCreateTools()
{
    LOGFONT lf;
    HDC hdc;
    int scale;

    hbrBackground = CreateSolidBrush(RGB(192,192,192));
    hpenHilight = CreatePen(0, 1, RGB(255, 255, 255));
    hpenLowlight = CreatePen(0, 1, RGB(128, 128, 128));
    hpenNeutral = CreatePen(0, 1, RGB(192, 192, 192));
    hpenBlack = CreatePen(0, 1, RGB(0, 0, 0));

    hdc = GetDC(NULL);
    scale = GetDeviceCaps(hdc, LOGPIXELSY);
    ReleaseDC(NULL, hdc);

    lf.lfHeight = -MulDiv(DEF_PTSIZE, scale, 720);
    lf.lfWidth = 0;
    lf.lfEscapement = 0;
    lf.lfOrientation = 0;
    lf.lfWeight = FW_REGULAR;
    lf.lfItalic = 0;
    lf.lfUnderline = 0;
    lf.lfStrikeOut = 0;
    lf.lfCharSet = ANSI_CHARSET;
    lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
    lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
    lf.lfQuality = PROOF_QUALITY;
    lf.lfPitchAndFamily = VARIABLE_PITCH | FF_SWISS;
    lf.lfFaceName[0] = '\0';
#ifdef COMPLEX
    hFont = CreateFontIndirect(&lf);
#else
    hFont = GetStockObject(SYSTEM_FONT);
#endif

}

/***************************************************************************
 * Function: StatusDeleteTools
 *
 * Purpose:
 *
 * Delete brushes and pens
 */
void
StatusDeleteTools()
{
    DeleteObject(hbrBackground);
    DeleteObject(hpenHilight);
    DeleteObject(hpenLowlight);
    DeleteObject(hpenBlack);
    DeleteObject(hpenNeutral);

#ifdef COMPLEX
    DeleteObject(hFont);
#endif
}

/***************************************************************************
 * Function: StatusWndProc
 *
 * Purpose:
 *
 * Main winproc for status windows
 *
 * handles create/destroy and paint requests
 */

long FAR PASCAL
StatusWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam)
{
    HANDLE hitems;
    PSTATEL ip;
    PILIST plist;
    CREATESTRUCT FAR * cp;
    int i;
    HDC hDC;
    RECT rc;
    POINT pt;

    switch(message) {

    case WM_CREATE:
        cp = (CREATESTRUCT FAR *) lParam;
        hitems = (HANDLE) (LONG) cp->lpCreateParams;
        SetWindowLong(hWnd, 0,  (LONG)hitems);
        plist = (PILIST) GlobalLock(hitems);
        if (plist != NULL) {
                plist->selitem = -1;
                GlobalUnlock(hitems);
        }
        break;

    case WM_SIZE:
        hitems = (HANDLE) GetWindowLong(hWnd, 0);
        plist = (PILIST) GlobalLock(hitems);
        if (plist != NULL) {
                StatusResize(hWnd, plist);
                GlobalUnlock(hitems);
        }
        break;

    case WM_PAINT:
        hitems = (HANDLE) GetWindowLong(hWnd, 0);
        plist = (PILIST) GlobalLock(hitems);
        StatusPaint(hWnd, plist);
        GlobalUnlock(hitems);

        break;

    case WM_LBUTTONUP:
        hitems = (HANDLE) GetWindowLong(hWnd, 0);
        plist = (PILIST) GlobalLock(hitems);
        pt.x = LOWORD(lParam);
        pt.y = HIWORD(lParam);

        if (plist == NULL) {
                break;
        }
        if (plist->selitem != -1) {
                ip = &plist->statels[plist->selitem];
                if (plist->isselected) {
                        hDC = GetDC(hWnd);
                        InitDC(hDC);
                        StatusButtonUp(hDC, ip);
                        ReleaseDC(hWnd, hDC);
                }
                plist->selitem = -1;
                ReleaseCapture();
                if (PtInRect(&ip->rc, pt)) {
                        SendMessage(GetParent(hWnd), WM_COMMAND,
                                MAKELONG(ip->id, WM_LBUTTONUP), (LONG)hWnd);
                }
        }
        GlobalUnlock(hitems);
        break;

    case WM_LBUTTONDOWN:
        hitems = (HANDLE) GetWindowLong(hWnd, 0);
        plist = (PILIST) GlobalLock(hitems);
        if (plist == NULL) {
                break;
        }
        pt.x = LOWORD(lParam);
        pt.y = HIWORD(lParam);
        if (plist->selitem == -1) {
                for (i = 0; i< plist->nitems; i++) {
                        ip = &plist->statels[i];
                        if (PtInRect(&ip->rc, pt)) {
                                if (ip->type != SF_BUTTON) {
                                        break;
                                }
                                plist->selitem = i;
                                SetCapture(hWnd);

                                plist->isselected = TRUE;
                                hDC = GetDC(hWnd);
                                InitDC(hDC);
                                StatusButtonDown(hDC, ip);
                                ReleaseDC(hWnd, hDC);
                                break;
                        }
                }
        }
        GlobalUnlock(hitems);
        break;

    case WM_MOUSEMOVE:
        hitems = (HANDLE) GetWindowLong(hWnd, 0);
        plist = (PILIST) GlobalLock(hitems);
        if (plist == NULL) {
                break;
        }
        pt.x = LOWORD(lParam);
        pt.y = HIWORD(lParam);
        if (plist->selitem != -1) {
                ip = &plist->statels[plist->selitem];
                if (PtInRect(&ip->rc, pt)) {
                        if (!plist->isselected) {
                                hDC = GetDC(hWnd);
                                InitDC(hDC);
                                StatusButtonDown(hDC, ip);
                                ReleaseDC(hWnd, hDC);
                                plist->isselected = TRUE;
                        }
                } else {
                        if(plist->isselected) {
                                hDC = GetDC(hWnd);
                                InitDC(hDC);
                                StatusButtonUp(hDC, ip);
                                ReleaseDC(hWnd, hDC);
                                plist->isselected = FALSE;
                        }
                }
        }
        GlobalUnlock(hitems);
        break;

    case WM_DESTROY:

        hitems = (HANDLE) GetWindowLong(hWnd, 0);
        GlobalUnlock(hitems);
        GlobalFree(hitems);

        SetWindowLong(hWnd, 0, 0L);
        break;

    case SM_NEW:
        hitems = (HANDLE) GetWindowLong(hWnd, 0);
        if (hitems != NULL) {
                GlobalFree(hitems);
        }
        hitems = (HANDLE) wParam;
        if (hitems == NULL) {
                SetWindowLong(hWnd, 0, 0L);
                InvalidateRect(hWnd, NULL, TRUE);
                break;
        }
        plist = (PILIST) GlobalLock(hitems);
        if (plist == NULL) {
                SetWindowLong(hWnd, 0, 0L);
                InvalidateRect(hWnd, NULL, TRUE);
                break;
        }
        plist->selitem = -1;
        StatusResize(hWnd, plist);
        GlobalUnlock(hitems);
        SetWindowLong(hWnd, 0, (LONG)hitems);
        InvalidateRect(hWnd, NULL, TRUE);
        break;

    case SM_SETTEXT:
        hitems = (HANDLE) GetWindowLong(hWnd, 0);
        if (hitems == NULL) {
                break;
        }
        plist = (PILIST) GlobalLock(hitems);
        ip = StatusGetItem(plist, wParam);
        if (ip != NULL) {
                if (lParam == 0) {
                        ip->text[0] = '\0';
                } else {
                        strncpy(ip->text, (LPSTR) lParam, SF_MAXLABEL);
                        ip->text[SF_MAXLABEL] = '\0';
                }

                /* if this is a variable width field, we need to redo
                 * all size calcs in case the field width has changed.
                 * in that case, we need to repaint the entire window
                 * and not just this field - so set rc to indicate the
                 * area to be redrawn.
                 */
                if (ip->flags & SF_VAR) {
                        StatusResize(hWnd, plist);
                        GetClientRect(hWnd, &rc);
                        RedrawWindow(hWnd, &rc, NULL,
                                RDW_INVALIDATE|RDW_ERASE|RDW_UPDATENOW);
                } else {
                        /* instead of just invalidating the window, we can
                         * force the window to be repainted now. This is
                         * essential for status updates during a busy
                         * loop when no messages are being processed,
                         * but we should still update the user on what's
                         * happening.
                         */
                        RedrawWindow(hWnd, &ip->rc, NULL,
                                RDW_INVALIDATE|RDW_NOERASE|RDW_UPDATENOW);
                }

        }
        GlobalUnlock(hitems);
        break;

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

/***************************************************************************
 * Function: StatusResize
 *
 * Purpose:
 *
 * Position the labels and buttons within the status window
 */
void
StatusResize(HWND hWnd, PILIST iplistp)
{
        RECT rc;
        int curpos_right, curpos_left;
        int height, width;
        int i;
        PSTATEL ip;

        if (iplistp == NULL) {
                return;
        }
        GetClientRect(hWnd, &rc);
        curpos_left = rc.left + status_charwidth / 2;
        curpos_right = rc.right - (status_charwidth / 2);

        /* loop through all items setting their position rects.
         * items are flagged as being left or right. We place them
         * in order starting at the left and the right, with a single
         * char's width between each item
         */
        for (i = 0; i < iplistp->nitems; i++) {
                ip = &iplistp->statels[i];

                width = StatusCalcWidth(hWnd, ip);
                height = StatusCalcHeight(hWnd, ip);
                ip->rc.top = (rc.bottom - height) / 2;
                ip->rc.bottom = ip->rc.top + height;

                /* see if  this item fits. Items that partially fit
                 * are placed reduced in size.
                 */
                if (ip->flags & SF_LEFT) {

                        if (curpos_left+width >= curpos_right) {
                                /* doesn't completely fit-does it partly? */
                                if ((curpos_left + 1) >= curpos_right){

                                        /* no - this item does not fit */
                                        ip->rc.left = 0;
                                        ip->rc.right = 0;
                                } else {
                                        /* partial fit */
                                        ip->rc.left = curpos_left;
                                        ip->rc.right = curpos_right - 1;
                                        curpos_left = curpos_right;
                                }
                        } else {
                                /* complete fit */
                                ip->rc.left = curpos_left;
                                ip->rc.right = curpos_left + width;
                                curpos_left += width + 1;
                        }
                } else {

                        /* same size check for right-aligned items */
                        if (curpos_right-width <= curpos_left) {

                                /* partial fit ? */
                                if (curpos_right <= curpos_left+1) {
                                        ip->rc.left = 0;
                                        ip->rc.right = 0;
                                } else {
                                        /* yes - partial fit */
                                        ip->rc.left = curpos_left + 1;
                                        ip->rc.right = curpos_right;
                                        curpos_right = curpos_left;
                                }
                        } else {
                                /* complete fit */
                                ip->rc.right = curpos_right;
                                ip->rc.left = curpos_right - width;
                                curpos_right -= (width + 1);
                        }
                }
        }
}

/***************************************************************************
 * Function: StatusPaint
 *
 * Purpose:
 *
 * Paint the status window
 */
void
StatusPaint(HWND hWnd, PILIST iplistp)
{
        RECT rc;
        HDC hDC;
        PAINTSTRUCT ps;
        int i;
        PSTATEL ip;
        HPEN hpenOld;

        GetClientRect(hWnd, &rc);
        hDC = BeginPaint(hWnd, &ps);
        InitDC(hDC);

        RaiseRect(hDC, &rc);
        if (iplistp == NULL) {
                EndPaint(hWnd, &ps);
                return;
        }
        for (i =0; i < iplistp->nitems; i++) {
                ip = &iplistp->statels[i];

                if (ip->rc.left == ip->rc.right) {
                        continue;
                }
                if (ip->type == SF_STATIC) {
                        if (ip->flags & SF_RAISE) {
                                RaiseRect(hDC, &ip->rc);
                        } else if (ip->flags & SF_LOWER) {
                                LowerRect(hDC, &ip->rc);
                        }
                        rc = ip->rc;
                        rc.left += (status_charwidth / 2);
                        rc.right--;
                        rc.top++;
                        rc.bottom--;
                        hpenOld = SelectObject(hDC, hpenNeutral);
                        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
                        SelectObject(hDC, hpenOld);
                        DrawText(hDC, ip->text, lstrlen(ip->text), &rc,
                                        DT_LEFT | DT_VCENTER);
                } else {
                        StatusButtonUp(hDC, ip);
                }
        }

        EndPaint(hWnd, &ps);
}

/***************************************************************************
 * Function: RaiseRect
 *
 * Purpose:
 *
 */
void
RaiseRect(HDC hDC, LPRECT rcp)
{
        TopLeft(hDC, rcp, hpenHilight, FALSE);
        BottomRight(hDC, rcp, hpenLowlight, FALSE);
}

/***************************************************************************
 * Function: LowerRect
 *
 * Purpose:
 *
 */
void
LowerRect(HDC hDC, LPRECT rcp)
{
        TopLeft(hDC, rcp, hpenLowlight, FALSE);
        BottomRight(hDC, rcp, hpenHilight, FALSE);
}

/***************************************************************************
 * Function: StatusButtonUp
 *
 * Purpose:
 *
 */
void
StatusButtonUp(HDC hDC, PSTATEL ip)
{
        RECT rc;
        HPEN hpenOld;

        rc = ip->rc;
        TopLeft(hDC, &rc, hpenBlack, TRUE);
        BottomRight(hDC, &rc, hpenBlack, FALSE);

        rc.top++;
        rc.bottom--;
        rc.left++;
        rc.right--;
        TopLeft(hDC, &rc, hpenHilight, FALSE);
        BottomRight(hDC, &rc, hpenLowlight, TRUE);

        rc.top++;
        rc.bottom--;
        rc.left++;
        rc.right--;
        BottomRight(hDC, &rc, hpenLowlight, TRUE);
        rc.bottom--;
        rc.right--;
        hpenOld = SelectObject(hDC, hpenNeutral);
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
        SelectObject(hDC, hpenOld);
        DrawText(hDC, ip->text, lstrlen(ip->text), &rc, DT_CENTER | DT_VCENTER);
}

/***************************************************************************
 * Function: StatusButtonDown
 *
 * Purpose:
 *
 */
void
StatusButtonDown(HDC hDC, PSTATEL ip)
{
        RECT rc;
        HPEN hpenOld;

        rc = ip->rc;
        TopLeft(hDC, &rc, hpenBlack, TRUE);
        BottomRight(hDC, &rc, hpenBlack, FALSE);

        rc.top++;
        rc.bottom--;
        rc.left++;
        rc.right--;
        TopLeft(hDC, &rc, hpenLowlight, TRUE);
        rc.top++;
        rc.left++;
        TopLeft(hDC, &rc, hpenNeutral, TRUE);
        rc.top++;
        rc.left++;
        TopLeft(hDC, &rc, hpenNeutral, TRUE);
        rc.top++;
        rc.left++;
        hpenOld = SelectObject(hDC, hpenNeutral);
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
        SelectObject(hDC, hpenOld);
        DrawText(hDC, ip->text, lstrlen(ip->text), &rc, DT_CENTER | DT_VCENTER);
}

/***************************************************************************
 * Function: TopLeft
 *
 * Purpose:
 *
 */
void
TopLeft(HDC hDC, LPRECT rcp, HPEN hpen, BOOL bCorners)
{
        HPEN hpenOld;
        int x, y;

        hpenOld = SelectObject(hDC, hpen);
        x = rcp->right - 1;
        y = rcp->bottom;
        if (!bCorners) {
                x--;
                y--;
        }
        MoveToEx(hDC, x, rcp->top, NULL);
        LineTo(hDC, rcp->left, rcp->top);
        LineTo(hDC, rcp->left, y);
        SelectObject(hDC, hpenOld);
}

/***************************************************************************
 * Function: BottomRight
 *
 * Purpose:
 *
 */
void
BottomRight(HDC hDC, LPRECT rcp, HPEN hpen, BOOL bCorners)
{
        HPEN hpenOld;
        int x, y;

        hpenOld = SelectObject(hDC, hpen);
        x = rcp->left - 1;
        y = rcp->top;
        if (!bCorners) {
                x++;
                y++;
        }
        MoveToEx(hDC, rcp->right-1, y, NULL);
        LineTo(hDC, rcp->right-1, rcp->bottom-1);
        LineTo(hDC, x, rcp->bottom-1);
        SelectObject(hDC, hpenOld);
}

/***************************************************************************
 * Function: StatusGetItem
 *
 * Purpose:
 *
 */
PSTATEL
StatusGetItem(PILIST plist, int id)
{
        int i;

        if (plist == NULL) {
                return(NULL);
        }
        for (i = 0; i < plist->nitems; i++) {
                if (plist->statels[i].id == id) {
                        return(&plist->statels[i]);
                }
        }
        return(NULL);
}

/***************************************************************************
 * Function: StatusCalcWidth
 *
 * Purpose:
 *
 * Calculate the width of a given field. This is the width in characters
 * multiplied by the average character width, plus a few units for
 * borders.
 *
 * If SF_VAR is set, this field size varies depending on the text, so
 * we use GetTextExtent for the field size. If SF_VAR is selected, the caller
 * can specify that the size is not to exceed the (width * avecharwidth)
 * size (using SF_SZMAX) or that it is not be less than it (SF_SZMIN).
 */
int
StatusCalcWidth(HWND hWnd, PSTATEL ip)
{
        int ch_size, t_size;
        SIZE sz;
        HDC hDC;

        ch_size = ip->width * status_charwidth;
        if (ip->flags & SF_VAR) {
                hDC = GetDC(hWnd);
                InitDC(hDC);
                GetTextExtentPoint(hDC, ip->text, lstrlen(ip->text), &sz);
                ReleaseDC(hWnd, hDC);
                t_size = sz.cx;

                /*
                 * check this size against min/max size if
                 * requested
                 */

                if (ip->flags & SF_SZMIN) {
                        if (ch_size > t_size) {
                                t_size = ch_size;
                        }
                }
                if (ip->flags & SF_SZMAX) {
                        if (ch_size < t_size) {
                                t_size = ch_size;
                        }
                }
                ch_size = t_size;
        }

        if (ch_size != 0) {
                if (ip->type == SF_BUTTON) {
                        return(ch_size+6);
                } else {
                        return(ch_size+4);
                }
        } else {
                return(0);
        }
}

/***************************************************************************
 * Function: StatusCalcHeight
 *
 * Purpose:
 *
 * Calculate the height of a given field
 */
int
StatusCalcHeight(HWND hWnd, PSTATEL ip)

{
        int size;

        size = status_charheight;
        if (ip->type == SF_BUTTON) {
                return(size + 6);
        } else {
                return(size + 2);
        }
}


TABLE.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 Header *******************************
* Module Name: TABLE.C
*
* Standard table class and main interface functions.
*
* Functions:
*
* gtab_init()
* gtab_deltools()
* gtab_sendtq()
* gtab_freelinedata()
* gtab_wndproc()
* gtab_createtools()
* gtab_deltable()
* gtab_buildtable()
* gtab_setsize()
* gtab_newsize()
* gtab_calcwidths()
* gtab_alloclinedata()
* gtab_invallines()
* gtab_append()
*
* Comments:
*
* The table class communicates with its 'owner' window to
* get the layout info and the data to display. The owner window handle
* can be sent as the lParam in CreateWindow - if not, the parent window will
* be used.
*
* After creating the window, send it a TM_NEWID message, with a 'data id'
* as the lParam. This is any non-zero 32-bit value. The table will then call
* back to its owner window to find out how many rows/columns, then to fetch
* the name/properties of each column, and finally to get the data to display.
*
* Send TM_NEWID of 0 to close (or destroy the window) - wait for TQ_CLOSE
* (in either case) before discarding data. Send
* TM_REFRESH if data or row-count changes; send TM_NEWLAYOUT if column
* properties or nr cols change etc - this is the same as sending TM_NEWID
* except that no TQ_CLOSE happens on TM_NEWLAYOUT.
*
* TQ_SELECT is sent whenever the current selection changes. TQ_ENTER is sent
* when enter or double-click occurs.
*
****************************************************************************/

#include <windows.h>
#include <commdlg.h>

#include "gutils.h"
#include "table.h"
#include "tpriv.h"

/* global tools etc */
extern HANDLE hLibInst;
HANDLE hVertCurs;
HANDLE hNormCurs;
HPEN hpenDotted;
UINT gtab_msgcode;

/* function prototypes */
long FAR PASCAL gtab_wndproc(HWND, UINT, UINT, long);
void gtab_createtools(void);
void gtab_deltable(HWND hwnd, lpTable ptab);
lpTable gtab_buildtable(HWND hwnd, DWORD id);
void gtab_setsize(HWND hwnd, lpTable ptab);
void gtab_newsize(HWND hwnd, lpTable ptab);
void gtab_calcwidths(HWND hwnd, lpTable ptab);
BOOL gtab_alloclinedata(HWND hwnd, HANDLE heap, lpTable ptab);
void gtab_invallines(HWND hwnd, lpTable ptab, int start, int count);
void gtab_append(HWND hwnd, lpTable ptab, int rows, DWORD id);

/***************************************************************************
 * Function: gtab_init
 *
 * Purpose:
 *
 * Initialise window class - called from DLL main init
 */
void
gtab_init(void)
{
        WNDCLASS wc;

        gtab_createtools();
        gtab_msgcode = RegisterWindowMessage(TableMessage);

        wc.style = CS_GLOBALCLASS | CS_DBLCLKS;
        wc.lpfnWndProc = gtab_wndproc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = WLTOTAL;
        wc.hInstance = hLibInst;
        wc.hIcon = NULL;
        wc.hCursor = NULL;
        wc.hbrBackground = GetStockObject(WHITE_BRUSH);
        wc.lpszClassName = TableClassName;
        wc.lpszMenuName = NULL;

        RegisterClass(&wc);
}

/***************************************************************************
 * Function: gtab_createtools
 *
 * Purpose:
 *
 * Load cursors and pens.
 */
 void
gtab_createtools(void)
{
        hVertCurs = LoadCursor(hLibInst, "VertLine");
        hNormCurs = LoadCursor(NULL, IDC_ARROW);

        hpenDotted = CreatePen(PS_DOT, 1, RGB(0, 0, 0));
}

/***************************************************************************
 * Function: gtab_deltools
 *
 * Purpose:
 *
 * Delete pen
 */
 void
gtab_deltools(void)
{
        DeleteObject(hpenDotted);
}

/***************************************************************************
 * Function: gtab_wndproc
 *
 * Purpose:
 *
 * Window procedure for table
 */
 long FAR PASCAL
gtab_wndproc(HWND hwnd, UINT msg, UINT wParam, long lParam)
{
        CREATESTRUCT FAR * csp;
        HWND hOwner;
        lpTable ptab;
        HANDLE hHeap;
        PAINTSTRUCT ps;
        int y, y2, i;
        HDC hDC;
        lpTableSelection pselect;
        long oldtop;
        long change;

        switch(msg) {

        case WM_CREATE:
                /* create window. set the wnd extra bytes to
                 * contain the owner window, a heap and a null table.
                 * Owner window is either in lParam or the parent.
                 * Then wait for TM_NEWID.
                 */
                csp = (CREATESTRUCT FAR *) lParam;
                if (csp->lpCreateParams == NULL) {
                        hOwner = GetParent(hwnd);
                } else {
                        hOwner = (HWND) (long) csp->lpCreateParams;
                }
                ptab = NULL;
                hHeap = gmem_init();
                SetWindowLong(hwnd, WL_TABLE, (LONG) ptab);
                SetWindowLong(hwnd, WW_OWNER, (LONG) hOwner);
                SetWindowLong(hwnd, WW_HEAP, (LONG) hHeap);

                SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
                SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
                break;

        case TM_NEWID:
                /* complete change of table.
                 * close old table, discard memory and
                 * build new table
                 */
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_sendtq(hwnd, TQ_CLOSE, ptab->hdr.id);
                        gtab_deltable(hwnd, ptab);
                        SetCursor(hNormCurs);
                        SetWindowLong(hwnd, WL_TABLE, 0);
                }
                if ( (ptab = gtab_buildtable(hwnd, lParam)) != NULL) {
                        SetWindowLong(hwnd, WL_TABLE, (long) (LPSTR) ptab);
                        gtab_setsize(hwnd, ptab);
                } else {
                        SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
                        SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
                }
                InvalidateRect(hwnd, NULL, TRUE);
                break;

        case TM_NEWLAYOUT:
                /* change of layout but for same id. no TQ_CLOSE,
                 * but otherwise same as TM_NEWID
                 */
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_deltable(hwnd, ptab);
                        SetCursor(hNormCurs);
                        SetWindowLong(hwnd, WL_TABLE, 0);
                }
                if ( (ptab = gtab_buildtable(hwnd, lParam)) != NULL) {
                        SetWindowLong(hwnd, WL_TABLE, (long) (LPSTR) ptab);
                        gtab_setsize(hwnd, ptab);
                } else {
                        SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
                        SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
                }
                InvalidateRect(hwnd, NULL, TRUE);
                break;

        case TM_REFRESH:
                /* data in table has changed. nrows may have
                 * changed. ncols and col types have not changed
                 */
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_newsize(hwnd, ptab);
                }
                InvalidateRect(hwnd, NULL, TRUE);
                break;

        case TM_SELECT:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        pselect = (lpTableSelection) lParam;

                        /*
                         * we only support TM_SINGLE - so force the
                         * selection to a single row or cell.
                         */
                        gtab_select(hwnd, ptab, pselect->startrow,
                                pselect->startcell,
                                1,
                                (ptab->hdr.selectmode & TM_ROW) ?
                                        ptab->hdr.ncols : 1,
                                TRUE);
                        gtab_showsel_middle(hwnd, ptab);
                }
                break;

        case TM_PRINT:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                hHeap = (HANDLE) GetWindowLong(hwnd, WW_HEAP);
                if (ptab != NULL) {
                        gtab_print(hwnd, ptab, hHeap, (lpPrintContext) lParam);
                        return(TRUE);
                }

        case TM_TOPROW:

                /* return top row. if wParam is TRUE, set lParam
                 * as the new toprow
                 */
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab == NULL) {
                        return(0);
                }
                oldtop = ptab->toprow;
                if ((wParam) && (lParam < ptab->hdr.nrows)) {
                        change = lParam - ptab->toprow;
                        change -= ptab->hdr.fixedrows;
                        gtab_dovscroll(hwnd, ptab, change);
                }
                return(oldtop);

        case TM_ENDROW:
                /* return the last visible row in the window */
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab == NULL) {
                        return(0);
                }
                return(ptab->nlines + ptab->toprow - 1);

        case TM_APPEND:
                /* new rows have been added to the end of the
                 * table, but the rest of the table has no
                 * been change. Update without forcing redraw of
                 * everything.
                 * lParam contains the new total nr of rows
                 */
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_append(hwnd, ptab, wParam, lParam);
                        return(TRUE);
                }
                break;

        case WM_SIZE:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_setsize(hwnd, ptab);
                }
                break;

        case WM_DESTROY:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_sendtq(hwnd, TQ_CLOSE, ptab->hdr.id);
                        gtab_deltable(hwnd, ptab);
                }
                hHeap = (HANDLE) GetWindowLong(hwnd, WW_HEAP);
                gmem_freeall(hHeap);
                break;

        case WM_PAINT:
                hDC = BeginPaint(hwnd, &ps);
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        /* separator lines between fixed rows/columns
                         * (ie headers) and the rest - if enabled
                         */
                        /* paint here first for good impression,
                         * and again after to clean up!!
                         */
                        if (ptab->hdr.vseparator) {
                                gtab_vsep(hwnd, ptab, hDC);
                        }
                        if (ptab->hdr.hseparator) {
                                gtab_hsep(hwnd, ptab, hDC);
                        }

                        /* paint only the rows that need painting */
                        for (i = 0; i < ptab->nlines; i++) {
                                y = ptab->pdata[i].linepos.start;
                                y2 = y + ptab->pdata[i].linepos.size;
                                if ( (y <= ps.rcPaint.bottom) &&
                                     (y2 >= ps.rcPaint.top)) {
                                        gtab_paint(hwnd, hDC, ptab, i);
                                }
                        }
                        if (ptab->hdr.vseparator) {
                                gtab_vsep(hwnd, ptab, hDC);
                        }
                        if (ptab->hdr.hseparator) {
                                gtab_hsep(hwnd, ptab, hDC);
                        }
                        if (ptab->selvisible) {
                                gtab_invertsel(hwnd, ptab, hDC);
                        }
                }

                EndPaint(hwnd, &ps);
                break;

        case WM_HSCROLL:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_msg_hscroll(hwnd, ptab,
                          GET_SCROLL_OPCODE(wParam, lParam),
                          GET_SCROLL_POS(wParam, lParam));
                }
                break;

        case WM_VSCROLL:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_msg_vscroll(hwnd, ptab,
                          GET_SCROLL_OPCODE(wParam, lParam),
                          GET_SCROLL_POS(wParam, lParam));
                }
                break;

        case WM_MOUSEMOVE:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_move(hwnd, ptab, LOWORD(lParam), HIWORD(lParam));
                } else {
                        SetCursor(hNormCurs);
                }
                break;

        case WM_LBUTTONDOWN:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_press(hwnd, ptab, LOWORD(lParam), HIWORD(lParam));
                }
                break;

        case WM_LBUTTONUP:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_release(hwnd, ptab,
                                LOWORD(lParam), HIWORD(lParam));
                }
                break;

        case WM_LBUTTONDBLCLK:
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        gtab_dblclick(hwnd, ptab,
                                LOWORD(lParam), HIWORD(lParam));
                }
                break;

        case WM_KEYDOWN:
                /* handle key presses for cursor movement about
                 * the table, and return/space for selection.
                 * Any key we don't handle is passed to the owner window
                 * for him to handle.
                 * The table window should have the focus
                 */
                ptab = (lpTable) GetWindowLong(hwnd, WL_TABLE);
                if (ptab != NULL) {
                        if (gtab_key(hwnd, ptab, wParam) != 0) {
                                /* pass key to owner since
                                 * we don't know what to do with it
                                 */
                                hOwner = (HANDLE) GetWindowLong(hwnd, WW_OWNER);
                                return(SendMessage(hOwner, WM_KEYDOWN,
                                        wParam, lParam));
                        } else {
                                return(0);
                        }
                }
                break;

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

/***************************************************************************
 * Function: gtab_sendtq
 *
 * Purpose:
 *
 * Send a table-query message to the owner window. Returns message
 * value.
 */
long
gtab_sendtq(HWND hwnd, UINT cmd, long lParam)
{
        HWND hOwner;

        hOwner = (HANDLE) GetWindowLong(hwnd, WW_OWNER);
        return (SendMessage(hOwner, gtab_msgcode, cmd, lParam));
}

/***************************************************************************
 * Function: gtab_freelinedata
 *
 * Purpose:
 *
 * Free the memory allocated for the array of lines (each containing
 * an array of Cells, each containing an array of chars for the actual
 * data). Called on any occasion that would change the number of visible lines
 */
void
gtab_freelinedata(HANDLE hHeap, lpTable ptab)
{
        int i, j, ncols;
        lpCellData cd;

        ncols = ptab->hdr.ncols;

        /* for each line */
        for(i = 0; i < ptab->nlines; i++) {
                /* for each cell */
                for (j = 0; j < ncols; j++) {
                        /* free up the actual text space */
                        cd = &ptab->pdata[i].pdata[j];
                        gmem_free(hHeap, (LPSTR) cd->ptext, cd->nchars);
                }
                /* dealloc array of CellData */
                gmem_free(hHeap, (LPSTR) ptab->pdata[i].pdata,
                        sizeof(CellData) * ncols);
        }
        /* de-alloc array of linedatas */
        gmem_free(hHeap, (LPSTR) ptab->pdata,
                sizeof(LineData) * ptab->nlines);
        ptab->pdata = NULL;
}

/***************************************************************************
 * Function: gtab_alloclinedata
 *
 * Purpose:
 *
 * Allocate and init array of linedatas (include cell array
 * and text for each cell)
 */
BOOL
gtab_alloclinedata(HWND hwnd, HANDLE heap, lpTable ptab)
{
        lpLineData pline;
        lpCellData cd;
        int i, j;

        ptab->pdata = (lpLineData) gmem_get(heap,
                sizeof(LineData) * ptab->nlines);
        if (ptab->pdata == NULL) {
                return(FALSE);
        }
        for (i = 0; i < ptab->nlines; i++) {
                pline = &ptab->pdata[i];
                pline->linepos.size = ptab->rowheight;
                pline->pdata = (lpCellData) gmem_get(heap,
                        sizeof(CellData) * ptab->hdr.ncols);
                if (pline->pdata == NULL) {
                        return(FALSE);
                }
                for (j = 0; j < ptab->hdr.ncols; j++) {
                        cd = &pline->pdata[j];
                        cd->props.valid = 0;
                        cd->flags = 0;
                        cd->nchars = ptab->pcolhdr[j].nchars;
                        if (cd->nchars > 0) {
                                cd->ptext = gmem_get(heap, cd->nchars);
                                if (cd->ptext == NULL) {
                                        return(FALSE);
                                }
                        }
                }
        }
}

/***************************************************************************
 * Function: gtab_deltable
 *
 * Purpose:
 *
 * Free up all table data structures. Called for new layout or new data.
 */
void
gtab_deltable(HWND hwnd, lpTable ptab)
{
        HANDLE hHeap;
        int ncols;

        if (ptab == NULL) {
                return;
        }
        hHeap = (HANDLE) GetWindowLong(hwnd, WW_HEAP);
        ncols = ptab->hdr.ncols;

        if (ptab->pcolhdr != NULL) {
                gmem_free(hHeap, (LPSTR) ptab->pcolhdr,
                        sizeof(ColProps) * ncols);
        }
        if (ptab->pcellpos != NULL) {
                gmem_free(hHeap, (LPSTR) ptab->pcellpos,
                        sizeof(CellPos) * ncols);
        }
        if (ptab->pdata != NULL) {
                gtab_freelinedata(hHeap, ptab);
        }
        gmem_free(hHeap, (LPSTR) ptab, sizeof(Table));
}

/***************************************************************************
 * Function: gtab_buildtable
 *
 * Purpose:
 *
 * Build up a Table struct (excluding data allocation and
 * anything to do with font or window size).
 * Return ptr to this or NULL if error
 */
lpTable
gtab_buildtable(HWND hwnd, DWORD id)
{
        lpTable ptab;
        HANDLE hHeap;
        int ncols, i;
        ColPropsList cplist;

        hHeap = (HANDLE) GetWindowLong(hwnd, WW_HEAP);
        ptab = (lpTable) gmem_get(hHeap, sizeof(Table));
        if (ptab == NULL) {
                return(NULL);
        }

        /* get the row/column count from owner window */
        ptab->hdr.id = id;
        ptab->hdr.props.valid = 0;
        ptab->hdr.sendscroll = FALSE;
        if (gtab_sendtq(hwnd, TQ_GETSIZE, (long) (LPSTR)&ptab->hdr) == FALSE) {
                return(NULL);
        }

        ncols = ptab->hdr.ncols;
        ptab->pcolhdr = (lpColProps) gmem_get(hHeap, sizeof(ColProps) * ncols);
        if (ptab->pcolhdr == NULL) {
                /* should prob send TQ_CLOSE at this point */
                return(NULL);
        }

        /* init col properties to default */
        for (i=0; i < ncols; i++) {
                ptab->pcolhdr[i].props.valid = 0;
                ptab->pcolhdr[i].nchars = 0;
        }
        /* get the column props from owner */
        cplist.plist = ptab->pcolhdr;
        cplist.id = id;
        cplist.startcol = 0;
        cplist.ncols = ncols;
        gtab_sendtq(hwnd, TQ_GETCOLPROPS, (long) (LPSTR)&cplist);

        /* init remaining fields */
        ptab->pcellpos = (lpCellPos) gmem_get(hHeap, sizeof(CellPos) * ncols);
        if (ptab->pcellpos == NULL) {
                return(NULL);
        }

        ptab->scrollscale = 1;
        ptab->scroll_dx = 0;
        ptab->toprow = 0;
        ptab->pdata = NULL;
        ptab->nlines = 0;
        ptab->trackmode = TRACK_NONE;

        /* we have to notify owner of the current selection
         * whenever it is changed
         */
        ptab->select.id = id;
        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);

        /* calc ave height/width, cell widths and min height.
         * these change only when cell properties / col count changes -
         * ie only on rebuild-header events
         */
        gtab_calcwidths(hwnd, ptab);
        return(ptab);
}

/***************************************************************************
 * Function: gtab_setsize
 *
 * Purpose:
 *
 * Set sizes that are based on window size and scroll pos
 * set:
 *      winwidth
 *      nlines
 *      cellpos start, clip start/end
 * Alloc linedata and init
 */
void
gtab_setsize(HWND hwnd, lpTable ptab)
{
        RECT rc;
        int nlines;
        HANDLE heap;
        long range, change;

        GetClientRect(hwnd, &rc);
        ptab->winwidth = rc.right - rc.left;
        nlines = (rc.bottom - rc.top) / ptab->rowheight;
        /* nlines is the number of whole lines - add one extra
         * for the partial line at the bottom
         */
        nlines += 1;

        /* alloc space for nlines of data - if nlines has changed */
        if (nlines != ptab->nlines) {
                heap = (HANDLE) GetWindowLong(hwnd, WW_HEAP);
                gtab_freelinedata(heap, ptab);
                ptab->nlines = nlines;
                if (!gtab_alloclinedata(hwnd, heap, ptab)) {
                        ptab->nlines = 0;
                        return;
                }
        }

        /* set scroll vertical range */
        range = ptab->hdr.nrows - (ptab->nlines - 1);
        if (range < 0) {
                range = 0;
                change =  -(ptab->toprow);
        } else if (ptab->toprow > range) {
                change = range - ptab->toprow;
        } else {
                change = 0;
        }
        /* the scroll range must be 16-bits for Win3
         * scale until this is true
         */
        ptab->scrollscale = 1;
        while (range > 32766) {
                ptab->scrollscale *= 16;
                range /= 16;
        }

        SetScrollRange(hwnd, SB_VERT, 0, (int) range, TRUE);
        gtab_dovscroll(hwnd, ptab, change);

        /* set horz scroll range */
        range = ptab->rowwidth - ptab->winwidth;
        if (range < 0) {
                range = 0;
                change = -(ptab->scroll_dx);
        } else if (ptab->scroll_dx > range) {
                change = range - ptab->scroll_dx;
        } else {
                change = 0;
        }
        /* horz scroll range will always be < 16 bits */
        SetScrollRange(hwnd, SB_HORZ, 0, (int) range, TRUE);
        gtab_dohscroll(hwnd, ptab, change);
}

/***************************************************************************
 * Function: gtab_calcwidths
 *
 * Purpose:
 *
 * Set column widths/height and totals (based on column props)
 * - no assumption of window size (see gtab_setsize)
 * sets avewidth,rowheight,cellpos.size,rowwidth (total of cellpos.size)
 */
void
gtab_calcwidths(HWND hwnd, lpTable ptab)
{
        int i, cxtotal, cx, ave;
        TEXTMETRIC tm, tmcol;
        HDC hdc;
        lpProps hdrprops, cellprops;
        HFONT hfont;

        hdrprops = &ptab->hdr.props;
        hdc = GetDC(hwnd);
        if (hdrprops->valid & P_FONT) {
                hfont = SelectObject(hdc, hdrprops->hFont);
        }
        GetTextMetrics(hdc, &tm);
        if (hdrprops->valid & P_FONT) {
                SelectObject(hdc, hfont);
        }
        ReleaseDC(hwnd, hdc);

        /* get width and height of average character */
        ptab->avewidth = tm.tmAveCharWidth;
        ptab->rowheight = tm.tmHeight + tm.tmExternalLeading;
        if (hdrprops->valid & P_HEIGHT) {
                ptab->rowheight = hdrprops->height;
        }

        /* set pixel width of each cell (and add up for row total)
         * based on ave width * nr chars, unless P_WIDTH set
         */
        cxtotal = 0;
        for (i = 0; i < ptab->hdr.ncols; i++) {
                cellprops = &ptab->pcolhdr[i].props;

                if (cellprops->valid & P_WIDTH) {
                        cx = cellprops->width;
                } else if (hdrprops->valid & P_WIDTH) {
                        cx = hdrprops->width;
                } else {

                        if (cellprops->valid & P_FONT) {
                                hdc = GetDC(hwnd);
                                hfont = SelectObject(hdc, cellprops->hFont);
                                GetTextMetrics(hdc, &tmcol);
                                SelectObject(hdc, hfont);
                                ReleaseDC(hwnd, hdc);
                                ave = tmcol.tmAveCharWidth;
                        } else {
                                ave = ptab->avewidth;
                        }
                        /* ave width * nchars */
                        cx =  ptab->pcolhdr[i].nchars + 1;
                        cx *= ave;
                }
                /* add 2 pixels for box lines */
                cx += 2;
                ptab->pcellpos[i].size = cx;
                cxtotal += cx;
        }
        ptab->rowwidth = cxtotal;
}

/***************************************************************************
 * Function: gtab_newsize
 *
 * Purpose:
 *
 * Called when row data + possible nrows changes.
 * other changes are ignored
 */
void
gtab_newsize(HWND hwnd, lpTable ptab)
{
        TableHdr hdr;

        /* get new row count */
        hdr = ptab->hdr;
        gtab_sendtq(hwnd, TQ_GETSIZE, (long) (LPSTR) &hdr);
        if (hdr.nrows != ptab->hdr.nrows) {
                ptab->hdr.nrows = hdr.nrows;
                gtab_setsize(hwnd, ptab);
        }

        gtab_invallines(hwnd, ptab, 0, ptab->nlines);

        InvalidateRect(hwnd, NULL, TRUE);
}

void
gtab_invallines(HWND hwnd, lpTable ptab, int start, int count)
{
        int i, j;

        for (i = start; i < start + count; i++) {
                for (j = 0; j < ptab->hdr.ncols; j++) {
                        ptab->pdata[i].pdata[j].flags = 0;
                }
        }
}

/***************************************************************************
 * Function: gtab_append
 *
 * Purpose:
 *
 * New rows have been added to the table. Adjust the scroll range and
 * position, and redraw the rows if the end of the table is currently
 * visible.
 * rows = the new total row count.
 */
void
gtab_append(HWND hwnd, lpTable ptab, int rows, DWORD id)
{
        long range;
        long oldrows;
        int line, nupdates;
        RECT rc;

        /* change to the new id */
        ptab->hdr.id = id;
        ptab->select.id = id;

        /* update the header, but remember the old nr of rows
         * so we know where to start updating
         */
        oldrows = ptab->hdr.nrows;

        /* check that the new nr of rows is not smaller. this is
         * illegal at this point and should be ignored
         */
        if (oldrows >= rows) {
                return;
        }

        ptab->hdr.nrows = rows;

        /* set the vertical scroll range */
        range = rows - (ptab->nlines - 1);

        if (range < 0) {
                range = 0;
        }

        /* force the scroll range into 16-bits for win 3.1 */
        ptab->scrollscale = 1;
        while (range > 32766) {
                ptab->scrollscale *= 16;
                range /= 16;
        }

        /* now set the scroll bar range and position */
        SetScrollRange(hwnd, SB_VERT, 0, (int) range, TRUE);
        if (range > 0) {
                SetScrollPos(hwnd, SB_VERT,
                        (int) (ptab->toprow / ptab->scrollscale), TRUE);
        }

        /* calculate which screen lines need to be updated - find what
         * screen line the start of the new section is at
         */
        line = gtab_rowtoline(hwnd, ptab, oldrows);
        if (line == -1) {
                /* not visible -> no more to do */
                return;
        }

        /* how many lines to update - rest of screen or nr of
         * new lines if less than rest of screen
         */
        nupdates = min((ptab->nlines - line), (int)(rows - oldrows));

        /* invalidate the screen line buffers to indicate data
         * needs to be refetch from parent window
         */
        gtab_invallines(hwnd, ptab, line, nupdates);

        /* calculate the region of the screen to be repainted -
         * left and right are same as window. top and bottom
         * need to be calculated from screen line height
         */

        GetClientRect(hwnd, &rc);
        rc.top += line * ptab->rowheight;
        rc.bottom = rc.top + (nupdates * ptab->rowheight);

        /* force a repaint of the updated region */
        InvalidateRect(hwnd, &rc, TRUE);
}


TABLE.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.
\******************************************************************************/

/*
 * TABLE.H
 *
 * public interface definition for table window class.
 *
 * include after gutils.h and commdlg.h
 */

/* -------class and message names --------------------------------------*/

/* create a window of this class */
#define  TableClassName "GTableClass"

/* all messages to the owner window are sent with this message.
 * call RegisterWindowsMessage with this string for the message UINT.
 */
#define TableMessage  "GTableQuery"

/* -------- messages to and from table class  --------------------------*/

/* messages to owner window are:
 *      message:        TableMessage
 *      wParam:         command code (below)
 * lParam:              struct pointer according to code
 * below is list of wParam codes & associated lParam struct
 */
#define TQ_GETSIZE      1       /* lParam: lpTableHdr */
#define TQ_GETCOLPROPS  2       /* lParam: lpColPropList */
#define TQ_GETDATA      3       /* lParam: lpCellDataList */
#define TQ_PUTDATA      4       /* lParam: lpCellDataList */
#define TQ_SELECT       5       /* lParam: lpTableSelection */
#define TQ_ENTER        6       /* lParam: lpTableSelection */
#define TQ_CLOSE        7       /* lParam: the data id to be closed */

/* optional */
#define TQ_SCROLL       8       /* lParam: the new top row nr */

/* messages to Table class */

/* data, or nrows has changed  wParam/lParam null*/
#define TM_REFRESH      (WM_USER)

/* nr cols/props/layout has changed  - wparam/lparam null */
#define TM_NEWLAYOUT    (WM_USER+1)

/* Close old id, and display new - wParam null, lParam has new id */
#define TM_NEWID        (WM_USER+2)

/* Select and show this area - wParam null, lParam is lpTableSelection */
#define TM_SELECT       (WM_USER+3)

/* Print current table - wParam null, lParam either null
 * or lpPrintContext.
 */
#define TM_PRINT        (WM_USER+4)

/* Return the top row in the window. If wParam is TRUE, then set
 * lParam to be the new toprow. top row is the number of rows scrolled down
 * from the top. Thus the first visible non-fixed row is toprow+fixedrows
 */
#define TM_TOPROW       (WM_USER+5)

/* Return the end row visible. This is the 0-based rownr of the last
 * visible row in the window
 */
#define TM_ENDROW       (WM_USER+6)

/* New rows have been added to the end of the table, but no other
 * rows or cols or properties have been changed.
 * wParam contains the new total nr of rows. lParam contains the id
 * in case this has changed.
 */
#define TM_APPEND       (WM_USER+7)

/*-----display properties -------------------------------------------------*/

/*
 * Display properties struct. can be set for whole table, for
 * each column, or for each cell. When looking for
 * a property, we search cell->column->table
 */
typedef struct {
        UINT valid;             /* flags (below) for what props we set */

/* remaining fields only valid when corresponding flag set in valid */

        DWORD forecolour;       /* RGB colour value */
        DWORD backcolour;       /* ditto */
        /* font to use - also set through WM_SETFONT. owner application
         * is responsible for DeleteObject call when no longer used
         */
        HFONT hFont;            /* handle to font  - caller should delete*/
        UINT alignment;         /* flags below */
        UINT box;               /* whether cell boxed (see below) */

        /* width/height settings not valid at cell level - only table or col.*/
        int width;              /* pixel width of this cell/column */
        int height;             /* pixel cell height */
} Props, FAR * lpProps;

/* Valid flags for fields that are changed in this Props struct */
#define P_FCOLOUR       1
#define P_BCOLOUR       2
#define P_FONT          4
#define P_ALIGN         8
#define P_BOX           0x20
#define P_WIDTH         0x40
#define P_HEIGHT        0x80

/* Box settings  or-ed together */
#define P_BOXTOP        1
#define P_BOXBOTTOM     2
#define P_BOXLEFT       4
#define P_BOXRIGHT      8
#define P_BOXALL        0xF

/* Alignment settings (expand later to include various tab-align settings */
#define P_LEFT          0
#define P_RIGHT         1
#define P_CENTRE        2

/* This struct is the master information about a table. It is
 * passed to the owner window with the id field filled in; fill in
 * all remaining fields and return.
 */
typedef struct {
        DWORD id;               /* owner's data id */

        long nrows;             /* how many rows ? TM_REFRESH to change */
        int ncols;              /* how many columns ? TM_NEWLAYOUT to chg */

        int fixedrows;          /* for headers - usually 0 or 1 */
        int fixedcols;          /* for hdrs - 0 or 1 normally */
        BOOL fixedselectable;   /* is fixed area selectable ? */
        BOOL hseparator;        /* is there a horz. line after fixed rows */
        BOOL vseparator;        /* is there a vert. line after fixed rows */

        UINT selectmode;        /* multiple/single selection - flags below*/
        BOOL sendscroll;        /* TRUE if TQ_SCROLL to be sent on scrolling*/

        Props props;
} TableHdr, FAR * lpTableHdr;

/*
 * selection mode;
 *
 * choose TM_CELL or TM_ROW, and TM_SINGLE or TM_MANY, and
 * TM_SOLID or TM_FOCUS and or them together.
 *
 * current implementation does not support TM_MANY !!
 */
#define TM_ROW          1       /* selectable items are rows */
#define TM_CELL         0       /* selectable items are cells */

#define TM_MANY         2       /* multiple selects possible */
#define TM_SINGLE       0       /* single item selectable at once only */

#define TM_SOLID        0       /* (default) use a solid black for selection*/
#define TM_FOCUS        4       /* use a dotted focus rect for selection */

/* --------- column header structs --------------------------------------*/

/*
 * This struct is sent to request column width and properties -
 * owner window must fill nchars and props.valid, at minimum.
 */
typedef struct {
        int nchars;     /* expected text width in chars */
        Props props;
} ColProps, FAR * lpColProps;

/* This is a set of column requests - owner should fill each one*/
typedef struct {
        DWORD id;               /* caller's id for data */
        int startcol;           /* zero-based column nr of first request */
        int ncols;              /* nr of columns in this set */
        lpColProps plist;       /* ptr to _array_ of ColProps */
} ColPropsList, FAR * lpColPropsList;

/* --- cell data structs ---------------------------------------------*/

/* This is the per-cell data struct.
 * When providing data (responding to TQ_GETDATA), fill out ptext[] and
 * props as appropriate. ptext will be pre-allocated with nchars bytes of
 * space. This may be larger than ColProps->nchars if the user has
 * stretched this column's width on screen
 *
 * Don't re-alloc ptext, or change flags.
 */
typedef struct {
        int nchars;             /* space in buffer */
        LPSTR ptext;            /* ptr to nchars of text space */
        Props props;            /* per-cell props */
        DWORD flags;            /* private table class flags */
} CellData, FAR * lpCellData;

/* List of cell data structures - please fill out all of these*/
typedef struct {
        DWORD id;               /* caller's id for data */
        long row;               /* zero-based row nr to fetch */
        int startcell;          /* zero-based cell nr on this row */
        int ncells;             /* count of cells to fetch */
        lpCellData plist;       /* ptr to array CellData[ncells] */
} CellDataList, FAR * lpCellDataList;

/*----- current selection----------------------------------------------*/

/* Describes the current selection - a rectangular selection area */
typedef struct {
        DWORD id;               /* caller's id for data */
        long startrow;          /* zero-based row nr of start of sel. */
        long startcell;         /* zero-based col nr of  start of sel */
        long nrows;             /* vertical depth of selection */
        long ncells;            /* horz width of selection */
} TableSelection, FAR * lpTableSelection;

/*----- print context -----------------------------------------------*/

/* Describes the margin settings for the print job - these are in CMs*/
typedef struct {
        int left;               /* edge of paper to start of print area */
        int right;              /* edge of paper to start of print area */
        int top;                /* edge of paper to start of hdr */
        int bottom;             /* end of hdr to end of paper */
        int topinner;           /* start of hdr to start of data */
        int bottominner;        /* end of data to start of hdr */
} Margin, FAR * lpMargin;

/* Position and clipping info - only used by table class
 */
typedef struct {
        int start;              /* co-ord of cell start (left or top) */
        int clipstart;          /* start of clipping (vis area) */
        int clipend;            /* end of clipping (vis area) */
        int size;               /* pixel size of cell (width or height) */
} CellPos, FAR * lpCellPos;

/* One of these for each header lines (top and bottom) */
typedef struct {
        CellPos xpos, ypos;     /* private: for table-class use only */
        Props props;
        LPSTR ptext;
} Title, FAR * lpTitle;

/* Print context data structure - any or all 4 pointers may be null */
typedef struct {
        DWORD id;               /* id of table to print */
        lpTitle head;
        lpTitle foot;
        lpMargin margin;
        PRINTDLG FAR * pd;
} PrintContext, FAR * lpPrintContext;


TPAINT.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 Header *******************************
* Module Name: TPAINT.C
*
* Paint functions
*
* Functions:
*
* GetTextExtent()
* gtab_updatecontig()
* gtab_delcr()
* gtab_updateline()
* gtab_updatecontig()
* gtab_boxcell()
* gtab_paintcell()
* gtab_paint()
* gtab_vsep()
* gtab_hsep()
* gtab_drawvertline()
* gtab_invertsel()
*
* Comments:
*
* See table.h for interface design.
*
****************************************************************************/
#include <string.h>
#include <windows.h>
#include <commdlg.h>

#include "gutils.h"
#include "table.h"
#include "tpriv.h"

/***************************************************************************
 * Function: GetTextExtent
 *
 * Purpose:
 *
 * Calls GetTextExtentPoint - for ease of porting.
 */
 int
GetTextExtent(HDC hdc, LPSTR text, int len)
{
    SIZE sz;

    GetTextExtentPoint(hdc, text, len, &sz);
    return(sz.cx);
}

void gtab_updatecontig(HWND hwnd, lpTable ptab, int line, int cell1, int count);

/***************************************************************************
 * Function: gtab_delcr
 *
 * Purpose:
 *
 * change all cr/lf chars in input text to spaces
 */
void gtab_delcr(LPSTR ptext)
{
        LPSTR chp;

        if (ptext == NULL) {
                return;
        }
        for(chp = ptext; (chp = strchr(chp, '\r')) != NULL; ) {
                *chp = ' ';
        }
        for(chp = ptext; (chp = strchr(chp, '\n')) != NULL; ) {
                *chp = ' ';
        }
}

/***************************************************************************
 * Function: gtab_updateline
 *
 * Purpose:
 *
 * Ensures that all visible cells in the given line have valid
 * text and property contents. loop through the cells, picking out
 * contiguous blocks of visible, invalid cells and call
 * gtab_updatecontig to update these from the owner window.
 */
void
gtab_updateline(HWND hwnd, lpTable ptab, int line)
{
        lpCellPos ppos;
        int cell1, cellcount;
        lpLineData pline;
        lpCellData cd;
        int i;

        pline = &ptab->pdata[line];
        cell1 = 0;
        cellcount = 0;
        for (i = 0; i < ptab->hdr.ncols; i++) {
                ppos = &ptab->pcellpos[i];
                cd = &pline->pdata[i];
                if (ppos->clipstart < ppos->clipend) {
                        if ((cd->flags & CELL_VALID) == 0) {
                                /* add a cell to the list to be updated*/
                                if (cellcount++ == 0) {
                                        cell1 = i;
                                }
                        } else {
                                /* this cell already valid - so end of
                                 * a contig block. if the contig
                                 * block just ended contained cells to update,
                                 * do it now
                                 */
                                if (cellcount > 0) {
                                        gtab_updatecontig(hwnd, ptab,
                                          line, cell1, cellcount);
                                }
                                cellcount = 0;
                        }
                }
                /* cell not visible - end of a contig block. If it was a
                 * non-empty contig block, then update it now.
                 */
                if (cellcount > 0)  {
                        gtab_updatecontig(hwnd, ptab, line, cell1, cellcount);
                        cellcount = 0;
                }
        }
        if (cellcount > 0) {
                gtab_updatecontig(hwnd, ptab, line, cell1, cellcount);
                cellcount = 0;
        }
}

/***************************************************************************
 * Function: gtab_updatecontig
 *
 * Purpose:
 *
 * Updates a contiguous block of invalid cells by calling the owner window
 */
void
gtab_updatecontig(HWND hwnd, lpTable ptab, int line, int cell1, int count)
{
        lpLineData pline;
        lpCellData cd;
        CellDataList list;
        lpProps colprops;
        int i;

        pline = &ptab->pdata[line];
        cd = &pline->pdata[cell1];

        list.id = ptab->hdr.id;
        list.row = gtab_linetorow(hwnd, ptab, line);
        list.startcell = cell1;
        list.ncells = count;
        list.plist = cd;

        /* clear out prop flags */
        for (i = 0; i < count; i++) {
                cd[i].props.valid = 0;
                if (cd[i].nchars > 0) {
                        cd[i].ptext[0] = '\0';
                }
        }

        if (list.row < ptab->hdr.nrows) {
                gtab_sendtq(hwnd, TQ_GETDATA, (long) (LPSTR) &list);
        }

        /* for each cell, mark valid and set properties */
        for (i = 0; i < count; i++) {
                cd[i].flags |= CELL_VALID;
                gtab_delcr(cd[i].ptext);
                /* fetch properties from hdr and colhdr */
                colprops = &ptab->pcolhdr[i + cell1].props;
                if (!(cd[i].props.valid & P_FCOLOUR)) {
                        if (colprops->valid & P_FCOLOUR) {
                                cd[i].props.valid |= P_FCOLOUR;
                                cd[i].props.forecolour = colprops->forecolour;
                        } else if (ptab->hdr.props.valid & P_FCOLOUR) {
                                cd[i].props.valid |= P_FCOLOUR;
                                cd[i].props.forecolour =
                                        ptab->hdr.props.forecolour;
                        }
                }

                if (!(cd[i].props.valid & P_BCOLOUR)) {
                        if (colprops->valid & P_BCOLOUR) {
                                cd[i].props.valid |= P_BCOLOUR;
                                cd[i].props.backcolour = colprops->backcolour;
                        } else if (ptab->hdr.props.valid & P_BCOLOUR) {
                                cd[i].props.valid |= P_BCOLOUR;
                                cd[i].props.backcolour =
                                        ptab->hdr.props.backcolour;
                        }
                }

                if (!(cd[i].props.valid & P_FONT)) {
                        if (colprops->valid & P_FONT) {
                                cd[i].props.valid |= P_FONT;
                                cd[i].props.hFont = colprops->hFont;
                        } else if (ptab->hdr.props.valid & P_FONT) {
                                cd[i].props.valid |= P_FONT;
                                cd[i].props.hFont = ptab->hdr.props.hFont;
                        }
                }

                if (!(cd[i].props.valid & P_ALIGN)) {
                        if (colprops->valid & P_ALIGN) {
                                cd[i].props.valid |= P_ALIGN;
                                cd[i].props.alignment = colprops->alignment;
                        } else if (ptab->hdr.props.valid & P_ALIGN) {
                                cd[i].props.valid |= P_ALIGN;
                                cd[i].props.alignment =
                                        ptab->hdr.props.alignment;
                        }
                }

                if (!(cd[i].props.valid & P_BOX)) {
                        if (colprops->valid & P_BOX) {
                                cd[i].props.valid |= P_BOX;
                                cd[i].props.box = colprops->box;
                        } else if (ptab->hdr.props.valid & P_BOX) {
                                cd[i].props.valid |= P_BOX;
                                cd[i].props.box = ptab->hdr.props.box;
                        }
                }
                /* you can't set width/height per cell - this
                 * is ignored at cell level.
                 */
        }

}

/***************************************************************************
 * Function: gtab_boxcell
 *
 * Purpose:
 *
 * Draws box around a cell in a table.
 */
void
gtab_boxcell(HWND hwnd, HDC hdc, LPRECT rcp, LPRECT pclip, UINT boxmode)
{
        if (boxmode & P_BOXTOP) {
                MoveToEx(hdc, max(rcp->left, pclip->left),
                        max(rcp->top, pclip->top), NULL);
                LineTo(hdc, min(rcp->right, pclip->right),
                        max(rcp->top, pclip->top));
        }
        if (boxmode & P_BOXBOTTOM) {
                MoveToEx(hdc, max(rcp->left, pclip->left),
                        min(rcp->bottom, pclip->bottom), NULL);
                LineTo(hdc, min(rcp->right, pclip->right),
                        min(rcp->bottom, pclip->bottom));
        }
        if (boxmode & P_BOXLEFT) {
                MoveToEx(hdc, max(rcp->left, pclip->left),
                        max(rcp->top, pclip->top), NULL);
                MoveToEx(hdc, max(rcp->left, pclip->left),
                        min(rcp->bottom, pclip->bottom), NULL);
        }
        if (boxmode & P_BOXRIGHT) {
                MoveToEx(hdc, min(rcp->right, pclip->right),
                        max(rcp->top, pclip->top), NULL);
                LineTo(hdc, min(rcp->right, pclip->right),
                        min(rcp->bottom, pclip->bottom));
        }
}

/***************************************************************************
 * Function: gtab_paintcell
 *
 * Purpose:
 *
 * Paints a cell.
 */
void
gtab_paintcell(HWND hwnd, HDC hdc, lpTable ptab, int line, int cell)
{
        lpLineData pline;
        lpCellData cd;
        lpCellPos ppos;
        RECT rc, rcbox;
        int cx, x, y, tabwidth;
        UINT align;
        LPSTR chp, tabp;
        DWORD fcol, bkcol;
        HFONT hfont;
        TEXTMETRIC tm;
        HBRUSH hbr;

        /* init pointers to cell text and properties */
        pline = &ptab->pdata[line];
        cd = &pline->pdata[cell];
        ppos = &ptab->pcellpos[cell];

        /* clip all output to this rectangle */
        rc.top = pline->linepos.clipstart;
        rc.bottom = pline->linepos.clipend;
        rc.left = ppos->clipstart;
        rc.right = ppos->clipend;

        /* check cell properties and colours */
        if (cd->props.valid & P_ALIGN) {
                align = cd->props.alignment;
        } else {
                align = P_LEFT;
        }
        if (cd->props.valid & P_FONT) {
                hfont = SelectObject(hdc, cd->props.hFont);
                GetTextMetrics(hdc, &tm);
                tabwidth = tm.tmAveCharWidth * 8;
        } else {
                tabwidth = ptab->avewidth * 8;
        }

        /* set colours if not default */
        if (cd->props.valid & P_FCOLOUR) {
                fcol = SetTextColor(hdc, cd->props.forecolour);
        }
        if (cd->props.valid & P_BCOLOUR) {
                /* there is a non-default background colour.
                 * create a brush and fill the entire cell with it
                 */
                hbr = CreateSolidBrush(cd->props.backcolour);
                FillRect(hdc, &rc, hbr);
                DeleteObject(hbr);

                /* also set colour as background colour for the text */
                bkcol = SetBkColor(hdc, cd->props.backcolour);
        }

        /* calc offset of text within cell for right-align or centering */
        if (align == P_LEFT) {
                cx = ptab->avewidth/2;
        } else {
                if (cd->ptext == NULL) {
                        cx = 0;
                } else {
                        cx = LOWORD(GetTextExtent(hdc, cd->ptext,
                                        lstrlen(cd->ptext)));
                }
                if (align == P_CENTRE) {
                        cx = (ppos->size - cx) / 2;
                } else {
                        cx = ppos->size - cx - (ptab->avewidth/2);
                }
        }
        cx += ppos->start;

        /* expand tabs on output */
        x = 0;
        y = pline->linepos.start;

        for (chp = cd->ptext;
            ((chp != NULL) && ((tabp = strchr(chp, '\t')) != NULL)); ) {
                /* perform output upto tab char */
                ExtTextOut(hdc, x+cx, y, ETO_CLIPPED, &rc, chp, tabp-chp, NULL);

                /* advance past the tab */
                x += LOWORD(GetTextExtent(hdc, chp, tabp - chp));
                x = ( (x + tabwidth) / tabwidth) * tabwidth;
                chp = ++tabp;
        }

        /*no more tabs - output rest of string */
        if (chp != NULL) {
                ExtTextOut(hdc, x+cx, y, ETO_CLIPPED, &rc,
                                chp, lstrlen(chp), NULL);
        }

        /* reset colours to original if not default */
        if (cd->props.valid & P_FCOLOUR) {
                SetTextColor(hdc, fcol);
        }
        if (cd->props.valid & P_BCOLOUR) {
                SetBkColor(hdc, bkcol);
        }
        if (cd->props.valid & P_FONT) {
                SelectObject(hdc, hfont);
        }

        /* now box cell if marked */
        if (cd->props.valid & P_BOX) {
                if (cd->props.box != 0) {
                        rcbox.top = pline->linepos.start;
                        rcbox.bottom = rcbox.top + pline->linepos.size;
                        rcbox.left = ppos->start;
                        rcbox.right = ppos->start + ppos->size;
                        gtab_boxcell(hwnd, hdc, &rcbox, &rc, cd->props.box);
                }
        }
}

/***************************************************************************
 * Function: gtab_paint
 *
 * Purpose:
 *
 * Fetch and paint the specified line
 */
void
gtab_paint(HWND hwnd, HDC hdc, lpTable ptab, int line)
{
        lpCellPos ppos;
        int i;

        gtab_updateline(hwnd, ptab, line);

        for (i = 0; i < ptab->hdr.ncols; i++) {
                ppos = &ptab->pcellpos[i];
                if (ppos->clipstart < ppos->clipend) {
                        gtab_paintcell(hwnd, hdc, ptab, line, i);
                }
        }
}

/***************************************************************************
 * Function: gtab_vsep
 *
 * Purpose:
 *
 */
void
gtab_vsep(HWND hwnd, lpTable ptab, HDC hdc)
{
        int x;
        RECT rc;

        if (ptab->hdr.fixedcols < 1) {
                return;
        }
        x = ptab->pcellpos[ptab->hdr.fixedcols - 1].clipend+1;
        GetClientRect(hwnd, &rc);
        MoveToEx(hdc, x, rc.top, NULL);
        LineTo(hdc, x, rc.bottom);
}

/***************************************************************************
 * Function: gtab_hsep
 *
 * Purpose:
 */
void
gtab_hsep(HWND hwnd, lpTable ptab, HDC hdc)
{
        int y;
        RECT rc;

        if (ptab->hdr.fixedrows < 1) {
                return;
        }
        y = ptab->rowheight * ptab->hdr.fixedrows;
        GetClientRect(hwnd, &rc);
        MoveToEx(hdc, rc.left, y-1, NULL);
        LineTo(hdc, rc.right, y-1);
}

/***************************************************************************
 * Function: gtab_drawverline
 *
 * Purpose:
 *
 * Draw in (inverting) the dotted selection lines for tracking a col width
 */
void
gtab_drawvertline(HWND hwnd, lpTable ptab)
{
        RECT rc;
        HDC hdc;
        HPEN hpen;

        hdc = GetDC(hwnd);
        SetROP2(hdc, R2_XORPEN);
        hpen = SelectObject(hdc, hpenDotted);
        GetClientRect(hwnd, &rc);

        MoveToEx(hdc, ptab->trackline1, rc.top, NULL);
        LineTo(hdc, ptab->trackline1, rc.bottom);
        if (ptab->trackline2 != -1) {
                MoveToEx(hdc, ptab->trackline2, rc.top, NULL);
                LineTo(hdc, ptab->trackline2, rc.bottom);
        }

        SelectObject(hdc, hpen);
        ReleaseDC(hwnd, hdc);
}

/***************************************************************************
 * Function: gtab_invertsel
 *
 * Purpose:
 *
 * Mark the selected line, if visible, in the style chosen by the
 * client app. This can be TM_SOLID, meaning an inversion of
 * the whole selected area or TM_FOCUS, meaning, inversion of the first
 * cell, and then a dotted focus rectangle for the rest.
 *
 * This function inverts either style, and so will turn the selection
 * both on and off.
 */
void
gtab_invertsel(HWND hwnd, lpTable ptab, HDC hdc_in)
{
        HDC hdc;
        int line;
        RECT rc;
        int lastcell;

        /* is row visible on screen ?  */
        line = gtab_rowtoline(hwnd, ptab, ptab->select.startrow);
        if (line < 0) {
                return;
        }

        /* selection mode includes a flag TM_FOCUS indicating we should
         * use a focus rect instead of the traditional inversion for
         * selections in this table. This interferes with multiple backgrnd
         * colours less.  However we still do inversion for fixedcols.
         */

        lastcell = (int)(ptab->select.startcell + ptab->select.ncells - 1);

        rc.top = ptab->pdata[line].linepos.clipstart;
        rc.bottom = ptab->pdata[line].linepos.clipend;

        /*
         * invert the whole area for TM_SOLID or just the first
         * cell for TM_FOCUS
         */
        rc.left = ptab->pcellpos[ptab->select.startcell].clipstart;
        if (ptab->hdr.selectmode & TM_FOCUS) {
                rc.right = ptab->pcellpos[ptab->select.startcell].clipend;
        }else {
                rc.right = ptab->pcellpos[lastcell].clipend;
        }

        if (hdc_in == NULL) {
                hdc = GetDC(hwnd);
        } else {
                hdc = hdc_in;
        }

        InvertRect(hdc, &rc);

        /*
         * draw focus rectangle around remaining cells on this line, if there
         * are any
         */
        if (ptab->hdr.selectmode & TM_FOCUS) {
                if (ptab->select.ncells > 1) {
                        rc.left = ptab->pcellpos[ptab->select.startcell+1].clipstart;
                        rc.right = ptab->pcellpos[lastcell].clipend;
                        DrawFocusRect(hdc, &rc);
                }
        }

        if (hdc_in == NULL) {
                ReleaseDC(hwnd, hdc);
        }
}


TPRINT.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 Header *******************************
* Module Name: TPRINT.C
*
* Print functions.
*
* Functions:
*
* gtab_print()
* gtab_printsetup()
* gtab_prtwidths()
* gtab_printjob()
* AbortProc()
* AbortDlg()
* gtab_printpage()
* gtab_setrects()
* gtab_printhead()
*
* Comments:
*
* See table.h for interface description.
*
****************************************************************************/

#include <string.h>
#include <windows.h>
#include <commdlg.h>

#include "gutils.h"
#include "gutilsrc.h"
#include "table.h"
#include "tpriv.h"

/* in tpaint.c, calls GetTextExtentPoint */
extern int GetTextExtent(HDC, LPSTR, int);

extern HANDLE hLibInst;

/* function prototypes */
lpTable gtab_printsetup(HWND hwnd, lpTable ptab, HANDLE heap,
        lpPrintContext pcontext);
BOOL gtab_prtwidths(HWND hwnd, lpTable ptab, HANDLE heap, lpPrintContext
        pcontext);
void gtab_printjob(HWND hwnd, lpTable ptab, lpPrintContext pcontext);
int APIENTRY AbortProc(HDC hpr, int code);
int APIENTRY AbortDlg(HWND hdlg, UINT msg, UINT wParam, LONG lParam);
BOOL gtab_printpage(HWND hwnd, lpTable ptab, lpPrintContext pcontext, int page);
void gtab_setrects(lpPrintContext pcontext, LPRECT rcinner, LPRECT rcouter);
void gtab_printhead(HWND hwnd, HDC hdc, lpTable ptab, lpTitle head, int page);

/***************************************************************************
 * Function: gtab_print
 *
 * Purpose:
 *
 * Prints a table.
 */
void
gtab_print(HWND hwnd, lpTable ptab, HANDLE heap, lpPrintContext pcontext)
{
        BOOL fNoContext, fNoMargin, fNoPD;
        lpTable ptab_prt;

        fNoContext = FALSE;
        fNoPD = FALSE;
        fNoMargin = FALSE;

        if (pcontext == NULL) {
                fNoContext = TRUE;
                pcontext = (lpPrintContext) gmem_get(heap,
                        sizeof(PrintContext));
                pcontext->head = pcontext->foot = NULL;
                pcontext->margin = NULL;
                pcontext->pd = NULL;
                pcontext->id = 0;
        }
        if (pcontext->pd == NULL) {
                fNoPD = TRUE;
        }
        if (pcontext->margin == NULL) {
                fNoMargin = TRUE;
        }
        ptab_prt = gtab_printsetup(hwnd, ptab, heap, pcontext);

        if (ptab_prt != NULL) {
                gtab_printjob(hwnd, ptab_prt, pcontext);

                gtab_deltable(hwnd, ptab_prt);
        }
        if (fNoMargin) {
                gmem_free(heap, (LPSTR)pcontext->margin,
                        sizeof(Margin));
                pcontext->margin = NULL;
        }
        if (fNoPD) {
                if (pcontext->pd->hDevMode != NULL) {
                        GlobalFree(pcontext->pd->hDevMode);
                }
                if (pcontext->pd->hDevNames != NULL) {
                        GlobalFree(pcontext->pd->hDevNames);
                }
                gmem_free(heap, (LPSTR) pcontext->pd, sizeof(PRINTDLG));
                pcontext->pd = NULL;
        }
        if (fNoContext) {
                gmem_free(heap, (LPSTR) pcontext, sizeof(PrintContext));
        }
}

/***************************************************************************
 * Function: gtab_printsetup
 *
 * Purpose:
 *
 * Sets up printercontext - builds lpTable for printer, incl. sizing
 * and initialises pcontext fields that may be null.
 */
lpTable
gtab_printsetup(HWND hwnd, lpTable ptab, HANDLE heap, lpPrintContext pcontext)
{
        lpTable pprttab;
        PRINTDLG FAR * pd;
        int ncols, i;
        ColPropsList cplist;

        /* set fields for context that user left null */
        if (pcontext->margin == NULL) {
                pcontext->margin = (lpMargin) gmem_get(heap, sizeof(Margin));
                if (pcontext->margin == NULL) {
                        return(NULL);
                }
                pcontext->margin->left = 10;
                pcontext->margin->right = 10;
                pcontext->margin->top = 15;
                pcontext->margin->bottom = 15;
                pcontext->margin->topinner = 15;
                pcontext->margin->bottominner = 15;
        }

        if (pcontext->pd == NULL) {
                pd = (PRINTDLG FAR *) gmem_get(heap, sizeof(PRINTDLG));
                if (pd == NULL) {
                        return(NULL);
                }
                pcontext->pd = pd;

                pd->lStructSize = sizeof(PRINTDLG);
                pd->hwndOwner = hwnd;
                pd->hDevMode = (HANDLE) NULL;
                pd->hDevNames = (HANDLE) NULL;
                pd->Flags = PD_RETURNDC|PD_RETURNDEFAULT;

                if (PrintDlg(pd) == FALSE) {
                        return(NULL);
                }
        }

        /* now create a Table struct by querying the owner */
        pprttab = (lpTable) gmem_get(heap, sizeof(Table));

        if (pprttab == NULL) {
                return(NULL);
        }
        pprttab->hdr = ptab->hdr;

        /* get the row/column count from owner window */
        if (pcontext->id == 0) {
                pprttab->hdr.id = ptab->hdr.id;
        } else {
                pprttab->hdr.id = pcontext->id;
        }
        pprttab->hdr.props.valid = 0;
        pprttab->hdr.sendscroll = FALSE;
        if (gtab_sendtq(hwnd, TQ_GETSIZE, (long) (LPSTR)&pprttab->hdr) == FALSE) {
                return(NULL);
        }

        /* alloc and init the col data structs */
        ncols = pprttab->hdr.ncols;
        pprttab->pcolhdr = (lpColProps) gmem_get(heap, sizeof(ColProps) * ncols);
        if (pprttab->pcolhdr == NULL) {
                gmem_free(heap, (LPSTR)pprttab, sizeof(Table));
                return(NULL);
        }

        /* init col properties to default */
        for (i=0; i < ncols; i++) {
                pprttab->pcolhdr[i].props.valid = 0;
                pprttab->pcolhdr[i].nchars = 0;
        }
        /* get the column props from owner */
        cplist.plist = pprttab->pcolhdr;
        cplist.id = pprttab->hdr.id;
        cplist.startcol = 0;
        cplist.ncols = ncols;
        gtab_sendtq(hwnd, TQ_GETCOLPROPS, (long) (LPSTR)&cplist);

        pprttab->scrollscale = 1;
        pprttab->pcellpos = (lpCellPos) gmem_get(heap,
                sizeof(CellPos) * ptab->hdr.ncols);
        if (pprttab->pcellpos == NULL) {
                gmem_free(heap, (LPSTR) pprttab->pcolhdr, sizeof(ColProps) * ncols);
                gmem_free(heap, (LPSTR)pprttab, sizeof(Table));
                return(NULL);
        }

        pprttab->pdata = NULL;
        pprttab->nlines = 0;

        if (!gtab_prtwidths(hwnd, pprttab, heap, pcontext)) {
                gmem_free(heap, (LPSTR)pprttab->pcellpos,
                        sizeof(CellPos) * ptab->hdr.ncols);
                gmem_free(heap, (LPSTR)pprttab, sizeof(Table));
                return(NULL);
        }
        return(pprttab);
}

/***************************************************************************
 * Function: gtab_prtwidths
 *
 * Purpose:
 *
 * Calc the height/width settings and alloc line data
 */
BOOL
gtab_prtwidths(HWND hwnd, lpTable ptab, HANDLE heap, lpPrintContext pcontext)
{
        TEXTMETRIC tm;
        int cx, cxtotal, i, curx, cury;
        lpProps hdrprops, cellprops;
        lpCellPos xpos, ypos;
        RECT rcinner, rcouter;

        hdrprops = &ptab->hdr.props;
        GetTextMetrics(pcontext->pd->hDC, &tm);
        ptab->avewidth = tm.tmAveCharWidth;
        ptab->rowheight = tm.tmHeight + tm.tmExternalLeading;
        if (hdrprops->valid & P_HEIGHT) {
                ptab->rowheight = hdrprops->height;
        }

        /* set sizes for headers */
        gtab_setrects(pcontext, &rcinner, &rcouter);

        /* set width/pos for each col. */
        cxtotal = 0;
        curx = rcinner.left;
        for (i = 0; i < ptab->hdr.ncols; i++) {
                cellprops = &ptab->pcolhdr[i].props;
                xpos = &ptab->pcellpos[i];

                if (cellprops->valid & P_WIDTH) {
                        cx = cellprops->width;
                } else if (hdrprops->valid & P_WIDTH) {
                        cx = hdrprops->width;
                } else {
                        cx = ptab->pcolhdr[i].nchars + 1;
                        cx *= ptab->avewidth;
                }
                /* add 2 for intercol spacing */
                cx += 2;

                xpos->size = cx;
                xpos->start = curx + 1;
                xpos->clipstart = xpos->start;
                xpos->clipend = xpos->start + xpos->size - 2;
                xpos->clipend = min(xpos->clipend, rcinner.right);

                cxtotal += xpos->size;
                curx += xpos->size;
        }
        ptab->rowwidth = cxtotal;

        if(pcontext->head != NULL) {
                xpos = &pcontext->head->xpos;
                ypos = &pcontext->head->ypos;

                xpos->start = rcouter.left + 1;
                xpos->clipstart = rcouter.left + 1;
                xpos->clipend = rcouter.right - 1;
                xpos->size = rcouter.right - rcouter.left;

                ypos->start = rcouter.top;
                ypos->clipstart = rcouter.top;
                ypos->clipend = rcinner.top;
                ypos->size = ptab->rowheight;
        }

        if (pcontext->foot != NULL) {
                xpos = &pcontext->foot->xpos;
                ypos = &pcontext->foot->ypos;

                xpos->start = rcouter.left + 1;
                xpos->clipstart = rcouter.left + 1;
                xpos->clipend = rcouter.right - 1;
                xpos->size = rcouter.right - rcouter.left;

                ypos->start = rcouter.bottom - ptab->rowheight;
                ypos->clipstart = rcinner.bottom;
                ypos->clipend = rcouter.bottom;
                ypos->size = ptab->rowheight;
        }

        /* set nr of lines per page */
        ptab->nlines = (rcinner.bottom - rcinner.top) / ptab->rowheight;
        if (!gtab_alloclinedata(hwnd, heap, ptab)) {
                return(FALSE);
        }
        /* set line positions */
        cury = rcinner.top;
        for (i = 0; i < ptab->nlines; i++) {
                ypos = &ptab->pdata[i].linepos;
                ypos->start = cury;
                ypos->clipstart = ypos->start;
                ypos->clipend = ypos->start + ypos->size;
                ypos->clipend = min(ypos->clipend, rcinner.bottom);
                cury += ypos->size;
        }
        return(TRUE);
}

/* static information for this module */
BOOL bAbort;
FARPROC lpAbortProc;
DLGPROC lpAbortDlg;
HWND hAbortWnd;
int npage;
int pages;

/***************************************************************************
 * Function: gtab_printjob
 *
 * Purpose:
 *
 * Sets up print job and dialogs
 */
void
gtab_printjob(HWND hwnd, lpTable ptab, lpPrintContext pcontext)
{
        int moveables;
        int endpage;
        int startpage = 1;
        HDC hpr;
        int status;
        HANDLE hcurs;
        static char str[256];
        DOCINFO di;

        CHAR szPage[60];  /* for LoadString */

        hcurs = SetCursor(LoadCursor(NULL, IDC_WAIT));

        moveables = ptab->nlines - ptab->hdr.fixedrows;
        pages = (int) (ptab->hdr.nrows - ptab->hdr.fixedrows + moveables - 1)
                        / moveables;
        endpage = pages;

        if (pcontext->pd->Flags & PD_PAGENUMS) {
                startpage = pcontext->pd->nFromPage;
                endpage = pcontext->pd->nToPage;
        }
        hpr = pcontext->pd->hDC;

        lpAbortDlg = (DLGPROC) MakeProcInstance((WNDPROC) AbortDlg, hLibInst);
        lpAbortProc = (FARPROC) MakeProcInstance((WNDPROC)AbortProc, hLibInst);

        SetAbortProc(hpr, (ABORTPROC) lpAbortProc);

        ZeroMemory( &di, sizeof( DOCINFO ) );
        di.lpszDocName = "Table";
        di.cbSize = sizeof( DOCINFO );
        di.lpszOutput = NULL;

        StartDoc(hpr, &di);

        bAbort = FALSE;

        /* add abort modeless dialog later!! */
        hAbortWnd = CreateDialog(hLibInst, "GABRTDLG", hwnd, lpAbortDlg);
        if (hAbortWnd != NULL) {
                ShowWindow(hAbortWnd, SW_NORMAL);
                EnableWindow(hwnd, FALSE);
        }
        SetCursor(hcurs);

        for (npage = startpage; npage<=endpage; npage++) {
                LoadString(hLibInst, IDS_PAGE_STR, szPage, sizeof(szPage));
                wsprintf(str, szPage,  npage, pages);
                SetDlgItemText(hAbortWnd, IDC_LPAGENR, str);
                status = gtab_printpage(hwnd, ptab, pcontext, npage);
                if (status < 0) {
                        AbortDoc(hpr);
                        break;
                }
        }
        if (status >= 0) {
                EndDoc(hpr);
        }

        if (hAbortWnd != NULL) {
                EnableWindow(hwnd, TRUE);
                DestroyWindow(hAbortWnd);
        }
        FreeProcInstance((WNDPROC) lpAbortDlg);
        FreeProcInstance(lpAbortProc);

        DeleteDC(hpr);
}

/***************************************************************************
 * Function: AbortProc
 *
 * Purpose:
 *
 * Abort procedure for print job
 */
int APIENTRY
AbortProc(HDC hpr, int code)
{

        MSG msg;

        if (!hAbortWnd) {
                return(TRUE);
        }
        while (!bAbort && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
                if (!IsDialogMessage(hAbortWnd, &msg)) {
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                }
        }
        return(!bAbort);
}

/***************************************************************************
 * Function: AbortDlg
 *
 * Purpose:
 *
 * Dialog for abort procedure
 */
int APIENTRY
AbortDlg(HWND hdlg, UINT msg, UINT wParam, LONG lParam)
{
        switch(msg) {

        case WM_COMMAND:
                bAbort = TRUE;
                DestroyWindow (hdlg);
                return TRUE;

        case WM_INITDIALOG:
                return TRUE;
        }
        return(FALSE);
}

/***************************************************************************
 * Function: gtab_printpage
 *
 * Purpose:
 *
 * Print a single page. page number is 1-based
 */
BOOL
gtab_printpage(HWND hwnd, lpTable ptab, lpPrintContext pcontext, int page)
{
        HDC hpr;
        int moveables, i;
        int x1, y1, x2, y2;

        hpr = pcontext->pd->hDC;
        StartPage(hpr);

        moveables = ptab->nlines - ptab->hdr.fixedrows;
        ptab->toprow = moveables * (page-1);
        gtab_invallines(hwnd, ptab, ptab->hdr.fixedrows, moveables);

        for (i =0; i < ptab->nlines; i++) {
                gtab_paint(hwnd, hpr, ptab, i);
        }
        if ((ptab->hdr.vseparator) && (ptab->hdr.fixedcols > 0)) {
                x1 = ptab->pcellpos[ptab->hdr.fixedcols -1].clipend+1;
                y1 = ptab->pdata[0].linepos.clipstart;
                y2 = ptab->pdata[ptab->nlines-1].linepos.clipend;
                MoveToEx(hpr, x1, y1, NULL);
                LineTo(hpr, x1, y2);
        }
        if ((ptab->hdr.hseparator) && (ptab->hdr.fixedrows > 0)) {
                y1 = ptab->pdata[ptab->hdr.fixedrows-1].linepos.clipend;
                x1 = ptab->pcellpos[0].clipstart;
                x2 = ptab->pcellpos[ptab->hdr.ncols-1].clipend;
                MoveToEx(hpr, x1, y1, NULL);
                LineTo(hpr, x2, y1);
        }

        if (pcontext->head != NULL) {
                gtab_printhead(hwnd, hpr, ptab, pcontext->head, page);
        }
        if (pcontext->foot != NULL) {
                gtab_printhead(hwnd, hpr, ptab, pcontext->foot, page);
        }

        return(EndPage(hpr));
}

/***************************************************************************
 * Function: gtab_setrects
 *
 * Purpose:
 *
 * Calculate the outline positions in pixels for the headers
 * (outer rect) and for the page itself (inner rect). Based on
 * page size and PrintContext margin info (which is in millimetres).
 */
void
gtab_setrects(lpPrintContext pcontext, LPRECT rcinner, LPRECT rcouter)
{
        HDC hpr;
        int hpixels, hmms;
        int vpixels, vmms;
        int h_pixpermm, v_pixpermm;

        hpr = pcontext->pd->hDC;
        hpixels = GetDeviceCaps(hpr, HORZRES);
        vpixels = GetDeviceCaps(hpr, VERTRES);
        vmms = GetDeviceCaps(hpr, VERTSIZE);
        hmms = GetDeviceCaps(hpr, HORZSIZE);

        h_pixpermm = hpixels / hmms;
        v_pixpermm = vpixels / vmms;

        rcouter->top = (pcontext->margin->top * v_pixpermm);
        rcouter->bottom = vpixels - (pcontext->margin->bottom * v_pixpermm);
        rcouter->left = (pcontext->margin->left * h_pixpermm);
        rcouter->right = hpixels - (pcontext->margin->right * h_pixpermm);

        rcinner->left = rcouter->left;
        rcinner->right = rcouter->right;
        rcinner->top = rcouter->top +
                (pcontext->margin->topinner * v_pixpermm);
        rcinner->bottom = rcouter->bottom -
                (pcontext->margin->bottominner * v_pixpermm);
}

/***************************************************************************
 * Function: gtab_printhead
 *
 * Purpose:
 *
 * Print header information
 */
void
gtab_printhead(HWND hwnd, HDC hdc, lpTable ptab, lpTitle head, int page)
{
        RECT rc, rcbox;
        int i, cx, x, y, tab;
        UINT align;
        LPSTR chp, tabp;
        DWORD fcol, bkcol;
        char str[256];

        rc.top = head->ypos.clipstart;
        rc.bottom = head->ypos.clipend;
        rc.left = head->xpos.clipstart;
        rc.right = head->xpos.clipend;

        /* update page number */
        chp = str;
        for (i = 0; i < lstrlen(head->ptext); i++) {
                switch(head->ptext[i]) {

                case '#':
                        chp += wsprintf(chp, "%d", page);
                        break;

                case '$':
                        chp += wsprintf(chp, "%d", pages);
                        break;

                default:
                        if (IsDBCSLeadByte(head->ptext[i]) &&
                            head->ptext[i+1]) {
                                *chp = head->ptext[i];
                                chp++;
                                i++;
                        }
                        *chp++ = head->ptext[i];
                        break;
                }
        }
        *chp = '\0';
        chp = str;

        if (head->props.valid & P_ALIGN) {
                align = head->props.alignment;
        } else {
                align = P_LEFT;
        }

        /* set colours if not default */
        if (head->props.valid & P_FCOLOUR) {
                fcol = SetTextColor(hdc, head->props.forecolour);
        }
        if (head->props.valid & P_BCOLOUR) {
                bkcol = SetBkColor(hdc, head->props.backcolour);
        }

        /* calc offset of text within cell for right-align or centering */
        if (align == P_LEFT) {
                cx = ptab->avewidth/2;
        } else {
                cx = LOWORD(GetTextExtent(hdc, chp, lstrlen(chp)));
                if (align == P_CENTRE) {
                        cx = (head->xpos.size - cx) / 2;
                } else {
                        cx = head->xpos.size - cx - (ptab->avewidth/2);
                }
        }
        cx += head->xpos.start;

        /* expand tabs on output */
        tab = ptab->avewidth * 8;
        x = 0;
        y = head->ypos.start;

        for ( ; (tabp = strchr(chp, '\t')) != NULL; ) {
                /* perform output upto tab char */
                ExtTextOut(hdc, x+cx, y, ETO_CLIPPED, &rc, chp, tabp-chp, NULL);

                /* advance past the tab */
                x += LOWORD(GetTextExtent(hdc, chp, tabp - chp));
                x = ( (x + tab) / tab) * tab;
                chp = ++tabp;
        }

        /*no more tabs - output rest of string */
        ExtTextOut(hdc, x+cx, y, ETO_CLIPPED, &rc, chp, lstrlen(chp), NULL);

        /* reset colours to original if not default */
        if (head->props.valid & P_FCOLOUR) {
                SetTextColor(hdc, fcol);
        }
        if (head->props.valid & P_BCOLOUR) {
                SetBkColor(hdc, bkcol);
        }

        /* now box cell if marked */
        if (head->props.valid & P_BOX) {
                if (head->props.box != 0) {
                        rcbox.top = head->ypos.start;
                        rcbox.bottom = rcbox.top + head->ypos.size;
                        rcbox.left = head->xpos.start;
                        rcbox.right = rcbox.left + head->xpos.size;
                        gtab_boxcell(hwnd, hdc, &rcbox, &rc, head->props.box);
                }
        }
}


TPRIV.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.
\******************************************************************************/

/*
 * TPRIV.H
 *
 * Data structures used internally by table class.
 *
 * Note: include after table.h
 */

#ifndef abs
#define abs(x)  (((x) > 0)? (x) : -(x))
#endif

/*  one of these per visible line */
typedef struct {
        CellPos linepos;        /* posn and clipping info for line */

        lpCellData pdata;       /* array of CellData structs for all cells */
} LineData, FAR * lpLineData;

/* master info struct pointed to by window extra bytes */

typedef struct {
        /* table info */
        TableHdr        hdr;            /* main hdr info from owner */
        lpColProps      pcolhdr;        /* ptr to array of phdr->ncols hdrs */

        /* window info */
        int     avewidth;       /* font ave width - for default cell sizing */
        int     rowheight;      /* height of one row */
        int     rowwidth;       /* total width of one row in pixels */
        int     winwidth;       /* width of window */
        int     nlines;         /* actual lines currently visible */

        lpCellPos pcellpos;     /* array of cell position structs */

        /* scroll settings */
        long    scrollscale;    /* scaling factor (force 16-bit range) */
        long    toprow;         /* 0-based rownr of top moveable line */
        int     scroll_dx;      /* horz scroll posn in pixels. */

        /* column data */
        lpLineData pdata;       /* ptr to array of nlines of LineData */

        /* selection/dragging */
        UINT    trackmode;      /* current mouse-tracking mode */
        int     tracknr;        /* col or row being resized */
        int     trackline1;     /* currently drawn track lines */
        int     trackline2;
        BOOL    selvisible;     /* used during mouse-down: T if sel drawn */
        TableSelection select;

} Table, FAR * lpTable;

/* trackmode constants */
#define TRACK_NONE              0
#define TRACK_COLUMN            1
#define TRACK_CELL              2

/* private flags in CellData struct */
#define CELL_VALID      1

/* window extra bytes are used to hold the owner, heap and Table structs */
#define WW_OWNER        0                               /* HWND of owner */
#define WW_HEAP         (WW_OWNER + sizeof(HWND))       /* gmem heap */
#define WL_TABLE        (WW_HEAP + sizeof(HANDLE))      /* lpTable */
#define WLTOTAL         (WL_TABLE + sizeof(lpTable))    /* total extra bytes */

/* ---------- global data -------------------*/

extern HPEN hpenDotted;         /* in table.c */
extern HANDLE hVertCurs;        /* in table.c */
extern HANDLE hNormCurs;        /* in table.c */

/*------function prototypes ---------------------------------------*/

/* in table.c */

void gtab_init(void);    /* called from DLL startup function */
long gtab_sendtq(HWND hwnd, UINT cmd, long lParam);
void gtab_invallines(HWND hwnd, lpTable ptab, int start, int count);
void gtab_setsize(HWND hwnd, lpTable ptab);
void gtab_calcwidths(HWND hwnd, lpTable ptab);
void gtab_deltable(HWND hwnd, lpTable ptab);
BOOL gtab_alloclinedata(HWND hwnd, HANDLE heap, lpTable ptab);

/* in tpaint.c */
void gtab_paint(HWND hwnd, HDC hdc, lpTable ptab, int line);
void gtab_vsep(HWND hwnd, lpTable ptab, HDC hdc);
void gtab_hsep(HWND hwnd, lpTable ptab, HDC hdc);
void gtab_invertsel(HWND hwnd, lpTable ptab, HDC hdc_in);
void gtab_drawvertline(HWND hwnd, lpTable ptab);

/* in tscroll.c */
void gtab_dovscroll(HWND hwnd, lpTable ptab, long change);
void gtab_dohscroll(HWND hwnd, lpTable ptab, long change);
long gtab_linetorow(HWND hwnd, lpTable ptab, int line);
int gtab_rowtoline(HWND hwnd, lpTable ptab, long row);
void gtab_msg_vscroll(HWND hwnd, lpTable ptab, int opcode, int pos);
void gtab_msg_hscroll(HWND hwnd, lpTable ptab, int opcode, int pos);
void gtab_select(HWND hwnd, lpTable ptab, long row, long col, long nrows,
        long ncells, BOOL bNotify);
void gtab_enter(HWND hwnd, lpTable ptab, long row, long col, long nrows,
        long ncells);
void gtab_press(HWND hwnd, lpTable ptab, int x, int y);
void gtab_release(HWND hwnd, lpTable ptab, int x, int y);
void gtab_move(HWND hwnd, lpTable ptab, int x, int y);
void gtab_dblclick(HWND hwnd, lpTable ptab, int x, int y);
void gtab_showsel(HWND hwnd, lpTable ptab, BOOL bToBottom);
void gtab_showsel_middle(HWND hwnd, lpTable ptab);
int gtab_key(HWND hwnd, lpTable ptab, int vkey);

/* in tprint.c */
void gtab_print(HWND hwnd, lpTable ptab, HANDLE heap, lpPrintContext pcontext);
void gtab_boxcell(HWND hwnd, HDC hdc, LPRECT rcp, LPRECT pclip, UINT boxmode);


TREE.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 Header *******************************
* Module Name: TREE.C
*
* Functions supporting an unbalanced binary tree.
*
* Functions:
*
* tree_delitem()
* tree_newitem()
* tree_getitem()
* tree_create()
* tree_delete()
* tree_update()
* tree_find()
* tree_search()
* tree_addafter()
* ctree_create()
* ctree_delete()
* ctree_update()
* ctree_getcount()
* ctree_find()
*
* Comments:
*
* TREE is a data type providing a map between a KEY and a VALUE. The KEY is a
* 32-bit DWORD, and the VALUE is any arbitrary area of storage.
*
* Mmemory is allocated from gmem_get, using hHeap as the heap handle.
* hHeap must be declared and initialised elsewhere.
*
* Currently implemented as a unbalanced binary tree.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <memory.h>

#include "gutils.h"
#include "tree.h"

/* -- data types ----------------------------------------------- */

/* on creating a tree, we return a TREE handle. This is in fact a pointer
 * to a struct tree, defined here.
 */
struct tree {
        HANDLE hHeap;
        TREEITEM first;
};

/* each element in the tree is stored in a TREEITEM. a TREEITEM handle
 * is a pointer to a struct treeitem, defined here
 */
struct treeitem {
        TREE root;

        TREEKEY key;

        TREEITEM left, right;

        UINT length;            /* length of the user's data */

        LPVOID data;            /* pointer to our copy of the users data */

};

/***************************************************************************
 * Function: tree_delitems
 *
 * Purpose:
 *
 * Free up an element of the tree. Recursively calls itself to
 * free left and right children
 */
void
tree_delitem(TREEITEM item)
{
        if (item == NULL) {
                return;
        }
        if (item->left != NULL) {
                tree_delitem(item->left);
        }
        if (item->right != NULL) {
                tree_delitem(item->right);
        }
        if (item->data != NULL) {
                gmem_free(item->root->hHeap, item->data, item->length);
        }

        gmem_free(item->root->hHeap, (LPSTR) item, sizeof(struct treeitem));
}

/***************************************************************************
 * Function: tree_newitem
 *
 * Purpose:
 *
 * Creates a new treeitem, with a data block of length bytes.
 * If the value pointer is not NULL, initialise the data block with
 * the contents of value.
 */
TREEITEM
tree_newitem(TREE root, TREEKEY key, LPVOID value, UINT length)
{
        TREEITEM item;

        item = (TREEITEM) gmem_get(root->hHeap, sizeof(struct treeitem));

        item->root = root;
        item->key = key;
        item->left = NULL;
        item->right = NULL;
        item->length = length;
        item->data = gmem_get(root->hHeap, length);
        if (value != NULL) {
                memcpy(item->data, value, length);
        }

        return(item);
}

/***************************************************************************
 * Function: tree_getitem
 *
 * Purpose:
 *
 * Finds the item with the given key. If it does not exist, return
 * the parent item to which it would be attached. Returns NULL if
 * no items in the tree
 */
TREEITEM
tree_getitem(TREE tree, TREEKEY key)
{
        TREEITEM item, prev;

        prev = NULL;
        for (item = tree->first; item != NULL; ) {

                if (item->key == key) {
                        return(item);
                }

                /* not this item - go on to the correct child item.
                 * remember this item as if the child is NULL, this item
                 * will be the correct insertion point for the new item
                 */
                prev = item;

                if (key < item->key) {
                        item = item->left;
                } else {
                        item = item->right;
                }
        }
        /* prev is the parent - or null if nothing in tree */
        return(prev);
}

/***************************************************************************
 * Function: tree_create
 *
 * Purpose:
 *
 * Creates an empty tree. hHeap is the handle to use for all
 * memory allocations for this tree.
 */
TREE APIENTRY
tree_create(HANDLE hHeap)
{
        TREE tree;

        tree = (TREE) gmem_get(hHeap, sizeof(struct tree));
        tree->first = NULL;
        tree->hHeap = hHeap;
        return(tree);
}

/***************************************************************************
 * Function: tree_delete
 *
 * Purpose:
 *
 * Deletes an entire tree, including all the user data
 */
void APIENTRY
tree_delete(TREE tree)
{

        tree_delitem(tree->first);

        gmem_free(tree->hHeap, (LPSTR) tree, sizeof(struct tree));
}

/***************************************************************************
 * Function: tree_update
 *
 * Purpose:
 *
 * Adds a new element to the tree, mapping the key given to the value given.
 * The value is a block of storage: a copy of this is inserted into the tree.
 * We return a pointer to the copy of the data in the tree.
 *
 * The value pointer can be NULL: in this case, we insert a block of
 * length bytes, but don't initialise it. You get a pointer to it and
 * can initialise it yourself.
 *
 * If the key already exists, the value will be replaced with the new data.
 */
LPVOID APIENTRY
tree_update(TREE tree, TREEKEY key, LPVOID value, UINT length)
{
        TREEITEM item;

        /* find the place in the tree for this key to go */
        item = tree_getitem(tree, key);

        if (item == NULL) {
                /* there is nothing in the tree: this item should
                 * go at the top
                 */
                tree->first = tree_newitem(tree, key, value, length);
                return(tree->first->data);
        }

        /* is this the same key ? */
        if (item->key == key) {

                /* this key already inserted. re-alloc the data */
                if (length != item->length) {
                        gmem_free(tree->hHeap, item->data, item->length);
                        item->data = gmem_get(tree->hHeap, length);
                }
                /* don't initialise block if no pointer passed */
                if (value != NULL) {
                        memcpy(item->data, value, length);
                }
                return(item->data);
        }

        /* not the same key - getitem returned the parent for
         * the new tree. insert it as a child of item.
         */
        return(tree_addafter(tree, &item, key, value, length));
}

/***************************************************************************
 * Function: tree_find
 *
 * Purpose:
 *
 * Returns a pointer to the value (data block) for a given key. Returns
 * null if not found.
 */
LPVOID APIENTRY
tree_find(TREE tree, TREEKEY key)
{
        TREEITEM item;

        /* find the correct place in the tree */
        item = tree_getitem(tree, key);

        if (item == NULL) {
                /* nothing in the tree */
                return(NULL);
        }

        if (item->key != key) {
                /* this key not in. getitem has returned parent */
                return(NULL);
        }

        /* found the right element - return pointer to the
         * data block
         */
        return(item->data);
}

/* The next two routines are an optimisation for a common tree operation. In
 * this case, the user will want to insert a new element only if
 * the key is not there. If it is there, he will want to modify the
 * existing value (increment a reference count, for example).
 *
 * If tree_search fails to find the key, it will return a TREEITEM handle
 * for the parent. This can be passed to tree_addafter to insert the
 * new element without re-searching the tree.
 */

/***************************************************************************
 * Function: tree_search
 *
 * Purpose:
 *
 * Find an element. If not, find it's correct parent item
 */
LPVOID APIENTRY
tree_search(TREE tree, TREEKEY key, PTREEITEM pplace)
{
        TREEITEM item;

        item = tree_getitem(tree, key);

        if (item == NULL) {
                /* no items in tree. set placeholder to NULL to
                 * indicate insert at top of tree
                 */
                *pplace = NULL;

                /* return NULL to indicate key not found */
                return(NULL);
        }

        if (item->key == key) {
                /* found the key already there -
                 * set pplace to null just for safety
                 */
                *pplace = NULL;

                /* give the user a pointer to his data */
                return(item->data);
        }

        /* key was not found - getitem has returned the parent
         * - set this as the place for new insertions
         */
        *pplace = item;

        /* return NULL to indicate that the key was not found */
        return(NULL);
}

/***************************************************************************
 * Function: tree_addafter
 *
 * Purpose:
 *
 * Insert a key in the position already found by tree_search.
 *
 * Return a pointer to the user's data in the tree. If the value
 * pointer passed in is null, then we allocate the block, but don't
 * initialise it to anything.
 */
LPVOID APIENTRY
tree_addafter(TREE tree, PTREEITEM place, TREEKEY key, LPVOID value, UINT length)
{
        TREEITEM item, child;

        item = *place;
        if (item == NULL) {
                tree->first = tree_newitem(tree, key, value, length);
                return (tree->first->data);
        }

        child = tree_newitem(tree, key, value, length);
        if (child->key < item->key ) {
                /* should go on left leg */
                item->left = child;
        } else {
                item->right = child;
        }
        return(child->data);
}

/* --- ctree ------------------------------------------------------*/

/*
 * ctree is a class of tree built on top of the tree interface. a
 * ctree keeps count of the number of insertions of identical keys.
 *
 * We do this be adding a long counter to the beginning of the user
 * data before inserting into the tree. If the key is not found, we set
 * this to one. If the key was already there, we *do not* insert the
 * data (data is always from the first insertion) - we simply increment
 * the count.
 */

/* Create a tree for use by CTREE - same as an ordinary tree
 */
TREE APIENTRY
ctree_create(HANDLE hHeap)
{
        return(tree_create(hHeap));
}

/*
 * Delete a ctree - same as for TREE
 */
void APIENTRY
ctree_delete(TREE tree)
{
        tree_delete(tree);
}

/***************************************************************************
 * Function: ctree_update
 *
 * Purpose:
 *
 * Insert an element in the tree. If the element is not there,
 * insert the data and set the reference count for this key to 1.
 * If the key was there already, don't change the data, just increment
 * the reference count
 *
 * If the value pointer is not null, we initialise the value block
 * in the tree to contain this.
 *
 * We return a pointer to the users data in the tree
 */
LPVOID APIENTRY
ctree_update(TREE tree, TREEKEY key, LPVOID value, UINT length)
{
        TREEITEM item;
        long FAR * pcounter;
        LPVOID datacopy;

        pcounter = tree_search(tree, key, &item);

        if (pcounter == NULL) {
                /* element not found - insert a new one
                 * the data block for this element should be
                 * the user's block with our reference count at
                 * the beginning
                 */
                pcounter = tree_addafter(tree, &item, key, NULL,
                                length + sizeof(long));
                *pcounter = 1;
                /* add on size of one long to get the start of the user
                 * data
                 */
                datacopy = pcounter + 1;
                if (value != NULL) {
                        memcpy(datacopy, value, length);
                }
                return(datacopy);
        }

        /* key was already there - increment reference count and
         * return pointer to data
         */

        (*pcounter)++;

        /* add on size of one long to get the start of the user
         * data
         */
        datacopy = pcounter + 1;
        return(datacopy);
}

/***************************************************************************
 * Function: ctree_getcount
 *
 * Purpose:
 *
 * Return the reference count for this key
 */
long APIENTRY
ctree_getcount(TREE tree, TREEKEY key)
{
        long FAR * pcounter;

        pcounter = tree_find(tree, key);
        if (pcounter == NULL) {
                return(0);
        }
        return(*pcounter);
}

/***************************************************************************
 * Function: ctree_find
 *
 * Purpose:
 *
 * Return a pointer to the user's data block for this key,
 * or NULL if key not present
 */
LPVOID APIENTRY
ctree_find(TREE tree, TREEKEY key)
{
        long FAR * pcounter;

        pcounter = tree_find(tree, key);
        if (pcounter == NULL) {
                return(0);
        }

        /* increment pointer by size of 1 long to point to
         * user's datablock
         */
        return(pcounter+1);
}


TREE.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.
\******************************************************************************/

/*
 * TREE.H
 *
 * NOTE: include gutils.h before this.
 */

/* handle for a tree */
typedef struct tree FAR * TREE;

/* keys in these trees are DWORDs */
typedef DWORD TREEKEY;

/* A place-holder understood only by tree_search and
 * tree_addafter
 */
typedef struct treeitem FAR * TREEITEM;

/* pointer to one of these place holders */
typedef TREEITEM FAR * PTREEITEM;

TREE APIENTRY tree_create(HANDLE hHeap);
void APIENTRY tree_delete(TREE tree);
LPVOID APIENTRY tree_update(TREE tree, TREEKEY key, LPVOID value, UINT length);
LPVOID APIENTRY tree_find(TREE tree, TREEKEY key);
LPVOID APIENTRY tree_search(TREE tree, TREEKEY key, PTREEITEM place);
LPVOID APIENTRY tree_addafter(TREE tree, PTREEITEM place, TREEKEY key, LPVOID value,
                        UINT length);
TREE APIENTRY ctree_create(HANDLE hHeap);
void APIENTRY ctree_delete(TREE tree);
LPVOID APIENTRY ctree_update(TREE tree, TREEKEY key, LPVOID value, UINT length);
long APIENTRY ctree_getcount(TREE tree, TREEKEY key);
LPVOID APIENTRY ctree_find(TREE tree, TREEKEY key);


TSCROLL.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 Header *******************************
* Module Name: TSCROLL.C
*
* Scrolling and selection routines.
*
* Functions:
*
* gtab_msg_vscroll()
* gtab_msg_hscroll()
* gtab_dovscroll()
* gtab_dohscroll()
* gtab_linetorow()
* gtab_rowtoline()
* gtab_select()
* gtab_ytoline()
* gtab_xtocol()
* gtab_isborder()
* gtab_enter()
* gtab_trackcol()
* gtab_press()
* gtab_release()
* gtab_move()
* gtab_dblclick()
* gtab_showsel()
* gtab_showsel_middle()
* gtab_changesel()
* gtab_selhome()
* gtab_key()
*
* Comments:
*
* This implementation currently only supports TM_SINGLE, not TM_MANY
* modes of selection.
*
****************************************************************************/

#include <windows.h>
#include <commdlg.h>

#include "gutils.h"
#include "table.h"
#include "tpriv.h"

/***************************************************************************
 * Function: gtab_msg_vscroll
 *
 * Purpose:
 *
 * Handle a vscroll message
 */
void
gtab_msg_vscroll(HWND hwnd, lpTable ptab, int opcode, int pos)
{
        long change;

        switch(opcode) {
        case SB_THUMBPOSITION:
                change = (pos * ptab->scrollscale) - ptab->toprow;
                break;

        case SB_LINEUP:
                change = -1;
                break;

        case SB_LINEDOWN:
                change = 1;
                break;

        case SB_PAGEUP:
                change = - (ptab->nlines - 3);
                break;

        case SB_PAGEDOWN:
                change = (ptab->nlines - 3);
                break;

        default:
                return;
        }
        gtab_dovscroll(hwnd, ptab, change);
}

/***************************************************************************
 * Function: gtab_msg_hscroll
 *
 * Purpose:
 *
 * Handle a hscroll message
 */
void
gtab_msg_hscroll(HWND hwnd, lpTable ptab, int opcode, int pos)
{
        int change;

        switch(opcode) {
        case SB_THUMBPOSITION:
                change = pos - ptab->scroll_dx;
                break;

        case SB_LINEUP:
                change = -(ptab->avewidth);
                break;

        case SB_LINEDOWN:
                change = ptab->avewidth;
                break;

        case SB_PAGEUP:
                change = - (ptab->winwidth * 2 / 3);
                break;

        case SB_PAGEDOWN:
                change = (ptab->winwidth * 2 / 3);
                break;

        default:
                return;
        }
        gtab_dohscroll(hwnd, ptab, change);
}

/***************************************************************************
 * Function: gtab_dovscroll
 *
 * Purpose:
 *
 * Set new vertical scroll pos,
 * adjust linedata array
 * set line win-relative start posns & clip top/bottom posns
 * revise display.
 */
void
gtab_dovscroll(HWND hwnd, lpTable ptab, long change)
{
        int cury, i;
        long ncopy;
        lpCellPos cp;
        LineData ldtemp;
        RECT rc, rcpaint;
        long range;
        long newtop;
        int newpos;

        range = ptab->hdr.nrows - (ptab->nlines - 1);
        newtop = ptab->toprow + change;
        if (range < 0) {
                range = 0;
        }
        if (newtop > range) {
                change = range - ptab->toprow;
        } else if (newtop < 0) {
                change = -(ptab->toprow);
        }
        ptab->toprow += change;

        newpos = (int) (newtop / ptab->scrollscale);
        SetScrollPos(hwnd, SB_VERT, newpos, TRUE);

        if (ptab->hdr.sendscroll) {
                gtab_sendtq(hwnd, TQ_SCROLL, ptab->toprow);
        }

        /* adjust data ptrs rather than invalidate, to retain the
         * data we know is still valid
         */
        if (abs(change) >= ptab->nlines) {
                gtab_invallines(hwnd, ptab, ptab->hdr.fixedrows,
                        ptab->nlines - ptab->hdr.fixedrows);
                InvalidateRect(hwnd, NULL, TRUE);
                change = 0;
        } else if (change < 0) {
                /* copy data down */
                ncopy = (ptab->nlines - ptab->hdr.fixedrows) - abs(change);
                for (i =  ptab->nlines - 1;
                  i >= (ptab->hdr.fixedrows + abs(change)); i--) {
                        ldtemp = ptab->pdata[i - abs(change)];
                        ptab->pdata[i - abs(change)] = ptab->pdata[i];
                        ptab->pdata[i] = ldtemp;
                }
                gtab_invallines(hwnd, ptab,
                        ptab->hdr.fixedrows, (int) abs(change));
        } else if (change > 0) {
                ncopy = (ptab->nlines - ptab->hdr.fixedrows) - change;
                for (i = ptab->hdr.fixedrows;
                  i < (ncopy + ptab->hdr.fixedrows); i++) {
                        ldtemp = ptab->pdata[i + change];
                        ptab->pdata[i + change] = ptab->pdata[i];
                        ptab->pdata[i] = ldtemp;
                }
                gtab_invallines(hwnd, ptab,
                        (int) ncopy + ptab->hdr.fixedrows, (int) change);
        }

        /* scroll window */
        GetClientRect(hwnd, &rc);
        rcpaint = rc;
        if (change > 0) {
                rc.top += (int) (change + ptab->hdr.fixedrows) * ptab->rowheight;
                rcpaint.top = (ptab->hdr.fixedrows * ptab->rowheight);
                rcpaint.top += rc.bottom - rc.top;
        } else if (change < 0) {
                rc.top += (ptab->hdr.fixedrows * ptab->rowheight);
                rc.bottom -= (int) (change * ptab->rowheight);
                rcpaint.bottom -= rc.bottom - rc.top;
        }

        /* loop through each line setting relative posn and clipping */

        /* set up all rows  - the fixed/moveable difference for
         * rows is made at fetch-time during painting, when we remember
         * which absolute row nr to ask for, for a given screen line
         */
        cury = 0;
        for (i = 0; i < ptab->nlines; i++) {
                cp = &ptab->pdata[i].linepos;
                cp->start = cury;
                cp->clipstart = cury;
                cp->clipend = cury + cp->size;
                cury += cp->size;
        }

        /* now move and repaint the window */
        if (change != 0) {
                if (rc.top < rc.bottom) {
                        ScrollWindow(hwnd, 0, (int) -(change * ptab->rowheight),
                                &rc, NULL);

                }

                /* force repaint now, not just post message for later,
                 * since we want to repaint that line before the next
                 * scroll down occurs
                 */
                RedrawWindow(hwnd, &rcpaint, NULL,
                        RDW_ERASE | RDW_INVALIDATE|RDW_UPDATENOW);
        }
}

/***************************************************************************
 * Function: gtab_dohscroll
 *
 * Purpose:
 *
 * Set new horizontal scroll pos,
 * set col win-relative start posns & clip left/right posns
 * revise display.
 */
void
gtab_dohscroll(HWND hwnd, lpTable ptab, long change)
{
        int curx, i;
        int moveable;
        lpCellPos cp;
        int newdx, range;

        /* check that the new scroll pos is still within the valid range */
        range = ptab->rowwidth - ptab->winwidth;
        newdx = ptab->scroll_dx + (int) change;
        if (range < 0) {
                range = 0;
        }
        if (newdx > range) {
                change = range - ptab->scroll_dx;
        } else if (newdx < 0) {
                change = -(ptab->scroll_dx);
        }
        ptab->scroll_dx += (int) change;

        SetScrollPos(hwnd, SB_HORZ, ptab->scroll_dx, TRUE);
        InvalidateRect(hwnd, NULL, TRUE);

        /* loop through each col setting relative posn and clipping */
        /* clip off 1 pixel left and right (we added 2 on to size for this) */

        /* first set up fixed columns */
        curx = 0;
        for (i = 0; i < ptab->hdr.fixedcols; i++) {
                cp = &ptab->pcellpos[i];
                cp->start = curx + 1;
                cp->clipstart = cp->start;
                cp->clipend = cp->start + cp->size - 2;
                curx += cp->size;
        }

        /* now moveable columns. remember start of moveable cols */
        moveable = curx;
        curx = - ptab->scroll_dx;       /* rel. pos of col */
        for (i = ptab->hdr.fixedcols; i < ptab->hdr.ncols; i++) {
                cp = &ptab->pcellpos[i];
                cp->start = curx + moveable + 1;
                cp->clipstart = max(moveable+1, cp->start);
                cp->clipend = cp->start + cp->size - 2;
                curx += cp->size;
        }
}

/***************************************************************************
 * Function: gtab_linetorow
 *
 * Purpose:
 *
 * Convert screen line nr to table row nr
 */
long
gtab_linetorow(HWND hwnd, lpTable ptab, int line)
{
        if (line < ptab->hdr.fixedrows) {
                return(line);
        }

        return (line + ptab->toprow);
}

/***************************************************************************
 * Function: gtab_rowtoline
 *
 * Purpose:
 *
 * Convert table row nr to screen line nr or -1 if not on screen
 */
int
gtab_rowtoline(HWND hwnd, lpTable ptab, long row)
{
        if (row < ptab->hdr.fixedrows) {
                return( (int) row);
        }

        row -= ptab->toprow;
        if ((row >= ptab->hdr.fixedrows) && (row < ptab->nlines)) {
                return ( (int) row);
        }
        return(-1);
}

/***************************************************************************
 * Function: gtab_select
 *
 * Purpose:
 *
 * Replace old selection with new. Notify owner if bNotify. Change
 * display to reflect new display.
 */
void
gtab_select(
        HWND hwnd,
        lpTable ptab,
        long row,
        long col,
        long nrows,
        long ncells,
        BOOL bNotify)
{
        int line;

        /* if in ROW mode, force col and ncells to reflect the entire row. */
        if (ptab->hdr.selectmode & TM_ROW) {
                col = 0;
                ncells = ptab->hdr.ncols;
        }

        /* clear existing sel if valid and visible */
        if ((ptab->select.nrows > 0) && (ptab->selvisible == TRUE)) {

                /* only clear sel if it is different from the new one */
                if ((ptab->select.startrow != row) ||
                    (ptab->select.startcell != col) ||
                    (ptab->select.nrows != nrows) ||
                    (ptab->select.ncells != ncells)) {
                        line = gtab_rowtoline(hwnd, ptab,
                                ptab->select.startrow);
                        if (line >= 0) {
                                gtab_invertsel(hwnd, ptab, NULL);
                        }
                        ptab->selvisible = FALSE;
                }
        }

        /* set select fields and send TQ_SELECT */
        if (row < ptab->hdr.nrows) {
                ptab->select.startrow = row;
                ptab->select.startcell = col;
                ptab->select.nrows = nrows;
                ptab->select.ncells = ncells;
        } else {
                ptab->select.nrows = 0;
                ptab->select.startrow = 0;
                ptab->select.startcell = 0;
                ptab->select.ncells = 0;
        }

        if (bNotify) {
                gtab_sendtq(hwnd, TQ_SELECT, (long) (LPSTR) &ptab->select);
        }

        /* paint in selection */
        if (nrows > 0) {
                if (!ptab->selvisible) {
                        gtab_invertsel(hwnd, ptab, NULL);
                        ptab->selvisible = TRUE;
                }
        } else {
                if (ptab->selvisible) {
                        gtab_invertsel(hwnd, ptab, NULL);
                        ptab->selvisible = FALSE;
                }
                ptab->selvisible = FALSE;
        }
}

/***************************************************************************
 * Function: gtab_ytoline
 *
 * Purpose:
 *
 * Convert window y co-ord to a line nr
 */
int
gtab_ytoline(HWND hwnd, lpTable ptab, int y)
{
        return(y / ptab->rowheight);
}

/***************************************************************************
 * Function: gtab_xtocol
 *
 * Purpose:
 *
 * Convert window x co-ord to a cell nr
 */
int
gtab_xtocol(HWND hwnd, lpTable ptab, int x)
{
        int i;
        lpCellPos ppos;

        for (i = 0; i < ptab->hdr.ncols; i++) {
                ppos = &ptab->pcellpos[i];
                if (ppos->clipstart < ppos->clipend) {
                        if ( (x >= ppos->clipstart) && (x < ppos->clipend)) {
                                return(i);
                        }
                }
        }
        return(-1);
}

/***************************************************************************
 * Function: gtab_isborder
 *
 * Purpose:
 *
 * Check if x co-ord is 'near' (+- 2 pixels) the right border of given cell
 */
BOOL
gtab_isborder(HWND hwnd, lpTable ptab, int x, int col)
{

        if (abs(ptab->pcellpos[col].clipend - x) < 2) {
                return(TRUE);
        } else {
                return(FALSE);
        }
}

/***************************************************************************
 * Function: gtab_enter
 *
 * Purpose:
 *
 * Set selection and send 'TQ_ENTER' event to owner
 */
void
gtab_enter(HWND hwnd, lpTable ptab, long row, long col, long nrows,
        long ncells)
{
        int line;

        /* clear existing sel if valid and visible */
        if ((ptab->select.nrows > 0) && (ptab->selvisible == TRUE)) {

                /* only clear sel if it is different from the new one */
                if ((ptab->select.startrow != row) ||
                    (ptab->select.startcell != col) ||
                    (ptab->select.nrows != nrows) ||
                    (ptab->select.ncells != ncells)) {
                        line = gtab_rowtoline(hwnd, ptab,
                                ptab->select.startrow);
                        if (line >= 0) {
                                gtab_invertsel(hwnd, ptab, NULL);
                        }
                        ptab->selvisible = FALSE;
                }
        }

        /* set select fields and send TQ_SELECT */
        if (row < ptab->hdr.nrows) {
                ptab->select.startrow = row;
                ptab->select.startcell = col;
                ptab->select.nrows = nrows;
                ptab->select.ncells = ncells;
        } else {
                ptab->select.nrows = 0;
                ptab->select.startrow = 0;
                ptab->select.startcell = 0;
                ptab->select.ncells = 0;
        }

        /* paint in selection */
        if (nrows > 0) {
                if (!ptab->selvisible) {
                        gtab_invertsel(hwnd, ptab, NULL);
                        ptab->selvisible = TRUE;
                }
                /* do this at end because it could cause a layout-change */
                gtab_sendtq(hwnd, TQ_ENTER, (long) (LPSTR) &ptab->select);
        } else {
                if (ptab->selvisible) {
                        gtab_invertsel(hwnd, ptab, NULL);
                }
                ptab->selvisible = FALSE;
        }
}

/***************************************************************************
 * Function: gtab_trackcol
 *
 * Purpose:
 *
 * Start re-sizing a column
 */
void
gtab_trackcol(HWND hwnd, lpTable ptab, int col, int x)
{

        /* ensure we see the mouse-up */
        SetCapture(hwnd);
        ptab->trackmode = TRACK_COLUMN;
        ptab->tracknr = col;
        ptab->trackline1 = x;

        /* if line at other side of cell is visible, draw that too */
        if (ptab->pcellpos[col].start >= ptab->pcellpos[col].clipstart) {
                ptab->trackline2 = ptab->pcellpos[col].start;
        } else {
                ptab->trackline2 = -1;
        }
        gtab_drawvertline(hwnd, ptab);
}

/***************************************************************************
 * Function: gtab_press
 *
 * Purpose:
 *
 * Called on mouse-down events. decide what to start tracking.
 */
void
gtab_press(HWND hwnd, lpTable ptab, int x, int y)
{
        int cell;
        long row;

        if (ptab->trackmode != TRACK_NONE) {
                return;
        }

        /* has he grabbed a cell-edge to resize ? */
        cell = gtab_xtocol(hwnd, ptab, x);
        if (cell == -1) {
                return;
        }
        if (gtab_isborder(hwnd, ptab, x, cell)) {
                gtab_trackcol(hwnd, ptab, cell, x);
                return;
        }
        if ( (cell > 0) && gtab_isborder(hwnd, ptab, x, cell-1)) {
                gtab_trackcol(hwnd, ptab, cell, x);
                return;
        }

        /* find which line he selected */
        row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));

        /* is he selecting a disabled fixed area ? */
        if ( (row < ptab->hdr.fixedrows) || (cell < ptab->hdr.fixedcols)) {
                if (ptab->hdr.fixedselectable == FALSE) {
                        return;
                }
        }

        /* ok, start cell selection */
        ptab->trackmode = TRACK_CELL;
        SetCapture(hwnd);

        /* record and paint new selection */
        if (ptab->hdr.selectmode & TM_ROW) {
                gtab_select(hwnd, ptab, row, 0, 1, ptab->hdr.ncols, FALSE);
        } else {
                gtab_select(hwnd, ptab, row, cell, 1, 1, FALSE);
        }
        return;
}

/***************************************************************************
 * Function: gtab_release
 *
 * Purpose:
 *
 * Called on mouse-up. complete any tracking that was happening
 */
void
gtab_release(HWND hwnd, lpTable ptab, int x, int y)
{
        lpCellPos ppos;
        lpProps pprop;
        long row;
        int cx;

        switch(ptab->trackmode) {

        case TRACK_NONE:
                return;

        case TRACK_COLUMN:
                /* erase marker lines */
                gtab_drawvertline(hwnd, ptab);
                ReleaseCapture();
                ptab->trackmode = TRACK_NONE;

                /* adjust cell width */
                ppos = &ptab->pcellpos[ptab->tracknr];
                cx = ptab->trackline1 - ppos->start;
                pprop = &ptab->pcolhdr[ptab->tracknr].props;
                pprop->valid |= P_WIDTH;
                pprop->width = cx;
                gtab_calcwidths(hwnd, ptab);
                gtab_setsize(hwnd, ptab);
                InvalidateRect(hwnd, NULL, TRUE);
                return;

        case TRACK_CELL:
                row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));
                ReleaseCapture();
                ptab->trackmode = TRACK_NONE;

                /* keep the same selection. if the mouse is still
                 * in the box, select it, otherwise de-select it
                 */
                if ((row == ptab->select.startrow) &&
                  ( (ptab->hdr.selectmode & TM_ROW) ||
                    (ptab->select.startcell == gtab_xtocol(hwnd, ptab, x))) ) {

                        gtab_select(hwnd, ptab, ptab->select.startrow,
                                ptab->select.startcell,
                                ptab->select.nrows, ptab->select.ncells, TRUE);
                } else {
                        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
                }
                return;
        }
}

/***************************************************************************
 * Function: gtab_move
 *
 * Purpose:
 *
 * Called on mouse-move. if tracking - adjust position, if not,
 * set correct cursor
 */
void
gtab_move(HWND hwnd, lpTable ptab, int x, int y)
{
        BOOL fOK;
        long row;
        int col;
        lpCellPos ppos;

        switch(ptab->trackmode) {

        case TRACK_NONE:
                col = gtab_xtocol(hwnd, ptab, x);
                if (col == -1) {
                        SetCursor(hNormCurs);
                        return;
                }
                if (gtab_isborder(hwnd, ptab, x, col)) {
                        SetCursor(hVertCurs);
                        return;
                }
                if ( (col > 0) && gtab_isborder(hwnd, ptab, x, col-1)) {
                        SetCursor(hVertCurs);
                        return;
                }
                SetCursor(hNormCurs);
                return;

        case TRACK_CELL:
                row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));

                /* keep the same selection. if the mouse is still
                 * in the box, select it, otherwise de-select it
                 */
                if ((row == ptab->select.startrow) &&
                  ( (ptab->hdr.selectmode & TM_ROW) ||
                    (ptab->select.startcell == gtab_xtocol(hwnd, ptab, x))) ) {

                        if (!ptab->selvisible) {
                                gtab_invertsel(hwnd, ptab, NULL);
                                ptab->selvisible = TRUE;
                        }
                } else {
                        if (ptab->selvisible) {
                                gtab_invertsel(hwnd, ptab, NULL);
                                ptab->selvisible = FALSE;
                        }
                }
                return;

        case TRACK_COLUMN:
                /* check that new x is still visible/valid */
                ppos = &ptab->pcellpos[ptab->tracknr];
                fOK = FALSE;

                if (ptab->tracknr < ptab->hdr.fixedcols)  {
                        if ((x > ppos->start) && (x < ptab->winwidth)) {
                                fOK = TRUE;
                        }
                } else {
                        if ((x > ppos->clipstart) && (x < ptab->winwidth)) {
                                fOK = TRUE;
                        }
                }
                if (fOK == TRUE) {
                        gtab_drawvertline(hwnd, ptab);
                        ptab->trackline1 = x;
                        gtab_drawvertline(hwnd, ptab);
                }
                return;
        }
}

/***************************************************************************
 * Function: gtab_dblclick
 *
 * Purpose:
 *
 * dbl-click - send an TQ_ENTER event to the owner (if valid)
 */
void
gtab_dblclick(HWND hwnd, lpTable ptab, int x, int y)
{
        int cell, line;
        long row;

        line = gtab_ytoline(hwnd, ptab, y);
        cell = gtab_xtocol(hwnd, ptab, x);
        if ( (line < ptab->hdr.fixedrows) || (cell < ptab->hdr.fixedcols) ) {
                if (!ptab->hdr.fixedselectable) {
                        return;
                }
        }
        row = gtab_linetorow(hwnd, ptab, line);

        if (ptab->hdr.selectmode & TM_ROW) {
                gtab_enter(hwnd, ptab, row, 0, 1, ptab->hdr.ncols);
        } else {
                gtab_enter(hwnd, ptab, row, cell, 1, 1);
        }
}

/***************************************************************************
 * Function: gtab_showsel
 *
 * Purpose:
 *
 * Move selection area to visible part of window. Argument bToBottom
 * indicates whether to move the line onto the bottom or the top of the
 * window if not visible - this affects the smoothness of scrolling
 * line-by-line.
 */
void
gtab_showsel(HWND hwnd, lpTable ptab, BOOL bToBottom)
{
        int line;
        long change;

        line = gtab_rowtoline(hwnd, ptab, ptab->select.startrow);

        /* move up if last line or not at all visible */
        if ( (line < 0) || line == (ptab->nlines - 1)) {
                change = ptab->select.startrow - ptab->toprow;
                if (bToBottom) {
                        /* change to bottom of window. subtract 2 not 1
                         * since nlines includes one line that is only
                         * partly visible
                         */
                        change -= (ptab->nlines - 2);
                }
                change -= ptab->hdr.fixedrows;
                gtab_dovscroll(hwnd, ptab, change);
        }
        /* add support for TM_CELL here! */
}

/***************************************************************************
 * Function: gtab_showsel_middle
 *
 * Purpose:
 *
 * Scroll the window so that if possible, the selected row is in the
 * middle 60% of the screen so that context around it is visible.
 */
void
gtab_showsel_middle(HWND hwnd, lpTable ptab)
{
        int line;
        long change;
        int mid_top, mid_end;

        line = gtab_rowtoline(hwnd, ptab, ptab->select.startrow);

        /* is this within the middle 60 % ?  */
        mid_top = ptab->nlines * 20 / 100;
        mid_end = ptab->nlines * 80 / 100;
        if ((line < mid_top) || (line > mid_end)) {

                /* no - scroll so that selected line is at
                 * the 20% mark
                 */
                change = (ptab->select.startrow - mid_top) - ptab->toprow;
                change -= ptab->hdr.fixedrows;
                gtab_dovscroll(hwnd, ptab, change);
        }
        /* again - need code here for TM_CELL mode to ensure that
         * active cell is horizontally scrolled correctly
         */
}

/***************************************************************************
 * Function: gtab_changesel
 *
 * Purpose:
 *
 * Move the selection a specified nr of rows or cells
 * if no selection, select first visible unit
 */
VOID
gtab_changesel(HWND hwnd, lpTable ptab, long rowincr, int cellincr, BOOL bToBottom)
{
        long row, col, nrows, ncols;

        /* is there a selection ? */
        if (ptab->select.nrows < 1) {

                /* no selection - force a selection
                 * at the first visible unit
                 */
                if (ptab->hdr.fixedselectable) {
                        row = 0;
                        col = 0;
                } else {
                        row = gtab_linetorow(hwnd, ptab, ptab->hdr.fixedrows);
                        /* should really check for first visible cell */
                        col = ptab->hdr.fixedcols;
                }
                nrows = 1;
                ncols = 1;
                if (ptab->hdr.selectmode & TM_ROW) {
                        col = 0;
                        ncols = ptab->hdr.ncols;
                }
        } else {
                row = ptab->select.startrow + rowincr;
                col = ptab->select.startcell + cellincr;
                while (col >= ptab->hdr.ncols) {
                        col -= ptab->hdr.ncols;
                        row++;
                }
                while (col < 0) {
                        col += ptab->hdr.ncols;
                        row--;
                }
                if (row < 0) {
                        row = 0;
                }
                if (row >= ptab->hdr.nrows) {
                        row = ptab->hdr.nrows-1;
                }
                /* check we haven't moved into non-selectable region */
                if ((row < ptab->hdr.fixedrows) &&
                        (!ptab->hdr.fixedselectable)) {
                                row = ptab->hdr.fixedrows;
                }
                nrows = ptab->select.nrows;
                ncols = ptab->select.ncells;
        }
        gtab_select(hwnd, ptab, row, col, nrows, ncols, TRUE);
        /* ensure selection visible */
        gtab_showsel(hwnd, ptab, bToBottom);
}

/***************************************************************************
 * Function: gtab_selhome
 *

* Purpose:
 *
 * Set the topmost selectable unit in window as the selection
 */
void
gtab_selhome(HWND hwnd, lpTable ptab)
{
        long row;

        if (ptab->hdr.fixedselectable) {
                row = gtab_linetorow(hwnd, ptab, 0);
                if (ptab->hdr.selectmode & TM_ROW) {
                        gtab_select(hwnd, ptab, row, 0, 1,
                                ptab->hdr.ncols, TRUE);
                } else {
                        gtab_select(hwnd, ptab, row, 0, 1, 1, TRUE);
                }
        } else {
                row = gtab_linetorow(hwnd, ptab, ptab->hdr.fixedrows);
                if (ptab->hdr.selectmode & TM_ROW) {
                        gtab_select(hwnd, ptab, row, 0, 1,
                                ptab->hdr.ncols, TRUE);
                } else {
                        gtab_select(hwnd, ptab, row,
                                ptab->hdr.fixedcols, 1, 1, TRUE);
                }
        }
}

/***************************************************************************
 * Function: gtab_key
 *
 * Purpose:
 *
 * Handle key-down events - scroll windows and/or move selection
 */
int
gtab_key(HWND hwnd, lpTable ptab, int vkey)
{
        long row;
        BOOL bControl = FALSE;

        if (GetKeyState(VK_CONTROL) & 0x8000) {
                bControl = TRUE;
        }

        switch(vkey) {

        case VK_UP:
                if (bControl) {
                        /* control-uparrow scrolls window without selection.
                         * the selection is de-selected (to avoid surprises
                         * moving back to it).
                         */
                        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
                        gtab_dovscroll(hwnd, ptab, -1);
                } else {
                        /* uparrow moves selection up one line */
                        gtab_changesel(hwnd, ptab, -1, 0, FALSE);
                }
                return(0);

        case VK_DOWN:
                if (bControl) {
                        /* control downarrow scrolls window without
                         * a selection.
                         */
                        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
                        gtab_dovscroll(hwnd, ptab, 1);
                } else {
                        /* the normal gtab_changesel behaviour is
                         * that if the selected line is not visible now,
                         * we scroll it to the top of the window. This is fine
                         * in most cases but causes unacceptable jumps when
                         * repeatedly scrolling down with the down key.
                         *
                         * Thus we now have an argument to changesel to say
                         * that in this case, if you need to move the line onto
                         * the window, move it to the bottom and not the top
                         */
                        gtab_changesel(hwnd, ptab, 1, 0, TRUE);
                }
                return(0);

        case VK_LEFT:
                /* if cell-selection mode, move left one cell.
                 * otherwise the whole row is selected - scroll
                 * the line left a little
                 */

                if (ptab->hdr.selectmode & TM_ROW) {
                        if (bControl) {
                                /* ctrl-left moves to start of line */
                                gtab_dohscroll(hwnd, ptab, -(ptab->scroll_dx));
                        } else {
                                gtab_dohscroll(hwnd, ptab, -(ptab->avewidth));
                        }
                } else {
                        gtab_changesel(hwnd, ptab, 0, -1, FALSE);
                }
                return(0);

        case VK_RIGHT:
                /* if cell-selection mode, move right one cell.
                 * otherwise the whole row is selected - scroll
                 * the line right a little
                 */
                if (ptab->hdr.selectmode & TM_ROW) {
                        if (bControl) {
                                /* control-right moves to right end of line */
                                gtab_dohscroll(hwnd, ptab, ptab->rowwidth -
                                                ptab->winwidth);
                        } else {
                                gtab_dohscroll(hwnd, ptab, ptab->avewidth);
                        }
                } else {
                        gtab_changesel(hwnd, ptab, 0, 1, TRUE);
                }
                return(0);

        case VK_HOME:
                if (bControl) {
                        /* control-home == top of file */
                        gtab_dovscroll(hwnd, ptab, -(ptab->toprow));
                }
                /* top of window */
                gtab_selhome(hwnd, ptab);
                gtab_showsel(hwnd, ptab, FALSE);

                return(0);

        case VK_END:
                if (bControl) {
                        /* control-end -> end of file */
                        row = ptab->hdr.nrows-1;
                } else {
                        row = gtab_linetorow(hwnd, ptab, ptab->nlines - 1);
                        if (row >= ptab->hdr.nrows) {
                                row = ptab->hdr.nrows-1;
                        }
                }
                if (ptab->hdr.selectmode & TM_ROW) {
                        gtab_select(hwnd, ptab, row, 0, 1,
                                ptab->hdr.ncols, TRUE);
                } else {
                        gtab_select(hwnd, ptab, row,
                                ptab->hdr.ncols-1, 1, 1, TRUE);
                }
                /* we have selected the bottom line. We don't want to
                 * move it up into the window, since the intended
                 * effect is to select the lowest line. This doesn't
                 * apply to the ctrl-end behaviour (move to bottom of
                 * buffer.
                 */
                if (bControl) {
                        /* move the selection to make it visible - but move it
                         * to the bottom and not to the top of the window
                         */
                        gtab_showsel(hwnd, ptab, TRUE);
                }
                return(0);

        case VK_RETURN:
                if (ptab->select.nrows > 0) {
                        gtab_showsel(hwnd, ptab, FALSE);
                        gtab_enter(hwnd, ptab, ptab->select.startrow,
                                ptab->select.startcell,
                                ptab->select.nrows, ptab->select.ncells);
                }
                return(0);

        case VK_SPACE:
                /* toggle the selection */
                if (ptab->select.nrows < 1) {
                        /* no selection - make one */
                        gtab_changesel(hwnd, ptab, 0, 0, TRUE);
                } else {
                        /* there is a selection - deselect it */
                        gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
                }
                return(0);

        case VK_PRIOR:          /* page up */

                gtab_dovscroll(hwnd, ptab, -(ptab->nlines - 3));
                gtab_selhome(hwnd, ptab);
                return(0);

        case VK_NEXT:           /* page down */

                /* scroll down one page */
                gtab_dovscroll(hwnd, ptab, (ptab->nlines - 3));

                /* select new bottom line */
                row = gtab_linetorow(hwnd, ptab, ptab->nlines - 1);
                if (row >= ptab->hdr.nrows) {
                        row = ptab->hdr.nrows-1;
                }
                /* select bottom line, but don't call showsel
                 * since we don't want to adjust it's position - we
                 * want it to remain at the bottom of the window
                 */
                if (ptab->hdr.selectmode & TM_ROW) {
                        gtab_select(hwnd, ptab, row, 0, 1,
                                ptab->hdr.ncols, TRUE);
                } else {
                        gtab_select(hwnd, ptab, row,
                                ptab->hdr.ncols-1, 1, 1, TRUE);
                }

                return(0);

        default:
                return(1);
        }
}


UTILS.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 Header *******************************
* Module Name: UTILS.C
*
* standard file-reading utilities.
*
* Functions:
*
* readfile_new()
* readfile_next()
* readfile_delete()
* utils_CompPath()
* has_string()
* utils_isblank()
* StringInput()
* dodlg_stringin()
*
* Comments:
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <string.h>

#include "gutils.h"
#include "gutilsrc.h"

/*
 * we need an instance handle. this should be the dll instance
 */
extern HANDLE hLibInst;

/*
 * -- forward declaration of procedures -----------------------------------
 */
int FAR PASCAL dodlg_stringin(HWND hDlg, UINT message, UINT wParam, LONG lParam);

/*-- readfile: buffered line input ------------------------------*/

/*
 * set of functions to read a line at a time from a file, using
 * a buffer to read a block at a time from the file
 *
 */

/*
 * a FILEBUFFER handle is a pointer to a struct filebuffer
 */
struct filebuffer {
        int fh;         /* open file handle */
        PSTR start;     /* offset within buffer of next character */
        PSTR last;      /* offset within buffer of last valid char read in */

        char buffer[512];
};

/***************************************************************************
 * Function: readfile_new
 *
 * Purpose:
 *
 * Initialise a filebuffer and return a handle to it
 */
FILEBUFFER APIENTRY
readfile_new(int fh)
{
        FILEBUFFER fbuf;

        fbuf = (FILEBUFFER) LocalLock(LocalAlloc(LHND, sizeof(struct filebuffer)));
        if (fbuf == NULL) {
                return(NULL);
        }

        fbuf->fh = fh;
        fbuf->start = fbuf->buffer;
        fbuf->last = fbuf->buffer;
        /* return file pointer to beginning of file */
        _llseek(fh, 0, 0);

        return(fbuf);
}

/***************************************************************************
 * Function: readfile_next
 *
 * Purpose:
 *
 * Get the next line from a file. Returns a pointer to the line
 * in the buffer - so copy it before changing it.
 *
 * The line is *not* null-terminated. *plen is set to the length of the
 * line.
 */
LPSTR APIENTRY
readfile_next(FILEBUFFER fbuf, int FAR * plen)
{
        PSTR cstart;

        /* look for an end of line in the buffer we have*/
        for (cstart = fbuf->start; cstart < fbuf->last; cstart++) {

                if (*cstart == '\n') {
                        *plen = (cstart - fbuf->start) + 1;
                        cstart = fbuf->start;
                        fbuf->start += *plen;
                        return(cstart);
                }

        }

        /* no cr in this buffer - this buffer contains a partial line.
         * copy the partial up to the beginning of the buffer, and
         * adjust the pointers to reflect this move
         */
        Old_strncpy(fbuf->buffer, fbuf->start, fbuf->last - fbuf->start);
        fbuf->last = &fbuf->buffer[fbuf->last - fbuf->start];
        fbuf->start = fbuf->buffer;

        /* read in to fill the block */
        fbuf->last += _lread(fbuf->fh, fbuf->last,
                        &fbuf->buffer[sizeof(fbuf->buffer)] - fbuf->last);

        /* look for an end of line in the newly filled buffer */
        for (cstart = fbuf->start; cstart < fbuf->last; cstart++) {

                if (*cstart == '\n') {
                        *plen = (cstart - fbuf->start) + 1;
                        cstart = fbuf->start;
                        fbuf->start += *plen;
                        return(cstart);
                }
        }

        /* still no end of line. either the buffer is empty -
         * because of end of file - or the line is longer than
         * the buffer. in either case, return all that we have
         */
        *plen = fbuf->last - fbuf->start;
        { // for JAPAN (nChars != nBytes)
            PSTR ptr;
            for(ptr=fbuf->start;ptr<fbuf->last;ptr++) ;
            if(ptr!=fbuf->last && *plen) {
                --(*plen);
                --(fbuf->last);
                _llseek(fbuf->fh,-1,1);
            }
        }
        cstart = fbuf->start;
        fbuf->start += *plen;
        if (*plen == 0) {
                return(NULL);
        } else {
                return(cstart);
        }
}

/***************************************************************************
 * Function: readfile_delete
 *
 * Purpose:
 *
 * Delete a FILEBUFFER - close the file handle and free the buffer
 */
void APIENTRY
readfile_delete(FILEBUFFER fbuf)
{
        _lclose(fbuf->fh);

        LocalUnlock(LocalHandle( (PSTR) fbuf));
        LocalFree(LocalHandle( (PSTR) fbuf));
}

/* ----------- things for strings-------------------------------------*/

/*
 * Compare two pathnames, and if not equal, decide which should come first.
 * Both path names should be lower cased by AnsiLowerBuff before calling.
 *
 * Returns 0 if the same, -1 if left is first, and +1 if right is first.
 *
 * The comparison is such that all filenames in a directory come before any
 * file in a subdirectory of that directory.
 *
 * Given direct\thisfile v. direct\subdir\thatfile, we take
 * thisfile < thatfile   even though it is second alphabetically.
 * We do this by picking out the shorter path
 * (fewer path elements), and comparing them up till the last element of that
 * path (in the example: compare the 'dir\' in both cases.)
 * If they are the same, then the name with more path elements is
 * in a subdirectory, and should come second.
 *
 * We have had trouble with apparently multiple collating sequences and
 * the position of \ in the sequence.  To eliminate this trouble
 * a. EVERYTHING is mapped to lower case first (actually this is done
 *    before calling this routine).
 * b. All comparison is done by using lstrcmpi with two special cases.
 *    1. Subdirs come after parents as noted above
 *    2. \ must compare low so that fred2\x > fred\x in the same way
 *       that fred2 < fred.  Unfortunately in ANSI '2' < '\\'
 *
 */
int APIENTRY
utils_CompPath(LPSTR left, LPSTR right)
{
        int compval;            // provisional value of comparison

        if (left==NULL) return -1;        // empty is less than anything else
        else if (right==NULL) return 1;  // anything is greater than empty

        for (; ; ) {
                if (*left=='\0' && *right=='\0') return 0;
                if (*left=='\0')  return -1;
                if (*right=='\0')  return 1;
                if (IsDBCSLeadByte(*left) || IsDBCSLeadByte(*right)) {
                        if (*right != *left) {
                                compval = (*left - *right);
                                break;
                        }
                        ++left;
                        ++right;
                        if (*right != *left) {
                                compval = (*left - *right);
                                break;
                        }
                        ++left;
                        ++right;
                } else {
                if (*right==*left)  {++left; ++right; continue;}
                if (*left=='\\') {compval = -1; break;}
                if (*right=='\\') {compval = 1; break;}
                compval = (*left - *right);
                break;
                }
        }

        /* We have detected a difference.  If the rest of one
           of the strings (including the current character) contains
           some \ characters, but the other one does not, then all
           elements up to the last element of the one with the fewer
           elements are equal and so the other one lies in a subdir
           and so compares greater i.e. x\y\f > x\f
           Otherwise compval tells the truth.
        */

        left = strchr(left, '\\');
        right = strchr(right, '\\');
        if (left && !right) return 1;
        if (right && !left) return -1;

        return compval;

} /* utils_CompPath */

/***************************************************************************
 * Function: hash_string
 *
 * Purpose:
 *
 * Generate a hashcode for a null-terminated ascii string.
 *
 * If bIgnoreBlanks is set, then ignore all spaces and tabs in calculating
 * the hashcode.
 *
 * Multiply each character by a function of its position and sum these.
 * The function chosen is to multiply the position by successive
 * powers of a large number.
 * The large multiple ensures that anagrams generate different hash
 * codes.
 */
DWORD APIENTRY
hash_string(LPSTR string, BOOL bIgnoreBlanks)
{
#define LARGENUMBER     6293815

        DWORD sum = 0;
        DWORD multiple = LARGENUMBER;
        int index = 1;

        while (*string != '\0') {

                if (bIgnoreBlanks) {
                        while ( (*string == ' ') || (*string == '\t')) {
                                string = CharNext(string);
                        }
                }

                sum += multiple * index++ * (*string++);
                multiple *= LARGENUMBER;
        }
        return(sum);
}

/***************************************************************************
 * Function: utils_isblank
 *
 * Purpose:
 *
 * Return TRUE iff the string is blank.  Blank means the same as
 * the characters which are ignored in hash_string when ignore_blanks is set
 */
BOOL APIENTRY
utils_isblank(LPSTR string)
{
        while ( (*string == ' ') || (*string == '\t')) {
                string = CharNext(string);
        }

        /* having skipped all the blanks, do we see the end delimiter? */
        return (*string == '\0' || *string == '\r' || *string == '\n');
}

/* --- simple string input -------------------------------------- */

/*
 * static variables for communication between function and dialog
 */
LPSTR dlg_result;
int dlg_size;
LPSTR dlg_prompt, dlg_default, dlg_caption;

/***************************************************************************
 * Function: StringInput
 *
 * Purpose:
 *
 * Input of a single text string, using a simple dialog.
 *
 * Returns TRUE if ok, or FALSE if error or user canceled. If TRUE,
 * puts the string entered into result (up to resultsize characters).
 *
 * Prompt is used as the prompt string, caption as the dialog caption and
 * default as the default input. All of these can be null.
 */

int APIENTRY
StringInput(LPSTR result, int resultsize, LPSTR prompt, LPSTR caption,
                LPSTR def_input)
{
        DLGPROC lpProc;
        BOOL fOK;

        /* copy args to static variable so that winproc can see them */

        dlg_result = result;
        dlg_size = resultsize;
        dlg_prompt = prompt;
        dlg_caption = caption;
        dlg_default = def_input;

        lpProc = (DLGPROC)MakeProcInstance((WNDPROC)dodlg_stringin, hLibInst);
        fOK = DialogBox(hLibInst, "StringInput", GetFocus(), lpProc);
        FreeProcInstance((WNDPROC)lpProc);

        return(fOK);
}

/***************************************************************************
 * Function: dodlg_stringin
 *
 */
int FAR PASCAL
dodlg_stringin(HWND hDlg, UINT message, UINT wParam, LONG lParam)
{
        switch(message) {

        case WM_INITDIALOG:
                if (dlg_caption != NULL) {
                        SendMessage(hDlg, WM_SETTEXT, 0, (LONG) dlg_caption);
                }
                if (dlg_prompt != NULL) {
                        SetDlgItemText(hDlg, IDD_LABEL, dlg_prompt);
                }
                if (dlg_default) {
                        SetDlgItemText(hDlg, IDD_FILE, dlg_default);
                }
                return(TRUE);

        case WM_COMMAND:
                switch(GET_WM_COMMAND_ID(wParam, lParam)) {

                case IDCANCEL:
                        EndDialog(hDlg, FALSE);
                        return(TRUE);

                case IDOK:
                        GetDlgItemText(hDlg, IDD_FILE, dlg_result, dlg_size);
                        EndDialog(hDlg, TRUE);
                        return(TRUE);
                }
        }
        return (FALSE);
}

/***************************************************************************
 * Function: My_mbschr
 *
 * Purpose:
 *
 * DBCS version of strchr
 *
 */
unsigned char * _CRTAPI1 My_mbschr(
    unsigned char *psz, unsigned short uiSep)
{
    while (*psz != '\0' && *psz != uiSep) {
        psz = CharNext(psz);
    }
    return *psz == uiSep ? psz : NULL;
}
/***************************************************************************
 * Function: My_mbsncpy
 *
 * Purpose:
 *
 * DBCS version of strncpy
 *
 */
unsigned char * _CRTAPI1 My_mbsncpy(
unsigned char *psz1, const unsigned char *psz2, size_t Length)
{
        int nLen = (int)Length;
unsigned char *pszSv = psz1;

while (0 < nLen) {
if (*psz2 == '\0') {
*psz1++ = '\0';
nLen--;
} else if (IsDBCSLeadByte(*psz2)) {
if (nLen == 1) {
*psz1 = '\0';
} else {
*psz1++ = *psz2++;
*psz1++ = *psz2++;
}
nLen -= 2;
} else {
*psz1++ = *psz2++;
nLen--;
}
}
return pszSv;
}

/***************************************************************************
 * Function: LoadRcString
 *
 * Purpose: Loads a resource string from string table and returns a pointer
 *          to the string.
 *
 * Parameters: wID - resource string id
 *
 */
LPTSTR APIENTRY LoadRcString(UINT wID)
{
    static TCHAR szBuf[512];

    LoadString((HANDLE)GetModuleHandle(NULL),wID,szBuf,sizeof(szBuf));
    return szBuf;
}

LPTSTR APIENTRY LoadRcString2(UINT wID)
{
    static TCHAR szBuf[512];

    LoadString((HANDLE)GetModuleHandle(NULL),wID,szBuf,sizeof(szBuf));
    return szBuf;
}


VIEW.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 Header *******************************
* Module Name: VIEW.C
*
* Maps rows in window to items in COMPLIST
*
* Functions:
*
* view_new()
* view_setcomplist()
* view_getcomplist()
* view_close()
* view_delete()
* view_outline()
* view_expand()
* view_gettext()
* view_getlinenr_left()
* view_getlinenr_right()
* view_getwidth()
* view_getrowcount()
* view_getstate()
* view_getitem()
* view_isexpanded()
* view_getcurrenttag()
* view_newitem()
* view_changeviewoptions()
* view_changediffoptions()
* view_findchange()
* view_outline_opt()
* view_freemappings()
* view_findrow()
* view_expand_item()
*
* Comments:
*
* A view owns a COMPLIST, and talks to a table window. The table window
* shows 3 columns: line nr, tag and text. We also need to supply a state
* for each row (used to select colour scheme).
*
* The COMPLIST can give us a list of its COMPITEMs. Each of these can give
* us a tag (eg the filenames compared) and the text (usually the compare
* result), and the state. We make the line number from the
* COMPITEM's place in the list.
*
* If we are asked to switch to 'expand' mode, we ask the selected COMPITEM
* for its composite section list. We can then get the state (and thus
* the tag) from each SECTION, and the line nr and text from the LINEs within
* each section.
*
* When moving between expand and outline, and when refreshing the view
* for some option change, we have to be careful to keep the current row
* and the selected row in the table what the user would expect.
*
* Functions in this module can be called from the UI thread (to refresh
* the display) and simultaneously from a worker thread to update the
* view mapping (view_setcomplist, view_newitem). We use a critical section
* to manage the synchronisation. We need to protect all access/modification
* to the view structure elements (particularly bExpand, rows, pLines and
* pItems), BUT we must not hold the critical section over any calls
* to SendMessage.
*
* We use the global options in windiff.h, and we allocate memory from the
* heap hHeap which has been initialised elsewhere. Points in time-intensive
* loops call Poll() defined elsewhere.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <commdlg.h>

#include "gutils.h"
#include "table.h"
#include "state.h"
#include "windiff.h"
#include "wdiffrc.h"
#include "list.h"
#include "line.h"
#include "scandir.h"
#include "file.h"
#include "section.h"
#include "compitem.h"
#include "complist.h"
#include "view.h"

/*
 * data structures
 */

/* in expand mode, we keep an array of one of these per screen line. */
typedef struct viewline {
        LINE line;              /* handle to LINE for this row */
        SECTION section;        /* handle to section containing this line */
        int nr_left;            /* line nr in left file */
        int nr_right;           /* line nr in right file */
} VIEWLINE, FAR * PVIEWLINE;

/*
 * The users VIEW handle is in fact a pointer to this structure
 */
struct view {

        HWND     hwnd;          /* the table window to send notifies to */

        COMPLIST cl;            /* the complist that we own */

   BOOL          bExpand;       /* true if we are in expand mode */

        COMPITEM ciSelect;      /* selected compitem (in expand mode) */

        int      rows;          /* number of rows in this view */

        char     nrtext[12];    /* we use this in view_gettext for the line
                                 * number column. overwritten on each call
                                 */
        int      maxtag, maxrest;/* column widths in characters for cols 1, 2 */

        /* if we are in outline mode, we map the row number to one entry
         * in this array of COMPITEM handles. this pointer will
         * be NULL in expand mode
         */
        COMPITEM FAR * pItems;

        /* in expand mode we use this array of line and section handles */
        PVIEWLINE pLines;
};

CRITICAL_SECTION CSView;
static BOOL bDoneInit = FALSE;

#define ViewEnter()     EnterCriticalSection(&CSView);
#define ViewLeave()     LeaveCriticalSection(&CSView);

void view_outline_opt(VIEW view, BOOL bRedraw);
void view_freemappings(VIEW view);
int view_findrow(VIEW view, int number, BOOL bRight);
BOOL view_expand_item(VIEW view, COMPITEM ci);

/***************************************************************************
 * Function: view_new
 *
 * Purpose:
 *
 * Create a new view. At this point, we are told the table window handle,
 * and nothing else.
 *
 */
VIEW
view_new(HWND hwndTable)
{
        VIEW view;

        if (!bDoneInit) {
                InitializeCriticalSection(&CSView);
                bDoneInit = TRUE;
        }

        /* alloc the view from the heap */
        view = (VIEW) gmem_get(hHeap, sizeof(struct view));

        /* set the default fields */
        view->hwnd = hwndTable;
        view->cl = NULL;
        view->bExpand = FALSE;
        view->ciSelect = NULL;
        view->rows = 0;
        view->pItems = NULL;
        view->pLines = NULL;

        return(view);
}

/***************************************************************************
 * Function: view_setcomplist
 *
 * Purpose:
 *
 * We have to separate view_new and view_setcomplist because we need
 * to give the view handle to the complist and the complist handle to the
 * view. So do a view_new to create a null view; then complist_new() to
 * which you pass a view handle. The complist will then register itself
 * with the view by calling this function. During the build of the complist,
 * it will also update us by calling view_additem, so that we can refresh
 * the display.
 *
 * Here we should initialise an outline view of the complist.
 *
 * We also talk to the status bar using SetNames to set the names of
 * the two items.
 */
BOOL
view_setcomplist(VIEW view, COMPLIST cl)
{
        LPSTR left, right, both;

        if (view == NULL) {
                return(FALSE);
        }

        /* there can be only one call to this per VIEW */
        if (view->cl != NULL) {
                return (FALSE);
        }

        ViewEnter();

        view->cl = cl;

        /* set names on status bar to root names of left and right trees */
        left = complist_getroot_left(cl);
        right = complist_getroot_right(cl);
        both = gmem_get(hHeap, lstrlen(left) + lstrlen(right) +4);
        wsprintf((LPTSTR)both, "%s : %s", left, right);
        ViewLeave();
        SetNames(both);
        ViewEnter();
        gmem_free(hHeap, both, lstrlen(both)+1);
        complist_freeroot_left(cl, left);
        complist_freeroot_right(cl, right);

        ViewLeave();

        view_outline(view);
}

/***************************************************************************
 * Function: view_getcomplist
 *
 * Purpose:
 *
 * Return a handle to the complist owned by this view
 */
COMPLIST
view_getcomplist(VIEW view)
{
        if (view == NULL) {
                return(NULL);
        }

        return(view->cl);
}

/***************************************************************************
 * Function: view_close
 *
 * Purpose:
 *
 * Close a view. Notify the table window that this view should be
 * closed. When the table window has finished with it, it will send
 * a TQ_CLOSE notify that should result in view_delete being called
 * and the memory being freed.
 */
void
view_close(VIEW view)
{
        if (view == NULL) {
                return;
        }

        SendMessage(view->hwnd, TM_NEWID, 0, 0);
}

/***************************************************************************
 * Function: view_delete
 *
 * Purpose:
 *
 * Delete a view and all associated data.
 *
 * This function should only be called in response to the table window
 * sending a TQ_CLOSE message. To close the view, call view_close and
 * wait for the TQ_CLOSE before calling this.
 *
 * We delete the associated COMPLIST and all its associated structures.
 */
void
view_delete(VIEW view)
{
        if (view == NULL) {
                return;
        }

        /* we have two arrays that are used for the mapping - an array
         * of compitem handles in outline mode, and an array of
         * VIEWLINE structures in expand mode
         */

        view_freemappings(view);

        complist_delete(view->cl);

        gmem_free(hHeap, (LPSTR) view, sizeof(struct view));
}

/***************************************************************************
 * Function: view_outline
 *
 * Purpose:
 *
 * Build an outline mode mapping where one row represents one COMPITEM in
 * the list. Check the global option flag outline_include to see which items
 * we should include.
 *
 * If we were in expand mode, then set as the selection the row in outline mode
 * that we were expanding. Also remember to free up the expand mode mapping
 * array
 *
 * Once we have built the new mapping, notify the table window to
 * redraw itself.
 */
void
view_outline(VIEW view)
{
        if (view == NULL) {
                return;
        }

        /* all work done by view_outline_opt - this function
         * gives us the option of not updating the display
         */
        view_outline_opt(view, TRUE);
}

/***************************************************************************
 * Function: view_expand
 *
 * Purpose:
 *
 * Switch to expand mode, expanding the given row into a view
 * of the differences in that file.
 *
 * Map the given row nr into a compitem handle, and then
 * call the internal function with that.
 */
BOOL
view_expand(VIEW view, long row)
{
        COMPITEM ci;
        BOOL bRet;

        ViewEnter();

        if ((view == NULL) || (view->bExpand)) {
                /* no view, or already expanded */
                ViewLeave();
                return(FALSE);
        }

        if (row >= view->rows) {
                /* no such row */
                ViewLeave();
                return FALSE;
        }

        /* remember the compitem we are expanding */
        ci = view->pItems[row];

        bRet = view_expand_item(view, ci);
        // view_expand_item does the...
        // ViewLeave();
        return(bRet);
}

/***************************************************************************
 * Function: view_gettext
 *
 * Purpose:
 *
 * Return the text associated with a given column of a given row.
 * Return a pointer that does not need to be freed after use - ie
 * a pointer into our data somewhere, not a copy
 */
LPSTR
view_gettext(VIEW view, long row, int col)
{
        int line;
        int state;
        LPSTR pstr;

        if (view == NULL) {
                return (NULL);
        }

        ViewEnter();

        if (row >= view->rows) {
                ViewLeave();
                return(NULL);
        }

        if (view->bExpand) {
                /* we are in expand mode */

                state = section_getstate(view->pLines[row].section);

                switch(col) {
                case 0:
                        /* row nr */

                        /* line numbers can be from either original file
                         * this is a menu-selectable option
                         */
                        switch(line_numbers) {
                        case IDM_NONRS:
                                pstr = NULL;
                                break;

                        case IDM_LNRS:
                                line = view->pLines[row].nr_left;
                                if (state == STATE_MOVEDRIGHT) {
                                        line = -line;
                                }
                                break;

                        case IDM_RNRS:
                                line = view->pLines[row].nr_right;
                                if (state == STATE_MOVEDLEFT) {
                                        line = -line;
                                }
                                break;
                        }
                        if (line == 0) {
                                ViewLeave();
                                return(NULL);
                        }

                        if (line < 0) {
                                /* lines that are moved appear twice.
                                 * show the correct-sequence line nr
                                 * for the out-of-seq. copy in brackets.
                                 */
                                wsprintf((LPTSTR)view->nrtext, "(%d)", abs(line));
                        } else  {
                                wsprintf((LPTSTR)view->nrtext, "%d", line);
                        }
                        pstr = view->nrtext;
                        break;

                case 1:
                        /* tag text - represents the state of the line */

                        switch(state) {
                        case STATE_SAME:
                                pstr = "    ";
                                break;

                        case STATE_LEFTONLY:
                                pstr = " <! ";
                                break;

                        case STATE_RIGHTONLY:
                                pstr = " !> ";
                                break;

                        case STATE_MOVEDLEFT:
                                pstr = " <- ";
                                break;

                        case STATE_MOVEDRIGHT:
                                pstr = " -> ";
                                break;
                        }
                        break;

                case 2:
                        /* main text - line */
                        pstr = line_gettext(view->pLines[row].line);
                        break;
                }
        } else {
                /* outline mode */
                switch(col) {
                case 0:
                        /* row number - just the line number */
                        wsprintf((LPTSTR)view->nrtext, "%d", row+1);
                        pstr = view->nrtext;
                        break;

                case 1:
                        /* tag */
                        pstr = compitem_gettext_tag(view->pItems[row]);
                        break;

                case 2:
                        /* result text */
                        pstr = compitem_gettext_result(view->pItems[row]);
                        break;
                }
        }
        ViewLeave();
        return(pstr);
}

/***************************************************************************
 * Function: view_getlinenr_left
 *
 * Purpose:
 *
 * Return the line number that this row had in the original left
 * file. 0 if not in expand mode. 0 if this row was not in the left file.
 * -(linenr) if this row is a MOVED line, and this is the right file
 * copy
 */
int
view_getlinenr_left(VIEW view, long row)
{
        int state, line;

        if ((view == NULL) || (row >= view->rows) || !view->bExpand) {
                return 0;
        }

        ViewEnter();
        state = section_getstate(view->pLines[row].section);
        line = view->pLines[row].nr_left;
        if (state == STATE_MOVEDRIGHT) {
                line = -line;
        }
        ViewLeave();

        return(line);
}

/***************************************************************************
 * Function: view_getlinenr_right
 *
 * Purpose:
 *
 * Return the line number that this row had in the original right
 * file. 0 if not in expand mode. 0 if this row was not in the right file.
 * -(linenr) if this row is a MOVED line, and this is the left file
 * copy
 */
int
view_getlinenr_right(VIEW view, long row)
{
        int state, line;

        if ((view == NULL) || (row > view->rows) || !view->bExpand) {
                return 0;
        }

        ViewEnter();

        state = section_getstate(view->pLines[row].section);
        line = view->pLines[row].nr_right;
        if (state == STATE_MOVEDLEFT) {
                line = -line;
        }
        ViewLeave();

        return(line);
}

/***************************************************************************
 * Function: view_getwidth
 *
 * Purpose:
 *
 * Find the maximum width in characters for the given column
 */
int
view_getwidth(VIEW view, int col)
{
        if (view == NULL) {
                return(0);
        }

        switch(col) {
        case 0:
                /* line nr column - always 5 characters wide */
                return(5);

        case 1:
                /* this is a proportional font field, so add on a margin
                 * for error
                 */
                return(view->maxtag + (view->maxtag / 20));
        case 2:
                /* this now includes the tab expansion allowance */
                return(view->maxrest);
        default:
                return(0);
        }
}

/***************************************************************************
 * Function: view_getrowcount
 *
 * Purpose:
 *
 * How many rows are there in this view ?
 */
long
view_getrowcount(VIEW view)
{
        if (view == NULL) {
                return(0);
        }

        return(view->rows);
}

/***************************************************************************
 * Function: view_getstate
 *
 * Purpose:
 *
 * Return the state for the current row. This is used
 * to select the text colour for the row
 *
 * States for sections are obtained from section_getstate (and apply, and
 * to all lines in that section. States for compitems are obtained
 * from compitem_getstate.
 */
int
view_getstate(VIEW view, long row)
{
        int state;

        if (view == NULL) {
                return(0);
        }

        ViewEnter();
        if (row >= view->rows) {
                state = 0;
        } else if (view->bExpand) {
                /* its a line state that's needed */
                state = section_getstate(view->pLines[row].section);
        } else {

                /* its a compitem state */
                state = compitem_getstate(view->pItems[row]);
        }
        ViewLeave();
        return(state);
}

/***************************************************************************
 * Function: view_gethandle
 *
 * Purpose:
 *
 * Return a handle to the current compitem. In expand mode,
 * returns the handle to the compitem we are expanding. In outline
 * mode, returns the handle to the compitem for the given row, if valid,
 * or NULL otherwise. row is only used if not in expand mode.
 */
COMPITEM
view_getitem(VIEW view, long row)
{
        COMPITEM ci;

        if (view == NULL) {
                return(NULL);
        }

        ViewEnter();

        if (!view->bExpand) {
                if ((row >= 0) && (row < view->rows)) {
                        ci = view->pItems[row];
                } else {
                        ci = NULL;
                }
        } else {
                ci = view->ciSelect;
        }

        ViewLeave();
        return(ci);
}

/***************************************************************************
 * Function: view_isexpanded
 *
 * Purpose:
 *
 * Return TRUE if the current mapping is expanded mode
 */
BOOL
view_isexpanded(VIEW view)
{
        if (view == NULL) {
                return(FALSE);
        }
        return(view->bExpand);
}

/***************************************************************************
 * Function: view_getcurrenttag
 *
 * Purpose:
 *
 * Return a text string describing the view. This is NULL in outline mode,
 * or the tag text for the current compitem in expanded mode
 */
LPSTR
view_getcurrenttag(VIEW view)
{
        LPSTR str;

        if ((view == NULL) || (!view->bExpand)) {
                return(NULL);
        } else {
                ViewEnter();

                str = compitem_gettext_tag(view->ciSelect);

                ViewLeave();
                return(str);

        }
}

/***************************************************************************
 * Function: view_newitem
 *
 * Purpose:
 *
 * Notify that CompItems have been added to the complist.
 *
 * Rebuild the view (if in outline mode), and refresh the table. Use
 * the table message TM_APPEND if possible (if column widths have not
 * change). If we have to do TM_NEWLAYOUT, then ensure we scroll
 * back to the right row afterwards.
 *
 * This causes a Poll() to take place. We return TRUE if an abort is
 * pending - in this case, the caller should abandon the scan loop.
 *
 * Enter the critical section for this function since this can be
 * called from the worker thread while the UI thread is using the
 * view that we are about to change.
 *
 * EXCEPT THAT WE DON'T DARE.  We cannot ever call SendMessage from the
 * worker thread within CSView.  If there is conflict, it will hang.
 */
BOOL
view_newitem(VIEW view)
{
        int maxtag, maxrest;
        long rownr;

        if ((view == NULL) || (view->bExpand)) {
                /* not in outline mode - nothing to do */
                return(Poll());
        }

        /* save some state about the present mapping */
        maxtag = view->maxtag;
        maxrest = view->maxrest;

        /* re-do the outline mapping, but don't tell the table
         * class.
         */
        view_outline_opt(view, FALSE);

        /* have the column widths changed ? */
        if ((maxtag < view->maxtag) || (maxrest < view->maxrest)) {
                /* yes - need complete redraw */

                /* find the row at the top of the window */
                rownr = SendMessage(view->hwnd, TM_TOPROW, FALSE, 0);

                /* switch to new mapping */
                SendMessage(view->hwnd, TM_NEWLAYOUT, 0, (DWORD) view);

                /* return to old row if possible - we know
                 * that row is still there since we have only added
                 * rows, and not changed any of the existing mapping
                 *
                 * Alas this is no longer true.  However the table class
                 * will defend itself against calls for a bogus top row.
                 */
                if (rownr >= 0) {
                        SendMessage(view->hwnd, TM_TOPROW, TRUE, rownr);
                }
        } else {
                /* no - we can just append */

                /*
                 * The mapping may have
                 * changed since we released the critsec. however we are still
                 * safe. The table will not allow us to reduce the number of
                 * rows, so the worst that can happen is that the table will
                 * think there are too many rows, and the table message handler
                 * will handle this correctly (return null for the text).
                 * The only visible effect is therefore that the scrollbar
                 * position is wrong.
                 */

                SendMessage(view->hwnd, TM_APPEND, view->rows, (DWORD) view);
        }

        /* Poll to keep the UI updated on NT. Returns true if abort pending.
         */
        return(Poll());
}

/***************************************************************************
 * Function: view_changeviewoptions
 *
 * Purpose:
 *
 * The view mapping options (eg outline_include, expand_mode) have changed -
 * re-do the mapping and then scroll back to the same position in the window
 * if possible.
 */
void
view_changeviewoptions(VIEW view)
{
        long row;
        int state, number;
        BOOL bRight;

        if (view == NULL) {
                return;
        }

        /* find what row we are currently on. Do this BEFORE we enter CSView */
        row = SendMessage(view->hwnd, TM_TOPROW, FALSE, 0);

        ViewEnter();

        if (!view->bExpand) {

                /* outline mode. maintaining current position is
                 * unimportant
                 */
                view_outline(view);
                ViewLeave();
                return;
        }

        /* expanded mode */

        /* save the line number on one side (and remember which side) */
        if (row >= view->rows) {
                number = -1;
        } else {
                state = section_getstate(view->pLines[row].section);
                if ((state == STATE_MOVEDRIGHT) ||
                    (state == STATE_RIGHTONLY)) {
                            bRight = TRUE;
                            number = view->pLines[row].nr_right;
                } else {
                        bRight = FALSE;
                        number = view->pLines[row].nr_left;
                }
        }

        /* make the new mapping */
        view_expand_item(view, view->ciSelect);

        /* find the nearest row in the new view */
        if (number >= 0) {

                ViewEnter();
                row = view_findrow(view, number, bRight);
                ViewLeave();

                /* scroll this row to top of window */
                if (row >= 0) {

                        SendMessage(view->hwnd, TM_TOPROW, TRUE, row);
                        return;
                }
        }
}

/***************************************************************************
 * Function: view_changediffoptions
 *
 * Purpose:
 *
 * The compare options have changed - re-do the compare completely
 * and make the new mapping. Retain current position in the file.
 */
void
view_changediffoptions(VIEW view)
{
        int state, number;
        long row;
        BOOL bRight;
        LIST li;
        COMPITEM ci;

        if (view == NULL) {
                return;
        }

        /*
         * get current row before entering critsec.
         */
        row = SendMessage(view->hwnd, TM_TOPROW, FALSE, 0);

        ViewEnter();

        /* find the current line number so we can go back to it
         * (only if we are in expanded mode
         */
        if (view->bExpand) {

                state = section_getstate(view->pLines[row].section);
                if ((state == STATE_MOVEDRIGHT) ||
                    (state == STATE_RIGHTONLY)) {
                            bRight = TRUE;
                            number = view->pLines[row].nr_right;
                } else {
                        bRight = FALSE;
                        number = view->pLines[row].nr_left;
                }
        }

        /* To force a recompare using the new options, we must
         * tell each compitem to discard its current compare result.
         * We need to traverse the list of compitems calling this
         * for each compare.
         */
        li = complist_getitems(view->cl);

        for (ci = (COMPITEM) List_First(li); ci != NULL; ci = (COMPITEM) List_Next(ci)) {
                compitem_discardsections(ci);
        }

        /* if we are in outline mode, we have nothing more to do */
        if (!view->bExpand) {
                ViewLeave();
                return;
        }

        view_expand_item(view, view->ciSelect);

        /* find the nearest row in the new view */
        ViewEnter();
        row = view_findrow(view, number, bRight);
        ViewLeave();

        /* scroll this row to top of window */
        if (row >= 0) {
                SendMessage(view->hwnd, TM_TOPROW, TRUE, row);
        }
}

/***************************************************************************
 * Function: view_findchange
 *

* Purpose:
 *
 * Find the next changed - ie non-same - row in a given direction.
 * For outline mode we find the next STATE_DIFFER. For expand mode, we
 * find the next section
 */
long
view_findchange(VIEW view, long startrow, BOOL bForward)
{
        long i;

        if (view == NULL) {
                return(0);
        }

        ViewEnter();

        if (bForward) {

                if (startrow >= view->rows) {
                        ViewLeave();
                        return(-1);
                }

                if (!view->bExpand) {

                        /* look for next compitem with an expandable state*/
                        for (i = startrow; i < view->rows; i++) {
                                if (compitem_getstate(view->pItems[i]) == STATE_DIFFER) {
                                        ViewLeave();
                                        return(i);
                                }
                        }
                        /* none found */
                        ViewLeave();
                        return(-1);
                } else {
                        /*
                         * find the next line that matches, then go on to the
                         * next line that does not match
                         *
                         */
                        for (i= startrow; i < view->rows; i++) {
                                if (section_getstate(view->pLines[i].section)
                                        == STATE_SAME) {
                                                break;
                                }
                        }
                        for ( ; i < view->rows; i++) {
                                if (section_getstate(view->pLines[i].section)
                                        != STATE_SAME) {
                                                ViewLeave();
                                                return(i);
                                }
                        }

                        ViewLeave();

                        return(-1);
                }
        } else {
                /* same search backwards */
                if (startrow <= 0) {
                        ViewLeave();
                        return(-1);
                }
                if (view->bExpand) {
                        /* search backwards for first row that is not
                         * changed (has state SAME). then carry on for
                         * the next changed row.
                         */
                        for (i = startrow; i >= 0; i--) {
                                if (section_getstate(view->pLines[i].section)
                                        == STATE_SAME) {
                                                break;
                                }
                        }
                        for ( ; i >= 0; i--) {
                                if (section_getstate(view->pLines[i].section)
                                        != STATE_SAME) {
                                                ViewLeave();
                                                return(i);
                                }
                        }
                        ViewLeave();
                        return(-1);
                } else {
                        for (i = startrow; i >= 0; i--) {
                                if(compitem_getstate(view->pItems[i]) == STATE_DIFFER) {
                                        ViewLeave();
                                        return(i);
                                }
                        }
                        ViewLeave();
                        return(-1);
                }
        }
}

/***************************************************************************
 * Function: view_findrow
 *
 * Purpose:
 *
 * Find the new row number for the line numbered 'number'
 * or the nearest line if possible. If bRight is true, number is
 * a right file number; otherwise it is a left file number.
 *
 * We must be in expand mode
 */
int
view_findrow(VIEW view, int number, BOOL bRight)
{
        int i;

        if (!view->bExpand) {
                return(0);
        }

        for (i = 0; i < view->rows; i++) {

                if (bRight) {
                        if (view->pLines[i].nr_right == number) {

                                /* found the exact number */
                                return(i);

                        } else if (view->pLines[i].nr_right > number) {

                                /* passed our line -stop here */
                                return(i);
                        }
                } else {
                        if (view->pLines[i].nr_left == number) {

                                /* found the exact number */
                                return(i);

                        } else if (view->pLines[i].nr_left > number) {

                                /* passed our line -stop here */
                                return(i);
                        }
                }
        }
        return(-1);
}

/***************************************************************************
 * Function: view_freemappings
 *
 * Purpose:
 *
 * Free memory associated with the expand mode or outline mode mappings
 * called whenever we rebuild the mapping, and on deletion
 */
void
view_freemappings(VIEW view)
{

        if (view->pLines) {
                gmem_free(hHeap, (LPSTR) view->pLines,
                        view->rows * sizeof(VIEWLINE));
                view->pLines = NULL;
        } else if (view->pItems) {

                /* previous outline mapping array is still there - free it
                 * before we build a new one
                 */

                gmem_free(hHeap, (LPSTR) view->pItems,
                        view->rows * sizeof(COMPLIST));
                view->pItems = NULL;
        }
}

/***************************************************************************
 * Function: view_outline_opt
 *
 * Purpose:
 *
 * Build a view outline to map one row to a COMPITEM handle by traversing
 * the list of COMPITEMs obtained from our complist.
 * Optionally tell the table class to redraw (if bRedraw), and if so,
 * scroll the new table to select the row that represents the
 * file we were expanding, if possible
 */
void
view_outline_opt(VIEW view, BOOL bRedraw)
{
        int prev_row = -1;      /* the row nr of the previously-expanded row*/
        int i;                  /* nr of includable items */
        LIST li;
        COMPITEM ci;
        int state;
        TableSelection select;

        /*
         * check that view_setcomplist has already been called. if not,
         * nothing to do
         */
        if (view->cl == NULL) {
                return;
        }

        ViewEnter();

        /* clear the mode flag and free up memory associated with expand mode */
        view->bExpand = FALSE;
        view_freemappings(view);

        /* traverse the list of compitems counting up the number of
         * includable items
         */
        li = complist_getitems(view->cl);

        ci = (COMPITEM) List_First(li);
        for (i = 0; ci != NULL; ci = (COMPITEM) List_Next(ci)) {

                state = compitem_getstate(ci);

                if (((outline_include & INCLUDE_SAME) && (state == STATE_SAME)) ||
                    ((outline_include & INCLUDE_DIFFER) && (state == STATE_DIFFER)) ||
                    ((outline_include & INCLUDE_LEFTONLY) && (state == STATE_FILELEFTONLY)) ||
                    ((outline_include & INCLUDE_RIGHTONLY) && (state == STATE_FILERIGHTONLY))) {
                        i++;
                }
        }

        /* allocate an array big enough for all of these */
        view->pItems = (COMPITEM FAR *) gmem_get(hHeap, i * sizeof(COMPITEM));
        view->rows = i;

        /* keep track of the column widths */
        view->maxtag = 0;
        view->maxrest = 0;

        /* loop through again filling the array, and at the same time looking
         * out for the handle of the previously expanded item
         */
        ci = (COMPITEM) List_First(li);
        for (i = 0; ci != NULL; ci = (COMPITEM) List_Next(ci)) {

                state = compitem_getstate(ci);

                if (((outline_include & INCLUDE_SAME) && (state == STATE_SAME)) ||
                    ((outline_include & INCLUDE_DIFFER) && (state == STATE_DIFFER)) ||
                    ((outline_include & INCLUDE_LEFTONLY) && (state == STATE_FILELEFTONLY)) ||
                    ((outline_include & INCLUDE_RIGHTONLY) && (state == STATE_FILERIGHTONLY))) {

                        view->pItems[i] = ci;

                        if (ci == view->ciSelect) {
                                prev_row = i;
                        }

                        /* check the column widths in characters */
                        view->maxtag = max(view->maxtag,
                                           lstrlen(compitem_gettext_tag(ci)));
                        view->maxrest = max(view->maxrest,
                                            lstrlen(compitem_gettext_result(ci)));

                        i++;

                }
        }
        ViewLeave();

        /* inform table of new layout of table - force refresh */
        if (bRedraw) {
                SendMessage(view->hwnd, TM_NEWLAYOUT, 0, (DWORD) view);

                /* scroll to and highlight the row that represents the file
                 * we were previously expanding
                 */
                if (prev_row != -1) {
                        select.startrow = prev_row;
                        select.startcell = 0;
                        select.nrows = 1;
                        select.ncells = 1;
                        SendMessage(view->hwnd, TM_SELECT, 0,
                                (DWORD) (LPSTR) &select);
                }
        }
}

/***************************************************************************
 * Function: view_expand_item
 *
 * Purpose:
 *
 * Expand a view - given the handle to the compitem to expand.
 *
 * Called from view_expand, and also to re-do an expanded view
 * after options change in view_changediffoptions and _changeviewoptions
 *
 * We get the composite section list from the compitem,
 * and pick out all the sections that are includable (according
 * to the global option expand_mode: we include all sections, or
 * just those in one side left or right). Once we know the count of rows,
 * allocate the mapping array: in each element of the array we keep
 * a handle to the section for that row (to get the state and hence the
 * tag text), and a handle to the line within that section (for the line text).
 *
 * We no longer insist on only expanding text files that differ - if the
 * compitem can give us a composite section list, we will map it.
 *
 * We need to be able to give a line number for a line, in either of
 * the original files according to which option is in force. Each section
 * can give us its base line number (number of first line in section) in
 * each of the two files or 0 if not present, and we track these here.
 *
 * MUST BE INSIDE CSView BEFORE CALLING HERE.
 */
BOOL
view_expand_item(VIEW view, COMPITEM ci)
{
        LIST li;
        SECTION sh;
        LINE line1, line2;
        int i, base_left, base_right, state;

        /* remember the compitem we are expanding */
        view->ciSelect = ci;

        /* get the composite section list */
        li = compitem_getcomposite(view->ciSelect);
        if (li == NULL) {
                ViewLeave();
                return FALSE;
        }

        /* switch modes and free the current mapping
         *
         * NOTE: must do this AFTER the compitem_getcomposite,
         * since that can fail: if it fails it could put up a
         * message box, and that could cause a queued paint message
         * to be processed, which would cause us to use these mappings
         * and gpfault if they had been cleared first.
         */
        view->bExpand = TRUE;
        view_freemappings(view);

        /* loop through totalling the lines in sections
         * that we should include
         */
        view->rows = 0;
        for (sh = (SECTION) List_First(li); sh != NULL;
            sh = (SECTION) List_Next(sh)) {

                state = section_getstate(sh);

                if (expand_mode == IDM_RONLY) {
                        if ((state == STATE_LEFTONLY) ||
                            (state == STATE_MOVEDLEFT)) {
                                    continue;
                        }
                } else if (expand_mode == IDM_LONLY) {
                        if ((state == STATE_RIGHTONLY) ||
                            (state == STATE_MOVEDRIGHT)) {
                                    continue;
                        }
                }

                /* include all lines in this section */
                view->rows += section_getlinecount(sh);
        }

        /* allocate the memory for the mapping array */
        view->pLines = (PVIEWLINE) gmem_get(hHeap, view->rows * sizeof(VIEWLINE));

        /* loop through the sections again filling in the mapping array */
        i = 0;
        view->maxtag = 5;
        view->maxrest = 0;
        for (sh = (SECTION) List_First(li); sh != NULL;
            sh = (SECTION) List_Next(sh)) {

                state = section_getstate(sh);

                if (expand_mode == IDM_RONLY) {
                        if ((state == STATE_LEFTONLY) ||
                            (state == STATE_MOVEDLEFT)) {
                                    continue;
                        }
                } else if (expand_mode == IDM_LONLY) {
                        if ((state == STATE_RIGHTONLY) ||
                            (state == STATE_MOVEDRIGHT)) {
                                    continue;
                        }
                }

                /* find the base line number in each file */
                base_left = section_getleftbasenr(sh);
                base_right = section_getrightbasenr(sh);

                /* add each line in section to the view. section_getfirst()
                 * returns us to a handle that is in a list. We can
                 * call List_Next and will eventually get to the
                 * line returned by section_getlast(). Sections always have
                 * at least one line
                 */
                line1 = section_getfirstline(sh);
                line2 = section_getlastline(sh);

                for (; line1 != NULL; line1 = (LINE) List_Next(line1)) {

                        view->pLines[i].line = line1;
                        view->pLines[i].section = sh;

                        /* calculate the line number for this line by
                         * incrementing the base nr for this section
                         */

                        view->pLines[i].nr_left = base_left;
                        if (base_left != 0) {
                                base_left++;
                        }

                        view->pLines[i].nr_right = base_right;
                        if (base_right != 0) {
                                base_right++;
                        }

                        /* increment index into view */
                        i++;

                        /* check the column widths */
                        view->maxrest = max(view->maxrest,
                                            (line_gettabbedlength(line1, 8)));

                        /* end of section ? */
                        if (line1 == line2) {
                                break;
                        }
                }
        }

        /* We must NOT hold a critical section here as SendMessage may hang */
        ViewLeave();

        /*inform table window of revised mapping */
        SendMessage(view->hwnd, TM_NEWLAYOUT, 0, (DWORD) view);

        return(TRUE);
}


VIEW.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.
\******************************************************************************/

/*
 * VIEW.H
 */

/* view.h includes the term COMPLIST: complist.h uses the term VIEW.
 */
#ifndef INC_VIEW_COMPLIST
#define INC_VIEW_COMPLIST
typedef struct view FAR * VIEW;                 /* handle to a VIEW     */
typedef struct complist FAR * COMPLIST;         /* handle to a complist */
#endif // INC_VIEW_COMPLIST

VIEW view_new(HWND hwndTable);
BOOL view_setcomplist(VIEW view, COMPLIST cl);
COMPLIST view_getcomplist(VIEW view);
void view_close(VIEW view);
void view_delete(VIEW view);
LPSTR view_gettext(VIEW view, long row, int col);
int view_getlinenr_left(VIEW view, long row);
int view_getlinenr_right(VIEW view, long row);
int view_getwidth(VIEW view, int col);
long view_getrowcount(VIEW view);
int view_getstate(VIEW view, long row);
BOOL view_expand(VIEW view, long row);
void view_outline(VIEW);
COMPITEM view_getitem(VIEW view, long row);
BOOL view_isexpanded(VIEW view);
LPSTR view_getcurrenttag(VIEW view);
BOOL view_newitem(VIEW view);
void view_changeviewoptions(VIEW view);
void view_changediffoptions(VIEW view);
long view_findchange(VIEW view, long startrow, BOOL bForward);


WDIFFRC.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_FILE      111
#define IDM_DIR       112
#define IDM_PRINT     113
#define IDM_EXIT      115
#define IDM_TIME      116
#define IDM_OPEN      121
#define IDM_SAVELIST  122
#define IDM_EDITLEFT  123
#define IDM_EDITRIGHT 124
#define IDM_EDITCOMP  125
#define IDM_SETEDIT   126
#define IDM_COPYFILES 127

#define IDM_ABOUT     131

#define IDD_ICON      132
#define IDD_VERSION   133
#define IDD_LABEL     136
#define IDD_DIR1      137
#define IDD_DIR2      138
#define IDD_LAB1      139
#define IDD_LAB2      140
#define IDD_LAB3      141
#define IDD_RECURSIVE 143

#define IDM_CLOSE     161

#define IDM_OUTLINE   180
#define IDM_EXPAND    181
#define IDM_FCHANGE   182
#define IDM_LNRS      185
#define IDM_RNRS      186
#define IDM_NONRS     187
#define IDM_LONLY     188
#define IDM_RONLY     189
#define IDM_BOTHFILES 190
#define IDM_UPDATE    194
#define IDM_FPCHANGE  195
#define IDM_IGNBLANKS 196
#define IDM_PICTURE   197
#define IDM_LAZY      198
#define IDM_INCSAME   201
#define IDM_INCLEFT   202
#define IDM_INCRIGHT  203
#define IDM_INCDIFFER 204

#define IDC_RCDISP1   501
#define IDC_BAR       510
#define IDC_STATUS    550
#define IDM_ABORT     551
#define IDL_STATLAB   552
#define IDL_NAMES     553

#define IDD_FILE                    601
#define IDD_IDENTICAL               602
#define IDD_DIFFER                  603
#define IDD_LEFT                    604
#define IDD_RIGHT                   605

#define IDD_FROMGROUP               610
#define IDD_FROMLEFT                611
#define IDD_FROMRIGHT               612

#define IDS_LINE_NOT_VISIBLE        650
#define IDS_ONLY_IN                 651
#define IDS_DIFFERENT_SIZES         652
#define IDS_IDENTICAL               653
#define IDS_FILES_DIFFER            654
#define IDS_SAME_SIZE               655
#define IDS_CANT_OPEN_TMP_FILE      656
#define IDS_BAD_ARGUMENT            657
#define IDS_SELECT_FIRST_FILE       658
#define IDS_SELECT_SECOND_FILE      659
#define IDS_COULDNT_FIND            660
#define IDS_CANT_OPEN               661
#define IDS_IDENTICAL_COMMA         662
#define IDS_LEFT_ONLY_COMMA         663
#define IDS_RIGHT_ONLY_COMMA        664
#define IDS_DIFFERING               665
#define IDS_FILES_LISTED            666
#define IDS_ENTER_DIR_NAME          667
#define IDS_FAILED_TO_COPY          668
#define IDS_COPYING                 669
#define IDS_COPY_ABORTED            670
#define IDS_COPY_FAILED             671
#define IDS_COPY_COMPLETE           672
#define IDS_FILES_ONLY              673
#define IDS_EXIT                    674
#define IDS_USAGE_STR               675
#define IDS_WINDIFF_USAGE           676
#define IDS_PAGE                    677
#define IDS_NO_MORE_CHANGES         678
#define IDS_NO_PREV_CHANGES         679
#define IDS_OUTLINE                 680
#define IDS_EXPAND                  681
#define IDS_FILE_DOESNT_EXIST       682
#define IDS_FAILED_TO_LAUNCH_EDT    683
#define IDS_COMPARING               684
#define IDS_SCANNING                685
#define IDS_ABORT                   686
#define IDS_PLEASE_WAIT             687
#define IDS_ABORT_PENDING           688
#define IDS_SECONDS                 690
#define IDS_CREATE_DIFF_LIST        691
#define IDS_ENTER_EDT_CMD_LINE      692
#define IDS_HEADER_LINE_STR         693
#define IDS_SAME_SIZE_ETC           694
#define IDS_COULDNT_BE_OPENED       695
#define IDS_FILEOPEN                696


WINDIFF.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 Header *******************************
* Module Name: WINDIFF.C
*
* File and directory comparisions.
*
* Functions:
*
* windiff_UI()
* WinMain()
* windiff_usage()
* Poll()
* DoResize()
* AboutBox()
* DoPrint()
* FindNextChange()
* FindPrevChange()
* WriteProfileInt()
* ToOutline()
* ToMoved()
* do_editfile()
* do_editthread()
* SetStatus()
* SetNames()
* IsBusy()
* BusyError()
* StateToColour()
* SetSelection()
* do_gethdr()
* do_getprops()
* do_getdata()
* SvrClose()
* TableServer()
* wd_dirdialog()
* wd_copy()
* InitApplication()
* InitInstance()
* CreateTools()
* DeleteTools()
* MainWndProc()
* SetBusy()
* SetNotBusy()
* SetSelection()
* SetButtonText()
* ToExpand()
* ParseArgs()
* wd_initial()
*
* Comments:
*
* Compare two directories (including all files and subdirs). Look for names
* that are present in both (report all that are not). For files that
* are present in both, produce a line-by-line comparison of the differences
* between the two files (if any).
*
* Overview of Windiff internals - the whole program.
*
* Windiff is built from several modules (a "module" has a .h file
* which describes its interface and a .c file which implements it).
* Apart from THIS comment which tries to give an overview of the whole
* scheme of things, each module is as self-contained as possible.
* This is enforced by the use of opaque data types.  Modules cannot
* see each others' internal data structures.  Modules are abstract
* data types.  The term "Module" (from Modula2) and "Class" (from C++)
* are used synonymously.
*
*    Windiff  - main program - parse arguments, put up main window,
*               handle input, calling other modules as needed
*               invoke table class to create the main display and
*               service callbacks from the table class.
*               Contains global flags for options (e.g. ignore_blanks)
*    list     - (in gutils) a generalised LIST of anything data type
*               has full set of operations for insert, delete, join etc.
*    line     - a LINE is a numbered line of text.  Information is kept to
*               allow fast comparisons of LINEs.  A LINE can hold a
*               link to another LINE.  The links are used to connect
*               lines in one file to matching lines in the other file.
*    file     - a FILEDATA represents a file as a file name in the form
*               of a DIRITEM and a LIST of LINEs
*    scandir  - a DIRITEM represents information about a file.  (for
*               instance its name, whether it has a local copy).
*    compitem - a COMPITEM is a pair of files together with information
*               on how they compare in the form of a breakdown of the
*               files into a LIST of matching or non-matching sections.
*               Either file can be absent.  This module contains the
*               file "contrast" algorithm used for the actual comparison
*    tree       (in gutils) A binary tree.  Important because it is what
*               gives the file comparison its speed as it makes it
*               an "N log N" algorithm rather than "N squared"
*    complist - a COMPLIST is the master data structure.  It has a DIRLIST
*               of the left hand files, a DIRLIST of the right hand files
*               and a LIST of COMPITEMs. The left and right hand DIRLISTs
*               are working data used to produce the COMPLIST.  The LIST
*               is displayed as the outline table.  Any given COMPITEM can
*               be displayed as an expanded item.
*    section  - a SECTION is a section of a file (first line, last line)
*               and information as to what it matches in the other file.
*    bar.c    - the picture down the left of the screen
*               has a WNDPROC.
*    view     - Although the COMPLIST is the master state, it doesn't do
*               all the work itself.  The data is actually displayed by
*               the table class which is highly generalised.  View
*               owns a COMPLIST (and therefore calls upon the functions
*               in complist to fill it and interrogate it) and calls
*               upon (and is called back by) the functions in table to
*               actually display it.  Read about table in gutils.h
*    table.c    (in gutils) a highly generalised system for displaying
*               data in rows and columns.  The interface is in gutils.h.
*    status.c   (in gutils) the status line at the top. See gutils.h
*************************************************************************
*
* Overview of this file:
*
*   We create a table window (gutils.dll) to show the files and the
*   results of their comparisons. We create a COMPLIST object representing
*   a list of files and their differences, and a VIEW object to map between
*   the rows of the table window and the COMPLIST.
*
*   This module is responsible for creating and managing the main window,
*   placing the child windows (table, status window etc) within it, and
*   handling all menu items. We maintain global option flags set by
*   menu commands.
*
*   Creating a COMPLIST creates a list of unmatched files, and of matching
*   files that are compared with each other (these are COMPITEMS).
*   The VIEW provides a mapping between rows on the screen, and items in
*   the COMPLIST.
*
*   This version tries to maintain a responsive user interface by
*   creating worker threads to do long jobs.  This potentially creates
*   conflicts between the threads as they will both want to update common
*   variables (for instance the UI thread may be changing the options to
*   exclude identical files while the worker thread is adding in the
*   results of new comparisons).  Critical sections are used to manage
*   the conflicts.
*
*   The Edit options invoke an editor on a separate thread.  This allows
*   us to repaint our window and thereby allow the user to refer back to
*   what he saw before invoking the editor.  When he's finished editing,
*   we would of course like to refresh things and if this is still on the
*   separate thread it might clash. We avoid this clash by POSTing ourselves
*   a (WM_COMMAND, IDM_UPDATE) message.
*
****************************************************************************/

#include <windows.h>
#include <shellapi.h>
#include <stdlib.h>
#include <commdlg.h>
#include <string.h>

#include "gutils.h"
#include "table.h"
#include "list.h"
#include "scandir.h"            /* needed for file.h     */
#include "file.h"               /* needed for compitem.h */
#include "compitem.h"           /* needed for view.h     */
#include "complist.h"
#include "view.h"
#include "state.h"
#include "windiff.h"
#include "wdiffrc.h"

/*--constants and data types--------------------------------------------*/

int Version = 2;
int SubVersion = 01;

/* When we print the current table, we pass this id as the table id
 * When we are queried for the properties of this table, we know they
 * want the printing properties for the current view. We use this to
 * select different fonts and colours for the printer.
 */
#define TABID_PRINTER   1

/*
 * structure containing args passed to worker thread in initial
 * case (executing command line instructions).
 */
typedef struct {

        LPSTR first;
        LPSTR second;
        LPSTR savelist;
        UINT saveopts;
        VIEW view;
        BOOL fDeep;
} THREADARGS, FAR * PTHREADARGS;

/* Structure containing all the arguments we'd like to give to do_editfile
   Need a structure because CreateThread only allows for one argument.
*/
typedef struct {
        VIEW view;
        int option;
        int selection;
} EDITARGS, FAR * PEDITARGS;

/*---- colour scheme------------------------------- */

/* outline */
DWORD rgb_outlinehi = RGB(255, 0, 0);   /* hilighted files in outline mode  */

/* expand view */
DWORD rgb_leftfore =   RGB(  0,   0,   0);         /* foregrnd for left lines */
DWORD rgb_leftback  =  RGB(255,   0,   0);         /* backgrnd for left lines */
DWORD rgb_rightfore =  RGB(  0,   0,   0);         /* foregrnd for right lines*/
DWORD rgb_rightback =  RGB(255, 255,   0);         /* backgrnd for right lines*/

/* moved lines */
DWORD rgb_mleftfore =  RGB(  0,   0, 128);         /* foregrnd for moved-left */
DWORD rgb_mleftback =  RGB(255,   0,   0);         /* backgrnd for moved-left */
DWORD rgb_mrightfore = RGB(  0,   0, 255);         /* foregrnd for moved-right*/
DWORD rgb_mrightback = RGB(255, 255,   0);         /* backgrnd for moved-right*/

/* bar window */
DWORD rgb_barleft =    RGB(255,   0,   0);         /* bar sections in left only  */
DWORD rgb_barright =   RGB(255, 255,   0);         /* bar sections in right only */
DWORD rgb_barcurrent = RGB(  0,   0, 255);         /* current pos markers in bar */

/* module static data -------------------------------------------------*/

/* current value of window title */
char AppTitle[256];

HWND hwndClient;        /* main window */
HWND hwndRCD;           /* table window */
HWND hwndStatus;        /* status bar across top */
HWND hwndBar;           /* graphic of sections as vertical bars */

HACCEL haccel;

/* The status bar told us it should be this high. Rest of client area
 * goes to the hwndBar and hwndRCD.
 */
int status_height;

HINSTANCE hInst;   /* handle to current app instance */
HMENU hMenu;    /* handle to menu for hwndClient */

int nMinMax = SW_SHOWNORMAL;         /* default state of window normal */

/* The message sent to us as a callback by the table window needs to be
 * registered - table_msgcode is the result of the RegisterMessage call
 */
UINT table_msgcode;

/* True if we are currently doing some scan or comparison.
 * Must get critical section before checking/changing this (call
 * SetBusy.
 */
BOOL fBusy = FALSE;

int     selection       =       -1;     /* selected row in table*/

/* Options for DisplayMode field indicating what is currently shown.
 * We use this to know whether or not to show the graphic bar window.
 */
#define MODE_NULL       0       /* nothing displayed */
#define MODE_OUTLINE    1       /* a list of files displayed */
#define MODE_EXPAND     2       /* view is expanded view of one file */

int DisplayMode = MODE_NULL;    /* indicates whether we are in expand mode */

VIEW current_view = NULL;

/* command line parameters */
extern int __argc;
extern char ** __argv;

BOOL bAbort = FALSE;    /* set to request abort of current operation */

char editor_cmdline[256] = "notepad %p";  /* editor cmdline */
                          /* slick version is "s %p -#%l" */

/* app-wide global data --------------------------------------------- */

/* Handle returned from gmem_init - we use this for all memory allocations */
HANDLE hHeap;

/* Current state of menu options */
int line_numbers = IDM_LNRS;
int expand_mode = IDM_BOTHFILES;
int outline_include = INCLUDE_LEFTONLY|INCLUDE_RIGHTONLY|INCLUDE_SAME|INCLUDE_DIFFER;
BOOL ignore_blanks = TRUE;
BOOL picture_mode = TRUE;

/* function prototypes ---------------------------------------------*/

BOOL InitApplication(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);
void CreateTools(void);
void DeleteTools(void);
long APIENTRY MainWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam);
BOOL SetBusy(void);
void SetNotBusy(void);
void SetSelection(long rownr);
void SetButtonText(LPSTR cmd);
BOOL ToExpand(HWND hwnd);
void ParseArgs(int argc, char ** argv);

DWORD wd_initial(LPVOID arg);

static HANDLE ghThread = NULL;

static DWORD gdwMainThreadId;     /* threadid of main (user interface) thread
                                     initialised in winmain(), thereafter constant.
                                     See windiff_UI()
                                  */

/***************************************************************************
 * Function: windiff_UI
 *
 * Purpose:
 *
 * If you are about to put up a dialog box or in fact process input in any way
 * on any thread other than the main thread - or if you MIGHT be on a thread other
 * than the main thread, then you must call this function with TRUE before doing
 * it and with FALSE immediately afterwards.  Otherwise you will get one of a
 * number of flavours of not-very-responsiveness
 */
void windiff_UI(BOOL bAttach)
{
        DWORD dwThreadId = GetCurrentThreadId();
        if (dwThreadId==gdwMainThreadId) return;

        if (bAttach) GetDesktopWindow();
        AttachThreadInput(dwThreadId, gdwMainThreadId, bAttach);
} /* windiff_UI */

/***************************************************************************
 * Function: WinMain
 *
 * Purpose:
 *
 * Main entry point. Register window classes, create windows,
 * parse command line arguments and then perform a message loop
 */
int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

        MSG msg;

        gdwMainThreadId = GetCurrentThreadId();

        /* create any pens/brushes etc and read in profile defaults */
        CreateTools();

        /* init window class unless other instances running */
        if (!hPrevInstance)
            if (!InitApplication(hInstance))
                return(FALSE);

        /* init this instance - create all the windows */
        if (!InitInstance(hInstance, nCmdShow))
            return(FALSE);

        ParseArgs(__argc, __argv);

        /* message loop */
        while(GetMessage(&msg, NULL, 0, 0)) {
                if (!TranslateAccelerator(hwndClient, haccel, &msg)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
        }

        return (msg.wParam);

}

/***************************************************************************
 * Function: InitApplication
 *
 * Purpose:
 *
 * Register window class for the main window and the bar window.
 */
BOOL
InitApplication(HINSTANCE hInstance)
{
        WNDCLASS    wc;
        BOOL resp;

        /* register the bar window class */
        InitBarClass(hInstance);

        wc.style = 0;
        wc.lpfnWndProc = MainWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(hInstance, "WinDiff");
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = NULL;
        wc.lpszClassName =  "WinDiffViewerClass";
        wc.lpszMenuName = NULL;

        resp = RegisterClass(&wc);

        return(resp);
}

/***************************************************************************
 * Function: InitInstance
 *
 * Purpose:
 *
 * Create and show the windows
 */
BOOL
InitInstance(HINSTANCE hInstance, int nCmdShow)
{
        RECT rect;
        HANDLE hstatus;
        int bar_width;
        RECT childrc;

        hInst = hInstance;

        /* initialise a heap. we use this one heap throughout
         * the app. for all memory requirements
         */
        hHeap = gmem_init();
        /* initialise the list package */
        List_Init();

        hMenu = LoadMenu(hInstance, "WinDiffMenu");
        haccel = LoadAccelerators(hInstance, "WinDiffAccel");

        /* create the main window */
        hwndClient = CreateWindow("WinDiffViewerClass",
                            "WinDiff",
                            WS_OVERLAPPEDWINDOW,
                            CW_USEDEFAULT,
                            CW_USEDEFAULT,
                            CW_USEDEFAULT,
                            CW_USEDEFAULT,
                            NULL,
                            hMenu,
                            hInstance,
                            NULL
                );

        if (!hwndClient) {
            return(FALSE);
        }

        /* create 3 child windows, one status, one table and one bar
         * Initially, the bar window is hidden and covered by the table.
         */

        /* create a status bar window as
         * a child of the main window.
         */

        /* build a status struct for two labels and an abort button */
        hstatus = StatusAlloc(3);
        StatusAddItem(hstatus, 0, SF_STATIC, SF_LEFT|SF_VAR|SF_SZMIN, IDL_STATLAB, 14, NULL);
        StatusAddItem(hstatus, 1, SF_BUTTON, SF_RIGHT|SF_RAISE, IDM_ABORT, 8,
                LoadRcString(IDS_EXIT));
        StatusAddItem(hstatus, 2, SF_STATIC, SF_LOWER|SF_LEFT|SF_VAR,
                        IDL_NAMES, 60, NULL);

        /* ask the status bar how high it should be for the controls
         * we have chosen, and save this value for re-sizing.
         */
        status_height = StatusHeight(hstatus);

        /* create a window of this height */
        GetClientRect(hwndClient, &rect);
        childrc = rect;
        childrc.bottom = status_height;
        hwndStatus = StatusCreate(hInst, hwndClient, IDC_STATUS, &childrc,
                        hstatus);

        /* layout constants are stated as percentages of the window width */
        bar_width = (rect.right - rect.left) * BAR_WIN_WIDTH / 100;

        /* create the table class covering all the remaining part of
         * the main window
         */
        hwndRCD = CreateWindow(TableClassName,
                        NULL,
                        WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL,
                        0,
                        status_height,
                        (int)(rect.right - rect.left),
                        (int)(rect.bottom - status_height),
                        hwndClient,
                        (HANDLE) IDC_RCDISP1,
                        hInst,
                        NULL);

        /* create a bar window as a child of the main window.
         * this window remains hidden until we switch into MODE_EXPAND
         */
        hwndBar = CreateWindow("BarClass",
                        NULL,
                        WS_CHILD | WS_VISIBLE,
                        0,
                        status_height,
                        bar_width,
                        (int)(rect.bottom - status_height),
                        hwndClient,
                        (HANDLE) IDC_BAR,
                        hInst,
                        NULL);

        /* nMinMax indicates whether we are to be minimised on startup,
         * on command line parameters
         */
        ShowWindow(hwndBar, SW_HIDE);

        if (GetProfileInt(APPNAME, "OutlineSaved", 0))
        {
                WINDOWPLACEMENT wp;

                /* restore the previous expanded size and position */
                wp.length                  = sizeof( WINDOWPLACEMENT );
                wp.flags                   = 0;
                wp.showCmd                 = GetProfileInt( APPNAME, "OutlineShowCmd",
                                                            SW_SHOWNORMAL);
                wp.ptMaxPosition.x         = GetProfileInt( APPNAME, "OutlineMaxX",       0);
                wp.ptMaxPosition.y         = GetProfileInt( APPNAME, "OutlineMaxY",       0);
                wp.rcNormalPosition.left   = (int)GetProfileInt( APPNAME, "OutlineNormLeft",  (UINT)(-1));
                wp.rcNormalPosition.top    = (int)GetProfileInt( APPNAME, "OutlineNormTop",   (UINT)(-1));
                wp.rcNormalPosition.right  = (int)GetProfileInt( APPNAME, "OutlineNormRight", (UINT)(-1));
                wp.rcNormalPosition.bottom = (int)GetProfileInt( APPNAME, "OutlineNormBottom",(UINT)(-1));
                SetWindowPlacement(hwndClient,&wp);
        }
        else ShowWindow(hwndClient, nMinMax);

        UpdateWindow(hwndClient);

        /* initialise busy flag and status line to show we are idle
         * (ie not comparing or scanning)
         */
        SetNotBusy();

        return(TRUE);

} /* InitInstance */

/***************************************************************************
 * Function: windiff_usage
 *
 * Purpose:
 *
 * Complain to command line users about poor syntax,
 * will be replaced by proper help file.
 */
void
windiff_usage(LPSTR msg)
{
        int retval;
        TCHAR szBuf[MAX_PATH];

        if (msg==NULL)
                msg = LoadRcString(IDS_USAGE_STR);

        LoadString(hInst, IDS_WINDIFF_USAGE, szBuf, sizeof(szBuf));
        retval = MessageBox(hwndClient,
                msg,
                szBuf, MB_ICONINFORMATION|MB_OKCANCEL);
        if (retval == IDCANCEL) {
                exit(1);
        }
}

/***************************************************************************
 * Function: ParseArgs
 *
 * Purpose:
 *
 * Parse command line arguments
 *
 * The user can give one or two paths. If only one, we assume the second
 * is '.' for the current directory. If one of the two paths is a directory
 * and the other a file, we compare a file of the same name in the two dirs.
 *
 * The command -s filename causes the outline list to be written to a file
 * and then the program exits. -s{slrd} filename allows selection of which
 * files are written out; by default, we assume -sld for files left and different.
 *
 * -T means tree.  Go deep.
 *
 * The default is Deep, -L overrides and implies shallow, -T overrides -L
 */
void
ParseArgs(int argc, char ** argv)
{
        int i;
        LPSTR chp;
        PTHREADARGS ta;
        DWORD threadid;

        /* thread args can't be on the stack since the stack will change
         * before the thread completes execution
         */
        ta = (PTHREADARGS) gmem_get(hHeap, sizeof(THREADARGS));
        ta->first = NULL;
        ta->second = NULL;
        ta->savelist = NULL;
        ta->saveopts = 0;
        ta->fDeep = FALSE;  /* No -T option seen yet */

        for (i = 1; i < argc; i++) {

                /* is this an option ? */
                if ((argv[i][0] == '-') || (argv[i][0] == '/')) {
                        switch(argv[i][1]) {

                        case 's':
                        case 'S':
                                /* read letters for the save option: s,l,r,d */
                                for(chp = &argv[i][2]; *chp != '\0'; chp++) {
                                        switch(*chp) {
                                        case 's':
                                        case 'S':
                                                ta->saveopts |= INCLUDE_SAME;
                                                break;
                                        case 'l':
                                        case 'L':
                                                ta->saveopts |= INCLUDE_LEFTONLY;
                                                break;
                                        case 'r':
                                        case 'R':
                                                ta->saveopts |= INCLUDE_RIGHTONLY;
                                                break;
                                        case 'd':
                                        case 'D':
                                                ta->saveopts |= INCLUDE_DIFFER;
                                                break;
                                        default:
                                                windiff_usage(NULL);
                                                return;
                                        }
                                }

                                if (ta->saveopts == 0) {
                                        /* default to left and differ */
                                        ta->saveopts = (INCLUDE_LEFTONLY) | (INCLUDE_DIFFER);
                                }
                                ta->savelist = argv[++i];
                                break;
                        case 't':
                        case 'T':
                                ta->fDeep = TRUE;
                                break;
                        default:
                                windiff_usage(NULL);
                                return;
                        }
                } else {
                        if (ta->first == NULL) {
                                ta->first = argv[i];
                        } else {
                                ta->second = argv[i];
                        }
                }
        }

        /* set the correct depth */
        if (ta->fDeep)
                ;                       /* explicitly set -- leave it alone */
        else ta->fDeep = TRUE;          /* global default */

        /* any paths to scan ? */
        if (ta->first == NULL) {
                return;
        }

        if (ta->second == NULL) {
                ta->second = ".";
        }

        SetBusy();

        /* minimise the window if -s flag given */
        if (ta->savelist != NULL) {
                ShowWindow(hwndClient, SW_MINIMIZE);
        }

        /* make an empty view */
        current_view = view_new(hwndRCD);
        DisplayMode = MODE_OUTLINE;

        ta->view = current_view;

        /* attempt to create a worker thread */

        ghThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)wd_initial, (LPVOID) ta,
                        0, &threadid);
        if (ghThread == NULL)
        {
                wd_initial( (LPVOID) ta);
        }
} /* ParseArgs */

/***************************************************************************
 * Function: CreateTools
 *
 * Purpose:
 *
 * Create any pens/brushes, and read defaults
 * from the profile file for menu settings etc.
 */
void
CreateTools(void)
{

        /* standard message that table class sends us for
         * notifications and queries.
         */
        table_msgcode = RegisterWindowMessage(TableMessage);

        line_numbers = GetProfileInt(APPNAME, "LineNumbers", line_numbers);
        outline_include = GetProfileInt(APPNAME, "FileInclude", outline_include);
        ignore_blanks = GetProfileInt(APPNAME, "Blanks", ignore_blanks);
        picture_mode = GetProfileInt(APPNAME, "Picture", picture_mode);

        GetProfileString(APPNAME, "Editor", editor_cmdline, (LPTSTR)editor_cmdline,
                        sizeof(editor_cmdline));
        InitializeCriticalSection(&CSWindiff);

}

/***************************************************************************
 * Function: DeleteTools
 *
 * Purpose:
 *
 * Delete any pens or brushes that were created in CreateTools
 */
void
DeleteTools(void)
{

        DeleteCriticalSection(&CSWindiff);

}

/***************************************************************************
 * Function:
 *
 * Purpose:
 *
 * Check whether we have had an abort request (IDM_ABORT), and
 * return TRUE if abort requested, otherwise FALSE
 */
BOOL
Poll(void)
{
    return(bAbort);
}

/***************************************************************************
 * Function: DoResize
 *
 * Purpose:
 *
 * Position child windows on a resize of the main window
 */
void
DoResize(HWND hWnd)
{
        RECT rc;
        int bar_width;

        GetClientRect(hWnd, &rc);
        MoveWindow(hwndStatus, 0, 0, rc.right - rc.left, status_height, TRUE);

        bar_width = (rc.right - rc.left) * BAR_WIN_WIDTH / 100;

        /* bar window is hidden unless in expand mode */
        if ((DisplayMode == MODE_EXPAND) && (picture_mode)) {
                ShowWindow(hwndBar, SW_SHOW);
                MoveWindow(hwndBar, 0, status_height,
                        bar_width, rc.bottom - status_height, TRUE);
                MoveWindow(hwndRCD, bar_width, status_height,
                        (rc.right - rc.left) - bar_width,
                        rc.bottom - status_height, TRUE);
        } else {
                MoveWindow(hwndRCD, 0, status_height, (rc.right - rc.left),
                        rc.bottom - status_height, TRUE);
                ShowWindow(hwndBar, SW_HIDE);
        }

}
/***************************************************************************
 * Function: AboutBox
 *
 * Purpose:
 *
 * Standard processing for About box.
 */
int APIENTRY
AboutBox(HWND hDlg, unsigned message, UINT wParam, LONG lParam)
{
        char ch[256];

        switch (message) {

        case WM_INITDIALOG:
                wsprintf((LPTSTR)ch, "%d.%02d", Version, SubVersion);
                SetDlgItemText(hDlg, IDD_VERSION, ch);

return(TRUE);

        case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDOK:
                        EndDialog(hDlg, 0);
                        return(TRUE);
                }
                break;
        }
        return(FALSE);
}

/* -- menu commands ---------------------------------------------------*/

/***************************************************************************
 * Function: DoPrint
 *
 * Purpose:
 *
 * Print the current view
 */
void
DoPrint(void)
{
        Title head, foot;
        PrintContext context;
        TCHAR szPage[50];

        /* print context contains the header and footer. Use the
         * default margins and printer selection
         */

        /* we set the table id to be TABID_PRINTER. When the table calls
         * back to get text and properties, we use this to indicate
         * that the table refered to is the 'current_view', but in print
         * mode, and thus we will use different colours/fonts.
         */
        context.head = &head;
        context.foot = &foot;
        context.margin = NULL;
        context.pd = NULL;
        context.id = TABID_PRINTER;

        /* header is filenames or just WinDiff if no names known*/
        if (strlen(AppTitle) > 0) {
                head.ptext = AppTitle;
        } else {
                head.ptext = "WinDiff";
        }

        /* header is centred, footer is right-aligned and
         * consists of the page number
         */
        head.props.valid = P_ALIGN;
        head.props.alignment = P_CENTRE;
        lstrcpy(szPage,LoadRcString(IDS_PAGE));
        foot.ptext = (LPSTR)szPage;
        foot.props.valid = P_ALIGN;
        foot.props.alignment = P_RIGHT;

        SendMessage(hwndRCD, TM_PRINT, 0, (DWORD) (LPSTR) &context);
}

/***************************************************************************
 * Function: FindNextChange
 *
 * Purpose:
 *
 * Find the next line in the current view that is
 * not STATE_SAME. Start from the current selection, if valid, or
 * from the top of the window if no selection.
 *
 */
BOOL
FindNextChange(void)
{
        long row;

        /* start from the selection or top of the window if no selection */
        if (selection >= 0) {
                row = selection;
        } else {
                row = (int) SendMessage(hwndRCD, TM_TOPROW, FALSE, 0);
        }

        /* find the next 'interesting' line */
        row = view_findchange(current_view, row, TRUE);
        if (row >= 0) {
                SetSelection(row);
                return(TRUE);
        } else {
                windiff_UI(TRUE);
                MessageBox(hwndClient, LoadRcString(IDS_NO_MORE_CHANGES), "Windiff",
                        MB_ICONINFORMATION|MB_OK);
                windiff_UI(FALSE);

                return(FALSE);
        }
}

/***************************************************************************
 * Function: FindPrevChange
 *
 * Purpose:
 *
 * Find the previous line in the current view that is not STATE_SAME
 */
BOOL
FindPrevChange(void)
{
        long row;

        /* start from the selection or top of window if no selection */
        if (selection >= 0) {
                row = selection;
        } else {
                row = (int) SendMessage(hwndRCD, TM_TOPROW, FALSE, 0);
        }

        /* find the previous 'interesting' line */
        row = view_findchange(current_view, row, FALSE);
        if (row >= 0) {
                SetSelection(row);
                return(TRUE);
        } else {
                windiff_UI(TRUE);
                MessageBox(hwndClient, LoadRcString(IDS_NO_PREV_CHANGES), "Windiff",
                        MB_ICONINFORMATION|MB_OK);
                windiff_UI(FALSE);

                return(FALSE);
        }

}
/***************************************************************************
 * Function: WriteProfileInt
 *
 */

BOOL WriteProfileInt(LPSTR AppName, LPSTR Key, int Int)
{       char Str[40];

        wsprintf((LPTSTR)Str, "%d", Int);
        return WriteProfileString(AppName, Key, Str);

} /* WriteProfileInt */

/***************************************************************************
 * Function: ToExpand
 *
 * Purpose:
 *
 * Switch to expand view of the selected line
 */
BOOL
ToExpand(HWND hwnd)
{
        if (selection < 0) {
                return(FALSE);
        }

        if (!view_isexpanded(current_view)) {
                /* save the current outline size and position */
                WINDOWPLACEMENT wp;
                if (GetWindowPlacement(hwndClient,&wp)) {
                        WriteProfileInt(APPNAME, "OutlineShowCmd", wp.showCmd);
                        WriteProfileInt(APPNAME, "OutlineMaxX", wp.ptMaxPosition.x);
                        WriteProfileInt(APPNAME, "OutlineMaxY", wp.ptMaxPosition.y);
                        WriteProfileInt(APPNAME, "OutlineNormLeft", wp.rcNormalPosition.left);
                        WriteProfileInt(APPNAME, "OutlineNormTop", wp.rcNormalPosition.top);
                        WriteProfileInt(APPNAME, "OutlineNormRight", wp.rcNormalPosition.right);
                        WriteProfileInt(APPNAME, "OutlineNormBottom", wp.rcNormalPosition.bottom);
                        WriteProfileInt(APPNAME, "OutlineSaved", 1);
                }

                /* restore the previous expanded size and position, if any */
                if (GetProfileInt(APPNAME, "ExpandedSaved", 0)) {
                        wp.flags                   = 0;
                        wp.showCmd
                                = GetProfileInt( APPNAME, "ExpandShowCmd"
                                               , SW_SHOWMAXIMIZED);
                        wp.ptMaxPosition.x
                                = GetProfileInt( APPNAME, "ExpandMaxX", 0);
                        wp.ptMaxPosition.y
                                = GetProfileInt( APPNAME, "ExpandMaxY", 0);
                        wp.rcNormalPosition.left
                                = GetProfileInt( APPNAME, "ExpandNormLeft"
                                               , wp.rcNormalPosition.left);
                        wp.rcNormalPosition.top
                                = GetProfileInt( APPNAME, "ExpandNormTop"
                                               , wp.rcNormalPosition.top);
                        wp.rcNormalPosition.right
                                = GetProfileInt( APPNAME, "ExpandNormRight"
                                               , wp.rcNormalPosition.right);
                        wp.rcNormalPosition.bottom
                                = GetProfileInt( APPNAME, "ExpandNormBottom"
                                               , wp.rcNormalPosition.bottom);
                        SetWindowPlacement(hwndClient,&wp);
                }
                else ShowWindow(hwndClient, SW_SHOWMAXIMIZED);
        }

        /*change the view mapping to expand mode */
        if (view_expand(current_view, selection)) {

                /* ok - we now have an expanded view - change status
                 * to show this
                 */

                DisplayMode = MODE_EXPAND;

                /* resize to show the graphic bar picture */
                DoResize(hwndClient);

                /* change button,status text-if we are not still busy*/
                if (!fBusy) {
                        TCHAR szBuf[10];
                        lstrcpy(szBuf,LoadRcString(IDS_OUTLINE));
                        /* the status field when we are expanded shows the
                         * tag field (normally the file name) for the
                         * item we are expanding
                         */
                        SetStatus(view_getcurrenttag(current_view) );
                        SetButtonText(szBuf);
                }

                return(TRUE);
        }
        return(FALSE);
} /* ToExpand */

/***************************************************************************
 * Function: ToOutline
 *
 * Purpose:
 *
 * Switch back to outline view - showing just the list of file names.
 */
void
ToOutline(HWND hwnd)
{
        if (view_isexpanded(current_view)) {
                /* save the current expanded size and position */
                WINDOWPLACEMENT wp;
                if (GetWindowPlacement(hwndClient,&wp)) {
                        WriteProfileInt(APPNAME, "ExpandShowCmd", wp.showCmd);
                        WriteProfileInt(APPNAME, "ExpandMaxX", wp.ptMaxPosition.x);
                        WriteProfileInt(APPNAME, "ExpandMaxY", wp.ptMaxPosition.y);
                        WriteProfileInt(APPNAME, "ExpandNormLeft", wp.rcNormalPosition.left);
                        WriteProfileInt(APPNAME, "ExpandNormTop", wp.rcNormalPosition.top);
                        WriteProfileInt(APPNAME, "ExpandNormRight", wp.rcNormalPosition.right);
                        WriteProfileInt(APPNAME, "ExpandNormBottom", wp.rcNormalPosition.bottom);
                        WriteProfileInt(APPNAME, "ExpandedSaved", 1);
                }

                /* restore the previous expanded size and position, if any */
                if (GetProfileInt(APPNAME, "OutlineSaved", 0))  {
                        wp.flags = 0;
                        wp.showCmd
                                = GetProfileInt( APPNAME, "OutlineShowCmd"
                                               , SW_SHOWNORMAL);
                        wp.ptMaxPosition.x
                                = GetProfileInt( APPNAME, "OutlineMaxX", 0);
                        wp.ptMaxPosition.y
                                = GetProfileInt( APPNAME, "OutlineMaxY", 0);
                        wp.rcNormalPosition.left
                                = GetProfileInt( APPNAME, "OutlineNormLeft"
                                               , wp.rcNormalPosition.left);
                        wp.rcNormalPosition.top
                                = GetProfileInt( APPNAME, "OutlineNormTop"
                                               , wp.rcNormalPosition.top);
                        wp.rcNormalPosition.right
                                = GetProfileInt( APPNAME, "OutlineNormRight"
                                               , wp.rcNormalPosition.right);
                        wp.rcNormalPosition.bottom
                                = GetProfileInt( APPNAME, "OutlineNormBottom"
                                               , wp.rcNormalPosition.bottom);
                        SetWindowPlacement(hwndClient,&wp);
                }
                ShowWindow(hwndClient, SW_SHOWNORMAL);
        }

        DisplayMode = MODE_OUTLINE;

        /* switch mapping back to outline view */
        view_outline(current_view);

        /* hide bar window and resize to cover */
        DoResize(hwndClient);

        /* change label on button */
        if (!fBusy) {
                TCHAR szBuf[8];
                lstrcpy(szBuf,LoadRcString(IDS_EXPAND));
                SetButtonText(szBuf);
                SetStatus(NULL);
        }
} /* ToOutline */

/***************************************************************************
 * Function: ToMoved
 *
 * Purpose:
 *
 * If the user clicks on a MOVED line in expand mode, we jump to the
 * other line. We return TRUE if this was possible,  or FALSE otherwise.
 */
BOOL
ToMoved(HWND hwnd)
{
        BOOL bIsLeft;
        int linenr, state;
        long i, total;

        if (DisplayMode != MODE_EXPAND) {
                return(FALSE);
        }
        if (selection < 0) {
                return(FALSE);
        }

        state = view_getstate(current_view, selection);
        if (state == STATE_MOVEDLEFT) {
                bIsLeft = TRUE;
                /* get the linenr of the other copy */
                linenr = abs(view_getlinenr_right(current_view, selection));
        } else if (state == STATE_MOVEDRIGHT) {
                bIsLeft = FALSE;
                /* get the linenr of the other copy */
                linenr = abs(view_getlinenr_left(current_view, selection));
        } else {
                /* not a moved line - so we can't find another copy */
                return(FALSE);
        }

        /* search the view for this line nr */
        total = view_getrowcount(current_view);
        for (i = 0; i < total; i++) {
                if (bIsLeft) {
                        if (linenr == view_getlinenr_right(current_view, i)) {
                                /* found it */
                                SetSelection(i);
                                return(TRUE);
                        }
                } else {
                        if (linenr == view_getlinenr_left(current_view, i)) {
                                SetSelection(i);
                                return(TRUE);
                        }
                }
        }
        return(FALSE);
}

/***************************************************************************
 * Function: do_editfile
 *
 * Purpose:
 *
 * Launch an editor on the current file (the file we are expanding, or
 * in outline mode the selected row. Option allows selection of the
 * left file, the right file or the composite view of this item.
 * pe points to a packet of parameters that must be freed before returning.
 * The return value is meaningless (just to conform to CreateThread).
 */
LONG
do_editfile(PEDITARGS pe)
{
        VIEW view = pe->view;
        int option = pe->option;
        int selection = pe->selection;

        COMPITEM item;
        LPSTR fname;
        char cmdline[256];
        int currentline;
        char * pOut = cmdline;
        char * pIn = editor_cmdline;

        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        item = view_getitem(view, selection);
        if (item == NULL) {
                return -1;
        }

        fname = compitem_getfilename(item, option);

        if ( 0 == fname )
        {
            windiff_UI(TRUE);
            MessageBox(hwndClient, LoadRcString(IDS_FILE_DOESNT_EXIST),
                       "Windiff", MB_ICONSTOP|MB_OK);
            windiff_UI(FALSE);
            goto error;
        }

       switch ( option )
        {
        case CI_LEFT:
            currentline = view_getlinenr_left( view,
                                               selection > 0 ? selection : 1);
            break;

        case CI_RIGHT:
            currentline = view_getlinenr_right( view,
                                                selection > 0 ? selection : 1);
            break;

        default:
            currentline = 1;
            break;
        }

        while( *pIn )
        {
            switch( *pIn )
            {
            case '%':
                pIn++;
                switch ( *pIn )
                {
                case 'p':
                    lstrcpy( (LPTSTR)pOut, fname );
                    while ( *pOut )
                        pOut++;
                    break;

                case 'l':
                    _ltoa( currentline, pOut, 10 );
                    while ( *pOut )
                        pOut++;
                    break;

                default:
                    if (IsDBCSLeadByte(*pIn) && *(pIn+1)) {
                        *pOut++ = *pIn++;
                    }
                    *pOut++ = *pIn;
                    break;
                }
                pIn++;
                break;

            default:
                if (IsDBCSLeadByte(*pIn) && *(pIn+1)) {
                    *pOut++ = *pIn++;
                }
                *pOut++ = *pIn++;
                break;
            }
        }

        /* Launch the process and waits for it to complete */

        si.cb = sizeof(STARTUPINFO);
        si.lpReserved = NULL;
        si.lpReserved2 = NULL;
        si.cbReserved2 = 0;
        si.lpTitle = (LPSTR)cmdline;
        si.lpDesktop = (LPTSTR)NULL;
        si.dwFlags = STARTF_FORCEONFEEDBACK;

        if (!CreateProcess(NULL,
                        cmdline,
                        NULL,
                        NULL,
                        FALSE,
                        NORMAL_PRIORITY_CLASS,
                        NULL,
                        (LPTSTR)NULL,
                        &si,
                        &pi)) {
                windiff_UI(TRUE);
                MessageBox(hwndClient, LoadRcString(IDS_FAILED_TO_LAUNCH_EDT),
                        "Windiff", MB_ICONSTOP|MB_OK);
                windiff_UI(FALSE);
                goto error;
        }

        /* wait for completion. */
        WaitForSingleObject(pi.hProcess, INFINITE);

        /* close process and thread handles */
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);

        /* finished with the filename. deletes it if it was a temp. */
        compitem_freefilename(item, option, fname);

        /*
         * refresh cached view always .  A common trick is to edit the
         * composite file and then save it as a new left or right file.
         * Equally the user can edit the left and save as a new right.
         */

        /* We want to force both files to be re-read, but it's not a terribly
         * good idea to throw the lines away on this thread.  Someone might
         * be reading them on another thread!
         */
        /* file_discardlines(compitem_getleftfile(item)) */
        /* file_discardlines(compitem_getrightfile(item)) */

        /* force the compare to be re-done */
        PostMessage(hwndClient, WM_COMMAND, IDM_UPDATE, (LONG)item);
error:
        gmem_free(hHeap, (LPSTR) pe, sizeof(EDITARGS));

        return 0;

} /* do_editfile */

/***************************************************************************
 * Function: do_editthread
 *
 * Purpose:
 *
 * Launch an editor on a separate thread.  It will actually get a separate
 * process, but we want our own thread in this process.  This thread will
 * wait until it's finished and then order up a refresh of the UI.
 * Need to give it its parameters as a gmem allocated packet because
 * it IS on a separate thread.
 */
void do_editthread(VIEW view, int option)
{
        PEDITARGS pe;
        HANDLE thread;
        DWORD threadid;

        pe = (PEDITARGS) gmem_get(hHeap, sizeof(EDITARGS));
        pe->view = view;
        pe->option = option;
        pe->selection = selection;

        thread = CreateThread( NULL
                             , 0
                             , (LPTHREAD_START_ROUTINE)do_editfile
                             , (LPVOID) pe
                             , 0
                             , &threadid
                             );
        if (thread == NULL)
        {
                /* The createthread failed, do without the extra thread - just
                 * call the function synchronously
                 */
                 do_editfile(pe);
        }
        else CloseHandle(thread);
} /* do_editthread */

/* status bar and busy flags --------------------------------------------*/

/***************************************************************************
 * Function: SetButtonText
 *
 * Purpose:
 *
 * Set the Text on the statusbar button to reflect the current state
 */
void
SetButtonText(LPSTR cmd)
{
        SendMessage(hwndStatus, SM_SETTEXT, IDM_ABORT, (DWORD) cmd);
}

/***************************************************************************
 * Function: SetStatus
 *
 * Purpose:
 *
 * Set the status field (left-hand part) of the status bar.
 */
void
SetStatus(LPSTR cmd)
{
        SendMessage(hwndStatus, SM_SETTEXT, IDL_STATLAB, (DWORD) cmd);
}

/***************************************************************************
 * Function: SetNames
 *
 * Purpose:
 *
 * Set the names field - the central box in the status bar
 */
void
SetNames(LPSTR names)
{
        SendMessage(hwndStatus, SM_SETTEXT, IDL_NAMES, (DWORD) names);
        if (names == NULL) {
                AppTitle[0] = '\0';
        } else {
                strncpy(AppTitle, names, sizeof(AppTitle));
        }
}

/***************************************************************************
 * Function: SetBusy
 *
 * Purpose:
 *
 * If we are not already busy, set the busy flag.
 *
 * Enter critical section first.
 */
BOOL
SetBusy(void)
{
        HMENU hmenu;

        WDEnter();

        if (fBusy) {
                WDLeave();
                return(FALSE);
        }

        fBusy = TRUE;

        SetStatus(LoadRcString(IDS_COMPARING));
        /* status also on window text, so that you can see even from
         * the icon when the scan has finished
         */
        SetWindowText(hwndClient, LoadRcString(IDS_SCANNING));

        /* disable appropriate parts of menu */
        hmenu = GetMenu(hwndClient);
        EnableMenuItem(hmenu, IDM_FILE,MF_DISABLED|MF_GRAYED|MF_BYCOMMAND);
        EnableMenuItem(hmenu, IDM_DIR,MF_DISABLED|MF_GRAYED|MF_BYCOMMAND);
        EnableMenuItem(hmenu, IDM_PRINT,MF_DISABLED|MF_GRAYED|MF_BYCOMMAND);

        /* enable abort only when busy */
        EnableMenuItem(hmenu, IDM_ABORT,MF_ENABLED|MF_BYCOMMAND);
        SetButtonText(LoadRcString(IDS_ABORT));  /* leave DisplayMode unchanged */

        WDLeave();
        return(TRUE);
} /* SetBusy */
/***************************************************************************
 * Function: SetNotBusy
 *
 * Purpose:
 *
 * This function can be called from the worker thread.
 * Thus we must not cause any SendMessage calls to windows
 * owned by the main thread while holding the CritSec or we
 * could cause deadlock.
 *
 * The critsec is only needed to protect the fBusy flag - so
 * clear the busy flag last, and only get the crit sec as needed.
 */
void
SetNotBusy(void)
{
        HMENU hmenu;

        /* reset button and status bar (clearing out busy flags) */
        if (current_view == NULL) {
                SetButtonText(LoadRcString(IDS_EXIT));
                SetStatus(NULL);
                DisplayMode = MODE_NULL;
        } else if (view_isexpanded(current_view)) {
                TCHAR szBuf[10];
                lstrcpy(szBuf,LoadRcString(IDS_OUTLINE));
                SetButtonText(szBuf);
                SetStatus(view_getcurrenttag(current_view) );
                DisplayMode = MODE_EXPAND;
        } else {
                TCHAR szBuf[8];
                lstrcpy(szBuf,LoadRcString(IDS_EXPAND));
                SetButtonText(szBuf);
                SetStatus(NULL);
                DisplayMode = MODE_OUTLINE;
        }

        SetWindowText(hwndClient, "WinDiff");

        /* re-enable appropriate parts of menu */
        hmenu = GetMenu(hwndClient);
        EnableMenuItem(hmenu, IDM_FILE,MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(hmenu, IDM_DIR,MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(hmenu, IDM_PRINT,MF_ENABLED|MF_BYCOMMAND);

        /* disable abort now no longer busy */
        EnableMenuItem(hmenu, IDM_ABORT,MF_DISABLED|MF_GRAYED|MF_BYCOMMAND);

        /* clear the busy flag, protected by critical section */
        WDEnter();

        fBusy = FALSE;
        bAbort = FALSE;

        if (ghThread!=NULL){
            CloseHandle(ghThread);
            ghThread = NULL;
        }
        WDLeave();
} /* SetNotBusy */

/***************************************************************************
 * Function: IsBusy
 *
 * Purpose:
 *
 * Checks whether or not crit sec is open
 */
BOOL
IsBusy()
{
        BOOL bOK;

        WDEnter();
        bOK = fBusy;
        WDLeave();
        return(bOK);
} /* IsBusy */

/***************************************************************************
 * Function: BusyError
 *
 * Purpose:
 *
 * Puts up message box that system is busy.
 */
void
BusyError(void)
{
        windiff_UI(TRUE);
        MessageBox(hwndClient,
                LoadRcString(IDS_PLEASE_WAIT),
                "WinDiff", MB_OK|MB_ICONSTOP);
        windiff_UI(FALSE);
} /* BusyError */

/* --- colour scheme --------------------------------------------------- */

/***************************************************************************
 * Function: StateToColour
 *
 * Purpose:
 *
 * Map the state given into a foreground and a background colour
 * for states that are highlighted. Return P_FCOLOUR if the foreground
 * colour (put in *foreground) is to be used, return P_FCOLOUR|P_BCOLOUR if
 * both *foreground and *background are to be used, or 0 if the default
 * colours are to be used.
 */
UINT
StateToColour(int state, int col, DWORD FAR * foreground, DWORD FAR * background)
{

        switch (state) {

        case STATE_DIFFER:
                /* files that differ are picked out in a foreground highlight,
                 * with the default background
                 */
                *foreground = rgb_outlinehi;
                return(P_FCOLOUR);

        case STATE_LEFTONLY:
                /* lines only in the left file */
                *foreground = rgb_leftfore;
                *background = rgb_leftback;
                return(P_FCOLOUR|P_BCOLOUR);

        case STATE_RIGHTONLY:
                /* lines only in the right file */
                *foreground = rgb_rightfore;
                *background = rgb_rightback;
                return(P_FCOLOUR|P_BCOLOUR);

        case STATE_MOVEDLEFT:
                /* displaced lines in both files - left file version */
                *foreground = rgb_mleftfore;
                *background = rgb_mleftback;
                return(P_FCOLOUR|P_BCOLOUR);

        case STATE_MOVEDRIGHT:
                /* displaced lines in both files - right file version */
                *foreground = rgb_mrightfore;
                *background = rgb_mrightback;
                return(P_FCOLOUR|P_BCOLOUR);

        default:

                /* no highlighting - default colours */
                return(0);
        }

}

/* table window communication routines ---------------------------------*/

/***************************************************************************
 * Function: SetSelection
 *
 * Purpose:
 *
 * Set a given row as the selected row in the table window
 */
void
SetSelection(long rownr)
{
        TableSelection select;

        select.startrow = rownr;
        select.startcell = 0;
        select.nrows = 1;
        select.ncells = 1;
        SendMessage(hwndRCD, TM_SELECT, 0, (long) (LPSTR)&select);
}

/***************************************************************************
 * Function: do_gethdr
 *
 * Purpose:
 *
 * Handle table class call back to get nr of rows and columns,
 * and properties for the whole table.
 * The 'table id' is either TABID_PRINTER - meaning we are
 * printing the current_view, or it is the view to
 * use for row/column nr information
 */
long
do_gethdr(HWND hwnd, lpTableHdr phdr)
{
        VIEW view;
        BOOL bIsPrinter = FALSE;

        if (phdr->id == TABID_PRINTER) {
                view = current_view;
                bIsPrinter = TRUE;
        } else {
                view = (VIEW) phdr->id;
        }
        if (view == NULL) {
                return(FALSE);
        }

        phdr->nrows = view_getrowcount(view);

        /*  three columns: line nr, tag and rest of line */

        /*
         * if IDM_NONRS (no line numbers) is selected, suppress the
         * line-nr column entirely to save screen space
         */
        if (line_numbers == IDM_NONRS) {
                phdr->ncols = 2;
                phdr->fixedcols = 0;
        } else {
                phdr->ncols = 3;
                phdr->fixedcols = 1;
        }

        phdr->fixedrows = 0;
        phdr->fixedselectable = FALSE;
        phdr->hseparator = TRUE;
        phdr->vseparator = TRUE;

        phdr->selectmode = TM_ROW | TM_SINGLE;
        /*
         * find if we are in expand mode - ask for the item we are expanding.
         */
        if (view_isexpanded(view) == TRUE) {

                /* use focus rect as selection mode in expand mode
                 * so as not to interfere with background colours.
                 */
                phdr->selectmode |= TM_FOCUS;
        } else {
                /* use default solid inversion when possible as it is clearer.*/
                phdr->selectmode |= TM_SOLID;
        }

        /* please send TQ_SCROLL notifications when the table is scrolled */
        phdr->sendscroll = TRUE;
        phdr->props.valid = 0;

        return TRUE;

}

/***************************************************************************
 * Function: do_getprops
 *
 * Purpose:
 *
 * Respond to table callback asking for the size and properties
 * of each column. Table id is either TABID_PRINTER (meaning the
 * current_view, for printing) or it is the view to be used.
 */
long
do_getprops(HWND hwnd, lpColPropsList propslist)
{
        int i, cell;
        BOOL bIsPrinter = FALSE;
        VIEW view;

        if (propslist->id == TABID_PRINTER) {
                view = current_view;
                bIsPrinter = TRUE;
        } else {
                view = (VIEW) propslist->id;
        }
        if (view == NULL) {
                return(FALSE);
        }

        /* The table inteface is slightly confused here. we are not
         * guaranteed which columns we are being asked about, so instead
         * of just setting each column cols[0], cols[1] etc, we need
         * to loop through, looking at each column in the table and
         * seeing which it is.
         */
        for (i = 0; i < propslist->ncols; i++) {
                cell = i + propslist->startcol;
                propslist->plist[i].props.valid = 0;

                /* for all column widths, add on 1 for the NULL char. */

                /*
                 * skip the line nr column if IDM_NONRS
                 */
                if (line_numbers == IDM_NONRS) {
                        cell++;
                }

                if (cell == 0) {
                        /* properties for line nr column */

                        propslist->plist[i].nchars = view_getwidth(view, 0)+1;
                        propslist->plist[i].props.valid |= P_ALIGN;
                        propslist->plist[i].props.alignment = P_CENTRE;
                } else if (cell == 1) {

                        /* properties for tag field */
                        propslist->plist[i].nchars = view_getwidth(view, 1)+1;
                        propslist->plist[i].props.valid |= P_ALIGN;
                        propslist->plist[i].props.alignment = P_LEFT;
                } else {
                        /* properties for main text column -
                         * use a fixed font unless printing (if
                         * printing, best to use the default font, because
                         * of resolution differences.
                         * add on 8 chars to the width to ensure that
                         * the width of lines beginning with tabs
                         * works out ok
                         */
                        propslist->plist[i].nchars = view_getwidth(view, 2)+1;
                        propslist->plist[i].props.valid |= P_ALIGN;
                        propslist->plist[i].props.alignment = P_LEFT;
                        if (!bIsPrinter) {
                                propslist->plist[i].props.valid |= P_FONT;
                                propslist->plist[i].props.hFont =
                                        GetStockObject(SYSTEM_FIXED_FONT);
                        }
                }
        }
        return (TRUE);
}

/***************************************************************************
 * Function: do_getdata
 *
 * Purpose:
 *
 * Respond to a table callback asking for the contents of individual cells.
 * table id is either TABID_PRINTER, or it is a pointer to the view
 * to use for data. If going to the printer, don't set the
 * colours (stick to black and white).
 */
long
do_getdata(HWND hwnd, lpCellDataList cdlist)
{
        int start, endcell, col, i;
        lpCellData cd;
        VIEW view;
        LPSTR textp;
        BOOL bIsPrinter = FALSE;

        if (cdlist->id == TABID_PRINTER) {
                view = current_view;
                bIsPrinter = TRUE;
        } else {
                view = (VIEW) cdlist->id;
        }

        start = cdlist->startcell;
        endcell = cdlist->ncells + start;
        if (cdlist->row >= view_getrowcount(view)) {
                return(FALSE);
        }
        for (i = start; i < endcell; i++) {
                cd = &cdlist->plist[i - start];

                /* skip the line number column if IDM_NONRS */
                if (line_numbers == IDM_NONRS) {
                        col = i+1;
                } else {
                        col = i;
                }

                /* set colour of text to mark out
                 * lines that are changed, if not printer - for the
                 * printer everything should stay in the default colours
                 */

                if (!bIsPrinter) {

                        /* convert the state of the requested row into a
                         * colour scheme. returns P_FCOLOUR and/or
                         * P_BCOLOUR if it sets either of the colours
                         */
                        cd->props.valid |=
                            StateToColour(view_getstate(view, cdlist->row), col,
                                        &cd->props.forecolour,
                                        &cd->props.backcolour);
                }

                textp = view_gettext(view, cdlist->row, col);
                if (cd->nchars != 0) {
                        if (textp == NULL) {
                                cd->ptext[0] = '\0';
                        } else {
                                strncpy(cd->ptext, textp, cd->nchars -1);
                                cd->ptext[cd->nchars - 1] = '\0';
                        }
                }

        }
        return(TRUE);
}

/***************************************************************************
 * Function: SvrClose
 *
 * Purpose:
 *
 * Table window has finished with this view. It can be deleted.
 */
void
SvrClose(void)
{
        view_delete(current_view);
        current_view = NULL;

        /* hide picture - only visible when we are in MODE_EXPAND */
        DisplayMode = MODE_NULL;
        DoResize(hwndClient);

        /* if we already busy when closing this view (ie
         * we are in the process of starting a new scan,
         * then leave the status bar alone, otherwise
         * we should clean up the state of the status bar
         */
        if (!fBusy) {
                SetButtonText(LoadRcString(IDS_EXIT));
                SetNames(NULL);
                SetStatus(NULL);

        }

} /* SvrClose */

/***************************************************************************
 * Function: TableServer
 *
 * Purpose:
 *
 * Handle callbacks and notifications from the table class
 */
long
TableServer(HWND hwnd, UINT cmd, long lParam)
{
        lpTableHdr phdr;
        lpColPropsList proplist;
        lpCellDataList cdlist;
        lpTableSelection pselect;

        switch(cmd) {
        case TQ_GETSIZE:
                /* get the nr of rows and cols in this table */
                phdr = (lpTableHdr) lParam;
                return(do_gethdr(hwnd, phdr));

        case TQ_GETCOLPROPS:
                /* get the size and properties of each column */
                proplist = (lpColPropsList) lParam;
                return (do_getprops(hwnd, proplist));

        case TQ_GETDATA:
                /* get the contents of individual cells */
                cdlist = (lpCellDataList) lParam;
                return (do_getdata(hwnd, cdlist));

        case TQ_SELECT:
                /* selection has changed */
        case TQ_ENTER:
                /* user has double-clicked or pressed enter */

                pselect = (lpTableSelection) lParam;

                /* store location for use in later search (IDM_FCHANGE) */
                if (pselect->nrows < 1) {
                        selection = -1;
                } else {
                        selection = (int) pselect->startrow;
                        if (cmd == TQ_ENTER) {
                                /* try to expand this row */
                                if (!ToExpand(hwnd)) {
                                        /* expand failed - maybe this
                                         * is a moved line- show the other
                                         * copy
                                         */
                                        ToMoved(hwnd);
                                }

                        }
                }
                break;

        case TQ_CLOSE:
                /* close this table - table class no longer needs data*/
                SvrClose();
                break;

        case TQ_SCROLL:
                /* notification that the rows visible in the window
                 * have changed -change the current position lines in
                 * the graphic bar view (the sections picture)
                 */
                if (picture_mode) {
                        BarDrawPosition(hwndBar, NULL, TRUE);
                }
                break;

        default:
                return(FALSE);
        }
        return(TRUE);
}

/***************************************************************************
 * Function: wd_initial
 *
 * Purpose:
 *
 * Called on worker thread (not UI thread) to handle the work
 * requested on the command line.
 * arg is a pointer to a THREADARGS block allocated from gmem_get(hHeap). This
 * needs to be freed before exiting.
 */
DWORD
wd_initial(LPVOID arg)
{
        PTHREADARGS pta = (PTHREADARGS) arg;
        COMPLIST cl;

        /* build a complist from these args,
         * and register with the view we have made
         */
        cl = complist_args(pta->first, pta->second, pta->view, pta->fDeep);

        if (cl == NULL) {
                view_close(pta->view);
                gmem_free(hHeap, (LPSTR) pta, sizeof(THREADARGS));
                SetNotBusy();
                return 0;
        }

        /* if savelist was selected, write out the list and exit */
        if(pta->savelist != NULL) {
                complist_savelist(cl, pta->savelist, pta->saveopts);
                gmem_free(hHeap, (LPSTR) pta, sizeof(THREADARGS));
                SetNotBusy();
                exit(0);
        }

        /* if there was only one file, expand it */
        if (view_getrowcount(pta->view) == 1) {
                SetSelection(0);
                ToExpand(hwndClient);
        }

        gmem_free(hHeap, (LPSTR) pta, sizeof(THREADARGS));
        SetNotBusy();
        return(0);
} /* wd_initial */

/***************************************************************************
 * Function: wd_dirdialog
 *
 * Purpose:
 *
 * Called on worker thread (not UI thread) to handle a Dir request
 */
DWORD
wd_dirdialog(LPVOID arg)
{

        VIEW view = (VIEW) arg;

        /* make a COMPLIST using the directory dialog,
         * and notify the view
         */
        if (complist_dirdialog(view) == NULL) {
                view_close(view);
        }

        /* all done! */
        SetNotBusy();
        return(0);
}

/***************************************************************************
 * Function: wd_copy
 *
 * Purpose:
 *
 * Called on worker thread to do a copy-files operation
 */
DWORD
wd_copy(LPVOID arg)
{

        VIEW view = (VIEW) arg;

        complist_copyfiles(view_getcomplist(view), NULL, 0);

        SetNotBusy();

        return(0);
}

/***************************************************************************
 * Function: MainWndProc
 *
 * Purpose:
 *
 * Window processing for main window
 */
long APIENTRY
MainWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam)
{
        char str[32];
        long ret;
        DWORD threadid;

        switch(message) {

        case WM_CREATE:

                /* initialise menu options to default/saved
                 * option settings
                 */

                CheckMenuItem(hMenu, IDM_INCSAME,
                      (outline_include & INCLUDE_SAME) ?
                                MF_CHECKED:MF_UNCHECKED);

                CheckMenuItem(hMenu, IDM_INCLEFT,
                      (outline_include & INCLUDE_LEFTONLY) ?
                                MF_CHECKED:MF_UNCHECKED);

                CheckMenuItem(hMenu, IDM_INCRIGHT,
                      (outline_include & INCLUDE_RIGHTONLY) ?
                                MF_CHECKED:MF_UNCHECKED);
                CheckMenuItem(hMenu, IDM_INCDIFFER,
                      (outline_include & INCLUDE_DIFFER) ?
                                MF_CHECKED:MF_UNCHECKED);

                CheckMenuItem(hMenu, line_numbers, MF_CHECKED);
                CheckMenuItem(hMenu, expand_mode, MF_CHECKED);

                CheckMenuItem(hMenu, IDM_IGNBLANKS,
                        ignore_blanks ? MF_CHECKED : MF_UNCHECKED);
                CheckMenuItem(hMenu, IDM_PICTURE,
                        picture_mode ? MF_CHECKED : MF_UNCHECKED);

                /* nothing currently displayed */
                DisplayMode = MODE_NULL;

                break;

        case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDM_EXIT:
                        if (ghThread!=NULL) {
                                extern CRITICAL_SECTION CSView;
                                /* Stop any other thread from allocating things that we
                                   want to free!  See the threads DOGMA at the top
                                   of this file.
                                */

                                /* Because the thread that we are about to kill might be in
                                   a critical section, we first grab them both.  It is
                                   essential that anyone else who ever does this, does
                                   so in the same order!
                                */
                                WDEnter();
                                EnterCriticalSection(&CSView);
                                TerminateThread(ghThread, 31);
                                CloseHandle(ghThread);
                                ghThread = NULL;
                                LeaveCriticalSection(&CSView);
                                WDLeave();
                        }
                        if (!view_isexpanded(current_view)) {
                                /* save the current outline size and position */
                                WINDOWPLACEMENT wp;
                                if (GetWindowPlacement(hwndClient,&wp)) {
                                        WriteProfileInt(APPNAME, "OutlineShowCmd", wp.showCmd);
                                        WriteProfileInt(APPNAME, "OutlineMaxX", wp.ptMaxPosition.x);
                                        WriteProfileInt(APPNAME, "OutlineMaxY", wp.ptMaxPosition.y);
                                        WriteProfileInt(APPNAME, "OutlineNormLeft", wp.rcNormalPosition.left);
                                        WriteProfileInt(APPNAME, "OutlineNormTop", wp.rcNormalPosition.top);
                                        WriteProfileInt(APPNAME, "OutlineNormRight", wp.rcNormalPosition.right);
                                        WriteProfileInt(APPNAME, "OutlineNormBottom", wp.rcNormalPosition.bottom);
                                        WriteProfileInt(APPNAME, "OutlineSaved", 1);
                                }
                        } else {
                                /* save the current expanded size and position */
                                WINDOWPLACEMENT wp;
                                if (GetWindowPlacement(hwndClient,&wp)) {
                                        WriteProfileInt(APPNAME, "ExpandShowCmd", wp.showCmd);
                                        WriteProfileInt(APPNAME, "ExpandMaxX", wp.ptMaxPosition.x);
                                        WriteProfileInt(APPNAME, "ExpandMaxY", wp.ptMaxPosition.y);
                                        WriteProfileInt(APPNAME, "ExpandNormLeft", wp.rcNormalPosition.left);
                                        WriteProfileInt(APPNAME, "ExpandNormTop", wp.rcNormalPosition.top);
                                        WriteProfileInt(APPNAME, "ExpandNormRight", wp.rcNormalPosition.right);
                                        WriteProfileInt(APPNAME, "ExpandNormBottom", wp.rcNormalPosition.bottom);
                                        WriteProfileInt(APPNAME, "ExpandedSaved", 1);
                                }
                        }
                        DestroyWindow(hWnd);
                        break;

                case IDM_ABORT:
                        /* abort menu item, or status bar button.
                         * the status bar button text gives the appropriate
                         * action depending on our state - abort, outline
                         * or expand. But the command sent is always
                         * IDM_ABORT. Thus we need to check the state
                         * to see what to do. If we are busy, set the abort
                         * flag. If there is nothing to view,
                         * exit, otherwise switch outline<->expand
                         */
                        if (IsBusy()) {
                                bAbort = TRUE;
                                SetStatus(LoadRcString(IDS_ABORT_PENDING));

                        } else if (DisplayMode == MODE_NULL) {
                                DestroyWindow(hWnd);
                        } else if (DisplayMode == MODE_EXPAND) {
                                ToOutline(hWnd);
                        } else {
                                ToExpand(hWnd);
                        }
                        break;

                case IDM_FILE:
                        /* select two files and compare them */
                        if (SetBusy()) {

                               /* close the current view */
                                view_close(current_view);

                                /* make a new empty view */
                                current_view = view_new(hwndRCD);

                                /* make a COMPLIST using the files dialog,
                                 * and notify the view
                                 */
                                if (complist_filedialog(current_view) == NULL) {
                                        view_close(current_view);
                                }

                                /* all done! */
                                SetNotBusy();
                        } else {
                                BusyError();
                        }
                        break;

                case IDM_DIR:

                        /* read two directory names, scan them and
                         * compare all the files and subdirs.
                         */
                        if (SetBusy()) {

                                /* close the current view */
                                view_close(current_view);

                                /* make a new empty view */
                                current_view = view_new(hwndRCD);

                                ghThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)wd_dirdialog,
                                        (LPVOID) current_view, 0, &threadid);

                                if (ghThread == NULL)
                                {
                                        wd_dirdialog( (LPVOID) current_view);
                                }

                        } else {
                                BusyError();
                        }
                        break;

                case IDM_CLOSE:
                        /* close the output list -
                         * discard all results so far
                         */
                        if (!IsBusy()) {
                                view_close(current_view);
                        }
                        break;

                case IDM_PRINT:
                        /* print the current view -
                         * either the outline list of filenames,
                         * or the currently expanded file.
                         */
                        if (!IsBusy()) {
                                DoPrint();
                        } else {
                                BusyError();
                        }
                        break;

                case IDM_TIME:
                        /* show time it took */
                        {       char msg[50];
                                DWORD tim;
                                if (IsBusy()) {
                                         BusyError();
                                }
                                else{
                                        tim = complist_querytime();
                                        wsprintf((LPTSTR)msg, LoadRcString(IDS_SECONDS), tim/1000, tim%1000);
                                }
                        }
                        break;

                case IDM_SAVELIST:
                        /* allow user to save list of same/different files
                         * to a text file. dialog box to give filename
                         * and select which types of file to include
                         */
                        complist_savelist(view_getcomplist(current_view), NULL, 0);
                        break;

                case IDM_COPYFILES:
                        /*
                         * copy files that are same/different to a new
                         * root directory. dialog box allows user
                         * to select new root and inclusion options
                         */
                        if (current_view == NULL) {
                                MessageBox(hWnd,
                                    LoadRcString(IDS_CREATE_DIFF_LIST),
                                    "WinDiff", MB_OK|MB_ICONSTOP);
                                break;
                        }

                        if (SetBusy()) {
                                ghThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)wd_copy,
                                        (LPVOID) current_view, 0, &threadid);
                                if (ghThread == NULL)
                                {
                                        wd_copy( (LPVOID) current_view);
                                }

                        } else {
                                BusyError();
                        }

                        break;

                case IDM_ABOUT:

                        DialogBox( hInst, "About", hWnd, (DLGPROC)AboutBox);
                        break;

                /* launch an editor on the current item - left, right or
                 * composite view
                 */
                case IDM_EDITLEFT:
                        do_editthread(current_view, CI_LEFT);
                        break;

                case IDM_EDITRIGHT:
                        do_editthread(current_view, CI_RIGHT);
                        break;

                case IDM_EDITCOMP:
                        do_editthread(current_view, CI_COMP);
                        break;

                /* allow customisation of the editor command line */
                case IDM_SETEDIT:
                        if (StringInput(editor_cmdline, sizeof(editor_cmdline),
                                        LoadRcString(IDS_ENTER_EDT_CMD_LINE),
                                        "Windiff", editor_cmdline))  {
                                WriteProfileString(APPNAME, "Editor",
                                        (LPCSTR)editor_cmdline);
                        }
                        break;

                case IDM_LNRS:
                case IDM_RNRS:
                case IDM_NONRS:

                        /* option selects whether the line nrs displayed
                         * in expand mode are the line nrs in the left
                         * file, the right file or none
                         */

                        CheckMenuItem(GetMenu(hWnd),
                                line_numbers, MF_UNCHECKED);
                        line_numbers = GET_WM_COMMAND_ID(wParam, lParam);
                        CheckMenuItem(GetMenu(hWnd), line_numbers, MF_CHECKED);
                        wsprintf((LPTSTR)str, "%d", line_numbers);
                        WriteProfileString(APPNAME, "LineNumbers", str);

                        /* change the display to show the line nr style
                         * chosen
                         */

                        view_changeviewoptions(current_view);

                        break;

                /*
                 * options selecting which files to include in the
                 * outline listing, based on their state
                 */
                case IDM_INCLEFT:

                        /* toggle flag in outline_include options */
                        outline_include ^= INCLUDE_LEFTONLY;

                        /* check/uncheck as necessary */
                        CheckMenuItem(hMenu, IDM_INCLEFT,
                              (outline_include & INCLUDE_LEFTONLY) ?
                                        MF_CHECKED:MF_UNCHECKED);

                        wsprintf((LPTSTR)str, "%d", outline_include);
                        WriteProfileString(APPNAME, "FileInclude", str);
                        view_changeviewoptions(current_view);

                        break;

                case IDM_INCRIGHT:

                        outline_include ^= INCLUDE_RIGHTONLY;

                        CheckMenuItem(hMenu, IDM_INCRIGHT,
                              (outline_include & INCLUDE_RIGHTONLY) ?
                                        MF_CHECKED:MF_UNCHECKED);
                        wsprintf((LPTSTR)str, "%d", outline_include);
                        WriteProfileString(APPNAME, "FileInclude", str);
                        view_changeviewoptions(current_view);

                        break;

                case IDM_INCSAME:

                        outline_include ^= INCLUDE_SAME;

                        CheckMenuItem(hMenu, IDM_INCSAME,
                              (outline_include & INCLUDE_SAME) ?
                                        MF_CHECKED:MF_UNCHECKED);
                        wsprintf((LPTSTR)str, "%d", outline_include);
                        WriteProfileString(APPNAME, "FileInclude", str);
                        view_changeviewoptions(current_view);

                        break;

                case IDM_INCDIFFER:

                        outline_include ^= INCLUDE_DIFFER;

                        CheckMenuItem(hMenu, IDM_INCDIFFER,
                              (outline_include & INCLUDE_DIFFER) ?
                                        MF_CHECKED:MF_UNCHECKED);

                        wsprintf((LPTSTR)str, "%d", outline_include);
                        WriteProfileString(APPNAME, "FileInclude", str);
                        view_changeviewoptions(current_view);

                        break;

                case IDM_UPDATE:
                        /* update the display.  Options or files may have changed */
                        /* discard lines  (thereby forcing re-read).
                         */
                        file_discardlines(compitem_getleftfile( (COMPITEM)lParam) );
                        file_discardlines(compitem_getrightfile( (COMPITEM)lParam) );

                        view_changediffoptions(current_view);

                        /* force repaint of bar window */
                        InvalidateRect(hwndBar, NULL, TRUE);
                        break;

                case IDM_LONLY:
                case IDM_RONLY:
                case IDM_BOTHFILES:
                        /* option selects whether the expanded file
                         * show is the combined file, or just one
                         * or other of the input files.
                         *
                         * if we are not in expand mode, this also
                         * causes us to expand the selection
                         */

                        CheckMenuItem(GetMenu(hWnd), expand_mode, MF_UNCHECKED);
                        expand_mode = GET_WM_COMMAND_ID(wParam, lParam);
                        CheckMenuItem(GetMenu(hWnd), expand_mode, MF_CHECKED);

                        /* change the current view to show only the lines
                         * of the selected type.
                         */
                        if (DisplayMode == MODE_OUTLINE) {
                                ToExpand(hWnd);
                        } else {
                                view_changeviewoptions(current_view);
                        }

break;

                case IDM_IGNBLANKS:

                        /* if selected, ignore all spaces and tabs on
                         * comparison - expand view only: outline view
                         * will still show that 'text files differ'
                         */

                        ignore_blanks = !ignore_blanks;
                        CheckMenuItem(hMenu, IDM_IGNBLANKS,
                                ignore_blanks? MF_CHECKED:MF_UNCHECKED);
                        wsprintf((LPTSTR)str, "%d", ignore_blanks);
                        WriteProfileString(APPNAME, "Blanks", str);

                        /* invalidate all diffs since we have
                         * changed diff options, and re-do and display the
                         * current diff if we are in expand mode.
                         */
                        view_changediffoptions(current_view);

                        /* force repaint of bar window */
                        InvalidateRect(hwndBar, NULL, TRUE);

                        break;

                case IDM_PICTURE:
                        /* do we show the bar picture in expand mode ? */
                        picture_mode = !picture_mode;
                        CheckMenuItem(hMenu, IDM_PICTURE,
                                picture_mode? MF_CHECKED:MF_UNCHECKED);
                        wsprintf((LPTSTR)str, "%d", picture_mode);
                        WriteProfileString(APPNAME, "Picture", str);
                        DoResize(hWnd);
                        break;

                case IDM_EXPAND:

                        /* show the expanded view of the
                         * selected file
                         */
                        if (current_view != NULL) {
                                ToExpand(hWnd);
                        }

                        break;

                case IDM_OUTLINE:
                        /* return to the outline view (list of filenames) */
                        ToOutline(hWnd);

                        break;

                case IDM_FCHANGE:
                        /* find the next line in the current view
                         * that is not the same in both files -
                         * in outline view, finds the next filename that
                         * is not identical
                         */
                        FindNextChange();

                        break;

                case IDM_FPCHANGE:
                        /* same as IDM_FCHANGE, but going backwards from
                         * current position
                         */
                        FindPrevChange();

                        break;
                }
                break;

        case WM_SIZE:
                DoResize(hWnd);
                break;

        case WM_SETFOCUS:
                /* set the focus on the table class so it can process
                 * page-up /pagedown keys etc.
                 */
                SetFocus(hwndRCD);
                break;

        case WM_KEYDOWN:
                /* although the table window has the focus, he passes
                 * back to us any keys he doesn't understand
                 * We handle escape here to mean 'return to outline view'
                 */
                if (wParam == VK_ESCAPE) {
                        ToOutline(hWnd);
                }
                break;

        case WM_CLOSE:
                /* don't allow close when busy - process this message in
                 * order to ensure this
                 */
                if (IsBusy()) {
                        return(TRUE);
                } else {
                        return(DefWindowProc(hWnd, message, wParam, lParam));
                }
                break;

        case WM_DESTROY:

                DeleteTools();
                PostQuitMessage(0);
                break;

        case TM_CURRENTVIEW:
                /* allow other people such as the bar window to query the
                 * current view
                 */
                return((DWORD) current_view);

        default:
                /* handle registered table messages */
                if (message == table_msgcode) {
                        ret = TableServer(hWnd, wParam, lParam);
                        return(ret);
                }
                return(DefWindowProc(hWnd, message, wParam, lParam));
        }
        return(0);
}

/***************************************************************************
 * Function: My_mbschr
 *
 * Purpose:
 *
 * DBCS version of strchr
 *
 */
unsigned char * _CRTAPI1 My_mbschr(
    unsigned char *psz, unsigned short uiSep)
{
    while (*psz != '\0' && *psz != uiSep) {
        psz = CharNext(psz);
    }
    return *psz == uiSep ? psz : NULL;
}
/***************************************************************************
 * Function: My_mbsncpy
 *
 * Purpose:
 *
 * DBCS version of strncpy
 *
 */
unsigned char * _CRTAPI1 My_mbsncpy(
unsigned char *psz1, const unsigned char *psz2, size_t Length)
{
        int nLen = (int)Length;
unsigned char *pszSv = psz1;

while (0 < nLen) {
if (*psz2 == '\0') {
*psz1++ = '\0';
nLen--;
} else if (IsDBCSLeadByte(*psz2)) {
if (nLen == 1) {
*psz1 = '\0';
} else {
*psz1++ = *psz2++;
*psz1++ = *psz2++;
}
nLen -= 2;
} else {
*psz1++ = *psz2++;
nLen--;
}
}
return pszSv;
}
/***************************************************************************
 * Function: My_mbsrchr
 *
 * Purpose:
 *
 * DBCS version of strrchr
 *
 */
unsigned char * _CRTAPI1 My_mbsrchr(
    unsigned char *psz, unsigned short uiSep)
{
    unsigned char *pszHead;

    pszHead = psz;

    while (*psz != '\0') {
        psz++;
    }
    if (uiSep == '\0') {
        return psz;
    }

    while (psz > pszHead) {
        psz = CharPrev(pszHead, psz);
        if (*psz == uiSep) {
            break;
        }
    }
    return *psz == uiSep ? psz : NULL;
}
/***************************************************************************
 * Function: My_mbsncmp
 *
 * Purpose:
 *
 * DBCS version of strncmp
 * If 'nLen' splits a DBC, this function compares the DBC's 2nd byte also.
 *
 */
int _CRTAPI1 My_mbsncmp(
    const unsigned char *psz1, const unsigned char *psz2, size_t nLen)
{
    int Length = (int)nLen;

    while (0 < Length) {
        if ('\0' == *psz1 || '\0' == *psz2) {
            return *psz1 - *psz2;
        }
        if (IsDBCSLeadByte(*psz1) || IsDBCSLeadByte(*psz2)) {
            if (*psz1 != *psz2 || *(psz1+1) != *(psz2+1)) {
                return *psz1 - *psz2;
            }
            psz1 += 2;
            psz2 += 2;
            Length -= 2;
        } else {
            if (*psz1 != *psz2) {
                return *psz1 - *psz2;
            }
            psz1++;
            psz2++;
            Length--;
        }
    }
    return 0;
}


WINDIFF.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.
\******************************************************************************/

/*
 * WINDIFF.H
 */

/* application-wide variables -------------------------------------*/

/* This is the section name in the win.ini file to which we
 * write profile info
 */
#define APPNAME "WinDiff"

/* A gmem_init() heap shared by the app. call gmem_get to alloc. */
extern HANDLE hHeap;

/* The instance handle for this app. Needed by anyone who uses resources
 * such as dialogs
 */
extern HINSTANCE hInst;

extern HWND hwndClient;
extern HWND hwndRCD;

/* global option flags-------------------------------------------  */

/* Which files do we show in outline mode ? all, changed... */
extern int outline_include;

/* Outline_include is an OR of the following */
#define INCLUDE_SAME            1
#define INCLUDE_DIFFER          2
#define INCLUDE_LEFTONLY        4
#define INCLUDE_RIGHTONLY       8

/* Do we ignore blanks during the line-by-line diff ? */
extern BOOL ignore_blanks;

/* Which line numbers do we show - left original, right original or none ?*/
extern int line_numbers;

/* What lines do we show in expand mode - all, left only, right only ? */
extern int expand_mode;

/*--- colour scheme ----------------------------------------------  */

/* Outline */
extern DWORD rgb_outlinehi;

/* Expand view */
extern DWORD rgb_leftfore;
extern DWORD rgb_leftback;
extern DWORD rgb_rightfore;
extern DWORD rgb_rightback;
extern DWORD rgb_mleftfore;
extern DWORD rgb_mleftback;
extern DWORD rgb_mrightfore;
extern DWORD rgb_mrightback;

/* Bar window */
extern DWORD rgb_barleft;
extern DWORD rgb_barright;
extern DWORD rgb_barcurrent;

/* -- display layout constants---------------------------------------*/

/* Percentage of width of window taken by bar display (when visible) */
#define BAR_WIN_WIDTH   10

/* Following are horizontal positions within the bar window, expressed
 * in percent of the width of the bar window
 */
#define L_POS_START     10      /* start of left position marker */
#define L_POS_WIDTH     5       /* width of left position marker */
#define R_POS_START     80      /* start of right position marker */
#define R_POS_WIDTH     5       /* width of right position marker */

#define L_UNMATCH_START 30      /* start of left bar for unmatched section */
#define L_UNMATCH_WIDTH 10      /* width of above */
#define R_UNMATCH_START 60      /* start of right bar for unmatch section */
#define R_UNMATCH_WIDTH 10      /* width of right unmatched section marker */
#define L_MATCH_START   30      /* start of left bar for matched section */
#define L_MATCH_WIDTH   10      /* width of left bar for matched section */
#define R_MATCH_START   60      /* start of right bar for matched section */
#define R_MATCH_WIDTH   10      /* width of right bar for matched section */

/* windiff.c functions */
void windiff_UI(BOOL bAttach);
BOOL Poll(void);                /* true if abort pending */
void SetNames(LPSTR names);
void SetStatus(LPSTR state);

/* in bar.c */
BOOL InitBarClass(HINSTANCE hInstance);
void BarDrawPosition(HWND hwndBar, HDC hdcIn, BOOL bErase);

/*-- private messages -- */
/* Send this to the main window. Return value is the VIEW handle */
#define TM_CURRENTVIEW  WM_USER

/* --- synchronisation ----------------------------------------- */
/*
 * In WIN32 we spawn worker threads to do time-consuming actions.
 * This causes a possible conflict with the UI thread when accessing the
 * BUSY flag.
 *
 * To protect against this we have a critical section. The UI thread
 * will get this before checking/changing the Busy flag,
 * The worker thread will get this before Busy flag* changes.
 *
 */

CRITICAL_SECTION CSWindiff;

#define WDEnter()       EnterCriticalSection(&CSWindiff);
#define WDLeave()       LeaveCriticalSection(&CSWindiff);


README.TXT

Display or Modify Access Control Lists (ACLS) of Files

The CACLS tool displays or modifies the ACLs of files.

Usage:

CACLS filename [/T] [/E] [/C] [/G user:perm] [/R user] [/P user:perm]
               [/D user]

filename       Displays ACLs.
/T             Changes ACLs of specified files in the current
               directory and all subdirectories.
/E             Edit ACL instead of replacing it.
/C             Continue on access denied errors.

/G user:perm   Grant specified user access rights.
               Perm can be: R  Read
                            C  Change (write)
                            F  Full control
/R user        Revoke specified user's access rights.
/P user:perm   Replace specified user's access rights.
               Perm can be: N  None
                            R  Read
                            C  Change (write)
                            F  Full control
/D user        Deny specified user access.

Wildcards can be used to specify more that one file in a command. You can
specify more than one user in a command.


ACCOUNT.CXX

//+------------------------------------------------------------------
//
// Copyright (C) 1995, Microsoft Corporation.
//
// File:        account.cxx
//
// Contents:    Class wrapping account sid and name
//
// Classes:     CAccount
//
// History:     Nov-93      DaveMont         Created.
//
//-------------------------------------------------------------------

#include <account.hxx>
//+---------------------------------------------------------------------------
//
//  Member:     CAccount::CAccount, public
//
//  Synopsis:   initializes data members
//
//  Arguments:  IN [Name]   - principal
//              IN [System] - server/domain
//
//----------------------------------------------------------------------------
CAccount::CAccount(WCHAR *Name, WCHAR *System)
    : _name(Name),
      _system(System),
      _domain(NULL),
      _psid(NULL),
      _fsid(TRUE)
{
}
//+---------------------------------------------------------------------------
//
//  Member:     CAccount::CAccount, public
//
//  Synopsis:   Initializes data members
//
//  Arguments:  IN [pSid]   - SID of principal
//              IN [System] - server/domain
//
//----------------------------------------------------------------------------
CAccount::CAccount(SID *pSid, WCHAR *System)
    : _name(NULL),
      _system(System),
      _domain(NULL),
      _psid(pSid),
      _fsid(FALSE)
{
}
//+---------------------------------------------------------------------------
//
//  Member:     Dtor, public
//
//  Synopsis:   frees sid or name and domain
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
CAccount::~CAccount()
{
    if (_fsid)
    {
        if (_psid)
        {
            LocalFree(_psid);
        }
    } else if (_name)
    {
        LocalFree(_name);
    }
    if (_domain)
        LocalFree(_domain);
}
//+---------------------------------------------------------------------------
//
//  Member:     CAccount::GetAccountName, public
//
//  Synopsis:   returns the Name associated with the instance of the class
//
//  Arguments:  OUT [name] address of the principal name
//
//----------------------------------------------------------------------------
ULONG CAccount::GetAccountName(WCHAR **name)
{

    ULONG ret = ERROR_SUCCESS;

    if (_name == NULL)
    {
        DWORD can = 0, crd = 0;
        SID_NAME_USE esnu;

        if (!LookupAccountSid( NULL,
                               _psid,
                               NULL,
                               &can,
                               NULL,
                               &crd,
                               &esnu))
        {
            if (ERROR_INSUFFICIENT_BUFFER == (ret = GetLastError()))
            {
                ret = ERROR_SUCCESS;
                if (NULL == (_name = (WCHAR *)LocalAlloc(LMEM_FIXED, can * sizeof(WCHAR))))
                {
                    return(ERROR_NOT_ENOUGH_MEMORY);
                }
                if (NULL == (_domain = (WCHAR *)LocalAlloc(LMEM_FIXED, crd * sizeof(WCHAR))))
                {
                    return(ERROR_NOT_ENOUGH_MEMORY);
                }

                if ( !LookupAccountSid( NULL,
                                       _psid,
                                       _name,
                                       &can,
                                       _domain,
                                       &crd,
                                       &esnu) )
                {
                   ret = GetLastError();
                }
            }
        }
     }
     *name = _name;
     return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CAccount::GetAccountSid, public
//
//  Synopsis:   returns the Sid
//
//  Arguments:  OUT [psid] - sid associated with instance of the class
//
//----------------------------------------------------------------------------
ULONG CAccount::GetAccountSid(SID **psid)
{

    ULONG ret = ERROR_SUCCESS;

    if (_psid == NULL && _name != NULL)
    {
        DWORD cusid = 0, crd = 0;
        SID_NAME_USE esnu;

        if (!LookupAccountName( _system,
                                _name,
                               NULL,
                               &cusid,
                               NULL,
                               &crd,
                               &esnu))
        {
            if (ERROR_INSUFFICIENT_BUFFER == (ret = GetLastError()))
            {

                ret = ERROR_SUCCESS;
                if (NULL == (_psid = (SID *)LocalAlloc(LMEM_FIXED, cusid)))
                {
                    return(ERROR_NOT_ENOUGH_MEMORY);
                }
                if (NULL == (_domain = (WCHAR *)LocalAlloc(LMEM_FIXED, crd * sizeof(WCHAR))))
                {
                    return(ERROR_NOT_ENOUGH_MEMORY);
                }

                if ( !LookupAccountName( _system,
                                         _name,
                                         _psid,
                                         &cusid,
                                         _domain,
                                         &crd,
                                         &esnu) )

                {
                   ret = GetLastError();
                }
            }
        }
     }
     *psid = _psid;
     return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CAccount::GetAccountDomain, public
//
//  Synopsis:   returns the domain for the class
//
//  Arguments:  [domain] - returns the domain associated with the instance of
//                         the class
//
//----------------------------------------------------------------------------
ULONG CAccount::GetAccountDomain(WCHAR **domain)
{
    ULONG ret = ERROR_SUCCESS;

    if (_domain == NULL)
    {
        if (_fsid)
        {
            SID *psid;
            ret = GetAccountSid(&psid);
        } else
        {
            WCHAR *name;
            ret = GetAccountName(&name);
        }
    }
    *domain = _domain;
    return(ret);
}


DACLWRAP.CXX

//+-------------------------------------------------------------------
//
// Copyright (C) 1995, Microsoft Corporation.
//
//  File:        daclwrap.cxx
//
//  Contents:    class encapsulating file security.
//
//  Classes:     CDaclWrap
//
//  History:     Nov-93        Created         DaveMont
//
//--------------------------------------------------------------------
#include <t2.hxx>
#include <daclwrap.hxx>

#if DBG
extern ULONG Debug;
#endif
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap::CDaclWrap, public
//
//  Synopsis:   initialize data members, constructor will not throw
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
CDaclWrap::CDaclWrap()
      : _ccaa(0)
{
}
//+---------------------------------------------------------------------------
//
//  Member:     Dtor, public
//
//  Synopsis:   cleanup allocated data
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
CDaclWrap::~CDaclWrap()
{
    for (ULONG j = 0; j < _ccaa; j++)
        delete _aaa[j].pcaa;
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap::SetAccess, public
//
//  Synopsis:   caches data for a new ACE
//
//  Arguments:  IN [option] - rePlace, Revoke, Grant, Deny
//              IN [Name] - principal (username)
//              IN [System] - server/machine where Name is defined
//              IN [access] - access mode (Read Change None All)
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::SetAccess(ULONG option, WCHAR *Name, WCHAR *System, ULONG access)
{
    ULONG ret;

    // sorry, static number of ACCESSes can be set at one time

    if (_ccaa >= CMAXACES)
        return(ERROR_BUFFER_OVERFLOW);

    // allocate a new account access class

    if (NULL == (_aaa[_ccaa].pcaa = new CAccountAccess(Name, System)))
    {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    // to fix the bug where someone asks to both grant and deny under
    // the /p option (the deny is thru access = N)

    if ((GENERIC_NONE == access) && (OPTION_REPLACE == option))
    {
_aaa[_ccaa].option = OPTION_DENY;
    } else
    {
_aaa[_ccaa].option = option;
    }

    SID *psid;

    if (ERROR_SUCCESS == ( ret = _aaa[_ccaa].pcaa->Init(access)))
    {
        // get the sid to make sure the username is valid

        if (ERROR_SUCCESS == ( ret =_aaa[_ccaa].pcaa->Sid(&psid)))
        {
            // loop thru the existing sids, making sure the new one is not a duplicate

            SID *poldsid;
            for (ULONG check = 0;check < _ccaa ; check++)
            {
                if (ERROR_SUCCESS == ( ret =_aaa[check].pcaa->Sid(&poldsid)))
                {
                    if (EqualSid(psid,poldsid))
                    {
                        VERBOSE((stderr, "SetAccess found matching new sids\n"))
                        return(ERROR_BAD_ARGUMENTS);
                    }
                }
            }
            _ccaa++;
        }
    }
    return(ret);
}

//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:BuildAcl, public
//
//  Synopsis:   merges cached new aces with the input ACL
//
//  Arguments:  OUT [pnewdacl] - Address of new ACL to build
//              IN  [poldacl]  - (OPTIONAL) old ACL that is to be merged
//              IN  [revision] - ACL revision
//              IN  [fdir]     - True = directory
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::BuildAcl(ACL **pnewdacl, ACL *poldacl, UCHAR revision, BOOL fdir)
{
    ULONG ret, caclsize;

    // get the size of the new ACL we are going to create

    if (ERROR_SUCCESS == (ret =  _GetNewAclSize(&caclsize, poldacl, fdir)))
    {
        // allocate the new ACL

        if (ERROR_SUCCESS == (ret =  _AllocateNewAcl(pnewdacl, caclsize, revision)))
        {
            // and fill it up

            if (ERROR_SUCCESS != (ret =  _FillNewAcl(*pnewdacl, poldacl, fdir)))
            {
                // free the buffer if we failed

                LocalFree(*pnewdacl);
            }

        }
    }
    return(ret);
}

//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_GetNewAclSize, private
//
//  Synopsis:   returns the size need to merge the new ACEs with the old ACL,
//              this is an ugly algorithm:
//
//if (old aces exist)
//   for (new aces)
//      if (new ace option == GRANT)
//         for (old aces)
//            if (new ace SID == old ace SID)
//               do inheritance check
//               found = true
//               if (old ace type == ALLOWED)
//                  old ace mask |= new ace mask
//               else
//                  old ace mask &= ~new ace mask
//         if (!found)
//            add size of new ace
//         else
//            new ace mask = 0
//      else
//         add size of new ace
//
//   for (old aces)
//      for (new aces)
//         if (new ace option == DENY, REPLACE, REVOKE)
//            if (new ace SID == old ace SID)
//               found = true
//               break
//      if (!found)
//         add size of old ace
//      else
//         old ace mask = 0
//else
//   for (new aces)
//      add size of new ace
//
//
//  Arguments:  OUT [caclsize] - returns size
//              IN  [poldacl]  - (OPTIONAL) old ACL that is to be merged
//              IN  [fdir]     - True = directory
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_GetNewAclSize(ULONG *caclsize, ACL *poldacl, BOOL fdir)
{
    ULONG ret;

    // the size for the ACL header

    *caclsize = sizeof(ACL);

    // initialize the access requests
    for (ULONG j = 0; j < _ccaa; j++)
       _aaa[j].pcaa->ReInit();

    // if we are merging, calculate the merge size

    if (poldacl)
    {
        // first the grant options

        for (j = 0; j < _ccaa; j++)
        {
            SID *psid;
            if (OPTION_GRANT == _aaa[j].option)
            {
                BOOL ffound = FALSE;
                ACE_HEADER *pah = (ACE_HEADER *)Add2Ptr(poldacl, sizeof(ACL));

                for (ULONG cace = 0; cace < poldacl->AceCount;
                     cace++, pah = (ACE_HEADER *)Add2Ptr(pah, pah->AceSize))
                {
                    if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                    {
                        if (EqualSid(psid,
                                     (SID *)&((ACCESS_ALLOWED_ACE *)
                                     pah)->SidStart) )
                        {
                            // if old and new types are the same, just and with the old

                            if (fdir && (pah->AceType == _aaa[j].pcaa->AceType()))
                            {
                                // make sure that we can handle the inheritance
                                _aaa[j].pcaa->AddInheritance(pah->AceFlags);

                                ffound = TRUE;
                            } else if (pah->AceType == _aaa[j].pcaa->AceType())
                            {
                                ffound = TRUE;
                            }

                            if (ACCESS_ALLOWED_ACE_TYPE == pah->AceType)
                            {
                                (ACCESS_MASK) ((ACCESS_ALLOWED_ACE *)
                                pah)->Mask |= _aaa[j].pcaa->AccessMask();
                            } else if (ACCESS_DENIED_ACE_TYPE == pah->AceType)
                            {
                                (ACCESS_MASK) ((ACCESS_ALLOWED_ACE *)
                                pah)->Mask &= ~_aaa[j].pcaa->AccessMask();
                            } else
                            {
                                VERBOSE((stderr, "_GetNewAclSize found an ace that was not allowed or denied\n"))
                                return(ERROR_INVALID_DATA);
                            }
                        }
                    } else
                    {
                        return(ret);
                    }
                }
                if (!ffound)
                {
                    // bugbug allowed/denied sizes currently the same

                    *caclsize += sizeof(ACCESS_ALLOWED_ACE) -
                                 sizeof(DWORD) +
                                 GetLengthSid(psid);

                    SIZE((stderr, "adding on size of an new ACE (to the new ACL) = %d\n",*caclsize))
                } else
                {
                    if (fdir && (ERROR_SUCCESS != (ret = _aaa[j].pcaa->TestInheritance())))
                        return(ret);
                    _aaa[j].pcaa->ClearAccessMask();
                }
            } else if ( (OPTION_REPLACE == _aaa[j].option) ||
                        (OPTION_DENY == _aaa[j].option) )
            {
                if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                {
                    // bugbug allowed/denied sizes currently the same

                    *caclsize += sizeof(ACCESS_ALLOWED_ACE) -
                                 sizeof(DWORD) +
                                 GetLengthSid(psid);

                    SIZE((stderr, "adding on size of an new ACE (to the new ACL) = %d\n",*caclsize))
                } else
                    return(ret);
            }
        }
        // now for the deny, replace & revoke options

        ACE_HEADER *pah = (ACE_HEADER *)Add2Ptr(poldacl, sizeof(ACL));
        SID *psid;

        // loop thru the old ACL

        for (ULONG cace = 0; cace < poldacl->AceCount;
            cace++, pah = (ACE_HEADER *)Add2Ptr(pah, pah->AceSize))
        {
            BOOL ffound = FALSE;

            // and thru the new ACEs looking for matching SIDs

            for (ULONG j = 0; j < _ccaa; j++)
            {
                if ( (_aaa[j].option & OPTION_DENY ) ||
                     (_aaa[j].option & OPTION_REPLACE ) ||
                     (_aaa[j].option & OPTION_REVOKE ) )
                {
                    if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                    {
                        if (EqualSid(psid,
                                     (SID *)&((ACCESS_ALLOWED_ACE *)
                                     pah)->SidStart) )
                        {
                            ffound = TRUE;
                        }
                    } else
                        return(ret);
                }
            }
            if (!ffound)
            {
                // if we did not find a match, add the size of the old ACE

                *caclsize += ((ACE_HEADER *)pah)->AceSize;

                SIZE((stderr, "adding on size of an old ACE (to the new ACL) = %d\n",*caclsize))
            } else
            {
                (ACCESS_MASK) ((ACCESS_ALLOWED_ACE *)pah)->Mask = 0;
            }
        }
        SIZE((stderr, "final size for new ACL = %d\n",*caclsize))
    } else
    {
        // no old ACL, just add up the sizes of the new aces

        for (j = 0; j < _ccaa; j++)
        {
            // need to know the size of the sid

            SID *psid;
            if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
            {
                // bugbug allowed/denied sizes currently the same

                *caclsize += sizeof(ACCESS_ALLOWED_ACE) -
                             sizeof(DWORD) +
                             GetLengthSid(psid);

                SIZE((stderr, "adding on size of an new ACE (to the new ACL) = %d\n",*caclsize))
            } else
            {
                return(ret);
            }
        }
        SIZE((stderr, "final size for new ACL = %d\n",*caclsize))
    }
    return(ERROR_SUCCESS);
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_AllocateNewAcl, private
//
//  Synopsis:   allocates and initializes the new ACL
//
//  Arguments:  OUT [pnewdacl] - address of new ACL to allocate
//              IN  [caclsize] - size to allocate for the new ACL
//              IN  [revision] - revision of the new ACL
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_AllocateNewAcl(ACL **pnewdacl, ULONG caclsize, ULONG revision)
{
    if (NULL == (*pnewdacl = (ACL *) LocalAlloc(LMEM_FIXED, caclsize)))
    {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    if (!InitializeAcl(*pnewdacl,caclsize, revision))
    {
        ULONG ret = GetLastError();
        LocalFree(*pnewdacl);
        return(ret);

    }

    return(ERROR_SUCCESS);
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_SetAllowedAce, private
//
//  Synopsis:   appends an allowed ACE to the input ACL
//
//  Arguments:  IN [dacl] - ACL to add the ACE to
//              IN [mask] - access mask to add
//              IN [psid] - SID to add
//              IN [fdir] - if a Dir add inherit ACE as well
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_SetAllowedAce(ACL *dacl, ACCESS_MASK mask, SID *psid, BOOL fdir)
{
    ULONG ret = ERROR_SUCCESS;

    // compute the size of the ACE we are making

    USHORT acesize = sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(psid);

    SIZE((stderr, "adding allowed ace, size = %d\n",fdir ? acesize*2 : acesize))

    // static buffer in the hopes we won't have to allocate memory

    BYTE buf[1024];

    // allocator either uses buf or allocates a new buffer if size is not enough

    FastAllocator fa(buf, 1024);

    // get the buffer for the ACE

    ACCESS_ALLOWED_ACE *paaa = (ACCESS_ALLOWED_ACE *)fa.GetBuf(acesize);

    // fill in the ACE

    memcpy(&paaa->SidStart,psid,GetLengthSid(psid));
    paaa->Mask = mask;

    paaa->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;
    paaa->Header.AceFlags = fdir ? CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE : 0;
    paaa->Header.AceSize = acesize;

    // put the ACE into the ACL

    if (!AddAce(dacl,
                dacl->AclRevision,
                0xffffffff,
                paaa,
                paaa->Header.AceSize))
        ret = GetLastError();
    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_SetDeniedAce, private
//
//  Synopsis:   appends a denied ACE to the input ACL
//
//  Arguments:  IN [dacl] - ACL to add the ACE to
//              IN [mask] - access mask to add
//              IN [psid] - SID to add
//              IN [fdir] - if a Dir add inherit ACE as well
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_SetDeniedAce(ACL *dacl, ACCESS_MASK mask, SID *psid, BOOL fdir)
{
    ULONG ret = ERROR_SUCCESS;

    // compute the size of the ACE we are making

    USHORT acesize = sizeof(ACCESS_DENIED_ACE) -
                   sizeof(DWORD) +
                   GetLengthSid(psid);

    SIZE((stderr, "adding denied ace, size = %d\n",acesize))

    // static buffer in the hopes we won't have to allocate memory

    BYTE buf[1024];

    // allocator either uses buf or allocates a new buffer if size is not enough

    FastAllocator fa(buf, 1024);

    // get the buffer for the ACE

    ACCESS_DENIED_ACE *paaa = (ACCESS_DENIED_ACE *)fa.GetBuf(acesize);

    // fill in the ACE

    memcpy(&paaa->SidStart,psid,GetLengthSid(psid));
    paaa->Mask = mask;

    paaa->Header.AceType = ACCESS_DENIED_ACE_TYPE;
    paaa->Header.AceFlags = fdir ? CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE : 0;
    paaa->Header.AceSize = acesize;

    // put the ACE into the ACL

    if (!AddAce(dacl,
                dacl->AclRevision,
                0xffffffff,
                paaa,
                paaa->Header.AceSize))
        ret = GetLastError();
    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CDaclWrap:_FillNewAcl, private
//
//  Synopsis:   The worker routine that actually fills the ACL, it adds the
//              new denied ACEs, then if the new ACEs are being merged with
//              an existing ACL, the existing ACL's ACE's (that don't
//              conflict) are added, finally the new allowed ACEs are added.
//              another ugly algorithm:
//
//for (new aces)
//   if (new ace option == DENY)
//      add new ace
//
//if (old aces)
//   for (old aces)
//      if (old ace mask != 0)
//         add old ace
//
//   for (new aces)
//      if (new ace option != DENY)
//         if ( new ace option != REVOKE)
//            if (new ace mask != 0
//                add new ace
//
//else
//   for (new aces)
//      if (new ace option != DENY)
//         add new ace
//
//  Arguments:  IN [pnewdacl] - the new ACL to be filled
//              IN [poldacl]  - (OPTIONAL) old ACL that is to be merged
//              IN [fdir]     - TRUE = directory
//
//----------------------------------------------------------------------------
ULONG CDaclWrap::_FillNewAcl(ACL *pnewdacl, ACL *poldacl, BOOL fdir)
{
    SID *psid;
    ULONG ret;

    // set new denied aces

    VERBOSE((stderr, "start addr of new ACL %0lx\n",pnewdacl))

    for (ULONG j = 0; j < _ccaa; j++)
    {
        if (_aaa[j].option & OPTION_DENY)
        {
            if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
            {
                if (ERROR_SUCCESS != (ret = _SetDeniedAce(pnewdacl,
                                                           _aaa[j].pcaa->AccessMask(),
                                                           psid,
                                                           fdir )))
                    return(ret);
            } else
                return(ret);
        }
    }

    // check and see if the ACL from from the file is in correct format

    if (poldacl)
    {
        SIZE((stderr, "old ACL size = %d, acecount = %d\n",poldacl->AclSize,
              poldacl->AceCount))

        ACE_HEADER *pah = (ACE_HEADER *)Add2Ptr(poldacl, sizeof(ACL));

        // loop thru the old ACL, looking for matches with the new ACEs

        BOOL fallowedacefound = FALSE;
        for (ULONG cace = 0; cace < poldacl->AceCount;
            cace++, pah = (ACE_HEADER *)Add2Ptr(pah, pah->AceSize))
        {
            // error exit if the old ACL is incorrectly formated

            if (pah->AceType == ACCESS_DENIED_ACE_TYPE && fallowedacefound)
            {
                VERBOSE((stderr, "_FillNewAcl found an denied ACE after an allowed ACE\n"))
                return(ERROR_INVALID_DATA);
            }
            else if (pah->AceType == ACCESS_ALLOWED_ACE_TYPE)
                fallowedacefound = TRUE;

            // add the old ace to the new ACL if the old ace's mask is not zero

            if ( 0 != (ACCESS_MASK)((ACCESS_ALLOWED_ACE *)pah)->Mask)
            {
                // add the old ace
                if (!AddAce(pnewdacl,
                            pnewdacl->AclRevision,
                            0xffffffff,
                            pah,
                            pah->AceSize))
                    return(GetLastError());
            }
        }
        // now for the new aces

        for (ULONG j = 0; j < _ccaa; j++)
        {
            if ( (_aaa[j].option != OPTION_DENY) &&
                 (_aaa[j].option != OPTION_REVOKE) &&
                 (_aaa[j].pcaa->AccessMask() != 0) )
            {
                if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                {
                    if (ERROR_SUCCESS != (ret = _SetAllowedAce(pnewdacl,
                                                               _aaa[j].pcaa->AccessMask(),
                                                               psid,
                                                               fdir )))
                        return(ret);
                } else
                    return(ret);
            }

        }
    } else
    {
        // no old acl, just add the (rest) of the new aces
        for (ULONG j = 0; j < _ccaa; j++)
        {
            if (_aaa[j].option != OPTION_DENY)
            {
                if (ERROR_SUCCESS == (ret = _aaa[j].pcaa->Sid(&psid)))
                {
                    if (ERROR_SUCCESS != (ret = _SetAllowedAce(pnewdacl,
                                                               _aaa[j].pcaa->AccessMask(),
                                                               psid,
                                                               fdir )))
                        return(ret);
                } else
                    return(ret);
            }
        }
    }

    return(ERROR_SUCCESS);
}


DUMPSEC.CXX

//+------------------------------------------------------------------
//
// Copyright (C) 1995, Microsoft Corporation.
//
// File:        DumpSec.cxx
//
// Contents:    class to dump file security ACL
//
// Classes:     CDumpSecurity
//
// History:     Nov-93      DaveMont         Created.
//
//-------------------------------------------------------------------

#include <DumpSec.hxx>

//+---------------------------------------------------------------------------
//
//  Member:     CDumpSecurity::CDumpSecurity, public
//
//  Synopsis:   initialized data members, constructor will not throw
//
//  Arguments:  IN [pfilename] - name of file to dump security for
//
//----------------------------------------------------------------------------
CDumpSecurity::CDumpSecurity(WCHAR *pfilename)
    : _psd(NULL),
      _pwfilename(pfilename),
      _pdacl(NULL),
      _pah(NULL),
      _psid(NULL),
      _cacethissid(0)
{
}
//+---------------------------------------------------------------------------
//
//  Member:     CDumpSecurity::Init, public
//
//  Synopsis:   Init must be called before any other methods - this
//              is not enforced.  Init gets the security descriptor and
//              ACL for the file
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
ULONG CDumpSecurity::Init()
{
    ULONG ret;
    ULONG cpsd;

    // get the size of the security buffer

    if (!GetFileSecurity(_pwfilename,
                         DACL_SECURITY_INFORMATION |
                         GROUP_SECURITY_INFORMATION |
                         OWNER_SECURITY_INFORMATION,
                         NULL,
                         0,
                         &cpsd) )
    {
        if (ERROR_INSUFFICIENT_BUFFER == (ret = GetLastError()))
        {
            if ( NULL == ( _psd = (BYTE *)LocalAlloc(LMEM_FIXED, cpsd)))
            {
                 return(ERROR_NOT_ENOUGH_MEMORY);
            }

            // actually get the buffer this time

            if ( GetFileSecurity(_pwfilename,
                                 DACL_SECURITY_INFORMATION |
                                 GROUP_SECURITY_INFORMATION |
                                 OWNER_SECURITY_INFORMATION,
                                 _psd,
                                 cpsd,
                                 &cpsd) )
            {
                BOOL fdaclpresent;
                BOOL cod;

                // get the ACL

                if ( GetSecurityDescriptorDacl(_psd,
                                           &fdaclpresent,
                                           &_pdacl,
                                           &cod) )

                {
                    if (!fdaclpresent)
                    {
                        _pdacl = NULL;
                        return(ERROR_NO_SECURITY_ON_OBJECT);
                    }
                    // save the ACL location

                    _pah = (ACE_HEADER *)Add2Ptr(_pdacl, sizeof(ACL));
                    return(ERROR_SUCCESS);

                } else
                   return(GetLastError());
            } else
               return(GetLastError());
        }
    } else
        return(ERROR_NO_SECURITY_ON_OBJECT);

    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     Dtor, public
//
//  Synopsis:   frees the security descriptor
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
CDumpSecurity::~CDumpSecurity()
{
    if (_psd)
    {
        LocalFree(_psd);
    }
}
//+---------------------------------------------------------------------------
//
//  Member:     CDumpSecurity::GetSDOwner, public
//
//  Synopsis:   returns the owner of the file
//
//  Arguments:  OUT [psid] - address of the returned sid
//
//----------------------------------------------------------------------------
ULONG CDumpSecurity::GetSDOwner(SID **psid)
{
    BOOL cod;
    if ( GetSecurityDescriptorOwner(_psd, (void **)psid, &cod) )
        return(0);
    else
        return(GetLastError());
}

//+---------------------------------------------------------------------------
//
//  Member:     CDumpSecurity::GetSDGroup, public
//
//  Synopsis:   returns the group from the file
//
//  Arguments:  OUT [pgsid] - address of the returned group sid
//
//----------------------------------------------------------------------------
ULONG CDumpSecurity::GetSDGroup(SID **pgsid)
{
    BOOL cod;
    if ( GetSecurityDescriptorGroup(_psd, (void **)pgsid, &cod) )
        return(0);
    else
        return(GetLastError());
}

//+---------------------------------------------------------------------------
//
//  Member:     CDumpSecurity::ResetAce, public
//
//  Synopsis:   sets the 'ace' index to the start of the DACL
//
//  Arguments:  IN - [psid] - the SID to find aces for
//
//----------------------------------------------------------------------------
VOID CDumpSecurity::ResetAce(SID *psid)
{

    _psid = psid;
    _cacethissid = 0;
    if (_pdacl)
        _pah = (ACE_HEADER *)Add2Ptr(_pdacl, sizeof(ACL));
}
//+---------------------------------------------------------------------------
//
//  Member:     CDumpSecurity::GetNextAce, public
//
//  Synopsis:   gets the next ACE from the DACL for the specified SID
//
//  Arguments:  OUT  [pace] - pointer to the next ace for the SID passed
//                            in at the last reset.
//
//  Returns:    the number of the ACE
//
//----------------------------------------------------------------------------
LONG CDumpSecurity::GetNextAce(ACE_HEADER **paceh)
{
    LONG ret = -1;

    if (_pdacl)
    {
        for (;_cacethissid < _pdacl->AceCount;
            _cacethissid++, _pah = (ACE_HEADER *)Add2Ptr(_pah, _pah->AceSize))
        {
            if (!_psid || EqualSid(_psid,(SID *)&((ACCESS_ALLOWED_ACE *)_pah)->SidStart) )
            {
               *paceh = _pah;
                ret = _cacethissid++;
                _pah = (ACE_HEADER *)Add2Ptr(_pah, _pah->AceSize);
                break;
            }
        }
    }
    return(ret);
}


FILEENUM.CXX

//+------------------------------------------------------------------
//
// Copyright (C) 1995, Microsoft Corporation.
//
// File:        FileEnum.cxx
//
// Contents:    class encapsulating file enumeration, including a deep option
//
// Classes:     CFileEnumeration
//
// History:     Nov-93      DaveMont         Created.
//
//-------------------------------------------------------------------

#include <t2.hxx>
#include <FileEnum.hxx>
#if DBG
extern ULONG Debug;
#endif
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::CFileEnumerate, public
//
//  Synopsis:   initializes data members, constructor will not throw
//
//  Arguments:  IN [fdeep] - TRUE = go into sub-directories
//
//----------------------------------------------------------------------------
CFileEnumerate::CFileEnumerate(BOOL fdeep)
    : _fdeep(fdeep),
      _findeep(FALSE),
      _froot(FALSE),
      _fcannotaccess(FALSE),
      _pcfe(NULL),
      _pwfileposition(NULL),
      _handle(INVALID_HANDLE_VALUE)
{
    ENUMERATE_RETURNS((stderr, "CFileEnumerate ctor\n"))
}
//+---------------------------------------------------------------------------
//
//  Member:     Dtor, public
//
//  Synopsis:   closes handles
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
CFileEnumerate::~CFileEnumerate()
{
    if (_handle != INVALID_HANDLE_VALUE)
        FindClose(_handle);
    ENUMERATE_RETURNS((stderr, "CFileEnumerate dtor (%ws)\n", _wpath))
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::Init, public
//
//  Synopsis:   Init must be called before any other methods - this
//              is not enforced. converts a ASCII file/path to a UNICODE
//              file/path, and gets the first file in the enumeration
//
//  Arguments:  IN  [filename]  - the path/file to enumerate
//              OUT [wfilename] - first file in the enumeration
//              OUT [fdir]      - TRUE = returned file is a directory
//
//----------------------------------------------------------------------------
ULONG CFileEnumerate::Init(CHAR *filename, WCHAR **wfilename, BOOL *fdir)
{
    // Initialize the file name

    if (filename && (strlen(filename) < MAX_PATH))
    {
        // make it wchar
        WCHAR winfilename[MAX_PATH];

        if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,
                                filename, -1,
                                winfilename, sizeof(winfilename)) == 0)
            return(ERROR_INVALID_NAME);

        // finish initialization

        return(_ialize(winfilename, wfilename, fdir));
    }
    ENUMERATE_FAIL((stderr, "Init bad file name: %ld\n",ERROR_INVALID_NAME))
    return(ERROR_INVALID_NAME);
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::Init, public
//
//  Synopsis:   Same as previous, except takes UNICODE file/path as input
//
//  Arguments:  IN  [filename]  - the path/file to enumerate
//              OUT [wfilename] - first file in the enumeration
//              OUT [fdir]      - TRUE = returned file is a directory
//
//----------------------------------------------------------------------------
ULONG CFileEnumerate::Init(WCHAR *filename, WCHAR **wfilename, BOOL *fdir)
{
    // Initialize the file name

    if (filename && (wcslen(filename) < MAX_PATH))
    {
        return(_ialize(filename, wfilename, fdir));
    }
    ENUMERATE_FAIL((stderr, "Init bad file name: %ld\n",ERROR_INVALID_NAME))
    return(ERROR_INVALID_NAME);
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::_ialize, private
//
//  Synopsis:   finishes initialization and starts search for first file in
//              the enumeration
//
//  Arguments:  OUT [wfilename] - first file in the enumeration
//              OUT [fdir]      - TRUE = returned file is a directory
//
//----------------------------------------------------------------------------
ULONG CFileEnumerate::_ialize(WCHAR *winfilename, WCHAR **wfilename, BOOL *fdir)
{
    ENUMERATE_RETURNS((stderr, "Init start, path =  %ws\n", winfilename))
    ULONG ret = ERROR_SUCCESS;

    ENUMERATE_STAT((stderr, "start path = %ws\n",winfilename))

    // save the location of the filename or wildcards

    ULONG cwcharcount;

    if (!(cwcharcount = GetFullPathName(winfilename,
                                       MAX_PATH,
                                       _wpath,
                                       &_pwfileposition)))
    {
        return(ERROR_INVALID_NAME);
    }

    ENUMERATE_STAT((stderr, "got full path name = %ws, filename = (%ws), total chars = %d\n",_wpath, _pwfileposition, cwcharcount))

    // if the filepart (_pwfileposition) is NULL, then the name must end in a slash.
    // add a *

    if (NULL == _pwfileposition)
    {
       _pwfileposition = (WCHAR *)Add2Ptr(_wpath,wcslen(_wpath)*sizeof(WCHAR));
    }

    // save the filename/wildcards

    wcscpy(_wwildcards, _pwfileposition);

    ENUMERATE_EXTRA((stderr, "wild cards = %ws\n",_wwildcards))

    // if we are at a root (path ends in :\)

    if ( (_wpath[wcslen(_wpath) - 1] == L'\\') &&
         (wcslen(_wpath) > 1) &&
         (_wpath[wcslen(_wpath) - 2] == L':') )
    {
        _wfd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
        _wfd.cFileName[0] = L'\0';
        *wfilename = _wpath;
        *fdir = TRUE;
        _froot = TRUE;
    } else
    {
    // check to see if we can iterate through files
        if ( (INVALID_HANDLE_VALUE == ( _handle = FindFirstFile(_wpath, &_wfd) ) ) )
        {
            ret = GetLastError();
            _fcannotaccess = (ERROR_ACCESS_DENIED == ret);

            ENUMERATE_FAIL((stderr, "find first returned: %ld\n",ret))
        }
        if (ERROR_SUCCESS == ret)
        {
            // reject . & .. filenames (go on to next file )

            if ( (0 == wcscmp(_wfd.cFileName, L".")) ||
                 (0 == wcscmp(_wfd.cFileName, L"..")) )
            {
                ret = _NextLocal(wfilename,fdir);
            } else
            {
                // return the current directory

                if (_wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                    *fdir = TRUE;
                else
                    *fdir = FALSE;

                // add the filename to the path so the whole thing is returned

                wcscpy(_pwfileposition, _wfd.cFileName);

                *wfilename = _wpath;
            }
        }

        ENUMERATE_STAT((stderr, "next filename = %ws\n", *wfilename))
    }

    // if we are going deep and we did not find a file yet:

    if ( _fdeep && ( ( ERROR_NO_MORE_FILES == ret ) ||
                     ( ERROR_FILE_NOT_FOUND == ret ) ) )
    {
        if (_handle != INVALID_HANDLE_VALUE)
        {
            FindClose(_handle);
            _handle = INVALID_HANDLE_VALUE;
        }
        ret = _InitDir(wfilename, fdir);
    }

    ENUMERATE_RETURNS((stderr, "Init returning  =  %ws(%ld)\n\n", *wfilename, ret))
    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::Next, public
//
//  Synopsis:   finds the next file in the enumeration
//
//  Arguments:  OUT [wfilename] - first file in the enumeration
//              OUT [fdir]      - TRUE = returned file is a directory
//
//----------------------------------------------------------------------------
ULONG CFileEnumerate::Next(WCHAR **wfilename, BOOL *fdir)
{
    ENUMERATE_RETURNS((stderr, "Next start, path =  %ws\n", _wpath))
    ULONG ret = ERROR_NO_MORE_FILES;

    // if we failed to initialize with an ERROR_ACCESS_DENIED, then exit
    if (_fcannotaccess)
        return(ERROR_NO_MORE_FILES);

    // if we are not in deep

    if (!_findeep)
    {
        if (!_froot)
           ret = _NextLocal(wfilename, fdir);

        // if we ran out of files and we are going deep:

        if ( _fdeep &&
             ( ( ERROR_NO_MORE_FILES == ret ) ||
               ( ERROR_FILE_NOT_FOUND == ret ) || _froot ) )
        {
            if (_handle != INVALID_HANDLE_VALUE)
            {
                FindClose(_handle);
                _handle = INVALID_HANDLE_VALUE;
            }
            ret = _InitDir(wfilename, fdir);
            _froot = FALSE; // (we are past the root now)
        }

    } else
    {
        // if we are already down a directory (and in deep)

        if (_pcfe)
        {
            if (ERROR_SUCCESS != (ret = _pcfe->Next(wfilename, fdir)))
            {
                if (ERROR_ACCESS_DENIED != ret)
                {
                    delete _pcfe;
                    _pcfe = NULL;
                }
            }
        }

        // we need to go to the next directory in the current dir

        if (ERROR_NO_MORE_FILES == ret)
        {
            ret = _NextDir(wfilename, fdir);
        }
    }
    ENUMERATE_RETURNS((stderr, "Next returning  =  %ws(%ld)\n\n", *wfilename, ret))
    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::_NextLocal, private
//
//  Synopsis:   searchs for the next file in the current directory
//
//  Arguments:  OUT [wfilename] - first file in the enumeration
//              OUT [fdir]      - TRUE = returned file is a directory
//
//----------------------------------------------------------------------------
ULONG CFileEnumerate::_NextLocal(WCHAR **wfilename, BOOL *fdir)
{
    ENUMERATE_RETURNS((stderr, "_NextLocal start, path =  %ws\n", _wpath))
    ULONG ret = ERROR_SUCCESS;

    // ensure that we have a valid handle for a findnextfile

    if (INVALID_HANDLE_VALUE == _handle)
    {
        ret = ERROR_INVALID_HANDLE;
    } else
    {
        do
        {
            if (!FindNextFile(_handle, &_wfd))
            {
                ret = GetLastError();
                ENUMERATE_FAIL((stderr, "find next returned: %ld\n",ret))
            } else
                ret = ERROR_SUCCESS;
        }
        while ( (ERROR_SUCCESS == ret) &&
                ( (0 == wcscmp(_wfd.cFileName, L".")) ||
                  (0 == wcscmp(_wfd.cFileName, L"..")) ) );

        // if we found a file

        if (ERROR_SUCCESS == ret)
        {
            // return the directory attrib.

            if (_wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                *fdir = TRUE;
            else
                *fdir = FALSE;

            // add the filename to the path so the whole thing is returned

            wcscpy(_pwfileposition, _wfd.cFileName);

            *wfilename = _wpath;

            ENUMERATE_STAT((stderr, "next filename = %ws\n", *wfilename))
        }
    }
    ENUMERATE_RETURNS((stderr, "_NextLocal returning  =  %ws(%ld)\n", *wfilename, ret))

    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::_InitDir, private
//
//  Synopsis:   (only called if going deep)
//              goes down a directory (and thus causing a new CFileEnumerator
//              to be created, or re-initializies
//
//  Arguments:  OUT [wfilename] - first file in the enumeration
//              OUT [fdir]      - TRUE = returned file is a directory
//
//----------------------------------------------------------------------------
ULONG CFileEnumerate::_InitDir(WCHAR **wfilename, BOOL *fdir)
{
    ENUMERATE_RETURNS((stderr, "_InitDir start, path =  %ws\n", _wpath))
    ULONG ret = ERROR_SUCCESS;

    // check and see if a directory was entered as the filename

    if ( (0 == _wcsicmp(_wwildcards, _wfd.cFileName)) &&
         (_wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
    {
        ENUMERATE_EXTRA((stderr, "first file matched directory = %ws\n", _wpath))
        _pwfileposition += wcslen(_wfd.cFileName);
        wcscpy(_pwfileposition, L"\\*.*");
        _pwfileposition++;
        wcscpy(_wwildcards, L"*.*");
        ENUMERATE_EXTRA((stderr, "      path = %ws\n",_wpath))
        ENUMERATE_EXTRA((stderr, "wild cards = %ws\n",_wwildcards))

        WCHAR winfilename[MAX_PATH];
        wcscpy(winfilename, _wpath);

        ret = _ialize(winfilename, wfilename, fdir);
    } else
    {

        // we are in deep

        _findeep = TRUE;

        // search thru all directories

        wcscpy(_pwfileposition, L"*.*");

        if (INVALID_HANDLE_VALUE == ( _handle = FindFirstFile(_wpath, &_wfd) ))
        {
            ret = GetLastError();
            ENUMERATE_FAIL((stderr, "find first (dir) returned: %ld\n",ret))
        } else
        {
            if ( !(_wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
                 (0 == wcscmp(_wfd.cFileName, L".")) ||
                 (0 == wcscmp(_wfd.cFileName, L"..")) )
            {
                ret = _NextDir(wfilename, fdir);
            } else
            {
                // if we have a sub directory, go down it

                ret = _DownDir(wfilename, fdir);

                // if we found nothing in that first sub directory, go the the next one

                if ( (ERROR_NO_MORE_FILES == ret ) ||
                     (ERROR_FILE_NOT_FOUND == ret ) )
                {
                    ret = _NextDir(wfilename, fdir);
                }
            }
        }
    }
    ENUMERATE_RETURNS((stderr, "_InitDir returning  =  %ws(%ld)\n", *wfilename, ret))

    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::_NextDir, private
//
//  Synopsis:   (only called if going deep)
//              finds the next sub-directory from the current directory,
//              and then goes down into that directory
//
//  Arguments:  OUT [wfilename] - first file in the enumeration
//              OUT [fdir]      - TRUE = returned file is a directory
//
//----------------------------------------------------------------------------
ULONG CFileEnumerate::_NextDir(WCHAR **wfilename, BOOL *fdir)
{
    ENUMERATE_RETURNS((stderr, "_NextDir start, path =  %ws\n", _wpath))
    ULONG ret = ERROR_SUCCESS;

    // skip the . & .. & files we cannot access

    if (INVALID_HANDLE_VALUE == _handle)
    {
        ret = ERROR_INVALID_HANDLE;
    } else
    {
        do
        {
            do
            {
                if (!FindNextFile(_handle, &_wfd))
                {
                    ret = GetLastError();
                    ENUMERATE_FAIL((stderr, "find next returned: %ld\n",ret))
                } else
                    ret = ERROR_SUCCESS;
            }
            while ( (ERROR_SUCCESS == ret) &&
                    ( !(_wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
                      (0 == wcscmp(_wfd.cFileName, L".")) ||
                      (0 == wcscmp(_wfd.cFileName, L"..")) ) );

            // if we found a directory

            if (ERROR_SUCCESS == ret)
            {
                ret = _DownDir(wfilename, fdir);
            } else
            {
                // out of subdirectories to search, break out of the loop
                break;
            }
        }
        while (( ERROR_NO_MORE_FILES == ret) || (ERROR_FILE_NOT_FOUND == ret));
    }
    ENUMERATE_RETURNS((stderr, "_NextDir returning  =  %ws(%ld)\n", *wfilename, ret))

    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileEnumerate::_DownDir, private
//
//  Synopsis:   (only called if going deep)
//              creates a new CFileEnumerator for a sub-directory
//
//  Arguments:  OUT [wfilename] - first file in the enumeration
//              OUT [fdir]      - TRUE = returned file is a directory
//
//----------------------------------------------------------------------------
ULONG CFileEnumerate::_DownDir(WCHAR **wfilename, BOOL *fdir)
{
    ENUMERATE_RETURNS((stderr, "_DownDir start, path =  %ws\n", _wpath))
    ULONG ret;

    // make a new file enumerator class (this one) We should only go down
    // 8 directories at most.

    _pcfe = new CFileEnumerate(_fdeep);

    // add the wildcards to the end of the directory we are going down

    wcscpy(_pwfileposition, _wfd.cFileName);
    wcscat(_pwfileposition, L"\\");
    wcscat(_pwfileposition, _wwildcards);

    // start it up and see if we find a match

    if (ERROR_SUCCESS != (ret = _pcfe->Init(_wpath, wfilename, fdir)))
    {
        if (ERROR_ACCESS_DENIED != ret)
        {
            delete _pcfe;
            _pcfe = NULL;
        }
    }
    ENUMERATE_RETURNS((stderr, "_DownDir returning  =  %ws(%ld)\n", *wfilename, ret))
    return(ret);
}


FILESEC.CXX

//+------------------------------------------------------------------
//
// Copyright (C) 1995, Microsoft Corporation.
//
// File:        filesec.cxx
//
// Classes:     CFileSecurity class encapsulating SECURITY_DESCRIPTOR
//
// History:     Nov-93      DaveMont         Created.
//
//-------------------------------------------------------------------

#include <filesec.hxx>
//+---------------------------------------------------------------------------
// Function:    Add2Ptr
//
// Synopsis:    Add an unscaled increment to a ptr regardless of type.
//
// Arguments:   [pv]-- Initial ptr.
//              [cb]-- Increment
//
// Returns:     Incremented ptr.
//
//----------------------------------------------------------------------------
VOID * Add2Ptr(VOID *pv, ULONG cb)
{
    return((BYTE *) pv + cb);
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileSecurity::CFileSecurity, public
//
//  Synopsis:   initializes data members
//              constructor will not throw
//
//  Arguments:  [filename] - name of file to apply security descriptor to
//
//----------------------------------------------------------------------------
CFileSecurity::CFileSecurity(WCHAR *filename)
    : _psd(NULL),
      _pwfilename(filename)
{
}
//+---------------------------------------------------------------------------
//
//  Member:     CFileSecurity::Init, public
//
//  Synopsis:   Init must be called before any other methods - this
//              is not enforced.  gets security descriptor from file
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
ULONG CFileSecurity::Init()
{
    ULONG ret;
    ULONG cpsd;

    // get the size of the security buffer

    if (!GetFileSecurity(_pwfilename,
                         DACL_SECURITY_INFORMATION |
                         GROUP_SECURITY_INFORMATION |
                         OWNER_SECURITY_INFORMATION,
                         NULL,
                         0,
                         &cpsd) )
    {
        if (ERROR_INSUFFICIENT_BUFFER == (ret = GetLastError()))
        {
            if (NULL == (_psd = (BYTE *)LocalAlloc(LMEM_FIXED, cpsd)))
            {
                 return(ERROR_NOT_ENOUGH_MEMORY);
            }

            // actually get the buffer this time

            if ( GetFileSecurity(_pwfilename,
                                 DACL_SECURITY_INFORMATION |
                                 GROUP_SECURITY_INFORMATION |
                                 OWNER_SECURITY_INFORMATION,
                                 _psd,
                                 cpsd,
                                 &cpsd) )
                ret = ERROR_SUCCESS;
            else
                ret = GetLastError();

        }
    } else
        return(ERROR_NO_SECURITY_ON_OBJECT);
    return(ret);
}
//+---------------------------------------------------------------------------
//
//  Member:     Dtor, public
//
//  Synopsis:   frees security descriptor if allocated
//
//  Arguments:  none
//
//----------------------------------------------------------------------------
CFileSecurity::~CFileSecurity()
{
    if (_psd)
    {
        LocalFree(_psd);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CFileSecurity::SetFS, public
//
//  Synopsis:   sets or modifies the security descriptor DACL on the specified file
//
//  Arguments:  IN - [fmodify] - TRUE = modify ACL, FALSE = replace ACL
//              IN - [pcdw]    - wrapper around new ACEs
//              IN - [fdir]    - TRUE = directory
//
//  Returns:    status
//
//----------------------------------------------------------------------------
ULONG CFileSecurity::SetFS(BOOL fmodify, CDaclWrap *pcdw, BOOL fdir)
{
   BOOL fdaclpresent;
   BOOL cod;
   ACL *pdacl;
   ULONG ret;

   // get the ACL from the security descriptor

   if ( GetSecurityDescriptorDacl(_psd,
                                  &fdaclpresent,
                                  &pdacl,
                                  &cod) )

   {
       if (fdaclpresent)
       {
           // build the new ACL (from the new ACEs and the old ACL)

           ACL *pnewdacl = NULL;

           if (ERROR_SUCCESS == (ret =  pcdw->BuildAcl(&pnewdacl, fmodify ? pdacl : NULL, pdacl ? pdacl->AclRevision : ACL_REVISION, fdir)))
           {
               // make a new security descriptor

               SECURITY_DESCRIPTOR newsd;
               newsd.Revision = ((SECURITY_DESCRIPTOR*) _psd)->Revision;
               newsd.Sbz1 = 0;
               newsd.Control = SE_DACL_PRESENT;
               newsd.Owner = _psd + (ULONG)((SECURITY_DESCRIPTOR*)_psd)->Owner; // convert to absolute
               newsd.Group = _psd + (ULONG)((SECURITY_DESCRIPTOR*)_psd)->Group; // convert to absolute
               newsd.Sacl  = (ACL *) (_psd + (ULONG)((SECURITY_DESCRIPTOR*)_psd)->Sacl); // convert to absolute
               newsd.Dacl = pnewdacl;

               // apply it to the file

               if (!SetFileSecurity(_pwfilename,
                                    DACL_SECURITY_INFORMATION,
                                    &newsd))
               {
                   ret = GetLastError();
               }
               LocalFree(pnewdacl);
           }
       }
       else
           return(ERROR_NO_SECURITY_ON_OBJECT);
    } else
    {
        ret = GetLastError();
    }

    return(ret);
}


ACCACC.CXX

//+------------------------------------------------------------------
//
// Copyright (C) 1995, Microsoft Corporation.
//
// File:        accacc.cxx
//
// Classes:     CAccountAccess
//
// History:     Nov-93      DaveMont         Created.
//
//-------------------------------------------------------------------
#include <accacc.hxx>
#if DBG
extern ULONG Debug;
#endif
//+---------------------------------------------------------------------------
//
//  Member:     CAccountAccess::CAccountAccess, public
//
//  Synopsis:   initializes data members, constructor will not throw
//
//  Arguments:  IN - [Name]   - principal
//              IN - [System] - server/domain
//
//----------------------------------------------------------------------------
CAccountAccess::CAccountAccess(WCHAR *Name, WCHAR *System)
    : _mask(0),
      _savemask(0),
      _foundinheritance(0),
      _acetype(0xff),
      CAccount(Name, System)
{
}
//+---------------------------------------------------------------------------
//
//  Member:     CAccountAccess::Init, public
//
//  Synopsis:   initializes access mask
//
//  Arguments:  IN [access] - access mask
//
//----------------------------------------------------------------------------
ULONG CAccountAccess::Init(ULONG access)
{
    if (access == 0)
    {
        _savemask = GENERIC_ALL;
        _mask = GENERIC_ALL;
        _acetype = ACCESS_DENIED_ACE_TYPE;
    } else
    {
        _acetype = ACCESS_ALLOWED_ACE_TYPE;
        _savemask = access;
        _mask = access;
    }
    return(ERROR_SUCCESS);
}
//+---------------------------------------------------------------------------
//
//  Member:     CAccountAccess::AddInheritance, public
//
//  Synopsis:   accumulates inheritance of ACEs with matching SIDS
//
//  Arguments:  inheritance flags
//
//--------------------------------------------------------------------
void CAccountAccess::AddInheritance(BYTE Flags)
{
    if (!(Flags & NO_PROPAGATE_INHERIT_ACE))
    {
        if (Flags & INHERIT_ONLY_ACE)
        {
            if (Flags & CONTAINER_INHERIT_ACE)
                _foundinheritance |= CONTAINER_INHERIT_ACE;
            if (Flags & OBJECT_INHERIT_ACE)
                _foundinheritance |= OBJECT_INHERIT_ACE;
        } else
        {
           _foundinheritance |= APPLIES_TO_CONTAINER;
           if (Flags & CONTAINER_INHERIT_ACE)
              _foundinheritance |= CONTAINER_INHERIT_ACE;
           if (Flags & OBJECT_INHERIT_ACE)
              _foundinheritance |= OBJECT_INHERIT_ACE;
        }
    }
}


T2.CXX

//+------------------------------------------------------------------
//
// Copyright (C) 1995, Microsoft Corporation.
//
// File:        t2.cxx
//
// Contents:
//
// Classes:
//
// History:     Nov-93      DaveMont         Created.
//
//-------------------------------------------------------------------
#include <t2.hxx>
#include <filesec.hxx>
#include <fileenum.hxx>
#include <dumpsec.hxx>
#include "caclsmsg.h"
#include <locale.h>
#include <string.h>

#if DBG
ULONG Debug;
#endif
//+----------------------------------------------------------------------------
//
// local prototypes
//
//+----------------------------------------------------------------------------
BOOLEAN OpenToken(PHANDLE ph);
void printfsid(SID *psid, ULONG *outputoffset);
void printface(ACE_HEADER *paceh, BOOL fdir, ULONG outputoffset);
void printfmask(ULONG mask, UCHAR acetype, BOOL fdir, ULONG outputoffset);
WCHAR *mbstowcs(char *aname );
BOOL GetUserAndAccess(CHAR *arg, WCHAR **user, ULONG *access);
#if DBG
ULONG DebugEnumerate(CHAR *filename, ULONG option);
#endif
ULONG DisplayAces(CHAR *filename, ULONG option);
ULONG ModifyAces(CHAR *filename,
                 MODE emode,
                 ULONG option,
                 CHAR *argv[],
                 LONG astart[], LONG aend[] );
ULONG GetCmdLineArgs(INT argc, char *argv[],
                     ULONG *option,
                     LONG astart[], LONG aend[],
                     MODE *emode
#if DBG
                     ,ULONG *debug
#endif
                     );
ULONG  printmessage (FILE* fp, DWORD messageID, ...);
//+----------------------------------------------------------------------------
//
//  Function:   Usage
//
//  Synopsis:   prints usage functionality
//
//  Arguments: none
//
//----------------------------------------------------------------------------
VOID usage()
{
    printmessage(stdout, MSG_CACLS_USAGE, NULL);

#if DBG
    if (Debug)
    {
        printf("\n   /B            deBug <[#]>\n");
        printf("                 default is display error returned\n");
        printf("                 in /B '#' is a mask: 1  display SIDS values\n");
        printf("                                      2  display access masks\n");
        printf("                                      4  display error returned\n");
        printf("                                      8  display error location\n");
        printf("                                   0x10  verbose\n");
        printf("                                   0x20  verboser\n");
        printf("                                   0x40  enumerate names\n");
        printf("                                   0x80  enumerate failures\n");
        printf("                                  0x100  enumerate starts and returns\n");
        printf("                                  0x200  enumerate extra data\n");
        printf("                                  0x400  size allocation data\n");
        printf("                                  0x800  display enumeration of files\n");
    }
#endif
}
//+----------------------------------------------------------------------------
//
//  Function:     Main, Public
//
//  Synopsis:     main!!
//
//  Arguments:    IN [argc] - cmdline arguement count
//                IN [argv] - input cmdline arguements
//
//----------------------------------------------------------------------------
VOID main(INT argc, char *argv[])
{
    char lBuf[6];

    DWORD dw = GetConsoleOutputCP();

//fix kksuzuka: #2383
//for bilingual messages
   switch (dw) {
       case 932:
       case 936:
       case 949:
       case 950:
           SetThreadLocale(
               MAKELCID( MAKELANGID( PRIMARYLANGID(GetSystemDefaultLangID()),
                                     SUBLANG_ENGLISH_US ),
                   SORT_DEFAULT ) );
           sprintf(lBuf, ".%d", dw);
           setlocale(LC_ALL, lBuf);
           break;
       default:
           SetThreadLocale(
               MAKELCID( MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US ),
                   SORT_DEFAULT ) );
           setlocale(LC_ALL,".437");
           break;
    }

    LONG astart[MAX_OPTIONS], aend[MAX_OPTIONS];
    MODE emode;

    LONG ret;
    ULONG option;

    if (ERROR_SUCCESS != (ret = GetCmdLineArgs(argc, argv,
                                               &option,
                                               astart, aend,
                                               &emode
#if DBG
                                               ,&Debug
#endif
                                               )))
    {
        usage();
        exit(ret);
    }

    switch (emode)
    {
        case MODE_DISPLAY:
            ret = DisplayAces(argv[1], option);
            break;
        case MODE_REPLACE:
        case MODE_MODIFY:
            ret = ModifyAces(argv[1], emode, option, argv, astart, aend );
            break;
#if DBG
        case MODE_DEBUG_ENUMERATE:
            ret = DebugEnumerate(argv[1], option);
            break;
#endif
        default:
        {
            usage();
            exit(1);
        }
    }
    if (ERROR_SUCCESS != ret)
    {
        LAST_ERROR((stderr, "Cacls failed, %ld\n",ret))
        printmessage(stderr, ret, NULL);
 }

    exit(ret);
}
//---------------------------------------------------------------------------
//
//  Function:     GetCmdLineArgs
//
//  Synopsis:     gets and parses command line arguments into commands
//                recognized by this program
//
//  Arguments:    IN  [argc]   - cmdline arguement count
//                IN  [argv]   - input cmdline arguements
//                OUT [option] - requested option
//                OUT [astart] - start of arguments for each option
//                OUT [aend]   - end of arguments for each option
//                OUT [emode]  - mode of operation
//                OUT [debug]  - debug mask
//
//
//----------------------------------------------------------------------------
ULONG GetCmdLineArgs(INT argc, char *argv[],
                     ULONG *option,
                     LONG astart[], LONG aend[],
                     MODE *emode
#if DBG
                     ,ULONG *debug
#endif
                     )
{
    ARG_MODE_INDEX am = ARG_MODE_INDEX_NEED_OPTION;

#if DBG
    *debug = 0;
#endif
    *emode = MODE_DISPLAY;
    *option = 0;

    for (LONG j=0; j < MAX_OPTIONS ;j++ )
    {
        astart[j] = 0;
        aend[j] = 0;
    }

    if ( (argc < 2) || (argv[1][0] == '/') )
    {
#if DBG
        // do this so debug args are printed out

        if (argc >= 2)
        {
            if ( (0 == _stricmp(&argv[1][1], "deBug")) ||
                 (0 == _stricmp(&argv[1][1], "b"))  )
            {
                *debug = DEBUG_LAST_ERROR;
            }
        }
#endif
        return(ERROR_BAD_ARGUMENTS);
    }

    for (LONG k = 2; k < argc ; k++ )
    {
        if (argv[k][0] == '/')
        {
            switch (am)
            {
                case ARG_MODE_INDEX_NEED_OPTION:
#if DBG
                case ARG_MODE_INDEX_DEBUG:
#endif
                    break;

                case ARG_MODE_INDEX_DENY:
                case ARG_MODE_INDEX_REVOKE:
                case ARG_MODE_INDEX_GRANT:
                case ARG_MODE_INDEX_REPLACE:
                    if (astart[am] == k)
                        return(ERROR_BAD_ARGUMENTS);
                    break;

                default:
                    return(ERROR_BAD_ARGUMENTS);
            }

            if ( (0 == _stricmp(&argv[k][1], "Tree")) ||
                 (0 == _stricmp(&argv[k][1], "t")) )
            {
                if (*option & OPTION_TREE)
                    return(ERROR_BAD_ARGUMENTS);
                *option |= OPTION_TREE;
                am = ARG_MODE_INDEX_NEED_OPTION;
                continue;
            }

            if ( (0 == _stricmp(&argv[k][1], "Continue")) ||
                 (0 == _stricmp(&argv[k][1], "c")) )
            {
                if (*option & OPTION_CONTINUE_ON_ERROR)
                    return(ERROR_BAD_ARGUMENTS);
                *option |= OPTION_CONTINUE_ON_ERROR;
                am = ARG_MODE_INDEX_NEED_OPTION;
                continue;
            }

            if ( (0 == _stricmp(&argv[k][1], "Edit")) ||
                 (0 == _stricmp(&argv[k][1], "E")) )
            {
                if (*emode != MODE_DISPLAY)
                    return(ERROR_BAD_ARGUMENTS);
                *emode = MODE_MODIFY;
                am = ARG_MODE_INDEX_NEED_OPTION;
                continue;
            }

#if DBG
            if ( (0 == _stricmp(&argv[k][1], "deBug")) ||
                 (0 == _stricmp(&argv[k][1], "b"))  )
            {
                if (*debug)
                    return(ERROR_BAD_ARGUMENTS);
                am = ARG_MODE_INDEX_DEBUG;
                *debug = DEBUG_LAST_ERROR;
                continue;
            }
#endif
            if ( (0 == _stricmp(&argv[k][1], "Deny")) ||
                 (0 == _stricmp(&argv[k][1], "D")) )
            {
                am = ARG_MODE_INDEX_DENY;
                *option |= OPTION_DENY;
            } else if ( (0 == _stricmp(&argv[k][1], "Revoke")) ||
                        (0 == _stricmp(&argv[k][1], "R")) )
            {
                am = ARG_MODE_INDEX_REVOKE;
                *option |= OPTION_REVOKE;
            } else if ( (0 == _stricmp(&argv[k][1], "Grant")) ||
                        (0 == _stricmp(&argv[k][1], "G")) )
            {
                am = ARG_MODE_INDEX_GRANT;
                *option |= OPTION_GRANT;
            } else if ( (0 == _stricmp(&argv[k][1], "rePlace")) ||
                        (0 == _stricmp(&argv[k][1], "P")) )
            {
                *option |= OPTION_REPLACE;
                am = ARG_MODE_INDEX_REPLACE;
            } else
                return(ERROR_BAD_ARGUMENTS);

            if (astart[am] != 0)
                return(ERROR_BAD_ARGUMENTS);
            astart[am] = k+1;
        } else
        {
            switch (am)
            {
                case ARG_MODE_INDEX_NEED_OPTION:
                    return(ERROR_BAD_ARGUMENTS);

#if DBG
                case ARG_MODE_INDEX_DEBUG:
                    *debug = atol(argv[k]);
                    if (*debug & DEBUG_ENUMERATE)
                        if (*emode == MODE_DISPLAY)
                            *emode = MODE_DEBUG_ENUMERATE;
                        else
                            return(ERROR_BAD_ARGUMENTS);

                    am = ARG_MODE_INDEX_NEED_OPTION;
                    break;
#endif
                case ARG_MODE_INDEX_DENY:
                case ARG_MODE_INDEX_REVOKE:
                case ARG_MODE_INDEX_GRANT:
                case ARG_MODE_INDEX_REPLACE:
                    aend[am] = k+1;
                    break;

                default:
                    return(ERROR_BAD_ARGUMENTS);
            }
        }
    }

    if ( ( (*option & OPTION_DENY) && (aend[ARG_MODE_INDEX_DENY] == 0) ) ||
         ( (*option & OPTION_REVOKE) && (aend[ARG_MODE_INDEX_REVOKE] == 0) ) ||
         ( (*option & OPTION_GRANT) && (aend[ARG_MODE_INDEX_GRANT] == 0) ) ||
         ( (*option & OPTION_REPLACE) && (aend[ARG_MODE_INDEX_REPLACE] == 0) ) )
    {
        return(ERROR_BAD_ARGUMENTS);
    } else if ( (*option & OPTION_DENY) ||
                (*option & OPTION_REVOKE) ||
                (*option & OPTION_GRANT) ||
                (*option & OPTION_REPLACE) )
    {
        if (*emode == MODE_DISPLAY)
        {
            if (*option & OPTION_REVOKE)
            {
                return(ERROR_BAD_ARGUMENTS);
            }
            *emode = MODE_REPLACE;
        }
    }
    return(ERROR_SUCCESS);
}

//---------------------------------------------------------------------------
//
//  Function:     DisplayAces
//
//  Synopsis:     displays ACL from specified file
//
//  Arguments:    IN [filename] - file name
//                IN [option]   - display option
//
//----------------------------------------------------------------------------
ULONG DisplayAces(CHAR *filename, ULONG option)
{
    CFileEnumerate cfe(option & OPTION_TREE);
    WCHAR *pwfilename;
    BOOL fdir;
    ULONG ret;

    if (NO_ERROR == (ret = cfe.Init(filename, &pwfilename, &fdir)))
    {
        while ( (NO_ERROR == ret) ||
                ( ( (ERROR_ACCESS_DENIED == ret ) || (ERROR_SHARING_VIOLATION == ret) )&&
                  (option & OPTION_CONTINUE_ON_ERROR) ) )
        {
#if DBG
            if (fdir)
                DISPLAY((stderr, "processing file: "))
            else
                DISPLAY((stderr, "processing dir: "))
#endif
            wprintf(L"%s",  pwfilename);
            if (ERROR_ACCESS_DENIED == ret)
            {
                printmessage(stdout,MSG_CACLS_ACCESS_DENIED, NULL);
            } else if (ERROR_SHARING_VIOLATION == ret)
            {
                printmessage(stdout,MSG_CACLS_SHARING_VIOLATION, NULL);
            } else
            {
                DISPLAY((stderr, "\n"))
                VERBOSE((stderr, "\n"))
                CDumpSecurity cds(pwfilename);

                if (NO_ERROR == (ret = cds.Init()))
                {
#if DBG
                    if (Debug & DEBUG_VERBOSE)
                    {
                        SID *psid;
                        ULONG oo;

                        if (NO_ERROR == (ret = cds.GetSDOwner(&psid)))
                        {
                            printf("  Owner = ");
                            printfsid(psid, &oo);
                            if (NO_ERROR == (ret = cds.GetSDGroup(&psid)))
                            {
                                printf("  Group = ");
                                printfsid(psid, &oo);
                            }
                            else
                                ERRORS((stderr, "GetSDGroup failed, %d\n",ret))
                        }
                        else
                            ERRORS((stderr, "GetSDOwner failed, %d\n",ret))
                    }
#endif
                    ACE_HEADER *paceh;

                    LONG retace;
                    if (NO_ERROR == ret)
                        for (retace = cds.GetNextAce(&paceh); retace >= 0; )
                        {
                            printface(paceh, fdir, wcslen(pwfilename));
                            retace = cds.GetNextAce(&paceh);
                            if (retace >= 0)
                                printf("%*s",
                                       WideCharToMultiByte(CP_ACP, 0,
                                               pwfilename, -1,
                                               NULL, 0,
                                               NULL, NULL)-1," ");
                        }
                }
#if DBG
                   else
                    ERRORS((stderr, "cds.init failed, %d\n",ret))
#endif
            }
            fprintf(stdout, "\n");

            if ( (NO_ERROR == ret) ||
                ( ( (ERROR_ACCESS_DENIED == ret ) || (ERROR_SHARING_VIOLATION == ret) )&&
                   (option & OPTION_CONTINUE_ON_ERROR) ) )
                ret = cfe.Next(&pwfilename, &fdir);
        }

        switch (ret)
        {
            case ERROR_NO_MORE_FILES:
                ret = ERROR_SUCCESS;
                break;
            case ERROR_ACCESS_DENIED:
            case ERROR_SHARING_VIOLATION:
                break;
            case ERROR_SUCCESS:
                break;
            default:
                break;
        }

    } else
    {
        ERRORS((stderr, "cfe.init failed, %d\n",ret))
    }
    return(ret);
}
//---------------------------------------------------------------------------
//
//  Function:     ModifyAces
//
//  Synopsis:     modifies the aces for the specified file(s)
//
//  Arguments:    IN [filename] - name of file(s) to modify the aces on
//                IN [emode]  - mode of operation
//                IN [option] - requested option
//                IN [astart] - start of arguments for each option
//                IN [aend]   - end of arguments for each option
//
//----------------------------------------------------------------------------
ULONG ModifyAces(CHAR *filename,
                 MODE emode,
                 ULONG option,
                 CHAR *argv[],
                 LONG astart[], LONG aend[])
{
    CDaclWrap cdw;
    CFileEnumerate cfe(option & OPTION_TREE);
    WCHAR *user = NULL;
    ULONG access;
    ULONG ret = ERROR_SUCCESS;
    WCHAR *pwfilename;
    ULONG curoption;

    VERBOSER((stderr, "user:permission pairs\n"))

    // first proces the command line args to build up the new ace

    for (ULONG j = 0, k = 1;j < MAX_OPTIONS ; k <<= 1, j++ )
    {
        curoption = k;
        if (option & k)
        {
            for (LONG q = astart[j];
                      q < aend[j] ; q++ )
            {
                VERBOSER((stderr, "      %s\n",argv[q]))

                if ((k & OPTION_GRANT) || (k & OPTION_REPLACE))
                {
                    if (!GetUserAndAccess(argv[q], &user, &access))
                    {
                        if (user)
                            LocalFree(user);
                        return(ERROR_BAD_ARGUMENTS);
                    }
                    if (GENERIC_NONE == access)
                    {
                        if (!(k & OPTION_REPLACE))
                        {
                            if (user)
                                LocalFree(user);
                            return(ERROR_BAD_ARGUMENTS);
                        }
                    }
                } else
                {
                    user = mbstowcs(argv[q]);
                    access = GENERIC_NONE;
                }

                VERBOSER((stderr, "OPTION = %d, USER = %ws, ACCESS = %lx\n",
                       option,
                       user,
                       access))

                if (ERROR_SUCCESS != (ret = cdw.SetAccess(curoption,
                                                     user,
                                                     NULL,
                                                     access)))
                {
                    ERRORS((stderr, "SetAccess for %ws:%lx failed, %d\n",
                           user,
                           access,
                           ret))

                    LocalFree(user);
                    return(ret);
                }
                LocalFree(user);
                user = NULL;
            }
        }
    }

    BOOL fdir;

    if (emode == MODE_REPLACE)
    {
        CHAR well[MAX_PATH];
        CHAR msgbuf[MAX_PATH];
        printmessage(stdout,MSG_CACLS_ARE_YOU_SURE, NULL);
        FormatMessageA(FORMAT_MESSAGE_FROM_HMODULE, NULL, MSG_CACLS_Y, 0,
                      msgbuf, MAX_PATH, NULL);
        fgets(well,MAX_PATH,stdin);

        // remove the trailing return
        if ('\n' == well[strlen(well) - sizeof(CHAR)])
            well[strlen(well) - sizeof(CHAR)] = '\0';

        if (0 != _stricmp(well, msgbuf))
        {
            FormatMessageA(FORMAT_MESSAGE_FROM_HMODULE, NULL, MSG_CACLS_YES, 0,
                          msgbuf, MAX_PATH, NULL);
            if (0 != _stricmp(well, msgbuf))
                return(ERROR_SUCCESS);
        }
    }

    if (NO_ERROR == (ret = cfe.Init(filename, &pwfilename, &fdir)))
    {
        while ( (NO_ERROR == ret) ||
                ( ( (ERROR_ACCESS_DENIED == ret ) || (ERROR_SHARING_VIOLATION == ret) )&&
                  (option & OPTION_CONTINUE_ON_ERROR) ) )
        {
            CFileSecurity cfs(pwfilename);

            if (NO_ERROR == (ret = cfs.Init()))
            {
                if (NO_ERROR != (ret = cfs.SetFS(emode == MODE_REPLACE ? FALSE : TRUE, &cdw, fdir)))
                {
                    if (!(((ERROR_ACCESS_DENIED == ret) || (ERROR_SHARING_VIOLATION == ret)) &&
                          (option & OPTION_CONTINUE_ON_ERROR)))
                    {
                        ERRORS((stderr, "SetFS on %ws failed %ld\n",pwfilename, ret))
                        return(ret);
                    }
                }
            }
            else
            {
               //
               // If the error is access denied or sharing violation and we are to continue on error,
               // then keep going. Otherwise bail out here.
               //

               if (!(((ERROR_ACCESS_DENIED == ret) || (ERROR_SHARING_VIOLATION == ret)) &&
                   (option & OPTION_CONTINUE_ON_ERROR))) {

                  ERRORS((stderr, "init failed, %d\n",ret))
                  return(ret);
               }
            }

            if (NO_ERROR == ret)
            {

                if (fdir)
                {
                    printmessage(stdout, MSG_CACLS_PROCESSED_DIR, NULL);
                    wprintf(L"%s",  pwfilename);
                }
                else
                {
                    printmessage(stdout, MSG_CACLS_PROCESSED_FILE, NULL);
                    wprintf(L"%s",  pwfilename);
                }
            }
            else if (ERROR_ACCESS_DENIED == ret)
            {
                printmessage(stdout, MSG_CACLS_ACCESS_DENIED, NULL);
                wprintf(L"%s",  pwfilename);
            }
            else if (ret == ERROR_SHARING_VIOLATION)
            {
                printmessage(stdout, MSG_CACLS_SHARING_VIOLATION, NULL);
                wprintf(L"%s",  pwfilename);
            }

            if ( (NO_ERROR == ret) ||
                 ( ( (ERROR_ACCESS_DENIED == ret ) || (ERROR_SHARING_VIOLATION == ret) ) &&
                   (option & OPTION_CONTINUE_ON_ERROR) ) )
                ret = cfe.Next(&pwfilename, &fdir);
        }

        switch (ret)
        {
            case ERROR_NO_MORE_FILES:
                ret = ERROR_SUCCESS;
                break;
            case ERROR_ACCESS_DENIED:
            case ERROR_SHARING_VIOLATION:
                break;
            case ERROR_SUCCESS:
                break;
            default:
                DISPLAY((stderr, "%ws failed: %d\n", pwfilename, ret))
                break;
        }
    } else
        ERRORS((stderr, "file enumeration failed to initialize %ws, %ld\n",pwfilename, ret))

    if (ret == ERROR_NO_MORE_FILES)
    {
        ret = ERROR_SUCCESS;
    }

    if (ret != ERROR_SUCCESS)
    {
        ERRORS((stderr, "Enumeration failed, %d\n",ret))
    }

    return(ret);
}
#if DBG
//---------------------------------------------------------------------------
//
//  Function:     DebugEnumerate
//
//  Synopsis:     debug function
//
//  Arguments:    IN [filename] - file name
//                IN [option]   - option
//
//----------------------------------------------------------------------------
ULONG DebugEnumerate(CHAR *filename, ULONG option)
{
    CFileEnumerate cfe(option & OPTION_TREE);
    WCHAR *pwfilename;
    BOOL fdir;
    ULONG ret;

    ret = cfe.Init(filename, &pwfilename, &fdir);
    while ( (ERROR_SUCCESS == ret) ||
            ( (ERROR_ACCESS_DENIED == ret ) &&
              (option & OPTION_CONTINUE_ON_ERROR) ) )
    {
        if (fdir)
            printf("dir  name = %ws%ws\n",pwfilename,
                   ERROR_ACCESS_DENIED == ret ? L"ACCESS DENIED" : L"");
        else
            printf("file name = %ws%ws\n",pwfilename,
                   ERROR_ACCESS_DENIED == ret ? L"ACCESS DENIED" : L"");
        ret = cfe.Next(&pwfilename, &fdir);
    }
    if (ret == ERROR_ACCESS_DENIED)
    {
        if (fdir)
            printf("dir  name = %ws%ws\n",pwfilename,
                   ERROR_ACCESS_DENIED == ret ? L"ACCESS DENIED" : L"");
        else
            printf("file name = %ws%ws\n",pwfilename,
                   ERROR_ACCESS_DENIED == ret ? L"ACCESS DENIED" : L"");
    }
    if (ret != ERROR_NO_MORE_FILES)
        printf("Enumeration failed, %d\n",ret);

    return(ret);
}
#endif
//---------------------------------------------------------------------------
//
//  Function:     GetUserAccess
//
//  Synopsis:     parses an input string for user:access
//
//  Arguments:    IN  [arg]    - input string to parse
//                OUT [user]   - user if found
//                OUT [access] - access if found
//
//----------------------------------------------------------------------------
BOOL GetUserAndAccess(CHAR *arg, WCHAR **user, ULONG *access)
{
    CHAR *saccess = strchr(arg,':');
    if (saccess)
    {
        *saccess = NULL;
        saccess++;

        if (strchr(saccess,':'))
            return(FALSE);
        *user = mbstowcs(arg);

        if (0 == _stricmp(saccess,"F"))
        {
            *access = ( STANDARD_RIGHTS_ALL |
                        FILE_READ_DATA |
                        FILE_WRITE_DATA |
                        FILE_APPEND_DATA |
                        FILE_READ_EA |
                        FILE_WRITE_EA |
                        FILE_EXECUTE |
                        FILE_DELETE_CHILD |
                        FILE_READ_ATTRIBUTES |
                        FILE_WRITE_ATTRIBUTES );
        }
        else if (0 == _stricmp(saccess,"R"))
        {
            *access = FILE_GENERIC_READ | FILE_EXECUTE;
        }
        else if (0 == _stricmp(saccess,"C"))
        {
            *access = FILE_GENERIC_WRITE | FILE_GENERIC_READ | FILE_EXECUTE | DELETE;
        }
        else if (0 == _stricmp(saccess,"N"))
        {
            *access = GENERIC_NONE;
        }
        else if (0 == _stricmp(saccess,"W"))
        {
            *access = FILE_GENERIC_WRITE | FILE_EXECUTE;
        }
        else
            return(FALSE);
        return(TRUE);
    }
    return(FALSE);
}
//---------------------------------------------------------------------------
//
//  Function:     mbstowcs
//
//  Synopsis:     converts char to wchar, allocates space for wchar
//
//  Arguments:    IN [aname] - char string
//
//----------------------------------------------------------------------------
WCHAR *mbstowcs(char *aname )
{
    if (aname)
    {
        WCHAR *pwname = NULL;
        pwname = (WCHAR *)LocalAlloc(LMEM_FIXED, sizeof(WCHAR) * (strlen(aname)+1));
        if (NULL == pwname)
            return(NULL);
        WCHAR *prwname = pwname;
        if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,
                                aname, -1,
                                prwname, sizeof(WCHAR)*(strlen(aname)+1)) == 0)
            return(NULL);
        return(pwname);
    } else
        return(NULL);
}
//----------------------------------------------------------------------------
//
//  Function:
//
//  Synopsis:
//
//  Arguments:
//
//----------------------------------------------------------------------------
BOOLEAN OpenToken(PHANDLE ph)
{
    HANDLE hprocess;

    hprocess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
    if (hprocess == NULL)
        return(FALSE);

    if (OpenProcessToken(hprocess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ph))
    {
        CloseHandle(hprocess);
        return(TRUE);
    }

    CloseHandle(hprocess);
    return(FALSE);
}
//----------------------------------------------------------------------------
//
//  Function:     printfsid
//
//  Synopsis:     prints a NT SID
//
//  Arguments:    IN [psid] - pointer to the sid to print
//
//----------------------------------------------------------------------------
void printfsid(SID *psid, ULONG *outputoffset)
{
#if DBG
    if ((Debug & DEBUG_VERBOSE) || (Debug & DEBUG_DISPLAY_SIDS))
    {
        printf("S-%lx",psid->Revision);

        if ( (psid->IdentifierAuthority.Value[0] != 0) ||
             (psid->IdentifierAuthority.Value[1] != 0) )
        {
            printf("0x%02hx%02hx%02hx%02hx%02hx%02hx",
                        (USHORT)psid->IdentifierAuthority.Value[0],
                        (USHORT)psid->IdentifierAuthority.Value[1],
                        (USHORT)psid->IdentifierAuthority.Value[2],
                        (USHORT)psid->IdentifierAuthority.Value[3],
                        (USHORT)psid->IdentifierAuthority.Value[4],
                        (USHORT)psid->IdentifierAuthority.Value[5] );
        } else
        {
            printf("-%lu",
                   (ULONG)psid->IdentifierAuthority.Value[5]          +
                   (ULONG)(psid->IdentifierAuthority.Value[4] <<  8)  +
                   (ULONG)(psid->IdentifierAuthority.Value[3] << 16)  +

(ULONG)(psid->IdentifierAuthority.Value[2] << 24) );
        }

        if ( 0 < psid->SubAuthorityCount )
        {
            for (int k = 0; k < psid->SubAuthorityCount; k++ )
            {
                printf("-%d",psid->SubAuthority[k]);
            }
        }
    }
#endif
    ULONG ret;

    CAccount ca(psid, NULL);

    WCHAR *domain = NULL;
    WCHAR *user;

    if (NO_ERROR == ( ret = ca.GetAccountDomain(&domain) ) )
    {
        if ( (NULL == domain) || (0 == wcslen(domain)) )
        {
            fprintf(stdout, " ");
            *outputoffset +=1;
        }
        else
        {
            fprintf(stdout, " ");
            wprintf(L"%s",  domain);
fprintf(stdout, "\\");
            *outputoffset += 2 + wcslen( domain );;
        }

        if (NO_ERROR == ( ret = ca.GetAccountName(&user) ) )
        {
            wprintf(L"%s",  user);
fprintf(stdout, ":");
            *outputoffset += 1 + wcslen(user);
        } else
        {
            *outputoffset += printmessage(stdout, MSG_CACLS_NAME_NOT_FOUND, NULL);

            ERRORS((stderr, "(%lx)",ret))
        }
    } else
    {
        *outputoffset+= printmessage(stdout, MSG_CACLS_DOMAIN_NOT_FOUND, NULL);
        ERRORS((stderr, "(%lx)",ret))
    }
    VERBOSE((stderr, "\n"))
}
//----------------------------------------------------------------------------
//
//  Function:     printface
//
//  Synopsis:     prints the specifed ace
//
//  Arguments:    IN [paceh] - input ace (header)
//                IN [fdir]  - TRUE = directory (different display options)
//
//----------------------------------------------------------------------------
void printface(ACE_HEADER *paceh, BOOL fdir, ULONG outputoffset)
{
    VERBOSE((stderr, "  "))
    VERBOSER((stderr, "\npaceh->AceType  = %x\n",paceh->AceType  ))
    VERBOSER((stderr, "paceh->AceFlags = %x\n",paceh->AceFlags ))
    VERBOSER((stderr, "paceh->AceSize  = %x\n",paceh->AceSize  ))
    ACCESS_ALLOWED_ACE *paaa = (ACCESS_ALLOWED_ACE *)paceh;
    printfsid((SID *)&(paaa->SidStart),&outputoffset);
    if (paceh->AceFlags & OBJECT_INHERIT_ACE      )
    {
        outputoffset+= printmessage(stdout, MSG_CACLS_OBJECT_INHERIT, NULL);
    }
    if (paceh->AceFlags & CONTAINER_INHERIT_ACE   )
    {
        outputoffset+= printmessage(stdout, MSG_CACLS_CONTAINER_INHERIT, NULL);
    }
    if (paceh->AceFlags & NO_PROPAGATE_INHERIT_ACE)
    {
        outputoffset+= printmessage(stdout, MSG_CACLS_NO_PROPAGATE_INHERIT, NULL);
    }
    if (paceh->AceFlags & INHERIT_ONLY_ACE        )
    {
        outputoffset+= printmessage(stdout, MSG_CACLS_INHERIT_ONLY, NULL);
    }

    if (paceh->AceType == ACCESS_DENIED_ACE_TYPE)
    {
            DISPLAY_MASK((stderr, "(DENIED)"))
            VERBOSE((stderr, "(DENIED)"))
    }

    printfmask(paaa->Mask, paceh->AceType, fdir, outputoffset);
    fprintf(stdout, "\n");
}
//----------------------------------------------------------------------------
//
//  Function:     printfmask
//
//  Synopsis:     prints the access mask
//
//  Arguments:    IN [mask]    - the access mask
//                IN [acetype] -  allowed/denied
//                IN [fdir]    - TRUE = directory
//
//----------------------------------------------------------------------------
CHAR  *aRightsStr[] = { "STANDARD_RIGHTS_ALL",
                        "DELETE",
                        "READ_CONTROL",
                        "WRITE_DAC",
                        "WRITE_OWNER",
                        "SYNCHRONIZE",
                        "STANDARD_RIGHTS_REQUIRED",
                        "SPECIFIC_RIGHTS_ALL",
                        "ACCESS_SYSTEM_SECURITY",
                        "MAXIMUM_ALLOWED",
                        "GENERIC_READ",
                        "GENERIC_WRITE",
                        "GENERIC_EXECUTE",
                        "GENERIC_ALL",
                        "FILE_GENERIC_READ",
                        "FILE_GENERIC_WRITE",
                        "FILE_GENERIC_EXECUTE",
                        "FILE_READ_DATA",
                        //FILE_LIST_DIRECTORY
                        "FILE_WRITE_DATA",
                        //FILE_ADD_FILE
                        "FILE_APPEND_DATA",
                        //FILE_ADD_SUBDIRECTORY
                        "FILE_READ_EA",
                        "FILE_WRITE_EA",
                        "FILE_EXECUTE",
                        //FILE_TRAVERSE
                        "FILE_DELETE_CHILD",
                        "FILE_READ_ATTRIBUTES",
                        "FILE_WRITE_ATTRIBUTES" };

#define NUMRIGHTS 26
ULONG aRights[NUMRIGHTS] = { STANDARD_RIGHTS_ALL  ,
                         DELETE                   ,
                         READ_CONTROL             ,
                         WRITE_DAC                ,
                         WRITE_OWNER              ,
                         SYNCHRONIZE              ,
                         STANDARD_RIGHTS_REQUIRED ,
                         SPECIFIC_RIGHTS_ALL      ,
                         ACCESS_SYSTEM_SECURITY   ,
                         MAXIMUM_ALLOWED          ,
                         GENERIC_READ             ,
                         GENERIC_WRITE            ,
                         GENERIC_EXECUTE          ,
                         GENERIC_ALL              ,
                         FILE_GENERIC_READ        ,
                         FILE_GENERIC_WRITE       ,
                         FILE_GENERIC_EXECUTE     ,
                         FILE_READ_DATA           ,
                         //FILE_LIST_DIRECTORY    ,
                         FILE_WRITE_DATA          ,
                         //FILE_ADD_FILE          ,
                         FILE_APPEND_DATA         ,
                         //FILE_ADD_SUBDIRECTORY  ,
                         FILE_READ_EA             ,
                         FILE_WRITE_EA            ,
                         FILE_EXECUTE             ,
                         //FILE_TRAVERSE          ,
                         FILE_DELETE_CHILD        ,
                         FILE_READ_ATTRIBUTES     ,
                         FILE_WRITE_ATTRIBUTES  };

void printfmask(ULONG mask, UCHAR acetype, BOOL fdir, ULONG outputoffset)
{
    ULONG savmask = mask;
    VERBOSER((stderr, "mask = %08lx ", mask))
    DISPLAY_MASK((stderr, "mask = %08lx\n", mask))

    VERBOSE((stderr, "    "))

#if DBG
    if (!(Debug & (DEBUG_VERBOSE | DEBUG_DISPLAY_MASK)))
    {
#endif
        if ((acetype == ACCESS_ALLOWED_ACE_TYPE) &&
                   (mask == (FILE_GENERIC_READ | FILE_EXECUTE)))
        {
            printmessage(stdout, MSG_CACLS_READ, NULL);
        } else if ((acetype == ACCESS_ALLOWED_ACE_TYPE) &&
                   (mask == (FILE_GENERIC_WRITE | FILE_GENERIC_READ | FILE_EXECUTE | DELETE)))
        {
            printmessage(stdout, MSG_CACLS_CHANGE, NULL);
        } else if ((acetype == ACCESS_ALLOWED_ACE_TYPE) &&
                   (mask == (GENERIC_WRITE | GENERIC_READ | GENERIC_EXECUTE | DELETE)))
        {
            printmessage(stdout, MSG_CACLS_CHANGE, NULL);
        } else if ((acetype == ACCESS_ALLOWED_ACE_TYPE) &&
                   (mask ==  ( STANDARD_RIGHTS_ALL |
                             FILE_READ_DATA |
                             FILE_WRITE_DATA |
                             FILE_APPEND_DATA |
                             FILE_READ_EA |
                             FILE_WRITE_EA |
                             FILE_EXECUTE |
                             FILE_DELETE_CHILD |
                             FILE_READ_ATTRIBUTES |
                             FILE_WRITE_ATTRIBUTES )) )
        {
            printmessage(stdout, MSG_CACLS_FULL_CONTROL, NULL);
        } else if ((acetype == ACCESS_ALLOWED_ACE_TYPE) &&
                   (mask ==  GENERIC_ALL))
        {
            printmessage(stdout, MSG_CACLS_FULL_CONTROL, NULL);
        } else if ((acetype == ACCESS_DENIED_ACE_TYPE) &&
                   (mask == GENERIC_ALL))
        {
            printmessage(stdout, MSG_CACLS_NONE, NULL);
        } else if ((acetype == ACCESS_DENIED_ACE_TYPE) &&
                   (mask ==  ( STANDARD_RIGHTS_ALL |
                             FILE_READ_DATA |
                             FILE_WRITE_DATA |
                             FILE_APPEND_DATA |
                             FILE_READ_EA |
                             FILE_WRITE_EA |
                             FILE_EXECUTE |
                             FILE_DELETE_CHILD |
                             FILE_READ_ATTRIBUTES |
                             FILE_WRITE_ATTRIBUTES )) )
        {
            printmessage(stdout, MSG_CACLS_NONE, NULL);
        } else
        {
            if (acetype == ACCESS_DENIED_ACE_TYPE)
                printmessage(stdout, MSG_CACLS_DENY, NULL);

            printmessage(stdout, MSG_CACLS_SPECIAL_ACCESS, NULL);

            for (int k = 0; k<NUMRIGHTS ; k++ )
            {
                if ((mask & aRights[k]) == aRights[k])
                {
                    fprintf(stdout, "%*s%s\n",outputoffset, " ", aRightsStr[k]);
                }
                if (mask == 0)
                    break;
            }
        }
#if DBG
    } else
    {
        if (Debug & (DEBUG_DISPLAY_MASK | DEBUG_VERBOSE))
        {
            printf("\n");
            for (int k = 0; k<NUMRIGHTS ; k++ )
            {
                if ((mask & aRights[k]) == aRights[k])
                {
                    if (mask != savmask) printf(" |\n");
                    printf("    %s",aRightsStr[k]);
                    mask &= ~aRights[k];
                }
                if (mask == 0)
                break;
            }
        }
        VERBOSE((stderr, "=%x",mask))
        if (mask != 0)
            DISPLAY((stderr, "=%x/%x",mask,savmask))
    }
#endif
    fprintf(stdout, " ");
}
//----------------------------------------------------------------------------
//
//  Function:     printmessage
//
//  Synopsis:     prints a message, either from the local message file, or from the system
//
//  Arguments:    IN [fp]    - stderr, stdio, etc.
//                IN [messageID] - variable argument list
//
//  Returns:      length of the output buffer
//
//----------------------------------------------------------------------------
ULONG  printmessage (FILE* fp, DWORD messageID, ...)
{
    WCHAR  messagebuffer[4096];
        va_list ap;

    va_start(ap, messageID);

    if (!FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, NULL, messageID, 0,
                      messagebuffer, 4095, &ap))
       FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, messageID, 0,
                        messagebuffer, 4095, &ap);

    fwprintf(fp,  messagebuffer);

    va_end(ap);
    return(wcslen(messagebuffer));
}


README.TXT

Display and Alter the Compression of Files or Directories

The COMPACT tool compresses files on an NTFS drive.

Usage:

COMPACT [/C | /U] [/S[:dir]] [/A] [/I] [/Q] [filename [...]]

/C       Compresses the specified directory or file.
/U       Uncompress the specified directory or file.
/S       Performs the specified operation on matching files in the
         given directory and all subdirectories. Default "dir" is
         the current directory.
/A       Do not ignore hidden or system files.
/I       Ignore errors.
/F       Force the operation to compress or uncompress the
         specified directory or file.
/Q       Be less verbose.
filename Specifies a pattern, file, or directory.

When used without parameters, COMPACT displays the compression state of the
current directory. You may use multiple filenames and wildcards.


COMPACT.C

/*++

Copyright (c) 1994-1995  Microsoft Corporation

Module Name:

    Compact.c

Abstract:

    This module implements the double stuff utility for compressed NTFS
    volumes.

Author:

    Gary Kimura     [garyki]        10-Jan-1994

Revision History:

--*/

//
// Include the standard header files.
//

#define UNICODE
#define _UNICODE

#include <stdio.h>
#include <windows.h>
#include <winioctl.h>
#include <shellapi.h>

#include "support.h"
#include "msg.h"

#define lstrchr wcschr
#define lstricmp _wcsicmp
#define lstrnicmp _wcsnicmp

//
//  FIRST_COLUMN_WIDTH - When compressing files, the width of the output
//  column which displays the file name
//

#define FIRST_COLUMN_WIDTH  (20)

//
//  Local procedure types
//

typedef BOOLEAN (*PACTION_ROUTINE) (
    IN PTCHAR DirectorySpec,
    IN PTCHAR FileSpec
    );

typedef VOID (*PFINAL_ACTION_ROUTINE) (
    );

//
//  Declare global variables to hold the command line information
//

BOOLEAN DoSubdirectories      = FALSE;      // recurse
BOOLEAN IgnoreErrors          = FALSE;      // keep going despite errs
BOOLEAN UserSpecifiedFileSpec = FALSE;
BOOLEAN ForceOperation        = FALSE;      // compress even if already so
BOOLEAN Quiet                 = FALSE;      // be less verbose
BOOLEAN DisplayAllFiles       = FALSE;      // dsply hidden, system?
TCHAR   StartingDirectory[MAX_PATH];        // parameter to "/s"
ULONG   AttributesNoDisplay = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;

//
//  Declere global variables to hold compression statistics
//

LARGE_INTEGER TotalDirectoryCount;
LARGE_INTEGER TotalFileCount;
LARGE_INTEGER TotalCompressedFileCount;
LARGE_INTEGER TotalUncompressedFileCount;

LARGE_INTEGER TotalFileSize;
LARGE_INTEGER TotalCompressedSize;

TCHAR Buf[1024];                            // for displaying stuff

HANDLE
OpenFileForCompress(
    IN      PTCHAR      ptcFile
    )
/*++

Routine Description:

    This routine jumps through the hoops necessary to open the file
    for READ_DATA|WRITE_DATA even if the file has the READONLY
    attribute set.

Arguments:

    ptcFile     - Specifies the file that should be opened.

Return Value:

    A handle open on the file if successfull, INVALID_HANDLE_VALUE
    otherwise, in which case the caller may use GetLastError() for more
    info.

--*/
{
    BY_HANDLE_FILE_INFORMATION fi;
    HANDLE hRet;
    HANDLE h;
    INT err;

    hRet = CreateFile(
                ptcFile,
                FILE_READ_DATA | FILE_WRITE_DATA,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
                NULL
                );

    if (INVALID_HANDLE_VALUE != hRet) {
        return hRet;
    }

    if (ERROR_ACCESS_DENIED != GetLastError()) {
        return INVALID_HANDLE_VALUE;
    }

    err = GetLastError();

    h = CreateFile(
            ptcFile,
            FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
            NULL
            );

    if (INVALID_HANDLE_VALUE == h) {
        return INVALID_HANDLE_VALUE;
    }

    if (!GetFileInformationByHandle(h, &fi)) {
        CloseHandle(h);
        return INVALID_HANDLE_VALUE;
    }

    if ((fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) {

        // If we couldn't open the file for some reason other than that
        // the readonly attribute was set, fail.

        SetLastError(err);
        CloseHandle(h);
        return INVALID_HANDLE_VALUE;
    }

    fi.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;

    if (!SetFileAttributes(ptcFile, fi.dwFileAttributes)) {
        CloseHandle(h);
        return INVALID_HANDLE_VALUE;
    }

    hRet = CreateFile(
            ptcFile,
            FILE_READ_DATA | FILE_WRITE_DATA,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
            NULL
            );

    CloseHandle(h);

    if (INVALID_HANDLE_VALUE == hRet) {
        return INVALID_HANDLE_VALUE;
    }

    fi.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;

    if (!SetFileAttributes(ptcFile, fi.dwFileAttributes)) {
        CloseHandle(hRet);
        return INVALID_HANDLE_VALUE;
    }

    return hRet;
}

//
//  Now do the routines to list the compression state and size of
//  a file and/or directory
//

BOOLEAN
DisplayFile (
    IN PTCHAR FileSpec,
    IN PWIN32_FIND_DATA FindData
    )
{
    LARGE_INTEGER FileSize;
    LARGE_INTEGER CompressedSize;
    TCHAR PrintState;

    ULONG Percentage = 100;
    double Ratio = 1.0;

    FileSize.LowPart = FindData->nFileSizeLow;
    FileSize.HighPart = FindData->nFileSizeHigh;
    PrintState = ' ';

    //
    //  Decide if the file is compressed and if so then
    //  get the compressed file size.
    //

    if (FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {

        CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
            &CompressedSize.HighPart );
        PrintState = 'C';
        TotalCompressedFileCount.QuadPart += 1;

    } else {

        CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
            &CompressedSize.HighPart );

        if (GetLastError() == 0 &&
            CompressedSize.QuadPart != 0 &&
            CompressedSize.QuadPart < FileSize.QuadPart) {

            // File on DblSpace partition.

            PrintState = 'd';
            TotalCompressedFileCount.QuadPart += 1;

        } else {

            CompressedSize = FileSize;
            TotalUncompressedFileCount.QuadPart += 1;
        }
    }

    //
    //  Calculate the compression ratio for this file
    //

    if (CompressedSize.QuadPart != 0) {

        if (CompressedSize.QuadPart > FileSize.QuadPart) {

            //
            // The file probably grew between the time we got its size
            // and the time we got its compressed size.  Kludge.
            //

            FileSize.QuadPart = CompressedSize.QuadPart;
        }

        Ratio = (double)FileSize.QuadPart / (double)CompressedSize.QuadPart;
    }

    //
    //  Print out the sizes compression state and file name
    //

    if (!Quiet &&
        (DisplayAllFiles ||
            (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {

        FormatFileSize(&FileSize, 9, Buf, FALSE);
        lstrcat(Buf, TEXT(" : "));
        FormatFileSize(&CompressedSize, 9, &Buf[lstrlen(Buf)], FALSE);

        swprintf(&Buf[lstrlen(Buf)], TEXT(" = %2.1lf "), Ratio);
        DisplayMsg(COMPACT_THROW, Buf);

        DisplayMsg(COMPACT_TO_ONE);

        swprintf(Buf, TEXT("%c %s",), PrintState, FindData->cFileName);
        DisplayMsg(COMPACT_THROW_NL, Buf);
    }

    //
    //  Increment our running total
    //

    TotalFileSize.QuadPart += FileSize.QuadPart;
    TotalCompressedSize.QuadPart += CompressedSize.QuadPart;
    TotalFileCount.QuadPart += 1;

    return TRUE;
}

BOOLEAN
DoListAction (
    IN PTCHAR DirectorySpec,
    IN PTCHAR FileSpec
    )

{
    PTCHAR DirectorySpecEnd;

    //
    //  So that we can keep on appending names to the directory spec
    //  get a pointer to the end of its string
    //

    DirectorySpecEnd = DirectorySpec + lstrlen(DirectorySpec);

    //
    //  List the compression attribute for the directory
    //

    {
        ULONG Attributes;

        if (!Quiet || Quiet) {

            Attributes = GetFileAttributes( DirectorySpec );

            if (0xFFFFFFFF == Attributes) {

                if (!Quiet || !IgnoreErrors) {

                    //
                    // Refrain from displaying error only when in quiet
                    // mode *and* we're ignoring errors.
                    //

                    DisplayErr(DirectorySpec, GetLastError());
                }

                if (!IgnoreErrors) {
                    return FALSE;
                }
            } else {

                if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {
                    DisplayMsg(COMPACT_LIST_CDIR, DirectorySpec);
                } else {
                    DisplayMsg(COMPACT_LIST_UDIR, DirectorySpec);
                }
            }
        }

        TotalDirectoryCount.QuadPart += 1;
    }

    //
    //  Now for every file in the directory that matches the file spec we will
    //  will open the file and list its compression state
    //

    {
        HANDLE FindHandle;
        WIN32_FIND_DATA FindData;

        //
        //  setup the template for findfirst/findnext
        //

        //
        //  Make sure we don't try any paths that are too long for us
        //  to deal with.
        //

        if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
            MAX_PATH) {

            lstrcpy( DirectorySpecEnd, FileSpec );

            FindHandle = FindFirstFile( DirectorySpec, &FindData );

            if (INVALID_HANDLE_VALUE != FindHandle) {

               do {

                   //
                   //  append the found file to the directory spec and open the
                   //  file
                   //

                   if (0 == lstrcmp(FindData.cFileName, TEXT("..")) ||
                       0 == lstrcmp(FindData.cFileName, TEXT("."))) {
                       continue;
                   }

                   //
                   //  Make sure we don't try any paths that are too long for us
                   //  to deal with.
                   //

                   if ((DirectorySpecEnd - DirectorySpec) +
                       lstrlen( FindData.cFileName ) >= MAX_PATH ) {

                       continue;
                   }

                   lstrcpy( DirectorySpecEnd, FindData.cFileName );

                   //
                   //  Now print out the state of the file
                   //

                   DisplayFile( DirectorySpec, &FindData );

               } while ( FindNextFile( FindHandle, &FindData ));

               FindClose( FindHandle );
           }
        }
    }

    //
    //  For if we are to do subdirectores then we will look for every
    //  subdirectory and recursively call ourselves to list the subdirectory
    //

    if (DoSubdirectories) {

        HANDLE FindHandle;

        WIN32_FIND_DATA FindData;

        //
        //  Setup findfirst/findnext to search the entire directory
        //

        if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
            MAX_PATH) {

           lstrcpy( DirectorySpecEnd, TEXT("*") );

           FindHandle = FindFirstFile( DirectorySpec, &FindData );

           if (INVALID_HANDLE_VALUE != FindHandle) {

               do {

                   //
                   //  Now skip over the . and .. entries otherwise we'll recurse
                   //  like mad
                   //

                   if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
                       0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {

                       continue;

                   } else {

                       //
                       //  If the entry is for a directory then we'll tack on the
                       //  subdirectory name to the directory spec and recursively
                       //  call otherselves
                       //

                       if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {

                           //
                           //  Make sure we don't try any paths that are too long for us
                           //  to deal with.
                           //

                           if ((DirectorySpecEnd - DirectorySpec) +
                               lstrlen( TEXT("\\") ) +
                               lstrlen( FindData.cFileName ) >= MAX_PATH ) {

                               continue;
                           }

                           lstrcpy( DirectorySpecEnd, FindData.cFileName );
                           lstrcat( DirectorySpecEnd, TEXT("\\") );

                           if (!DoListAction( DirectorySpec, FileSpec )) {

                               FindClose( FindHandle );
                               return FALSE || IgnoreErrors;
                           }
                       }
                   }

               } while ( FindNextFile( FindHandle, &FindData ));

               FindClose( FindHandle );
           }
        }
    }

    return TRUE;
}

VOID
DoFinalListAction (
    )
{
    ULONG TotalPercentage = 100;
    double f = 1.0;

    TCHAR FileCount[32];
    TCHAR DirectoryCount[32];
    TCHAR CompressedFileCount[32];
    TCHAR UncompressedFileCount[32];
    TCHAR CompressedSize[32];
    TCHAR FileSize[32];
    TCHAR Percentage[10];
    TCHAR Ratio[8];

    if (TotalCompressedSize.QuadPart != 0) {
        f = (double)TotalFileSize.QuadPart /
            (double)TotalCompressedSize.QuadPart;
    }

    FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
    FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
    FormatFileSize(&TotalCompressedFileCount, 0, CompressedFileCount, FALSE);
    FormatFileSize(&TotalUncompressedFileCount, 0, UncompressedFileCount, FALSE);
    FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE);
    FormatFileSize(&TotalFileSize, 0, FileSize, TRUE);

    swprintf(Percentage, TEXT("%d"), TotalPercentage);
    swprintf(Ratio, TEXT("%2.1lf"), f);

    DisplayMsg(COMPACT_LIST_SUMMARY, FileCount, DirectoryCount,
               CompressedFileCount, UncompressedFileCount,
               FileSize, CompressedSize,
               Ratio );

    return;
}

BOOLEAN
CompressFile (
    IN HANDLE Handle,
    IN PTCHAR FileSpec,
    IN PWIN32_FIND_DATA FindData
    )

{
    USHORT State = 1;
    ULONG Length;
    ULONG i;
    BOOLEAN Success;
    double f = 1.0;

    if ((FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) &&
        !ForceOperation) {

        return TRUE;
    }

    Success = DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State,
        sizeof(USHORT), NULL, 0, &Length, FALSE );

    if (!Success) {

        if (Quiet && IgnoreErrors) {
            return FALSE || IgnoreErrors;
        }

        swprintf(Buf, TEXT("%s "), FindData->cFileName);
        DisplayMsg(COMPACT_THROW, Buf);

        for (i = lstrlen(FindData->cFileName) + 1; i < FIRST_COLUMN_WIDTH; ++i) {
            swprintf(Buf, TEXT("%c"), ' ');
            DisplayMsg(COMPACT_THROW, Buf);
        }

        DisplayMsg(COMPACT_ERR);

        if (!Quiet && !IgnoreErrors) {
            if (ERROR_INVALID_FUNCTION == GetLastError()) {

                // This error is caused by doing the fsctl on a
                // non-compressing volume.

                DisplayMsg(COMPACT_WRONG_FILE_SYSTEM_OR_CLUSTER_SIZE, FindData->cFileName);

            } else {
                DisplayErr(FindData->cFileName, GetLastError());
            }
        }

        return FALSE || IgnoreErrors;
    }

    if (!Quiet &&
        (DisplayAllFiles ||
            (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
        swprintf(Buf, TEXT("%s "), FindData->cFileName);
        DisplayMsg(COMPACT_THROW, Buf);

        for (i = lstrlen(FindData->cFileName) + 1; i < FIRST_COLUMN_WIDTH; ++i) {
            swprintf(Buf, TEXT("%c"), ' ');
            DisplayMsg(COMPACT_THROW, Buf);
        }
    }

    //
    //  Gather statistics and increment our running total
    //

    {
        LARGE_INTEGER FileSize;
        LARGE_INTEGER CompressedSize;
        ULONG Percentage = 100;

        FileSize.LowPart = FindData->nFileSizeLow;
        FileSize.HighPart = FindData->nFileSizeHigh;

        CompressedSize.LowPart = GetCompressedFileSize( FileSpec,
            &CompressedSize.HighPart );

        //
        // This statement to prevent confusion from the case where the
        // compressed file had been 0 size, but has grown since the filesize
        // was examined.
        //

        if (0 == FileSize.QuadPart) {
            CompressedSize.QuadPart = 0;
        }

        if (CompressedSize.QuadPart != 0) {

            f = (double)FileSize.QuadPart / (double)CompressedSize.QuadPart;
        }

        //
        //  Print out the sizes compression state and file name
        //

        if (!Quiet &&
            (DisplayAllFiles ||
                (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {

            FormatFileSize(&FileSize, 9, Buf, FALSE);
            lstrcat(Buf, TEXT(" : "));
            FormatFileSize(&CompressedSize, 9, &Buf[lstrlen(Buf)], FALSE);

            swprintf(&Buf[lstrlen(Buf)], TEXT(" = %2.1lf "), f);

            DisplayMsg(COMPACT_THROW, Buf);

            DisplayMsg(COMPACT_TO_ONE);
            DisplayMsg(COMPACT_OK);
        }

        //
        //  Increment our running total
        //

        TotalFileSize.QuadPart += FileSize.QuadPart;
        TotalCompressedSize.QuadPart += CompressedSize.QuadPart;
        TotalFileCount.QuadPart += 1;
    }

    return TRUE;
}

BOOLEAN
DoCompressAction (
    IN PTCHAR DirectorySpec,
    IN PTCHAR FileSpec
    )

{
    PTCHAR DirectorySpecEnd;

    //
    //  If the file spec is null then we'll set the compression bit for the
    //  the directory spec and get out.
    //

    if (lstrlen(FileSpec) == 0) {

        HANDLE FileHandle;
        USHORT State = 1;
        ULONG Length;

        FileHandle = OpenFileForCompress(DirectorySpec);

        if (INVALID_HANDLE_VALUE == FileHandle) {

            DisplayErr(DirectorySpec, GetLastError());
            return FALSE || IgnoreErrors;
        }

        DisplayMsg(COMPACT_COMPRESS_DIR, DirectorySpec);

        if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State,
            sizeof(USHORT), NULL, 0, &Length, FALSE )) {

            if (!Quiet || !IgnoreErrors) {
                DisplayMsg(COMPACT_ERR);
            }
            if (!Quiet && !IgnoreErrors) {
                DisplayErr(DirectorySpec, GetLastError());
            }
            CloseHandle( FileHandle );
            return FALSE || IgnoreErrors;
        }

        if (!Quiet) {
            DisplayMsg(COMPACT_OK);
        }

        CloseHandle( FileHandle );

        TotalDirectoryCount.QuadPart += 1;
        TotalFileCount.QuadPart += 1;

        return TRUE;
    }

    //
    //  So that we can keep on appending names to the directory spec
    //  get a pointer to the end of its string
    //

    DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec );

    //
    //  List the directory that we will be compressing within and say what its
    //  current compress attribute is
    //

    {
        ULONG Attributes;

        if (!Quiet || Quiet) {

            Attributes = GetFileAttributes( DirectorySpec );

            if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {

                DisplayMsg(COMPACT_COMPRESS_CDIR, DirectorySpec);

            } else {

                DisplayMsg(COMPACT_COMPRESS_UDIR, DirectorySpec);

            }
        }

        TotalDirectoryCount.QuadPart += 1;
    }

    //
    //  Now for every file in the directory that matches the file spec we will
    //  will open the file and compress it
    //

    {
        HANDLE FindHandle;
        HANDLE FileHandle;

        WIN32_FIND_DATA FindData;

        //
        //  setup the template for findfirst/findnext
        //

        if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
            MAX_PATH) {

           lstrcpy( DirectorySpecEnd, FileSpec );

           FindHandle = FindFirstFile( DirectorySpec, &FindData );

           if (INVALID_HANDLE_VALUE != FindHandle) {

               do {

                   //
                   //  Now skip over the . and .. entries
                   //

                   if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
                       0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {

                       continue;

                   } else {

                       //
                       //  Make sure we don't try any paths that are too long for us
                       //  to deal with.
                       //

                       if ( (DirectorySpecEnd - DirectorySpec) +
                           lstrlen( FindData.cFileName ) >= MAX_PATH ) {

                           continue;
                       }

                       //
                       //  append the found file to the directory spec and open
                       //  the file
                       //

                       lstrcpy( DirectorySpecEnd, FindData.cFileName );

                       //
                       //  Hack hack, kludge kludge.  Refrain from compressing
                       //  files named "\NTDLR" to help users avoid hosing
                       //  themselves.
                       //

                       if (IsNtldr(DirectorySpec)) {

                           if (!Quiet) {
                               DisplayMsg(COMPACT_SKIPPING, DirectorySpecEnd);
                           }

                           continue;
                       }

                       FileHandle = OpenFileForCompress(DirectorySpec);

                       if (INVALID_HANDLE_VALUE == FileHandle) {

                           if (!Quiet || !IgnoreErrors) {
                               DisplayErr(FindData.cFileName, GetLastError());
                           }

                           if (!IgnoreErrors) {
                               FindClose(FindHandle);
                               return FALSE;
                           }
                           continue;
                       }

                       //
                       //  Now compress the file
                       //

                       if (!CompressFile( FileHandle, DirectorySpec, &FindData )) {
                           CloseHandle( FileHandle );
                           FindClose( FindHandle );
                           return FALSE || IgnoreErrors;
                       }

                       //
                       //  Close the file and go get the next file
                       //

                       CloseHandle( FileHandle );
                   }

               } while ( FindNextFile( FindHandle, &FindData ));

               FindClose( FindHandle );
           }
        }
    }

    //
    //  If we are to do subdirectores then we will look for every subdirectory
    //  and recursively call ourselves to list the subdirectory
    //

    if (DoSubdirectories) {

        HANDLE FindHandle;

        WIN32_FIND_DATA FindData;

        //
        //  Setup findfirst/findnext to search the entire directory
        //

        if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
            MAX_PATH) {

           lstrcpy( DirectorySpecEnd, TEXT("*") );

           FindHandle = FindFirstFile( DirectorySpec, &FindData );

           if (INVALID_HANDLE_VALUE != FindHandle) {

               do {

                   //
                   //  Now skip over the . and .. entries otherwise we'll recurse
                   //  like mad
                   //

                   if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
                       0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {

                       continue;

                   } else {

                       //
                       //  If the entry is for a directory then we'll tack on the
                       //  subdirectory name to the directory spec and recursively
                       //  call otherselves
                       //

                       if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {

                           //
                           //  Make sure we don't try any paths that are too long for us
                           //  to deal with.
                           //

                           if ((DirectorySpecEnd - DirectorySpec) +
                               lstrlen( TEXT("\\") ) +
                               lstrlen( FindData.cFileName ) >= MAX_PATH ) {

                               continue;
                           }

                           lstrcpy( DirectorySpecEnd, FindData.cFileName );
                           lstrcat( DirectorySpecEnd, TEXT("\\") );

                           if (!DoCompressAction( DirectorySpec, FileSpec )) {
                               FindClose( FindHandle );
                               return FALSE || IgnoreErrors;
                           }
                       }
                   }

               } while ( FindNextFile( FindHandle, &FindData ));

               FindClose( FindHandle );
           }
        }
    }

    return TRUE;
}

VOID
DoFinalCompressAction (
    )
{
    ULONG TotalPercentage = 100;
    double f = 1.0;

    TCHAR FileCount[32];
    TCHAR DirectoryCount[32];
    TCHAR CompressedSize[32];
    TCHAR FileSize[32];
    TCHAR Percentage[32];
    TCHAR Ratio[8];

    if (TotalCompressedSize.QuadPart != 0) {
        f = (double)TotalFileSize.QuadPart /
            (double)TotalCompressedSize.QuadPart;
    }

    FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
    FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);
    FormatFileSize(&TotalCompressedSize, 0, CompressedSize, TRUE);
    FormatFileSize(&TotalFileSize, 0, FileSize, TRUE);

    swprintf(Percentage, TEXT("%d"), TotalPercentage);
    swprintf(Ratio, TEXT("%2.1f"), f);

    DisplayMsg(COMPACT_COMPRESS_SUMMARY, FileCount, DirectoryCount,
                FileSize, CompressedSize, Ratio );

}

BOOLEAN
UncompressFile (
    IN HANDLE Handle,
    IN PWIN32_FIND_DATA FindData
    )
{
    USHORT State = 0;
    ULONG Length;

    if (!(FindData->dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) &&
        !ForceOperation) {

        return TRUE;
    }

    if (!DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, &State,
        sizeof(USHORT), NULL, 0, &Length, FALSE )) {

        if (!Quiet || !IgnoreErrors) {

            swprintf(Buf, TEXT("%s "), FindData->cFileName);
            DisplayMsg(COMPACT_THROW, Buf);

            DisplayMsg(COMPACT_ERR);

            if (!Quiet && !IgnoreErrors) {

                if (ERROR_INVALID_FUNCTION == GetLastError()) {

                    // This error is caused by doing the fsctl on a
                    // non-compressing volume.

                    DisplayMsg(COMPACT_WRONG_FILE_SYSTEM, FindData->cFileName);

                } else {
                    DisplayErr(FindData->cFileName, GetLastError());
                }
            }
        }
        return FALSE || IgnoreErrors;
    }

    if (!Quiet &&
        (DisplayAllFiles ||
            (0 == (FindData->dwFileAttributes & AttributesNoDisplay)))) {
        swprintf(Buf, TEXT("%s "), FindData->cFileName);
        DisplayMsg(COMPACT_THROW, Buf);

        DisplayMsg(COMPACT_OK);
    }

    //
    //  Increment our running total
    //

    TotalFileCount.QuadPart += 1;

    return TRUE;
}

BOOLEAN
DoUncompressAction (
    IN PTCHAR DirectorySpec,
    IN PTCHAR FileSpec
    )

{
    PTCHAR DirectorySpecEnd;

    //
    //  If the file spec is null then we'll clear the compression bit for the
    //  the directory spec and get out.
    //

    if (lstrlen(FileSpec) == 0) {

        HANDLE FileHandle;
        USHORT State = 0;
        ULONG Length;

        FileHandle = OpenFileForCompress(DirectorySpec);

        if (INVALID_HANDLE_VALUE == FileHandle) {

            if (!Quiet || !IgnoreErrors) {
                DisplayErr(DirectorySpec, GetLastError());
            }
            return FALSE || IgnoreErrors;
        }

        DisplayMsg(COMPACT_UNCOMPRESS_DIR, DirectorySpec);

        if (!DeviceIoControl(FileHandle, FSCTL_SET_COMPRESSION, &State,
            sizeof(USHORT), NULL, 0, &Length, FALSE )) {

            if (!Quiet || !IgnoreErrors) {
                DisplayMsg(COMPACT_ERR);

            }
            if (!Quiet && !IgnoreErrors) {
                DisplayErr(DirectorySpec, GetLastError());
            }

            return FALSE || IgnoreErrors;
        }

        if (!Quiet) {
            DisplayMsg(COMPACT_OK);
        }

        CloseHandle( FileHandle );

        TotalDirectoryCount.QuadPart += 1;
        TotalFileCount.QuadPart += 1;

        return TRUE;
    }

    //
    //  So that we can keep on appending names to the directory spec
    //  get a pointer to the end of its string
    //

    DirectorySpecEnd = DirectorySpec + lstrlen( DirectorySpec );

    //
    //  List the directory that we will be uncompressing within and say what its
    //  current compress attribute is
    //

    {
        ULONG Attributes;

        if (!Quiet || Quiet) {

            Attributes = GetFileAttributes( DirectorySpec );

            if (Attributes & FILE_ATTRIBUTE_COMPRESSED) {

                DisplayMsg(COMPACT_UNCOMPRESS_CDIR, DirectorySpec);

            } else {

                DisplayMsg(COMPACT_UNCOMPRESS_UDIR, DirectorySpec);
            }
        }

        TotalDirectoryCount.QuadPart += 1;
    }

    //
    //  Now for every file in the directory that matches the file spec we will
    //  will open the file and uncompress it
    //

    {
        HANDLE FindHandle;
        HANDLE FileHandle;

        WIN32_FIND_DATA FindData;

        //
        //  setup the template for findfirst/findnext
        //

        if (((DirectorySpecEnd - DirectorySpec) + lstrlen( FileSpec )) <
            MAX_PATH) {

           lstrcpy( DirectorySpecEnd, FileSpec );

           FindHandle = FindFirstFile( DirectorySpec, &FindData );

           if (INVALID_HANDLE_VALUE != FindHandle) {

               do {

                   //
                   //  Now skip over the . and .. entries
                   //

                   if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
                       0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {

                       continue;

                   } else {

                       //
                       //  Make sure we don't try any paths that are too long for us
                       //  to deal with.
                       //

                       if ((DirectorySpecEnd - DirectorySpec) +
                           lstrlen( FindData.cFileName ) >= MAX_PATH ) {

                           continue;
                       }

                       //
                       //  append the found file to the directory spec and open
                       //  the file
                       //

                       lstrcpy( DirectorySpecEnd, FindData.cFileName );

                       FileHandle = OpenFileForCompress(DirectorySpec);

                       if (INVALID_HANDLE_VALUE == FileHandle) {

                           if (!Quiet || !IgnoreErrors) {
                               DisplayErr(DirectorySpec, GetLastError());
                           }

                           if (!IgnoreErrors) {
                               FindClose( FindHandle );
                               return FALSE;
                           }
                           continue;
                       }

                       //
                       //  Now compress the file
                       //

                       if (!UncompressFile( FileHandle, &FindData )) {
                           CloseHandle( FileHandle );
                           FindClose( FindHandle );
                           return FALSE || IgnoreErrors;
                       }

                       //
                       //  Close the file and go get the next file
                       //

                       CloseHandle( FileHandle );
                   }

               } while ( FindNextFile( FindHandle, &FindData ));

               FindClose( FindHandle );
           }
        }
    }

    //
    //  If we are to do subdirectores then we will look for every subdirectory
    //  and recursively call ourselves to list the subdirectory
    //

    if (DoSubdirectories) {

        HANDLE FindHandle;

        WIN32_FIND_DATA FindData;

        //
        //  Setup findfirst/findnext to search the entire directory
        //

        if (((DirectorySpecEnd - DirectorySpec) + lstrlen( TEXT("*") )) <
            MAX_PATH) {

           lstrcpy( DirectorySpecEnd, TEXT("*") );

           FindHandle = FindFirstFile( DirectorySpec, &FindData );
           if (INVALID_HANDLE_VALUE != FindHandle) {

               do {

                   //
                   //  Now skip over the . and .. entries otherwise we'll recurse
                   //  like mad
                   //

                   if (0 == lstrcmp(&FindData.cFileName[0], TEXT(".")) ||
                       0 == lstrcmp(&FindData.cFileName[0], TEXT(".."))) {

                       continue;

                   } else {

                       //
                       //  If the entry is for a directory then we'll tack on the
                       //  subdirectory name to the directory spec and recursively
                       //  call otherselves
                       //

                       if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {

                           //
                           //  Make sure we don't try any paths that are too long for us
                           //  to deal with.
                           //

                           if ((DirectorySpecEnd - DirectorySpec) +
                               lstrlen( TEXT("\\") ) +
                               lstrlen( FindData.cFileName ) >= MAX_PATH ) {

                               continue;
                           }

                           lstrcpy( DirectorySpecEnd, FindData.cFileName );
                           lstrcat( DirectorySpecEnd, TEXT("\\") );

                           if (!DoUncompressAction( DirectorySpec, FileSpec )) {
                               FindClose( FindHandle );
                               return FALSE || IgnoreErrors;
                           }
                       }
                   }

               } while ( FindNextFile( FindHandle, &FindData ));

               FindClose( FindHandle );
           }
        }
    }

    return TRUE;
}

VOID
DoFinalUncompressAction (
    )

{
    TCHAR FileCount[32];
    TCHAR DirectoryCount[32];

    FormatFileSize(&TotalFileCount, 0, FileCount, FALSE);
    FormatFileSize(&TotalDirectoryCount, 0, DirectoryCount, FALSE);

    DisplayMsg(COMPACT_UNCOMPRESS_SUMMARY, FileCount, DirectoryCount);

    return;
}

VOID
__cdecl
main()
{
    PTCHAR *argv;
    ULONG argc;

    ULONG i;

    PACTION_ROUTINE ActionRoutine = NULL;
    PFINAL_ACTION_ROUTINE FinalActionRoutine = NULL;

    BOOLEAN UserSpecifiedFileSpec = FALSE;

    TCHAR DirectorySpec[MAX_PATH];
    TCHAR FileSpec[MAX_PATH];
    PTCHAR p;

    InitializeIoStreams();

    argv = CommandLineToArgvW(GetCommandLine(), &argc);
    if (NULL == argv) {
        DisplayErr(NULL, GetLastError());
        return;
    }

    //
    //  Scan through the arguments looking for switches
    //

    for (i = 1; i < argc; i += 1) {

        if (argv[i][0] == '/') {

            if (0 == lstricmp(argv[i], TEXT("/c"))) {

                if (ActionRoutine != NULL &&
                    ActionRoutine != DoCompressAction) {

                    DisplayMsg(COMPACT_USAGE, NULL);
                    return;
                }

                ActionRoutine = DoCompressAction;
                FinalActionRoutine = DoFinalCompressAction;

            } else if (0 == lstricmp(argv[i], TEXT("/u"))) {

                if (ActionRoutine != NULL && ActionRoutine != DoListAction) {

                    DisplayMsg(COMPACT_USAGE, NULL);
                    return;
                }

                ActionRoutine = DoUncompressAction;
                FinalActionRoutine = DoFinalUncompressAction;

            } else if (0 == lstricmp(argv[i], TEXT("/q"))) {

                Quiet = TRUE;

            } else if (0 == lstrnicmp(argv[i], TEXT("/s"), 2)) {

                PTCHAR pch;

                DoSubdirectories = TRUE;

                pch = lstrchr(argv[i], ':');
                if (NULL != pch) {
                    lstrcpy(StartingDirectory, pch + 1);
                } else if (2 == lstrlen(argv[i])) {

                    // Starting dir is CWD

                    GetCurrentDirectory( MAX_PATH, StartingDirectory );

                } else {
                    DisplayMsg(COMPACT_USAGE, NULL);
                    return;
                }

            } else if (0 == lstricmp(argv[i], TEXT("/i"))) {

                IgnoreErrors = TRUE;

            } else if (0 == lstricmp(argv[i], TEXT("/f"))) {

                ForceOperation = TRUE;

            } else if (0 == lstricmp(argv[i], TEXT("/a"))) {

                DisplayAllFiles = TRUE;

            } else {

                DisplayMsg(COMPACT_USAGE, NULL);
                return;
            }

        } else {

            UserSpecifiedFileSpec = TRUE;
        }
    }

    //
    //  If the use didn't specify an action then set the default to do a listing
    //

    if (ActionRoutine == NULL) {

        ActionRoutine = DoListAction;
        FinalActionRoutine = DoFinalListAction;
    }

    //
    //  Get our current directoy because the action routines might move us
    //  around
    //

    if (!DoSubdirectories) {
        GetCurrentDirectory( MAX_PATH, StartingDirectory );
    } else if (!SetCurrentDirectory( StartingDirectory )) {
        DisplayErr(StartingDirectory, GetLastError());
        return;
    }

    //
    //  If the user didn't specify a file spec then we'll do just "*"
    //

    if (!UserSpecifiedFileSpec) {

        (VOID)GetFullPathName( TEXT("*"), MAX_PATH, DirectorySpec, &p );

        lstrcpy( FileSpec, p ); *p = '\0';

        //
        // Also want to make "compact /c" set the bit for the current
        // directory.
        //

        if (ActionRoutine != DoListAction) {

            (VOID)(ActionRoutine)( DirectorySpec, TEXT("") );
        }

        (VOID)(ActionRoutine)( DirectorySpec, FileSpec );

    } else {

        //
        //  Now scan the arguments again looking for non-switches
        //  and this time do the action, but before calling reset
        //  the current directory so that things work again
        //

        for (i = 1; i < argc; i += 1) {

            if (argv[i][0] != '/') {

                SetCurrentDirectory( StartingDirectory );

                //
                // Handle a command with "." as the file argument specially,
                // since it doesn't make good sense and the results without
                // this code are surprising.
                //

                if ('.' == argv[i][0] && '\0' == argv[i][1]) {
                    argv[i] = TEXT("*");
                    GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p);
                    *p = '\0';
                    p = NULL;
                } else {

                    PWCHAR pwch;

                    GetFullPathName(argv[i], MAX_PATH, DirectorySpec, &p);

                    //
                    // We want to treat "foobie:xxx" as an invalid drive name,
                    // rather than as a name identifying a stream.  If there's
                    // a colon, there should be only a single character before
                    // it.
                    //

                    pwch = wcschr(argv[i], ':');
                    if (NULL != pwch && pwch - argv[i] != 1) {
                        DisplayMsg(COMPACT_INVALID_PATH, argv[i]);
                        break;
                    }

                    //
                    // GetFullPathName strips trailing dots, but we want
                    // to save them so that "*." will work correctly.
                    //

                    if ('.' == argv[i][lstrlen(argv[i]) - 1]) {
                        lstrcat(DirectorySpec, TEXT("."));
                    }
                }

                if (IsUncRoot(DirectorySpec)) {

                    //
                    // If the path is like \\server\share, we append an
                    // additional slash to make things come out right.
                    //

                    lstrcat(DirectorySpec, TEXT("\\"));
                    p = NULL;
                }

                if (p != NULL) {
                    lstrcpy( FileSpec, p ); *p = '\0';
                } else {
                    FileSpec[0] = '\0';
                }

                if (!(ActionRoutine)( DirectorySpec, FileSpec ) &&
                    !IgnoreErrors) {
                    break;
                }
            }
        }
    }

    //
    //  Reset our current directory back
    //

    SetCurrentDirectory( StartingDirectory );

    //
    //  And do the final action routine that will print out the final
    //  statistics of what we've done
    //

    (FinalActionRoutine)();
}


SUPPORT.C

/*++

Copyright (c) 1994-1995  Microsoft Corporation

Module Name:

    Support.c

Abstract:

    Support routines for compact utility

Author:

    Matthew Bradburn    [mattbr]        05-Oct-1994

Revision History:

--*/

#define UNICODE
#define _UNICODE

#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <windows.h>
#include "support.h"
#include "msg.h"

//
//  Declare routines to put out internationalized messages
//

typedef enum {
    READ_ACCESS,
    WRITE_ACCESS
} STREAMACCESS;

HANDLE
GetStandardStream(
    IN HANDLE   Handle,
    IN STREAMACCESS Access
    );

HANDLE hInput;
HANDLE hOutput;
HANDLE hError;

#define STDIN   0
#define STDOUT  1
#define STDERR  2

BOOL ConsoleInput;
BOOL ConsoleOutput;
BOOL ConsoleError;

int
FileIsConsole(int fh)
{
    unsigned htype;
    DWORD dwMode;
    HANDLE hFile;

    hFile = (HANDLE)_get_osfhandle(fh);
    htype = GetFileType(hFile);
    htype &= ~FILE_TYPE_REMOTE;

    if (FILE_TYPE_CHAR == htype) {

        switch (fh) {
        case STDIN:
            hFile = GetStdHandle(STD_INPUT_HANDLE);
            break;
        case STDOUT:
            hFile = GetStdHandle(STD_OUTPUT_HANDLE);
            break;
        case STDERR:
            hFile = GetStdHandle(STD_ERROR_HANDLE);
            break;
        }

        if (GetConsoleMode(hFile, &dwMode)) {
            return TRUE;
        }
    }

    return FALSE;

}

VOID
InitializeIoStreams()
{
#ifdef FE_SB
    LANGID LangId;

    switch (GetConsoleOutputCP()) {
        case 932:
            LangId = MAKELANGID( LANG_JAPANESE, SUBLANG_DEFAULT );
            break;
        case 949:
            LangId = MAKELANGID( LANG_KOREAN, SUBLANG_KOREAN );
            break;
        case 936:
            LangId = MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED );
            break;
        case 950:
            LangId = MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL );
            break;
        default:
            LangId = MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT );
            break;
    }

    SetThreadLocale( MAKELCID(LangId, SORT_DEFAULT) );
#endif

    hInput = GetStdHandle(STD_INPUT_HANDLE);
    ConsoleInput = FileIsConsole(STDIN);

    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    ConsoleOutput = FileIsConsole(STDOUT);

    hError = GetStdHandle(STD_ERROR_HANDLE);
    ConsoleError = FileIsConsole(STDERR);
}

TCHAR DisplayBuffer[4096];
CHAR DisplayBuffer2[4096];

VOID
DisplayMsg(DWORD MsgNum, ... )
{
    DWORD len, bytes_written;
    BOOL success;
    DWORD status;
    va_list ap;

    va_start(ap, MsgNum);

    len = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, NULL, MsgNum, 0,
        DisplayBuffer, 4096, &ap);

    if (ConsoleOutput) {
        success = WriteConsole(hOutput, (LPVOID)DisplayBuffer, len,
                 &bytes_written, NULL);

    } else {
        CharToOem(DisplayBuffer, DisplayBuffer2);
        success = WriteFile(hOutput, (LPVOID)DisplayBuffer2, len,
                 &bytes_written, NULL);
    }

    if (!success || bytes_written != len) {
        status = GetLastError();
    }

    va_end(ap);
}

VOID
DisplayErr(
    PTCHAR Prefix,
    DWORD MsgNum,
    ...
    )
{
    DWORD len, bytes_written;
    BOOL success;
    DWORD status;
    va_list ap;
    ULONG i;

    va_start(ap, MsgNum);

    if (NULL != Prefix) {
        lstrcpy(DisplayBuffer, Prefix);
        lstrcat(DisplayBuffer, TEXT(": "));
    } else {
        DisplayBuffer[0] = UNICODE_NULL;
    }

    i = lstrlen(DisplayBuffer);

    len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, MsgNum, 0,
        DisplayBuffer + i, 4096 - i, &ap);

    if (ConsoleError) {
        success = WriteConsole(hError, (LPVOID)DisplayBuffer, len + i,
                 &bytes_written, NULL);

    } else {
        CharToOem(DisplayBuffer, DisplayBuffer2);
        success = WriteFile(hError, (LPVOID)DisplayBuffer2, len + i,
                 &bytes_written, NULL);
    }

    if (!success) {
        status = GetLastError();
    }
    va_end(ap);
}

BOOLEAN
IsNtldr(
    PTCHAR Path
    )
/*++

Routine Description:

    Used to keep the user from compressing \NTLDR (which would prevent
    the machine from booting).

Arguments:

    Path - the path to examine.

Return Value:

    TRUE - the path looks like \ntldr.
    FALSE - the path does not look like \ntldr.

--*/
{
    PTCHAR pch;

    // try "X:\ntldr"

    if (0 == lstricmp(Path + 2, TEXT("\\ntldr"))) {
        return TRUE;
    }

    // try "\\machine\share\ntldr"

    if ('\\' == Path[0] && '\\' != Path[1]) {
        pch = lstrchr(Path + 2, '\\');
        if (NULL == pch) {
            return FALSE;
        }
        pch = lstrchr(pch + 1, '\\');
        if (NULL == pch) {
            return FALSE;
        }
        if (0 == lstricmp(pch, TEXT("\\ntldr"))) {
            return TRUE;
        }
    }

    return FALSE;
}

BOOLEAN
IsUncRoot(
    PTCHAR Path
    )
/*++

Routine Description:

    Determine whether the given path is of the form \\server\share.

Arguments:

    Path - the path to examine.

Return Value:

    TRUE - the path looks like a unc share name.
    FALSE - the path does not look like that.

--*/
{
    PTCHAR pch;

    if ('\\' != *Path || '\\' != *(Path + 1)) {
        return FALSE;
    }

    pch = lstrchr(Path + 2, '\\');
    if (NULL == pch) {
        //
        // There is no slash to seperate server and share.
        //

        return FALSE;
    }

    pch = lstrchr(pch + 1, '\\');
    if (NULL != pch) {
        //
        // There are additional component -- no match.
        //

        return FALSE;
    }

    if ('\\' == *(Path + lstrlen(Path))) {

        //
        // The string ends in slash -- it doesn't match.
        //

        return FALSE;
    }

    return TRUE;
}

ULONG
FormatFileSize(
    IN PLARGE_INTEGER FileSize,
    IN DWORD Width,
    OUT PTCHAR FormattedSize,
    IN BOOLEAN Commas
    )
{
    TCHAR Buffer[100];
    PTCHAR s, s1;
    ULONG DigitIndex, Digit;
    ULONG Size;
    LARGE_INTEGER TempSize;

    s = &Buffer[ 99 ];
    *s = TEXT('\0');
    DigitIndex = 0;
    TempSize = *FileSize;
    while (TempSize.HighPart != 0) {
        if (TempSize.HighPart != 0) {
            Digit = (ULONG)(TempSize.QuadPart % 10);
            TempSize.QuadPart = TempSize.QuadPart / 10;
        } else {
            Digit = TempSize.LowPart % 10;
            TempSize.LowPart = TempSize.LowPart / 10;
        }
        *--s = (TCHAR)(TEXT('0') + Digit);

        if ((++DigitIndex % 3) == 0 && Commas) {
            *--s = TEXT(',');
        }
    }
    Size = TempSize.LowPart;
    while (Size != 0) {
        *--s = (TCHAR)(TEXT('0') + (Size % 10));
        Size = Size / 10;

        if ((++DigitIndex % 3) == 0 && Commas) {
            *--s = TEXT(',');
        }
    }

    if (DigitIndex == 0) {
        *--s = TEXT('0');
    } else if (Commas && *s == TEXT(',')) {
        s += 1;
    }

    Size = lstrlen( s );
    if (Width != 0 && Size < Width) {
        s1 = FormattedSize;
        while (Width > Size) {
            Width -= 1;
            *s1++ = TEXT(' ');
        }
        lstrcpy( s1, s );
    } else {
        lstrcpy( FormattedSize, s );
    }

    return lstrlen( FormattedSize );
}


SUPPORT.H

/*++

Copyright (c) 1994-1995  Microsoft Corporation

Module Name:

    Support.h

Abstract:

    Support routine interfaces

Author:

    Matthew Bradburn    [mattbr]        05-Oct-1994

Revision History:

--*/

extern VOID
ArrangeCommandLine(
    PTCHAR **pargv,
    int *pargc
    );

extern BOOLEAN
IsNtldr(
    PTCHAR pch
    );

extern BOOLEAN
IsUncRoot(
    PTCHAR pch
    );

extern VOID
DisplayMsg(DWORD MsgNum, ... );

extern VOID
DisplayErr(PTCHAR Prefix, DWORD MsgNum, ... );

extern VOID
InitializeIoStreams();

#define lstrchr wcschr
#define lstricmp _wcsicmp
#define lstrnicmp _wcsnicmp

extern ULONG
FormatFileSize(
    IN  PLARGE_INTEGER FileSize,
    IN  DWORD          Width,
    OUT PTCHAR         FormattedSize,
    IN  BOOLEAN        WithCommas
    );


README.TXT

DBMON notes

10/7/94

The first DWORD of the shared memory buffer contains the process ID of the
client that sent the debug string.  DBMON will print the process ID if the
previous line printed was from another process, or if the previous line was
from the same process and ended in a newline.

The shared memory buffer must be 4096 bytes.  If it is larger, it won't do any
good.  If it is smaller, nothing terrible will happen, but strings larger than
the buffer will not be delivered, and a complaining message will be sent to the
kernel debugger by the client.  Why is it this size?  Because shared memory
comes in page-sized chunks, so we might as well ask for as much as will be used
on the machines with the smallest pages.  On machines with larger pages, the
remainder of the page will be wasted anyway.

Event DBWIN_BUFFER_READY must be an auto-reset event, so that only one client
thread will use the buffer at a time.  Using a manual reset event with
PulseEvent will not work.

Event DBWIN_DATA_READY need not be auto-reset, but it is simpler that way.

Allowing multiple copies of DBMON to run might be amusing, put not particularly
useful.  As I originally wrote it, the messages were distributed randomly among
the DBMON instances or be garbled.  I added a check for ERROR_ALREADY_EXISTS so
it won't run twice.  The worst you can do to the client is cause it to print a
message on the kernel debugger, as above.

If DBMON is broken and never calls SetEvent(AckEvent), it will cause a 10
second delay in the client every time OutputDebugString is called, then the
message will be printed on the kernel debugger.

OutputDebugString will force synchronization of client threads when DBMON is
running.  Letting the kernel debugger catch the strings will also synchronize
threads, but the timing effects may be different.  Caveat emptor.

In a GUI version of DBMON, use MsgWaitForMultipleObjects.


DBMON.C

/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    dbmon.c

Abstract:

    A simple program to print strings passed to OutputDebugString when
    the app printing the strings is not being debugged.

Author:

    Kent Forschmiedt (kentf) 30-Sep-1994

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

int _cdecl
main(
    int argc,
    char ** argv
    )
/*++

Routine Description:

Arguments:

Return Value:

--*/
{
    HANDLE AckEvent;
    HANDLE ReadyEvent;
    HANDLE SharedFile;
    LPVOID SharedMem;
    LPSTR  String;
    DWORD  ret;
    DWORD  LastPid;
    LPDWORD pThisPid;
    BOOL    DidCR;

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = &sd;

    if(!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
        fprintf(stderr,"unable to InitializeSecurityDescriptor, err == %d\n",
            GetLastError());
        exit(1);
    }

    if(!SetSecurityDescriptorDacl(&sd, TRUE, (PACL)NULL, FALSE)) {
        fprintf(stderr,"unable to SetSecurityDescriptorDacl, err == %d\n",
            GetLastError());
        exit(1);
    }

    AckEvent = CreateEvent(&sa, FALSE, FALSE, "DBWIN_BUFFER_READY");

    if (!AckEvent) {
        fprintf(stderr,
                "dbmon: Unable to create synchronization object, err == %d\n",
                GetLastError());
        exit(1);
    }

    if (GetLastError() == ERROR_ALREADY_EXISTS) {
        fprintf(stderr, "dbmon: already running\n");
        exit(1);
    }

    ReadyEvent = CreateEvent(&sa, FALSE, FALSE, "DBWIN_DATA_READY");

    if (!ReadyEvent) {
        fprintf(stderr,
                "dbmon: Unable to create synchronization object, err == %d\n",
                GetLastError());
        exit(1);
    }

    SharedFile = CreateFileMapping(
                        (HANDLE)-1,
                        &sa,
                        PAGE_READWRITE,
                        0,
                        4096,
                        "DBWIN_BUFFER");

    if (!SharedFile) {
        fprintf(stderr,
                "dbmon: Unable to create file mapping object, err == %d\n",
                GetLastError());
        exit(1);
    }

    SharedMem = MapViewOfFile(
                        SharedFile,
                        FILE_MAP_READ,
                        0,
                        0,
                        512);

    if (!SharedMem) {
        fprintf(stderr,
                "dbmon: Unable to map shared memory, err == %d\n",
                GetLastError());
        exit(1);
    }

    String = (LPSTR)SharedMem + sizeof(DWORD);
    pThisPid = SharedMem;

    LastPid = 0xffffffff;
    DidCR = TRUE;

    SetEvent(AckEvent);

    for (;;) {

        ret = WaitForSingleObject(ReadyEvent, INFINITE);

        if (ret != WAIT_OBJECT_0) {

            fprintf(stderr, "dbmon: wait failed; err == %d\n", GetLastError());
            exit(1);

        } else {

            if (LastPid != *pThisPid) {
                LastPid = *pThisPid;
                if (!DidCR) {
                    putchar('\n');
                    DidCR = TRUE;
                }
            }

            if (DidCR) {
                printf("%3u: ", LastPid);
            }

            printf("%s", String);
            DidCR = (*String && (String[strlen(String) - 1] == '\n'));
            SetEvent(AckEvent);

        }

    }

    return 0;
}


README.TXT

Dynamic Data Exchange (DDE) Event and Status Viewer

SUMMARY
=======

The DDESPY tool allows you to view information about the status of dynamic
data exchange (DDE) conversations.

MORE INFORMATION
================

The DDESPY tool demonstrates how to monitor DDE messages by setting up an
APPCLASS_MONITOR callback function. The DDESPY-monitored data can be
selected and filtered through various options. The information is
represented in up to four selectable tracks for String Handles,
Conversations, Links and Services, each displayed in different overlapped
windows. DDESPY records the messages and displays them on the related
window. DDESPY is useful in debugging to verify DDE data and messages.

DDESPY demonstrates the following techniques:

  - Using a DDE APPCLASS_ MONITOR.
  - Using a DDE callback routine.
  - Creating ownerdraw multi-column list boxes (sorted by column).
  - Creating a string class window to spool endless text.

To use DDESPY to monitor messages, do the following:

 1. Choose the Outputs menu to select the Output device.
 2. Choose the Monitor menu to select the data you wish to monitor.
 3. Choose the Track menu to select the track to be displayed.
 4. Install a client server DDE conversation, such as \ddeml\client and
    \ddeml\server.


DDESPY.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.
\******************************************************************************/

/*
 * DDESPY.H
 *
 * Header file for DDESPY Symbols
 */
#define DDEMLDB

#include <ddeml.h>
#include "dialog.h"

#define MH_INTCREATE                5
#define MH_INTKEEP                  6
#define MH_INTDELETE                7

#define IDD_MSGFILTERS              300
#define IDRB_WM_DDE_INITIATE        305
#define IDRB_WM_DDE_TERMINATE       306
#define IDRB_WM_DDE_ADVISE          307
#define IDRB_WM_DDE_UNADVISE        308
#define IDRB_WM_DDE_ACK             309
#define IDRB_WM_DDE_DATA            310
#define IDRB_WM_DDE_REQUEST         311
#define IDRB_WM_DDE_POKE            312
#define IDRB_WM_DDE_EXECUTE         313
#define IDRB_XTYP_ERROR             314
#define IDRB_XTYP_ADVDATA           315
#define IDRB_XTYP_ADVREQ            316
#define IDRB_XTYP_ADVSTART          317
#define IDRB_XTYP_ADVSTOP           318
#define IDRB_XTYP_EXECUTE           319
#define IDRB_XTYP_CONNECT           320
#define IDRB_XTYP_CONNECT_CONFIRM   321
#define IDRB_XACT_COMPLETE          322
#define IDRB_XTYP_POKE              323
#define IDRB_XTYP_REGISTER          324
#define IDRB_XTYP_REQUEST           325
#define IDRB_XTYP_DISCONNECT        326
#define IDRB_XTYP_UNREGISTER        327
#define IDRB_XTYP_WILDCONNECT       328
#define IDRB_TERSE                  329

#define IDR_ACCEL                   30
#define IDR_MENU                    31
#define IDD_VALUEENTRY              50
#define IDD_ABOUTBOX                51
#define IDD_OPEN                    52
#define IDM_OUTPUT_FIRST            100
#define IDM_OUTPUT_FILE             100
#define IDM_OUTPUT_DEBUG            101
#define IDM_OUTPUT_SCREEN           102
#define IDM_CLEARSCREEN             103
#define IDM_MARK                    104
#define IDM_FILTER_FIRST            200
#define IDM_FILTER_HSZINFO          200
#define IDM_FILTER_INIT_TERM        201
#define IDM_FILTER_DDEMSGS          202
#define IDM_FILTER_CALLBACKS        203
#define IDM_FILTER_ERRORS           204
#define IDM_FILTER_DIALOG           205
#define IDM_TRACK_FIRST             301
#define IDM_TRACK_HSZS              301
#define IDM_TRACK_CONVS             302
#define IDM_TRACK_LINKS             303
#define IDM_TRACK_SVRS              304
#define IDM_TRACK_LAST              304
#define IDM_ABOUT                   401
#define IDM_TEST                    402

#define IDM_MSGF_0                  500
#define IDM_MSGF_1                  501
#define IDM_MSGF_2                  502
#define IDM_MSGF_3                  503
#define IDM_MSGF_4                  504
#define IDM_MSGF_5                  505
#define IDM_MSGF_6                  506
#define IDM_MSGF_7                  507
#define IDM_MSGF_8                  508

#define IDM_CBF_0                   600
#define IDM_CBF_1                   601
#define IDM_CBF_2                   602
#define IDM_CBF_3                   603
#define IDM_CBF_4                   604
#define IDM_CBF_5                   605
#define IDM_CBF_6                   606
#define IDM_CBF_7                   607
#define IDM_CBF_8                   608
#define IDM_CBF_9                   609
#define IDM_CBF_10                  610
#define IDM_CBF_11                  611
#define IDM_CBF_12                  612
#define IDM_CBF_13                  613
#define IDM_CBF_14                  614

#define IDS_TITLE                   0
#define IDS_DEFAULT_OUTPUT_FNAME    1
#define IDS_INIFNAME                2
#define IDS_CLASS                   3
#define IDS_HUH                     4
#define IDS_ZERO                    5
#define IDS_CRLF                    6
#define IDS_TRACKTITLE_1            7
#define IDS_TRACKTITLE_2            8
#define IDS_TRACKTITLE_3            9
#define IDS_TRACKTITLE_4            10
#define IDS_TRACKHEADING_1          11
#define IDS_TRACKHEADING_2          12
#define IDS_TRACKHEADING_3          13
#define IDS_TRACKHEADING_4          14
#define IDS_QCLOSEFILE_TEXT         15
#define IDS_QCLOSEFILE_CAPTION      16
#define IDS_ACTION_CLEANEDUP        17
#define IDS_ACTION_DESTROYED        18
#define IDS_ACTION_INCREMENTED      19
#define IDS_ACTION_CREATED          20
#define IDS_SENT                    21
#define IDS_POSTED                  22
#define IDS_INPUT_DATA              23
#define IDS_TABDDD                  24
#define IDS_OUTPUT_DATA             25
#define IDS_WARM                    26
#define IDS_HOT                     27
#define IDS_UNKNOWN_CALLBACK        28
#define IDS_APPIS                   29
#define IDS_TOPICIS                 30
#define IDS_ITEMIS                  31
#define IDS_OR                      32
#define IDS_FACKREQ                 33
#define IDS_DEFERUPD                34
#define IDS_FACK                    35
#define IDS_FBUSY                   36
#define IDS_FRELEASE                37
#define IDS_FREQUESTED              38
#define IDS_ERRST0                  39
#define IDS_ERRST1                  40
#define IDS_ERRST2                  41
#define IDS_ERRST3                  42
#define IDS_ERRST4                  43
#define IDS_ERRST5                  44
#define IDS_ERRST6                  45
#define IDS_ERRST7                  46
#define IDS_ERRST8                  47
#define IDS_ERRST9                  48
#define IDS_ERRST10                 49
#define IDS_ERRST11                 50
#define IDS_ERRST12                 51
#define IDS_ERRST13                 52
#define IDS_ERRST14                 53
#define IDS_ERRST15                 54
#define IDS_ERRST16                 55
#define IDS_ERRST17                 56
#define IDS_MSG0                    57
#define IDS_MSG1                    58
#define IDS_MSG2                    59
#define IDS_MSG3                    60
#define IDS_MSG4                    61
#define IDS_MSG5                    62
#define IDS_MSG6                    63
#define IDS_MSG7                    64
#define IDS_MSG8                    65
#define IDS_TYPE0                   66
#define IDS_TYPE1                   67
#define IDS_TYPE2                   68
#define IDS_TYPE3                   69
#define IDS_TYPE4                   70
#define IDS_TYPE5                   71
#define IDS_TYPE6                   72
#define IDS_TYPE7                   73
#define IDS_TYPE8                   74
#define IDS_TYPE9                   75
#define IDS_TYPE10                  76
#define IDS_TYPE11                  77
#define IDS_TYPE12                  78
#define IDS_TYPE13                  79
#define IDS_TYPE14                  80
#define IDS_TYPE15                  81
#define IDS_INVALID_FNAME           82
#define IDS_PROF_OUT_FILE           83
#define IDS_PROF_OUT_DEBUG          84
#define IDS_PROF_OUT_SCREEN         85
#define IDS_PROF_MONITOR_STRINGHANDLES   86
#define IDS_PROF_MONITOR_INITIATES       87
#define IDS_PROF_MONITOR_DDE_MESSAGES    88
#define IDS_PROF_MONITOR_CALLBACKS       89
#define IDS_PROF_MONITOR_ERRORS          90
#define IDS_PROF_TRACK_STRINGHANDLES     91
#define IDS_PROF_TRACK_LINKS             92
#define IDS_PROF_TRACK_CONVERSATIONS     93
#define IDS_PROF_TRACK_SERVICES          94
#define IDS_PROF_TERSE              95
#define IDS_YES                     96
#define IDS_NO                      97
#define IDS_MARKDLGTITLE            98
#define IDS_SEPERATOR               99
#define IDS_MARKTEXT                100
#define IDS_LISTCLASS               101
#define IDS_LBOX                    102
#define IDS_WILD                    103
#define IDS_STRINGCLASS             104
#define IDS_FMT_CB1                 105
#define IDS_FMT_CB2                 106
#define IDS_FMT_CTXT1               107
#define IDS_FMT_DATAIS1             108
#define IDS_FMT_ER1                 109
#define IDS_FMT_EXEC1               110
#define IDS_FMT_MSG1                111
#define IDS_FMT_MSG2                112
#define IDS_FMT_STATUSIS            113
#define IDS_FMT_TRS_CB1             114
#define IDS_FMT_TRS_CB2             115
#define IDS_FMT_TRS_CTXT1           116
#define IDS_FMT_TRS_DATAIS1         117
#define IDS_FMT_TRS_ER1             118
#define IDS_FMT_TRS_EXEC1           119
#define IDS_FMT_TRS_MSG1            120
#define IDS_FMT_TRS_MSG2            121
#define IDS_FMT_TRS_STATUSIS        122
#define IDS_FMT_SH_MSG1             123
#define IDS_FMT_BADATOM    124
#define IDS_LAST                    124
// IDS_LAST marks the last string ID that is used.

#define T_ATOM                      0x0001
#define T_OPTIONHANDLE              0x0002
#define T_FORMAT                    0x0004
#define T_STATUS                    0x0008
#define T_DATAHANDLE                0x0010
#define T_STRINGHANDLE              0x0020
#define T_VALUE                     0x0040
#define T_APP                       0x0080
#define T_TOPIC                     0x0100
#define T_ITEM                      0x0200
#define T_OR                        0x0400

#define IO_FILE                     0
#define IO_DEBUG                    1
#define IO_SCREEN                   2
#define IO_COUNT                    3

#define IF_HSZ                      0
#define IF_SEND                     1
#define IF_POST                     2
#define IF_CB                       3
#define IF_ERR                      4

#define IF_COUNT                    5

#define IT_HSZS                     0
#define IT_CONVS                    1
#define IT_LINKS                    2
#define IT_SVRS                     3

#define IT_COUNT                    4

#define MAX_FNAME                   MAX_PATH
#define BUFFER_SIZE                 400
#define MAX_MARK                    32
#define MAX_DISPDATA                48      // max bytes of non-text data to dump

#define CLINES                      100
#define CCHARS                      200

extern HINSTANCE hInst;

/* macro definitions */
#define MyAlloc(cb)         (LPTSTR)LocalAlloc(LPTR, (cb))
#define MyFree(p)           (LocalUnlock((HANDLE)(p)), LocalFree((HANDLE)(p)))
#define RefString(id)       (LPTSTR)apszResources[id]
#define Type2String(type)   apszResources[IDS_TYPE0 + ((type & XTYP_MASK) >> XTYP_SHIFT)]
#define MPRT(b)             (isprint(b) ? (b) : '.')

/* prototype definitions */
BOOL InitApplication(HANDLE);
BOOL InitInstance(HANDLE, INT);
VOID CloseApp(VOID);
LONG CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK About(HWND, UINT, WPARAM, LPARAM);
BOOL SetFilters(VOID);
BOOL CALLBACK OpenDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK FilterDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
LPTSTR DisectMsgLP(UINT msg, MONMSGSTRUCT *pmms, LPTSTR pszBuf);
LPTSTR DisectWord(UINT type, UINT data, DDEML_MSG_HOOK_DATA *pdmhd, LPTSTR pstr);
LPTSTR pdf(UINT fmt);
LPTSTR DumpFormat(UINT fmt, LPTSTR pstr);
LPTSTR DdeMsg2String(UINT msg);
LPTSTR Error2String(UINT error);
LPTSTR DumpData(LPBYTE pData, UINT cb, TCHAR *szBuf, UINT fmt);
LPTSTR GetHszName(HSZ hsz);
VOID OutputString(LPTSTR pstr);
VOID GetProfile(VOID);
VOID SaveProfile(VOID);
BOOL GetProfileBoolean(LPTSTR pszKey, BOOL fDefault);
VOID SetProfileBoolean(LPTSTR pszKey, BOOL fSet);
HDDEDATA CALLBACK DdeCallback(UINT wType, UINT wFmt, HCONV hConv, HSZ hsz1,
      HSZ hsz2, HDDEDATA hData, UINT lData1, UINT lData2);
INT FAR DoDialog(LPTSTR lpTemplateName, DLGPROC lpDlgProc, UINT param,
      BOOL fRememberFocus, HWND hwndParent, HANDLE hInst);
BOOL  CALLBACK MarkDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

/* testsubs.c prototypes */

/* StringWindow structure */
typedef struct {
    INT cchLine;
    INT cLine;
    INT offBuffer;
    INT offBufferMax;
    INT offBottomLine;
    INT offOutput;
    INT cBottomLine;
    INT cLeftChar;
} STRWND;

BOOL InitTestSubs(VOID);
VOID CloseTestSubs(HANDLE hInst);
VOID NextLine(STRWND *psw);
VOID DrawString(HWND hwnd, TCHAR *sz);
VOID ClearScreen(STRWND *psw);
LONG CALLBACK StrWndProc(HWND hwnd, UINT msg, WPARAM wParam, UINT lParam);
VOID scroll(HWND hwnd, UINT msg, UINT sliderpos, UINT style);
BOOL StrWndCreate(HWND hwnd, INT cchLine, INT cLine);
VOID PaintStrWnd(HWND hwnd, LPPAINTSTRUCT pps);

#define strchr      My_mbschr
#ifdef stricmp
#undef stricmp
#endif
#define stricmp     lstrcmpi
LPTSTR _CRTAPI1 My_mbschr(LPTSTR, unsigned short);


DIALOG.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.
\******************************************************************************/

/*
 * DIALOG.H
 *
 * Header file for Dialogs
 */

#define IDC_EDIT                400
#define IDEF_VALUE              500
#define IDTX_VALUE              501


GLOBALS.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.
\******************************************************************************/

/*
 * GLOBALS.H
 *
 * Header file for DDESPY Global prototypes and Variables
 */

extern HINSTANCE hInst;
extern HICON hIcon;
extern HWND hWndString;
extern INT fhOutput;
extern OFSTRUCT ofsOpen;
extern CHAR OpenName[MAX_FNAME + 1];
extern CHAR TBuf[BUFFER_SIZE];
extern CHAR TBuf2[BUFFER_SIZE];
extern CHAR szNULL[];
extern PSTR apszResources[IDS_LAST];
extern PFNCALLBACK pfnDdeCallback;
extern HWND hwndTrack[IT_COUNT];
extern LPTSTR TrackTitle[IT_COUNT];
extern LPTSTR TrackHeading[IT_COUNT];

extern struct {                            /* profile data structure */
    BOOL fOutput[IO_COUNT];
    BOOL fFilter[IF_COUNT];
    BOOL fTrack[IT_COUNT];
} pro;


LISTS.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.
\******************************************************************************/

/*
 * LISTS.C
 *
 * This file implements a generalized multi-collumn listbox with a standard
 * frame window.
 */
#define UNICODE
#include <windows.h>
#include <windowsx.h>
#include <string.h>
#include <stdlib.h>
#include "ddespy.h"
#include "globals.h"
#include "lists.h"

int  CompareItems(LPTSTR psz1, LPTSTR psz2, INT SortCol, INT cCols);
int  CmpCols(LPTSTR psz1, LPTSTR psz2, INT SortCol);
void DrawLBItem(LPDRAWITEMSTRUCT lpdis);
long CALLBACK MCLBClientWndProc(HWND hwnd, UINT msg, WPARAM wParam, LONG lPAram);

UINT cyHeading;

#ifdef UNICODE

#define atoi    atoiW

//*********************************************************************
//
//  atoiW
//
//      Unicode version of atoi.
//

INT atoiW (LPTSTR s) {
   INT i = 0;

   while (isdigit (*s)) {
      i = i*10 + (BYTE)*s - TEXT('0');
      s++;
   }
   return i;
}

#endif

HWND CreateMCLBFrame(
                    HWND hwndParent,
                    LPTSTR lpszTitle,        // frame title string
                    UINT dwStyle,          // frame styles
                    HICON hIcon,
                    HBRUSH hbrBkgnd,        // background for heading.
                    LPTSTR lpszHeadings)     // tab delimited list of headings.  The number of
                        // headings indicate the number of collumns.
{
    static BOOL fRegistered = FALSE;
    MCLBCREATESTRUCT mclbcs;

    if (!fRegistered) {
        WNDCLASS wc;
        HDC hdc;
        TEXTMETRIC tm;

        wc.style = WS_OVERLAPPED | CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = MCLBClientWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 4;
        wc.hInstance = hInst;
        wc.hIcon = hIcon;
        wc.hCursor = NULL;
        wc.hbrBackground = hbrBkgnd;
        wc.lpszMenuName = NULL;
        wc.lpszClassName = (LPCTSTR) RefString(IDS_LISTCLASS);
        RegisterClass(&wc);

        hdc = GetDC(GetDesktopWindow());
        GetTextMetrics(hdc, &tm);
        cyHeading = tm.tmHeight;
        ReleaseDC(GetDesktopWindow(), hdc);

        fRegistered = TRUE;
    }
    mclbcs.lpszHeadings = lpszHeadings;

    return(CreateWindow((LPCTSTR) RefString(IDS_LISTCLASS),
    (LPCTSTR) lpszTitle, dwStyle,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            hwndParent, NULL, hInst, (LPVOID)&mclbcs));
}

LONG  CALLBACK MCLBClientWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    MCLBSTRUCT *pmclb;
    RECT rc;
    INT  i;

    if (msg == WM_CREATE) {
        LPTSTR psz;
        MCLBCREATESTRUCT FAR *pcs;

        pcs = (MCLBCREATESTRUCT FAR *)((LPCREATESTRUCT)lParam)->lpCreateParams;
        pmclb = (MCLBSTRUCT *)LocalAlloc(LPTR, sizeof(MCLBSTRUCT));
        psz = (LPTSTR)LocalAlloc(LPTR, sizeof(TCHAR)
      * (lstrlen(pcs->lpszHeadings) + 1));
        lstrcpy((LPTSTR)psz, pcs->lpszHeadings);
        pmclb->pszHeadings = psz;
        pmclb->cCols = 1;
while (*psz) {
   if (*psz == '\t') {
      pmclb->cCols++;
   }
   psz++;
}
        pmclb->SortCol = 0;
        SetWindowLong(hwnd, 0, (UINT)pmclb);
        GetClientRect(hwnd, &rc);
        pmclb->hwndLB = CreateWindow((LPCTSTR) RefString(IDS_LBOX),
      (LPCTSTR) szNULL,
       MYLBSTYLE | WS_VISIBLE,
               0, 0, 0, 0, hwnd, (HMENU)pmclb->cCols, hInst, NULL);
        return(pmclb->hwndLB ? 0 : -1);
    }

    pmclb = (MCLBSTRUCT *)GetWindowLong(hwnd, 0);

    switch (msg) {
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            DRAWITEMSTRUCT dis;

            BeginPaint(hwnd, &ps);
            SetBkMode(ps.hdc, TRANSPARENT);
            dis.hwndItem = hwnd;
            dis.hDC = ps.hdc;
            GetClientRect(hwnd, &dis.rcItem);
            dis.rcItem.bottom = dis.rcItem.top + cyHeading;
            dis.CtlType = ODT_BUTTON;   // hack to avoid erasure
            dis.CtlID = pmclb->cCols;
            dis.itemID = 0;
            dis.itemAction = ODA_DRAWENTIRE;
            dis.itemData = (UINT)(LPTSTR)pmclb->pszHeadings;
            dis.itemState = 0;
            DrawLBItem(&dis);
            EndPaint(hwnd, &ps);
        }
        break;

    case WM_SIZE:
        MoveWindow(pmclb->hwndLB, 0, cyHeading, LOWORD(lParam),
                HIWORD(lParam) - cyHeading, TRUE);
        break;

    case WM_LBUTTONDOWN:
        {
            HWND hwndLB;
            INT i;

            // determine which collumn the mouse landed and sort on that collumn.

            SendMessage(hwnd, WM_SETREDRAW, 0, 0);
            GetClientRect(hwnd, &rc);
            InflateRect(&rc, -1, -1);
            pmclb->SortCol = LOWORD(lParam) * pmclb->cCols / (rc.right - rc.left);
            hwndLB = CreateWindow((LPCTSTR) RefString(IDS_LBOX),
    (LPCTSTR) szNULL, MYLBSTYLE, 1, cyHeading + 1,
                    rc.right - rc.left, rc.bottom - rc.top - cyHeading,
                    hwnd, (HMENU)pmclb->cCols, hInst, NULL);
            for (i = (INT)SendMessage(pmclb->hwndLB, LB_GETCOUNT, 0, 0); i;
   i--) {
                SendMessage(hwndLB, LB_ADDSTRING, 0,
                    SendMessage(pmclb->hwndLB, LB_GETITEMDATA, i - 1, 0));
                SendMessage(pmclb->hwndLB, LB_SETITEMDATA, i - 1, 0);
            }
            ShowWindow(hwndLB, SW_SHOW);
            ShowWindow(pmclb->hwndLB, SW_HIDE);
            DestroyWindow(pmclb->hwndLB);
            pmclb->hwndLB = hwndLB;
            SendMessage(hwnd, WM_SETREDRAW, 1, 0);
            InvalidateRect(hwnd, NULL, FALSE);
        }
        break;

    case WM_DELETEITEM:

        if ((UINT)((LPDELETEITEMSTRUCT)lParam)->itemData)
            LocalFree(LocalHandle((PVOID)((LPDELETEITEMSTRUCT)lParam)->itemData));
        break;

    case WM_MEASUREITEM:
        ((LPMEASUREITEMSTRUCT)lParam)->itemHeight = cyHeading;
        break;

    case WM_DRAWITEM:
        GetClientRect(hwnd, &rc);
        // This fudge makes the collumns line up with the heading.
        ((LPDRAWITEMSTRUCT)lParam)->rcItem.right = rc.right;
        DrawLBItem((LPDRAWITEMSTRUCT)lParam);
        return(DefWindowProc(hwnd, msg, wParam, lParam));
        break;

    case WM_COMPAREITEM:
        return(CompareItems((LPTSTR)((LPCOMPAREITEMSTRUCT)lParam)->itemData1,
                (LPTSTR)((LPCOMPAREITEMSTRUCT)lParam)->itemData2,
                pmclb->SortCol,
                pmclb->cCols));
        break;

    case WM_DESTROY:
        LocalFree(LocalHandle((PVOID)pmclb->pszHeadings));
        LocalFree(LocalHandle((PVOID)pmclb));
        break;

    case WM_CLOSE:
        for (i = 0; i < IT_COUNT && (hwndTrack[i] != hwnd); i++) {
            ;
        }
        pro.fTrack[i] = FALSE;
        hwndTrack[i] = NULL;
        SetFilters();
        DestroyWindow(hwnd);
        break;

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

/*
 * Make this return FALSE if addition not needed.
 *
 * if pszSearch != NULL, searches for pszSearch - collumns may contain
 * wild strings - TEXT("*")
 * If found, the string is removed from the LB.
 * Adds pszReplace to LB.
 */
VOID AddMCLBText(LPTSTR pszSearch, LPTSTR pszReplace, HWND hwndLBFrame)
{
    MCLBSTRUCT *pmclb;
    INT lit;
    LPTSTR psz;

    pmclb = (MCLBSTRUCT *)GetWindowLong(hwndLBFrame, 0);

    SendMessage(pmclb->hwndLB, WM_SETREDRAW, 0, 0);
    if (pszSearch != NULL) {
        lit = (INT)SendMessage(pmclb->hwndLB, LB_FINDSTRING, (WPARAM)-1, (LONG)(LPTSTR)pszSearch);
        if (lit >= 0) {
            SendMessage(pmclb->hwndLB, LB_DELETESTRING, lit, 0);
        }
    }
    psz = (LPTSTR)LocalAlloc(LPTR, sizeof(TCHAR) * (lstrlen(pszReplace) + 1));
    lstrcpy(psz, pszReplace);
    SendMessage(pmclb->hwndLB, WM_SETREDRAW, 1, 0);
    SendMessage(pmclb->hwndLB, LB_ADDSTRING, 0, (LONG)(LPTSTR)psz);
}

/*
 * This function assumes that the text in cCol is an ASCII number.  0 is
 * returned if it is not found.
 */
INT GetMCLBColValue(LPTSTR pszSearch, HWND hwndLBFrame, INT  cCol)
{
    MCLBSTRUCT *pmclb;
    LPTSTR psz;
    INT lit;

    pmclb = (MCLBSTRUCT *)GetWindowLong(hwndLBFrame, 0);

    lit = (INT)SendMessage(pmclb->hwndLB, LB_FINDSTRING, (WPARAM)-1,
  (LPARAM)(LPTSTR)pszSearch);
    if (lit < 0) {
        return(0);
    }
    psz = (LPTSTR)SendMessage(pmclb->hwndLB, LB_GETITEMDATA, lit, 0);
    while (--cCol && (psz = wcschr(psz, '\t') + 1)) {
        ;
    }
    if (psz) {
        return(atoi(psz));
    } else {
        return(0);
    }
}

/*
 * Returns fFoundAndRemoved
 */
BOOL DeleteMCLBText(LPTSTR pszSearch, HWND hwndLBFrame)
{
    MCLBSTRUCT *pmclb;
    INT lit;

    pmclb = (MCLBSTRUCT *)GetWindowLong(hwndLBFrame, 0);
    lit = (INT)SendMessage(pmclb->hwndLB, LB_FINDSTRING, (WPARAM)-1,
            (LONG)(LPTSTR)pszSearch);
    if (lit >= 0) {
        SendMessage(pmclb->hwndLB, LB_DELETESTRING, lit, 0);
        return(TRUE);
    }
    return(FALSE);
}

/*
 * Returns >0 if item1 comes first, <0 if item2 comes first, 0 if ==.
 */
INT CompareItems(LPTSTR psz1, LPTSTR psz2, INT SortCol, INT cCols)
{
    INT i, Col;

    i = CmpCols(psz1, psz2, SortCol);
    if (i != 0) {
        return(i);
    }
    for (Col = 0; Col < cCols; Col++) {
        if (Col == SortCol) {
            continue;
        }
        i = CmpCols(psz1, psz2, Col);
        if (i != 0) {
            return(i);
        }
    }
    return(0);
}

INT CmpCols(LPTSTR psz1, LPTSTR psz2, INT SortCol)
{
    LPTSTR psz, pszT1, pszT2;
    INT iRet;

    while (SortCol--) {
        psz = wcschr(psz1, '\t');
        if (psz != NULL) {
            psz1 = psz + 1;
        } else {
            psz1 = psz1 + lstrlen(psz1);
        }
        psz = wcschr(psz2, '\t');
        if (psz != NULL) {
            psz2 = psz + 1;
        } else {
            psz2 = psz2 + lstrlen(psz2);
        }
    }
    pszT1 = wcschr(psz1, '\t');
    pszT2 = wcschr(psz2, '\t');

    if (pszT1) {
        *pszT1 = '\0';
    }
    if (pszT2) {
        *pszT2 = '\0';
    }

    if (!lstrcmp((LPCTSTR)RefString(IDS_WILD), psz1)
     || !lstrcmp((LPCTSTR) RefString(IDS_WILD), psz2)) {
        iRet = 0;
    } else {
        iRet = lstrcmp(psz1, psz2);
    }

    if (pszT1) {
        *pszT1 = '\t';
    }
    if (pszT2) {
        *pszT2 = '\t';
    }

    return(iRet);
}

VOID DrawLBItem(LPDRAWITEMSTRUCT lpdis)
{
    RECT rcDraw;
    INT cxSection;
    LPTSTR psz, pszEnd;

    if (!lpdis->itemData)
        return;
    if ((lpdis->itemAction & ODA_DRAWENTIRE) ||
            ((lpdis->itemAction & ODA_SELECT) &&
            (lpdis->itemState & ODS_SELECTED))) {
        rcDraw = lpdis->rcItem;
        if (lpdis->CtlType != ODT_BUTTON) { // hack to avoid erasure
            HBRUSH hbr;

            hbr = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            FillRect(lpdis->hDC, &lpdis->rcItem, hbr);
            DeleteObject(hbr);
        }
        cxSection = (rcDraw.right - rcDraw.left) / lpdis->CtlID;
        psz = (LPTSTR)(UINT)lpdis->itemData;
        rcDraw.right = rcDraw.left + cxSection;
        while (pszEnd = wcschr(psz, '\t')) {
            *pszEnd = '\0';
            DrawText(lpdis->hDC, psz, -1, &rcDraw, DT_LEFT);
            OffsetRect(&rcDraw, cxSection, 0);
            *pszEnd = '\t';
            psz = pszEnd + 1;
        }
        DrawText(lpdis->hDC, psz, -1, &rcDraw, DT_LEFT);

        if (lpdis->itemState & ODS_SELECTED)
            InvertRect(lpdis->hDC, &lpdis->rcItem);

        if (lpdis->itemState & ODS_FOCUS)
            DrawFocusRect(lpdis->hDC, &lpdis->rcItem);

    } else if (lpdis->itemAction & ODA_SELECT) {

        InvertRect(lpdis->hDC, &lpdis->rcItem);

    } else if (lpdis->itemAction & ODA_FOCUS) {

        DrawFocusRect(lpdis->hDC, &lpdis->rcItem);

    }
}


LISTS.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.
\******************************************************************************/

/*
 * LISTS.H
 *
 * Header file for multi-column listbox module.
 */

typedef struct {
    LPTSTR   lpszHeadings;
} MCLBCREATESTRUCT;

typedef struct {
    HWND    hwndLB;
    LPTSTR    pszHeadings;
    INT     cCols;
    INT     SortCol;
} MCLBSTRUCT;

#define MYLBSTYLE   WS_CHILD|WS_BORDER |LBS_SORT| \
                    WS_VSCROLL|LBS_OWNERDRAWFIXED|LBS_NOINTEGRALHEIGHT

HWND CreateMCLBFrame(
                    HWND hwndParent,
                    LPTSTR lpszTitle,       /* frame title string */
                    UINT dwStyle,          /* frame styles */
                    HICON hIcon,           /* icon */
                    HBRUSH hbrBkgnd,       /* background for heading.*/
                    LPTSTR lpszHeadings);   /* tab delimited list of headings.  */
                                           /* The number of headings indicate  */
                                           /* the number of collumns. */

VOID AddMCLBText(LPTSTR pszSearch, LPTSTR pszReplace, HWND hwndLBFrame);
INT GetMCLBColValue(LPTSTR pszSearch, HWND hwndLBFrame, int  cCol);
BOOL DeleteMCLBText(LPTSTR pszSearch, HWND hwndLBFrame);


DDESPY.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: DdeSpy.c

****************************************************************************/

#define UNICODE
#include <windows.h>                /* required for all Windows applications */
#include <windowsx.h>
#include <shellapi.h>
#include <dde.h>
#include <stdio.h>
#include <io.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "ddespy.h"
#include "lists.h"

/* GLOBAL Variables used for DDESPY */

UINT        idInst = 0;
HINSTANCE   hInst;
HICON       hIcon;
HWND        hWndString = NULL;
HWND        hwndSpy = NULL;
HANDLE      fhOutput = NULL;
OFSTRUCT    ofsOpen;
TCHAR        OpenName[MAX_FNAME + 1];
TCHAR        TBuf[BUFFER_SIZE];
TCHAR        TBuf2[BUFFER_SIZE];
TCHAR        szNULL[] = TEXT("");
LPTSTR        apszResources[IDS_LAST + 1];
PFNCALLBACK pfnDdeCallback = NULL;
HWND        hwndTrack[IT_COUNT] = { 0 };
LPTSTR        TrackTitle[IT_COUNT];
BOOL        fBlockMsg[WM_DDE_LAST - WM_DDE_FIRST + 1] = { 0 };
BOOL        fBlockCb[15] = { 0 };
LPTSTR        TrackHeading[IT_COUNT];
struct {                           /* profile data */
    BOOL fOutput[IO_COUNT];
    BOOL fFilter[IF_COUNT];
    BOOL fTrack[IT_COUNT];
    BOOL fTerse;
} pro;

BOOL LoadResourceStrings()
{
    int i, cbLeft, cbRes;
    LPTSTR psz;

    cbLeft = 0x1000;
    psz = LocalAlloc(LPTR, sizeof(TCHAR) * cbLeft);
    for (i = 0; i <= IDS_LAST; i++) {
        apszResources[i] = psz;
        cbRes = LoadString(hInst, i, psz, cbLeft) + 1;
        cbLeft -= cbRes;
        psz += cbRes;
    }
    for (i = 0; i < IT_COUNT; i++) {
        TrackTitle[i] = RefString(IDS_TRACKTITLE_1 + i);
        TrackHeading[i] = RefString(IDS_TRACKHEADING_1 + i);
    }
    lstrcpy(TBuf, RefString(IDS_DEFAULT_OUTPUT_FNAME));
    GetFullPathName(TBuf, sizeof(OpenName) / sizeof(TCHAR),
    OpenName, (LPTSTR *)TBuf2);
    return(TRUE);
}

int WINAPI WinMain(
        HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,
        int nCmdShow)
{
    MSG msg;

    UNREFERENCED_PARAMETER(lpCmdLine);

    hInst = hInstance;

    if (!LoadResourceStrings()) {
        return (FALSE);
    }

    if (!hPrevInstance)
        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)) {
        CloseApp();
        return (FALSE);
    }

    /* 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             */
        {
        TranslateMessage(&msg);    /* Translates virtual key codes           */
        DispatchMessage(&msg);     /* Dispatches message to window           */
    }
    CloseApp();
    return (msg.wParam);           /* Returns the value from PostQuitMessage */
}

BOOL InitApplication(HINSTANCE hInstance)
{
    WNDCLASS  wc;

    if (!InitTestSubs())
        return(FALSE);

    /* Fill in window class structure with parameters that describe the       */
    /* main window.                                                           */

    wc.style = 0;                    /* Class style(s).                    */
    wc.lpfnWndProc = (WNDPROC)MainWndProc;       /* Function to retrieve messages for  */
                                        /* windows of this class.             */
    wc.cbClsExtra = 0;                  /* No per-class extra data.           */
    wc.cbWndExtra = 0;                  /* No per-window extra data.          */
    wc.hInstance = hInstance;           /* Application that owns the class.   */
    hIcon = wc.hIcon = LoadIcon(hInstance, TEXT("DDESpy"));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName =  MAKEINTRESOURCE(IDR_MENU);   /* Name of menu resource in .RC file. */
    wc.lpszClassName = RefString(IDS_CLASS);

    /* Register the window class and return success/failure code. */

    return (RegisterClass(&wc));
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    RECT            Rect;
    INT             i;

    /* Save the instance handle in static variable, which will be used in  */
    /* many subsequence calls from this application to Windows.            */

    pfnDdeCallback = (PFNCALLBACK)MakeProcInstance((FARPROC)DdeCallback,
            hInstance);

    GetProfile();

    /* Create a main window for this application instance.  */

    hwndSpy = CreateWindow(
        RefString(IDS_CLASS),
        RefString(IDS_TITLE),
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT,                  /* Default horizontal position.       */
        CW_USEDEFAULT,                  /* Default vertical position.         */
        CW_USEDEFAULT,                  /* Default width.                     */
        CW_USEDEFAULT,                  /* Default height.                    */
        NULL,                           /* Overlapped windows have no parent. */
        NULL,                           /* Use the window class menu.         */
        hInstance,                      /* This instance owns this window.    */
        NULL                            /* Pointer not needed.                */
    );

    GetClientRect(hwndSpy, (LPRECT) &Rect);

    hWndString = CreateWindow(          /* String Window (class Registered in Teststubs)*/
        RefString(IDS_STRINGCLASS),
        szNULL,
        WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL,
        0,
        0,
        Rect.right - Rect.left,
        Rect.bottom - Rect.top,
        hwndSpy,
        NULL,
        hInst,
        (LPTSTR)MAKELONG(CCHARS, CLINES));

    for (i = 0; i < IT_COUNT; i++) {
        if (pro.fTrack[i]) {
            pro.fTrack[i] = FALSE;
            SendMessage(hwndSpy, WM_COMMAND,
                    GET_WM_COMMAND_MPS(IDM_TRACK_FIRST + i, 0, 0));
        }
    }

    if (!hwndSpy || !hWndString) {
        CloseApp();
        return (FALSE);
    }

    /* Make the window visible; update its client area; and return "success" */

    ShowWindow(hwndSpy, nCmdShow);  /* Show the window                        */
    UpdateWindow(hwndSpy);          /* Sends WM_PAINT message                 */

    if (SetFilters()) {
        return(FALSE);
    }

    return(TRUE);
}

VOID CloseApp()
{
    DdeUninitialize(idInst);        /* perform cleanup and store profile */
    SaveProfile();
    if (fhOutput != NULL)
        CloseHandle(fhOutput);
    UnregisterClass(RefString(IDS_CLASS), hInst);
    CloseTestSubs(hInst);
}

LONG  CALLBACK MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int i;

    switch (message) {
    case WM_CREATE:
        LoadAccelerators(hInst, MAKEINTRESOURCE(IDR_ACCEL));
        if (pro.fOutput[IO_FILE])
            fhOutput = (HANDLE)DoDialog(
                    MAKEINTRESOURCE(IDD_OPEN),
                    (DLGPROC)OpenDlg,
                    0,
                    TRUE,
                    hWnd,
                    hInst);
            pro.fOutput[IO_FILE] = (fhOutput != NULL);
        break;

    case WM_INITMENU:
        if (GetMenu(hWnd) != (HMENU)wParam)
            break;

        for (i = 0; i < IO_COUNT; i++) {
        CheckMenuItem((HMENU)wParam, IDM_OUTPUT_FIRST + i,
                pro.fOutput[i] ? MF_CHECKED : MF_UNCHECKED);
        }

        for (i = 0; i < IF_COUNT; i++) {
            CheckMenuItem((HMENU)wParam, IDM_FILTER_FIRST + i,
                pro.fFilter[i] ? MF_CHECKED : MF_UNCHECKED);
        }

        for (i = 0; i < IT_COUNT; i++) {
            CheckMenuItem((HMENU)wParam, IDM_TRACK_FIRST + i,
                pro.fTrack[i] ? MF_CHECKED : MF_UNCHECKED);
        }
        break;

    case WM_COMMAND:           /* message: command from application menu */
        switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDM_OUTPUT_FILE:
        case IDM_OUTPUT_DEBUG:
        case IDM_OUTPUT_SCREEN:
            switch (wParam) {
            case IDM_OUTPUT_FILE:
                if (fhOutput != NULL) {
                    wsprintf(TBuf, RefString(IDS_QCLOSEFILE_TEXT), OpenName);
                    if (IDYES != MessageBox(hWnd,
                            TBuf, RefString(IDS_QCLOSEFILE_CAPTION),
                            MB_YESNO | MB_ICONQUESTION)) {
                        break;
                    }
                    CloseHandle(fhOutput);
                }
                fhOutput = (HANDLE)DoDialog(
                        MAKEINTRESOURCE(IDD_OPEN),
                        (DLGPROC)OpenDlg,
                        0,
                        TRUE,
                        hWnd,
                        hInst);
                pro.fOutput[IO_FILE] = (fhOutput != NULL);
                break;

            case IDM_OUTPUT_DEBUG:
                pro.fOutput[IO_DEBUG] = !pro.fOutput[IO_DEBUG];
                break;

            case IDM_OUTPUT_SCREEN:
                pro.fOutput[IO_SCREEN] = !pro.fOutput[IO_SCREEN];
                break;

            }
            break;

        case IDM_CLEARSCREEN:
            if (hWndString) {
                HANDLE hpsw;
                STRWND *psw;

                hpsw = (HANDLE)GetWindowLong(hWndString, 0);
                psw = (STRWND *)LocalLock(hpsw);
                ClearScreen(psw);
                LocalUnlock(hpsw);
                InvalidateRect(hWndString, NULL, TRUE);
            }
            break;

        case IDM_MARK:
            DoDialog(MAKEINTRESOURCE(IDD_VALUEENTRY), (DLGPROC)MarkDlgProc, 0, TRUE, hWnd, hInst);
            break;

        case IDM_FILTER_HSZINFO:
        case IDM_FILTER_INIT_TERM:
        case IDM_FILTER_DDEMSGS:
        case IDM_FILTER_CALLBACKS:
        case IDM_FILTER_ERRORS:
            pro.fFilter[wParam - IDM_FILTER_FIRST] =
                    !pro.fFilter[wParam - IDM_FILTER_FIRST];
            SetFilters();
            break;

        case IDM_FILTER_DIALOG:
            DoDialog(MAKEINTRESOURCE(IDD_MSGFILTERS), (DLGPROC)FilterDlgProc, 0, TRUE, hWnd, hInst);
            break;

        case IDM_TRACK_HSZS:
        case IDM_TRACK_CONVS:
        case IDM_TRACK_LINKS:
        case IDM_TRACK_SVRS:
            pro.fTrack[wParam - IDM_TRACK_FIRST] =
                    !pro.fTrack[wParam - IDM_TRACK_FIRST];
            if (pro.fTrack[wParam - IDM_TRACK_FIRST]) {
                hwndTrack[wParam - IDM_TRACK_FIRST] = CreateMCLBFrame(
                        NULL,
                        TrackTitle[wParam - IDM_TRACK_FIRST],
                        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_MINIMIZE,
                        hIcon, (HBRUSH)(COLOR_APPWORKSPACE + 1),
                        TrackHeading[wParam - IDM_TRACK_FIRST]);
            } else {
                DestroyWindow(hwndTrack[wParam - IDM_TRACK_FIRST]);
                hwndTrack[wParam - IDM_TRACK_FIRST] = 0;
            }
            SetFilters();
            break;

        case IDM_ABOUT:
            DoDialog(MAKEINTRESOURCE(IDD_ABOUTBOX), (DLGPROC)About, 0, TRUE, hWnd, hInst);
            break;

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

    case WM_DESTROY:                  /* message: window being destroyed */
        for (i = IDM_TRACK_FIRST; i <= IDM_TRACK_LAST; i++) {
            if (pro.fTrack[i - IDM_TRACK_FIRST]) {
                DestroyWindow(hwndTrack[i - IDM_TRACK_FIRST]);
                hwndTrack[i - IDM_TRACK_FIRST] = 0;
            }
        }
        PostQuitMessage(0);
        break;

    case WM_SIZE:
        if (hWndString) {
            RECT rc;

            GetClientRect(hWnd, &rc);
            MoveWindow(hWndString, 0, 0, rc.right, rc.bottom, TRUE);
        }
        // fall through
    default:
        return (DefWindowProc(hWnd, message, wParam, lParam));
    }
    return (0);
}

BOOL  CALLBACK About(
                    HWND hDlg,
                    UINT message,
                    WPARAM wParam,
                    LPARAM lParam)
{
    switch (message) {
        case WM_INITDIALOG:                /* message: initialize dialog box */
            return (TRUE);

        case WM_COMMAND:                      /* message: received a command */
            if (GET_WM_COMMAND_ID(wParam, lParam) == IDOK
                || GET_WM_COMMAND_ID(wParam, lParam) == IDCANCEL) {
                EndDialog(hDlg, TRUE);        /* Exits the dialog box        */
                return (TRUE);
            }
            break;
    }
    return (FALSE);                           /* Didn't process a message    */
}

HDDEDATA CALLBACK DdeCallback(
                            UINT wType,
                            UINT wFmt,
                            HCONV hConv,
                            HSZ hsz1,
                            HSZ hsz2,
                            HDDEDATA hData,
                            UINT dwData1,
                            UINT dwData2)
{
    LPVOID pData;
    UINT cb;
    TCHAR *psz1, *psz2, *psz3;
    TCHAR *szAction;
    INT i;
    BOOL fInt = FALSE;
    wFmt;
    hConv;
    dwData1;

    switch (wType) {
    case XTYP_MONITOR:
    if (pData = DdeAccessData(hData, (LPDWORD)&cb)) {
        switch (dwData2) {
        case MF_HSZ_INFO:
            if (pro.fTrack[IT_HSZS]) {
                switch (((MONHSZSTRUCT FAR *)pData)->fsAction) {
                case MH_DELETE:
                    wsprintf(TBuf, fInt ? TEXT("0x%lx\t*\t%s(int)")
    : TEXT("0x%lx\t*\t%s"),
                            ((MONHSZSTRUCT FAR *)pData)->hsz,
                            (LPTSTR)((MONHSZSTRUCT FAR *)pData)->str);
                    i = GetMCLBColValue(TBuf, hwndTrack[IT_HSZS], 2);
                    if (i > 1) {
                        wsprintf(TBuf2, fInt ? TEXT("0x%lx\t%d\t%s(int)")
: TEXT("0x%lx\t%d\t%s"),
                                ((MONHSZSTRUCT FAR *)pData)->hsz,
                                 i - 1,
                                 (LPTSTR)((MONHSZSTRUCT FAR *)pData)->str);
                        AddMCLBText(TBuf, TBuf2, hwndTrack[IT_HSZS]);
                    } else if (i == 1) {
                        DeleteMCLBText(TBuf, hwndTrack[IT_HSZS]);
                    }
                    break;

                case MH_KEEP:
                case MH_CREATE:
                    wsprintf(TBuf, fInt ? TEXT("0x%lx\t*\t%s(int)")
    : TEXT("0x%lx\t*\t%s"),
                            ((MONHSZSTRUCT FAR *)pData)->hsz,
                            (LPTSTR)((MONHSZSTRUCT FAR *)pData)->str);
                    i = GetMCLBColValue(TBuf, hwndTrack[IT_HSZS], 2) + 1;
                    wsprintf(TBuf2, fInt ? TEXT("0x%lx\t%d\t%s(int)")
    : TEXT("0x%lx\t%d\t%s"),
                            ((MONHSZSTRUCT FAR *)pData)->hsz,
                             i,
                             (LPTSTR)((MONHSZSTRUCT FAR *)pData)->str);
                    AddMCLBText(TBuf, TBuf2, hwndTrack[IT_HSZS]);
                }
            }

            if (!pro.fFilter[IF_HSZ]) {
                return(0);
            }

            switch (((MONHSZSTRUCT FAR *)pData)->fsAction) {
            case MH_CLEANUP:
                szAction = RefString(IDS_ACTION_CLEANEDUP);
                break;

            case MH_DELETE:
                szAction = RefString(IDS_ACTION_DESTROYED);
                break;

            case MH_KEEP:
                szAction = RefString(IDS_ACTION_INCREMENTED);
                break;

            case MH_CREATE:
                szAction = RefString(IDS_ACTION_CREATED);
                break;

            default:
                DdeUnaccessData(hData);
                return(0);
            }
            if (pro.fTerse) {
                wsprintf(TBuf, TEXT("[%x:%ld] HSZ %s: %lx(%s)"),
                        ((MONHSZSTRUCT FAR *)pData)->hTask,
                        ((MONHSZSTRUCT FAR *)pData)->dwTime,
                        (LPTSTR)szAction,
                        ((MONHSZSTRUCT FAR *)pData)->hsz,
                        (LPTSTR)((MONHSZSTRUCT FAR *)pData)->str);
            } else {
                wsprintf(TBuf,
                        RefString(IDS_FMT_SH_MSG1),
                        ((MONHSZSTRUCT FAR *)pData)->hTask,
                        ((MONHSZSTRUCT FAR *)pData)->dwTime,
                        (LPTSTR)szAction,
                        ((MONHSZSTRUCT FAR *)pData)->hsz,
                        (LPTSTR)((MONHSZSTRUCT FAR *)pData)->str);
            }
            break;

        case MF_SENDMSGS:
        case MF_POSTMSGS:
            if (fBlockMsg[((MONMSGSTRUCT FAR *)pData)->wMsg - WM_DDE_FIRST]) {
                return(0);
            }
            if (pro.fTerse) {
                wsprintf(TBuf, RefString(IDS_FMT_TRS_MSG1),
                        ((MONMSGSTRUCT FAR *)pData)->hTask,
                        ((MONMSGSTRUCT FAR *)pData)->dwTime,
                        ((MONMSGSTRUCT FAR *)pData)->wParam,
                        ((MONMSGSTRUCT FAR *)pData)->hwndTo,
                        (dwData2 == MF_SENDMSGS) ? RefString(IDS_SENT) : RefString(IDS_POSTED),
                        (LPTSTR)DdeMsg2String(((MONMSGSTRUCT FAR *)pData)->wMsg));
            } else {
                wsprintf(TBuf, RefString(IDS_FMT_MSG1),
                        ((MONMSGSTRUCT FAR *)pData)->hTask,
                        ((MONMSGSTRUCT FAR *)pData)->dwTime,
                        ((MONMSGSTRUCT FAR *)pData)->hwndTo,
                        (dwData2 == MF_SENDMSGS) ? RefString(IDS_SENT) : RefString(IDS_POSTED),
                        (LPTSTR)DdeMsg2String(((MONMSGSTRUCT FAR *)pData)->wMsg));
            }
            OutputString(TBuf);
            wsprintf(TBuf, pro.fTerse ? RefString(IDS_FMT_TRS_MSG2) : RefString(IDS_FMT_MSG2),
                        ((MONMSGSTRUCT FAR *)pData)->wParam);
            DisectMsgLP(((MONMSGSTRUCT FAR *)pData)->wMsg,
                        ((MONMSGSTRUCT FAR *)pData),
                        &TBuf[lstrlen(TBuf)]);
            break;

        case MF_CALLBACKS:
            if (fBlockCb[(((MONCBSTRUCT FAR *)pData)->wType & XTYP_MASK) >> XTYP_SHIFT]) {
                return(0);
            }
            wsprintf(TBuf,
                    pro.fTerse ? RefString(IDS_FMT_TRS_CB1) : RefString(IDS_FMT_CB1),
                    ((MONCBSTRUCT FAR *)pData)->hTask,
                    ((MONCBSTRUCT FAR *)pData)->dwTime,
                    (LPTSTR)Type2String(((MONCBSTRUCT FAR *)pData)->wType));
            wsprintf(DumpFormat(((MONCBSTRUCT FAR *)pData)->wFmt, &TBuf[lstrlen(TBuf)]),
                    pro.fTerse ? RefString(IDS_FMT_TRS_CB2) : RefString(IDS_FMT_CB2),
                    (UINT)((MONCBSTRUCT FAR *)pData)->hConv,
                    ((MONCBSTRUCT FAR *)pData)->hsz1,
                    (LPTSTR)(psz1 = GetHszName(((MONCBSTRUCT FAR *)pData)->hsz1)),
                    ((MONCBSTRUCT FAR *)pData)->hsz2,
                    (LPTSTR)(psz2 = GetHszName(((MONCBSTRUCT FAR *)pData)->hsz2)),
                    ((MONCBSTRUCT FAR *)pData)->hData,
                    ((MONCBSTRUCT FAR *)pData)->dwData1,
                    ((MONCBSTRUCT FAR *)pData)->dwData2,
                    ((MONCBSTRUCT FAR *)pData)->dwRet);
            MyFree(psz1);
            MyFree(psz2);
            OutputString(TBuf);
            if (((MONCBSTRUCT FAR *)pData)->dwData1 &&
               (((MONCBSTRUCT FAR *)pData)->wType == XTYP_CONNECT ||
               ((MONCBSTRUCT FAR *)pData)->wType == XTYP_WILDCONNECT)) {
                // display proposed context
                wsprintf(TBuf,
                    pro.fTerse ? RefString(IDS_FMT_TRS_CTXT1) : RefString(IDS_FMT_CTXT1),
                    ((MONCBSTRUCT FAR *)pData)->cc.wFlags,
                    ((MONCBSTRUCT FAR *)pData)->cc.wCountryID,
                    ((MONCBSTRUCT FAR *)pData)->cc.iCodePage,
                    ((MONCBSTRUCT FAR *)pData)->cc.dwLangID,
                    ((MONCBSTRUCT FAR *)pData)->cc.dwSecurity,
                    ((MONCBSTRUCT FAR *)pData)->cc.qos.ImpersonationLevel,
                    ((MONCBSTRUCT FAR *)pData)->cc.qos.ContextTrackingMode,
                    ((MONCBSTRUCT FAR *)pData)->cc.qos.EffectiveOnly);
                OutputString(TBuf);
            }
            if (((MONCBSTRUCT FAR *)pData)->hData && ((MONCBSTRUCT FAR *)pData)->cbData) {
                wsprintf(TBuf, RefString(IDS_INPUT_DATA));
                OutputString(TBuf);
                DumpData((LPBYTE)((MONCBSTRUCT FAR *)pData)->Data,
                                 ((MONCBSTRUCT FAR *)pData)->cbData,
                                 TBuf,
                                 ((MONCBSTRUCT FAR *)pData)->wFmt);
                OutputString(TBuf);
                if (cb > MAX_DISPDATA)
                    OutputString(RefString(IDS_TABDDD));
                DdeUnaccessData(((MONCBSTRUCT FAR *)pData)->hData);
            }
            if ((((MONCBSTRUCT FAR *)pData)->wType & XCLASS_DATA) &&
                 ((MONCBSTRUCT FAR *)pData)->dwRet &&
                 ((MONCBSTRUCT FAR *)pData)->cbData) {
                wsprintf(TBuf, RefString(IDS_OUTPUT_DATA));
                OutputString(TBuf);
                DumpData((LPBYTE)((MONCBSTRUCT FAR *)pData)->Data,
                                 ((MONCBSTRUCT FAR *)pData)->cbData,
                                 TBuf,
                                 ((MONCBSTRUCT FAR *)pData)->wFmt);
                OutputString(TBuf);
                if (cb > MAX_DISPDATA)
                    OutputString(RefString(IDS_TABDDD));
                DdeUnaccessData((HDDEDATA)((MONCBSTRUCT FAR *)pData)->dwRet);
            }
            DdeUnaccessData(hData);
            return(0);
            break;

        case MF_ERRORS:
            wsprintf(TBuf, pro.fTerse ? RefString(IDS_FMT_TRS_ER1) : RefString(IDS_FMT_ER1),
                    ((MONERRSTRUCT FAR *)pData)->hTask,
                    ((MONERRSTRUCT FAR *)pData)->dwTime,
                    ((MONERRSTRUCT FAR *)pData)->wLastError,
                    (LPTSTR)Error2String(((MONERRSTRUCT FAR *)pData)->wLastError));
            break;

        case MF_LINKS:
            psz1 = GetHszName(((MONLINKSTRUCT FAR *)pData)->hszSvc);
            psz2 = GetHszName(((MONLINKSTRUCT FAR *)pData)->hszTopic);
            psz3 = GetHszName(((MONLINKSTRUCT FAR *)pData)->hszItem);
            if (!GetClipboardFormatName(((MONLINKSTRUCT FAR *)pData)->wFmt, TBuf2, BUFFER_SIZE))
                lstrcpy(TBuf2, pdf(((MONLINKSTRUCT FAR *)pData)->wFmt));
            if (!lstrcmp(RefString(IDS_HUH), TBuf2)) {
                wsprintf(TBuf2, TEXT("%d"), ((MONLINKSTRUCT FAR *)pData)->wFmt);
            }

            wsprintf(TBuf, TEXT("%s\t%s\t%s\t%s\t%s\t%lx\t%lx"),
                    (LPTSTR)psz1, (LPTSTR)psz2, (LPTSTR)psz3,
                    (LPTSTR)TBuf2,
                    ((MONLINKSTRUCT FAR *)pData)->fNoData ?
                     RefString(IDS_WARM) : RefString(IDS_HOT),
                    ((MONLINKSTRUCT FAR *)pData)->hConvClient,
                    ((MONLINKSTRUCT FAR *)pData)->hConvServer);

            if (((MONLINKSTRUCT FAR *)pData)->fEstablished) {
                AddMCLBText(TBuf, TBuf, hwndTrack[IT_LINKS]);
            } else {
                DeleteMCLBText(TBuf, hwndTrack[IT_LINKS]);
            }

            MyFree(psz1);
            MyFree(psz2);
            MyFree(psz3);
            DdeUnaccessData(hData);
            return(0);

        case MF_CONV:
            psz1 = GetHszName(((MONCONVSTRUCT FAR *)pData)->hszSvc);
            psz2 = GetHszName(((MONCONVSTRUCT FAR *)pData)->hszTopic);

            wsprintf(TBuf, TEXT("%s\t%s\t%lx\t%lx"),
                    (LPTSTR)psz1, (LPTSTR)psz2,
                    ((MONCONVSTRUCT FAR *)pData)->hConvClient,
                    ((MONCONVSTRUCT FAR *)pData)->hConvServer);

            if (((MONCONVSTRUCT FAR *)pData)->fConnect) {
                AddMCLBText(TBuf, TBuf, hwndTrack[IT_CONVS]);
            } else {
                DeleteMCLBText(TBuf, hwndTrack[IT_CONVS]);
            }

            MyFree(psz1);
            MyFree(psz2);
            DdeUnaccessData(hData);
            return(0);

        default:
            lstrcpy(TBuf, RefString(IDS_UNKNOWN_CALLBACK));
        }
        DdeUnaccessData(hData);
        OutputString(TBuf);
    }
    break;

    case XTYP_REGISTER:
    case XTYP_UNREGISTER:
        if (!pro.fTrack[IT_SVRS]) {
            return(0);
        }
        psz1 = GetHszName(hsz1);
        psz2 = GetHszName(hsz2);
        wsprintf(TBuf, TEXT("%s\t%s"), (LPTSTR)psz1, (LPTSTR)psz2);
        if (wType == XTYP_REGISTER) {
            AddMCLBText(NULL, TBuf, hwndTrack[IT_SVRS]);
        } else {
            DeleteMCLBText(TBuf, hwndTrack[IT_SVRS]);
        }
        MyFree(psz1);
        MyFree(psz2);
        break;
    }
    return(0);
}

LPTSTR DisectMsgLP(UINT msg, MONMSGSTRUCT *pmms,  LPTSTR pszBuf)
{
    static LONG m2t[] = {

    /*              LOW                       HIGH */

        MAKELONG(T_APP | T_ATOM,        T_TOPIC | T_ATOM),  // WM_DDE_INITIATE
        0,                                                  // WM_DDE_TERMINATE
        MAKELONG(T_OPTIONHANDLE,        T_ITEM | T_ATOM),   // WM_DDE_ADVISE
        MAKELONG(T_FORMAT,              T_ITEM | T_ATOM),   // WM_DDE_UNADVISE
        MAKELONG(T_APP | T_ATOM | T_OR | T_STATUS,
                                        T_TOPIC | T_ITEM | T_ATOM | T_OR | T_STRINGHANDLE),
                                                            // WM_DDE_ACK
        MAKELONG(T_DATAHANDLE,          T_ITEM | T_ATOM),   // WM_DDE_DATA
        MAKELONG(T_FORMAT,              T_ITEM | T_ATOM),   // WM_DDE_REQUEST
        MAKELONG(T_DATAHANDLE,          T_ITEM | T_ATOM),   // WM_DDE_POKE
        MAKELONG(0,                     T_STRINGHANDLE),    // WM_DDE_EXECUTE
    };

    // ASSUMED: msg is a valid DDE message!!!

    pszBuf = DisectWord(LOWORD(m2t[msg - WM_DDE_FIRST]),
                        pmms->dmhd.uiLo, &pmms->dmhd, pszBuf);
    *pszBuf++ = TEXT('\r');
    *pszBuf++ = TEXT('\n');
    *pszBuf++ = TEXT('\t');
    return(DisectWord(HIWORD(m2t[msg - WM_DDE_FIRST]),
                        pmms->dmhd.uiHi, &pmms->dmhd, pszBuf));
}

/*
 * Allocates local memory for and retrieves the string form of an HSZ.
 * Returns a pointer to the local memory or NULL if failure.
 * The string must be freed via MyFree().
 */
LPTSTR GetHszName(HSZ hsz)
{
    LPTSTR psz;
    UINT cb;

    cb = (UINT)DdeQueryString(idInst, hsz, NULL, 0, 0) + 1;
    psz = LocalAlloc (LPTR, sizeof(TCHAR) * cb);
    DdeQueryString(idInst, hsz, psz, cb, 0);
    return(psz);
}

LPTSTR DisectWord( UINT type,
UINT data,
DDEML_MSG_HOOK_DATA *pdmhd,
LPTSTR pstr)
{
    UINT wT;

    *pstr = TEXT('\0');   // in case we do nothing.

    if (type & T_ATOM) {
        wT = GlobalGetAtomName((ATOM)data, (LPTSTR)pstr, 25);
        if (wT || data == 0) {
            if (type & T_APP) {
                lstrcpy(pstr, RefString(IDS_APPIS));
                pstr += lstrlen(pstr);
            }

            if (type & T_TOPIC) {
                lstrcpy(pstr, RefString(IDS_TOPICIS));
                pstr += lstrlen(pstr);
            }

            if (type & T_ITEM) {
                lstrcpy(pstr, RefString(IDS_ITEMIS));
                pstr += lstrlen(pstr);
            }
        }
        if (wT) {
            wsprintf(pstr, TEXT("0x%x(\""), data);
            pstr += lstrlen(pstr);
            GlobalGetAtomName((ATOM)data, (LPTSTR)pstr, 25);
            pstr += wT;
            if (wT == 25) {
                *pstr++ = TEXT('.');
                *pstr++ = TEXT('.');
                *pstr++ = TEXT('.');
            }
            *pstr++ = TEXT('\"');
            *pstr++ = TEXT(')');
            *pstr = TEXT('\0');

type &= ~(T_OR | T_STRINGHANDLE);  // its an atom, so its not an object!
        } else if (data == 0) {     // could be a wild atom
            *pstr++ = TEXT('*');
            *pstr = TEXT('\0');
        } else if (type & T_OR) {
            type &= ~T_OR;   // not an atom, must be somthin else.
        } else {
            wsprintf(pstr, RefString(IDS_FMT_BADATOM), data);
            pstr += lstrlen(pstr);
        }
    }

    if (type & T_OR) {
        lstrcpy(pstr, RefString(IDS_OR));
        pstr += lstrlen(pstr);
    }

    if (type & T_OPTIONHANDLE) {
        if (pdmhd->cbData >= 4) {
            wsprintf(pstr, pro.fTerse ? RefString(IDS_FMT_TRS_STATUSIS) : RefString(IDS_FMT_STATUSIS), LOWORD(pdmhd->Data[0]));
            pstr += lstrlen(pstr);
            if (LOWORD(pdmhd->Data[0]) & DDE_FACKREQ) {
                lstrcpy(pstr, RefString(IDS_FACKREQ));
                pstr += lstrlen(pstr);
            }
            if (LOWORD(pdmhd->Data[0]) & DDE_FDEFERUPD) {
                lstrcpy(pstr, RefString(IDS_DEFERUPD));
                pstr += lstrlen(pstr);
            }
            *pstr++ = TEXT(')');
            *pstr++ = TEXT(' ');
            pstr = DumpFormat((UINT)HIWORD(pdmhd->Data[0]), pstr);
        }
    }

    if (type & T_FORMAT) {
        pstr = DumpFormat(data, pstr);
    }

    if (type & T_STATUS) {
        wsprintf(pstr, pro.fTerse ? RefString(IDS_FMT_TRS_STATUSIS) : RefString(IDS_FMT_STATUSIS), LOWORD(data));
        pstr += lstrlen(pstr);
        if (data & DDE_FACK) {
            lstrcpy(pstr, RefString(IDS_FACK));
            pstr += lstrlen(pstr);
        }
        if (data & DDE_FBUSY) {
            lstrcpy(pstr, RefString(IDS_FBUSY));
            pstr += lstrlen(pstr);
        }
        *pstr++ = TEXT(')');
        *pstr = TEXT('\0');
    }

    if (type & T_STRINGHANDLE && pdmhd->cbData) {
        WCHAR szData[16];

        memset(szData, '\0', 16 * sizeof(WCHAR));
        memcpy(szData, pdmhd->Data, min(16 * sizeof(WCHAR), pdmhd->cbData));
        szData[15] = L'\0';
        wsprintf(pstr, pro.fTerse ?
                    RefString(IDS_FMT_TRS_EXEC1) : RefString(IDS_FMT_EXEC1), (LPWSTR)szData);
        pstr += lstrlen(pstr);
        *pstr = TEXT('\0');
    }

    if (type & T_DATAHANDLE && pdmhd->cbData) {
        wsprintf(pstr, pro.fTerse ?
                    RefString(IDS_FMT_TRS_STATUSIS) : RefString(IDS_FMT_STATUSIS),
                    LOWORD(pdmhd->Data[0]));
        pstr += lstrlen(pstr);
        if (LOWORD(pdmhd->Data[0]) & DDE_FRELEASE) {
            lstrcpy(pstr, RefString(IDS_FRELEASE));
            pstr += lstrlen(pstr);
        }
        if (LOWORD(pdmhd->Data[0]) & DDE_FREQUESTED) {
            lstrcpy(pstr, RefString(IDS_FREQUESTED));
            pstr += lstrlen(pstr);
        }
        *pstr++ = TEXT(')');
        *pstr++ = TEXT(' ');
        pstr = DumpFormat(HIWORD(pdmhd->Data[0]), pstr);
        lstrcpy(pstr, pro.fTerse ? RefString(IDS_FMT_TRS_DATAIS1) : RefString(IDS_FMT_DATAIS1));
        pstr += lstrlen(pstr);
        pstr = DumpData((LPBYTE)&pdmhd->Data[1], min(28, pdmhd->cbData - 4),
                pstr, HIWORD(pdmhd->Data[0]));
    }
    return(pstr);
}

LPTSTR pdf(UINT fmt)
{
    INT i;
    static struct {
        UINT fmt;
        LPTSTR psz;
    } fmts[] = {
        { CF_TEXT             ,     TEXT("CF_TEXT")           }   ,
        { CF_UNICODETEXT      ,     TEXT("CF_UNICODETEXT")    }   ,
        { CF_BITMAP           ,     TEXT("CF_BITMAP")         }   ,
        { CF_METAFILEPICT     ,     TEXT("CF_METAFILEPICT")   }   ,
        { CF_ENHMETAFILE      ,     TEXT("CF_ENHMETAFILE")    }   ,
        { CF_SYLK             ,     TEXT("CF_SYLK")           }   ,
        { CF_DIF              ,     TEXT("CF_DIF")            }   ,
        { CF_TIFF             ,     TEXT("CF_TIFF")           }   ,
        { CF_OEMTEXT          ,     TEXT("CF_OEMTEXT")        }   ,
        { CF_DIB              ,     TEXT("CF_DIB")            }   ,
        { CF_PALETTE          ,     TEXT("CF_PALETTE")        }   ,
    };
    for (i = 0; i < 10; i++)
        if (fmts[i].fmt == fmt)
            return(fmts[i].psz);
    return(RefString(IDS_HUH));
}

LPTSTR DumpFormat(UINT fmt, LPTSTR pstr)
{
    UINT cb;

    wsprintf(pstr, TEXT("fmt=0x%x(\""), (WORD)fmt);
    pstr += lstrlen(pstr);
    if (cb = GetClipboardFormatName(fmt, pstr, 25)) {
        pstr += cb;
        *pstr++ = TEXT('\"');
        *pstr++ = TEXT(')');
    } else {
        wsprintf(pstr, TEXT("%s\")"), (LPTSTR)pdf(fmt));
        pstr += lstrlen(pstr);
    }
    return(pstr);
}

LPTSTR DumpData(LPBYTE pData, UINT cb, TCHAR *szBuf, UINT fmt)
{
    register INT i;
    LPTSTR psz = szBuf;

    while (cb) {
        if (fmt == CF_TEXT || fmt == CF_UNICODETEXT) {
            *szBuf++ = TEXT('\t');
            if (fmt == CF_UNICODETEXT) {
                *szBuf++ = TEXT('U');
            }
            *szBuf++ = TEXT('\"');
            if (fmt == CF_UNICODETEXT) {
                memcpy(szBuf, pData, cb);
            } else {
                MultiByteToWideChar(CP_ACP, 0, pData, cb, szBuf, cb / sizeof(TCHAR));
            }
            szBuf[cb - 2] = TEXT('\0');
            lstrcat(szBuf, TEXT("\""));
            cb = 0;
        } else {
            for (i = 0; i < 80 ; i++) {
                szBuf[i] = TEXT(' ');
            }
            szBuf[0] = TEXT('\t');
            i = 0;
            while (cb && (i < 16)) {
                wsprintf(&szBuf[i * 3 + 1], TEXT("%02x "), pData[0]);
                wsprintf(&szBuf[17 * 3 + i + 1], TEXT("%c"), MPRT(pData[0]));
                pData++;
                cb--;
                i++;
            }
            szBuf[i * 3 + 1] = TEXT(' ');
            szBuf[17 * 3 + i + 1] = TEXT(' ');
            szBuf[68] = TEXT('\0');
        }
        szBuf += lstrlen(szBuf);
    }
    return(szBuf);
}

LPTSTR Error2String(UINT error)
{
    static TCHAR szErr[23];

    if (error == 0) {
        lstrcpy(szErr, RefString(IDS_ZERO));
    } else if (error > DMLERR_LAST || error < DMLERR_FIRST) {
        lstrcpy(szErr, RefString(IDS_HUH));
    } else {
        lstrcpy(szErr, apszResources[IDS_ERRST0 + error - DMLERR_FIRST]);
    }
    return(szErr);
}

LPTSTR DdeMsg2String(UINT msg)
{
    static TCHAR szBadMsg[10];

    if (msg < WM_DDE_FIRST || msg > WM_DDE_LAST) {
       wsprintf (szBadMsg, TEXT("%ld"), szBadMsg);
       return (szBadMsg);
//        return((LPTSTR)itoa(msg, szBadMsg, 10));
    } else {
        return(apszResources[IDS_MSG0 + msg - WM_DDE_FIRST]);
    }
}

VOID OutputString(LPTSTR pstr)
{
    DWORD cbWritten;

    if (pro.fOutput[IO_FILE] && fhOutput != NULL) {
        static CHAR szT[200];

        WideCharToMultiByte(
                CP_ACP,
                0,
                pstr,
                -1,
                szT,
                200,
                NULL,
                NULL);
        WriteFile(fhOutput, (LPCSTR) szT, lstrlenA(szT), &cbWritten, NULL);
        WriteFile(fhOutput, (LPCSTR) "\r\n", 2, &cbWritten, NULL);
        FlushFileBuffers(fhOutput);
    }
    if (pro.fOutput[IO_DEBUG]) {
        OutputDebugString((LPTSTR)pstr);
        OutputDebugString(RefString(IDS_CRLF));
    }
    if (pro.fOutput[IO_SCREEN]) {
        if (IsWindow(hWndString))
            DrawString(hWndString, pstr);
    }
}

BOOL SetFilters()
{
    UINT cbf;

    cbf = 0;
    if (pro.fTrack[IT_HSZS] || pro.fFilter[IF_HSZ])
        cbf |= MF_HSZ_INFO;
    if (pro.fTrack[IT_LINKS])
        cbf |= MF_LINKS;
    if (pro.fTrack[IT_CONVS])
        cbf |= MF_CONV;
    if (pro.fFilter[IF_SEND])
        cbf |= MF_SENDMSGS;
    if (pro.fFilter[IF_POST])
        cbf |= MF_POSTMSGS;
    if (pro.fFilter[IF_CB])
        cbf |= MF_CALLBACKS;
    if (pro.fFilter[IF_ERR])
        cbf |= MF_ERRORS;
    return((BOOL)DdeInitialize(&idInst, pfnDdeCallback, APPCLASS_MONITOR | cbf, 0));
}

/*
 * This dialog returns a file handle to the opened file name given or NULL
 * if cancel.
 */

BOOL CALLBACK OpenDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    HANDLE fh;
    lParam;

    switch (message) {
        case WM_INITDIALOG:
            SetDlgItemText(hDlg, IDC_EDIT, (LPTSTR)OpenName);
            SendDlgItemMessage(hDlg, IDC_EDIT, EM_SETSEL,
                    GET_EM_SETSEL_MPS(0, 0x7fff));
            SetFocus(GetDlgItem(hDlg, IDC_EDIT));
            return (FALSE); /* Indicates the focus is set to a control */
            break;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDOK:
                    GetDlgItemText(hDlg, IDC_EDIT, TBuf, MAX_FNAME);
                    GetFullPathName(TBuf, sizeof(OpenName), OpenName, (LPTSTR *)TBuf2);
                    fh = CreateFile(
                            OpenName,
                            GENERIC_WRITE,
                            FILE_SHARE_READ,
                            (PSECURITY_ATTRIBUTES)NULL,
                            CREATE_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);
                    if (fh == INVALID_HANDLE_VALUE) {
                        MessageBox(hDlg, RefString(IDS_INVALID_FNAME),
                            NULL, MB_OK | MB_ICONHAND);
                        return (TRUE);
                    }

                    EndDialog(hDlg, (INT)fh);
                    return (TRUE);

                case IDCANCEL:
                    EndDialog(hDlg, 0);
                    return (FALSE);
            }
            break;
    }
    return FALSE;
}

BOOL CALLBACK FilterDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    int i;
    lParam;

    switch (message) {
    case WM_INITDIALOG:
            for (i = IDRB_WM_DDE_INITIATE; i <= IDRB_WM_DDE_EXECUTE; i++) {
                CheckDlgButton(hDlg, i, !fBlockMsg[i - IDRB_WM_DDE_INITIATE]);
            }
            for (i = IDRB_XTYP_ERROR; i <= IDRB_XTYP_WILDCONNECT; i++) {
                CheckDlgButton(hDlg, i, !fBlockCb[i - IDRB_XTYP_ERROR]);
            }
            CheckDlgButton(hDlg, IDRB_TERSE, pro.fTerse);
            return TRUE;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDOK:
                    for (i = IDRB_WM_DDE_INITIATE; i <= IDRB_WM_DDE_EXECUTE; i++) {
                        fBlockMsg[i - IDRB_WM_DDE_INITIATE] = !IsDlgButtonChecked(hDlg, i);
                    }
                    for (i = IDRB_XTYP_ERROR; i <= IDRB_XTYP_WILDCONNECT; i++) {
                        fBlockCb[i - IDRB_XTYP_ERROR] = !IsDlgButtonChecked(hDlg, i);
                    }
                    pro.fTerse = IsDlgButtonChecked(hDlg, IDRB_TERSE);
                    EndDialog(hDlg, TRUE);
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, 0);
                    break;
            }
            break;
    }
    return FALSE;
}

VOID GetProfile()
{
    pro.fOutput[IO_FILE]    = GetProfileBoolean(RefString(IDS_PROF_OUT_FILE),FALSE);
    pro.fOutput[IO_DEBUG]   = GetProfileBoolean(RefString(IDS_PROF_OUT_DEBUG),FALSE);
    pro.fOutput[IO_SCREEN]  = GetProfileBoolean(RefString(IDS_PROF_OUT_SCREEN),FALSE);

    pro.fFilter[IF_HSZ]     = GetProfileBoolean(RefString(IDS_PROF_MONITOR_STRINGHANDLES),FALSE);
    pro.fFilter[IF_SEND]    = GetProfileBoolean(RefString(IDS_PROF_MONITOR_INITIATES), FALSE);
    pro.fFilter[IF_POST]    = GetProfileBoolean(RefString(IDS_PROF_MONITOR_DDE_MESSAGES), FALSE);
    pro.fFilter[IF_CB]      = GetProfileBoolean(RefString(IDS_PROF_MONITOR_CALLBACKS), FALSE);
    pro.fFilter[IF_ERR]     = GetProfileBoolean(RefString(IDS_PROF_MONITOR_ERRORS),FALSE);

    pro.fTrack[IT_HSZS]     = GetProfileBoolean(RefString(IDS_PROF_TRACK_STRINGHANDLES), FALSE);
    pro.fTrack[IT_LINKS]    = GetProfileBoolean(RefString(IDS_PROF_TRACK_LINKS), FALSE);
    pro.fTrack[IT_CONVS]    = GetProfileBoolean(RefString(IDS_PROF_TRACK_CONVERSATIONS), FALSE);
    pro.fTrack[IT_SVRS]     = GetProfileBoolean(RefString(IDS_PROF_TRACK_SERVICES), FALSE);

    pro.fTerse              = GetProfileBoolean(RefString(IDS_PROF_TERSE), FALSE);
}

VOID SaveProfile()
{
    SetProfileBoolean(RefString(IDS_PROF_OUT_FILE), pro.fOutput[IO_FILE]  );
    SetProfileBoolean(RefString(IDS_PROF_OUT_DEBUG), pro.fOutput[IO_DEBUG] );
    SetProfileBoolean(RefString(IDS_PROF_OUT_SCREEN), pro.fOutput[IO_SCREEN]);

    SetProfileBoolean(RefString(IDS_PROF_MONITOR_STRINGHANDLES), pro.fFilter[IF_HSZ]   );
    SetProfileBoolean(RefString(IDS_PROF_MONITOR_INITIATES), pro.fFilter[IF_SEND]  );
    SetProfileBoolean(RefString(IDS_PROF_MONITOR_DDE_MESSAGES), pro.fFilter[IF_POST]  );
    SetProfileBoolean(RefString(IDS_PROF_MONITOR_CALLBACKS), pro.fFilter[IF_CB]    );
    SetProfileBoolean(RefString(IDS_PROF_MONITOR_ERRORS), pro.fFilter[IF_ERR]   );

    SetProfileBoolean(RefString(IDS_PROF_TRACK_STRINGHANDLES), pro.fTrack[IT_HSZS]   );
    SetProfileBoolean(RefString(IDS_PROF_TRACK_LINKS), pro.fTrack[IT_LINKS]  );
    SetProfileBoolean(RefString(IDS_PROF_TRACK_CONVERSATIONS), pro.fTrack[IT_CONVS]  );
    SetProfileBoolean(RefString(IDS_PROF_TRACK_SERVICES), pro.fTrack[IT_SVRS]   );

    SetProfileBoolean(RefString(IDS_PROF_TERSE), pro.fTerse   );
}

BOOL GetProfileBoolean(LPTSTR pszKey, BOOL fDefault)
{
    GetPrivateProfileString(RefString(IDS_TITLE), pszKey,
                    fDefault ? RefString(IDS_YES) : RefString(IDS_NO), TBuf,
                    sizeof(TBuf), RefString(IDS_INIFNAME));
    return(lstrcmpi(RefString(IDS_NO), TBuf));
}

VOID SetProfileBoolean(LPTSTR pszKey, BOOL fSet)
{
    WritePrivateProfileString(RefString(IDS_TITLE), pszKey,
                    fSet ? RefString(IDS_YES) : RefString(IDS_NO),
                    RefString(IDS_INIFNAME));
}

/*
 * Generic dialog invocation routine.  Handles procInstance stuff and param
 * passing.
 */
INT FAR DoDialog(
                LPTSTR lpTemplateName,
                DLGPROC lpDlgProc,
                UINT param,
                BOOL fRememberFocus,
                HWND hwndParent,
                HANDLE hInst)
{
    UINT wRet;
    HWND hwndFocus;

    if (fRememberFocus)
        hwndFocus = GetFocus();
    lpDlgProc = (DLGPROC)MakeProcInstance(lpDlgProc, hInst);
    wRet = DialogBoxParam(hInst, (LPCTSTR)lpTemplateName, hwndParent,
            lpDlgProc, param);
    FreeProcInstance((FARPROC)lpDlgProc);
    if (fRememberFocus)
        SetFocus(hwndFocus);
    return wRet;
}

BOOL CALLBACK MarkDlgProc(
                        HWND hwnd,
                        UINT msg,
                        WPARAM wParam,
                        LPARAM lParam)
{
    TCHAR szT[MAX_MARK + 1];
    lParam;

    switch (msg){
    case WM_INITDIALOG:
        SetWindowText(hwnd, RefString(IDS_MARKDLGTITLE));
        SendDlgItemMessage(hwnd, IDEF_VALUE, EM_LIMITTEXT, MAX_MARK, 0);
        SetDlgItemText(hwnd, IDEF_VALUE, RefString(IDS_SEPERATOR));
        SetDlgItemText(hwnd, IDTX_VALUE, RefString(IDS_MARKTEXT));
        return(1);
        break;

    case WM_COMMAND:
        switch (GET_WM_COMMAND_ID(wParam, lParam)) {
        case IDOK:
            GetDlgItemText(hwnd, IDEF_VALUE, szT, MAX_MARK);
            OutputString(szT);
            // fall through
        case IDCANCEL:
            EndDialog(hwnd, 0);
            break;

        default:
            return(FALSE);
        }
        break;
    }
    return(FALSE);
}

/****************************************************************************
    My_mbschr:  strchr() DBCS version
****************************************************************************/
LPTSTR _CRTAPI1 My_mbschr(
    LPTSTR psz, unsigned short uiSep)
{
    while (*psz != '\0' && *psz != uiSep) {
        psz = CharNext(psz);
    }
    if (*psz == '\0' && uiSep != '\0') {
        return NULL;
    } else {
        return psz;
    }
}


TESTSUBS.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.
\******************************************************************************/

/*
 * TESTSUBS.C
 *
 *   String formatting class, window procedure and helper functions
 */

#define UNICODE
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <string.h>
#include "ddespy.h"
#include "globals.h"

#define OFF2P(psw, off)  ((TCHAR *)psw + off)
#define BOUND(n, min, max) ((n) < (min) ? (min) : ((n) > (max) ? (max) : n))

INT cyChar;                     /* Height of a line */
INT cxChar;
INT cyDescent;

/***************************** Public  Function ****************************\
* BOOL InitTestSubs( )
*
* This routine MUST be called before using anything in this file.  Registers
* window classes, loads brushes, etc.  Returns TRUE if successful, FALSE
* otherwise.
*
\***************************************************************************/

BOOL InitTestSubs()
{
    WNDCLASS cls;

    cls.style =         0;
    cls.lpfnWndProc =   (WNDPROC)StrWndProc;
    cls.cbClsExtra =    0;
    cls.cbWndExtra =    sizeof(HANDLE);
    cls.hInstance =     hInst;
    cls.hIcon =         NULL;
    cls.hCursor =       LoadCursor(NULL, IDC_ARROW);
    cls.hbrBackground = (HBRUSH)COLOR_WINDOW;
    cls.lpszMenuName =  NULL;
    cls.lpszClassName = (LPCTSTR) RefString(IDS_STRINGCLASS);

    if (!RegisterClass((WNDCLASS FAR * ) & cls))
        return(FALSE);

    return(TRUE);
}

VOID CloseTestSubs(
HANDLE hInst)
{
    UnregisterClass((LPCTSTR) RefString(IDS_STRINGCLASS), hInst);
}

VOID NextLine( STRWND *psw)
{
    psw->offBottomLine += psw->cchLine;
    if (psw->offBottomLine == psw->offBufferMax)
        psw->offBottomLine = psw->offBuffer;
    psw->offOutput = psw->offBottomLine;
    *OFF2P(psw, psw->offOutput) = TEXT('\0');
}

/***************************** Public  Function ****************************\
* VOID DrawString(hwnd, sz)
*
* This routine prints a string in the specified StringWindow class window.
* sz is a NEAR pointer to a zero-terminated string, which can be produced
* with wsprintf().
\***************************************************************************/

VOID DrawString( HWND hwnd, TCHAR *sz)
{
    register STRWND *psw;
    INT cLines = 1;
    HANDLE hpsw;

    hpsw = (HANDLE)GetWindowLong(hwnd, 0);
    psw = (STRWND *)LocalLock(hpsw);

    NextLine(psw);
    while (*sz) {
        switch (*sz) {
        case TEXT('\r'):
            break;

        case TEXT('\n'):
            *OFF2P(psw, psw->offOutput++) = TEXT('\0');
            NextLine(psw);
            cLines++;
            break;

        default:
            *OFF2P(psw, psw->offOutput++) = *sz;
        }
        sz++;
    }
    *OFF2P(psw, psw->offOutput++) = TEXT('\0');
    LocalUnlock(hpsw);

    ScrollWindow(hwnd, 0, -((cyChar + cyDescent) * cLines), (LPRECT)NULL,
            (LPRECT)NULL);
    UpdateWindow(hwnd);
}

/***************************** Public  Function ****************************\
* "StringWindow" window class
*
* Windows of the "StringWindow" window class are simple scrolling text output
* windows that are refreshed properly as windows are rearranged.  A text buffer
* is maintained to store the characters as they are drawn.
*
* When creating a StringWindow window, lpCreateParams is actually a UINT
* containing the dimensions of the text buffer to be created, if 0L, then
* a 80 by 25 buffer is created.
*
\***************************************************************************/

LONG  CALLBACK StrWndProc(HWND hwnd, UINT msg, WPARAM wParam, UINT lParam)
{
    HANDLE hpsw;
    PAINTSTRUCT ps;
    RECT rc;

    switch (msg) {
    case WM_CREATE:
        cyChar = 14;
        cxChar = 8;
        cyDescent = 2;
        if (*(PUINT)lParam     == 0L) {
            *(PUINT)lParam     = MAKELONG(80, 50);
        }
        if (!StrWndCreate(hwnd, LOWORD(*(PUINT)lParam), HIWORD(*(PUINT)lParam)))
            return(TRUE);
        break;

    case WM_SIZE:
        InvalidateRect(hwnd, NULL, TRUE);
        break;

    case WM_DESTROY:
        if ((hpsw = (HANDLE)GetWindowLong(hwnd, 0)) != NULL)
            LocalFree(hpsw);
        break;

    case WM_ERASEBKGND:
        GetClientRect(hwnd, (LPRECT) &rc);
        FillRect((HDC) wParam, (LPRECT) &rc, GetStockObject(WHITE_BRUSH));
        break;

    case WM_VSCROLL:
        scroll(hwnd, GET_WM_VSCROLL_CODE(wParam, lParam),
                GET_WM_VSCROLL_POS(wParam, lParam), SB_VERT);
        break;

    case WM_HSCROLL:
        scroll(hwnd, GET_WM_HSCROLL_CODE(wParam, lParam),
                GET_WM_HSCROLL_POS(wParam, lParam), SB_HORZ);
        break;

    case WM_PAINT:
        BeginPaint(hwnd, &ps);
        PaintStrWnd(hwnd, &ps);
        EndPaint(hwnd, &ps);
        break;

    default:
        return(DefWindowProc(hwnd, msg, wParam, lParam));
        break;
    }
    return(0L);
}

VOID scroll(HWND hwnd, UINT msg, UINT sliderpos, UINT style)
{
    RECT rc;
    INT iPos;
    INT dn;
    HANDLE hpsw;
    register STRWND *psw;

    GetClientRect(hwnd, (LPRECT) &rc);
    iPos = GetScrollPos(hwnd, style);
    hpsw = (HANDLE)GetWindowLong(hwnd, 0);
    psw = (STRWND *)LocalLock(hpsw);

    switch (msg) {
    case SB_LINEDOWN:
        dn =  1;
        break;

    case SB_LINEUP:
        dn = -1;
        break;

    case SB_PAGEDOWN:
        if (style == SB_VERT) {
            dn = rc.bottom / (cyChar + cyDescent);
        } else {
            dn = rc.right / cxChar;
        }
        break;

    case SB_PAGEUP:
        if (style == SB_VERT) {
            dn = -rc.bottom / (cyChar + cyDescent);
        } else {
            dn = -rc.right / cxChar;
        }
        break;

    case SB_THUMBTRACK:
    case SB_THUMBPOSITION:
        dn = sliderpos-iPos;
        break;

    default:
        dn = 0;
    }
    if (style == SB_VERT) {
        if (dn = BOUND (iPos + dn, 0, psw->cLine) - iPos) {
            psw->cBottomLine -= dn;
            ScrollWindow (hwnd, 0, -dn * (cyChar + cyDescent), NULL, NULL);
            SetScrollPos (hwnd, SB_VERT, iPos + dn, TRUE);
        }
    } else /* style == SB_HORZ */ {
        if (dn = BOUND (iPos + dn, 0, psw->cchLine) - iPos) {
            psw->cLeftChar += dn;
            ScrollWindow (hwnd, -dn * cxChar, 0, NULL, NULL);
            SetScrollPos (hwnd, SB_HORZ, iPos + dn, TRUE);
        }
    }
    LocalUnlock(hpsw);
}

BOOL StrWndCreate(HWND hwnd, INT cchLine, INT cLine)
{
    register INT off;
    STRWND *psw;
    HANDLE hpsw;

    if ((hpsw = LocalAlloc(LMEM_MOVEABLE, sizeof(STRWND)
   + (sizeof (TCHAR) * cchLine * cLine))) == NULL)
        return(FALSE);
    SetWindowLong(hwnd, 0, (UINT)hpsw);

    psw = (STRWND *)LocalLock(hpsw);
    psw->cchLine       = cchLine;
    psw->cLine         = cLine;
    off                = sizeof(STRWND);
    psw->offBuffer     = off;
    psw->offBufferMax  = off + cchLine * cLine;
    psw->offBottomLine = off;
    psw->offOutput     = off;
    psw->cBottomLine   = 0;
    psw->cLeftChar     = 0;

    ClearScreen(psw);

    SetScrollRange(hwnd, SB_VERT, 0, cLine, FALSE);
    SetScrollPos(hwnd, SB_VERT, cLine, TRUE);
    SetScrollRange(hwnd, SB_HORZ, 0, cchLine, TRUE);
    LocalUnlock(hpsw);
    return(TRUE);
}

VOID ClearScreen(register STRWND *psw)
{
    register INT off;
    /*
     * Make all the lines empty
     */
    off = psw->offBuffer;
    while (off < psw->offBufferMax) {
        *OFF2P(psw, off) = TEXT('\0');
        off += psw->cchLine;
    }
}

VOID PaintStrWnd( HWND hwnd, LPPAINTSTRUCT pps)
{
    register STRWND *psw;
    register INT off;
    INT x;
    INT y;
    RECT rc, rcOut;
    HANDLE hpsw;

    SelectObject(pps->hdc, GetStockObject(SYSTEM_FIXED_FONT));
    hpsw = (HANDLE)GetWindowLong(hwnd, 0);
    psw = (STRWND *)LocalLock(hpsw);

    GetClientRect(hwnd, (LPRECT)&rc);
    if (!pps->fErase)
        FillRect(pps->hdc, (LPRECT)&rc, GetStockObject(WHITE_BRUSH));

    x = rc.left - cxChar * psw->cLeftChar;
    y = rc.bottom - cyDescent + (cyChar + cyDescent) * psw->cBottomLine;
    off = psw->offBottomLine;

    if (&pps->rcPaint != NULL)
        IntersectRect((LPRECT)&rc, (LPRECT)&rc, &pps->rcPaint);

    do {
        if (y <= rc.top - cyDescent)
            break;
        if (y - cyChar <= rc.bottom) {
            rcOut.left = x;
            rcOut.bottom = y + cyDescent;
            rcOut.right = 1000;
            rcOut.top = y - cyChar;
            DrawText(pps->hdc, (LPTSTR)OFF2P(psw, off), -1, (LPRECT)&rcOut,
                    DT_LEFT | DT_VCENTER | DT_NOCLIP | DT_EXPANDTABS |
                    DT_EXTERNALLEADING | DT_NOPREFIX | DT_TABSTOP | 0x0400);
        }
        y -= cyChar + cyDescent;
        /*
         * Back up to previous line
         */
        if (off == psw->offBuffer)
            off = psw->offBufferMax;
        off -= psw->cchLine;
    } while (off != psw->offBottomLine);
    LocalUnlock(hpsw);
}


README.TXT

Dialog Editor

The DlgEdit tool allows you to design and test a dialog box on a display
screen instead of defining dialog box statements in a resource script. You
can use DlgEdit to create new dialog boxes and to add or remove controls
from an existing dialog box. You can then compile this resource file
using the resource compiler. The binary output from the resource
compiler can then be linked into your application's executable file.

DlgEdit demonstrates the following techniques:

1. Creating dialog boxes.
2. Reading and writing resource files.
3. Creating control windows.
4. Creating a help file.
5. Creating custom controls.
6. Creating groups of controls.
7. Working with include files.


CTRLPROC.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 Header *******************************
* Module Name: ctrlproc.c
*
* Contains the window procedures for controls in the dialog being edited.
*
* Functions:
*
*    DialogCtrlWndProc()
*    CtrlWndProc()
*    ChildWndProc()
*    DrawOwnerDrawButton()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

/************************************************************************
* DialogCtrlWndProc
*
* This is the window procedure that subclasses the dialog being
* edited.  It handles a few messages that have to be special-cased
* for the dialog.  Most messages, however, are passed on to the
* generic control subclass procedure (CtrlWndProc).
*
* Arguments:
*    HWND - Handle to the dialog window
*    UINT  - window message
*    WPARAM - message parameter
*    LPARAM - message parameter
*
* Returns:
*    The default window procedure
*
************************************************************************/

WINDOWPROC DialogCtrlWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    POINT pt;
    BOOL fTracking;

    switch (msg) {
        case WM_NCPAINT:
        case WM_PAINT:
            if (gfTrackRectShown) {
                fTracking = TRUE;
                HideTrackRect();
            }
            else {
                fTracking = FALSE;
            }

            /*
             * Allow the dialog to paint first.
             */
            CallWindowProc((WNDPROC)CtrlWndProc, hwnd, msg, wParam, lParam);

            /*
             * Draw the handles if the dialog is selected.
             */
            if (gfDlgSelected) {
                HDC hDC;

                hDC = GetWindowDC(hwnd);
                DrawHandles(hwnd, hDC, TRUE);
                ReleaseDC(hwnd, hDC);
            }

            if (fTracking)
                ShowTrackRect();

            break;

        case WM_LBUTTONDOWN:
            /*
             * Discard all mouse messages during certain operations.
             */
            if (gfDisabled)
                break;

            /*
             * Also, be sure any outstanding changes get applied
             * without errors.
             */
            if (!StatusApplyChanges())
                break;

            /*
             * Check to see if we are in a normal mode.  If we are
             * in some other mode, like dragging a new control,
             * we want to ignore this mouse down and wait for the
             * mouse up.  For instance, this can happen when the
             * Duplicate command is selected from the Edit menu.
             */
            if (gState == STATE_NORMAL) {
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                MapDlgClientPoint(&pt, TRUE);

                /*
                 * Is the dialog selected and was one of its handles hit?
                 * If so, call CtrlButtonDown as if we are a drag window.
                 */
                if (gfDlgSelected &&
                        HandleHitTest(hwnd, pt.x, pt.y) != DRAG_CENTER) {
                    CtrlButtonDown(hwnd, pt.x, pt.y, TRUE);
                }
                else {
                    /*
                     * If the click was within the client area and
                     * there is not a tool selected, start an outline
                     * selection operation.  Otherwise call CtrlButtonDown
                     * which will either begin dragging the dialog or
                     * dragging the new control.
                     */
                    if (gCurTool == W_NOTHING &&
                            PtInRect(&grcDlgClient, pt))
                        OutlineSelectBegin(pt.x, pt.y);
                    else
                        CtrlButtonDown(hwnd, pt.x, pt.y, FALSE);
                }
            }

            break;

        case WM_MOUSEMOVE:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);

            /*
             * If we are not dragging a new control, then this
             * message is for the dialog, and we map it from
             * the dialog client to the dialog window.
             */
            if (gState != STATE_DRAGGINGNEW)
                MapDlgClientPoint(&pt, TRUE);

            /*
             * Now we process the mouse move message.  If the dialog is
             * selected, and if we are not dragging a new control, we
             * pass in a TRUE for fDragWindow.  This is because the
             * dialog itself does not have a separate drag window like
             * controls, and if it is selected this message needs to
             * be processed as if a drag window was hit.
             */
            CtrlMouseMove(hwnd,
                    (gfDlgSelected && gState != STATE_DRAGGINGNEW) ?
                    TRUE : FALSE, pt.x, pt.y);

            break;

        case WM_LBUTTONUP:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);

            /*
             * If we are not dragging a new control, then this
             * message is for the dialog, and we map it from
             * the dialog client to the dialog window.
             */
            if (gState != STATE_DRAGGINGNEW)
                MapDlgClientPoint(&pt, TRUE);

            CtrlButtonUp(pt.x, pt.y);

            break;

        case WM_DRAWITEM:
            return DrawOwnerDrawButton((LPDRAWITEMSTRUCT)lParam);

        default:
            return CallWindowProc(
                    (WNDPROC)CtrlWndProc, hwnd, msg, wParam, lParam);
    }

    return 0L;
}

/************************************************************************
* CtrlWndProc
*
* This is the window procedure that subclasses all of the controls.
* The dialog being edited will also pass messages that it does not
* handle through this procedure.
*
* Arguments:
*    HWND - Handle to the control window
*    UINT  - window message
*    WPARAM - message parameter
*    LPARAM - message parameter
*
* Returns:
*    The default window procedure
*
************************************************************************/

WINDOWPROC CtrlWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    POINT pt;

    switch (msg) {
        case WM_NCPAINT:
        case WM_PAINT:
            {
                BOOL fTracking;

                if (gfTrackRectShown) {
                    fTracking = TRUE;
                    HideTrackRect();
                }
                else {
                    fTracking = FALSE;
                }

                /*
                 * Allow the control to paint first.
                 */
                CallWindowProc((WNDPROC)PCFROMHWND(hwnd)->pwcd->pfnOldWndProc,
                        hwnd, msg, wParam, lParam);

                if (fTracking)
                    ShowTrackRect();
            }

            break;

        case WM_SETCURSOR:
            /*
             * Defeat the system changing cursors on us.  We do it based
             * on our own hit testing.
             */
            return TRUE;

        case WM_TIMER:
            PreDragTimeout(hwnd, TRUE);
            break;

        case WM_LBUTTONDOWN:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
            CtrlButtonDown(hwnd, pt.x, pt.y, FALSE);
            break;

        case WM_MOUSEMOVE:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
            CtrlMouseMove(hwnd, FALSE, pt.x, pt.y);
            break;

        case WM_LBUTTONUP:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
            CtrlButtonUp(pt.x, pt.y);
            break;

        case WM_LBUTTONDBLCLK:
            if (gfDisabled)
                break;

            /*
             * Also, be sure any outstanding changes get applied
             * without errors.
             */
            if (!StatusApplyChanges())
                break;

            StylesDialog();

            break;

        case WM_NCHITTEST:
            return HTCLIENT;

        case WM_RBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_RBUTTONDBLCLK:
        case WM_MBUTTONDBLCLK:
            /*
             * Helps prevent anything from happening when
             * the middle or right mouse buttons are pressed
             * (or doubleclicked).
             */
            break;

        case WM_MOUSEACTIVATE:
            /*
             * Defeat this message so that mouse clicks do not activate
             * the control.
             */
            return MA_NOACTIVATE;

        case WM_DESTROY:
            /*
             * Unsubclass the control.
             */
            SetWindowLong(hwnd, GWL_WNDPROC,
                    (DWORD)(WNDPROC)(PCFROMHWND(hwnd)->pwcd->pfnOldWndProc));

            UNSETPCINTOHWND(hwnd);

            break;

        default:
            return CallWindowProc(
                    (WNDPROC)PCFROMHWND(hwnd)->pwcd->pfnOldWndProc,
                    hwnd, msg, wParam, lParam);
    }

    return 0L;
}

/************************************************************************
* ChildWndProc
*
* This is the window procedure that subclasses all of the children
* of controls that have them.  Currently this is only comboboxes.
*
* Arguments:
*    HWND - Handle to the child control window
*    UINT  - window message
*    WPARAM - message parameter
*    LPARAM - message parameter
*
* Returns:
*    The default window procedure
*
************************************************************************/

WINDOWPROC ChildWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    /*
     * Is this a mouse message?
     */
    if (msg >= WM_MOUSEFIRST && msg <= WM_MOUSELAST) {
        POINT pt;

        /*
         * Yes, convert the coordinates and send it to the parent.
         */
        ((pt).x = (SHORT)LOWORD(lParam), (pt).y = (SHORT)HIWORD(lParam));
        ClientToScreen(hwnd, &pt);
        ScreenToClient(GetParent(hwnd), &pt);
        POINT2LONG(pt, lParam);
        SendMessage(GetParent(hwnd), msg, wParam, lParam);
        return FALSE;
    }
    else if (msg == WM_SETCURSOR) {
        /*
         * Defeat the system changing cursors on us.  We do it based
         * on our own hit testing.
         */
        return TRUE;
    }
    else if (msg == WM_NCDESTROY) {
        /*
         * Unsubclass the child.
         */
        SetWindowLong(hwnd, GWL_WNDPROC, (DWORD)(WNDPROC)GETCHILDPROC(hwnd));

        /*
         * When destroying the child window, we must be sure and
         * remove the properties associated with it.
         */
        UNSETCHILDPROC(hwnd);

        return 0;
    }
    else {
        /*
         * A benign message, call the class proc.
         */
        return CallWindowProc(GETCHILDPROC(hwnd), hwnd, msg, wParam, lParam);
    }
}

/************************************************************************
* DrawOwnerDrawButton
*
* Draws the owner-draw buttons.
*
* Arguments:
*    LPDRAWITEMSTRUCT -  owner-draw item structure
*
* Returns:
*    TRUE if the button was drawn; FALSE if the button could not be drawn.
*
************************************************************************/

BOOL DrawOwnerDrawButton(
    LPDRAWITEMSTRUCT lpdis)
{
    TCHAR szText[CCHTEXTMAX];

    if (lpdis->CtlType != ODT_BUTTON || lpdis->itemAction != ODA_DRAWENTIRE)
        return FALSE;

    RoundRect(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top,
            lpdis->rcItem.right, lpdis->rcItem.bottom, 4, 4);

    GetWindowText(lpdis->hwndItem, szText, CCHTEXTMAX);
    SetBkMode(lpdis->hDC, TRANSPARENT);

    if (gcd.hFont)
        SelectObject(lpdis->hDC, gcd.hFont);

#ifdef JAPAN
    {
        TCHAR   szTmp[CCHTEXTMAX];

        KDExpandCopy(szTmp, szText, CCHTEXTMAX);
        lstrcpy(szText, szTmp);
    }
#endif

    DrawText(lpdis->hDC, szText, -1, &lpdis->rcItem,
            DT_CENTER | DT_NOCLIP | DT_VCENTER | DT_SINGLELINE);

    return TRUE;
}


CUSTCNTL.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 Header *******************************
* Module Name: custcntl.c
*
* Contains functions to support custom controls.
*
* Functions:
*    NewCustDlgProc()
*    OpenCustomDialog()
*    SelCustDialog()
*    SelCustDlgProc()
*    RemCustDlgProc()
*    CustomWndProc()
*    AddCustomLink()
*    RemoveCustomLink()
*    CallCustomStyle()
*    CallCustomSizeToText()
*    ReadCustomProfile()
*    WriteCustomProfile()
*    NewCustInit()
*    NewCustOK()
*    OpenDLLFile()
*    CallCustomInfoA()
*    SelCustInit()
*    SelCustSelect()
*    SelCustOK()
*    RemCustInit()
*    RemCustOK()
*    AllocCUSTLINK()
*    FreeCUSTLINK()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"
#include "dlghelp.h"

#include <stdlib.h>
#include <string.h>

#include <commdlg.h>

/*
 * Minimum margin around the sample control.
 */
#define SAMPLEMARGIN                4

STATICFN VOID NewCustInit(HWND hwnd);
STATICFN BOOL NewCustOK(HWND hwnd);
STATICFN VOID OpenDLLFile(LPTSTR pszFileName);
STATICFN UINT CallCustomInfoA(LPFNCCINFOA lpfnInfoA, LPCCINFO acciW,
    INT nControls);
STATICFN VOID SelCustInit(HWND hwnd);
STATICFN VOID SelCustSelect(HWND hwnd);
STATICFN BOOL SelCustOK(HWND hwnd);
STATICFN VOID RemCustInit(HWND hwnd);
STATICFN BOOL RemCustOK(HWND hwnd);
STATICFN PCUSTLINK AllocCUSTLINK(LPCCINFO pcci, BOOL fEmulated,
    BOOL fUnicodeDLL, LPTSTR pszFileName, HANDLE hmod);
STATICFN VOID FreeCUSTLINK(PCUSTLINK pclFree);

/*
 * Used to return the pwcd that is chosen from the Select Custom
 * Control dialog.
 */
static PWINDOWCLASSDESC pwcdChosen;

/*
 * Has the window handle of the sample custom control in the
 * Select Custom Control dialog.
 */
static HWND hwndCustomSample;

/************************************************************************
* NewCustDlgProc
*
* This is the Add Custom Control dialog procedure.
*
* :
*
************************************************************************/

DIALOGPROC NewCustDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            NewCustInit(hwnd);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                    if (NewCustOK(hwnd))
                        EndDialog(hwnd, IDOK);

                    break;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    break;

                case IDHELPDLG:
                    WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                            HELPID_NEWCUST);
                    break;
            }

            return TRUE;

        default:
            return FALSE;
    }
}

/************************************************************************
* NewCustInit
*
* Processes the WM_INITDIALOG message for the New Temporary Custom Control
* dialog procedure.
*
* History:
*
************************************************************************/

STATICFN VOID NewCustInit(
    HWND hwnd)
{
    TCHAR szStyles[32];

    SendDlgItemMessage(hwnd, DID_NEWCUSTCLASS, EM_LIMITTEXT, CCHCCCLASS - 1, 0L);

    SendDlgItemMessage(hwnd, DID_NEWCUSTSTYLES, EM_LIMITTEXT, CCHHEXLONGMAX, 0L);
    wsprintf(szStyles, L"%#.8lx", awcd[W_CUSTOM].flStyles);
    SetDlgItemText(hwnd, DID_NEWCUSTSTYLES, szStyles);

    SendDlgItemMessage(hwnd, DID_NEWCUSTCX, EM_LIMITTEXT, 3, 0L);
    SetDlgItemInt(hwnd, DID_NEWCUSTCX, awcd[W_CUSTOM].cxDefault, FALSE);

    SendDlgItemMessage(hwnd, DID_NEWCUSTCY, EM_LIMITTEXT, 3, 0L);
    SetDlgItemInt(hwnd, DID_NEWCUSTCY, awcd[W_CUSTOM].cyDefault, FALSE);

    SendDlgItemMessage(hwnd, DID_NEWCUSTTEXT, EM_LIMITTEXT, CCHCCTEXT - 1, 0L);

    CenterWindow(hwnd);
}

/************************************************************************
* NewCustOK
*
* Processes the OK button from the New Temporary Custom Control dialog.
*
* History:
*
************************************************************************/

STATICFN BOOL NewCustOK(
    HWND hwnd)
{
    TCHAR szStyles[CCHHEXLONGMAX + 1];
    CCINFO cci;

    /*
     * Read the class field.  It is required.
     */
    if (!GetDlgItemText(hwnd, DID_NEWCUSTCLASS, cci.szClass, CCHCCCLASS)) {
        Message(MSG_NOCLASS);
        SetFocus(GetDlgItem(hwnd, DID_NEWCUSTCLASS));
        return FALSE;
    }

    GetDlgItemText(hwnd, DID_NEWCUSTSTYLES, szStyles, CCHHEXLONGMAX + 1);
    cci.flStyleDefault = valtoi(szStyles);

    if (!(cci.cxDefault = GetDlgItemInt(hwnd, DID_NEWCUSTCX, NULL, FALSE))) {
        Message(MSG_GTZERO, ids(IDS_WIDTH));
        SetFocus(GetDlgItem(hwnd, DID_NEWCUSTCX));
        return FALSE;
    }

    if (!(cci.cyDefault = GetDlgItemInt(hwnd, DID_NEWCUSTCY, NULL, FALSE))) {
        Message(MSG_GTZERO, ids(IDS_HEIGHT));
        SetFocus(GetDlgItem(hwnd, DID_NEWCUSTCY));
        return FALSE;
    }

    GetDlgItemText(hwnd, DID_NEWCUSTTEXT, cci.szTextDefault, CCHCCTEXT);

    cci.flOptions = 0;
    *cci.szDesc = TEXT('\0');
    cci.flExtStyleDefault = 0;
    cci.flCtrlTypeMask = 0;
    cci.cStyleFlags = 0;
    cci.aStyleFlags = NULL;
    cci.lpfnStyle = NULL;
    cci.lpfnSizeToText = NULL;
    cci.dwReserved1 = 0;
    cci.dwReserved2 = 0;

    if (AddCustomLink(&cci, TRUE, FALSE, NULL, NULL))
        return TRUE;
    else
        return FALSE;
}

/************************************************************************
* OpenCustomDialog
*
* Displays the file open dialog and allows a custom DLL to be selected
* and loaded.
*
* History:
*
************************************************************************/

VOID OpenCustomDialog(VOID)
{
    BOOL fGotName;
    OPENFILENAME ofn;
    TCHAR szNewFileName[CCHMAXPATH];
    TCHAR szFilter[CCHTEXTMAX];
    INT idPrevDlg;

    /*
     * Begin setting up the globals and the open file dialog structure.
     */
    *szNewFileName = CHAR_NULL;

    /*
     * Build up the filter string.
     */
    BuildFilterString(FILE_DLL, szFilter);

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = ghwndMain;
    ofn.hInstance = NULL;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = szNewFileName;
    ofn.nMaxFile = CCHMAXPATH;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrTitle = ids(IDS_DLLOPENTITLE);
    ofn.Flags = OFN_HIDEREADONLY | OFN_SHOWHELP | OFN_FILEMUSTEXIST;
    ofn.lpstrDefExt = ids(IDS_DLLEXT);
    ofn.lpstrInitialDir = NULL;
    ofn.lCustData = 0;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;

    /*
     * Fire off the dialog box to open the file.
     */
    EnteringDialog(DID_COMMONFILEOPENDLL, &idPrevDlg, TRUE);
    fGotName = GetOpenFileName(&ofn);
    EnteringDialog(idPrevDlg, NULL, FALSE);

    if (fGotName)
        OpenDLLFile(szNewFileName);
}

/************************************************************************
* OpenDLLFile
*
*
* History:
*
************************************************************************/

STATICFN VOID OpenDLLFile(
    LPTSTR pszFileName)
{
    HANDLE hmod;
    LPFNCCINFOA lpfnInfoA;
    LPFNCCINFOW lpfnInfoW;
    INT i;
    BOOL fSuccess = FALSE;
    BOOL fUnicodeDLL;
    PCUSTLINK pclT;
    INT nControls;
    INT nControls2;
    LPCCINFO acci;

    /*
     * Check to see if the DLL has already been loaded.
     */
    for (pclT = gpclHead; pclT &&
            (pclT->pwcd->fEmulated ||
            lstrcmpi(pclT->pszFileName, pszFileName) != 0);
            pclT = pclT->pclNext)
        ;

    /*
     * Is the DLL already loaded?
     */
    if (pclT) {
        Message(MSG_CUSTALREADYLOADED, pszFileName);
        return;
    }

    if (!(hmod = LoadLibrary(pszFileName))) {
        Message(MSG_CANTLOADDLL, pszFileName);
        return;
    }

    lpfnInfoA = (LPFNCCINFOA)GetProcAddress(hmod, "CustomControlInfoA");
    lpfnInfoW = (LPFNCCINFOW)GetProcAddress(hmod, "CustomControlInfoW");

    if (!lpfnInfoA && !lpfnInfoW) {
        Message(MSG_BADCUSTDLL, pszFileName);
        goto Error1;
    }

    if (lpfnInfoW) {
        nControls = (*lpfnInfoW)(NULL);
        fUnicodeDLL = TRUE;
    }
    else {
        nControls = (*lpfnInfoA)(NULL);
        fUnicodeDLL = FALSE;
    }

    if (!nControls) {
        Message(MSG_CANTINITDLL, pszFileName);
        goto Error1;
    }

    if (!(acci = (LPCCINFO)MyAlloc(nControls * sizeof(CCINFO))))
        goto Error1;

    if (fUnicodeDLL)
        nControls2 = (*lpfnInfoW)(acci);
    else
        nControls2 = CallCustomInfoA(lpfnInfoA, acci, nControls);

    if (!nControls2) {
        Message(MSG_CANTINITDLL, pszFileName);
        goto Error2;
    }

    for (i = 0; i < nControls; i++) {
        if (!AddCustomLink(&acci[i], FALSE, fUnicodeDLL, pszFileName, hmod))
            goto Error2;
    }

    fSuccess = TRUE;

Error2:
    MyFree(acci);

Error1:
    if (!fSuccess)
        FreeLibrary(hmod);
}

/************************************************************************
* CallCustomInfoA
*
* Thunks the call from the unicode DlgEdit to the ANSI custom control
* info procedure.
*
* History:
*
************************************************************************/

STATICFN UINT CallCustomInfoA(
    LPFNCCINFOA lpfnInfoA,
    LPCCINFO acciW,
    INT nControls)
{
    LPCCINFOA acciA;
    INT nControls2;
    INT i;
    INT j;
    LPCCSTYLEFLAGA lpFlagsA;
    LPCCSTYLEFLAGW aFlagsW = NULL;
    INT cch;

    /*
     * Allocate the appropriate number of ANSI info structures.
     */
    if (!(acciA = (LPCCINFOA)MyAlloc(nControls * sizeof(CCINFOA))))
        return 0;

    /*
     * Call the ANSI info function.
     */
    if (nControls2 = (*lpfnInfoA)(acciA)) {
        /*
         * Copy all the ANSI structures to the UNICODE structures,
         * converting strings to UNICODE as we go.
         */
        for (i = 0; i < nControls; i++) {
            MultiByteToWideChar(CP_ACP, 0, acciA[i].szClass, -1,
                    acciW[i].szClass, CCHCCCLASS);
            acciW[i].flOptions = acciA[i].flOptions;
            MultiByteToWideChar(CP_ACP, 0, acciA[i].szDesc, -1,
                    acciW[i].szDesc, CCHCCDESC);
            acciW[i].cxDefault = acciA[i].cxDefault;
            acciW[i].cyDefault = acciA[i].cyDefault;
            acciW[i].flStyleDefault = acciA[i].flStyleDefault;
            acciW[i].flExtStyleDefault = acciA[i].flExtStyleDefault;
            acciW[i].flCtrlTypeMask = acciA[i].flCtrlTypeMask;
            MultiByteToWideChar(CP_ACP, 0, acciA[i].szTextDefault, -1,
                    acciW[i].szTextDefault, CCHCCTEXT);

            /*
             * Is there a table of style flags?  If so, we need to build
             * up a table of unicode style flags.  Note that since we
             * allocate this table, the table must be freed when the
             * custom link is destroyed!
             */
            if (acciA[i].cStyleFlags) {
                /*
                 * If they specified that there are style flags, the pointer
                 * to the table must not be NULL.
                 */
                if (!acciA[i].aStyleFlags)
                    return 0;

                if (!(aFlagsW = (LPCCSTYLEFLAGW)MyAlloc(
                        acciA[i].cStyleFlags * sizeof(CCSTYLEFLAGW))))
                    return 0;

                /*
                 * Copy all the flags to the new unicode style flag table.
                 */
                for (j = 0, lpFlagsA = acciA[i].aStyleFlags;
                        j < acciA[i].cStyleFlags; j++, lpFlagsA++) {
                    aFlagsW[j].flStyle = lpFlagsA->flStyle;
                    aFlagsW[j].flStyleMask = lpFlagsA->flStyleMask;

                    cch =  lstrlenA(lpFlagsA->pszStyle) + 1;
                    aFlagsW[j].pszStyle = (LPWSTR)MyAlloc(cch * sizeof(WCHAR));

                    if (!aFlagsW[j].pszStyle)
                        return 0;

                    MultiByteToWideChar(CP_ACP, 0, lpFlagsA->pszStyle, -1,
                            aFlagsW[j].pszStyle, cch);
                }
            }

            acciW[i].cStyleFlags = acciA[i].cStyleFlags;
            acciW[i].aStyleFlags = aFlagsW;

            acciW[i].lpfnStyle = (LPFNCCSTYLE)acciA[i].lpfnStyle;
            acciW[i].lpfnSizeToText = (LPFNCCSIZETOTEXT)acciA[i].lpfnSizeToText;
            acciW[i].dwReserved1 = acciA[i].dwReserved1;
            acciW[i].dwReserved2 = acciA[i].dwReserved2;
        }
    }

    MyFree(acciA);

    return nControls2;
}

/************************************************************************
* SelCustDialog
*
* Displays the Select Custom Control dialog to choose which custom
* control tool should be selected.
*
* History:
*
************************************************************************/

PWINDOWCLASSDESC SelCustDialog(VOID)
{
    if (DlgBox(DID_SELCUST, (WNDPROC)SelCustDlgProc) == IDOK)
        return pwcdChosen;
    else
        return NULL;
}

/************************************************************************
* SelCustDlgProc
*
* This is the Select Custom Control dialog procedure.
*
* History:
*
************************************************************************/

DIALOGPROC SelCustDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            SelCustInit(hwnd);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_SELCUSTLIST:
                    switch (HIWORD(wParam)) {
                        case LBN_DBLCLK:
                            if (SelCustOK(hwnd))
                                EndDialog(hwnd, IDOK);

                            break;

                        case LBN_SELCHANGE:
                            SelCustSelect(hwnd);
                            break;
                    }

                    break;

                case IDOK:
                    if (SelCustOK(hwnd))
                        EndDialog(hwnd, IDOK);

                    break;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    break;

                case IDHELPDLG:
                    WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                            HELPID_SELCUST);
                    break;
            }

            return TRUE;

        default:
            return FALSE;
    }
}

/************************************************************************
* SelCustInit
*
* Processes the WM_INITDIALOG message for the Select Custom Control
* dialog procedure.
*
* History:
*
************************************************************************/

STATICFN VOID SelCustInit(
    HWND hwnd)
{
    HWND hwndLB;
    INT i;
    PCUSTLINK pcl;
    LPTSTR pszDesc;

    hwndLB = GetDlgItem(hwnd, DID_SELCUSTLIST);

    /*
     * Insert each custom control into the listbox.
     */
    for (pcl = gpclHead; pcl; pcl = pcl->pclNext) {
        /*
         * Use the short description, if the control has one,
         * otherwise use the class name itself.
         */
        if (pcl->pszDesc)
            pszDesc = pcl->pszDesc;
        else
            pszDesc = pcl->pwcd->pszClass;

        i = (INT)SendMessage(hwndLB, LB_ADDSTRING, 0, (DWORD)pszDesc);
        SendMessage(hwndLB, LB_SETITEMDATA, i, (DWORD)pcl);
    }

    hwndCustomSample = NULL;

    /*
     * Select the first item.
     */
    SendMessage(hwndLB, LB_SETCURSEL, 0, 0L);
    SelCustSelect(hwnd);

    CenterWindow(hwnd);
}

/************************************************************************
* SelCustSelect
*
* Called every time that a different control is selected in the list box
* in the Select Custom Control dialog.  It will create a sample control
* and show it in the Sample box.
*
* History:
*
************************************************************************/

STATICFN VOID SelCustSelect(
    HWND hwnd)
{
    HWND hwndLB;
    INT iSelect;
    PCUSTLINK pcl;
    PWINDOWCLASSDESC pwcd;
    LPTSTR pszClass;
    RECT rc;
    RECT rcParent;
    HWND hwndParent;
    INT x;
    INT y;
    INT cx;
    INT cy;
    INT cxParent;
    INT cyParent;

    hwndLB = GetDlgItem(hwnd, DID_SELCUSTLIST);

    if ((iSelect = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0)) == LB_ERR)
        return;

    /*
     * Get a pointer to the custom control link (stored in the listbox
     * items data field).
     */
    pcl = (PCUSTLINK)SendMessage(hwndLB, LB_GETITEMDATA, iSelect, 0L);
    pwcd = pcl->pwcd;

    /*
     * Get the coordinates of the Sample box.
     */
    hwndParent = GetDlgItem(hwnd, DID_SELCUSTSAMPLE);
    GetWindowRect(hwndParent, &rcParent);
    ScreenToClientRect(hwnd, &rcParent);
    cxParent = (rcParent.right - rcParent.left) - (2 * SAMPLEMARGIN);
    cyParent = (rcParent.bottom - rcParent.top) - (2 * SAMPLEMARGIN);

    /*
     * Calculate the window size of the sample control.
     */
    SetRect(&rc, 0, 0, pwcd->cxDefault, pwcd->cyDefault);
    DUToWinRect(&rc);
    cx = rc.right - rc.left;
    cy = rc.bottom - rc.top;

    /*
     * Be sure that the control can fit within the sample box.  Adjust
     * it down if necessary.
     */
    if (cx < cxParent) {
        x = ((cxParent - cx) / 2) + SAMPLEMARGIN;
    }
    else {
        x = SAMPLEMARGIN;
        cx = cxParent;
    }

    if (cy < cyParent) {
        y = ((cyParent - cy) / 2) + SAMPLEMARGIN;
    }
    else {
        y = SAMPLEMARGIN;
        cy = cyParent;
    }

    x += rcParent.left;
    y += rcParent.top;

    /*
     * Destroy the old sample.
     */
    if (hwndCustomSample)
        DestroyWindow(hwndCustomSample);

    /*
     * Get the class name to use.
     * If the control is emulated, use the special emulator class.
     * Otherwise, it is an installed custom control, and we can use
     * it's real class string.
     */
    if (pwcd->fEmulated)
        pszClass = szCustomClass;
    else
        pszClass = pwcd->pszClass;

    /*
     * Create the sample control.  We always create it visible here,
     * even if the style says it isn't.
     */
    hwndCustomSample = CreateWindow(
            pszClass,
            pwcd->pszTextDefault,
            pwcd->flStyles | WS_VISIBLE,
            x, y, cx, cy,
            hwnd,
            0,
            ghInst,
            NULL);
}

/************************************************************************
* SelCustOK
*
* Processes the final selection of a custom control from the
* Select Custom Control dialog.
*
* History:
*
************************************************************************/

STATICFN BOOL SelCustOK(
    HWND hwnd)
{
    HWND hwndLB;
    INT iSelect;
    PCUSTLINK pcl;

    hwndLB = GetDlgItem(hwnd, DID_SELCUSTLIST);

    if ((iSelect = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0)) == LB_ERR)
        return FALSE;

    /*
     * Get a pointer to the custom control link (stored in the listbox
     * items data field).
     */
    pcl = (PCUSTLINK)SendMessage(hwndLB, LB_GETITEMDATA, iSelect, 0L);

    pwcdChosen = pcl->pwcd;

    return TRUE;
}

/************************************************************************
* RemCustDlgProc
*
* This is the Remove Custom Control dialog procedure.
* It is used to de-install a custom control.
*
* History:
*
************************************************************************/

DIALOGPROC RemCustDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            RemCustInit(hwnd);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_REMCUSTLIST:
                    if (HIWORD(wParam) == LBN_DBLCLK) {
                        if (RemCustOK(hwnd))
                            EndDialog(hwnd, IDOK);
                    }

                    break;

                case IDOK:
                    if (RemCustOK(hwnd))
                        EndDialog(hwnd, IDOK);

                    break;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    break;

                case IDHELPDLG:
                    WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                            HELPID_REMCUST);
                    break;
            }

            return TRUE;

        default:
            return FALSE;
    }
}

/************************************************************************
* RemCustInit
*
* Processes the WM_INITDIALOG message for the Remove Custom Control
* dialog procedure.
*
* History:
*
************************************************************************/

STATICFN VOID RemCustInit(
    HWND hwnd)
{
    HWND hwndLB;
    INT i;
    PCUSTLINK pcl;
    LPTSTR pszDesc;

    hwndLB = GetDlgItem(hwnd, DID_REMCUSTLIST);

    /*
     * Insert each custom control into the listbox.
     */
    for (pcl = gpclHead; pcl; pcl = pcl->pclNext) {
        /*
         * Use the short description, if the control has one,
         * otherwise use the class name itself.
         */
        if (pcl->pszDesc)
            pszDesc = pcl->pszDesc;
        else
            pszDesc = pcl->pwcd->pszClass;

        i = (INT)SendMessage(hwndLB, LB_ADDSTRING, 0, (DWORD)pszDesc);
        SendMessage(hwndLB, LB_SETITEMDATA, i, (DWORD)pcl);
    }

    /*
     * Select the first item.
     */
    SendMessage(hwndLB, LB_SETCURSEL, 0, 0L);

    CenterWindow(hwnd);
}

/************************************************************************
* RemCustOK
*
* Processes the selection of a custom control to delete from the
* Remove Custom Control dialog.
*
* History:
*
************************************************************************/

STATICFN BOOL RemCustOK(
    HWND hwnd)
{
    HWND hwndLB;
    INT iSelect;
    PCUSTLINK pcl;
    NPCTYPE npc;

    hwndLB = GetDlgItem(hwnd, DID_REMCUSTLIST);

    if ((iSelect = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0)) != LB_ERR) {
        /*
         * Get a pointer to the custom control link (stored in the listbox
         * items data field).
         */
        pcl = (PCUSTLINK)SendMessage(hwndLB, LB_GETITEMDATA, iSelect, 0L);

        /*
         * Cannot delete if any controls in the current dialog
         * are of this type.
         */
        for (npc = npcHead; npc; npc = npc->npcNext) {
            if (pcl->pwcd == npc->pwcd) {
                Message(MSG_CUSTCNTLINUSE);
                return FALSE;
            }
        }

        RemoveCustomLink(pcl);
    }

    return TRUE;
}

/****************************************************************************
* CustomWndProc
*
* This is the window procedure for the emulated Custom control.
*
* History:
*
****************************************************************************/

WINDOWPROC CustomWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_PAINT:
            {
                HDC hDC;
                PAINTSTRUCT ps;
                RECT rc;
                TCHAR szText[CCHTEXTMAX];

                hDC = BeginPaint(hwnd, &ps);

                SelectObject(hDC, GetStockObject(LTGRAY_BRUSH));
                GetClientRect(hwnd, &rc);
                Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
                GetWindowText(hwnd, szText, CCHTEXTMAX);
#ifdef JAPAN
                {
                    TCHAR   szTmp[CCHTEXTMAX];

                    KDExpandCopy(szTmp, szText, CCHTEXTMAX);
                    lstrcpy(szText, szTmp);
                }
#endif
                SetBkMode(hDC, TRANSPARENT);

                if (gcd.hFont)
                    SelectObject(hDC, gcd.hFont);

                DrawText(hDC, szText, -1, &rc,
                        DT_CENTER | DT_NOCLIP | DT_VCENTER | DT_SINGLELINE);

                EndPaint(hwnd, &ps);
            }

            break;

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

    return 0;
}

/************************************************************************
* AddCustomLink
*
* Adds a new custom control to the linked list.
*
* Note that normally duplicates are checked for, but it allows multiple
* links to be added with the same class if it is a DLL control.  This
* is to support multiple control types being added from the same DLL.
* Because of this, if the caller is adding a non-emulated link, they
* are responsible for checking the list for duplicates first!
*
* There is one special case.  If it is adding a DLL link, and an
* emulated link with the same class name is found, it will walk the
* current list of controls and replace all of them with the new DLL
* control type, then delete the emulated link.  This is to support
* the case where the user creates some controls of class FOO, where
* FOO is emulated, then later loads the FOO DLL.  All controls of
* this emulated class will be changed to be the real FOO class, and
* the DLL FOO link replaces the emulated one.
*
* History:
*
************************************************************************/

PCUSTLINK AddCustomLink(
    LPCCINFO pcci,
    BOOL fEmulated,
    BOOL fUnicodeDLL,
    LPTSTR pszFileName,
    HANDLE hmod)
{
    PCUSTLINK pcl;
    PCUSTLINK pclT;
    PCUSTLINK pclPrev;
    NPCTYPE npc;
    HWND hwndOld;

    if (!(pcl = AllocCUSTLINK(pcci, fEmulated, fUnicodeDLL, pszFileName, hmod)))
        return NULL;

    if (fEmulated) {
        /*
         * Search the list for another link with the same class.
         */
        for (pclT = gpclHead;
                pclT && lstrcmpi(pclT->pwcd->pszClass, pcci->szClass) != 0;
                pclT = pclT->pclNext)
            ;

        /*
         * Was a duplicate found?
         */
        if (pclT) {
            FreeCUSTLINK(pcl);
            Message(MSG_CUSTALREADYLOADED, pcci->szClass);

            return NULL;
        }
    }
    else {
        /*
         * Search the list for another link with the same class that
         * is an emulated control.
         */
        for (pclT = gpclHead;
                pclT &&
                (lstrcmpi(pclT->pwcd->pszClass, pcci->szClass) != 0 ||
                !pclT->pwcd->fEmulated);
                pclT = pclT->pclNext)
            ;

        /*
         * Was a duplicate found?
         */
        if (pclT) {
            /*
             * At this point we know that this is a DLL link replacing
             * an existing emulated control class.  We want to go through
             * the existing controls and replace any of this class with
             * the new DLL class.  This allows a user to load a dialog
             * with some emulated controls, then later install the custom
             * DLL and have all the existing controls of that class
             * change to show the real control.
             */
            for (npc = npcHead; npc; npc = npc->npcNext) {
                /*
                 * Is the control of the type that we are replacing?
                 */

if (npc->pwcd == pclT->pwcd) {
                    hwndOld = npc->hwnd;

                    /*
                     * Unsubclass the old control window, then switch
                     * the pwcd pointer before calling CreateControl.
                     */
                    SetWindowLong(hwndOld, GWL_WNDPROC,
                            (DWORD)npc->pwcd->pfnOldWndProc);
                    UNSETPCINTOHWND(hwndOld);
                    npc->pwcd = pcl->pwcd;

                    /*
                     * Create a control of the new type in the same position.
                     */
                    if (CreateControl(npc, npc->text, npc->flStyle,
                            npc->flExtStyle, npc->id, &npc->rc,
                            hwndOld, NULL)) {
                        /*
                         * Get rid of the old control window.
                         */
                        DestroyWindow(hwndOld);

                        /*
                         * Adjust the size and position of its drag window.
                         */
                        SizeDragToControl(npc);
                    }
                }
            }

            /*
             * Remove the old link, now that all the controls that
             * used it are gone.
             */
            RemoveCustomLink(pclT);
        }
    }

    /*
     * Search for the end of the list.  Get a pointer to the last link.
     */
    for (pclT = gpclHead, pclPrev = NULL; pclT;
            pclPrev = pclT, pclT = pclT->pclNext)
        ;

    /*
     * Add the new link to the list.  Add it to the end if there are
     * other links, or initialize the head pointer if this is the
     * first one.
     */
    if (pclPrev)
        pclPrev->pclNext = pcl;
    else
        gpclHead = pcl;

    return pcl;
}

/************************************************************************
* AllocCUSTLINK
*
* Allocates a CUSTLINK structure and initializes it.  This includes
* allocating an associated WINDOWCLASSDESC structure.
*
* History:
*
************************************************************************/

STATICFN PCUSTLINK AllocCUSTLINK(
    LPCCINFO pcci,
    BOOL fEmulated,
    BOOL fUnicodeDLL,
    LPTSTR pszFileName,
    HANDLE hmod)
{
    PCUSTLINK pcl;
    PWINDOWCLASSDESC pwcd;

    if (!(pwcd = (PWINDOWCLASSDESC)MyAlloc(sizeof(WINDOWCLASSDESC))))
        return NULL;

    /*
     * Initialize the structure to be like an emulated custom control.
     */
    *pwcd = awcd[W_CUSTOM];

    /*
     * Now override some values.
     */
    pwcd->flStyles = pcci->flStyleDefault;
    pwcd->flExtStyle = pcci->flExtStyleDefault;
    pwcd->cxDefault = pcci->cxDefault;
    pwcd->cyDefault = pcci->cyDefault;
    pwcd->fEmulated = fEmulated;
    pwcd->fUnicodeDLL = fUnicodeDLL;
    pwcd->hmod = hmod;
    pwcd->cStyleFlags = pcci->cStyleFlags;
    pwcd->aStyleFlags = pcci->aStyleFlags;
    pwcd->lpfnStyle = (PROC)pcci->lpfnStyle;
    pwcd->lpfnSizeToText = (PROC)pcci->lpfnSizeToText;
    pwcd->flCtrlTypeMask = pcci->flCtrlTypeMask;

    if (pcci->flOptions & CCF_NOTEXT)
        pwcd->fHasText = FALSE;
    else
        pwcd->fHasText = TRUE;

    if (pcci->lpfnSizeToText && pwcd->fHasText)
        pwcd->fSizeToText = TRUE;

    /*
     * Copy the class name.
     */
    if (!(pwcd->pszClass = NameOrdDup(pcci->szClass)))
        goto error1;

    /*
     * Copy the default text.  This is an optional field.
     */
    if (*pcci->szTextDefault) {
        if (!(pwcd->pszTextDefault = NameOrdDup(pcci->szTextDefault)))
            goto error2;
    }
    else {
        pwcd->pszTextDefault = NULL;
    }

    if (!(pcl = (PCUSTLINK)MyAlloc(sizeof(CUSTLINK))))
        goto error3;

    /*
     * Copy the DLL file name (NULL for emulated controls).
     */
    if (pszFileName && *pszFileName) {
        if (!(pcl->pszFileName = NameOrdDup(pszFileName)))
            goto error4;
    }
    else {
        pcl->pszFileName = NULL;
    }

    /*
     * Copy the descriptive text.  This is an optional field.
     */
    if (*pcci->szDesc) {
        if (!(pcl->pszDesc = NameOrdDup(pcci->szDesc)))
            goto error5;
    }
    else {
        pcl->pszDesc = NULL;
    }

    pcl->pclNext = NULL;
    pcl->pwcd = pwcd;

    return pcl;

error5:
    if (pcl->pszFileName)
        MyFree(pcl->pszFileName);

error4:
    MyFree(pcl);

error3:
    if (pwcd->pszTextDefault)
        MyFree(pwcd->pszTextDefault);

error2:
    MyFree(pwcd->pszClass);

error1:
    MyFree(pwcd);

    return NULL;
}

/************************************************************************
* RemoveCustomLink
*
* Removes and frees a custom control link from the list.
*
* History:
*
************************************************************************/

VOID RemoveCustomLink(
    PCUSTLINK pclFree)
{
    PCUSTLINK pcl;
    PCUSTLINK pclPrev;

    /*
     * Search for the link in the list.
     */
    for (pcl = gpclHead, pclPrev = NULL; pcl != pclFree;
            pclPrev = pcl, pcl = pcl->pclNext)
        ;

    /*
     * Link was not found.
     */
    if (!pcl)
        return;

    /*
     * Remove the link from the list.
     */
    if (pclPrev)
        pclPrev->pclNext = pclFree->pclNext;
    else
        gpclHead = pclFree->pclNext;

    /*
     * Finally, free the link completely.
     */
    FreeCUSTLINK(pclFree);
}

/************************************************************************
* FreeCUSTLINK
*
* Frees a CUSTLINK structure.  This includes freeing the
* associated WINDOWCLASSDESC structure.
*
* History:
*
************************************************************************/

STATICFN VOID FreeCUSTLINK(
    PCUSTLINK pclFree)
{
    PCUSTLINK pcl;
    INT i;

    /*
     * Do we need to unload the associated DLL?
     */
    if (pclFree->pwcd->hmod) {
        /*
         * Run throught the custom list looking to see if any other
         * installed custom control has the same module handle as the
         * one that we are freeing.
         */
        for (pcl = gpclHead;
                pcl &&
                (pcl == pclFree || pcl->pwcd->hmod != pclFree->pwcd->hmod);
                pcl = pcl->pclNext)
            ;

        /*
         * If none were found, it is safe to unload this library.
         * Otherwise, we must leave the library loaded for the
         * others!
         */
        if (!pcl)
            FreeLibrary(pclFree->pwcd->hmod);
    }

    MyFree(pclFree->pwcd->pszClass);

    if (pclFree->pwcd->pszTextDefault)
        MyFree(pclFree->pwcd->pszTextDefault);

    /*
     * Is this a non-unicode DLL?  If so, then when it was loaded,
     * the dialog editor allocated a table of unicode style strings.
     * This table must now be freed.  If the DLL was a unicode one,
     * then the table pointed to by aStyleFlags belongs to the DLL,
     * and it must NOT be freed.
     */
    if (pclFree->pwcd->hmod && !pclFree->pwcd->fUnicodeDLL) {
        for (i = 0; i < pclFree->pwcd->cStyleFlags; i++)
            MyFree(pclFree->pwcd->aStyleFlags[i].pszStyle);

        if (pclFree->pwcd->aStyleFlags)
            MyFree(pclFree->pwcd->aStyleFlags);
    }

    MyFree(pclFree->pwcd);

    if (pclFree->pszFileName)
        MyFree(pclFree->pszFileName);

    if (pclFree->pszDesc)
        MyFree(pclFree->pszDesc);

    MyFree(pclFree);
}

/************************************************************************
* CallCustomStyle
*
*
* History:
*
************************************************************************/

BOOL CallCustomStyle(
    NPCTYPE npc,
    PDWORD pflStyleNew,
    PDWORD pflExtStyleNew,
    LPTSTR pszTextNew)
{
    CCSTYLE ccs;
    CCSTYLEA ccsA;
    BOOL fSuccess;
    BOOL fDefCharUsed;
    INT idPrevDlg;

    /*
     * Because we are about ready to display the dialog, we need to
     * call EnteringDialog so that the properties bar, toolbox and
     * work mode dialog get disabled.  The first parameter is the
     * dialog id, used so that the proper help will be brought up
     * for this dialog.  Since we don't have a meaningful help screen
     * for any old random custom control, just pass in a value of
     * zero, which will cause the Help Contents screen to be
     * brought up if the user presses F1 while the dialog is up.
     */
    EnteringDialog(0, &idPrevDlg, TRUE);

    /*
     * Is this a UNICODE DLL?
     */
    if (npc->pwcd->fUnicodeDLL) {
        ccs.flStyle = *pflStyleNew;
        ccs.flExtStyle = *pflExtStyleNew;
        lstrcpy(ccs.szText, pszTextNew);
        ccs.lgid = gcd.di.wLanguage;
        ccs.wReserved1 = 0;

        fSuccess = ((LPFNCCSTYLE)(*npc->pwcd->lpfnStyle))(ghwndMain, &ccs);

        if (fSuccess) {
            *pflStyleNew = ccs.flStyle;
            *pflExtStyleNew = ccs.flExtStyle;
            lstrcpy(pszTextNew, ccs.szText);
        }
    }
    else {
        ccsA.flStyle = *pflStyleNew;
        ccsA.flExtStyle = *pflExtStyleNew;
        WideCharToMultiByte(CP_ACP, 0, pszTextNew, -1, ccsA.szText, CCHCCTEXT,
                NULL, &fDefCharUsed);
        ccsA.lgid = gcd.di.wLanguage;
        ccsA.wReserved1 = 0;

        fSuccess = ((LPFNCCSTYLEA)(*npc->pwcd->lpfnStyle))(ghwndMain, &ccsA);

        if (fSuccess) {
            *pflStyleNew = ccsA.flStyle;
            *pflExtStyleNew = ccsA.flExtStyle;
            MultiByteToWideChar(CP_ACP, 0, ccsA.szText, -1, pszTextNew,
                    CCHTEXTMAX);
        }
    }

    EnteringDialog(idPrevDlg, NULL, FALSE);

    return fSuccess;
}

/************************************************************************
* CallCustomSizeToText
*
*
*
* Returns:
*
* History:
*
************************************************************************/

INT CallCustomSizeToText(
    NPCTYPE npc)
{
    INT x;
    INT xDU;
    BOOL fDefCharUsed;
    CHAR szTextA[CCHTEXTMAX];
    PSTR pszTextA;

    /*
     * Does this custom control have a SizeToText function?
     */
    if (!npc->pwcd->lpfnSizeToText)
        return -1;

    /*
     * Is this a UNICODE DLL that we are calling to?
     */
    if (npc->pwcd->fUnicodeDLL) {
        x = ((LPFNCCSIZETOTEXT)(*npc->pwcd->lpfnSizeToText))
                (npc->flStyle, npc->flExtStyle, gcd.hFont, npc->text);
    }
    else {
        /*
         * No, not a UNICODE DLL.  We must convert from UNICODE to
         * ANSI first.  NULL text cases must be handled properly.
         */
        if (npc->text) {
            WideCharToMultiByte(CP_ACP, 0, npc->text, -1, szTextA, CCHTEXTMAX,
                    NULL, &fDefCharUsed);
            pszTextA = szTextA;
        }
        else {
            pszTextA = NULL;
        }

        x = ((LPFNCCSIZETOTEXTA)(*npc->pwcd->lpfnSizeToText))
                (npc->flStyle, npc->flExtStyle, gcd.hFont, pszTextA);
    }

    /*
     * Did the call to the DLL fail?
     */
    if (x == -1)
        return -1;

    /*
     * Convert the size in pixels to a size in Dialog Units.  Be sure
     * that we round any fraction up to the next higher DU.  Since
     * we know how wide the control must be to fit the text, we must
     * be sure that the size does not get rounded down below this
     * value when converting to DU's.
     */
    xDU = MulDiv(x, 4, gcd.cxChar);
    if (MulDiv(xDU, gcd.cxChar, 4) != x)
        xDU++;

    return xDU;
}

/************************************************************************
* ReadCustomProfile
*
*
* History:
*
************************************************************************/

VOID ReadCustomProfile(VOID)
{
    TCHAR szBuf[CCHTEXTMAX];
    TCHAR szBuf2[CCHTEXTMAX];
    LPTSTR pszKey;

    GetPrivateProfileString(szCustomDLL, NULL, szEmpty,
            szBuf, CCHTEXTMAX, ids(IDS_DLGEDITINI));

    /*
     * Get the file name for each custom control DLL and load it.
     */
    for (pszKey = szBuf; *pszKey; pszKey += lstrlen(pszKey) + 1) {
        if (GetPrivateProfileString(szCustomDLL, pszKey, szEmpty,
                szBuf2, CCHTEXTMAX, ids(IDS_DLGEDITINI)))
            OpenDLLFile(szBuf2);
    }
}

/************************************************************************
* WriteCustomProfile
*
*
* History:
*
************************************************************************/

VOID WriteCustomProfile(VOID)
{
    PCUSTLINK pcl;
    PCUSTLINK pcl2;
    BOOL fSecond;

    /*
     * Clear out the section.
     */
    WritePrivateProfileString(szCustomDLL, NULL, NULL, ids(IDS_DLGEDITINI));

    for (pcl = gpclHead; pcl; pcl = pcl->pclNext) {
        /*
         * Only write out installed DLL's, not emulated controls.
         */
        if (pcl->pszFileName) {
            /*
             * Before writing out the path to the DLL, be sure
             * that this DLL's path has not been written out
             * already.  This would only occur if they have
             * multiple control types within the DLL.
             */
            for (pcl2 = gpclHead, fSecond = FALSE;
                    pcl2 && pcl2 != pcl; pcl2 = pcl2->pclNext) {
                if (lstrcmpi(pcl2->pszFileName, pcl->pszFileName) == 0) {
                    fSecond = TRUE;
                    break;
                }
            }

            if (!fSecond)
                WritePrivateProfileString(szCustomDLL, pcl->pwcd->pszClass,
                        pcl->pszFileName, ids(IDS_DLGEDITINI));
        }
    }
}


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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: dialogs.c
*
* Contains many of the dialogs and supporting routines used by the
* dialog box editor.
*
* Functions:
*
*    DlgBox()
*    EnteringDialog()
*    CreateTestDialog()
*    DestroyTestDialog()
*    SelectDialogDialog()
*    ArrangeSettingsDialog()
*    AboutDlgProc()
*    SelectDialogDlgProc()
*    SelectDialogInit()
*    SelectDialogFillLangList()
*    SelectDialogOK()
*    TestDlgProc()
*    TestInitDlg()
*    ArrangeSettingsDlgProc()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"
#include "dlghelp.h"

/*
 * Maximum number of characters in the Arrange Settings fields.
 */
#define CCHARRSETMAX    2

DIALOGPROC SelectDialogDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
STATICFN VOID SelectDialogInit(HWND hwnd);
STATICFN VOID SelectDialogFillLangList(HWND hwnd);
STATICFN BOOL SelectDialogOK(HWND hwnd);
DIALOGPROC TestDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
STATICFN VOID TestInitDlg(HWND hwnd);
DIALOGPROC ArrangeSettingsDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);

/************************************************************************
* DlgBox
*
* This function basically does a DialogBox, but it does it safely
* for the dialog editor, saving some states.
*
* Arguments:
*     INT idDlg       = Ordinal name of the dialog.
*     WNDPROC lpfnDlg = Dialog procedure to use (this function will
*                       call Make/FreeProcInstance).
*
* Returns:
*     What DialogBox returned.
*
************************************************************************/

INT DlgBox(
    INT idDlg,
    WNDPROC lpfnDlg)
{
    INT nResult;
    INT idPrevDlg;

    EnteringDialog(idDlg, &idPrevDlg, TRUE);
    nResult = DialogBox(ghInst, MAKEINTRESOURCE(idDlg), ghwndMain, (DLGPROC)lpfnDlg);
    EnteringDialog(idPrevDlg, NULL, FALSE);

    return nResult;
}

/************************************************************************
* EnteringDialog
*
* This function enables or disables things based on whether we are
* going to show one of the editor's dialogs.  It must be called
* before and after showing a dialog box.
*
* Arguments:
*   INT idDlg       - Ordinal name of the dialog.
*   PINT pidPrevDlg - Points to where to save the id of the previous
*                     (current) dialog.  If fEntering is FALSE, this
*                     is not used and should be NULL.
*   BOOL fEntering  - TRUE if about ready to show the dialog.  FALSE if
*                     the dialog was just dismissed.  For the FALSE case,
*                     the idDlg should be zero, or the id of the previous
*                     dialog.
*
************************************************************************/

VOID EnteringDialog(
    INT idDlg,
    PINT pidPrevDlg,
    BOOL fEntering)
{
    /*
     * If we are entering a new dialog, save the previous dialog
     * in the place specified.
     */
    if (fEntering)
        *pidPrevDlg = gidCurrentDlg;

    gfDisabled = fEntering;
    gidCurrentDlg = idDlg;
    StatusSetEnable();

    if (ghwndToolbox)
        EnableWindow(ghwndToolbox, !fEntering);
}

/************************************************************************
* SelectDialogDialog
*
* This function saves the current dialog box in the resource in
* memory, then it puts up a dialog box with a list of dialog
* boxes in the resource.  Finally it puts up the selected dialog.
*
************************************************************************/

VOID SelectDialogDialog(VOID)
{
    if (!SynchDialogResource())
        return;

    DlgBox(DID_SELECTDIALOG, (WNDPROC)SelectDialogDlgProc);
}

/************************************************************************
* SelectDialogDlgProc
*
* This is the View Dialog dialog procedure.
*
* Arguments:
*    HWND - handle to the dialog
*    UINT - window message
*    WPARAM - message parameter
*    LPARAM - message parameter
*
* Returns:
*    Default dialog return.
*
************************************************************************/

DIALOGPROC SelectDialogDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            SelectDialogInit(hwnd);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_SELECTDIALOGNAMELIST:
                    switch (HIWORD(wParam)) {
                        case LBN_SELCHANGE:
                            SelectDialogFillLangList(hwnd);
                            break;

                        case LBN_DBLCLK:
                            if (SelectDialogOK(hwnd))
                                EndDialog(hwnd, IDOK);

                            break;
                    }

                    break;

                case DID_SELECTDIALOGLANGLIST:
                    switch (HIWORD(wParam)) {
                        case LBN_DBLCLK:
                            if (SelectDialogOK(hwnd))
                                EndDialog(hwnd, IDOK);

                            break;
                    }

                    break;

                case IDOK:
                    if (SelectDialogOK(hwnd))
                        EndDialog(hwnd, IDOK);

                    break;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    break;

                case IDHELPDLG:
                    WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                            HELPID_SELECTDIALOG);
                    break;
            }

            return TRUE;

        default:
            return FALSE;
    }
}

/************************************************************************
* SelectDialogInit
*
* Processes the WM_INITDIALOG message for the Select Dialog dialog
* procedure.
*
* This function fills the select dialog listbox with the names or
* numbers of all the dialogs in the resource list.  If the dialog has
* a name, that is used.  If it just has an ordinal, then the decimal
* ascii string for the ordinal number is used.
*
* Arguments:
*    HWND - handle to the dialog
*
************************************************************************/

STATICFN VOID SelectDialogInit(
    HWND hwnd)
{
    HWND hwndNameLB;
    TCHAR szName[CCHTEXTMAX];
    INT i;
    INT cDlgsAdded;
    INT iSelect;
    PRESLINK prl;
    PRESLINK prl2;
    LPTSTR pszName;
    NPLABEL npLabel;

    hwndNameLB = GetDlgItem(hwnd, DID_SELECTDIALOGNAMELIST);
    cDlgsAdded = 0;

    /*
     * Insert each dialog found in the resource.
     */
    for (prl = gprlHead; prl; prl = prl->prlNext) {
        if (prl->fDlgResource) {
            /*
             * Check to be sure we have not added a dialog with this
             * name already (but perhaps a different language).
             */
            for (prl2 = gprlHead; prl2 != prl; prl2 = prl2->prlNext) {
                if (prl2->fDlgResource) {
                    if (NameOrdCmp(prl2->pszName, prl->pszName) == 0)
                        break;
                }
            }

            if (prl2 == prl) {
                /*
                 * If the name is an ordinal, display a number.  If there
                 * happens to be a define for this number, display that
                 * instead, however.
                 *
                 * Note that we do not ever show it in hex, because
                 * rc.exe does not parse hex ordinals for dialogs, so
                 * we never show it in that format.
                 */
                if (IsOrd(prl->pszName)) {
                    if (npLabel = FindID(OrdID(prl->pszName), plInclude))
                        pszName = npLabel->pszLabel;
                    else
                        pszName = itoaw(OrdID(prl->pszName), szName, 10);
                }
                else {
                    pszName = prl->pszName;
                }

                i = (INT)SendMessage(hwndNameLB, LB_ADDSTRING, 0, (DWORD)pszName);
                SendMessage(hwndNameLB, LB_SETITEMDATA, i, (DWORD)prl);
                cDlgsAdded++;
            }
        }
    }

    /*
     * If there is a current dialog, search for it and
     * make it the default selected item.
     */
    iSelect = 0;
    if (gcd.prl) {
        for (i = 0; i < cDlgsAdded; i++) {
            prl = (PRESLINK)SendMessage(hwndNameLB, LB_GETITEMDATA, i, 0L);
            if (NameOrdCmp(prl->pszName, gcd.prl->pszName) == 0) {
                iSelect = i;
                break;
            }
        }
    }

    SendMessage(hwndNameLB, LB_SETCURSEL, iSelect, 0L);

    SelectDialogFillLangList(hwnd);

    CenterWindow(hwnd);
}

/************************************************************************
* SelectDialogFillLangList
*
* Fills the listbox with the list of dialogs matching the specified name
*
* Arguments:
*    HWND - handle to the dialog editor window
*
************************************************************************/

STATICFN VOID SelectDialogFillLangList(
    HWND hwnd)
{
    TCHAR szLang[CCHTEXTMAX];
    HWND hwndNameLB;
    HWND hwndLangLB;
    PRESLINK prl;
    INT i;
    INT iSelect;
    WORD wPrimary;
    WORD wSubLang;
    INT iLang;
    INT iSubLang;
    LPTSTR pszName;
    INT cItems;

    hwndNameLB = GetDlgItem(hwnd, DID_SELECTDIALOGNAMELIST);
    hwndLangLB = GetDlgItem(hwnd, DID_SELECTDIALOGLANGLIST);

    SendMessage(hwndLangLB, LB_RESETCONTENT, 0, 0);

    if ((iSelect = (INT)SendMessage(hwndNameLB, LB_GETCURSEL, 0, 0)) == LB_ERR)
        return;

    /*
     * Get the name of the dialog selected in the Name listbox
     * (the reslink pointer is stored in the listbox items data field).
     */
    prl = (PRESLINK)SendMessage(hwndNameLB, LB_GETITEMDATA, iSelect, 0L);
    pszName = prl->pszName;

    /*
     * Insert each dialog found in the resource that matches that name.
     */
    for (prl = gprlHead; prl; prl = prl->prlNext) {
        if (prl->fDlgResource && NameOrdCmp(prl->pszName, pszName) == 0) {
            wPrimary = (WORD)PRIMARYLANGID(prl->wLanguage);
            for (iLang = 0; iLang < gcLanguages; iLang++) {
                if (gaLangTable[iLang].wPrimary == wPrimary) {
                    break;
                }
            }

            wSubLang = SUBLANGID(prl->wLanguage);
            for (iSubLang = 0;
                    iSubLang < gaLangTable[iLang].cSubLangs;
                    iSubLang++) {
                if (wSubLang == gaLangTable[iLang].asl[iSubLang].wSubLang) {
                    break;
                }
            }

            wsprintf(szLang, L"%s, %s",
                    ids(gaLangTable[iLang].idsLangDesc),
                    ids(gaLangTable[iLang].asl[iSubLang].idsSubLangDesc));

            i = (INT)SendMessage(hwndLangLB, LB_ADDSTRING, 0, (DWORD)szLang);
            SendMessage(hwndLangLB, LB_SETITEMDATA, i, (DWORD)prl);
        }
    }

    iSelect = 0;
    cItems = (INT)SendMessage(hwndLangLB, LB_GETCOUNT, 0, 0);
    if (gcd.prl && NameOrdCmp(gcd.prl->pszName, pszName) == 0) {
        for (i = 0; i < cItems; i++) {
            prl = (PRESLINK)SendMessage(hwndLangLB, LB_GETITEMDATA, i, 0);
            if (gcd.prl == prl) {
                iSelect = i;
                break;
            }
        }
    }

    SendMessage(hwndLangLB, LB_SETCURSEL, iSelect, 0L);
}

/************************************************************************
* SelectDialogOK
*
* Processes the selection of a new dialog from the Select Dialog
* dialog procedure.
*
* Arguments:
*    HWND - handle to the select dialog box
*
* Returns:
*    TRUE
*
************************************************************************/

STATICFN BOOL SelectDialogOK(
    HWND hwnd)
{
    HWND hwndLangLB;
    INT iSelect;
    PRESLINK prl;

    hwndLangLB = GetDlgItem(hwnd, DID_SELECTDIALOGLANGLIST);

    if ((iSelect = (INT)SendMessage(hwndLangLB, LB_GETCURSEL, 0, 0)) !=
            LB_ERR) {
        /*
         * Get a pointer to the selected dialog (stored in the listbox
         * items data field).
         */
        prl = (PRESLINK)SendMessage(hwndLangLB, LB_GETITEMDATA, iSelect, 0L);

        /*
         * Is there a dialog currently being edited?
         */
        if (gfEditingDlg) {
            /*
             * If they chose the same dialog as what they are currently
             * editing, just get out without doing anything more.
             * Otherwise, delete the current dialog because we are
             * now committed to loading a new one.
             */
            if (prl == gcd.prl)
                return TRUE;
            else
                DeleteDialog(FALSE);
        }

        /*
         * Finally, load the new dialog (make it current).
         */
        ResLinkToDialog(prl);

        /*
         * Select the new dialog, if it was successfully created.
         */
        if (gfEditingDlg)
            SelectControl(gcd.npc, FALSE);
    }

    return TRUE;
}

/************************************************************************
* CreateTestDialog
*
* Creates a dialog for the test mode of the dialog editor
*
************************************************************************/

VOID CreateTestDialog(VOID)
{
    PRES pRes;
    PDIALOGBOXHEADER pdbh;

    if (!gfEditingDlg)
        return;

    CancelSelection(TRUE);

    /*
     * Save the current dialog in the resource buffer.
     */
    if (!SynchDialogResource())
        return;

    /*
     * Make a memory copy of the current dialog resource for test mode.
     */
    if (!(pRes = AllocDialogResource(TRUE, FALSE)))
        return;

    pdbh = (PDIALOGBOXHEADER)SkipResHeader(pRes);

    /*
     * Create the test dialog in a modeless loop using the test dialog proc.
     */
    ghwndTestDlg = CreateDialogIndirect(ghInst, (LPDLGTEMPLATE)pdbh,
            ghwndSubClient, TestDlgProc);

    if (ghwndTestDlg) {
        gfTestMode = TRUE;

        /*
         * Disable various top level menus.
         */
        MyEnableMenuItemByPos(ghMenuMain, MENUPOS_FILE, FALSE);
        MyEnableMenuItemByPos(ghMenuMain, MENUPOS_EDIT, FALSE);
        MyEnableMenuItemByPos(ghMenuMain, MENUPOS_ARRANGE, FALSE);
        DrawMenuBar(ghwndMain);

        ToolboxSelectTool(W_NOTHING, FALSE);
        if (gfShowToolbox)
            ToolboxShow(FALSE);

        StatusSetEnable();

        /*
         * Remove the work mode dialog from view (actually, it is now
         * hidden behind the test dialog, but we hide it in case the
         * user moves the test dialog.
         */
        ShowWindow(gcd.npc->hwnd, SW_HIDE);
    }
}

/************************************************************************
* TestDlgProc
*
* This is the dialog procedure for the dialog in Test mode.
*
************************************************************************/

DIALOGPROC TestDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            TestInitDlg(hwnd);
            return TRUE;

        case WM_SYSCOMMAND:
            if (wParam == SC_CLOSE) {
                DestroyTestDialog();
                return TRUE;
            }

            return FALSE;

        case WM_DRAWITEM:
            return DrawOwnerDrawButton((LPDRAWITEMSTRUCT)lParam);

        case WM_DESTROY:
            gfTestMode = FALSE;

            ShowWindow(gcd.npc->hwnd, SW_SHOWNA);

            /*
             * Enable various top level menus.
             */
            MyEnableMenuItemByPos(ghMenuMain, MENUPOS_FILE, TRUE);
            MyEnableMenuItemByPos(ghMenuMain, MENUPOS_EDIT, TRUE);
            MyEnableMenuItemByPos(ghMenuMain, MENUPOS_ARRANGE, TRUE);
            DrawMenuBar(ghwndMain);

            if (gfShowToolbox)
                ToolboxShow(TRUE);

            StatusSetEnable();

            return TRUE;

        default:
            return FALSE;
    }
}

/************************************************************************
* TestInitDlg
*
* This function handles the initialization of the test dialog.
*
* Arguments:
*     HWND hwnd = The test dialog window handle.
*
************************************************************************/

STATICFN VOID TestInitDlg(
    HWND hwnd)
{
    register INT i;
    TCHAR szBuf[CCHTEXTMAX];
    HWND hwndCtrl;
    LPTSTR pszTextEnd;
    TCHAR szClass[32];
    INT iClass;

    /*
     * The following will fill some controls with sample text lines.
     */
    lstrcpy(szBuf, ids(IDS_DEFLBTEXT));
    pszTextEnd = szBuf + lstrlen(szBuf);
    hwndCtrl = GetWindow(hwnd, GW_CHILD);
    while (hwndCtrl) {
        GetClassName(hwndCtrl, szClass, sizeof(szClass));

        switch (iClass = GetiClass(szClass)) {
            case IC_LISTBOX:
            case IC_COMBOBOX:
                /*
                 * Fill listboxes and comboboxes with some sample lines.
                 */
                for (i = 1; i <= CLBTESTLINES; i++) {
                    itoaw(i, pszTextEnd, 10);
                    SendMessage(hwndCtrl,
                            (WORD)((iClass == IC_LISTBOX) ?
                            LB_INSERTSTRING : CB_INSERTSTRING),
                            (WPARAM)-1, (DWORD)szBuf);
                }

                break;
        }

        hwndCtrl = GetWindow(hwndCtrl, GW_HWNDNEXT);
    }
}

/************************************************************************
* DestroyTestDialog
*
* Destroys the test dialog window.
************************************************************************/

VOID DestroyTestDialog(VOID)
{
    DestroyWindow(ghwndTestDlg);
    ghwndTestDlg = NULL;
}

/************************************************************************
* ArrangeSettingsDialog
*
* This function displays the Arrange Settings dialog box.
*
************************************************************************/

VOID ArrangeSettingsDialog(VOID)
{
    DlgBox(DID_ARRSETTINGS, (WNDPROC)ArrangeSettingsDlgProc);
}

/************************************************************************
* ArrangeSettingsDlgProc
*
* This is the Arrange Settings dialog procedure.
*
************************************************************************/

DIALOGPROC ArrangeSettingsDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            SendDlgItemMessage(hwnd, DID_ARRSETCXGRID, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SendDlgItemMessage(hwnd, DID_ARRSETCYGRID, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SetDlgItemInt(hwnd, DID_ARRSETCXGRID, gcxGrid, TRUE);
            SetDlgItemInt(hwnd, DID_ARRSETCYGRID, gcyGrid, TRUE);

            SendDlgItemMessage(hwnd, DID_ARRSETXMARGIN, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SendDlgItemMessage(hwnd, DID_ARRSETYMARGIN, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SetDlgItemInt(hwnd, DID_ARRSETXMARGIN, gxMargin, TRUE);
            SetDlgItemInt(hwnd, DID_ARRSETYMARGIN, gyMargin, TRUE);

            SendDlgItemMessage(hwnd, DID_ARRSETXSPACE, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SendDlgItemMessage(hwnd, DID_ARRSETYSPACE, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SetDlgItemInt(hwnd, DID_ARRSETXSPACE, gxSpace, TRUE);
            SetDlgItemInt(hwnd, DID_ARRSETYSPACE, gySpace, TRUE);

            SendDlgItemMessage(hwnd, DID_ARRSETXMINPUSHSPACE, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SendDlgItemMessage(hwnd, DID_ARRSETXMAXPUSHSPACE, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SendDlgItemMessage(hwnd, DID_ARRSETYPUSHSPACE, EM_LIMITTEXT,
                    CCHARRSETMAX, 0L);
            SetDlgItemInt(hwnd, DID_ARRSETXMINPUSHSPACE, gxMinPushSpace, TRUE);
            SetDlgItemInt(hwnd, DID_ARRSETXMAXPUSHSPACE, gxMaxPushSpace, TRUE);
            SetDlgItemInt(hwnd, DID_ARRSETYPUSHSPACE, gyPushSpace, TRUE);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                INT cxGridNew;
                INT cyGridNew;
                INT xMarginNew;
                INT yMarginNew;
                INT xSpaceNew;
                INT ySpaceNew;
                INT xMinPushSpaceNew;
                INT xMaxPushSpaceNew;
                INT yPushSpaceNew;
                BOOL fTranslated1;
                BOOL fTranslated2;
                BOOL fTranslated3;

                case IDOK:
                    cxGridNew = GetDlgItemInt(hwnd, DID_ARRSETCXGRID,
                            &fTranslated1, TRUE);
                    cyGridNew = GetDlgItemInt(hwnd, DID_ARRSETCYGRID,
                            &fTranslated2, TRUE);

                    if (!fTranslated1 || !fTranslated2 ||
                            cxGridNew <= 0 || cyGridNew <= 0) {
                        Message(MSG_GTZERO, ids(IDS_GRID));
                        SetFocus(GetDlgItem(hwnd, DID_ARRSETCXGRID));
                        break;
                    }

                    xMarginNew = GetDlgItemInt(hwnd, DID_ARRSETXMARGIN,
                            &fTranslated1, TRUE);
                    yMarginNew = GetDlgItemInt(hwnd, DID_ARRSETYMARGIN,
                            &fTranslated2, TRUE);

                    if (!fTranslated1 || !fTranslated2 ||
                            xMarginNew < 0 || yMarginNew < 0) {
                        Message(MSG_POSITIVENUM, ids(IDS_MARGIN));
                        SetFocus(GetDlgItem(hwnd, DID_ARRSETXMARGIN));
                        break;
                    }

                    xSpaceNew = GetDlgItemInt(hwnd, DID_ARRSETXSPACE,
                            &fTranslated1, TRUE);
                    ySpaceNew = GetDlgItemInt(hwnd, DID_ARRSETYSPACE,
                            &fTranslated2, TRUE);

                    if (!fTranslated1 || !fTranslated2 ||
                            xSpaceNew < 0 || ySpaceNew < 0) {
                        Message(MSG_POSITIVENUM, ids(IDS_CTRLSPACING));
                        SetFocus(GetDlgItem(hwnd, DID_ARRSETXSPACE));
                        break;
                    }

                    xMinPushSpaceNew = GetDlgItemInt(hwnd,
                            DID_ARRSETXMINPUSHSPACE, &fTranslated1, TRUE);
                    xMaxPushSpaceNew = GetDlgItemInt(hwnd,
                            DID_ARRSETXMAXPUSHSPACE, &fTranslated2, TRUE);
                    yPushSpaceNew = GetDlgItemInt(hwnd, DID_ARRSETYPUSHSPACE,
                            &fTranslated3, TRUE);

                    if (!fTranslated1 || !fTranslated2 || !fTranslated3 ||
                            xMinPushSpaceNew < 0 || xMaxPushSpaceNew < 0 ||
                            yPushSpaceNew < 0) {
                        Message(MSG_POSITIVENUM, ids(IDS_PUSHSPACING));
                        SetFocus(GetDlgItem(hwnd, DID_ARRSETXMINPUSHSPACE));
                        break;
                    }

                    if (xMinPushSpaceNew > xMaxPushSpaceNew) {
                        Message(MSG_MINGTMAXSPACE);
                        SetFocus(GetDlgItem(hwnd, DID_ARRSETXMINPUSHSPACE));
                        break;
                    }

                    gcxGrid = cxGridNew;
                    gcyGrid = cyGridNew;
                    gxMargin = xMarginNew;
                    gyMargin = yMarginNew;
                    gxSpace = xSpaceNew;
                    gySpace = ySpaceNew;
                    gxMinPushSpace = xMinPushSpaceNew;
                    gxMaxPushSpace = xMaxPushSpaceNew;
                    gyPushSpace = yPushSpaceNew;

                    EndDialog(hwnd, LOWORD(wParam));
                    break;

                case DID_ARRSETDEFAULTS:
                    SetDlgItemInt(hwnd, DID_ARRSETCXGRID,
                            DEFCXGRID, TRUE);
                    SetDlgItemInt(hwnd, DID_ARRSETCYGRID,
                            DEFCYGRID, TRUE);

                    SetDlgItemInt(hwnd, DID_ARRSETXMARGIN,
                            DEFXMARGIN, TRUE);
                    SetDlgItemInt(hwnd, DID_ARRSETYMARGIN,
                            DEFYMARGIN, TRUE);

                    SetDlgItemInt(hwnd, DID_ARRSETXSPACE,
                            DEFXSPACE, TRUE);
                    SetDlgItemInt(hwnd, DID_ARRSETYSPACE,
                            DEFYSPACE, TRUE);

                    SetDlgItemInt(hwnd, DID_ARRSETXMINPUSHSPACE,
                            DEFXMINPUSHSPACE, TRUE);
                    SetDlgItemInt(hwnd, DID_ARRSETXMAXPUSHSPACE,
                            DEFXMAXPUSHSPACE, TRUE);
                    SetDlgItemInt(hwnd, DID_ARRSETYPUSHSPACE,
                            DEFYPUSHSPACE, TRUE);

                    break;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    break;

                case IDHELPDLG:
                    WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                            HELPID_ARRSETTINGS);
                    break;
            }

            return TRUE;

        default:
            return FALSE;
    }
}

/************************************************************************
* AboutDlgProc
*
* This is the About Box dialog procedure.
*
************************************************************************/

DIALOGPROC AboutDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            {
                TCHAR szVersion[CCHTEXTMAX];

                lstrcpy(szVersion, ids(IDS_APPVERSION));
                lstrcat(szVersion, ids(IDS_APPVERSIONMINOR));

                SetDlgItemText(hwnd, DID_ABOUTVERSION, szVersion);
                CenterWindow(hwnd);
            }

            return TRUE;

        case WM_COMMAND:
            EndDialog(hwnd, IDOK);
            return TRUE;

        default:
            return FALSE;
    }
}


DIALOGS.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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: dialogs.h
****************************************************************************/

#define IDHELPDLG8

/*
 * Defines for the "Styles" dialogs. ---------------------------------------
 */

/*
 * These controls are common to several of the dialogs.
 */
#define DID_WS_POPUP                    100
#define DID_WS_CHILD                    101
#define DID_WS_MINIMIZE                 102
#define DID_WS_VISIBLE                  103
#define DID_WS_DISABLED                 104
#define DID_WS_CLIPSIBLINGS             105
#define DID_WS_CLIPCHILDREN             106
#define DID_WS_MAXIMIZE                 107
#define DID_WS_CAPTION                  108
#define DID_WS_BORDER                   109
#define DID_WS_DLGFRAME                 110
#define DID_WS_VSCROLL                  111
#define DID_WS_HSCROLL                  112
#define DID_WS_SYSMENU                  113
#define DID_WS_THICKFRAME               114
#define DID_WS_GROUP                    115
#define DID_WS_TABSTOP                  116

/*
 * These controls are found in several of the button styles dialogs.
 */
#define DID_BS_AUTOXXX                  130
#define DID_BS_LEFTTEXT                 131

/*
 * Check Box Styles dialog.
 */
#define DID_CHECKBOXSTYLES              200
#define DID_BS_3STATE                   201

/*
 * Combo Box Styles dialog.
 */
#define DID_COMBOBOXSTYLES              300
#define DID_CBS_SIMPLE                  301
#define DID_CBS_DROPDOWN                302
#define DID_CBS_DROPDOWNLIST            303
#define DID_CBS_OWNERDRAWFIXED          304
#define DID_CBS_OWNERDRAWVARIABLE       305
#define DID_CBS_AUTOHSCROLL             306
#define DID_CBS_OEMCONVERT              307
#define DID_CBS_SORT                    308
#define DID_CBS_HASSTRINGS              309
#define DID_CBS_NOINTEGRALHEIGHT        310
#define DID_CBS_DISABLENOSCROLL         311

/*
 * Dialog Styles dialog.
 */
#define DID_DIALOGSTYLES                400
#define DID_MMF_PRELOAD                 401
#define DID_MMF_MOVEABLE                402
#define DID_MMF_DISCARDABLE             403
#define DID_MMF_PURE                    404
#define DID_DS_ABSALIGN                 405
#define DID_DS_SYSMODAL                 406
#define DID_DS_LOCALEDIT                407
#define DID_DS_MODALFRAME               408
#define DID_DS_NOIDLEMSG                409
#define DID_WS_MINIMIZEBOX              410
#define DID_WS_MAXIMIZEBOX              411
#define DID_DLGSTYLEFONTNAME            412
#define DID_DLGSTYLEPOINTSIZE           413
#define DID_DLGSTYLECLASS               414
#define DID_DLGSTYLEMENU                415
#define DID_DLGSTYLELANG                416
#define DID_DLGSTYLESUBLANG             417

/*
 * Edit Field Styles dialog.
 */
#define DID_EDITSTYLES                  500
#define DID_ES_LEFT                     501
#define DID_ES_CENTER                   502
#define DID_ES_RIGHT                    503
#define DID_ES_MULTILINE                504
#define DID_ES_UPPERCASE                505
#define DID_ES_LOWERCASE                506
#define DID_ES_PASSWORD                 507
#define DID_ES_AUTOVSCROLL              508
#define DID_ES_AUTOHSCROLL              509
#define DID_ES_NOHIDESEL                510
#define DID_ES_OEMCONVERT               511
#define DID_ES_READONLY                 512

/*
 * Frame Styles dialog.
 */
#define DID_FRAMESTYLES                 600
#define DID_SS_BLACKFRAME               601
#define DID_SS_GRAYFRAME                602
#define DID_SS_WHITEFRAME               603

/*
 * Group Box Styles dialog.
 */
#define DID_GROUPBOXSTYLES              700

/*
 * Horizontal Scroll Bar Styles dialog.
 */
#define DID_HORZSCROLLSTYLES            800

/*
 * Icon Styles dialog.
 */
#define DID_ICONSTYLES                  900

/*
 * List Box Styles dialog.
 */
#define DID_LISTBOXSTYLES               1000
#define DID_LBS_STANDARD                1001
#define DID_LBS_NOTIFY                  1002
#define DID_LBS_SORT                    1003
#define DID_LBS_NOREDRAW                1004
#define DID_LBS_MULTIPLESEL             1005
#define DID_LBS_OWNERDRAWFIXED          1006
#define DID_LBS_OWNERDRAWVARIABLE       1007
#define DID_LBS_HASSTRINGS              1008
#define DID_LBS_USETABSTOPS             1009
#define DID_LBS_NOINTEGRALHEIGHT        1010
#define DID_LBS_MULTICOLUMN             1011
#define DID_LBS_WANTKEYBOARDINPUT       1012
#define DID_LBS_EXTENDEDSEL             1013
#define DID_LBS_DISABLENOSCROLL         1014
#define DID_LBS_NODATA                  1015

/*
 * Push Button Styles dialog.
 */
#define DID_PUSHBUTTONSTYLES            1100
#define DID_BS_PUSHBUTTON               1101
#define DID_BS_DEFPUSHBUTTON            1102
#define DID_BS_OWNERDRAW                1103

/*
 * Frame Styles dialog.
 */
#define DID_RADIOBUTTONSTYLES           1200

/*
 * Rectangle Styles dialog.
 */
#define DID_RECTSTYLES                  1300
#define DID_SS_BLACKRECT                1301
#define DID_SS_GRAYRECT                 1302
#define DID_SS_WHITERECT                1303

/*
 * Text Styles dialog.
 */
#define DID_TEXTSTYLES                  1400
#define DID_SS_LEFT                     1401
#define DID_SS_CENTER                   1402
#define DID_SS_RIGHT                    1403
#define DID_SS_NOPREFIX                 1404
#define DID_SS_SIMPLE                   1405
#define DID_SS_LEFTNOWORDWRAP           1406
#define DID_SS_USERITEM                 1407

/*
 * Vertical Scroll Bar Styles dialog.
 */
#define DID_VERTSCROLLSTYLES            1500

/*
 * Custom Styles dialog.
 */
#define DID_CUSTOMSTYLES                1600
#define DID_CUSTOMSTYLESCLASS           1601
#define DID_CUSTOMSTYLESSTYLES          1602

/*
 * Defines for other dialogs. ----------------------------------------------
 */

/*
 * About dialog.
 */
#define DID_ABOUT                       2000
#define DID_ABOUTVERSION                2001

/*
 * Arrange Settings dialog.
 */
#define DID_ARRSETTINGS                 2100
#define DID_ARRSETDEFAULTS              2101
#define DID_ARRSETCXGRID                2102
#define DID_ARRSETCYGRID                2103
#define DID_ARRSETXMARGIN               2104
#define DID_ARRSETYMARGIN               2105
#define DID_ARRSETXSPACE                2106
#define DID_ARRSETYSPACE                2107
#define DID_ARRSETXMINPUSHSPACE         2108
#define DID_ARRSETXMAXPUSHSPACE         2109
#define DID_ARRSETYPUSHSPACE            2110

/*
 * Order/Group dialog.
 */
#define DID_ORDERGROUP                  2200
#define DID_ORDERLIST                   2201
#define DID_ORDERMAKEGROUP              2202
#define DID_ORDERSETTAB                 2203
#define DID_ORDERCLEARTAB               2204

/*
 * Select Dialog dialog.
 */
#define DID_SELECTDIALOG                2300
#define DID_SELECTDIALOGNAMELIST        2301
#define DID_SELECTDIALOGLANGLIST        2302

/*
 * Status Ribbon dialog.
 */
#define DID_STATUS                      2401
#define DID_STATUSXY                    2402
#define DID_STATUSX2Y2                  2403
#define DID_STATUSCX                    2404
#define DID_STATUSCY                    2405
#define DID_STATUSLABEL1                2406
#define DID_STATUSSYM                   2407
#define DID_STATUSSYMID                 2408
#define DID_STATUSLABEL2                2409
#define DID_STATUSNAME                  2410
#define DID_STATUSNAMEID                2411
#define DID_STATUSTEXT                  2412

/*
 * Symbols dialog.
 */
#define DID_SYMBOLS                     2500
#define DID_SYMBOLSADD                  2501
#define DID_SYMBOLSDELETE               2502
#define DID_SYMBOLSCHANGE               2503
#define DID_SYMBOLSEDITID               2504
#define DID_SYMBOLSEDITSYM              2505
#define DID_SYMBOLSLIST                 2506
#define DID_SYMBOLSUNUSED               2507

/*
 * Create New Custom Control dialog.
 */
#define DID_NEWCUST                     2600
#define DID_NEWCUSTCLASS                2601
#define DID_NEWCUSTSTYLES               2602
#define DID_NEWCUSTCX                   2603
#define DID_NEWCUSTCY                   2604
#define DID_NEWCUSTTEXT                 2605

/*
 * Select Custom Control dialog.
 */
#define DID_SELCUST                     2700
#define DID_SELCUSTLIST                 2701
#define DID_SELCUSTSAMPLE               2702

/*
 * Remove Custom Control dialog.
 */
#define DID_REMCUST                     2800
#define DID_REMCUSTLIST                 2801

/*
 * These dialog ids are dummy ones that are only used so that there
 * can be an entry in the dialog help table.  They are for the CommDlg
 * dialogs and the palettes that are not dialogs.
 */

#define DID_COMMONFILEOPENINCLUDE       3000
#define DID_COMMONFILEOPENRES           3100
#define DID_COMMONFILESAVEINCLUDE       3200
#define DID_COMMONFILESAVERES           3300
#define DID_COMMONFILEOPENDLL           3400

#define DID_TOOLBOX                     3500


DLGEDIT.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 Header *******************************
* Module Name: dlgedit.c
*
* Main function and window procedure for the Dialog Box Editor.
*
* Functions:
*
*   MainWndProc()
*   ReadWindowPos()
*   WriteWindowPos()
*   InitApplication()
*   InitInstance()
*   PenWinRegister()
*   GetSystemValues()
*   ReadEnv()
*   WriteEnv()
*   LoadSysColorBitmaps()
*   LoadAlterBitmap()
*   RGBInvertRGB()
*   SizeRibbons()
*   DialogTerminate()
*
* Comments:
*
* Because of the need for a dialog in both work and test mode to be
* shown relative to the client area of its parent, and because the
* editor has a ribbon control along the top of its client area, there
* needed to be another window created that will be the actual parent
* of the dialog being edited.  This window, called the ghwndSubClient
* window, is sized to be the size of the editors client area minus
* the height of the ribbon window at the top.  This makes it so that
* a dialog that has an origin of 0,0 will have the top edge of its
* client area just below the bottom of the ribbon window in the
* editor.  This window does not need any special processing.  It simply
* paints its background with the app workspace color, and is used as
* the basis for coordinate conversion for the dialog.
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"

#include <commdlg.h>

#include <stdlib.h>
#include <string.h>
#if defined(DBCS) && !defined(UNICODE)
#define _MBCS
#include <mbstring.h>
#define strtok      _mbstok
#endif

STATICFN BOOL InitApplication(HANDLE hInstance);
STATICFN BOOL InitInstance(HANDLE hInstance, INT nCmdShow);
STATICFN VOID PenWinRegister(VOID);
STATICFN VOID GetSystemValues(VOID);
STATICFN VOID ReadEnv(VOID);
STATICFN VOID WriteEnv(VOID);
STATICFN VOID LoadSysColorBitmaps(VOID);
STATICFN HBITMAP LoadAlterBitmap(INT idbm, DWORD rgbNew, DWORD rgbNew2);
STATICFN DWORD RGBInvertRGB(DWORD rgb);
STATICFN VOID SizeRibbons(HWND hwnd);
STATICFN VOID DialogTerminate(VOID);

static RECT grcAppPos;              // Saves the app's window pos.
static UINT gmsgHelp;               // Registered help message from commdlg.
static BOOL fStartAsIcon = FALSE;   // TRUE if app is started minimized.

/*
 * Contains the address of the Pen Windows callback.
 */
typedef VOID ( APIENTRY *LPFNPENWIN)(WORD, BOOL);
static LPFNPENWIN lpfnRegisterPenApp;

/************************************************************************
* WinMain
*
* This is the main function for the dialog editor.
*
************************************************************************/

INT WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    INT nCmdShow)
{
    MSG msg;

    if (!hPrevInstance) {
        if (!InitApplication(hInstance)) {
            Message(MSG_NOINIT);
            return FALSE;
        }
    }

    if (!InitInstance(hInstance, nCmdShow)) {
        Message(MSG_NOINIT);
        return FALSE;
    }

    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!ghwndTestDlg || !IsDialogMessage(ghwndTestDlg, &msg)) {
            if (!hwndStatus || !IsDialogMessage(hwndStatus, &msg)) {
                if (!TranslateAccelerator(ghwndMain, ghAccTable, &msg)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
        }
    }

    DialogTerminate();

    /*
     * Return the value from PostQuitMessage.
     */
    return msg.wParam;
}

/************************************************************************
* InitApplication
*
* Registers the window classes.
*
* Arguments:
*   HANDLE hInstance - Instance handle from WinMain.
*
* Returns:
*   TRUE if all of the window classes were created; otherwise, FALSE.
*
************************************************************************/

STATICFN BOOL InitApplication(
    HANDLE hInstance)
{
    WNDCLASS wc;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDICON_DLGEDIT));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wc.lpszMenuName = MAKEINTRESOURCE(IDMENU_MAIN);
    wc.lpszClassName = szMainClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = 0;
    wc.lpfnWndProc = DefWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szSubClientClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = DragWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(DWORD);
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szDragClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = 0;
    wc.lpfnWndProc = ToolboxWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szToolboxClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = 0;
    wc.lpfnWndProc = ToolBtnWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szToolBtnClass;
    if (!RegisterClass(&wc))
        return FALSE;

    wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = CustomWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szCustomClass;
    if (!RegisterClass(&wc))
        return FALSE;

    return TRUE;
}

/************************************************************************
* InitInstance
*
* Initializes the dialog editor by loading resources, etc.
*
* Arguments:
*   HANDLE hInstance - Instance handle from WinMain.
*   int nCmdShow     - Show command from WinMain.
*
* Returns:
*   FALSE if any errors occurred during initialization
*
************************************************************************/

STATICFN BOOL InitInstance(
    HANDLE hInstance,
    INT nCmdShow)
{
    HDC hDC;
    TEXTMETRIC tm;
    INT x;
    INT y;
    INT cx;
    INT cy;
    BOOL fMaximized;
    INT i;
    TCHAR szArg1[CCHTEXTMAX];

    ghInst = hInstance;

    /*
     * We need a mouse - make sure we have one.
     */
    if (!GetSystemMetrics(SM_MOUSEPRESENT)) {
        Message(MSG_NOMOUSE);
        return FALSE;
    }

    /*
     * Register for Pen Windows, if it is present.
     */
    PenWinRegister();

    ghAccTable = LoadAccelerators(ghInst, MAKEINTRESOURCE(IDACCEL_MAIN));

    /*
     * Create a dark gray pen for use in borders later.
     */
    if (!(hpenDarkGray = CreatePen(PS_SOLID, 1, DARKGRAY)))
        return FALSE;

    /*
     * Get some system constants.
     */
    GetSystemValues();

    /*
     * Note that this must be done instead of using the text metrics,
     * because Windows internally generates a better average value for
     * proportional fonts, and we must match it or our dialogs will
     * be out of proportion.
     */
    gcxSysChar = LOWORD(GetDialogBaseUnits());
    gcySysChar = HIWORD(GetDialogBaseUnits());

    /*
     * Because some useful worker routines like WinToDUPoint use
     * the values in gcd.c*Char, set them to be the default font right
     * away.  When a dialog is loaded with a different font, they
     * will be modified.
     */
    gcd.cxChar = gcxSysChar;
    gcd.cyChar = gcySysChar;

    /*
     * Build the help file name path.  Assume the help file is in the
     * same directory as the executable.
     */
    GetModuleFileName(ghInst, gszHelpFile, CCHMAXPATH);
    *FileInPath(gszHelpFile) = CHAR_NULL;
    lstrcat(gszHelpFile, ids(IDS_HELPFILE));

    /*
     * Register the message for help from the common dialogs.
     */
    gmsgHelp = RegisterWindowMessage(HELPMSGSTRING);

    /*
     * Hook the message filter stream so that we can detect F1 keystrokes.
     */
    ghhkMsgFilter = SetWindowsHook(WH_MSGFILTER, (HOOKPROC)MsgFilterHookFunc);

    /*
     * Read the last position for the app.
     */
    if (!ReadWindowPos(szAppPos, &x, &y, &cx, &cy, &fMaximized)) {
        x = CW_USEDEFAULT;
        y = CW_USEDEFAULT;
        cx = CW_USEDEFAULT;
        cy = CW_USEDEFAULT;
        fMaximized = FALSE;
    }

    /*
     * Create the main window.
     */
    if (!(ghwndMain = CreateWindow(szMainClass, NULL,
            WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
            x, y, cx, cy, NULL, NULL, hInstance, NULL)))
        return FALSE;

    ShowFileStatus(TRUE);

    /*
     * Read the Preferences data.
     */
    ReadEnv();

    /*
     * If the app was saved when maximized (and they didn't start it up
     * with some kind of an option to have it minimized or in some
     * other funny initial state from the shell), then cause it to
     * be maximized when shown.
     */
    if (fMaximized && (nCmdShow == SW_SHOWNORMAL || nCmdShow == SW_SHOW))
        nCmdShow = SW_SHOWMAXIMIZED;

    ShowWindow(ghwndMain, nCmdShow);
    UpdateWindow(ghwndMain);

    /*
     * Did the user start this app minimized from the program manager?
     */
    if (IsIconic(ghwndMain)) {
        /*
         * Set a flag.  The showing of the toolbox will be deferred
         * until the app is restored.
         */
        fStartAsIcon = TRUE;
    }
    else {
        /*
         * If they had the Toolbox before, show it now.
         */
        if (gfShowToolbox)
            ToolboxShow(TRUE);
    }

    hcurArrow = LoadCursor(NULL, IDC_ARROW);
    hcurWait = LoadCursor(NULL, IDC_WAIT);
    hcurOutSel = LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_OUTSEL));
    hcurMove = LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_MOVE));
    hcurInsert = LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_INSERT));
    hcurDropTool = LoadCursor(ghInst, MAKEINTRESOURCE(IDCUR_DROPTOOL));
    hcurSizeNESW = LoadCursor(NULL, IDC_SIZENESW);
    hcurSizeNS = LoadCursor(NULL, IDC_SIZENS);
    hcurSizeNWSE = LoadCursor(NULL, IDC_SIZENWSE);
    hcurSizeWE = LoadCursor(NULL, IDC_SIZEWE);

    if (!hcurArrow ||
            !hcurWait ||
            !hcurOutSel ||
            !hcurMove ||
            !hcurDropTool ||
            !hcurInsert)
        return FALSE;

    if ((hDC = GetDC(ghwndMain)) == NULL)
        return FALSE;

    GetTextMetrics(hDC, &tm);

    gcyPixelsPerInch = GetDeviceCaps(hDC, LOGPIXELSY);

    /*
     * Create a memory DC for drawing bitmaps.
     */
    ghDCMem = CreateCompatibleDC(hDC);

    ReleaseDC(ghwndMain, hDC);

    /*
     * Load the bitmaps that depend on system colors.
     */
    LoadSysColorBitmaps();

    fmtDlg = RegisterClipboardFormat(L"DIALOG");

    /*
     * Initialize the icon control ordinal to the icon id from our exe
     * that we will use to show these kind of controls.
     */
    WriteOrd(&gordIcon, IDICON_ICON);

    /*
     * Initialize the default text fields in the awcd array.  Because
     * CCONTROLS does not include the dialog type, it has to be done
     * separately.
     */
    awcd[W_DIALOG].pszTextDefault = ids(awcd[W_DIALOG].idsTextDefault);
    for (i = 0; i < CCONTROLS; i++)
        awcd[i].pszTextDefault = ids(awcd[i].idsTextDefault);

    /*
     * If there was a command line argument specified, try and open
     * it as the initial file.
     */
    if (__argc > 1) {
        MultiByteToWideChar(CP_ACP, 0, __argv[1], -1, szArg1, CCHTEXTMAX);
        OpenCmdLineFile(szArg1);
    }

    /*
     * Be sure the focus is on the main window.  This corrects a
     * problem where the accelerators don't initially work because
     * the focus gets placed on the Properties Bar.
     */
    SetFocus(ghwndMain);

    return TRUE;
}

/************************************************************************
* PenWinRegister
*
* This function will register for Pen Windows, if it is present.
*
************************************************************************/

STATICFN VOID PenWinRegister(VOID)
{
    HANDLE hmod;

    if (!(hmod = (HANDLE)GetSystemMetrics(SM_PENWINDOWS)))
        return;

    if (lpfnRegisterPenApp =
            (LPFNPENWIN)GetProcAddress(hmod, "RegisterPenApp"))
        (*lpfnRegisterPenApp)(1, TRUE);     // Be Pen-Enhanced!
}

/************************************************************************
* GetSystemValues
*
* This function reads various system values.  It is called at init time,
* as well as if we are informed by a WM_SYSVALUECHANGED message that
* some of these values have been changed.
*
************************************************************************/

STATICFN VOID GetSystemValues(VOID)
{
    gcyBorder = GetSystemMetrics(SM_CYBORDER);

    /*
     * The distance that the mouse can move during a pre-drag operation
     * before starting to drag the control anyways is based on the
     * mouse double-click movement distances in the system.
     */
    gcxPreDragMax = GetSystemMetrics(SM_CXDOUBLECLK);
    gcyPreDragMax = GetSystemMetrics(SM_CYDOUBLECLK);

    /*
     * The number of milliseconds that the pre-drag debounce time lasts.
     */
    gmsecPreDrag = 250;
}

/************************************************************************
* ReadWindowPos
*
* This function retrieves the saved window position for a window and
* returns it in the specified variables.  It is used between sessions
* to restore the application windows to the position they had when
* the editor was last exited.
*
* Arguments:
*   LPTSTR pszKeyName  - KeyName the position was saved under.
*   PINT px            - Saved x position.
*   PINT py            - Saved y position.
*   PINT pcx           - Saved width.
*   PINT pcy           - Saved height.
*   BOOL *pfMaximized  - Set to TRUE if window was maximized when saved.
*
* Returns:
*   TRUE if the position could be read, or FALSE otherwise.
*   If FALSE is returned, the values in the specified variables are
*   not valid!  The caller must be able to handle a FALSE return and
*   supply a default position for the window.
*
************************************************************************/

BOOL ReadWindowPos(
    LPTSTR pszKeyName,
    PINT px,
    PINT py,
    PINT pcx,
    PINT pcy,
    BOOL *pfMaximized)
{
    static CHAR szSep[] = " ,";
    TCHAR szBuf[CCHTEXTMAX];
    CHAR szBufAnsi[CCHTEXTMAX];
    PSTR psz;
    BOOL fDefCharUsed;

    if (!GetPrivateProfileString(ids(IDS_APPNAME),
            pszKeyName, szEmpty, szBuf, CCHTEXTMAX, ids(IDS_DLGEDITINI)))
        return FALSE;

    WideCharToMultiByte(CP_ACP, 0, szBuf, -1, szBufAnsi, CCHTEXTMAX,
            NULL, &fDefCharUsed);

    if (!(psz = strtok(szBufAnsi, szSep)))
        return FALSE;

    *px = atoi(psz);

    if (!(psz = strtok(NULL, szSep)))
        return FALSE;

    *py = atoi(psz);

    if (!(psz = strtok(NULL, szSep)))
        return FALSE;

    *pcx = atoi(psz);

    if (!(psz = strtok(NULL, szSep)))
        return FALSE;

    *pcy = atoi(psz);

    /*
     * If there is a "1" following the coordinates, the window was
     * maximized when it was saved.
     */
    *pfMaximized = FALSE;
    if ((psz = strtok(NULL, szSep)) && atoi(psz) == 1)
        *pfMaximized = TRUE;

    /*
     * Don't allow a zero sized window.
     */
    if (*pcx == 0 || *pcy == 0)
        return FALSE;

    /*
     * Return success.
     */
    return TRUE;

}

/************************************************************************
* WriteWindowPos
*
* This function writes the position of a window to the
* editor's profile file under the specified keyname.
* The ReadWindowPos function is the counterpart of this
* function.
*
* Arguments:
*   PRECT prc          - Rectangle for the "restored" window size.
*   BOOL fMaximized    - TRUE if the window is maximized.
*   LPTSTR pszKeyName  - KeyName to save the position under.
*
************************************************************************/

VOID WriteWindowPos(
    PRECT prc,
    BOOL fMaximized,
    LPTSTR pszKeyName)
{
    TCHAR szBuf[CCHTEXTMAX];

    wsprintf(szBuf, L"%d %d %d %d", prc->left, prc->top,
            prc->right - prc->left, prc->bottom - prc->top);

    if (fMaximized)
        lstrcat(szBuf, L" 1");

    WritePrivateProfileString(ids(IDS_APPNAME),
            pszKeyName, szBuf, ids(IDS_DLGEDITINI));
}

/*************************************************************************
* ReadEnv
*
* This function initializes variables from their counterparts
* in the private profile file for DlgEdit.  The application
* merely needs to construct an array of INIENTRY structures
* to describe the variables that must be initialized.
*
* Note that the original value read from the profile is saved when
* it is read.  This allows us to optimize what needs to be written
* out with WriteEnv.
*
*************************************************************************/

STATICFN VOID ReadEnv(VOID)
{
    register INT i;

    for (i = 0; gaie[i].pszKeyName; i++) {
        *gaie[i].pnVar = gaie[i].nSave =
                GetPrivateProfileInt(ids(IDS_APPNAME),
                gaie[i].pszKeyName, gaie[i].nDefault,
                ids(IDS_DLGEDITINI));
    }

    ReadCustomProfile();
}

/*************************************************************************
* WriteEnv
*
* This function is the counterpart to ReadEnv.  It saves values
* in the profile file.
*
*************************************************************************/

STATICFN VOID WriteEnv(VOID)
{
    register INT i;
    TCHAR szBuf[17];

    for (i = 0; gaie[i].pszKeyName; i++) {
        /*
         * Has the user changed the value since it was read?
         */
        if (gaie[i].nSave != *gaie[i].pnVar) {
            /*
             * If the new value is the same as the default value,
             * erase the entry from the ini file.  Otherwise,
             * write the user-specified value out.
             */
            if (*gaie[i].pnVar == gaie[i].nDefault) {
                WritePrivateProfileString(ids(IDS_APPNAME),
                        gaie[i].pszKeyName, NULL, ids(IDS_DLGEDITINI));
            }
            else {
                itoaw(*gaie[i].pnVar, szBuf, 10);
                WritePrivateProfileString(ids(IDS_APPNAME),
                        gaie[i].pszKeyName, szBuf, ids(IDS_DLGEDITINI));
            }
        }
    }

    WriteCustomProfile();
}

/************************************************************************
* MainWndProc
*
* This is the window procedure for the "dlgedit" class.  This is the
* class of the main dialog editor "client" window.
*
************************************************************************/

WINDOWPROC MainWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_CREATE:
            {
                RECT rc;

                /*
                 * Create the status window.
                 */
                CreateDialog(ghInst, MAKEINTRESOURCE(DID_STATUS),
                        hwnd, StatusDlgProc);

                /*
                 * Save away its height for sizing later (like when
                 * the app is minimized then restored).
                 */
                GetWindowRect(hwndStatus, &rc);
                gcyStatus = rc.bottom - rc.top;

                ghwndSubClient = CreateWindow(szSubClientClass, NULL,
                        WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
                        hwnd, NULL, ghInst, NULL);

                ghMenuMain = GetMenu(hwnd);
                LoadMenuBitmaps(ghMenuMain);
            }

            break;

        case WM_ACTIVATE:
            /*
             * If the main window is getting activated, there is no
             * currently active dialog.
             */
            if (LOWORD(wParam))
                gidCurrentDlg = 0;

            goto DoDefault;

        case WM_INITMENU:
            if (GetMenu(ghwndMain) == (HMENU)wParam)
                InitMenu((HMENU)wParam);

            break;

        case WM_MENUSELECT:
            if (HIWORD(wParam) &
                    (MF_POPUP | MF_SYSMENU))
                gMenuSelected = 0;
            else
                gMenuSelected = LOWORD(wParam);

            break;

        case WM_COMMAND:
            DialogMenu(LOWORD(wParam));
            break;

        case WM_KEYDOWN:
            switch (wParam) {
                case VK_UP:
                case VK_DOWN:
                case VK_LEFT:
                case VK_RIGHT:
                    if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                            (GetKeyState(VK_CONTROL) & 0x8000))
                        break;

                    /*
                     * Ignore it if we are not in a normal state
                     * (don't allow when dragging).
                     */
                    if (gState != STATE_NORMAL)
                        break;

                    /*
                     * Be sure any outstanding changes get applied
                     * without errors.
                     */
                    if (!StatusApplyChanges())
                        break;

                    /*
                     * Move the control in the specified direction.
                     */
                    MoveControl(wParam);
                    break;

                case VK_TAB:
                    if (GetKeyState(VK_CONTROL) & 0x8000)
                        break;

                    /*
                     * Ignore it if we are not in a normal state
                     * (don't allow when dragging).
                     */
                    if (gState != STATE_NORMAL)
                        break;

                    /*
                     * Be sure any outstanding changes get applied
                     * without errors.
                     */
                    if (!StatusApplyChanges())
                        break;

                    /*
                     * Is the shift key pressed also?
                     */
                    if (GetKeyState(VK_SHIFT) & 0x8000)
                        SelectPrevious();
                    else
                        SelectNext();

                    break;

                case VK_ESCAPE:
                    if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                            (GetKeyState(VK_CONTROL) & 0x8000))
                        break;

                    /*
                     * Be sure any outstanding changes get applied
                     * without errors.
                     */
                    if (!StatusApplyChanges())
                        break;

                    if (gState == STATE_SELECTING)
                        OutlineSelectCancel();

                    /*
                     * Cancel any drag operation they might have been doing.
                     */
                    if (gState != STATE_NORMAL)
                        DragCancel();

                    break;

                case VK_RETURN:
                    if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                            (GetKeyState(VK_CONTROL) & 0x8000))
                        break;

                    /*
                     * Be sure any outstanding changes get applied
                     * without errors.
                     */
                    if (!StatusApplyChanges())
                        break;

                    switch (gState) {
                        POINTS mpt;
                        POINT pt;
                        DWORD dwPos;

                        case STATE_SELECTING:
                            /*
                             * In outline selection mode.  Map the
                             * location of the mouse at the time that
                             * the user pressed Enter into a point
                             * relative to the dialog client and complete
                             * the selection operation.
                             */
                            dwPos = GetMessagePos();
                            mpt = (*((POINTS *)&(dwPos)));
                            ((pt).x = (mpt).x, (pt).y = (mpt).y);
                            ScreenToClient(gcd.npc->hwnd, &pt);
                            OutlineSelectEnd(pt.x, pt.y);

                            break;

                        case STATE_DRAGGING:
                        case STATE_DRAGGINGNEW:
                            /*
                             * We are dragging something.  Map the
                             * location of the mouse at the time
                             * that the user pressed Enter into a
                             * point relative to the proper window
                             * and complete the drag operation.
                             */
                            dwPos = GetMessagePos();
                            mpt = (*((POINTS *)&(dwPos)));
                            ((pt).x = (mpt).x, (pt).y = (mpt).y);

                            /*
                             * The point must be changed to be relative to
                             * the window that the ending mouse up message
                             * would have come through, which will be the
                             * capture window for the drag.  This will be
                             * the dialog if we are adding a new control,
                             * or it will be the selected control if we are
                             * dragging an existing control.
                             */
                            ScreenToClient((gState == STATE_DRAGGING) ?
                                    gnpcSel->hwnd : gcd.npc->hwnd, &pt);

                            /*
                             * If the dialog is selected, map the points from
                             * the client area to the window.
                             */
                            if (gfDlgSelected)
                                MapDlgClientPoint(&pt, TRUE);

                            DragEnd(pt.x, pt.y);

                            break;
                    }

                    break;
            }

            break;

        case WM_NCCALCSIZE:
            /*
             * Save away what is going to be the new window position.
             */
            if (!IsIconic(hwnd) && !IsZoomed(hwnd))
                grcAppPos = *((LPRECT)lParam);

            /*
             * Now let the DefWindowProc calculate the client area normally.
             */
            goto DoDefault;

        case WM_MOVE:
            if (gfEditingDlg)
                RepositionDialog();

            break;

        case WM_SIZE:
            SizeRibbons(hwnd);

            /*
             * Did the app start minimized and is it being restored
             * for the first time?  If so, show the toolbox if
             * the user has requested it.
             */

if (fStartAsIcon && !IsIconic(hwnd)) {
                if (gfShowToolbox)
                    ToolboxShow(TRUE);

                fStartAsIcon = FALSE;
            }

            break;

        case WM_SYSCOLORCHANGE:
            LoadSysColorBitmaps();
            break;

        case WM_CLOSE:
            if (ghwndTestDlg)
                DestroyTestDialog();

            if (DoWeSave(FILE_INCLUDE) == IDCANCEL ||
                    DoWeSave(FILE_RESOURCE) == IDCANCEL)
                break;

            /*
             * First destroy the Properties Bar.
             */
            DestroyWindow(hwndStatus);
            hwndStatus = NULL;

            DestroyWindow(hwnd);
            break;

        case WM_QUERYENDSESSION:
            if (ghwndTestDlg)
                DestroyTestDialog();

            if (DoWeSave(FILE_INCLUDE) == IDCANCEL ||
                    DoWeSave(FILE_RESOURCE) == IDCANCEL)
                return FALSE;
            else
                return TRUE;

        case WM_DESTROY:
            /*
             * Save the position of the app's window.
             */
            WriteWindowPos(&grcAppPos, IsZoomed(hwnd), szAppPos);

            WinHelp(hwnd, gszHelpFile, HELP_QUIT, 0L);
            FreeMenuBitmaps();
            PostQuitMessage(0);
            break;

        default:
            /*
             * Is this the registered help message from one of the common
             * dialogs?  If so, show the help for it.
             *
             * The check to be sure gmsgHelp is non-zero is just in
             * case the call to register the help message failed
             * (it will return zero) and there happens to be a zero
             * message that gets sent to this window somehow.
             */
            if (msg == gmsgHelp && gmsgHelp) {
                ShowHelp(FALSE);
                return 0;
            }

        DoDefault:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

/************************************************************************
* LoadSysColorBitmaps
*
* This function loads bitmaps that depend on the system window and
* highlight colors.  As it loads them, it replaces two special colors
* in them with some system colors.
* This is used for the control type bitmaps that appear in lines
* in the listbox in the Order/Group dialog.
*
************************************************************************/

STATICFN VOID LoadSysColorBitmaps(VOID)
{
    DWORD rgbWindow;
    DWORD rgbWindowText;
    DWORD rgbHighlight;
    DWORD rgbHighlightText;
    INT i;

    rgbWindow = GetSysColor(COLOR_WINDOW);
    rgbWindowText = GetSysColor(COLOR_WINDOWTEXT);
    rgbHighlight = GetSysColor(COLOR_HIGHLIGHT);
    rgbHighlightText = GetSysColor(COLOR_HIGHLIGHTTEXT);

    if (hbmTabStop)
        DeleteObject(hbmTabStop);

    hbmTabStop = LoadAlterBitmap(IDBM_TABSTOP, rgbWindow, rgbWindowText);

    if (hbmTabStopSel)
        DeleteObject(hbmTabStopSel);

    hbmTabStopSel = LoadAlterBitmap(IDBM_TABSTOP,
            rgbHighlight, rgbHighlightText);

    for (i = 0; i < CCONTROLS; i++) {
        if (awcd[i].hbmCtrlType)
            DeleteObject(awcd[i].hbmCtrlType);

        awcd[i].hbmCtrlType = LoadAlterBitmap(
                awcd[i].idbmCtrlType, rgbWindow, rgbWindowText);

        if (awcd[i].hbmCtrlTypeSel)
            DeleteObject(awcd[i].hbmCtrlTypeSel);

        awcd[i].hbmCtrlTypeSel = LoadAlterBitmap(
                awcd[i].idbmCtrlType, rgbHighlight, rgbHighlightText);
    }

    if (ghbmDragHandle)
        DeleteObject(ghbmDragHandle);

    ghbmDragHandle = LoadAlterBitmap(IDBM_DRAGHANDLE,
            rgbWindow, rgbHighlight);

    if (ghbmDragHandle2)
        DeleteObject(ghbmDragHandle2);

    ghbmDragHandle2 = LoadAlterBitmap(IDBM_DRAGHANDLE2,
            rgbWindow, rgbHighlight);
}

/************************************************************************
* LoadAlterBitmap
*
* This function loads a single bitmap.  As it does, it replaces a
* couple special RGB colors (REPLACECOLOR1 and REPLACECOLOR2) with
* the passed in RGB colors.
*
* Arguments:
*   INT idbm      - Integer ID of the bitmap to load.
*   DWORD rgbNew  - Color to replace the special color with.
*   DWORD rgbNew2 - A second color to replace the second special color with.
*
* Returns:
*   The handle to the bitmap, or NULL if an error occurs.
*
************************************************************************/

STATICFN HBITMAP LoadAlterBitmap(
    INT idbm,
    DWORD rgbNew,
    DWORD rgbNew2)
{
    register INT i;
    LPBITMAPINFOHEADER lpbihInfo;
    HDC hdcScreen;
    HANDLE hresLoad;
    HANDLE hres;
    DWORD FAR *qlng;
    LPBYTE lpbBits;
    HANDLE hbmp;
    DWORD rgbReplace1;
    DWORD rgbReplace2;

    hresLoad = FindResource(ghInst, MAKEINTRESOURCE(idbm), RT_BITMAP);
    if (!hresLoad)
        return NULL;

    hres = LoadResource(ghInst, hresLoad);
    if (!hresLoad)
        return NULL;

    rgbNew = RGBInvertRGB(rgbNew);
    rgbNew2 = RGBInvertRGB(rgbNew2);
    rgbReplace1 = RGBInvertRGB(REPLACECOLOR1);
    rgbReplace2 = RGBInvertRGB(REPLACECOLOR2);
    lpbihInfo = (LPBITMAPINFOHEADER)LockResource(hres);
    qlng = (LPDWORD)((PBYTE)(lpbihInfo) + lpbihInfo->biSize);

    for (i = 0; i < (1 << lpbihInfo->biBitCount); i++, qlng++) {
        if (*qlng == rgbReplace1)
            *qlng = rgbNew;
        else if (*qlng == rgbReplace2)
            *qlng = rgbNew2;
    }

    /*
     * First skip over the header structure.
     */
    lpbBits = (LPBYTE)(lpbihInfo + 1);

    /*
     * Skip the color table entries, if any.
     */
    lpbBits += (1 << (lpbihInfo->biBitCount)) * sizeof(RGBQUAD);

    /*
     * Create a color bitmap compatible with the display device.
     */
    if (hdcScreen = GetDC(NULL)) {
        hbmp = CreateDIBitmap(hdcScreen, lpbihInfo, (LONG)CBM_INIT,
                lpbBits, (LPBITMAPINFO)lpbihInfo, DIB_RGB_COLORS);
        ReleaseDC(NULL, hdcScreen);
    }

    UnlockResource(hres);
    FreeResource(hres);

    return hbmp;
}

/************************************************************************
* RGBInvertRGB
*
* Reverses the RGB order of a color.  This needs to be done to match
* the resource file format of the color table.
*
************************************************************************/

STATICFN DWORD RGBInvertRGB(
    DWORD rgb)
{
    return (DWORD)RGB(GetBValue(rgb), GetGValue(rgb), GetRValue(rgb));
}

/************************************************************************
* SizeRibbons
*
* This function positions and sizes the child ribbon and subclient
* windows in the dialog editor.  It needs to be called any time the
* size of the main windows changes.
*
* Arguments:
*   HWND hwnd - Parent window handle.
*
************************************************************************/

STATICFN VOID SizeRibbons(
    HWND hwnd)
{
    RECT rcClient;

    if (hwndStatus && !IsIconic(hwnd)) {
        /*
         * Get the client area.
         */
        GetClientRect(hwnd, &rcClient);

        /*
         * Size/move the status and subclient window to fit
         * the new client area.
         */
        SetWindowPos(hwndStatus, NULL,
                0, 0,
                rcClient.right - rcClient.left,
                min(rcClient.bottom - rcClient.top, gcyStatus),
                SWP_NOACTIVATE | SWP_NOZORDER);

        SetWindowPos(ghwndSubClient, NULL,
                0, gcyStatus,
                rcClient.right - rcClient.left,
                max((rcClient.bottom - rcClient.top) - gcyStatus, 0),
                SWP_NOACTIVATE | SWP_NOZORDER);
    }
}

/****************************************************************************
* DialogTerminate
*
* This undoes what DialogInit does.  It should be called before terminating
* and after a DialogInit.
*
****************************************************************************/

STATICFN VOID DialogTerminate(VOID)
{
    register INT i;

    /*
     * Save the Preferences data.
     */
    WriteEnv();

    if (hbmTabStop)
        DeleteObject(hbmTabStop);

    if (hbmTabStopSel)
        DeleteObject(hbmTabStopSel);

    if (ghbmDragHandle)
        DeleteObject(ghbmDragHandle);

    if (ghbmDragHandle2)
        DeleteObject(ghbmDragHandle2);

    if (ghDCMem)
        DeleteDC(ghDCMem);

    /*
     * Free the control type bitmaps.
     */
    for (i = 0; i < CCONTROLS; i++) {
        if (awcd[i].hbmCtrlType)
            DeleteObject(awcd[i].hbmCtrlType);

        if (awcd[i].hbmCtrlTypeSel)
            DeleteObject(awcd[i].hbmCtrlTypeSel);
    }

    /*
     * Free all the custom control links.  This must be done before the
     * app exits so that any loaded DLL's get unloaded!
     */
    while (gpclHead)
        RemoveCustomLink(gpclHead);

    if (hpenDarkGray)
        DeleteObject(hpenDarkGray);

    if (ghhkMsgFilter)
        UnhookWindowsHookEx(ghhkMsgFilter);

    if (lpfnRegisterPenApp)
        (*lpfnRegisterPenApp)((WORD)1, FALSE);
}


DLGEDIT.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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: dlgedit.h
*
* Main header file for the dialog box editor.
*
****************************************************************************/

#define NOMINMAX
#include <windows.h>
#include <custcntl.h>
#include <setjmp.h>
#include "ids.h"

/*
 * For unicode support.
 */
LPWSTR itoaw(INT value, LPWSTR string, INT radix);
INT awtoi(LPWSTR string);

/*
 * temporary define
 */
#define BS_PUSHBOX 0x0AL

#define STATICFN        static

#define WINDOWPROC      LONG APIENTRY
#define DIALOGPROC      BOOL APIENTRY

typedef HWND FAR *LPHWND;

/*
 * Used to create a window of "DIALOG" class.
 */
#define DIALOGCLASS     0x8002

#define ORDID_RT_RESOURCE32     0x00    // Aligned res file dummy resource.
#define ORDID_RT_DIALOG         0x05    // Dialog resource type.
#define ORDID_RT_DLGINCLUDE     0x11    // Dialog include file resource type.

/*
 * The ordinal for the name of the DLGINCLUDE resource.
 */
#define ORDID_DLGINCLUDE_NAME   1

/*
 * Macro to pack a point into a long value.
 */
#define POINT2LONG(pt, l)   (l = MAKELONG(LOWORD((pt).x), LOWORD((pt).y)))

/*
 * Macros to simplify working with menus.
 */
#define MyEnableMenuItem(hMenu, wIDEnableItem, fEnable) \
    EnableMenuItem((hMenu),(wIDEnableItem),(fEnable)?MF_ENABLED:MF_GRAYED)

#define MyEnableMenuItemByPos(hMenu, wPosEnableItem, fEnable) \
    EnableMenuItem((hMenu),(wPosEnableItem),(fEnable)? \
    MF_ENABLED | MF_BYPOSITION:MF_GRAYED | MF_BYPOSITION)

#define MyCheckMenuItem(hMenu, wIDCheckItem, fCheck) \
    CheckMenuItem((hMenu),(wIDCheckItem),(fCheck)?MF_CHECKED:MF_UNCHECKED)

/*
 * This macro returns TRUE if the given string is an ordinal.
 */
#define IsOrd(psz)      (((PORDINAL)(psz))->wReserved == \
                        (WORD)0xffff ? TRUE : FALSE)

/*
 * This macro returns the ordinal id in the specified name/ord field.
 */
#define OrdID(psz)      (((PORDINAL)(psz))->wOrdID)

/*
 * Integer property values.
 */
#define PROP_FNCHILD        MAKEINTRESOURCE(0x3345)

/*
 * Macro to set/remove an NPCTYPE pointer into a control or dialog hwnd.
 */
#define SETPCINTOHWND(hwnd, npc) \
    SetWindowLong((hwnd), GWL_USERDATA, (DWORD)(npc))

#define UNSETPCINTOHWND(hwnd)   (hwnd)

/*
 * Macro to extract an NPCTYPE from a control or dialog hwnd.
 */
#define PCFROMHWND(hwnd)    ((NPCTYPE)GetWindowLong(hwnd, GWL_USERDATA))

/*
 * Macros to set and retrieve the original window proc from
 * a child window that has been subclassed by the editor.
 */
#define SETCHILDPROC(hwnd, lpfn) SetProp((hwnd), PROP_FNCHILD, (HANDLE)(lpfn))
#define GETCHILDPROC(hwnd)      ((WNDPROC)GetProp((hwnd), PROP_FNCHILD))
#define UNSETCHILDPROC(hwnd)    RemoveProp((hwnd), PROP_FNCHILD)

/*
 * Used to indicate an "impossible" file position (offset).
 */
#define FPOS_MAX                ((DWORD)(-1L))

/*
 * Special flag I place in the resource that goes into the clipboard
 * that means that only the controls in the dialog template in the
 * clipboard are to be copied, not the entire dialog.  Because this
 * value is placed into the cx field of the dialog template, it can
 * only be a WORD in size.  I use 0xffff (-1) because this would be
 * an impossible value for the width of a dialog.
 */
#define CONTROLS_ONLY           ((WORD)0xffff)

/*
 * Some colors used in the editor.
 */
#define LIGHTGRAY               RGB(192, 192, 192)
#define DARKGRAY                RGB(128, 128, 128)
#define REPLACECOLOR1           RGB(255, 255, 255)      // White
#define REPLACECOLOR2           RGB(0, 0, 0)            // Black

/*
 * Maximum size of a file name plus path specification.
 */
#define CCHMAXPATH              260

/*
 * Maximum length of a long hex value ("0x" + 8 digits) not counting the null.
 */
#define CCHHEXLONGMAX           10

/*
 * Maximum number of characters in an ID not counting the null character.
 * This allows room for "0xFFFF" or "-32768".
 */
#define CCHIDMAX                6

/*
 * Width and height of a handle in pixels.
 */
#define CHANDLESIZE             6

/*
 * Height in DU's of the edit field of a combobox.
 */
#define COMBOEDITHEIGHT         12

/*
 * Character constants.
 */
#define CHAR_NULL               L'\0'
#define CHAR_TAB                L'\t'
#define CHAR_NEWLINE            L'\n'
#define CHAR_RETURN             L'\r'
#define CHAR_BACKSLASH          L'\\'
#define CHAR_COLON              L':'
#define CHAR_DOT                L'.'
#define CHAR_UNDERLINE          L'_'
#define CHAR_ASTERISK           L'*'
#define CHAR_SLASH              L'/'
#define CHAR_POUND              L'#'
#define CHAR_ORSYMBOL           L'|'
#define CHAR_COMMA              L','
#define CHAR_SPACE              L' '
#define CHAR_DBLQUOTE           L'"'
#define CHAR_MINUS              L'-'
#define CHAR_PLUS               L'+'
#define CHAR_0                  L'0'
#define CHAR_A                  L'a'
#define CHAR_CAP_A              L'A'
#define CHAR_F                  L'f'
#define CHAR_CAP_F              L'F'
#define CHAR_X                  L'x'
#define CHAR_CAP_X              L'X'
#define CHAR_Z                  L'z'
#define CHAR_CAP_Z              L'Z'
#define CHAR_DOSEOF             L'\x1a'

/*
 * Defines for the different drag handles.
 */
#define DRAG_CENTER             (-1)
#define DRAG_LEFTBOTTOM         0
#define DRAG_BOTTOM             1
#define DRAG_RIGHTBOTTOM        2
#define DRAG_RIGHT              3
#define DRAG_RIGHTTOP           4
#define DRAG_TOP                5
#define DRAG_LEFTTOP            6
#define DRAG_LEFT               7

/*
 * Count of lines to insert into listbox and combobox controls during
 * test mode.
 */
#define CLBTESTLINES            25

#define CCHTEXTMAX              256
#define CCHFILEBUFFER           256

/*
 * Timer ID for the pre-drag timer.
 */
#define TID_PREDRAG             5

/*
 * The id of the "unused" item.  This is the id value for controls
 * that the user creates where they do not care about the value
 * of the id because it will not be referenced in their code.
 */
#define IDUNUSED                (-1)

/*
 * Defines for the NextID() function.
 */
#define NEXTID_DIALOG           0           /* ID for a new dialog.         */
#define NEXTID_CONTROL          1           /* ID for a new control.        */
#define NEXTID_LABEL            2           /* ID for a new label.          */

/*
 * Flags for the GridizeRect function.  They specify which points in
 * the rectangle to apply gridding to.
 */
#define GRIDIZE_LEFT            0x0001      // Gridize the left edge.
#define GRIDIZE_BOTTOM          0x0002      // Gridize the bottom edge.
#define GRIDIZE_RIGHT           0x0004      // Gridize the right edge.
#define GRIDIZE_TOP             0x0008      // Gridize the top edge.
#define GRIDIZE_SAMESIZE        0x0010      // Don't change cx or cy.

/*
 * Default spacing constants.
 */
#define DEFCXGRID               1           // X grid.
#define DEFCYGRID               1           // Y grid.
#define DEFXMARGIN              6           // Top/bottom margin.
#define DEFYMARGIN              6           // Left/right margin.
#define DEFXMINPUSHSPACE        3           // Min. horizontal button spacing.
#define DEFXMAXPUSHSPACE        16          // Max. horizontal button spacing.
#define DEFYPUSHSPACE           3           // Vertical button spacing.
#define DEFXSPACE               6           // Horizontal control spacing.
#define DEFYSPACE               0           // Vertical control spacing.

#define IC_UNKNOWN              (-1)
#define IC_BUTTON               0
#define IC_SCROLLBAR            1
#define IC_EDIT                 2
#define IC_STATIC               3
#define IC_LISTBOX              4
#define IC_COMBOBOX             5
#define IC_CUSTOM               6
#define IC_DIALOG               7
#define IC_WINDOW               8
#define IC_RESFLAGS             9
#define IC_EXSTYLE              10

#define MSG_DELETEDIALOG        0
#define MSG_OUTOFMEMORY         1
#define MSG_CANTCREATE          2
#define MSG_SYMNOCHANGE         3
#define MSG_IDSYMMISMATCH       4
#define MSG_CLOSING             5
#define MSG_BADRESFILE          6
#define MSG_INCLCLOSING         7
#define MSG_SYMEXISTS           8
#define MSG_BADSYMBOLID         9
#define MSG_LABELDUPID          10
#define MSG_SELECTFIRST         11
#define MSG_CTRLDUPID           12
#define MSG_BADCUSTDLL          13
#define MSG_NOCLIP              14
#define MSG_INTERNAL            15
#define MSG_NOMOUSE             16
#define MSG_NOINIT              17
#define MSG_GTZERO              18
#define MSG_ICONNAMEHASBLANKS   19
#define MSG_IDUPIDS             20
#define MSG_CREATECTRLERROR     21
#define MSG_CANTOPENRES         22
#define MSG_CONFIRMDISCARD      23
#define MSG_SYMNOTFOUND         24
#define MSG_NOCLASS             25
#define MSG_POSITIVENUM         26
#define MSG_MEMERROR            27
#define MSG_DLGNAMEHASBLANKS    28
#define MSG_NODLGNAME           29
#define MSG_CANTINITDLL         30
#define MSG_NOICONNAME          31
#define MSG_RESTOREDIALOG       32
#define MSG_ZEROPOINTSIZE       33
#define MSG_MINGTMAXSPACE       34
#define MSG_CUSTCNTLINUSE       35
#define MSG_CUSTALREADYLOADED   36
#define MSG_CANTLOADDLL         37
#define MSG_DLLBADEXPORTS       38
#define MSG_DLLBADCOUNT         39

/*
 * The following defines are used as masks in the styles arrays.
 * They each define a set of bits, all of which have to be set
 * properly for any of the individual styles to be considered
 * to be a match.  They are used for groups of styles that do
 * not have a single bit set.  In other words, these styles
 * depend on having some bits OFF, as well as other bits ON.
 */
#define BS_ALL          (BS_PUSHBUTTON | BS_DEFPUSHBUTTON | BS_CHECKBOX | \
                        BS_AUTOCHECKBOX | BS_RADIOBUTTON | BS_3STATE | \
                        BS_AUTO3STATE | BS_GROUPBOX | BS_USERBUTTON | \
                        BS_AUTORADIOBUTTON | BS_PUSHBOX | BS_OWNERDRAW)
#define SS_ALL          (SS_LEFT | SS_CENTER | SS_RIGHT | SS_ICON | \
                        SS_BLACKRECT | SS_GRAYRECT | SS_WHITERECT | \
                        SS_BLACKFRAME | SS_GRAYFRAME | SS_WHITEFRAME | \
                        SS_USERITEM | SS_SIMPLE | SS_LEFTNOWORDWRAP)
#define CBS_ALL         (CBS_SIMPLE | CBS_DROPDOWN | CBS_DROPDOWNLIST)
#define SBS_ALL         (SBS_HORZ | SBS_VERT)
#define ES_ALIGN        (ES_LEFT | ES_CENTER | ES_RIGHT)
#define WS_CAPTIONALL   (WS_CAPTION | WS_BORDER | WS_DLGFRAME)

/*
 * Possible values for gState, which tells us about special modes
 * (states) the editor is in.
 */
#define STATE_NORMAL        0   // Normal state.
#define STATE_DRAGGINGNEW   1   // Dragging a new control from the toolbox.
#define STATE_DRAGGING      2   // Dragging an existing control.
#define STATE_SELECTING     3   // Outline selecting is in progress.
#define STATE_PREDRAG       4   // During debounce period before dragging.

/*
 * Control type (W_*) constants.  These are used as indexes into the
 * awcd structure that describes each type of class.
 */
#define W_NOTHING           (-1)

#define W_TEXT              0
#define W_EDIT              1
#define W_GROUPBOX          2
#define W_PUSHBUTTON        3
#define W_CHECKBOX          4
#define W_RADIOBUTTON       5
#define W_COMBOBOX          6
#define W_LISTBOX           7
#define W_HORZSCROLL        8
#define W_VERTSCROLL        9
#define W_FRAME             10
#define W_RECT              11
#define W_ICON              12
#define W_CUSTOM            13

#define W_DIALOG            14

/*
 * Number of control types.  Note that this does NOT count the
 * "W_DIALOG" type, only actual controls like "W_CHECKBOX", etc.
 */
#define CCONTROLS           14

/*
 * The following defines have the location (by zero based position)
 * of popup menu items.  If the menu arrangement is changed in the
 * .RC file, these defines MUST be updated!
 */
#define MENUPOS_FILE            0
#define MENUPOS_EDIT            1
#define MENUPOS_ARRANGE         2
#define MENUPOS_ARRANGEALIGN        0
#define MENUPOS_ARRANGESPACE        1
#define MENUPOS_ARRANGESIZE         2
#define MENUPOS_ARRANGEPUSH         3

/*
 * Resource memory management flags.
 */
#define MMF_MOVEABLE            0x0010
#define MMF_PURE                0x0020
#define MMF_PRELOAD             0x0040
#define MMF_DISCARDABLE         0x1000

#define DEFDLGMEMFLAGS          (MMF_MOVEABLE | MMF_PURE | MMF_DISCARDABLE)

/*
 * Default location of a new dialog.
 */
#define DEFDIALOGXPOS   6
#define DEFDIALOGYPOS   18

/*
 * Default point size for a new dialog's font.  The default face name
 * is in the string IDS_DEFFONTNAME.
 */
#ifdef JAPAN
#define DEFPOINTSIZE    12
#else
#define DEFPOINTSIZE    8
#endif

#define FILE_NOSHOW             0x0001  /* Save without prompting for name  */
#define FILE_INCLUDE            0x0002  /* Save/load include file           */
#define FILE_RESOURCE           0x0004  /* Save/load resource file          */
#define FILE_SAVEAS             0x0008  /* Save as (prompt for file name).  */
#define FILE_DLL                0x0010  /* A custom control DLL file.       */

/*
 * Special case ordinal id values for the predefined control classes.
 */
#define ORDID_BUTTONCLASS           0x80
#define ORDID_EDITCLASS             0x81
#define ORDID_STATICCLASS           0x82
#define ORDID_LISTBOXCLASS          0x83
#define ORDID_SCROLLBARCLASS        0x84
#define ORDID_COMBOBOXCLASS         0x85

/*
 * This structure is used to link resources.
 */
typedef struct tagRESLINK {
    struct tagRESLINK *prlNext;     /* Next in list.                        */
    BOOL fDlgResource;              /* TRUE if this is a dialog resource.   */
    INT cbRes;                      /* Size of the resource.                */
    HANDLE hRes;                    /* Handle to global memory with the res.*/
    LPTSTR pszName;                 /* Name/ord of the resource (if dialog).*/
    WORD wLanguage;                 /* Language identifier (if dialog).     */
} RESLINK, *PRESLINK;

/*
 * Describes a window class.
 *
 * The flStyles field is the default styles that this control type will have
 * when first created.  The flStylesBad field is the styles that can cause
 * problems when manipulating the control in work mode, such as *_OWNERDRAW
 * and so forth.  Controls with this style can be created with the editor
 * and will be saved as such in the .DLG file, but the actual control created
 * in work mode will not have any of these styles.
 */
typedef struct {
    INT iType;              /* Control type index, one of the W_ constants. */
    DWORD flStyles;         /* Default control styles for this window class.*/
    DWORD flStylesBad;      /* Styles NOT to use when creating this control.*/
    DWORD flStylesTestBad;  /* Styles NOT to use in Test mode.              */
    DWORD flExtStyle;       /* Default extended styles.                     */
    INT cxDefault;          /* Default x size for this control.             */
    INT cyDefault;          /* Default y size for this control.             */
    INT iClass;             /* Index to the IC_ class for this window class.*/
    LPTSTR pszClass;        /* Class name (for custom controls).            */
    UINT fEmulated:1;       /* TRUE if this is an emulated custom control.  */
    UINT fUnicodeDLL:1;     /* TRUE if the DLL functions are UNICODE.       */
    UINT fHasText:1;        /* TRUE if this control type can have text.     */
    UINT fSizeable:1;       /* TRUE if the control can be sized.            */
    UINT fSizeToText:1;     /* TRUE if the control can be sized to its text.*/
    INT idStylesDialog;     /* Styles dialog id for this window class.      */
    WNDPROC pfnStylesDlgProc; /* Styles dialog procedure.                   */
    INT HelpContext;        /* Help context ID for the styles dialog.       */
    UINT idsTextDefault;    /* String id of default text.                   */
    LPTSTR pszTextDefault;  /* Default text for a new control of this type. */
    WNDPROC pfnOldWndProc;  /* Saves the old window proc when subclassing.  */
    INT idbmCtrlType;       /* ID of the bitmap res. for this control type. */
    HBITMAP hbmCtrlType;    /* Handle of the bitmap for this control type.  */
    HBITMAP hbmCtrlTypeSel; /* The selected version of the above.           */
    INT idbmToolBtnUp;      /* ID of "up" bmp res. for the Toolbox button.  */
    HBITMAP hbmToolBtnUp;   /* hbm of "up" bitmap for the Toolbox button.   */
    INT idbmToolBtnDown;    /* ID of "down" bmp res. for the Toolbox button.*/
    HBITMAP hbmToolBtnDown; /* hbm of "down" bitmap for the Toolbox button. */
    HANDLE hmod;            /* Custom control DLL module handle.            */
    INT cStyleFlags;        /* Count of custom control style flags.         */
    LPCCSTYLEFLAG aStyleFlags; /* Ptr to custom control style flag table.   */
    PROC lpfnStyle;         /* Custom control Style function.               */
    PROC lpfnSizeToText;    /* Custom control SizeToText function.          */
    DWORD flCtrlTypeMask;   /* Mask for custom control type styles.         */
} WINDOWCLASSDESC;
typedef WINDOWCLASSDESC *PWINDOWCLASSDESC;

typedef struct tagCTYPE {
    struct tagCTYPE *npcNext;   /* Next CTYPE in linked list.               */
    PWINDOWCLASSDESC pwcd;      /* Points to the window class desc. struct. */
    HWND hwnd;                  /* Handle of control window.                */
    HWND hwndDrag;              /* Handle of the drag window for this ctrl. */
    DWORD flStyle;              /* Control style.                           */
    DWORD flExtStyle;           /* Control extended style.                  */
    INT id;                     /* Control window id.                       */
    LPTSTR text;                /* Text for control window.                 */
    RECT rc;                    /* Location and size of the control.        */
    UINT fSelected:1;           /* TRUE if the control is selected.         */
    UINT fGroupEnd:1;           /* TRUE if ctrl is the last one in a group. */
} CTYPE;
typedef CTYPE *NPCTYPE;

typedef struct tagLABEL {
    struct tagLABEL *npNext;    /* pointer to next in the list              */
    LPTSTR pszLabel;            /* Name of the symbol                       */
    INT id;                     /* ID value for this label                  */
    INT idOrig;                 /* Original ID value for this label         */
    DWORD fpos;                 /* File pointer to "#define" in include file*/
    INT nValueOffset;           /* Offset to id value start from fpos       */
} LABEL;
typedef LABEL *NPLABEL;

/*
 * Structure that is used to link together a list of custom controls.
 * Each link points to an associated WINDOWCLASSDESC structure that
 * defines the custom control type in detail.
 */
typedef struct tagCUSTLINK {
    struct tagCUSTLINK *pclNext;/* Next CUSTLINK in linked list.            */
    LPTSTR pszFileName;         /* Full path to DLL file (NULL if emulated).*/
    LPTSTR pszDesc;             /* Short, descriptive text for the control. */
    PWINDOWCLASSDESC pwcd;      /* Points to the window class desc. struct. */
} CUSTLINK, *PCUSTLINK;

typedef struct {
    UINT ids;                   /* String id for the message text.          */
    UINT fMessageBox;           /* Flags for the MessageBox function.       */
} MESSAGEDATA;

/*
 * Class Style structure.  Specifies the style bits that describe
 * each style, along with a mask that specifies the bits to compare
 * when looking for this style.  The mask is necessary when more than
 * one bit specifies a style.  For example, look at the BS_* styles,
 * which currently use the low 3 bits of the style flag to specify
 * eight different styles.  The idControl field is the checkbox or
 * radio button control id in the styles dialogs that corresponds to
 * this particular style, or zero if it is not settable by the user.
 */
typedef struct {
    DWORD flStyle;              /* Style bits that identify this style.     */
    DWORD flStyleMask;          /* Mask with the relevant bits.             */
    INT idControl;              /* ID of the control in the styles dlg.     */
} CLASSSTYLE, *PCLASSSTYLE;

/*
 * RC Keyword structure.  This describes a predefined RC keyword, like
 * "RADIOBUTTON" and "LISTBOX".
 *
 * rckwd, prckwd
 */
typedef struct {
    DWORD flStyle;              /* Style that identifies this keyword.      */
    DWORD flStyleMask;          /* Mask with the relevant bits.             */
    DWORD flStyleDefault;       /* Other style bits implicitly defined.     */
    UINT idsKeyword;            /* The RC keyword.                          */
    BOOL fHasText;              /* TRUE if this keywd has a text field.     */
} RCKEYWORD, *PRCKEYWORD;

/*
 * Class style description structure.  These contain information on each
 * of the IC_* constants.
 *
 * csd, pcsd
 */
typedef struct {
    UINT idsClass;              /* Class string for this class.             */
    PCLASSSTYLE pacs;           /* Pointer to class styles array.           */
    INT cClassStyles;           /* Count of class styles.                   */
    UINT idsStylesStart;        /* Starting index to style strings.         */
    PRCKEYWORD parckwd;         /* Pointer to predefined RC keywords.       */
    INT cKeywords;              /* Count of predefined RC keywords.         */
    WORD idOrd;                 /* Predefined ordinal id for this class.    */
} CLASSSTYLEDESC;

/*
 * One single entry for an environment setting saved in the
 * profile file.  Used by ReadEnv and WriteEnv.
 */
typedef struct _INIENTRY {
    LPTSTR pszKeyName;
    PINT pnVar;
    INT nDefault;
    INT nSave;
} INIENTRY;

/*
 * This structure defines additional information on the dialog being
 * edited that only pertains to dialogs, not controls.  This information
 * is therefore in a separate structure rather than the CTYPE structure.
 * Any dialog specific information that can be changed using the Dialog
 * Styles dialog must be contained in this structure.
 */
typedef struct {
    WORD fResFlags;                 /* Dialog resource memory flags.        */
    WORD wLanguage;                 /* Language identifier for the dialog.  */
    LPTSTR pszClass;                /* The dialog's class (or NULL).        */
    LPTSTR pszMenu;                 /* The dialog's menu (or NULL).         */
    DWORD DataVersion;              /* Data Version data for this dialog.   */
    DWORD Version;                  /* Version data for this dialog.        */
    DWORD Characteristics;          /* Characteristics data for this dialog.*/
    INT nPointSize;                 /* Point size of the dialog's font.     */
    TCHAR szFontName[LF_FACESIZE];  /* Face name of the dialog's font.      */
} DIALOGINFO, *PDIALOGINFO;

/*
 * This structure contains the globals that describe the current
 * dialog being edited.
 */
typedef struct {
    NPCTYPE npc;                    /* CTYPE structure for the dialog.      */
    LPTSTR pszDlgName;              /* Current dialog's name.               */
    PRESLINK prl;                   /* NULL or the dlg's resource link.     */
    BOOL fFontSpecified;            /* TRUE if a font is set for the dialog.*/
    HFONT hFont;                    /* Font handle of the dialog's font.    */
    INT cxChar;                     /* Pixel width of character box.        */
    INT cyChar;                     /* Pixel height of character box.       */
    DIALOGINFO di;                  /* Additional info for current dialog.  */
} CURRENTDLG;

/*
 * Structure that maps a subject (like a menu id or a dialog id) with
 * a help context to pass in to WinHelp.
 */
typedef struct {
    INT idSubject;                  /* Subject, usually a menu or dialog id.*/
    INT HelpContext;                /* The matching help context.           */
} HELPMAP;
typedef HELPMAP *PHELPMAP;

/*
 * The aligned ordinal structure.  Ordinals start with a word that is
 * always 0xffff, followed by a word that contains the ordinal id.
 */
typedef struct {
    WORD wReserved;
    WORD wOrdID;
} ORDINAL, *PORDINAL;

typedef struct {
    DWORD DataSize;                 // Size of data.
    DWORD HeaderSize;               // Size of the resource header.
} RES, *PRES;

typedef struct {
    DWORD DataVersion;              // Predefined resource data version.
    WORD MemoryFlags;               // Resource memory flags.
    WORD LanguageId;                // UNICODE support for NLS.
    DWORD Version;                  // Version of the resource data.
    DWORD Characteristics;          // Characteristics of the data.
} RES2, *PRES2;

typedef struct {
    DWORD lStyle;                   // Style for the dialog.
    DWORD lExtendedStyle;           // The extended style.
    WORD NumberOfItems;             // Number of controls.
    WORD x;                         // Starting x location.
    WORD y;                         // Starting y location.
    WORD cx;                        // Dialog width.
    WORD cy;                        // Dialog height.
} *PDIALOGBOXHEADER;

#define SIZEOF_DIALOGBOXHEADER  (                               \
    sizeof(DWORD) +                 /* lStyle           */      \
    sizeof(DWORD) +                 /* lExtendedStyle   */      \
    sizeof(WORD) +                  /* NumberOfItems    */      \
    sizeof(WORD) +                  /* x                */      \
    sizeof(WORD) +                  /* y                */      \
    sizeof(WORD) +                  /* cx               */      \
    sizeof(WORD)                    /* cy               */      \
    )

typedef struct {
    DWORD lStyle;                   // Style for the control.
    DWORD lExtendedStyle;           // The extended style.
    WORD x;                         // Starting x location.
    WORD y;                         // Starting y location.
    WORD cx;                        // Control width.
    WORD cy;                        // Control height.
    WORD wId;                       // Control id.
} *PCONTROLDATA;

#define SIZEOF_CONTROLDATA  (                                   \
    sizeof(DWORD) +                 /* lStyle           */      \
    sizeof(DWORD) +                 /* lExtendedStyle   */      \
    sizeof(WORD) +                  /* x                */      \
    sizeof(WORD) +                  /* y                */      \
    sizeof(WORD) +                  /* cx               */      \
    sizeof(WORD) +                  /* cy               */      \
    sizeof(WORD))                   /* wId              */

/*
 * SubLanguage table structure.  This structure describes each entry of a
 * sub language table.  These tables are pointed to by each entry in
 * the language table.
 */
typedef struct {
    WORD wSubLang;                  // SubLanguage value.
    INT idsSubLang;                 // String id of SUBLANG_* define.
    INT idsSubLangDesc;             // String id of sub-lang description.
} SUBLANGTABLE, *PSUBLANGTABLE;

/*
 * Language table structure.  This structure describes each entry in the
 * language table, which describes each unicode language.
 */
typedef struct {
    WORD wPrimary;                  // Language primary value.
    INT idsLang;                    // String id of LANG_* define.
    INT idsLangDesc;                // String id of language description.
    INT cSubLangs;                  // Number of sublanguages for this lang.
    PSUBLANGTABLE asl;              // Points to table of sublanguages.
} LANGTABLE;


DLGEXTRN.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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: dlgextrn.h
*
* External declarations for global variables.
*
****************************************************************************/

extern HANDLE ghInst;           /* Application instance handle.         */
extern HMENU ghMenuMain;        /* Main menu handle.                    */
extern PRESLINK gprlHead;       /* Head of the linked list of resources.*/
extern CURRENTDLG gcd;          /* Describes the current dialog.        */
extern HPEN hpenDarkGray;       /* A dark gray pen.                     */
extern HANDLE ghAccTable;       /* The accelerator table handle.        */
extern INT gMenuSelected;       /* Currently selected menu item.        */
extern HBITMAP ghbmDragHandle;  /* Handle for the drag handle bitmap.   */
extern HBITMAP ghbmDragHandle2; /* Handle for hollow drag handle bitmap.*/
extern HDC ghDCMem;             /* Memory DC for drawing bitmaps.       */
extern INT gCurTool;            /* Currently selected tool.             */
extern PWINDOWCLASSDESC gpwcdCurTool; /* Describes current tool.        */
extern BOOL gfToolLocked;       /* TRUE if a tool is locked down.       */
extern PCUSTLINK gpclHead;      /* Head of custom control linked list.  */
extern INT gidCurrentDlg;       /* Zero, or res id of DlgEdit dlg shown.*/
extern ORDINAL gordIcon;        /* Ordinal for the editor's W_ICON icon.*/

/*
 * Bitmap handles for the up and down W_NOTHING (pointer) tool bitmaps.
 */
extern HBITMAP ghbmPointerToolUp;
extern HBITMAP ghbmPointerToolDown;

/*
 * External declarations for the Windows variables that contain
 * command line information.
 */
extern INT __argc;
extern CHAR **__argv;

/*-- Initialized "Preferences" Data ------------------------------------*/
extern INIENTRY gaie[];         /* Initialization data desc. structure. */
extern BOOL gfHexMode;          /* TRUE if in "hex" mode.               */
extern BOOL gfTranslateMode;    /* TRUE if in "translate" mode.         */
extern BOOL gfShowToolbox;      /* TRUE if Toolbox is to be shown.      */
extern BOOL gfUseNewKeywords;   /* FALSE to only use "CONTROL" keyword. */
extern INT gcxGrid;             /* Current X grid.                      */
extern INT gcyGrid;             /* Current Y grid.                      */
extern INT gxMargin;            /* Top/bottom margin.                   */
extern INT gyMargin;            /* Left/right margin.                   */
extern INT gxSpace;             /* Horizontal control spacing.          */
extern INT gySpace;             /* Vertical control spacing.            */
extern INT gxMinPushSpace;      /* Minimum horizontal button spacing.   */
extern INT gxMaxPushSpace;      /* Maximum horizontal button spacing.   */
extern INT gyPushSpace;         /* Vertical button spacing.             */

extern TCHAR szAppPos[];        /* App window's position keyname.       */
extern TCHAR szTBPos[];         /* Toolbox window's position keyname.   */
extern TCHAR szCustomDLL[];     /* Section name for DLL custom cntls.   */

/*-- Sundry Handles.----------------------------------------------------*/
extern HWND hwndStatus;         /* Status ribbon window handle.         */
extern HWND ghwndToolbox;       /* Toolbox window handle.               */
extern HWND ghwndTestDlg;       /* Handle of the Test Mode dialog.      */
extern HWND ghwndMain;          /* Main application window.             */
extern HWND ghwndSubClient;     /* The "fake" client area.              */
extern HWND ghwndTrackOver;     /* Window being tracked over.           */

/*-- Some System constants.---------------------------------------------*/
extern INT gcxSysChar;          /* Pixel width of system font char box. */
extern INT gcySysChar;          /* Pixel height of system font char box.*/
extern INT gcyBorder;           /* System height of a border.           */
extern INT gcxPreDragMax;       /* Max X mouse move during pre-drag.    */
extern INT gcyPreDragMax;       /* Max Y mouse move during pre-drag.    */
extern INT gmsecPreDrag;        /* The milliseconds that pre-drag lasts.*/
extern INT gcyPixelsPerInch;    /* Vertical pixels/inch of system.      */
extern INT gcyStatus;           /* Saves height of the status window.   */

/*-- Some state variables.----------------------------------------------*/
extern INT gState;              /* Has the editor "state" or mode.      */
extern BOOL gfResChged;         /* Tell if RES has changed              */
extern BOOL gfIncChged;         /* Tell if include has changed          */
extern BOOL gfDlgChanged;       /* TRUE if current dialog has changed.  */
extern INT gcSelected;          /* Count of selected windows.           */
extern BOOL gfTestMode;         /* TRUE if in "test" mode.              */
extern BOOL gfDisabled;         /* TRUE if editing is disabled for now. */
extern BOOL gfEditingDlg;       /* TRUE means a dlg is picked to edit.  */
extern BOOL gfDlgSelected;      /* TRUE if the dialog has the selection.*/
extern RECT grcMinDialog;       /* Minimum tracking size of the dialog. */
extern RECT grcDlgClient;       /* Rectangle of the dialogs "client".   */
extern POINT gptCursorOffset;   /* Offset from ctrl origin to pointer.  */
extern RECT grcSelected;        /* Rectangle around selected controls.  */
extern RECT grcCopy;            /* Rect around controls being copied.   */
extern RECT grcTrackDU;         /* Tracking rect when dragging a control*/
extern RECT grcTrackWin;        /* Track rect in window units.          */
extern BOOL gfTrackRectShown;   /* TRUE if track rect is visible.       */
extern HDC ghDCTrack;           /* Clip DC used when tracking.          */
extern INT gHandleHit;          /* Current drag handle being tracked.   */
extern INT gnOverHang;          /* Maximum overhang during the drag.    */
extern PRES gpResCopy;          /* Copy of dialog/controls.             */

/*-- CTYPE linked lists.------------------------------------------------*/
extern NPCTYPE npcHead;         /* Linked List of controls.             */
extern INT cWindows;            /* Number of Controls in pctype list.   */
extern NPCTYPE gnpcSel;         /* The currently selected control.      */

/*-- Cursors used by editor.--------------------------------------------*/
extern HCURSOR hcurArrow;       /* Normal arrow cursor.                 */
extern HCURSOR hcurWait;        /* User Wait cursor, Hourglass.         */
extern HCURSOR hcurOutSel;      /* Outline selection cursor.            */
extern HCURSOR hcurMove;        /* System "Move" cursor.                */
extern HCURSOR hcurInsert;      /* Insert cursor for Order/Group dialog.*/
extern HCURSOR hcurDropTool;    /* Cursor for when dropping new ctrls.  */
extern HCURSOR hcurSizeNESW;    /* System sizing "NESW" cursor.         */
extern HCURSOR hcurSizeNS;      /* System sizing "NS" cursor.           */
extern HCURSOR hcurSizeNWSE;    /* System sizing "NWSE" cursor.         */
extern HCURSOR hcurSizeWE;      /* System sizing "WE" cursor.           */
extern HBITMAP hbmTabStop;      /* Bitmap for showing WS_TABSTOP style. */
extern HBITMAP hbmTabStopSel;   /* Selected version of the above.       */

/*-- Window Class Strings.----------------------------------------------*/
extern TCHAR szMainClass[];     /* Application window class.            */
extern TCHAR szDragClass[];     /* Class for drag handle windows.       */
extern TCHAR szSubClientClass[];/* Short client area window class.      */
extern TCHAR szToolboxClass[];  /* Toolbox window class.                */
extern TCHAR szToolBtnClass[];  /* Toolbox button window class.         */
extern TCHAR szCustomClass[];   /* Our custom emulator class.           */

/*-- Miscellaneous variables.-------------------------------------------*/
extern UINT fmtDlg;             /* The Dialog Clipboard format          */
extern TCHAR szEmpty[];         /* An empty string.                     */
extern HHOOK ghhkMsgFilter;     /* Hook for message filter func.        */

/*-- Buffers.-----------------------------------------------------------*/
extern TCHAR szFullResFile[];   /* Full resource file name              */
extern LPTSTR pszResFile;       /* Points to resource file name         */
extern TCHAR szFullIncludeFile[];/* Full include file name              */
extern LPTSTR pszIncludeFile;   /* Points to include file name          */
extern TCHAR gszHelpFile[];     /* Path to the help file.               */

/*
 * Write buffer and index into it.  This buffer is used by several
 * sections to write out the different files.  Note that only one
 * file can be written out at a time using these globals.
 */
extern TCHAR gachWriteBuffer[]; /* Buffer for written file data.        */
extern INT cbWritePos;          /* Pointer into gachWriteBuffer.        */

/*-- Include Data.------------------------------------------------------*/
extern NPLABEL plInclude;       /* Pointer to Include data              */
extern NPLABEL plDelInclude;    /* Pointer to deleted includes          */

/*
 * Describes each window class.  Indexed by the W_ defined constants.
 */
extern WINDOWCLASSDESC awcd[];

/* Map low word of button control style to button type (W_). */
extern INT rgmpiClsBtnType[];

/* Map low word of Static Control Style to static type. */
extern INT rgmpiClsStcType[];

extern CLASSSTYLEDESC acsd[];

/*
 * Message box messages, for the Message() function.
 */
extern MESSAGEDATA gamdMessages[];

extern HELPMAP gahmapMenu[];    /* Maps menu ids to their help context. */
extern HELPMAP gahmapDialog[];  /* Maps dlg ids to their help context.  */

extern LANGTABLE gaLangTable[]; /* Table of languages.                  */
extern INT gcLanguages;         /* Count of languages in the table.     */


DLGFUNCS.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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: dlgfuncs.h
*
* Contains prototypes for the functions used in the dialog box editor.
*
****************************************************************************/

/*
 * addctrl.c
 */

VOID AddNewDialog(VOID);
VOID DropControl(PWINDOWCLASSDESC pwcd, PRECT prc);
NPCTYPE AddControl(PWINDOWCLASSDESC pwcd, LPTSTR pszText, DWORD style,
    DWORD flExtStyle, INT id, INT x, INT y, INT cx, INT cy, LPTSTR pszDlgName,
    PDIALOGINFO pdi);
HWND CreateControl(NPCTYPE npc, LPTSTR pszText, DWORD flStyle, DWORD flExtStyle,
    INT id, PRECT prc, HWND hwndBehind, PDIALOGINFO pdi);
VOID DeleteControl(VOID);
VOID DeleteDialog(BOOL fResAlso);
#ifdef JAPAN
VOID KDExpandCopy(LPTSTR, LPTSTR, WORD);
#endif

/*
 * ctrlproc.c
 */

WINDOWPROC DialogCtrlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
WINDOWPROC CtrlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
WINDOWPROC ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL DrawOwnerDrawButton(LPDRAWITEMSTRUCT lpdis);

/*
 * custcntl.c
 */

DIALOGPROC NewCustDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
VOID OpenCustomDialog(VOID);
PWINDOWCLASSDESC SelCustDialog(VOID);
DIALOGPROC SelCustDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
DIALOGPROC RemCustDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
WINDOWPROC CustomWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
PCUSTLINK AddCustomLink(LPCCINFO pcci, BOOL fEmulated, BOOL fUnicodeDLL,
    LPTSTR pszFileName, HANDLE hmod);
VOID RemoveCustomLink(PCUSTLINK pcl);
BOOL CallCustomStyle(NPCTYPE npc, PDWORD pflStyleNew, PDWORD pflExtStyleNew,
    LPTSTR pszTextNew);
INT CallCustomSizeToText(NPCTYPE npc);
VOID ReadCustomProfile(VOID);
VOID WriteCustomProfile(VOID);

/*
 * dialogs.c
 */

INT DlgBox(INT idDlg, WNDPROC lpfnDlg);
VOID EnteringDialog(INT idDlg, PINT pidPrevDlg, BOOL fEntering);
VOID CreateTestDialog(VOID);
VOID DestroyTestDialog(VOID);
VOID SelectDialogDialog(VOID);
VOID ArrangeSettingsDialog(VOID);
DIALOGPROC AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

/*
 * dlgedit.c
 */

WINDOWPROC MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL ReadWindowPos(LPTSTR pszKeyName, PINT px, PINT py, PINT pcx, PINT pcy,
    BOOL *pfMaximized);
VOID WriteWindowPos(PRECT prc, BOOL fMaximized, LPTSTR pszKeyName);

/*
 * drag.c
 */

VOID ShowTrackRect(VOID);
VOID HideTrackRect(VOID);
VOID FitRectToBounds(PRECT prc, INT nOverHang, INT HandleHit, BOOL fDialog);
INT GetOverHang(INT iType, INT cy);
VOID GridizeRect(PRECT prc, INT fGridFlags);
VOID SizeDragToControl(NPCTYPE npc);
WINDOWPROC DragWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
VOID DrawHandles(HWND hwnd, HDC hDC, BOOL fCurrentSelection);
INT HandleHitTest(HWND hwnd, INT x, INT y);
VOID CtrlButtonDown(HWND hwnd, INT x, INT y, BOOL fHandleWindow);
VOID DragNewBegin(INT cx, INT cy, INT nOverHang);
VOID CtrlMouseMove(HWND hwnd, BOOL fDragWindow, INT x, INT y);
VOID PreDragTimeout(HWND hwnd, BOOL fTimedOut);
VOID DragCancel(VOID);
VOID CtrlButtonUp(INT x, INT y);
VOID DragEnd(INT x, INT y);

/*
 * file.c
 */

BOOL Open(INT FileType);
VOID BuildFilterString(INT FileType, LPTSTR pszFilter);
INT DoWeSave(INT rgbFlags);
BOOL Save(INT rgbFlags);
VOID OpenCmdLineFile(LPTSTR pszFileName);
LPTSTR FileInPath(LPTSTR pszPath);
VOID ShowFileStatus(BOOL fForce);
BOOL DifferentDirs(LPTSTR pszPath1, LPTSTR pszPath2);
BOOL HasPath(LPTSTR pszFileSpec);
BOOL WriteDWordPad(HANDLE hf, DWORD cbFile);

/*
 * groupdlg.c
 */

VOID OrderGroupDialog(VOID);
DIALOGPROC OrderDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

/*
 * include.c
 */

NPLABEL AddLabel(LPTSTR pszLabel, INT id, DWORD fpos,
    INT nValueOffset, NPLABEL *pplHead, NPLABEL *pplDelHead,
    NPLABEL npLabelSkip, BOOL *pfDups);
NPLABEL FindLabel(LPTSTR pszLabel, NPLABEL plHead);
NPLABEL FindID(INT id,  NPLABEL plHead);
BOOL FindIDInRes(INT id);
VOID DeleteLabel(LPTSTR pszLabel, NPLABEL *pplHead, NPLABEL *pplDelHead);
BOOL IsSymbol(LPTSTR pszSym);
VOID IDToLabel(LPTSTR pchLabel, INT id, BOOL fHexOK);
BOOL LabelToID(LPTSTR pszLabel, PINT pID);
VOID FreeLabels(NPLABEL *nppLabels);

/*
 * menu.c
 */

VOID DialogMenu(INT cmd);
VOID LoadMenuBitmaps(HMENU hMenu);
VOID FreeMenuBitmaps(VOID);
VOID InitMenu(HMENU hMenu);
BOOL APIENTRY MsgFilterHookFunc(INT nCode, WPARAM wParam, LPMSG lpMsg);
VOID ShowHelp(BOOL fMenuHelp);

/*
 * reslist.c
 */

BOOL FindDialog(LPTSTR pszDlgName);
PRESLINK AllocResLink(PRES pRes);
VOID RestoreDialog(VOID);
VOID FreeRes(VOID);
VOID FreeResList(VOID);
VOID FreeResLink(PRESLINK prl);
VOID DeleteDialogResource(VOID);

/*
 * restodlg.c
 */

BOOL SynchDialogResource(VOID);
PRES AllocDialogResource(BOOL fTestMode, BOOL fClipboard);
VOID ResLinkToDialog(PRESLINK prl);
BOOL ResToDialog(PRES pRes, BOOL fDoDialog);
INT GetiClass(LPTSTR pszClass);
VOID Duplicate(VOID);
VOID MakeCopyFromRes(PRES pRes);

/*
 * resutil.c
 */

PCONTROLDATA ParseDialogBoxHeader(PDIALOGBOXHEADER pdbh, PDWORD pflStyle,
    PDWORD pflExtStyle, PINT pcdit, PINT px, PINT py, PINT pcx, PINT pcy,
    LPTSTR *ppszMenuName, LPTSTR *ppszClass, LPTSTR *ppszCaption,
    PINT pPointSize, LPTSTR *ppszFontName);
PCONTROLDATA ParseControlData(PCONTROLDATA pcd, PDWORD pflStyle,
    PDWORD pflExtStyle, PINT px, PINT py, PINT pcx, PINT pcy, PINT pid,
    LPTSTR *ppszClass, LPTSTR *ppszText);
VOID DWordAlign(PBYTE *ppb);
VOID DWordPad(PBYTE *ppb);
LPTSTR ResourceType(PRES pRes);
LPTSTR ResourceName(PRES pRes);
PRES2 ResourcePart2(PRES pRes);
DWORD ResourceSize(PRES pRes);
PBYTE SkipResHeader(PRES pRes);
PBYTE SkipSz(LPTSTR pNameOrd);
PCONTROLDATA SkipDialogBoxHeader(PDIALOGBOXHEADER pdbh);
PCONTROLDATA SkipControlData(PCONTROLDATA pcd);
PBYTE NameOrdCpy(LPTSTR pNameOrdDest, LPTSTR pNameOrdSrc);
INT NameOrdCmp(LPTSTR pNameOrd1, LPTSTR pNameOrd2);
INT NameOrdLen(LPTSTR pNameOrd);
LPTSTR NameOrdDup(LPTSTR pNameOrd);
VOID StrToNameOrd(LPTSTR pszNameOrd, BOOL fDecOnly);
PBYTE WriteOrd(PORDINAL pOrd, INT nOrdinalID);
PBYTE WriteResHeader(PRES pRes, DWORD DataSize, INT iResType, LPTSTR pszResName,
    WORD fResFlags, WORD LanguageId, DWORD DataVersion, DWORD Version,
    DWORD Characteristics);
PBYTE WriteSz(LPTSTR pszDest, LPTSTR pszSrc);

/*
 * rwdlg.c
 */

BOOL WriteDlg(HANDLE hfWrite, LPTSTR pszFullDlgFile);

/*
 * rwinc.c
 */

BOOL OpenIncludeFile(LPTSTR pszOpenInclude);
VOID FreeInclude(VOID);
BOOL WriteInc(HANDLE hfWrite);

/*
 * rwres.c
 */

BOOL OpenResFile(LPTSTR pszFullPath);
BOOL WriteRes(HANDLE hfWrite, LPTSTR pszFullResFile);

/*
 * select.c
 */

BOOL SelectControl(NPCTYPE npc, BOOL fCheckShift);
VOID SelectControl2(NPCTYPE npc, BOOL fDontUpdate);
VOID RedrawSelection(VOID);
VOID SetAnchorToFirstSel(BOOL fDontUpdate);
VOID SelectNext(VOID);
VOID SelectPrevious(VOID);
VOID UnSelectControl(NPCTYPE npc);
VOID CalcSelectedRect(VOID);
VOID CancelSelection(BOOL fUpdate);
VOID OutlineSelectBegin(INT x, INT y);
VOID OutlineSelectDraw(INT x, INT y);
VOID OutlineSelectCancel(VOID);
VOID OutlineSelectEnd(INT x, INT y);
VOID MyFrameRect(HDC hDC, PRECT prc, DWORD dwRop);
VOID MoveControl(WPARAM vKey);
VOID PositionControl(PRECT prc);
VOID RepositionDialog(VOID);
VOID SaveDlgClientRect(HWND hwndDlg);
VOID SizeToText(VOID);
VOID AlignControls(INT cmd);
VOID ArrangeSpacing(INT cmd);
VOID ArrangeSize(INT cmd);
VOID ArrangePushButtons(INT cmd);

/*
 * status.c
 */

DIALOGPROC StatusDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL StatusApplyChanges(VOID);
VOID StatusFillSymbolList(NPLABEL plHead);
VOID StatusSetCoords(PRECT prc);
VOID StatusUpdate(VOID);
VOID StatusSetEnable(VOID);
#ifdef JAPAN
VOID KKExpandCopy(LPTSTR, LPTSTR, WORD);
#endif

/*
 * styles.c
 */

VOID StylesDialog(VOID);
DIALOGPROC GenericStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
DIALOGPROC CheckBoxStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
DIALOGPROC RadioButtonStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
DIALOGPROC PushButtonStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
DIALOGPROC ComboBoxStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
DIALOGPROC EditStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
DIALOGPROC ListBoxStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
DIALOGPROC CustomStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
DIALOGPROC DialogStylesDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);
BOOL APIENTRY FontNameEnumFunc(LPLOGFONT lpLogFont,
    LPTEXTMETRIC lpTextMetric, INT nFontType, LPVOID lpData);
BOOL APIENTRY PointSizeEnumFunc(LPLOGFONT lpLogFont,
    LPTEXTMETRIC lpTextMetric, INT nFontType, LPVOID lpData);

/*
 * toolbox.c
 */

VOID ToolboxShow(BOOL fShow);
VOID ToolboxOnTop(VOID);
WINDOWPROC ToolboxWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
WINDOWPROC ToolBtnWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
VOID ToolboxSelectTool(INT type, BOOL fLock);

/*
 * util.c
 */

VOID *MyAlloc(INT cbAlloc);
VOID *MyRealloc(VOID *npMem, INT cbNewAlloc);
VOID *MyFree(VOID *npMem);
BOOL IsValue(LPTSTR pszValue);
BOOL HasBlanks(LPTSTR psz);
INT valtoi(LPTSTR pszValue);
INT axtoi(LPTSTR pch);
VOID Myitoa(INT n, LPTSTR psz);
VOID itoax(INT n, LPTSTR pszBuff);
BOOL IsUniqueID(INT id);
INT NextID(INT idType, NPLABEL plHead, INT idExclude);
INT Message(INT idMsg, ...);
VOID ClientToScreenRect(HWND hwnd, PRECT prc);
VOID ScreenToClientRect(HWND hwnd, PRECT prc);
VOID DUToWinPoint(PPOINT ppt);
VOID WinToDUPoint(PPOINT ppt);
VOID DUToWinRect(PRECT prc);
VOID WinToDURect(PRECT prc);
VOID MapDlgClientPoint(PPOINT ppt, BOOL fFromClient);
VOID MapWindowPoint(HWND hwndFrom, HWND hwndTo, PPOINT ppt);
VOID MyMapWindowRect(HWND hwndFrom, HWND hwndTo, PRECT prc);
VOID GetChildRect(HWND hwndChild, PRECT prc);
VOID CenterWindow(HWND hwnd);
VOID FitRectToScreen(PRECT prc);
LPTSTR ids(UINT idString);
INT PixelsToPointSize(INT nPixels);
INT PointSizeToPixels(INT nPointSize);

/*
 * viewinc.c
 */

VOID ViewInclude(VOID);
DIALOGPROC ViewIncludeDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);


DLGHELP.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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: dlghelp.h
*
* Contains help context id values for the dialog editor's help.
*
****************************************************************************/

/*
 * Help for menus. ---------------------------------------------------------
 */

/*
 * File menu.
 */
#define HELPID_FILE_NEWRES              100
#define HELPID_FILE_OPEN                101
#define HELPID_FILE_SAVE                102
#define HELPID_FILE_SAVEAS              103
#define HELPID_FILE_SETINCLUDE          104
#define HELPID_FILE_EXIT                105
#define HELPID_FILE_NEWCUST             106
#define HELPID_FILE_OPENCUST            107
#define HELPID_FILE_REMCUST             108

/*
 * Edit menu.
 */
#define HELPID_EDIT_RESTOREDIALOG       200
#define HELPID_EDIT_CUT                 201
#define HELPID_EDIT_COPY                202
#define HELPID_EDIT_PASTE               203
#define HELPID_EDIT_DELETE              204
#define HELPID_EDIT_DUPLICATE           205
#define HELPID_EDIT_SYMBOLS             206
#define HELPID_EDIT_STYLES              207
#define HELPID_EDIT_SIZETOTEXT          208
#define HELPID_EDIT_NEWDIALOG           209
#define HELPID_EDIT_SELECTDIALOG        210

/*
 * Arrange menu.
 */
#define HELPID_ARRANGE_ALIGN            400
#define HELPID_ARRANGE_ALIGNLEFT        401
#define HELPID_ARRANGE_ALIGNVERT        402
#define HELPID_ARRANGE_ALIGNRIGHT       403
#define HELPID_ARRANGE_ALIGNTOP         404
#define HELPID_ARRANGE_ALIGNHORZ        405
#define HELPID_ARRANGE_ALIGNBOTTOM      406
#define HELPID_ARRANGE_SPACE            407
#define HELPID_ARRANGE_SPACEHORZ        408
#define HELPID_ARRANGE_SPACEVERT        409
#define HELPID_ARRANGE_ARRSIZE          410
#define HELPID_ARRANGE_ARRSIZEWIDTH     411
#define HELPID_ARRANGE_ARRSIZEHEIGHT    412
#define HELPID_ARRANGE_ARRPUSH          413
#define HELPID_ARRANGE_ARRPUSHBOTTOM    414
#define HELPID_ARRANGE_ARRPUSHRIGHT     415
#define HELPID_ARRANGE_ORDERGROUP       416
#define HELPID_ARRANGE_ARRSETTINGS      417

/*
 * Options menu.
 */
#define HELPID_OPTIONS_TESTMODE         500
#define HELPID_OPTIONS_HEXMODE          501
#define HELPID_OPTIONS_TRANSLATE        502
#define HELPID_OPTIONS_USENEWKEYWORDS   503
#define HELPID_OPTIONS_SHOWTOOLBOX      504

/*
 * Help menu.
 */
#define HELPID_HELP_CONTENTS            600
#define HELPID_HELP_SEARCH              601
#define HELPID_HELP_ABOUT               602

/*
 * Help for dialogs. -------------------------------------------------------
 */

#define HELPID_ARRSETTINGS              1000    /* Arrange.Settings cmd.    */
#define HELPID_CHECKBOXSTYLES           1001    /* Edit.Styles cmd.         */
#define HELPID_COMBOBOXSTYLES           1002    /* Edit.Styles cmd.         */
#define HELPID_CUSTOMSTYLES             1003    /* Edit.Styles cmd.         */
#define HELPID_DIALOGSTYLES             1004    /* Edit.Styles cmd.         */
#define HELPID_EDITSTYLES               1005    /* Edit.Styles cmd.         */
#define HELPID_FRAMESTYLES              1006    /* Edit.Styles cmd.         */
#define HELPID_GROUPBOXSTYLES           1007    /* Edit.Styles cmd.         */
#define HELPID_ORDERGROUP               1008    /* Arrange.Order/Group cmd. */
#define HELPID_HORZSCROLLSTYLES         1009    /* Edit.Styles cmd.         */
#define HELPID_ICONSTYLES               1010    /* Edit.Styles cmd.         */
#define HELPID_LISTBOXSTYLES            1011    /* Edit.Styles cmd.         */
#define HELPID_PUSHBUTTONSTYLES         1012    /* Edit.Styles cmd.         */
#define HELPID_RADIOBUTTONSTYLES        1013    /* Edit.Styles cmd.         */
#define HELPID_RECTSTYLES               1014    /* Edit.Styles cmd.         */
#define HELPID_SELECTDIALOG             1015    /* Edit.Select Dialog cmd.  */
#define HELPID_SYMBOLS                  1016    /* Edit.Symbols cmd.        */
#define HELPID_TEXTSTYLES               1017    /* Edit.Styles cmd.         */
#define HELPID_VERTSCROLLSTYLES         1018    /* Edit.Styles cmd.         */

#define HELPID_NEWCUST                  1019    /* File.New Custom cmd.     */
#define HELPID_REMCUST                  1020    /* File.Remove Custom cmd.  */
#define HELPID_SELCUST                  1021    /* Select Custom in toolbox.*/

/*
 * Common file open/save dialog help id's.
 */
#define HELPID_COMMONFILEOPENINCLUDE    1100    /* File.Set Include cmd.    */
#define HELPID_COMMONFILEOPENRES        1101    /* File.Open cmd.           */
#define HELPID_COMMONFILESAVEINCLUDE    1102    /* File.Save (Save As) cmd. */
#define HELPID_COMMONFILESAVERES        1103    /* File.Save (Save As) cmd. */
#define HELPID_COMMONFILEOPENDLL        1104    /* File.Open Custom cmd.    */

/*
 * Miscellaneous subjects. -------------------------------------------------
 */

#define HELPID_TOOLBOX                  2000    /* Toolbox has the focus.   */
#define HELPID_PROPERTIESBAR            2001    /* Properties Bar has focus.*/


DRAG.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 Header *******************************
* Module Name: drag.c
*
* Contains routines for dragging and sizing controls.
*
* Functions:
*    ShowTrackRect()
*    HideTrackRect()
*    FitRectToBounds()
*    GetOverHang()
*    GridizeRect()
*    SizeDragToControl()
*    DragWndProc()
*    DrawHandles()
*    HandleHitTest()
*    CtrlButtonDown()
*    DragNewBegin()
*    CtrlMouseMove()
*    PreDragTimeout()
*    DragCancel()
*    CtrlButtonUp()
*    DragEnd()
*    CalcCursorOffset()
*    InitTracking()
*    DrawTrackRect()
*    CancelTracking()
*    AtOrAbove()
*    AtOrBelow()
*    PaintUnderDrag()
*    MouseToDragRect()
*    MouseToDU()
*    DragBegin()
*    CtrlHitTest()
*    DragBegin2()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

#include <stdlib.h>

STATICFN VOID CalcCursorOffset(POINT *ppt);
STATICFN VOID InitTracking(VOID);
STATICFN VOID DrawTrackRect(PRECT prc, BOOL fDialog, BOOL fDraw);
STATICFN VOID CancelTracking(VOID);
STATICFN INT AtOrAbove(INT nStart, INT nGrid);
STATICFN INT AtOrBelow(INT nStart, INT nGrid);
STATICFN VOID PaintUnderDrag(HWND hwndDrag);
STATICFN VOID MouseToDragRect(INT x, INT y, PRECT prc);
STATICFN VOID MouseToDU(PPOINT ppt);
STATICFN VOID DragBegin(HWND hwnd, INT x, INT y, BOOL fHandleWindow);
STATICFN HWND CtrlHitTest(HWND hwnd, PPOINT ppt);
STATICFN VOID DragBegin2(PPOINT ppt);

/*
 * This contains the initial location of the mouse when going into
 * pre-drag mode.  If the mouse pointer is moved too far away from
 * this point, we will start the drag operation, even if the pre-drag
 * timer has not elapsed yet.
 */
static POINT gptPreDragStart;

/************************************************************************
* CalcCursorOffset
*
* This routine updates the gptCursorOffset point.  This is used during
* dragging operations.  It contains the offset from the mouse pointer
* at the time a dragging operation is begun and the upper left corner
* of the dragging rectangle.  This value is needed for determining
* where mouse events are occuring in relation to where the drag was
* initially begun from.
*
* Arguments:
*    POINT - offset where the mouse pointer began drag.
*
************************************************************************/

STATICFN VOID CalcCursorOffset(
    POINT *ppt)
{
    RECT rc;
    POINT pt;

    if (gfDlgSelected) {
        gptCursorOffset = *ppt;
    }
    else {
        rc = grcSelected;
        pt = *ppt;
        DUToWinRect(&rc);

        ClientToScreen(gnpcSel->hwnd, &pt);
        ScreenToClient(gcd.npc->hwnd, &pt);

        gptCursorOffset.x = pt.x - rc.left;
        gptCursorOffset.y = pt.y - rc.top;
    }
}

/************************************************************************
* InitTracking
*
* This function initializes a tracking operation.  The pointer is
* changed to be the system "move" pointer if we are moving the
* control (not sizing it).
*
************************************************************************/

STATICFN VOID InitTracking(VOID)
{
    if (gfDlgSelected)
        ghDCTrack = CreateDC(L"DISPLAY", NULL, NULL, NULL);
    else
        ghDCTrack = GetDC(gcd.npc->hwnd);

    SetROP2(ghDCTrack, R2_NOT);
}

/************************************************************************
* DrawTrackRect
*
* This routine draws the drag rectangle.  It is assumed that the window
* has been locked for update appropriately or this could leave garbage
* around.  The rectangle given is in dialog units, and is converted
* to window coordinates using different rules based on the value of
* fDialog.  After this routine has been called to set the rectangle,
* the HideTrackRect and ShowTrackRect functions can be called to
* temporarily hide the track rectangle, but this routine must be called
* again every time that the tracking rectangle is to be changed.
*
* Arguments:
*   PRECT prc     - Drag rectangle to draw (in dialog units).
*   BOOL fDialog  - TRUE if the control being dragged is the dialog.
*   BOOL fDraw    - If TRUE, the rectangle will be drawn.  Having this
*                   FALSE is useful to just initialize the state globals,
*                   but defer the drawing of the rectangle until the mouse
*                   is moved from its starting point.
*
************************************************************************/

STATICFN VOID DrawTrackRect(
    PRECT prc,
    BOOL fDialog,
    BOOL fDraw)
{
    HideTrackRect();

    grcTrackWin = grcTrackDU = *prc;
    DUToWinRect(&grcTrackWin);

    if (fDialog) {
        AdjustWindowRectEx(&grcTrackWin, gcd.npc->flStyle, FALSE,
                (gcd.npc->flStyle & DS_MODALFRAME) ?
                gcd.npc->flExtStyle | WS_EX_DLGMODALFRAME :
                gcd.npc->flExtStyle);
        ClientToScreenRect(ghwndSubClient, &grcTrackWin);
    }

    if (fDraw)
        ShowTrackRect();
}

/************************************************************************
* ShowTrackRect
*
* This routine shows the current tracking rectangle.
*
************************************************************************/

VOID ShowTrackRect(VOID)
{
    if (!gfTrackRectShown) {
        MyFrameRect(ghDCTrack, &grcTrackWin, DSTINVERT);
        gfTrackRectShown = TRUE;
    }
}

/************************************************************************
* HideTrackRect
*
* This routine hides the current tracking rectangle.
*
************************************************************************/

VOID HideTrackRect(VOID)
{
    if (gfTrackRectShown) {
        MyFrameRect(ghDCTrack, &grcTrackWin, DSTINVERT);
        gfTrackRectShown = FALSE;
    }
}

/************************************************************************
* CancelTracking
*
* This routine is used to cancel the display of the tracking rectangle.
* It is basically the opposite of InitTracking.
*
************************************************************************/

STATICFN VOID CancelTracking(VOID)
{
    if (gfTrackRectShown) {
        HideTrackRect();

        if (gfDlgSelected)
            DeleteDC(ghDCTrack);
        else
            ReleaseDC(gcd.npc->hwnd, ghDCTrack);
    }
}

/************************************************************************
* FitRectToBounds
*
* This routine fits the given rectangle to the appropriate boundary.
* If fDialog is FALSE, the rectangle is a control and it must fall
* entirely within the area of the current dialog being edited.  If the
* rectangle is adjusted to fit, the moved edge(s) will be aligned on
* a grid boundary.  The wHandleHit parameter is used to tell this routine
* what edges are allowed to move, in other words, what edges are
* "anchored" down and what edges are being tracked.
*
* Arguments:
*   PRECT prc     - Rectangle to be adjusted to the allowed size.
*   INT nOverHang - How much the control can hang below the dialog.
*                   This is primarily for the combobox listboxes.
*   INT HandleHit - One of the DRAG_* constants.
*   BOOL fDialog  - TRUE if the rectangle is for a dialog.
*
************************************************************************/

VOID FitRectToBounds(
    PRECT prc,
    INT nOverHang,
    INT HandleHit,
    BOOL fDialog)
{
    INT cxDlg;
    INT cyDlg;
    INT dx;
    INT dy;

    /*
     * Are we just moving the control (not sizing)?
     */
    if (HandleHit == DRAG_CENTER) {
        /*
         * We only do range checking if it is a control (not on the dialog).
         */
        if (!fDialog) {
            dx = prc->right - prc->left;
            dy = prc->bottom - prc->top;
            cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
            cyDlg = gcd.npc->rc.bottom - gcd.npc->rc.top + nOverHang;

            if (prc->right > cxDlg) {
                prc->left = AtOrBelow(cxDlg - dx, gcxGrid);
                prc->right = prc->left + dx;
            }

            if (prc->left < 0) {
                prc->left = 0;
                prc->right = prc->left + dx;
            }

            if (prc->bottom > cyDlg) {
                prc->top = AtOrBelow(cyDlg - dy, gcyGrid);
                prc->bottom = prc->top + dy;
            }

            if (prc->top < 0) {
                prc->top = 0;
                prc->bottom = prc->top + dy;
            }
        }

        return;
    }

    if (fDialog) {
        /*
         * When dealing with the dialog, we want to take into account
         * the controls so that the dialog is never sized to hide a
         * control.  This routine assumes that grcMinDialog has already
         * been set to enclose the controls.  If the dialog has no
         * controls, this rectangle is not used, but the dialog's size
         * is still limited so that it never goes negative.
         */
        /*
         * First deal with the x coordinates.
         */
        switch (HandleHit) {
            case DRAG_LEFTBOTTOM:
            case DRAG_LEFT:
            case DRAG_LEFTTOP:
                if (npcHead) {
                    if (prc->left > grcMinDialog.left)
                        prc->left = AtOrBelow(grcMinDialog.left, gcxGrid);
                }
                else {
                    if (prc->left > prc->right)
                        prc->left = AtOrBelow(prc->right, gcxGrid);
                }

                break;

            case DRAG_RIGHTBOTTOM:
            case DRAG_RIGHT:
            case DRAG_RIGHTTOP:
                if (npcHead) {
                    if (prc->right < grcMinDialog.right)
                        prc->right = AtOrAbove(grcMinDialog.right, gcxGrid);
                }
                else {
                    if (prc->right < prc->left)
                        prc->right = AtOrAbove(prc->left, gcxGrid);
                }

                break;
        }

        /*
         * Now deal with the y coordinates.
         */
        switch (HandleHit) {
            case DRAG_LEFTBOTTOM:
            case DRAG_BOTTOM:
            case DRAG_RIGHTBOTTOM:
                if (npcHead) {
                    if (prc->bottom < grcMinDialog.bottom)
                        prc->bottom = AtOrAbove(grcMinDialog.bottom, gcyGrid);
                }
                else {
                    if (prc->bottom < prc->top)
                        prc->bottom = AtOrAbove(prc->top, gcyGrid);
                }

                break;

            case DRAG_LEFTTOP:
            case DRAG_TOP:
            case DRAG_RIGHTTOP:
                if (npcHead) {
                    if (prc->top > grcMinDialog.top)
                        prc->top = AtOrBelow(grcMinDialog.top, gcyGrid);
                }
                else {
                    if (prc->top > prc->bottom)
                        prc->top = AtOrBelow(prc->bottom, gcyGrid);
                }

                break;
        }
    }
    else {
        /*
         * First deal with the x coordinates.
         */
        switch (HandleHit) {
            case DRAG_LEFTBOTTOM:
            case DRAG_LEFT:
            case DRAG_LEFTTOP:
                if (prc->left > prc->right)
                    prc->left = AtOrBelow(prc->right, gcxGrid);

                if (prc->left == prc->right)
                    prc->left -= gcxGrid;

                if (prc->left < 0)
                    prc->left = 0;

                break;

            case DRAG_RIGHTBOTTOM:
            case DRAG_RIGHT:
            case DRAG_RIGHTTOP:
                cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
                if (prc->right > cxDlg)
                    prc->right = AtOrBelow(cxDlg, gcxGrid);

                if (prc->right < prc->left)
                    prc->right = AtOrAbove(prc->left, gcxGrid);

                if (prc->right == prc->left)
                    prc->right += gcxGrid;

                break;
        }

        /*
         * Now deal with the y coordinates.
         */
        switch (HandleHit) {
            case DRAG_LEFTTOP:
            case DRAG_TOP:
            case DRAG_RIGHTTOP:
                if (prc->top > prc->bottom)
                    prc->top = AtOrBelow(prc->bottom, gcyGrid);

                if (prc->top == prc->bottom)
                    prc->top -= gcyGrid;

                if (prc->top < 0)
                    prc->top = 0;

                break;

            case DRAG_LEFTBOTTOM:
            case DRAG_BOTTOM:
            case DRAG_RIGHTBOTTOM:
                cyDlg = gcd.npc->rc.bottom - gcd.npc->rc.top;

                /*
                 * Note that if there is an overhang allowed, then
 * we do not limit how far down the bottom of the
                 * control can be.
                 */
                if (prc->bottom > cyDlg && !nOverHang)
                    prc->bottom = AtOrBelow(cyDlg, gcyGrid);

                if (prc->bottom < prc->top)
                    prc->bottom = AtOrAbove(prc->top, gcyGrid);

                if (prc->bottom == prc->top)
                    prc->bottom += gcyGrid;

                break;
        }
    }
}

/************************************************************************
* AtOrAbove
*
* This routine takes a number, and returns the closest number that
* is equal to or above that number and is an integral of the given
* grid value.
*
* Arguments:
*   INT nStart - Starting number (can be negative).
*   INT nGrid  - Grid value.
*
************************************************************************/

STATICFN INT AtOrAbove(
    INT nStart,
    INT nGrid)
{
    register INT nAbove;

    nAbove = (nStart / nGrid) * nGrid;

    if (nStart > 0 && nStart != nAbove)
        nAbove += nGrid;

    return nAbove;
}

/************************************************************************
* AtOrBelow
*
* This routine takes a number, and returns the closest number that
* is equal to or below that number and is an integral of the given
* grid value.
*
* Arguments:
*   INT nStart - Starting number (can be negative).
*   INT nGrid  - Grid value.
*
************************************************************************/

STATICFN INT AtOrBelow(
    INT nStart,
    INT nGrid)
{
    register INT nBelow;

    nBelow = (nStart / nGrid) * nGrid;

    if (nStart < 0 && nStart != nBelow)
        nBelow -= nGrid;

    return nBelow;
}

/************************************************************************
* GetOverHang
*
* This function returns the height that the control can overhang the
* bottom of the dialog.  This is currently only meaningful for comboboxes.
* If the control is not a combobox, zero is returned.
*
* Arguments:
*   INT iType - Type of control (W_* constant).
*   INT cy    - Height of the control (in DU's).
*
************************************************************************/

INT GetOverHang(
    INT iType,
    INT cy)
{
    if (iType != W_COMBOBOX)
        return 0;

    return max(cy - COMBOEDITHEIGHT, 0);
}

/************************************************************************
* GridizeRect
*
* This function "gridizes" coordinates in a rectangle.  The current
* grid values are used.  The fsGrid flag can contain OR'd together
* GRIDIZE_* values that specify which points to apply the gridding to.
* Upon return, all coordinates specified will have been rounded to the
* nearest grid boundary.
*
* If GRIDIZE_SAMESIZE is specified, the size of the control will be
* kept the same.  This overrides the GRIDIZE_RIGHT and GRIDIZE_BOTTOM
* flags.  In other words, any delta applied to left or top will
* be added to right and bottom to retain the original size of the
* rectangle.
*
* Arguments:
*   PRECT prc       - Rectangle to adjust to the current grid.
*   INT fGridFlags  - GRIDIZE_* flags.  Specifies which points to gridize.
*
************************************************************************/

VOID GridizeRect(
    PRECT prc,
    INT fGridFlags)
{
    register INT nTemp;
    INT leftOld = prc->left;
    INT topOld = prc->top;

    if (fGridFlags & GRIDIZE_LEFT) {
        nTemp = AtOrBelow(prc->left, gcxGrid);

        if (prc->left - nTemp > gcxGrid / 2)
            nTemp += gcxGrid;

        prc->left = nTemp;
    }

    if (fGridFlags & GRIDIZE_TOP) {
        nTemp = AtOrBelow(prc->top, gcyGrid);

        if (prc->top - nTemp > gcyGrid / 2)
            nTemp += gcyGrid;

        prc->top = nTemp;
    }

    /*
     * Do they want to retain the same size of the rectangle?
     */
    if (fGridFlags & GRIDIZE_SAMESIZE) {
        /*
         * Shift the right coordinate over by the delta that
         * was applied to the left.
         */
        prc->right += prc->left - leftOld;
        prc->bottom += prc->top - topOld;
    }
    else {
        if (fGridFlags & GRIDIZE_RIGHT) {
            nTemp = AtOrBelow(prc->right, gcxGrid);

            if (prc->right - nTemp > gcxGrid / 2)
                nTemp += gcxGrid;

            prc->right = nTemp;
        }

        if (fGridFlags & GRIDIZE_BOTTOM) {
            nTemp = AtOrBelow(prc->bottom, gcyGrid);

            if (prc->bottom - nTemp > gcyGrid / 2)
                nTemp += gcyGrid;

            prc->bottom = nTemp;
        }
    }
}

/************************************************************************
* SizeDragToControl
*
* This routine sizes and positions the drag window associated with a
* control, based on the current size and position of the control.
*
* It takes into account the different origin that controls and dialogs
* have, and sizes the drag window to fit around the control properly.
* The Z order of the drag window is NOT changed.
*
* This routine should only be called for controls, not the dialog.
*
* Arguments:
*   NPCTYPE npc - Control whose drag window needs to be sized.
*
************************************************************************/

VOID SizeDragToControl(
    NPCTYPE npc)
{
    RECT rc;

    rc = npc->rc;
    DUToWinRect(&rc);

    InflateRect(&rc, CHANDLESIZE / 2, CHANDLESIZE / 2);

    SetWindowPos(npc->hwndDrag, NULL, rc.left, rc.top,
            rc.right - rc.left, rc.bottom - rc.top,
            SWP_NOACTIVATE | SWP_NOZORDER);
}

/************************************************************************
* DragWndProc
*
* This is the window procedure for the "drag" class.  This window
* is placed behind a control and is what the user grabs to size a
* window with the mouse.
*
************************************************************************/

WINDOWPROC DragWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    POINT pt;

    switch (msg) {
        case WM_PAINT:
            {
                PAINTSTRUCT ps;
                HDC hDC;

                PaintUnderDrag(hwnd);

                hDC = BeginPaint(hwnd, &ps);
                DrawHandles(hwnd, hDC,
                        (gnpcSel && hwnd == gnpcSel->hwndDrag) ? TRUE : FALSE);
                EndPaint(hwnd, &ps);
            }

            break;

        case WM_NCHITTEST:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
            ScreenToClient(hwnd, &pt);

            if (HandleHitTest(hwnd, pt.x, pt.y) == DRAG_CENTER)
                return HTTRANSPARENT;
            else
                return HTCLIENT;

        case WM_SETCURSOR:
            /*
             * Defeat the system changing cursors on us.  We do it based
             * on our own hit testing.
             */
            break;

        case WM_LBUTTONDOWN:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
            CtrlButtonDown(hwnd, pt.x, pt.y, TRUE);
            break;

        case WM_MOUSEMOVE:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
            CtrlMouseMove(hwnd, TRUE, pt.x, pt.y);
            break;

        case WM_LBUTTONUP:
            ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
            CtrlButtonUp(pt.x, pt.y);
            break;

        case WM_RBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_LBUTTONDBLCLK:
        case WM_RBUTTONDBLCLK:
        case WM_MBUTTONDBLCLK:
            /*
             * Prevents calling SetFocus when the middle or right
             * mouse buttons are pressed (or doubleclicked).
             */
            break;

        case WM_NCCALCSIZE:
            /*
             * The client area is the entire control.
             */
            break;

        case WM_DESTROY:
            /*
             * When destroying the drag window, we must be sure and
             * remove the properties associated with it.
             */
            UNSETPCINTOHWND(hwnd);
            break;

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

    return 0L;
}

/************************************************************************
* DrawHandles
*
* This routine draws the drag handles for a drag window.  The handles
* will be solid (filled) if fCurrentSelection is TRUE, or hollow if it
* is FALSE.
*
* Arguments:
*   HWND hwnd              - Drag window handle.
*   HDC hDC                - DC to use to draw in this window.
*   BOOL fCurrentSelection - TRUE if this control is the "current"
*                            selection.
*
************************************************************************/

VOID DrawHandles(
    HWND hwnd,
    HDC hDC,
    BOOL fCurrentSelection)
{
    RECT rc;
    INT xMid;
    INT yMid;
    INT x2;
    INT y2;
    HBITMAP hbmOld;

    GetWindowRect(hwnd, &rc);
    OffsetRect(&rc, -rc.left, -rc.top);

    /*
     * Precalculate some points.
     */
    xMid = ((rc.right + 1) / 2) - (CHANDLESIZE / 2);
    yMid = ((rc.bottom + 1) / 2) - (CHANDLESIZE / 2);
    x2 = rc.right - CHANDLESIZE;
    y2 = rc.bottom - CHANDLESIZE;

    /*
     * Draw a solid box if this is the currently selected
     * control, otherwise draw a hollow box.
     */
    if (fCurrentSelection)
        hbmOld = SelectObject(ghDCMem, ghbmDragHandle);
    else
        hbmOld = SelectObject(ghDCMem, ghbmDragHandle2);

    BitBlt(hDC, 0, 0, CHANDLESIZE, CHANDLESIZE,
            ghDCMem, 0, 0, SRCCOPY);
    BitBlt(hDC, xMid, 0, CHANDLESIZE, CHANDLESIZE,
            ghDCMem, 0, 0, SRCCOPY);
    BitBlt(hDC, x2, 0, CHANDLESIZE, CHANDLESIZE,
            ghDCMem, 0, 0, SRCCOPY);
    BitBlt(hDC, x2, yMid, CHANDLESIZE, CHANDLESIZE,
            ghDCMem, 0, 0, SRCCOPY);
    BitBlt(hDC, x2, y2, CHANDLESIZE, CHANDLESIZE,
            ghDCMem, 0, 0, SRCCOPY);
    BitBlt(hDC, xMid, y2, CHANDLESIZE, CHANDLESIZE,
            ghDCMem, 0, 0, SRCCOPY);
    BitBlt(hDC, 0, y2, CHANDLESIZE, CHANDLESIZE,
            ghDCMem, 0, 0, SRCCOPY);
    BitBlt(hDC, 0, yMid, CHANDLESIZE, CHANDLESIZE,
            ghDCMem, 0, 0, SRCCOPY);

    SelectObject(ghDCMem, hbmOld);
}

/************************************************************************
* PaintUnderDrag
*
* This function is used during a paint operation for a visible drag
* window.  It checks underneath the area to update and forces any
* windows down there to paint themselves, in such an order as to cause
* the last one painted to be the visually top window.  This is necessary
* to implement the drag windows as "transparent" windows.  After this
* routine finishes, the caller can paint the "handles".  The caller must
* call this routine before calling BeginPaint, or the update area
* will be null and nothing will get painted.
*
* It is assumed that the drag windows are for controls, not for the
* dialog.
*
* Arguments:
*   HWND hwndDrag - Drag window to paint under.
*
************************************************************************/

STATICFN VOID PaintUnderDrag(
    HWND hwndDrag)
{
    RECT rc;
    RECT rcInt;
    RECT rcUpdate;
    HWND hwnd;
    HWND hwndControl;
    NPCTYPE npc;

    /*
     * Get our corresponding control window.
     */
    hwndControl = (PCFROMHWND(hwndDrag))->hwnd;

    /*
     * Get the update rectangle and convert to screen coords.
     */
    GetUpdateRect(hwndDrag, &rcUpdate, TRUE);
    ClientToScreenRect(hwndDrag, &rcUpdate);

    /*
     * Start enumerating windows.
     */
    hwnd = hwndDrag;
    while (hwnd = GetWindow(hwnd, GW_HWNDNEXT)) {
        /*
         * Skip invisible drag windows.
         */
        if (IsWindowVisible(hwnd)) {
            /*
             * Does the window rectangle intersect the update rectangle?
             */
            GetWindowRect(hwnd, &rc);
            if (IntersectRect(&rcInt, &rc, &rcUpdate)) {
                npc = PCFROMHWND(hwnd);

                if (npc->hwndDrag == hwnd || !npc->fSelected) {
                    ScreenToClientRect(hwnd, &rcInt);
                    InvalidateRect(hwnd, &rcInt, TRUE);
                    UpdateWindow(hwnd);
                }

                if (npc->hwndDrag == hwnd)
                    break;
            }
        }
    }

    /*
     * Finally, paint the control associated with this drag window.
     */
    InvalidateRect(hwndControl, NULL, TRUE);
    UpdateWindow(hwndControl);
}

/************************************************************************
* HandleHitTest
*
* This routine takes a point from a mouse button press on a drag window
* and returns which "handle" was hit, if any.
*
* The coordinates are given in zero based coordinates of the drag
* window.
*
* Arguments:
*   HWND hwnd   - Drag window handle the x,y point is relative to.
*   INT x       - Mouse X location (in the drag's client coordinates).
*   INT y       - Mouse Y location (in the drag's client coordinates).
*
* Returns:
*   One of the DRAG_* constants.  If no handle was hit, the
*   return will be DRAG_CENTER.
*
************************************************************************/

INT HandleHitTest(
    HWND hwnd,
    INT x,
    INT y)
{
    RECT rc;
    INT xMidStart;
    INT yMidStart;

    /*
     * If there are multiple controls selected, or if the control
     * type does not allow sizing, defeat the ability to size
     * with the handles by returning DRAG_CENTER.
     */
    if (gcSelected > 1 || !(PCFROMHWND(hwnd))->pwcd->fSizeable)
        return DRAG_CENTER;

    /*
     * Get the window rectangle and cause it to be zero-origined.
     */
    GetWindowRect(hwnd, &rc);
    OffsetRect(&rc, -rc.left, -rc.top);

    /*
     * Calculate the starting points for the handles
     * that are not on a corner.
     */
    xMidStart = ((rc.right + 1) / 2) - (CHANDLESIZE / 2);
    yMidStart = ((rc.bottom + 1) / 2) - (CHANDLESIZE / 2);

    if (x < CHANDLESIZE) {
        if (y < CHANDLESIZE)
            return DRAG_LEFTTOP;
        else if (y > rc.bottom - CHANDLESIZE)
            return DRAG_LEFTBOTTOM;
        else if (y >= yMidStart && y < yMidStart + CHANDLESIZE)
            return DRAG_LEFT;
    }
    else if (x > rc.right - CHANDLESIZE) {
        if (y < CHANDLESIZE)
            return DRAG_RIGHTTOP;
        else if (y > rc.bottom - CHANDLESIZE)
            return DRAG_RIGHTBOTTOM;
        else if (y >= yMidStart && y < yMidStart + CHANDLESIZE)
            return DRAG_RIGHT;
    }
    else if (x >= xMidStart && x < xMidStart + CHANDLESIZE) {
        if (y < CHANDLESIZE)
            return DRAG_TOP;
        else if (y > rc.bottom - CHANDLESIZE)
            return DRAG_BOTTOM;
    }

    return DRAG_CENTER;
}

/************************************************************************
* MouseToDragRect
*
* This routine takes the mouse pointer coordinates from a mouse message

* and produces a rectangle that contains the coordinates that the drag
* rectangle should be displayed as.  This is for tracking (moving/sizing)
* operations.  It relies on a number of globals to have been set up
* prior to the call with such things as the current control, type of
* drag, old location of the control, offset of the original mouse press
* from the origin of the control, etc.
*
* The returned rectangle is pegged to the boundaries of the dialog (if it
* is for a control), and is aligned to the current grid units.  It is in
* dialog units relative to the appropriate point based on whether it is
* for a control or the dialog, and must be converted to window points
* before the actual drag rectangle can be drawn on the screen.
*
* Arguments:
*   INT x       - Mouse X location (in window coordinates).
*   INT y       - Mouse Y location (in window coordinates).
*   PRECT prc   - Rectangle to return the appropriate drag rectangle
*                 in.
*
************************************************************************/

STATICFN VOID MouseToDragRect(
    INT x,
    INT y,
    PRECT prc)
{
    POINT pt;
    INT fGridFlags;

    pt.x = x;
    pt.y = y;
    MouseToDU(&pt);

    switch (gHandleHit) {
        case DRAG_LEFTBOTTOM:
            SetRect(prc, pt.x, grcSelected.top, grcSelected.right,
                    (grcSelected.bottom - grcSelected.top) + pt.y);
            fGridFlags = GRIDIZE_LEFT | GRIDIZE_BOTTOM;
            break;

        case DRAG_BOTTOM:
            SetRect(prc, grcSelected.left, grcSelected.top,
                    grcSelected.right,
                    (grcSelected.bottom - grcSelected.top) + pt.y);
            fGridFlags = GRIDIZE_BOTTOM;
            break;

        case DRAG_RIGHTBOTTOM:
            SetRect(prc, grcSelected.left, grcSelected.top,
                    (grcSelected.right - grcSelected.left) + pt.x,
                    (grcSelected.bottom - grcSelected.top) + pt.y);
            fGridFlags = GRIDIZE_BOTTOM | GRIDIZE_RIGHT;
           break;

        case DRAG_RIGHT:
            SetRect(prc, grcSelected.left, grcSelected.top,
                    (grcSelected.right - grcSelected.left) + pt.x,
                    grcSelected.bottom);
            fGridFlags = GRIDIZE_RIGHT;
            break;

        case DRAG_RIGHTTOP:
            SetRect(prc, grcSelected.left, pt.y,
                    (grcSelected.right - grcSelected.left) + pt.x,
                    grcSelected.bottom);
            fGridFlags = GRIDIZE_RIGHT | GRIDIZE_TOP;
            break;

        case DRAG_TOP:
            SetRect(prc, grcSelected.left, pt.y,
                    grcSelected.right, grcSelected.bottom);
            fGridFlags = GRIDIZE_TOP;
            break;

        case DRAG_LEFTTOP:
            SetRect(prc, pt.x, pt.y, grcSelected.right, grcSelected.bottom);
            fGridFlags = GRIDIZE_LEFT | GRIDIZE_TOP;
            break;

        case DRAG_LEFT:
            SetRect(prc, pt.x, grcSelected.top,
                    grcSelected.right, grcSelected.bottom);
            fGridFlags = GRIDIZE_LEFT;
            break;

        case DRAG_CENTER:
            SetRect(prc, pt.x, pt.y,
                    (grcSelected.right - grcSelected.left) + pt.x,
                    (grcSelected.bottom - grcSelected.top) + pt.y);
            fGridFlags = GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE;
            break;
    }

    GridizeRect(prc, fGridFlags);
    FitRectToBounds(prc, gnOverHang, gHandleHit, gfDlgSelected);
}

/************************************************************************
* MouseToDU
*
* This routine converts the point at ppt from window coordinates
* for the current control into the closest Dialog Unit point.
*
* This routine normally assumes that the point is relative to the selected
* control, and will convert the point into DU's.  The current control
* can either be the current dialog or one of its child controls.
* The DU's returned will be appropriate for the type of control.  If
* it is the dialog, they will be relative to the apps client area.
* If it is a control, they will be relative to the "client" area of
* the current dialog.
*
* If there is no current selection (such as when dropping a new control),
* the point is assumed to already be relative to the dialog and it will
* will not be mapped.  This only applies when called with a point for a
* control, not a dialog.
*
* Arguments:
*   PPOINT ppt - Point to convert.
*
************************************************************************/

STATICFN VOID MouseToDU(
    PPOINT ppt)
{
    if (gfDlgSelected) {
        /*
         * Map the points from the dialog to the app client.
         */
        ClientToScreen(gcd.npc->hwnd, ppt);
        ScreenToClient(ghwndSubClient, ppt);

        /*
         * Subtract the cursor offset.
         */
        ppt->x -= gptCursorOffset.x;
        ppt->y -= gptCursorOffset.y;
    }
    else {
        /*
         * Map the points from the control to the dialog window,
         * but only if there is a current selection.  There will
         * not be a current selection when dropping a new control,
         * and the point must already be relative to the dialog!
         */
        if (gnpcSel) {
            ClientToScreen(gnpcSel->hwnd, ppt);
            ScreenToClient(gcd.npc->hwnd, ppt);
        }

        /*
         * Subtract the cursor offset, then the dialogs frame
         * controls offset.
         */
        ppt->x -= gptCursorOffset.x;
        ppt->y -= gptCursorOffset.y;
    }

    /*
     * Convert this position to dialog units.
     */
    WinToDUPoint(ppt);
}

/************************************************************************
* CtrlButtonDown
*
* This routine is called by the control and drag window procs when
* the left mouse button is pressed.  It checks for the Ctrl modifier key
* then passes control on to the appropriate routine.
*
* When hwnd is the dialog itself, fHandleWindow must be TRUE, even
* if a handle was not hit, because of the special-case code that must
* be done for the dialog (it doesn't have a separate drag window).
*
* Arguments:
*   HWND hwnd          - Window handle, can be a drag window or control.
*   INT x              - X mouse location (window coordinates).
*   INT y              - Y mouse location (window coordinates).
*   BOOL fHandleWindow - TRUE if a handle was clicked on, or FALSE if the
*                        body of a control was clicked on.
*
************************************************************************/

VOID CtrlButtonDown(
    HWND hwnd,
    INT x,
    INT y,
    BOOL fHandleWindow)
{
    POINT pt;
    HWND hwndHit;
    INT nOverHang;

    /*
     * Discard all mouse messages during certain operations.
     */
    if (gfDisabled)
        return;

    /*
     * Also, be sure any outstanding changes get applied
     * without errors.
     */
    if (!StatusApplyChanges())
        return;

    if (gCurTool != W_NOTHING) {
        nOverHang = GetOverHang(gpwcdCurTool->iType, gpwcdCurTool->cyDefault);
        DragNewBegin(gpwcdCurTool->cxDefault,
                gpwcdCurTool->cyDefault, nOverHang);
    }
    else {
        /*
         * If the Control key is down, duplicate the control, unless
         * it is the dialog (mouse duplicate of the dialog is not
         * supported).
         */
        if ((GetKeyState(VK_CONTROL) & 0x8000) &&
                (PCFROMHWND(hwnd))->pwcd->iType != W_DIALOG) {
            /*
             * First, figure out which control was hit and select it.
             */
            if (fHandleWindow) {
                hwndHit = hwnd;
            }
            else {
                pt.x = x;
                pt.y = y;
                hwndHit = CtrlHitTest(hwnd, &pt);
            }

            SelectControl(PCFROMHWND(hwndHit), TRUE);

            /*
             * If there is still a selection, begin dragging a copy
             * of it.  The check here is necessary because the prior
             * call to SelectControl can unselect the last selected
             * control if the Shift key was held down.
             */
            if (gcSelected)
                Duplicate();
        }
        else {
            /*
             * Start a drag operation.  This can be either moving or sizing
             * the control, depending on fHandleWindow and where the mouse
             * click is at.
             */
            DragBegin(hwnd, x, y, fHandleWindow);
        }
    }
}

/************************************************************************
* DragNewBegin
*
* This routine begins a drag operation when dropping a new control, or
* group of controls.  It is NOT used when dragging existing controls.
*
* Arguments:
*   INT cx        - Width of the new control.
*   INT cy        - Height of the new control.
*   INT nOverHang - How much the control can overhang the dialog bottom.
*
************************************************************************/

VOID DragNewBegin(
    INT cx,
    INT cy,
    INT nOverHang)
{
    POINTS mpt;
    POINT pt;
    DWORD dwPos;

    /*
     * Always be sure the focus is where we want it, not on something
     * like the status ribbon or "Esc" won't work to cancel the tracking.
     */
    SetFocus(ghwndMain);

    /*
     * Cancel any current selection, and set some state globals.
     */
    CancelSelection(TRUE);
    gHandleHit = DRAG_CENTER;
    gState = STATE_DRAGGINGNEW;
    SetCursor(hcurMove);

    /*
     * The cursor offset is set to be located in the middle of the
     * new control.  This causes the pointer to be initially located
     * exactly in the center.
     */
    gptCursorOffset.x = cx;
    gptCursorOffset.y = cy;
    DUToWinPoint(&gptCursorOffset);
    gptCursorOffset.x /= 2;
    gptCursorOffset.y /= 2;

    /*
     * Set a global with the overhang.  This is used all during the
     * drag operation we are starting.
     */
    gnOverHang = nOverHang;

    /*
     * Now we make up a dummy rectangle for the new control.  We start
     * it at (0,0) with a size of cx and cy.  The point where the mouse
     * was when the command was done is obtained and mapped to the dialog
     * (it is assumed that only controls will be done here, not a dialog).
     * The new control rectangle is then converted to be at this location,
     * it is gridized, the tracking rectangle is shown and we are off.
     */
    SetRect(&grcSelected, 0, 0, cx, cy);
    dwPos = GetMessagePos();
    mpt = (*((POINTS *)&(dwPos)));
    ((pt).x = (mpt).x, (pt).y = (mpt).y);
    ScreenToClient(gcd.npc->hwnd, &pt);
    MouseToDragRect(pt.x, pt.y, &grcSelected);
    InitTracking();
    DrawTrackRect(&grcSelected, FALSE, TRUE);

    /*
     * Display the initial coordinates.
     */
    StatusSetCoords(&grcSelected);

    /*
     * The mouse messages will come through the dialog subclass proc for
     * these kinds of operations.
     */
    SetCapture(gcd.npc->hwnd);
}

/************************************************************************
* DragBegin
*
* This routine begins a drag operation for either moving a control or
* sizing it.  The tracking rectangle is not actually drawn until the
* mouse moves by a grid unit, however.
*
* To begin a drag on the dialog itself, fHandleWindow must be TRUE, even
* if a handle was not hit, because of the special-case code that must
* be done for the dialog (it doesn't have a separate drag window).
*
* Arguments:
*   HWND hwnd          - Window handle, can be a drag window or control.
*   INT x              - Starting X mouse location (window coordinates).
*   INT y              - Starting Y mouse location (window coordinates).
*   BOOL fHandleWindow - TRUE if the drag is happening because a drag
*                        handle was clicked on, or FALSE if the drag is
*                        happening because the body of a control is
*                        clicked on.
*
*
************************************************************************/

STATICFN VOID DragBegin(
    HWND hwnd,
    INT x,
    INT y,
    BOOL fHandleWindow)
{
    NPCTYPE npcT;
    HWND hwndHit;
    POINT pt;
    NPCTYPE npc;
    BOOL fPrevSelect = FALSE;
    INT nBottom;

    /*
     * Always be sure the focus is where we want it, not on something
     * like the status ribbon or "Esc" won't work to cancel the tracking.
     */
    SetFocus(ghwndMain);

    pt.x = x;
    pt.y = y;

    /*
     * Is this drag happening because a drag handle was clicked on?
     */
    if (fHandleWindow) {
        /*
         * Find out which handle was clicked on.  It is assumed that
         * hwnd is a drag window.
         */
        gHandleHit = HandleHitTest(hwnd, pt.x, pt.y);
        hwndHit = hwnd;
    }
    else {
        /*
         * The body of a control was clicked on.  Set a global to say
         * that we are moving a control, then find out which control
         * was hit (with our own hit testing).
         */
        gHandleHit = DRAG_CENTER;
        hwndHit = CtrlHitTest(hwnd, &pt);
    }

    /*
     * Find out if the control clicked on was the currently selected
     * control already.
     */
    npc = PCFROMHWND(hwndHit);
    if (npc == gnpcSel)
        fPrevSelect = TRUE;

    /*
     * Select the control.  This can return FALSE if the control is
     * unselected (shift key is down and the control is already selected).
     */
    if (!SelectControl(npc, TRUE))
        return;

    /*
     * If the dialog is selected, we make a rectangle that encloses all
     * the controls.  This will be used to limit the size that the dialog
     * can be sized to so that it cannot cover any existing controls.
     */
    if (gfDlgSelected) {
        /*
         * Seed the rectangle with impossible values.
         */
        SetRect(&grcMinDialog, 32000, 32000, -32000, -32000);

        /*
         * Loop through all the controls, expanding the rectangle to
         * fit around all the controls.
         */
        for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
            if (npcT->rc.left < grcMinDialog.left)
                grcMinDialog.left = npcT->rc.left;

            if (npcT->rc.right > grcMinDialog.right)
                grcMinDialog.right = npcT->rc.right;

            if (npcT->rc.top < grcMinDialog.top)
                grcMinDialog.top = npcT->rc.top;

            /*
             * When calculating the bottom boundary of the controls,
             * make the rectangle shorter by the ovehang amount.  This
             * allows the dialog to be sized up so that it covers
             * parts of controls with overhang (comboboxes).
             */
            nBottom = npcT->rc.bottom - GetOverHang(npcT->pwcd->iType,
                    npcT->rc.bottom - npcT->rc.top);
            if (nBottom > grcMinDialog.bottom)
                grcMinDialog.bottom = nBottom;
        }

        OffsetRect(&grcMinDialog, gcd.npc->rc.left, gcd.npc->rc.top);
    }

    /*
     * If the control clicked on was already the anchor, go right into
     * dragging mode.  If it was not, go into pre-drag mode, which will
     * defer the calculation of offsets, etc., until after a certain
     * small amount of time so the mouse can be "debounced".
     */
    if (fPrevSelect) {
        DragBegin2(&pt);
    }
    else {
        gState = STATE_PREDRAG;

        /*
         * Save the point in a global.  If the mouse pointer is moved
 * too far away from this point, we will start the drag operation
         * even if the pre-drag time has not elapsed yet.
         */
        gptPreDragStart = pt;

        /*
         * Start the pre-drag timer.
         */
        SetTimer(hwndHit, TID_PREDRAG, gmsecPreDrag, NULL);
    }

    /*
     * The mouse messages from now on will go to the window clicked on,
     * either the drag window or the control window.
     */
    SetCapture(hwndHit);
}

/************************************************************************
* DragBegin2
*
* This routine continues the initiation of a drag operation started
* by DragBegin.  It is separate because it calculates offsets based
* on where the mouse pointer is, and these calculations can be deferred
* until a later time than when DragBegin was called so that the mouse
* can be "debounced".
*
* Arguments:
*   POINT ppt - Starting mouse location (window coordinates).
*
************************************************************************/

STATICFN VOID DragBegin2(
    PPOINT ppt)
{
    gState = STATE_DRAGGING;

    /*
     * Set the pointer to the "move" pointer if we are moving.
     * Otherwise, the pointer should already be set to the proper
     * sizing pointer.
     */
    if (gHandleHit == DRAG_CENTER)
        SetCursor(hcurMove);

    /*
     * Save away the initial offset of the cursor.
     */
    CalcCursorOffset(ppt);

    /*
     * Initialize the track rectangle.  Note we are calling DrawTrackRect
     * with FALSE.
     */
    DrawTrackRect(&grcSelected, gfDlgSelected, FALSE);
}

/****************************************************************************
* CtrlHitTest
*
* This routine walks the list of controls and determines which one is
* "hit" by this point.  If a hit is found, the point is also converted
* to coordinates for the hit window.
*
* There is a special case when hitting controls over a groupbox.
* Controls within a groupbox will always be hit instead of the
* groupbox itself.
*
* Arguments:
*   HWND hwnd  - Window handle the coordinates are relative to.
*   PPOINT ppt - Window point where the click occurred (window coords).
*
* Returns:
*   The hwnd of the "hit" control will be returned.  If no control was hit,
*   the hwnd that was passed in is returned.
*
****************************************************************************/

STATICFN HWND CtrlHitTest(
    HWND hwnd,
    PPOINT ppt)
{
    NPCTYPE npc;
    RECT rc;
    HWND hwndHit = (HWND)NULL;
    BOOL fGroupHit = FALSE;

    for (npc = npcHead; npc; npc = npc->npcNext) {
        GetWindowRect(npc->hwnd, &rc);
        ScreenToClientRect(npc->hwnd, &rc);
        MyMapWindowRect(npc->hwnd, hwnd, &rc);

        /*
         * Is this a hit, and was there either no control hit as
         * yet, or this control is not a groupbox, or the control
         * that was previously hit was a groupbox also?
         */
        if (PtInRect(&rc, *ppt) &&
                (!hwndHit || npc->pwcd->iType != W_GROUPBOX || fGroupHit)) {
            hwndHit = npc->hwnd;
            if (npc->pwcd->iType == W_GROUPBOX)
                fGroupHit = TRUE;
            else
                fGroupHit = FALSE;
        }
    }

    if (hwndHit) {
        MapWindowPoint(hwnd, hwndHit, ppt);
        return hwndHit;
    }
    else {
        return hwnd;
    }
}

/****************************************************************************
* PreDragTimeout
*
* This function handles the WM_TIMER message from the control window
* proc.  It is used so that the dragging of a newly selected control
* can be deferred for a small period of time to "debounce" the mouse.
*
* This function is also called if the mouse is moved too much during the
* debounce time, effectively cutting the debounce time short.
*
* Arguments:
*   HWND hwnd      - Window handle the timer came from.
*   BOOL fTimedOut - TRUE if the predrag is ending because the timer
*                    expired.  FALSE if the predrag is ending because
*     the mouse was moved too far.
*
****************************************************************************/

VOID PreDragTimeout(
    HWND hwnd,
    BOOL fTimedOut)
{
    POINT pt;

    /*
     * The debounce time is over and the mouse button is still
     * down.  Get the current mouse pointer location and go into
     * drag mode.
     */
    if (gState == STATE_PREDRAG) {
        /*
         * If we timed out (the mouse was not moved a large distance),
         * eat any small movement that may have been done during the
         * predrag time by setting the mouse cursor back to the location
         * that it started at.  Note that we do not do this if the mouse
         * was moved a large distance, because the efect would be
         * noticeable for that case, and we want the control to be
         * moved then anyways.
         */
        if (fTimedOut) {
            pt = gptPreDragStart;
            ClientToScreen(hwnd, &pt);
            SetCursorPos(pt.x, pt.y);
        }

        DragBegin2(&gptPreDragStart);
    }

    KillTimer(hwnd, TID_PREDRAG);
}

/************************************************************************
* CtrlMouseMove
*
* This routine handles the mouse move messages for controls, the dialog,
* drag windows and when dropping a new control.
*
* During a drag operation, the tracking rectangle will be adjusted.
* If there is not a drag operation is effect, the mouse cursor will
* be changed to a sizing pointer if it is over a drag handle.  If not
* over a drag handle, the pointer will be changed back to the arrow.
*
* Arguments:
*   HWND hwnd        - Window handle the x and y are relative to.
*   BOOL fDragWindow - TRUE if hwnd is a drag window.
*   INT x            - X location of the mouse movement (window coords).
*   INT y            - Y location of the mouse movement (window coords).
*
************************************************************************/

VOID CtrlMouseMove(
    HWND hwnd,
    BOOL fDragWindow,
    INT x,
    INT y)
{
    RECT rc;
    RECT rc2;
    HCURSOR hcur = NULL;

    /*
     * Discard all mouse messages during certain operations
     * (but still set the pointer properly).
     */
    if (gfDisabled) {
        SetCursor(hcurArrow);
        return;
    }

    switch (gState) {
        case STATE_PREDRAG:
            /*
     * If the mouse was moved too far, consider the
             * pre-drag time elapsed and go into drag mode.
             */
            if (abs(gptPreDragStart.x - x) > gcxPreDragMax ||
                    abs(gptPreDragStart.y - y) > gcyPreDragMax)
                PreDragTimeout(hwnd, FALSE);

            break;

        case STATE_DRAGGING:
        case STATE_DRAGGINGNEW:
            MouseToDragRect(x, y, &rc);

            if (!EqualRect(&rc, &grcTrackDU)) {
                /*
                 * If the tracking rectangle is not shown, this means that
                 * this is the first significant mouse move since the start
                 * of a drag operation, and we need to lock the window, get
                 * our clip DC, etc.
                 */
                if (!gfTrackRectShown)
                    InitTracking();

                DrawTrackRect(&rc, gfDlgSelected, TRUE);

                if (gcSelected > 1) {
                    /*
                     * Since there are multiple controls selected,
                     * rc will be the rectangle that surrounds them
                     * all.  We really want to just show the anchor
                     * controls new position, so we have to do a
                     * little math to calculate and display it.
                     */
                    rc2 = gnpcSel->rc;
                    OffsetRect(&rc2, rc.left - grcSelected.left,
                            rc.top - grcSelected.top);
                    StatusSetCoords(&rc2);
                }
                else {
                    /*
                     * Either a single control is being dragged or
                     * a new control is being dropped.
                     */
                    StatusSetCoords(&rc);
                }
            }

            break;

        case STATE_SELECTING:
            OutlineSelectDraw(x, y);
            break;

        default:
            /*
             * Is there a tool selected?
             */
            if (gCurTool != W_NOTHING) {
                hcur = hcurDropTool;
            }
            else {
                /*
                 * If hwnd is a drag window, see if the pointer is over
                 * over any of the handles and change it to one of the
                 * sizing pointers if necessary.  Otherwise set the pointer
                 * to the default arrow pointer.
                 */
                if (fDragWindow) {
                    switch (HandleHitTest(hwnd, x, y)) {
                        case DRAG_LEFTBOTTOM:
                        case DRAG_RIGHTTOP:
                            hcur = hcurSizeNESW;
                            break;

                        case DRAG_LEFTTOP:
                        case DRAG_RIGHTBOTTOM:
                            hcur = hcurSizeNWSE;
                            break;

                        case DRAG_BOTTOM:
                        case DRAG_TOP:
                            hcur = hcurSizeNS;
                            break;

                        case DRAG_RIGHT:
                        case DRAG_LEFT:
                            hcur = hcurSizeWE;
                            break;

                        case DRAG_CENTER:
                        default:
                            hcur = hcurArrow;
                            break;
                    }
                }
                else {
                    hcur = hcurArrow;
                }
            }

            break;
    }

    if (hcur)
        SetCursor(hcur);
}

/************************************************************************
* DragCancel
*
* This function cancels any drag operation in effect.  It handles
* such things as erasing any visible tracking rectangle, freeing any
* "copy" data, setting globals and updating the status display.  It
* can be used no matter how the drag operation was started.
*
************************************************************************/

VOID DragCancel(VOID)
{
    HWND hwnd;

    switch (gState) {
        case STATE_PREDRAG:
            /*
             * Stop the timer.  Note that this assumes the timer
             * was attached to the capture window.  This should
             * be safe (see the associated SetTimer).
             */
            if (hwnd = GetCapture())
                KillTimer(hwnd, TID_PREDRAG);

            break;

        case STATE_DRAGGING:
        case STATE_DRAGGINGNEW:
            CancelTracking();

            if (gpResCopy) {
                MyFree(gpResCopy);
                gpResCopy = NULL;
            }

            break;
    }

    gState = STATE_NORMAL;
    ReleaseCapture();
    SetCursor(hcurArrow);

    StatusUpdate();
    StatusSetEnable();
}

/************************************************************************
* CtrlButtonUp
*
* This function is called when the left mouse button is released.  Depending
* on the mode, it will complete the operation started when the button
* was pressed down.
*
* Arguments:
*   INT x - Mouse X location (in window coords).
*   INT y - Mouse Y location (in window coords).
*
************************************************************************/

VOID CtrlButtonUp(
    INT x,
    INT y)
{
    /*
     * Discard all mouse messages during certain operations.
     */
    if (gfDisabled)
        return;

    switch (gState) {
        case STATE_PREDRAG:
            /*
             * They released the mouse button during the debounce time,
             * so cancel the drag.
             */
            DragCancel();
            break;

        case STATE_DRAGGING:
        case STATE_DRAGGINGNEW:
            DragEnd(x, y);
            break;

        case STATE_SELECTING:
            OutlineSelectEnd(x, y);
            break;

        default:
            break;
    }
}

/************************************************************************
* DragEnd
*
* This function completes all kinds of drag operations.  If dragging a
* new control, it will be dropped at the specified location.  If dragging
* an existing control, it will be positioned to the given location.
*
* Arguments:
*   INT x - X location the control ended up at (in window coords).
*   INT y - Y location the control ended up at (in window coords).
*
************************************************************************/

VOID DragEnd(
    INT x,
    INT y)
{
    PDIALOGBOXHEADER pdbh;
    PCONTROLDATA pcd;
    INT cControls;
    RECT rc;
    INT i;
    INT cx;
    INT cy;

CancelTracking();
    MouseToDragRect(x, y, &rc);

    if (gState == STATE_DRAGGING) {
        PositionControl(&rc);
    }
    else {
        if (gpResCopy) {
            pdbh = (PDIALOGBOXHEADER)SkipResHeader(gpResCopy);
            cControls = (INT)pdbh->NumberOfItems;
            pcd = SkipDialogBoxHeader(pdbh);
            cx = rc.left - grcCopy.left;
            cy = rc.top - grcCopy.top;

            /*
             * Loop through all the controls, adjusting their position
             * according to where the drag rectangle ended up.
             */
            for (i = 0; i < cControls; i++) {
                /*
                 * Add cx and cy to the resource's x and y fields.
                 */
                pcd->x += (WORD)cx;
                pcd->y += (WORD)cy;

                pcd = SkipControlData(pcd);
            }

            /*
             * Now we go and create all the controls, adding them to
             * the current dialog.  It is assumed that the image in
             * gpResCopy specifies controls to add, and not a dialog
             * to create!
             */
            if (ResToDialog(gpResCopy, FALSE)) {
                gfResChged = gfDlgChanged = TRUE;
                ShowFileStatus(FALSE);
            }

            MyFree(gpResCopy);
            gpResCopy = NULL;

            StatusUpdate();
            StatusSetEnable();
        }
        else {
            /*
             * Drop the new control.
             */
            DropControl(gpwcdCurTool, &rc);

            if (!gfToolLocked)
                ToolboxSelectTool(W_NOTHING, FALSE);
        }
    }

    gState = STATE_NORMAL;
    ReleaseCapture();
    SetCursor(hcurArrow);
}


FILE.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 Header *******************************
* Module Name: file.c
*
* This file contains the high level routines that begin opening
* and saving files.
*
* Functions:
*    Open()
*    BuildFilterString()
*    DoWeSave()
*    Save()
*    OpenCmdLineFile()
*    FileInPath()
*    ShowFileStatus()
*    DifferentDirs()
*    HasPath()
*    WriteDWordPad()
*    BuildDefSaveName()
*    WriteTheFile()
*    FormTempFileName()
*    FileCat()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"

#include <wchar.h>

#include <commdlg.h>

/*
 * File types.
 */
#define FILE_RES    0               // Resource (.RES) file.
#define FILE_DLG    1               // Dialog (.DLG) file.
#define FILE_INC    2               // Include (.H) file.

STATICFN VOID BuildDefSaveName(INT FileType, LPTSTR pszFullFileName,
    LPTSTR pszFileName, LPTSTR pszOtherFullFileName, LPTSTR pszOtherFileName,
    LPTSTR pszFullFileNameBuffer, INT cchBuffer);
STATICFN BOOL WriteTheFile(LPTSTR pszFile, INT fmt);
STATICFN VOID FormTempFileName(LPTSTR pszBaseName,  LPTSTR pszBuffer);
STATICFN VOID FileCat(LPTSTR pchName, LPTSTR pchCat, BOOL fChop);

/************************************************************************
* Open
*
* Handles opening of resource and include files.
* Saves current dialog in the resource.
* Might put up a message box.
* Cancels moves.
* Changes szFullResFile, pszResFile, szFullIncludeFile, pszIncludeFile
* Puts up dialog boxes.
* Restores dialog box from resource.
* Sets changed flags.
*
* Arguments:
*   INT FileType - FILE_RESOURCE or FILE_INCLUDE.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
************************************************************************/

BOOL Open(
    INT FileType)
{
    BOOL fSuccess;
    BOOL fGotName;
    OPENFILENAME ofn;
    TCHAR szNewFileName[CCHMAXPATH];
    TCHAR szInitialDir[CCHMAXPATH];
    TCHAR szFilter[CCHTEXTMAX];
    INT idPrevDlg;

    /*
     * Cancel any outstanding selection(s).
     */
    CancelSelection(TRUE);

    /*
     * Put current dialog back into the resource buffer.
     */
    if (!SynchDialogResource())
        return FALSE;

    /*
     * Begin setting up the globals and the open file dialog structure.
     */
    fSuccess = FALSE;
    *szNewFileName = CHAR_NULL;

    /*
     * Build up the filter string.
     */
    BuildFilterString(FileType, szFilter);

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = ghwndMain;
    ofn.hInstance = NULL;
    ofn.lpstrFilter = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = szNewFileName;
    ofn.nMaxFile = CCHMAXPATH;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;

    if (FileType == FILE_INCLUDE) {
        /*
         * If there is a res file, set the default include file
         * name to open to be the basename of the res file with
         * a .H extension, if such a file exists.  We use szInitialDir
         * here as a temporary buffer.
         */
        if (pszResFile) {
            lstrcpy(szInitialDir, szFullResFile);
            FileCat(szInitialDir, ids(IDS_DOTH), TRUE);
            if (GetFileAttributes(szInitialDir) != -1) {
                lstrcpy(szNewFileName, pszResFile);
                FileCat(szNewFileName, ids(IDS_DOTH), TRUE);
            }
        }

        ofn.lpstrTitle = ids(IDS_INCOPENTITLE);
        ofn.lpstrDefExt = ids(IDS_INCEXT);
    }
    else {
        ofn.lpstrTitle = ids(IDS_RESOPENTITLE);
        ofn.lpstrDefExt = ids(IDS_RESEXT);
    }

    /*
     * If they have already opened one res file, start looking for
     * any new files to open in the same directory.  Otherwise, just
     * default to the current directory.
     */
    if (pszResFile) {
        lstrcpy(szInitialDir, szFullResFile);
        *FileInPath(szInitialDir) = CHAR_NULL;
        ofn.lpstrInitialDir = szInitialDir;
    }
    else {
        ofn.lpstrInitialDir = NULL;
    }

    ofn.Flags = OFN_HIDEREADONLY | OFN_SHOWHELP | OFN_FILEMUSTEXIST;
    ofn.lCustData = 0;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;

    /*
     * Fire off the dialog box to open the file.
     */
    EnteringDialog((FileType == FILE_INCLUDE) ?
            DID_COMMONFILEOPENINCLUDE : DID_COMMONFILEOPENRES,
            &idPrevDlg, TRUE);
    fGotName = GetOpenFileName(&ofn);
    EnteringDialog(idPrevDlg, NULL, FALSE);
    if (fGotName) {
        if (FileType == FILE_INCLUDE) {
            if (OpenIncludeFile(szNewFileName)) {
                /*
                 * Since we just loaded a new include file, we mark the
                 * resource as changed so that the .RES and .DLG files
                 * will be written out with the proper name in the
                 * DLGINCLUDE statement.
                 */
                gfResChged = TRUE;
                fSuccess = TRUE;
            }
        }
        else {
            if (OpenResFile(szNewFileName))
                fSuccess = TRUE;
        }
    }

    ShowFileStatus(TRUE);

    return fSuccess;
}

/************************************************************************
* BuildFilterString
*
* This function creates a filter string to be passed into the
* standard file open and save dialogs.  This will be something like:
* "Resource (*.res)\0*.res\0\0"
*
* Arguments:
*   INT FileType      - Flags for type of file, FILE_INCLUDE, FILE_RESOURCE
*                       or FILE_DLL.
*   LPTSTR pszFilter  - Where to return the filter string.
*
************************************************************************/

VOID BuildFilterString(
    INT FileType,
    LPTSTR pszFilter)
{
    INT idsFileSpecName;
    INT idsFileSpec;
    LPTSTR psz;

    if (FileType & FILE_INCLUDE) {
        idsFileSpecName = IDS_DEFINCFILESPECNAME;
        idsFileSpec = IDS_DEFINCFILESPEC;
    }
    else if (FileType & FILE_RESOURCE) {
        idsFileSpecName = IDS_DEFRESFILESPECNAME;
        idsFileSpec = IDS_DEFRESFILESPEC;
    }
    else { // Must be a DLL.
        idsFileSpecName = IDS_DEFDLLFILESPECNAME;
        idsFileSpec = IDS_DEFDLLFILESPEC;
    }

    /*
     * Build up the filter string.  This will be something like:
     * "Resource (*.res)\0*.res\0\0"
     */
    psz = (LPTSTR)WriteSz(pszFilter, ids(idsFileSpecName));
    psz = (LPTSTR)WriteSz(psz, ids(idsFileSpec));
    *psz = CHAR_NULL;
}

/************************************************************************
* OpenCmdLineFile
*
* Handles opening of the resource file specified on the command line.
*
* Arguments:
*   LPTSTR - pointer to the file name string
*
************************************************************************/

VOID OpenCmdLineFile(
    LPTSTR pszFileName)
{
    TCHAR szFullPath[CCHMAXPATH];
    LPTSTR pszOnlyFileName;

    if (SearchPath(L".", pszFileName, ids(IDS_DOTRES), CCHMAXPATH,
            szFullPath, &pszOnlyFileName) == -1) {
        Message(MSG_CANTOPENRES, pszFileName);
    }
    else {
        OpenResFile(szFullPath);
    }
}

/************************************************************************
* DoWeSave
*
* This function checks to see if the include file or the resource file
* needs to be saved.  It first checks the changed flags and if TRUE,
* asks the user if they want to save the file.  If they say yes, it
* calls Save to do the actual work.
*
* Arguments:
*     INT rgbFlags = FILE_RESOURCE or FILE_INCLUDE (but not both).
*
* Returns:
*     IDYES    - The user wanted to save the file AND the save
*                was successful, or the file has not been changed.
*     IDNO     - The file had been changed but the user did not
*                want it saved.
*     IDCANCEL - The file had been changed, and either the user wanted
*                it saved and the save failed, or they specified that
*                they wanted the operation cancelled.
*
************************************************************************/

INT DoWeSave(
    INT rgbFlags)
{
    LPTSTR pszFile;
    INT MsgCode;
    BOOL fChanged;
    INT nRet = IDYES;

    /*
     * First set variables for current case.
     */
    if (rgbFlags & FILE_RESOURCE) {
        fChanged = gfResChged;
        MsgCode = MSG_CLOSING;
        pszFile = pszResFile ? pszResFile : ids(IDS_UNTITLED);
    }
    else {
        fChanged = gfIncChged;
        MsgCode = MSG_INCLCLOSING;
        pszFile = pszIncludeFile ? pszIncludeFile : ids(IDS_UNTITLED);
    }

    if (fChanged) {
        nRet = Message(MsgCode, pszFile);
        if (nRet == IDYES) {
            if (!Save(FILE_NOSHOW | rgbFlags))
                nRet = IDCANCEL;
        }
    }

    return nRet;
}

/************************************************************************
* Save
*
* Handles all saving of files based on menu choice.  Does a
* CancelSelection and a SynchDialogResource.
*
* Arguments:
*     INT rgbFlags - Can include FILE_SHOW, FILE_INCLUDE, FILE_SAVEAS.
*
* Returns:
*     TRUE if the file was saved, FALSE if not.
*
************************************************************************/

BOOL Save(
    INT rgbFlags)
{
    OPENFILENAME ofn;
    BOOL fGotName;
    LPTSTR pszFileName;
    LPTSTR pszFileNameDlg;
    LPTSTR pszFullFileName;
    BOOL fSuccess = FALSE;
    TCHAR szInitialDir[CCHMAXPATH];
    TCHAR szSaveFileName[CCHMAXPATH];
    TCHAR szSaveFileNameDlg[CCHMAXPATH];
    TCHAR szFilter[CCHTEXTMAX];
    INT idPrevDlg;

    /*
     * Put current dialog back into the resource buffer.
     */
    if (!SynchDialogResource())
        return FALSE;

    /*
     * If the file being saved has not been named, force a "Save As".
     */
    if ((rgbFlags & FILE_INCLUDE) ? !pszIncludeFile : !pszResFile)
        rgbFlags |= FILE_SAVEAS;

    if (rgbFlags & FILE_SAVEAS) {
        ofn.lStructSize = sizeof(ofn);
        ofn.hwndOwner = ghwndMain;
        ofn.hInstance = NULL;

        /*
         * Build up the filter string.
         */
        BuildFilterString(rgbFlags, szFilter);
        ofn.lpstrFilter = szFilter;
        ofn.lpstrCustomFilter = NULL;
        ofn.nMaxCustFilter = 0;
        ofn.nFilterIndex = 1;

        ofn.lpstrFile = szSaveFileName;
        ofn.nMaxFile = CCHMAXPATH;
        ofn.lpstrFileTitle = NULL;
        ofn.nMaxFileTitle = 0;

        if (rgbFlags & FILE_INCLUDE) {
            ofn.lpstrTitle = ids(IDS_INCSAVETITLE);
            ofn.lpstrDefExt = ids(IDS_INCEXT);
            BuildDefSaveName(FILE_INCLUDE,
                    szFullIncludeFile, pszIncludeFile,
                    szFullResFile, pszResFile,
                    szInitialDir, CCHMAXPATH);
        }
        else {
            ofn.lpstrTitle = ids(IDS_RESSAVETITLE);
            ofn.lpstrDefExt = ids(IDS_RESEXT);
            BuildDefSaveName(FILE_RESOURCE,
                    szFullResFile, pszResFile,
                    szFullIncludeFile, pszIncludeFile,
                    szInitialDir, CCHMAXPATH);
        }

        /*
         * At this point, szInitialDir contains the full path to
         * the suggested save file name.  Find the end of the path,
         * copy just the filename to the file name buffer and cut
         * the filename portion off the initial directory buffer.
         */
        pszFileName = FileInPath(szInitialDir);
        lstrcpy(szSaveFileName, pszFileName);
        *pszFileName = CHAR_NULL;
        ofn.lpstrInitialDir = szInitialDir;

        ofn.Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY | OFN_SHOWHELP;
        ofn.lCustData = 0;
        ofn.lpfnHook = NULL;
        ofn.lpTemplateName = NULL;

        /*
         * Fire off the dialog box to get the file name to use.
         */
        EnteringDialog((rgbFlags & FILE_INCLUDE) ?
                DID_COMMONFILESAVEINCLUDE : DID_COMMONFILESAVERES,
                &idPrevDlg, TRUE);
        fGotName = GetSaveFileName(&ofn);
        EnteringDialog(idPrevDlg, NULL, FALSE);
        if (fGotName) {
            pszFullFileName = szSaveFileName;
            pszFileName = FileInPath(szSaveFileName);
            fSuccess = TRUE;
        }
    }
    else {
        if (rgbFlags & FILE_INCLUDE) {
            pszFileName = pszIncludeFile;
            pszFullFileName = szFullIncludeFile;
        }
        else {
            pszFileName = pszResFile;
            pszFullFileName = szFullResFile;
        }

        fSuccess = TRUE;
    }

    if (fSuccess) {
        if (rgbFlags & FILE_INCLUDE) {
            /*
             * Save include file.
             */
            if (!WriteTheFile(pszFullFileName, FILE_INC)) {
                Message(MSG_CANTCREATE, pszFileName);
                fSuccess = FALSE;
            }
        }
        else {
            /*
             * Form the same name as the .res file but with
             * a .dlg extension.
             */
            lstrcpy(szSaveFileNameDlg, pszFullFileName);
            pszFileNameDlg = FileInPath(szSaveFileNameDlg);
            FileCat(pszFileNameDlg, ids(IDS_DOTDLG), TRUE);

            /*
             * Save .RES file, then the .DLG file.  It is done
             * in this order so that makes wil notice that the
             * .dlg file has a newer time stamp than the .res
             * and will cause the .res to be rebuilt.  This
             * could be necessary to pick up other changes
             * in the resources in a project.
             */
            if (!WriteTheFile(pszFullFileName, FILE_RES)) {
                Message(MSG_CANTCREATE, pszFileName);
                fSuccess = FALSE;
            }
            else if (!WriteTheFile(szSaveFileNameDlg, FILE_DLG)) {
                Message(MSG_CANTCREATE, pszFileNameDlg);
                fSuccess = FALSE;
            }
            else {
                /*
                 * Successfully saved both files.  Update our
                 * globals.
                 */
                lstrcpy(szFullResFile, pszFullFileName);
                pszResFile = FileInPath(szFullResFile);
                gfResChged = FALSE;
            }
        }
    }

    ShowFileStatus(TRUE);

    return fSuccess;
}

/************************************************************************
* BuildDefSaveName
*
* This function takes the filenames of the current resource and include
* files and builds the default filename that will be shown in the
* "Save As" dialog.  If the current file is still untitled, it will
* attempt to pick a default name based on the other files name.
*
* To use, pass in the file type (FILE_RESOURCE or FILE_INCLUDE) and
* give the current file name and full file name of both the current
* file you are building, and the other type of file.  The following
* rules will be followed, in order:
*
*   1. If the file name is valid (not NULL) and it is either the
*      include file we are naming or it is the res file but there
*      is no include file, it will copy the full file name to the
*      output buffer.
*
*   2. If the other file name is valid, it will take this name, add the
*      appropriate extension and copy it to the output buffer.
*
*   3. If neither of the file names are valid (they are BOTH untitled),
*      it will assume the current directory and make a default file
*      name with the appropriate extension.
*
* Rule 1 is a little complicated, but it's purpose is to make it so
* that if a default res file name is being requested, and they changed
* the directory and/or name for the include file that was just saved,
* the default directory and name for the res file will be the same
* directory and base name as the new include file directory and name.
*
* Arguments:
*   INT FileType                 - Either FILE_RESOURE or FILE_INCLUDE.
*   LPTSTR pszFullFileName       - The full file name.  This will only
*                                  be used if pszFileName is not NULL.
*   LPTSTR pszFileName           - File name to use, or NULL if it is
*                                  currently untitled.
*   LPTSTR pszOtherFullFileName  - Full file name of the other file.  Only
*                                  considered valid if pszOtherFileName is
*                                  not NULL.
*   LPTSTR pszOtherFileName      - File name of the other file, or NULL if
*                                  it is untitled.
*   LPTSTR pszFullFileNameBuffer - Where to put the full file name.
*   INT cchBuffer                - Size of the buffer in characters.
*
************************************************************************/

STATICFN VOID BuildDefSaveName(
    INT FileType,
    LPTSTR pszFullFileName,
    LPTSTR pszFileName,
    LPTSTR pszOtherFullFileName,
    LPTSTR pszOtherFileName,
    LPTSTR pszFullFileNameBuffer,
    INT cchBuffer)
{
    TCHAR szBuffer[CCHMAXPATH];

    if (pszFileName && (FileType == FILE_INCLUDE || !pszOtherFileName)) {
        /*
         * Simple case.  The file already has a title.
         */
        lstrcpy(pszFullFileNameBuffer, pszFullFileName);
    }
    else if (pszOtherFileName) {
        /*
         * Copy the other files name and add the proper extension.
         */
        lstrcpy(pszFullFileNameBuffer, pszOtherFullFileName);
        FileCat(pszFullFileNameBuffer,
                (FileType == FILE_INCLUDE) ? ids(IDS_DOTH) :
                ids(IDS_DOTRES), TRUE);
    }
    else {
        /*
         * Pick a default name in the current directory and
         * add the proper extension.
         */
        lstrcpy(szBuffer, ids(IDS_DEFSAVENAME));
        FileCat(szBuffer,
                (FileType == FILE_INCLUDE) ? ids(IDS_DOTH) :
                ids(IDS_DOTRES), TRUE);
        GetFullPathName(szBuffer, cchBuffer, pszFullFileNameBuffer, NULL);
    }
}

/************************************************************************
* WriteTheFile
*
* This function accepts a pointer to a resource buffer and a format
* type.  It writes the buffer out in the appropriate format.  It
* gets the file name from pszFile, adding the appropriate extension
* for the type of file.  The file is first written to a temporary file
* then the old file is removed and finally the new file is renamed.
*
* Arguments:
*   LPTSTR pszFile  - The name to save to.
*   INT fmt         - format to write the buffer out in,
*                     FILE_RES, FILE_INC or FILE_DLG.
*
* Returns:
*     TRUE => File successfully written.
*     FALSE => Failure in writing file.
*
************************************************************************/

STATICFN BOOL WriteTheFile(
    LPTSTR pszFile,
    INT fmt)
{
    TCHAR szTempFile[CCHMAXPATH]; /* Used for temporary filename            */
    TCHAR szSrcFile[CCHMAXPATH];  /* Source file with proper extension      */
    HANDLE hfWrite;
    HCURSOR hcurSave;
    BOOL fSuccess = FALSE;
    WORD idsExt;

    hcurSave = SetCursor(hcurWait);

    switch (fmt) {
        case FILE_RES:
            idsExt = IDS_DOTRES;
            break;

        case FILE_DLG:
            idsExt = IDS_DOTDLG;
            break;

        case FILE_INC:
            idsExt = IDS_DOTH;
            break;
    }

    /*
     * Append appropriate file name extension.
     */
    lstrcpy(szSrcFile, pszFile);
    FileCat(szSrcFile, ids(idsExt), fmt == FILE_DLG ? TRUE : FALSE);

    /*
     * Generate appropriate temporary file name in the same directory.
     * It is done in the same directory so that a simple rename can
     * be done later.
     */
    FormTempFileName(szSrcFile, szTempFile);

    if ((hfWrite = CreateFile(szTempFile, GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
            NULL)) == (HANDLE)-1)
        goto Exit;

    switch (fmt) {
        case FILE_RES:
            if (!WriteRes(hfWrite, szSrcFile))
                goto CloseAndExit;

            break;

        case FILE_DLG:
            if (!WriteDlg(hfWrite, szSrcFile))
                goto CloseAndExit;

            break;

        case FILE_INC:
            if (!WriteInc(hfWrite))
                goto CloseAndExit;

            break;
    }

    CloseHandle((HANDLE)hfWrite);
    DeleteFile(szSrcFile);
    if (!MoveFile(szTempFile, szSrcFile)) {
        DeleteFile(szTempFile);
        goto Exit;
    }

    fSuccess = TRUE;

    /*
     * If we just wrote to the include file, read it to get the new
     * file offsets, etc.
     */
    if (fmt == FILE_INC) {
        if (!OpenIncludeFile(szSrcFile))
            fSuccess = FALSE;
    }

Exit:
    SetCursor(hcurSave);
    return fSuccess;

CloseAndExit:
    CloseHandle(hfWrite);
    DeleteFile(szTempFile);
    SetCursor(hcurSave);
    return fSuccess;
}

/************************************************************************
* FormTempFileName
*
* This function forms a temporary file name in the provided string.
* The provided string is assumed to have been filled with a filename
* that includes a path.  The temp file will be created in the same
* directory as the file that is currently in the string.
*
* Arguments:
*   LPTSTR pszBaseName - The base name (a filename that includes a path).
*   LPTSTR pszBuffer   - Where to return the
*
************************************************************************/

STATICFN VOID FormTempFileName(
    LPTSTR pszBaseName,
    LPTSTR pszBuffer)
{
    TCHAR szBuffer[CCHMAXPATH];
    LPTSTR psz;

    /*
     * Cut the base file name down to just the path portion.
     */
    lstrcpy(szBuffer, pszBaseName);
    psz = FileInPath(szBuffer);
    psz--;
    *psz = TEXT('\0');

    /*
     * Create a temporary file in the same directory.
     */
    GetTempFileName(szBuffer, L"dlg", 0, pszBuffer);
}

/************************************************************************
* FileInPath
*
* This function takes a path and returns a pointer to the file name
* portion of it.  For instance, it will return a pointer to
* "abc.res" if it is given the following path: "c:\windows\abc.res".
*
* Arguments:
*   LPTSTR pszPath - Path to look through.
*
************************************************************************/

LPTSTR FileInPath(
    LPTSTR pszPath)
{
    LPTSTR psz;

    psz = pszPath + lstrlen(pszPath);
    while (psz > pszPath) {
        psz--;
        if (*psz == CHAR_BACKSLASH || *psz == CHAR_COLON) {
            psz++;
            break;
        }
    }

    return psz;
}

/************************************************************************
* FileCat
*
* This function puts the extension pchCat on the file spec pch.
* If fChop, this is done regardless of whether pch has an extension
* or not (replacing the old extension).  Otherwise, pchCat is added
* only if there is no extension on the spec pch.
*
* Arguments:
*     LPTSTR pch        - The file spec to "cat" the extension to.
*     LPTSTR pchCat     - The extension to "cat" on to pch,
*                         including the '.'
*
************************************************************************/

STATICFN VOID FileCat(
    LPTSTR pchName,
    LPTSTR pchCat,
    BOOL fChop)
{
    LPTSTR pch;

    pch = pchName + lstrlen(pchName);
    pch--;

    /* back up to '.' or '\\' */
    while (*pch != CHAR_DOT) {
        if (*pch == CHAR_BACKSLASH || pch <= pchName) {
            /* no extension, add one */
            lstrcat(pchName, pchCat);
            return;
        }

        pch--;
    }

    if (fChop)
        lstrcpy(pch, pchCat);
}

/************************************************************************
* ShowFileStatus
*
* This function displays the title of the Dialog Editor, along with
* the file names for the RES and H files with asterisks if they have
* changed.  It displays this information only if one of these items
* has changed or if fForce is TRUE.
*
* Arguments:
*   BOOL fForce - TRUE if the title should be updated even if the value
*                 of gfResChged or gfIncChged has not changed since the
*                 last call.  This function should be called with fForce
*                 equal to TRUE if it is known that one of the file names
*                 has just been changed.
*
************************************************************************/

VOID ShowFileStatus(
    BOOL fForce)
{
    static BOOL fResChgedSave = FALSE;
    static BOOL fIncChgedSave = FALSE;
    TCHAR szTitle[CCHTEXTMAX];

    if (gfResChged != fResChgedSave || gfIncChged != fIncChgedSave ||
            fForce) {
        lstrcpy(szTitle, ids(IDS_DLGEDIT));
        lstrcat(szTitle, L" - ");
        lstrcat(szTitle, pszResFile ? pszResFile : ids(IDS_UNTITLED));
        if (gfResChged)
            lstrcat(szTitle, L"*");

        lstrcat(szTitle, L", ");
        lstrcat(szTitle, pszIncludeFile ? pszIncludeFile : ids(IDS_UNTITLED));
        if (gfIncChged)
            lstrcat(szTitle, L"*");

        SetWindowText(ghwndMain, szTitle);

        fResChgedSave = gfResChged;
        fIncChgedSave = gfIncChged;
    }
}

/************************************************************************
* DifferentDirs
*
* This function returns TRUE if the given full paths are to files
* that are in different directories.
*
* Arguments:
*   LPTSTR pszPath1 - First path.
*   LPTSTR pszPath2 - Second path.
*
************************************************************************/

BOOL DifferentDirs(
    LPTSTR pszPath1,
    LPTSTR pszPath2)
{
    INT nLen1;
    INT nLen2;
    LPTSTR pszFile1;
    LPTSTR pszFile2;

    pszFile1 = FileInPath(pszPath1);
    pszFile2 = FileInPath(pszPath2);

    nLen1 = lstrlen(pszPath1) - lstrlen(pszFile1);
    nLen2 = lstrlen(pszPath2) - lstrlen(pszFile2);
    if (nLen1 != nLen2 || _wcsnicmp(pszPath1, pszPath2, nLen1) != 0)
        return TRUE;
    else
        return FALSE;
}

/************************************************************************
* HasPath
*
* This function returns TRUE if the given filespec includes a path
* specification.  It returns false if it is a filename without a
* path.
*
* A filespec is considered to have a path if a backslash character (\)
* is found in it.
*
* Arguments:
*   LPTSTR pszFileSpec - File spec to check.
*
************************************************************************/

BOOL HasPath(
    LPTSTR pszFileSpec)
{
    LPTSTR psz;

    for (psz = pszFileSpec; *psz; psz = CharNext(psz))
        if (*psz == CHAR_BACKSLASH)
            return TRUE;
    return FALSE;
}

/************************************************************************
* WriteDWordPad
*
* This function writes nulls to the specified file until it is
* dword aligned.  If the file is already dword aligned, nothing
* will be written.
*
* Arguments:
*   HANDLE hf    - The file to write to.
*   DWORD cbFile - Where the file pointer is at in the file.
*
* Returns:
*   TRUE if successful, FALSE otherwise.
*
************************************************************************/

BOOL WriteDWordPad(
    HANDLE hf,
    DWORD cbFile)
{
    static BYTE Buf[3] = {0, 0, 0};
    WORD cb;

    cb = (WORD)((4 - (((WORD)cbFile) & 3)) % 4);
    if (cb) {
        if (_lwrite((HFILE)hf, (LPSTR)Buf, cb) == -1)
            return FALSE;
    }

    return TRUE;
}


GLOBALS.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 Header *******************************
* Module Name: globals.c
*
* Contains global data for the dialog box editor.
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgextrn.h"           /* Including this verifies they are synched.*/
#include "dlgfuncs.h"
#include "dialogs.h"
#include "dlghelp.h"

HANDLE ghInst;                  /* Application instance handle.         */
HMENU ghMenuMain;               /* Main menu handle.                    */
PRESLINK gprlHead = NULL;       /* Head of the linked list of resources.*/
CURRENTDLG gcd;                 /* Describes the current dialog.        */
HPEN hpenDarkGray;              /* A dark gray pen.                     */
HANDLE ghAccTable;              /* The accelerator table handle.        */
INT gMenuSelected = 0;          /* Currently selected menu item.        */
HBITMAP ghbmDragHandle = NULL;  /* Handle for the drag handle bitmap.   */
HBITMAP ghbmDragHandle2 = NULL; /* Handle for hollow drag handle bitmap.*/
HDC ghDCMem = NULL;             /* Memory DC for drawing bitmaps.       */
INT gCurTool = W_NOTHING;       /* Currently selected tool.             */
PWINDOWCLASSDESC gpwcdCurTool = NULL; /* Describes current tool.        */
BOOL gfToolLocked = FALSE;      /* TRUE if a tool is locked down.       */
PCUSTLINK gpclHead = NULL;      /* Head of custom control linked list.  */

/*
 * When the dialog editor displays one of its own dialogs, this value
 * will contain the resource id of it.  It is zero if there is not a
 * dialog up.
 */
INT gidCurrentDlg = 0;

/*
 * Ordinal for the icon control to display in the dialog.  It will be
 * initialized to one of the editor's own icons.
 */
ORDINAL gordIcon;

/*
 * Bitmap handles for the up and down W_NOTHING (pointer) tool bitmaps.
 */
HBITMAP ghbmPointerToolUp = NULL;
HBITMAP ghbmPointerToolDown = NULL;

/*-- Initialized "Preferences" Data ------------------------------------*/

/*
 * Initialization data structure.  This describes each profile entry
 * that is contained in the initialization file.
 */
INIENTRY gaie[] = {
    { L"fHexMode",      &gfHexMode,         FALSE,              0 },
    { L"fTranslateMode",&gfTranslateMode,   FALSE,              0 },
    { L"fShowToolbox",  &gfShowToolbox,     TRUE,               0 },
    { L"fUseNewKeywords",&gfUseNewKeywords, TRUE,               0 },
    { L"cxGrid",        &gcxGrid,           DEFCXGRID,          0 },
    { L"cyGrid",        &gcyGrid,           DEFCYGRID,          0 },
    { L"xMargin",       &gxMargin,          DEFXMARGIN,         0 },
    { L"yMargin",       &gyMargin,          DEFYMARGIN,         0 },
    { L"xSpace",        &gxSpace,           DEFXSPACE,          0 },
    { L"ySpace",        &gySpace,           DEFYSPACE,          0 },
    { L"xMinPushSpace", &gxMinPushSpace,    DEFXMINPUSHSPACE,   0 },
    { L"xMaxPushSpace", &gxMaxPushSpace,    DEFXMAXPUSHSPACE,   0 },
    { L"yPushSpace",    &gyPushSpace,       DEFYPUSHSPACE,      0 },
    { NULL,             NULL,               0,                  0 }
};

BOOL gfHexMode;                 /* TRUE if in "hex" mode.               */
BOOL gfTranslateMode;           /* TRUE if in "translate" mode.         */
BOOL gfShowToolbox;             /* TRUE if Toolbox is to be shown.      */
BOOL gfUseNewKeywords;          /* FALSE to only use "CONTROL" keyword. */
INT gcxGrid;                    /* Current X grid.                      */
INT gcyGrid;                    /* Current Y grid.                      */
INT gxMargin;                   /* Top/bottom margin.                   */
INT gyMargin;                   /* Left/right margin.                   */
INT gxSpace;                    /* Horizontal control spacing.          */
INT gySpace;                    /* Vertical control spacing.            */
INT gxMinPushSpace;             /* Minimum horizontal button spacing.   */
INT gxMaxPushSpace;             /* Maximum horizontal button spacing.   */
INT gyPushSpace;                /* Vertical button spacing.             */

TCHAR szAppPos[] = L"AppPos";   /* App window's position keyname.       */
TCHAR szTBPos[] = L"TBPos";     /* Toolbox window's position keyname.   */
TCHAR szCustomDLL[] = L"CustomDLL";/* Section name for DLL cust. cntls. */

/*-- Sundry Handles.----------------------------------------------------*/
HWND hwndStatus = NULL;         /* Status ribbon window handle.         */
HWND ghwndToolbox = NULL;       /* Toolbox window handle.               */
HWND ghwndTestDlg = NULL;       /* Handle of the Test Mode dialog.      */
HWND ghwndMain = NULL;          /* Main application window.             */
HWND ghwndSubClient = NULL;     /* The "fake" client area.              */
HWND ghwndTrackOver = NULL;     /* Window being tracked over.           */

/*-- Some System constants.---------------------------------------------*/
INT gcxSysChar;                 /* Pixel width of system font char box. */
INT gcySysChar;                 /* Pixel height of system font char box.*/
INT gcyBorder;                  /* System height of a border.           */
INT gcxPreDragMax;              /* Max X mouse move during pre-drag.    */
INT gcyPreDragMax;              /* Max Y mouse move during pre-drag.    */
INT gmsecPreDrag;               /* The milliseconds that pre-drag lasts.*/
INT gcyPixelsPerInch;           /* Vertical pixels/inch of system.      */
INT gcyStatus;                  /* Saves height of the status window.   */

/*-- Some state variables.----------------------------------------------*/
INT gState = STATE_NORMAL;      /* Has the editor "state" or mode.      */
BOOL gfResChged = FALSE;        /* Tell if RES has changed              */
BOOL gfIncChged = FALSE;        /* Tell if include has changed          */
BOOL gfDlgChanged = FALSE;      /* TRUE if current dialog has changed.  */
INT gcSelected = 0;             /* Count of selected windows.           */
BOOL gfTestMode = FALSE;        /* TRUE if in "test" mode.              */
BOOL gfDisabled = FALSE;        /* TRUE if editing is disabled for now. */
BOOL gfEditingDlg = FALSE;      /* TRUE means a dlg is picked to edit.  */
BOOL gfDlgSelected = FALSE;     /* TRUE if the dialog has the selection.*/

/*
 * Contains the window rectangle, in window units, for the "client"
 * area for the currently chosen dialog being edited.  This rectangle
 * is relative to the dialog box window.  The xLeft and yBottom fields
 * contain the offset from the window origin of the dialog box to the
 * origin of the "client" area.
 */
RECT grcDlgClient;

/*
 * Contains a rectangle that surrounds all the existing controls.  This
 * is used during tracking of the dialog to limit the minimum size that
 * the dialog can be sized to.
 */
RECT grcMinDialog;

/*
 * Contains the offset from the origin of the currently selected
 * control to the mouse pointer.  This is updated when a control
 * is clicked on and is used for dragging calculations.
 */
POINT gptCursorOffset;

/*
 * Contains the rectangle that surrounds the selected control(s).  This
 * rectangle is only valid if there are selected controls.
 */
RECT grcSelected;

/*
 * Contains the rectangle that surrounds the control(s) that are being
 * copied.  This is also used during a clipboard paste operation.  In
 * that case, it contains the rectangle that surrounds the control(s)
 * as they are defined in the res image.
 */
RECT grcCopy;

/*
 * These contain the current location of the tracking rectangle when
 * dragging a control.  The values for grcTrackDU are in Dialog Units
 * (DU's) and the values in grcTrackWin are in window units.  The
 * grcTrackWin values will only be valid if gfTrackRectShown is TRUE;
 */
RECT grcTrackDU;                /* Track rect in dialog units.          */
RECT grcTrackWin;               /* Track rect in window units.          */
BOOL gfTrackRectShown = FALSE;  /* TRUE if track rect is visible.       */
HDC ghDCTrack;                  /* Clip DC used when tracking.          */

/*
 * Contains the current drag handle that is being tracked.  This will
 * be one of the DRAG_* constants.
 */
INT gHandleHit = DRAG_CENTER;

/*
 * Contains the overhang that is allowed during the current tracking
 * operation.  This is used by various routines during dragging so
 * that limiting the tracking to the dialog boundaries works properly.
 * In actuality, this is only non-zero when a combo box control is
 * being drapped or dragged.  It will be the height of the listbox
 * portion of the combo.  This is how combos are allowed to extend
 * below the bottom of the dialog.
 */
INT gnOverHang;                 /* Maximum overhang during the drag.    */

/*
 * This pointer is either NULL, or else it points to a dialog resource.
 * It is used when copying dialogs/controls, either with the Duplicate
 * command or pasting from the clipboard.
 */
PRES gpResCopy;                 /* Copy of dialog/controls.             */

/*-- CTYPE linked lists.------------------------------------------------*/
NPCTYPE npcHead = NULL;         /* Linked List of controls.             */
INT cWindows = 0;               /* Number of Controls in pctype list.   */

/*
 * Pointer to the CTYPE structure for the currently selected control.
 * This will be NULL if there is no control selected.
 */
NPCTYPE gnpcSel = NULL;

/*-- Cursors used by editor.--------------------------------------------*/
HCURSOR hcurArrow = NULL;       /* Normal arrow cursor.                 */
HCURSOR hcurWait = NULL;        /* User Wait cursor, Hourglass.         */
HCURSOR hcurOutSel = NULL;      /* Outline selection cursor.            */
HCURSOR hcurMove = NULL;        /* System "Move" cursor.                */
HCURSOR hcurInsert = NULL;      /* Insert cursor for Order/Group dialog.*/
HCURSOR hcurDropTool = NULL;    /* Cursor for when dropping new ctrls.  */
HCURSOR hcurSizeNESW = NULL;    /* System sizing "NESW" cursor.         */
HCURSOR hcurSizeNS = NULL;      /* System sizing "NS" cursor.           */
HCURSOR hcurSizeNWSE = NULL;    /* System sizing "NWSE" cursor.         */
HCURSOR hcurSizeWE = NULL;      /* System sizing "WE" cursor.           */
HBITMAP hbmTabStop = NULL;      /* Bitmap for showing WS_TABSTOP style. */
HBITMAP hbmTabStopSel = NULL;   /* Selected version of the above.       */

/*-- Window Class Strings.----------------------------------------------*/
TCHAR szMainClass[] = L"DlgEdit";/* Application window class.           */
TCHAR szDragClass[] = L"Drag";  /* Class for drag handle windows.       */
TCHAR szSubClientClass[] =
    L"SubClient";               /* Short client area window class.      */
TCHAR szToolboxClass[] =
    L"Toolbox";                 /* Toolbox window class.                */
TCHAR szToolBtnClass[] =
    L"ToolBtn";                 /* Toolbox button window class.         */
TCHAR szCustomClass[] =
    L"DlgCustom";               /* Our custom emulator class.           */

/*-- Miscellaneous variables.-------------------------------------------*/
UINT fmtDlg;                    /* The Dialog Clipboard format          */
TCHAR szEmpty[] = L"";          /* An empty string.                     */
HHOOK ghhkMsgFilter;            /* Hook handle for message filter func. */

/*-- Buffers.-----------------------------------------------------------*/
TCHAR szFullResFile[CCHMAXPATH];    /* Full resource file name          */
LPTSTR pszResFile;                  /* Points to resource file name     */
TCHAR szFullIncludeFile[CCHMAXPATH];/* Full include file name           */
LPTSTR pszIncludeFile;              /* Points to include file name      */
TCHAR gszHelpFile[CCHMAXPATH];      /* Path to the help file.           */

/*
 * Write buffer and index into it.  This buffer is used by several
 * sections to write out the different files.  Note that only one
 * file can be written out at a time using these globals.
 */
TCHAR gachWriteBuffer[CCHFILEBUFFER];/* Buffer for written file data.   */
INT cbWritePos;                     /* Pointer into gachWriteBuffer.    */

/*-- Include Data.------------------------------------------------------*/
NPLABEL plInclude = NULL;       /* Pointer to Include data              */
NPLABEL plDelInclude = NULL;    /* Pointer to deleted includes          */

/*
 * Describes each window class.  Indexed by the W_ defined constants.
 * The define CCONTROLS needs to be updated if controls are added or
 * removed from this array.  Note that CCONTROLS does NOT count the
 * W_DIALOG type as a control, however.
 */
WINDOWCLASSDESC awcd[] = {
    /*
     * W_TEXT
     */
    {
        W_TEXT,
        WS_CHILD | WS_GROUP | WS_VISIBLE | SS_LEFT,
        WS_DISABLED,
        0,
        0,
        20, 8,
        IC_STATIC, NULL,
        FALSE, FALSE, TRUE, TRUE, TRUE,
        DID_TEXTSTYLES, (WNDPROC)GenericStylesDlgProc,
        HELPID_TEXTSTYLES, IDS_DEFTXTTEXT, NULL, NULL,
        IDBM_CTTEXT, NULL, NULL,
        IDBM_TUTEXT, NULL, IDBM_TDTEXT, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_EDIT
     */
    {
        W_EDIT,
        WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_LEFT |
        ES_AUTOHSCROLL,
        WS_DISABLED,
        0,
        0,
        32, 12,
        IC_EDIT, NULL,
        FALSE, FALSE, FALSE, TRUE, FALSE,
        DID_EDITSTYLES, (WNDPROC)EditStylesDlgProc,
        HELPID_EDITSTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTEDIT, NULL, NULL,
        IDBM_TUEDIT, NULL, IDBM_TDEDIT, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_GROUPBOX
     */
    {
        W_GROUPBOX,
        WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
        WS_DISABLED,
        0,
        0,
        48, 40,
        IC_BUTTON, NULL,
        FALSE, FALSE, TRUE, TRUE, FALSE,
        DID_GROUPBOXSTYLES, (WNDPROC)GenericStylesDlgProc,
        HELPID_GROUPBOXSTYLES, IDS_DEFTXTGROUP, NULL, NULL,
        IDBM_CTGROUP, NULL, NULL,
        IDBM_TUGROUP, NULL, IDBM_TDGROUP, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_PUSHBUTTON
     */
    {
        W_PUSHBUTTON,
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
        WS_DISABLED,
        0,
        0,
        40, 14,
        IC_BUTTON, NULL,
        FALSE, FALSE, TRUE, TRUE, TRUE,
        DID_PUSHBUTTONSTYLES, (WNDPROC)PushButtonStylesDlgProc,
        HELPID_PUSHBUTTONSTYLES, IDS_DEFTXTPUSHBUTTON, NULL, NULL,
        IDBM_CTPUSH, NULL, NULL,
        IDBM_TUPUSH, NULL, IDBM_TDPUSH, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_CHECKBOX
     */
    {
        W_CHECKBOX,
        WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX | WS_TABSTOP,
        WS_DISABLED,
        0,
        0,
        40, 10,
        IC_BUTTON, NULL,
        FALSE, FALSE, TRUE, TRUE, TRUE,
        DID_CHECKBOXSTYLES, (WNDPROC)CheckBoxStylesDlgProc,
        HELPID_CHECKBOXSTYLES, IDS_DEFTXTCHECKBOX, NULL, NULL,
        IDBM_CTCHECK, NULL, NULL,
        IDBM_TUCHECK, NULL, IDBM_TDCHECK, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_RADIOBUTTON
     */
    {
        W_RADIOBUTTON,
        WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
        WS_DISABLED,
        0,
        0,
        39, 10,
        IC_BUTTON, NULL,
        FALSE, FALSE, TRUE, TRUE, TRUE,
        DID_RADIOBUTTONSTYLES, (WNDPROC)RadioButtonStylesDlgProc,
        HELPID_RADIOBUTTONSTYLES, IDS_DEFTXTRADIOBUTTON, NULL, NULL,
        IDBM_CTRADIO, NULL, NULL,
        IDBM_TURADIO, NULL, IDBM_TDRADIO, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_COMBOBOX
     */
    {
        W_COMBOBOX,
        WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | CBS_DROPDOWN |
        CBS_SORT,
        WS_DISABLED | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE |
        CBS_HASSTRINGS,
        CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE | CBS_HASSTRINGS,
        0,
        48, 35,
        IC_COMBOBOX, NULL,
        FALSE, FALSE, FALSE, TRUE, FALSE,
        DID_COMBOBOXSTYLES, (WNDPROC)ComboBoxStylesDlgProc,
        HELPID_COMBOBOXSTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTCOMBO, NULL, NULL,
        IDBM_TUCOMBO, NULL, IDBM_TDCOMBO, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_LISTBOX
     */
    {
        W_LISTBOX,
        WS_CHILD | WS_VISIBLE | LBS_STANDARD | WS_TABSTOP,
        WS_DISABLED | LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE |
        LBS_HASSTRINGS | LBS_NODATA,
        LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE | LBS_HASSTRINGS |
        LBS_NODATA,
        0,
        48, 40,
        IC_LISTBOX, NULL,
        FALSE, FALSE, FALSE, TRUE, FALSE,
        DID_LISTBOXSTYLES, (WNDPROC)ListBoxStylesDlgProc,
        HELPID_LISTBOXSTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTLIST, NULL, NULL,
        IDBM_TULIST, NULL, IDBM_TDLIST, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_HORZSCROLL
     */
    {
        W_HORZSCROLL,
        WS_CHILD | WS_VISIBLE | SBS_HORZ,
        WS_DISABLED,
        0,
        0,
        48, 0,
        IC_SCROLLBAR, NULL,
        FALSE, FALSE, FALSE, TRUE, FALSE,
        DID_HORZSCROLLSTYLES, (WNDPROC)GenericStylesDlgProc,
        HELPID_HORZSCROLLSTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTHSCROL, NULL, NULL,
        IDBM_TUHSCROL, NULL, IDBM_TDHSCROL, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_VERTSCROLL
     */
    {
        W_VERTSCROLL,
        WS_CHILD | WS_VISIBLE | SBS_VERT,
        WS_DISABLED,
        0,
        0,
        0, 40,
        IC_SCROLLBAR, NULL,
        FALSE, FALSE, FALSE, TRUE, FALSE,
        DID_VERTSCROLLSTYLES, (WNDPROC)GenericStylesDlgProc,
        HELPID_VERTSCROLLSTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTVSCROL, NULL, NULL,
        IDBM_TUVSCROL, NULL, IDBM_TDVSCROL, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_FRAME
     */
    {
        W_FRAME,
        WS_CHILD | WS_VISIBLE | SS_BLACKFRAME,
        WS_DISABLED,
        0,
        0,
        20, 16,
        IC_STATIC, NULL,
        FALSE, FALSE, FALSE, TRUE, FALSE,
        DID_FRAMESTYLES, (WNDPROC)GenericStylesDlgProc,
        HELPID_FRAMESTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTFRAME, NULL, NULL,
        IDBM_TUFRAME, NULL, IDBM_TDFRAME, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_RECT
     */
    {
        W_RECT,
        WS_CHILD | WS_VISIBLE | SS_BLACKRECT,
        WS_DISABLED,
        0,
        0,
        20, 16,
        IC_STATIC, NULL,
        FALSE, FALSE, FALSE, TRUE, FALSE,
        DID_RECTSTYLES, (WNDPROC)GenericStylesDlgProc,
        HELPID_RECTSTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTRECT, NULL, NULL,
        IDBM_TURECT, NULL, IDBM_TDRECT, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_ICON
     */
    {
        W_ICON,
        WS_CHILD | WS_VISIBLE | SS_ICON,
        WS_DISABLED,
        0,
        0,
        0, 0,
        IC_STATIC, NULL,
        FALSE, FALSE, TRUE, FALSE, FALSE,
        DID_ICONSTYLES, (WNDPROC)GenericStylesDlgProc,
        HELPID_ICONSTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTICON, NULL, NULL,
        IDBM_TUICON, NULL, IDBM_TDICON, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_CUSTOM
     *
     * For Custom controls, we do not allow our emulator control
     * to be created with any other styles than the default ones
     * (WS_CHILD and WS_VISIBLE), but whatever styles the user
     * specifies are written out to the .res and .dlg files,
     * of course.
     */
    {
        W_CUSTOM,
        WS_CHILD | WS_VISIBLE,
        WS_DISABLED,
        0,
        0,
        40, 14,
        IC_CUSTOM, NULL,
        TRUE, FALSE, TRUE, TRUE, FALSE,
        DID_CUSTOMSTYLES, (WNDPROC)CustomStylesDlgProc,
        HELPID_CUSTOMSTYLES, IDS_NULL, NULL, NULL,
        IDBM_CTCUSTOM, NULL, NULL,
        IDBM_TUCUSTOM, NULL, IDBM_TDCUSTOM, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    },
    /*
     * W_DIALOG
     */
    {
        W_DIALOG,
        WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME | WS_POPUP |
        DS_SETFONT,
        WS_DISABLED | DS_SYSMODAL | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
        WS_CHILD,
        DS_SYSMODAL,
        0,
        160, 100,
        IC_DIALOG, NULL,
        FALSE, FALSE, TRUE, TRUE, FALSE,
        DID_DIALOGSTYLES, (WNDPROC)DialogStylesDlgProc,
        HELPID_DIALOGSTYLES, IDS_DEFTXTDIALOG, NULL, NULL,
        0, NULL, NULL,
        0, NULL, 0, NULL,
        NULL, 0, NULL, NULL, NULL, 0
    }
};

/*
 * This table maps the BS_* style of a button control into its
 * appropriate W_* type that is used internally by the editor.
 * This table assumes that any value used to index into it is
 * masked by BS_ALL.
 */
INT rgmpiClsBtnType[] = {
    W_PUSHBUTTON,               /* BS_PUSHBUTTON                        */
    W_PUSHBUTTON,               /* BS_DEFPUSHBUTTON                     */
    W_CHECKBOX,                 /* BS_CHECKBOX                          */
    W_CHECKBOX,                 /* BS_AUTOCHECKBOX                      */
    W_RADIOBUTTON,              /* BS_RADIOBUTTON                       */
    W_CHECKBOX,                 /* BS_3STATE                            */
    W_CHECKBOX,                 /* BS_AUTO3STATE                        */
    W_GROUPBOX,                 /* BS_GROUPBOX                          */
    W_PUSHBUTTON,               /* BS_USERBUTTON                        */
    W_RADIOBUTTON,              /* BS_AUTORADIOBUTTON                   */
    W_PUSHBUTTON,               /* BS_PUSHBOX                           */
    W_PUSHBUTTON                /* BS_OWNERDRAW                         */
};

/* Map low word of Static Control Style to static type. */
/*
 * This table maps the SS_* style of a static control into its
 * appropriate W_* type that is used internally by the editor.
 * This table assumes that any value used to index into it is
 * masked by SS_ALL.
 */
INT rgmpiClsStcType[] = {
    W_TEXT,                     /* SS_LEFT                              */
    W_TEXT,                     /* SS_CENTER                            */
    W_TEXT,                     /* SS_RIGHT                             */
    W_ICON,                     /* SS_ICON                              */
    W_RECT,                     /* SS_BLACKRECT                         */
    W_RECT,                     /* SS_GREYRECT                          */
    W_RECT,                     /* SS_WHITERECT                         */
    W_FRAME,                    /* SS_BLACKFRAME                        */
    W_FRAME,                    /* SS_GRAYFRAME                         */
    W_FRAME,                    /* SS_WHITEFRAME                        */
    W_TEXT,                     /* SS_USERITEM                          */
    W_TEXT,                     /* SS_SIMPLE                            */
    W_TEXT                      /* SS_LEFTNOWORDWRAP                    */
};

/*
 * Following are the tables with the predefined RC keywords for each
 * class (IC_*).  These tables describe each keyword other than the
 * generic "CONTROL" keyword that is possible to use within a dialog
 * template.  The style describes the minimum bits that must be set
 * to define the keyword.  The mask allows a style to be specified
 * that must have certain bits OFF for a match to occur.  The default
 * styles flag specifies style bits that are implicitly turned on
 * when this keyword is specified in the dialog template in the .DLG
 * file.  These bits are checked against the style flag of the control
 * that we are trying to match and if any of these default bits are
 * NOT set for that control, we need to specify them in the .DLG file
 * with a "NOT" in front of them to explicitly turn them off.
 *
 * The "Has Text" flag is set to FALSE in those cases where the syntax
 * for the keyword does NOT include a text field, like "ICON" and
 * "LISTBOX".
 */

/*
 * Array of the predefined RC keywords for Button styles.
 */
static RCKEYWORD arckwdButton[] = {
    /*
     * RADIOBUTTON
     */
    {
        BS_RADIOBUTTON,
        BS_ALL,
        WS_CHILD | WS_VISIBLE,
        IDS_KEYRADIOBUTTON, TRUE
    },
    /*
     * CHECKBOX
     */
    {
        BS_CHECKBOX,
        BS_ALL,
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        IDS_KEYCHECKBOX, TRUE
    },
    /*
     * DEFPUSHBUTTON
     */
    {
        BS_DEFPUSHBUTTON,
        BS_ALL,
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        IDS_KEYDEFPUSHBUTTON, TRUE
    },
    /*
     * PUSHBUTTON
     */
    {
        BS_PUSHBUTTON,
        BS_ALL,
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        IDS_KEYPUSHBUTTON, TRUE
    },
    /*
     * GROUPBOX
     */
    {
        BS_GROUPBOX,
        BS_ALL,
        WS_CHILD | WS_VISIBLE,
        IDS_KEYGROUPBOX, TRUE
    },
    /*
     * AUTO3STATE
     */
    {
        BS_AUTO3STATE,
        BS_ALL,
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        IDS_KEYAUTO3STATE, TRUE
    },
    /*
     * AUTOCHECKBOX
     */
    {
        BS_AUTOCHECKBOX,
        BS_ALL,
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        IDS_KEYAUTOCHECKBOX, TRUE
    },
    /*
     * AUTORADIOBUTTON
     */
    {
        BS_AUTORADIOBUTTON,
        BS_ALL,
        WS_CHILD | WS_VISIBLE,
        IDS_KEYAUTORADIOBUTTON, TRUE
    },
    /*
     * STATE3
     */
    {
        BS_3STATE,
        BS_ALL,
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        IDS_KEYSTATE3, TRUE
    },
    /*
     * USERBUTTON
     */
    {
        BS_USERBUTTON,
        BS_ALL,
        WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        IDS_KEYUSERBUTTON, TRUE
    }
};

/*
 * Array of the predefined RC keywords for Edit control styles.
 */
static RCKEYWORD arckwdEdit[] = {
    /*
     * EDIT
     */
    {
        0L,
        0L,
        WS_CHILD | WS_VISIBLE | ES_LEFT | WS_TABSTOP | WS_BORDER,
        IDS_KEYEDITTEXT, FALSE
    }
};

/*
 * Array of the predefined RC keywords for Static styles.
 */
static RCKEYWORD arckwdStatic[] = {
    /*
     * ICON
     */
    {
        SS_ICON,
        SS_ALL,
        WS_CHILD | WS_VISIBLE,
        IDS_KEYICON, TRUE
    },
    /*
     * RTEXT
     */
    {
        SS_RIGHT,
        SS_ALL,
        WS_CHILD | WS_GROUP | WS_VISIBLE,
        IDS_KEYRTEXT, TRUE
    },
    /*
     * CTEXT
     */
    {
        SS_CENTER,
        SS_ALL,
        WS_CHILD | WS_GROUP | WS_VISIBLE,
        IDS_KEYCTEXT, TRUE
    },
    /*
     * LTEXT
     */
    {
        SS_LEFT,
        SS_ALL,
        WS_CHILD | WS_GROUP | WS_VISIBLE,
        IDS_KEYLTEXT, TRUE
    }
};

/*
 * Array of the predefined RC keywords for ListBox styles.
 */
static RCKEYWORD arckwdLB[] = {
    /*
     * LISTBOX
     */
    {
        0L,
        0L,
        WS_CHILD | WS_VISIBLE | WS_BORDER | LBS_NOTIFY,
        IDS_KEYLISTBOX, FALSE
    }
};

/*
 * Array of the predefined RC keywords for ComboBox styles.
 */
static RCKEYWORD arckwdComboBox[] = {
    /*
     * COMBOBOX
     */
    {
        0L,
        0L,
        WS_CHILD | WS_VISIBLE,
        IDS_KEYCOMBOBOX, FALSE
    }
};

/*
 * Array of the predefined RC keywords for ScrollBar styles.
 */
static RCKEYWORD arckwdScrollBar[] = {
    /*
     * SCROLLBAR
     */
    {
        0L,
        0L,
        WS_CHILD | WS_VISIBLE,
        IDS_KEYSCROLLBAR, FALSE
    }
};

/*
 * Following are the Class Styles structures.  These tables define the
 * styles for the different window classes (IC_*).  The first element
 * is the style flag, followed by an optional style mask.  If the style
 * mask is zero, the style flag becomes the mask also.  This is often good
 * enough, but there are cases where the style depends on certain bits
 * being off, as well as certain bits being on, to definitively identify
 * a certain style.  An extreme example of this is the BS_PUSHBUTTON
 * style, which is actually zero (no bits are on).  The mask for this
 * style had to be set to include all four of the lower bits, or all
 * buttons would be incorrectly figured to have the BS_PUSHBUTTON style.
 * With the proper mask, only styles that have all four lower bits
 * OFF will be considered to have the BS_PUSHBUTTON style.
 */

/*
 * Button styles.
 */
static CLASSSTYLE acsButton[] = {

{BS_PUSHBUTTON,         BS_ALL,     DID_BS_PUSHBUTTON           },
    {BS_DEFPUSHBUTTON,      BS_ALL,     DID_BS_DEFPUSHBUTTON        },
    {BS_CHECKBOX,           BS_ALL,     0                           },
    {BS_AUTOCHECKBOX,       BS_ALL,     0                           },
    {BS_RADIOBUTTON,        BS_ALL,     0                           },
    {BS_3STATE,             BS_ALL,     0                           },
    {BS_AUTO3STATE,         BS_ALL,     0                           },
    {BS_GROUPBOX,           BS_ALL,     0                           },
    {BS_USERBUTTON,         BS_ALL,     0                           },
    {BS_AUTORADIOBUTTON,    BS_ALL,     0                           },
    {BS_PUSHBOX,            BS_ALL,     0                           },
    {BS_OWNERDRAW,          BS_ALL,     DID_BS_OWNERDRAW            },
    {BS_LEFTTEXT,           0,          DID_BS_LEFTTEXT             }
};

/*
 * Scroll bar styles.
 */
static CLASSSTYLE acsSB[] = {
    {SBS_HORZ,              SBS_ALL,    0                           },
    {SBS_VERT,              SBS_ALL,    0                           },
    {SBS_TOPALIGN,          0,          0                           },
    {SBS_LEFTALIGN,         0,          0                           },
    {SBS_BOTTOMALIGN,       0,          0                           },
    {SBS_RIGHTALIGN,        0,          0                           },
    {SBS_SIZEBOXTOPLEFTALIGN, 0,        0                           },
    {SBS_SIZEBOXBOTTOMRIGHTALIGN, 0,    0                           },
    {SBS_SIZEBOX,           0,          0                           }
};

/*
 * Entry field styles.
 */
static CLASSSTYLE acsEdit[] = {
    {ES_LEFT,               ES_ALIGN,   DID_ES_LEFT                 },
    {ES_CENTER,             ES_ALIGN,   DID_ES_CENTER               },
    {ES_RIGHT,              ES_ALIGN,   DID_ES_RIGHT                },
    {ES_MULTILINE,          0,          DID_ES_MULTILINE            },
    {ES_UPPERCASE,          0,          DID_ES_UPPERCASE            },
    {ES_LOWERCASE,          0,          DID_ES_LOWERCASE            },
    {ES_PASSWORD,           0,          DID_ES_PASSWORD             },
    {ES_AUTOVSCROLL,        0,          DID_ES_AUTOVSCROLL          },
    {ES_AUTOHSCROLL,        0,          DID_ES_AUTOHSCROLL          },
    {ES_NOHIDESEL,          0,          DID_ES_NOHIDESEL            },
    {ES_OEMCONVERT,         0,          DID_ES_OEMCONVERT           },
    {ES_READONLY,           0,          DID_ES_READONLY             }
};

/*
 * Static styles.
 */
static CLASSSTYLE acsStatic[] = {
    {SS_LEFT,               SS_ALL,     DID_SS_LEFT                 },
    {SS_CENTER,             SS_ALL,     DID_SS_CENTER               },
    {SS_RIGHT,              SS_ALL,     DID_SS_RIGHT                },
    {SS_ICON,               SS_ALL,     0                           },
    {SS_BLACKRECT,          SS_ALL,     DID_SS_BLACKRECT            },
    {SS_GRAYRECT,           SS_ALL,     DID_SS_GRAYRECT             },
    {SS_WHITERECT,          SS_ALL,     DID_SS_WHITERECT            },
    {SS_BLACKFRAME,         SS_ALL,     DID_SS_BLACKFRAME           },
    {SS_GRAYFRAME,          SS_ALL,     DID_SS_GRAYFRAME            },
    {SS_WHITEFRAME,         SS_ALL,     DID_SS_WHITEFRAME           },
    {SS_USERITEM,           SS_ALL,     DID_SS_USERITEM             },
    {SS_SIMPLE,             SS_ALL,     DID_SS_SIMPLE               },
    {SS_LEFTNOWORDWRAP,     SS_ALL,     DID_SS_LEFTNOWORDWRAP       },
    {SS_NOPREFIX,           0,          DID_SS_NOPREFIX             }
};

/*
 * List box styles.
 */
static CLASSSTYLE acsLB[] = {
    {LBS_NOTIFY,            0,          DID_LBS_NOTIFY              },
    {LBS_SORT,              0,          DID_LBS_SORT                },
    {LBS_NOREDRAW,          0,          DID_LBS_NOREDRAW            },
    {LBS_MULTIPLESEL,       0,          DID_LBS_MULTIPLESEL         },
    {LBS_OWNERDRAWFIXED,    0,          DID_LBS_OWNERDRAWFIXED      },
    {LBS_OWNERDRAWVARIABLE, 0,          DID_LBS_OWNERDRAWVARIABLE   },
    {LBS_HASSTRINGS,        0,          DID_LBS_HASSTRINGS          },
    {LBS_USETABSTOPS,       0,          DID_LBS_USETABSTOPS         },
    {LBS_NOINTEGRALHEIGHT,  0,          DID_LBS_NOINTEGRALHEIGHT    },
    {LBS_MULTICOLUMN,       0,          DID_LBS_MULTICOLUMN         },
    {LBS_WANTKEYBOARDINPUT, 0,          DID_LBS_WANTKEYBOARDINPUT   },
    {LBS_EXTENDEDSEL,       0,          DID_LBS_EXTENDEDSEL         },
    {LBS_DISABLENOSCROLL,   0,          DID_LBS_DISABLENOSCROLL     },
    {LBS_NODATA,            0,          DID_LBS_NODATA              }
};

/*
 * Combo Box styles.
 */
static CLASSSTYLE acsComboBox[] = {
    {CBS_SIMPLE,            CBS_ALL,    DID_CBS_SIMPLE              },
    {CBS_DROPDOWN,          CBS_ALL,    DID_CBS_DROPDOWN            },
    {CBS_DROPDOWNLIST,      CBS_ALL,    DID_CBS_DROPDOWNLIST        },
    {CBS_OWNERDRAWFIXED,    0,          DID_CBS_OWNERDRAWFIXED      },
    {CBS_OWNERDRAWVARIABLE, 0,          DID_CBS_OWNERDRAWVARIABLE   },
    {CBS_AUTOHSCROLL,       0,          DID_CBS_AUTOHSCROLL         },
    {CBS_OEMCONVERT,        0,          DID_CBS_OEMCONVERT          },
    {CBS_SORT,              0,          DID_CBS_SORT                },
    {CBS_HASSTRINGS,        0,          DID_CBS_HASSTRINGS          },
    {CBS_NOINTEGRALHEIGHT,  0,          DID_CBS_NOINTEGRALHEIGHT    },
    {CBS_DISABLENOSCROLL,   0,          DID_CBS_DISABLENOSCROLL     }
};

/*
 * Dialog styles.
 */
static CLASSSTYLE acsDialog[] = {
    {DS_ABSALIGN,           0,          DID_DS_ABSALIGN             },
    {DS_SYSMODAL,           0,          DID_DS_SYSMODAL             },
    {DS_LOCALEDIT,          0,          DID_DS_LOCALEDIT            },
    {DS_SETFONT,            0,          0                           },
    {DS_MODALFRAME,         0,          DID_DS_MODALFRAME           },
    {DS_NOIDLEMSG,          0,          DID_DS_NOIDLEMSG            },
    /*
     * The following two styles are the same bits as WS_GROUP and
     * WS_TABSTOP, so they must be in this table and there has
     * to be special case code for writing the appropriate string
     * out when writing dialog styles.
     */
    {WS_MINIMIZEBOX,        0,          DID_WS_MINIMIZEBOX          },
    {WS_MAXIMIZEBOX,        0,          DID_WS_MAXIMIZEBOX          }
};

/*
 * Window styles.
 */
static CLASSSTYLE acsWindow[] = {
    {WS_POPUP,              0,          DID_WS_POPUP                },
    {WS_CHILD,              0,          DID_WS_CHILD                },
    {WS_MINIMIZE,           0,          DID_WS_MINIMIZE             },
    {WS_VISIBLE,            0,          DID_WS_VISIBLE              },
    {WS_DISABLED,           0,          DID_WS_DISABLED             },
    {WS_CLIPSIBLINGS,       0,          DID_WS_CLIPSIBLINGS         },
    {WS_CLIPCHILDREN,       0,          DID_WS_CLIPCHILDREN         },
    {WS_MAXIMIZE,           0,          DID_WS_MAXIMIZE             },
    {WS_CAPTION,            WS_CAPTIONALL, DID_WS_CAPTION           },
    {WS_BORDER,             WS_CAPTIONALL, DID_WS_BORDER            },
    {WS_DLGFRAME,           WS_CAPTIONALL, DID_WS_DLGFRAME          },
    {WS_VSCROLL,            0,          DID_WS_VSCROLL              },
    {WS_HSCROLL,            0,          DID_WS_HSCROLL              },
    {WS_SYSMENU,            0,          DID_WS_SYSMENU              },
    {WS_THICKFRAME,         0,          DID_WS_THICKFRAME           },
    {WS_GROUP,              0,          DID_WS_GROUP                },
    {WS_TABSTOP,            0,          DID_WS_TABSTOP              }
};

/*
 * Resource Flags styles.
 */
static CLASSSTYLE acsResFlags[] = {
    {MMF_MOVEABLE,          0,          DID_MMF_MOVEABLE            },
    {MMF_PURE,              0,          DID_MMF_PURE                },
    {MMF_PRELOAD,           0,          DID_MMF_PRELOAD             },
    {MMF_DISCARDABLE,       0,          DID_MMF_DISCARDABLE         }
};

/*
 * Extended Styles.
 */
static CLASSSTYLE acsExStyle[] = {
    {WS_EX_DLGMODALFRAME,   0,          0                           },
    {0x0002 /*WS_EX_DRAGOBJECT*/, 0,    0                           },
    {WS_EX_NOPARENTNOTIFY,  0,          0                           },
    {WS_EX_TOPMOST,         0,          0                           },
    {WS_EX_ACCEPTFILES,     0,          0                           },
    {WS_EX_TRANSPARENT,     0,          0                           }
};

/*
 * Array of class style description structures.  These are indexed by
 * the IC_* constants and describe each class.  They contain pointers
 * to both the class styles array and the predefined keywords array
 * for each class.
 *
 * The last few entries are included in the table for convenience,
 * and are used to describe things like the various window (WS_*, WS_EX_*)
 * and resource memory flags (MMF_*) styles, although they don't exactly
 * map to an IC_* style that a control will have.
 */
CLASSSTYLEDESC acsd[] = {
    /*
     * IC_BUTTON
     */
    {
        IDS_WCBUTTON,
        acsButton, sizeof(acsButton) / sizeof(CLASSSTYLE), IDS_IC_BUTTON,
        arckwdButton, sizeof(arckwdButton) / sizeof(RCKEYWORD),
        ORDID_BUTTONCLASS
    },
    /*
     * IC_SCROLLBAR
     */
    {
        IDS_WCSCROLLBAR,
        acsSB, sizeof(acsSB) / sizeof(CLASSSTYLE), IDS_IC_SCROLLBAR,
        arckwdScrollBar, sizeof(arckwdScrollBar) / sizeof(RCKEYWORD),
        ORDID_SCROLLBARCLASS
    },
    /*
     * IC_EDIT
     */
    {
        IDS_WCEDIT,
        acsEdit, sizeof(acsEdit) / sizeof(CLASSSTYLE), IDS_IC_EDIT,
        arckwdEdit, sizeof(arckwdEdit) / sizeof(RCKEYWORD),
        ORDID_EDITCLASS
    },
    /*
     * IC_STATIC
     */
    {
        IDS_WCSTATIC,
        acsStatic, sizeof(acsStatic) / sizeof(CLASSSTYLE), IDS_IC_STATIC,
        arckwdStatic, sizeof(arckwdStatic) / sizeof(RCKEYWORD),
        ORDID_STATICCLASS
    },
    /*
     * IC_LISTBOX
     */
    {
        IDS_WCLISTBOX,
        acsLB, sizeof(acsLB) / sizeof(CLASSSTYLE), IDS_IC_LISTBOX,
        arckwdLB, sizeof(arckwdLB) / sizeof(RCKEYWORD),
        ORDID_LISTBOXCLASS
    },
    /*
     * IC_COMBOBOX
     */
    {
        IDS_WCCOMBOBOX,
        acsComboBox, sizeof(acsComboBox) / sizeof(CLASSSTYLE), IDS_IC_COMBOBOX,
        arckwdComboBox, sizeof(arckwdComboBox) / sizeof(RCKEYWORD),
        ORDID_COMBOBOXCLASS
    },
    /*
     * IC_CUSTOM
     */
    {
        IDS_WCCUSTOM,
        NULL, 0, 0,
        NULL, 0,
        0
    },
    /*
     * IC_DIALOG
     */
    {
        IDS_WCDIALOG,
        acsDialog, sizeof(acsDialog) / sizeof(CLASSSTYLE), IDS_IC_DIALOG,
        NULL, 0,
        0
    },
    /*
     * IC_WINDOW
     */
    {
        IDS_NULL,
        acsWindow, sizeof(acsWindow) / sizeof(CLASSSTYLE), IDS_IC_WINDOW,
        NULL, 0,
        0
    },
    /*
     * IC_RESFLAGS
     */
    {
        IDS_NULL,
        acsResFlags, sizeof(acsResFlags) / sizeof(CLASSSTYLE), 0,
        NULL, 0,
        0
    },
    /*
     * IC_EXSTYLE
     */
    {
        IDS_NULL,
        acsExStyle, sizeof(acsExStyle) / sizeof(CLASSSTYLE), IDS_IC_EXSTYLE,
        NULL, 0,
        0
    }
};

/*
 * Message box messages, for the Message() function.
 */
MESSAGEDATA gamdMessages[] = {
    { IDS_DELETEDIALOG,     MB_YESNO | MB_ICONEXCLAMATION       },
    { IDS_OUTOFMEMORY,      MB_OK | MB_ICONHAND                 },
    { IDS_CANTCREATE,       MB_OK | MB_ICONEXCLAMATION          },
    { IDS_SYMNOCHANGE,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_IDSYMMISMATCH,    MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CLOSING,          MB_YESNOCANCEL | MB_ICONEXCLAMATION },
    { IDS_BADRESFILE,       MB_OK | MB_ICONEXCLAMATION          },
    { IDS_INCLCLOSING,      MB_YESNOCANCEL | MB_ICONEXCLAMATION },
    { IDS_SYMEXISTS,        MB_OK | MB_ICONEXCLAMATION          },
    { IDS_BADSYMBOLID,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_LABELDUPID,       MB_OK | MB_ICONEXCLAMATION          },
    { IDS_SELECTFIRST,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CTRLDUPID,        MB_OK | MB_ICONEXCLAMATION          },
    { IDS_BADCUSTDLL,       MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOCLIP,           MB_OK | MB_ICONEXCLAMATION          },
    { IDS_INTERNAL,         MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOMOUSE,          MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOINIT,           MB_OK | MB_ICONEXCLAMATION          },
    { IDS_GTZERO,           MB_OK | MB_ICONEXCLAMATION          },
    { IDS_ICONNAMEHASBLANKS,MB_OK | MB_ICONEXCLAMATION          },
    { IDS_IDUPIDS,          MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CREATECTRLERROR,  MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CANTOPENRES,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CONFIRMDISCARD,   MB_YESNO | MB_ICONEXCLAMATION       },
    { IDS_SYMNOTFOUND,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOCLASS,          MB_OK | MB_ICONEXCLAMATION          },
    { IDS_POSITIVENUM,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_MEMERROR,         MB_OK | MB_ICONHAND                 },
    { IDS_DLGNAMEHASBLANKS, MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NODLGNAME,        MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CANTINITDLL,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_NOICONNAME,       MB_OK | MB_ICONEXCLAMATION          },
    { IDS_RESTOREDIALOG,    MB_YESNO | MB_ICONEXCLAMATION       },
    { IDS_ZEROPOINTSIZE,    MB_OK | MB_ICONEXCLAMATION          },
    { IDS_MINGTMAXSPACE,    MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CUSTCNTLINUSE,    MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CUSTALREADYLOADED,MB_OK | MB_ICONEXCLAMATION          },
    { IDS_CANTLOADDLL,      MB_OK | MB_ICONEXCLAMATION          },
    { IDS_DLLBADEXPORTS,    MB_OK | MB_ICONEXCLAMATION          },
    { IDS_DLLBADCOUNT,      MB_OK | MB_ICONEXCLAMATION          }
};

/*
 * Table that maps menu items to help context id's for them.
 */
HELPMAP gahmapMenu[] = {
    {MENU_NEWRES,               HELPID_FILE_NEWRES              },
    {MENU_OPEN,                 HELPID_FILE_OPEN                },
    {MENU_SAVE,                 HELPID_FILE_SAVE                },
    {MENU_SAVEAS,               HELPID_FILE_SAVEAS              },
    {MENU_SETINCLUDE,           HELPID_FILE_SETINCLUDE          },
    {MENU_NEWCUST,              HELPID_FILE_NEWCUST             },
    {MENU_OPENCUST,             HELPID_FILE_OPENCUST            },
    {MENU_REMCUST,              HELPID_FILE_REMCUST             },
    {MENU_EXIT,                 HELPID_FILE_EXIT                },

    {MENU_RESTOREDIALOG,        HELPID_EDIT_RESTOREDIALOG       },
    {MENU_CUT,                  HELPID_EDIT_CUT                 },
    {MENU_COPY,                 HELPID_EDIT_COPY                },
    {MENU_PASTE,                HELPID_EDIT_PASTE               },
    {MENU_DELETE,               HELPID_EDIT_DELETE              },
    {MENU_DUPLICATE,            HELPID_EDIT_DUPLICATE           },
    {MENU_SYMBOLS,              HELPID_EDIT_SYMBOLS             },
    {MENU_STYLES,               HELPID_EDIT_STYLES              },
    {MENU_SIZETOTEXT,           HELPID_EDIT_SIZETOTEXT          },
    {MENU_NEWDIALOG,            HELPID_EDIT_NEWDIALOG           },
    {MENU_SELECTDIALOG,         HELPID_EDIT_SELECTDIALOG        },

    {MENU_ALIGNLEFT,            HELPID_ARRANGE_ALIGNLEFT        },
    {MENU_ALIGNVERT,            HELPID_ARRANGE_ALIGNVERT        },
    {MENU_ALIGNRIGHT,           HELPID_ARRANGE_ALIGNRIGHT       },
    {MENU_ALIGNTOP,             HELPID_ARRANGE_ALIGNTOP         },
    {MENU_ALIGNHORZ,            HELPID_ARRANGE_ALIGNHORZ        },
    {MENU_ALIGNBOTTOM,          HELPID_ARRANGE_ALIGNBOTTOM      },
    {MENU_SPACEHORZ,            HELPID_ARRANGE_SPACEHORZ        },
    {MENU_SPACEVERT,            HELPID_ARRANGE_SPACEVERT        },
    {MENU_ARRSIZEWIDTH,         HELPID_ARRANGE_ARRSIZEWIDTH     },
    {MENU_ARRSIZEHEIGHT,        HELPID_ARRANGE_ARRSIZEHEIGHT    },
    {MENU_ARRPUSHBOTTOM,        HELPID_ARRANGE_ARRPUSHBOTTOM    },
    {MENU_ARRPUSHRIGHT,         HELPID_ARRANGE_ARRPUSHRIGHT     },
    {MENU_ORDERGROUP,           HELPID_ARRANGE_ORDERGROUP       },
    {MENU_ARRSETTINGS,          HELPID_ARRANGE_ARRSETTINGS      },

    {MENU_TESTMODE,             HELPID_OPTIONS_TESTMODE         },
    {MENU_HEXMODE,              HELPID_OPTIONS_HEXMODE          },
    {MENU_TRANSLATE,            HELPID_OPTIONS_TRANSLATE        },
    {MENU_USENEWKEYWORDS,       HELPID_OPTIONS_USENEWKEYWORDS   },
    {MENU_SHOWTOOLBOX,          HELPID_OPTIONS_SHOWTOOLBOX      },

    {MENU_CONTENTS,             HELPID_HELP_CONTENTS            },
    {MENU_SEARCH,               HELPID_HELP_SEARCH              },
    // No help for the About menu command.

    {0,                         0                               }
};

/*
 * Table that maps dialog ids to help context id's for them.
 */
HELPMAP gahmapDialog[] = {
    // No help for the About dialog.
    {DID_ARRSETTINGS,           HELPID_ARRSETTINGS              },
    {DID_CHECKBOXSTYLES,        HELPID_CHECKBOXSTYLES           },
    {DID_COMBOBOXSTYLES,        HELPID_COMBOBOXSTYLES           },
    {DID_CUSTOMSTYLES,          HELPID_CUSTOMSTYLES             },
    {DID_DIALOGSTYLES,          HELPID_DIALOGSTYLES             },
    {DID_EDITSTYLES,            HELPID_EDITSTYLES               },
    {DID_FRAMESTYLES,           HELPID_FRAMESTYLES              },
    {DID_GROUPBOXSTYLES,        HELPID_GROUPBOXSTYLES           },
    {DID_ORDERGROUP,            HELPID_ORDERGROUP               },
    {DID_HORZSCROLLSTYLES,      HELPID_HORZSCROLLSTYLES         },
    {DID_ICONSTYLES,            HELPID_ICONSTYLES               },
    {DID_LISTBOXSTYLES,         HELPID_LISTBOXSTYLES            },
    {DID_NEWCUST,               HELPID_NEWCUST                  },
    {DID_PUSHBUTTONSTYLES,      HELPID_PUSHBUTTONSTYLES         },
    {DID_RADIOBUTTONSTYLES,     HELPID_RADIOBUTTONSTYLES        },
    {DID_RECTSTYLES,            HELPID_RECTSTYLES               },
    {DID_REMCUST,               HELPID_REMCUST                  },
    {DID_SELECTDIALOG,          HELPID_SELECTDIALOG             },
    {DID_SYMBOLS,               HELPID_SYMBOLS                  },
    {DID_TEXTSTYLES,            HELPID_TEXTSTYLES               },
    {DID_VERTSCROLLSTYLES,      HELPID_VERTSCROLLSTYLES         },

    {DID_COMMONFILEOPENINCLUDE, HELPID_COMMONFILEOPENINCLUDE    },
    {DID_COMMONFILEOPENRES,     HELPID_COMMONFILEOPENRES        },
    {DID_COMMONFILESAVEINCLUDE, HELPID_COMMONFILESAVEINCLUDE    },
    {DID_COMMONFILESAVERES,     HELPID_COMMONFILESAVERES        },
    {DID_COMMONFILEOPENDLL,     HELPID_COMMONFILEOPENDLL        },

    {DID_TOOLBOX,               HELPID_TOOLBOX                  },
    {DID_STATUS,                HELPID_PROPERTIESBAR            },

    {0,                         0                               }
};

/*
 * Language and Sub Language tables.
 */

static SUBLANGTABLE aslNeutral[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_DEFAULT,      IDS_SUBLANG_DEFAULT,        IDS_SL_DEFAULT      }
};

static SUBLANGTABLE aslChinese[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_CHINESE_SIMPLIFIED, IDS_SUBLANG_CHINESE_SIMPLIFIED, IDS_SL_CHINESE_SIMPLIFIED   },
    { SUBLANG_CHINESE_TRADITIONAL, IDS_SUBLANG_CHINESE_TRADITIONAL, IDS_SL_CHINESE_TRADITIONAL  }
};

static SUBLANGTABLE aslDutch[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_DUTCH,        IDS_SUBLANG_DUTCH,          IDS_SL_DUTCH        },
    { SUBLANG_DUTCH_BELGIAN,IDS_SUBLANG_DUTCH_BELGIAN,  IDS_SL_DUTCH_BELGIAN}
};

static SUBLANGTABLE aslEnglish[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_ENGLISH_US,   IDS_SUBLANG_ENGLISH_US,     IDS_SL_ENGLISH_US   },
    { SUBLANG_ENGLISH_UK,   IDS_SUBLANG_ENGLISH_UK,     IDS_SL_ENGLISH_UK   },
    { SUBLANG_ENGLISH_AUS,  IDS_SUBLANG_ENGLISH_AUS,    IDS_SL_ENGLISH_AUS  },
    { SUBLANG_ENGLISH_CAN,  IDS_SUBLANG_ENGLISH_CAN,    IDS_SL_ENGLISH_CAN  }
};

static SUBLANGTABLE aslFrench[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_FRENCH,       IDS_SUBLANG_FRENCH,         IDS_SL_FRENCH       },
    { SUBLANG_FRENCH_BELGIAN, IDS_SUBLANG_FRENCH_BELGIAN, IDS_SL_FRENCH_BELGIAN  },
    { SUBLANG_FRENCH_CANADIAN, IDS_SUBLANG_FRENCH_CANADIAN, IDS_SL_FRENCH_CANADIAN },
    { SUBLANG_FRENCH_SWISS, IDS_SUBLANG_FRENCH_SWISS,   IDS_SL_FRENCH_SWISS }
};

static SUBLANGTABLE aslGerman[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_GERMAN,       IDS_SUBLANG_GERMAN,         IDS_SL_GERMAN       },
    { SUBLANG_GERMAN_SWISS, IDS_SUBLANG_GERMAN_SWISS,   IDS_SL_GERMAN_SWISS }
};

static SUBLANGTABLE aslItalian[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_ITALIAN,      IDS_SUBLANG_ITALIAN,        IDS_SL_ITALIAN      },
    { SUBLANG_ITALIAN_SWISS, IDS_SUBLANG_ITALIAN_SWISS, IDS_SL_ITALIAN_SWISS }
};

static SUBLANGTABLE aslNorwegian[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_NORWEGIAN_BOKMAL, IDS_SUBLANG_NORWEGIAN_BOKMAL, IDS_SL_NORWEGIAN_BOKMAL },
    { SUBLANG_NORWEGIAN_NYNORSK, IDS_SUBLANG_NORWEGIAN_NYNORSK, IDS_SL_NORWEGIAN_NYNORSK }
};

static SUBLANGTABLE aslPortuguese[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_PORTUGUESE,   IDS_SUBLANG_PORTUGUESE,     IDS_SL_PORTUGUESE   },
    { SUBLANG_PORTUGUESE_BRAZILIAN, IDS_SUBLANG_PORTUGUESE_BRAZILIAN, IDS_SL_PORTUGUESE_BRAZILIAN }
};

static SUBLANGTABLE aslSpanish[] = {
    { SUBLANG_NEUTRAL,      IDS_SUBLANG_NEUTRAL,        IDS_SL_NEUTRAL      },
    { SUBLANG_SPANISH,      IDS_SUBLANG_SPANISH,        IDS_SL_SPANISH      },
    { SUBLANG_SPANISH_MEXICAN, IDS_SUBLANG_SPANISH_MEXICAN, IDS_SL_SPANISH_MEXICAN },
    { SUBLANG_SPANISH_MODERN, IDS_SUBLANG_SPANISH_MODERN, IDS_SL_SPANISH_MODERN }
};

LANGTABLE gaLangTable[] = {
    { LANG_NEUTRAL,         IDS_LANG_NEUTRAL,           IDS_L_NEUTRAL,
  sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_CHINESE,         IDS_LANG_CHINESE,           IDS_L_CHINESE,
        sizeof(aslChinese) / sizeof(SUBLANGTABLE), aslChinese },
    { LANG_CZECH,           IDS_LANG_CZECH,             IDS_L_CZECH,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_DANISH,          IDS_LANG_DANISH,            IDS_L_DANISH,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_DUTCH,           IDS_LANG_DUTCH,             IDS_L_DUTCH,
        sizeof(aslDutch) / sizeof(SUBLANGTABLE), aslDutch },
    { LANG_ENGLISH,         IDS_LANG_ENGLISH,           IDS_L_ENGLISH,
        sizeof(aslEnglish) / sizeof(SUBLANGTABLE), aslEnglish },
    { LANG_FINNISH,         IDS_LANG_FINNISH,           IDS_L_FINNISH,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_FRENCH,          IDS_LANG_FRENCH,            IDS_L_FRENCH,
        sizeof(aslFrench) / sizeof(SUBLANGTABLE), aslFrench },
    { LANG_GERMAN,          IDS_LANG_GERMAN,            IDS_L_GERMAN,
        sizeof(aslGerman) / sizeof(SUBLANGTABLE), aslGerman },
    { LANG_GREEK,           IDS_LANG_GREEK,             IDS_L_GREEK,
  sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_HUNGARIAN,       IDS_LANG_HUNGARIAN,         IDS_L_HUNGARIAN,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_ICELANDIC,       IDS_LANG_ICELANDIC,         IDS_L_ICELANDIC,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_ITALIAN,         IDS_LANG_ITALIAN,           IDS_L_ITALIAN,
        sizeof(aslItalian) / sizeof(SUBLANGTABLE), aslItalian },
    { LANG_JAPANESE,        IDS_LANG_JAPANESE,          IDS_L_JAPANESE,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_KOREAN,          IDS_LANG_KOREAN,            IDS_L_KOREAN,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_NORWEGIAN,       IDS_LANG_NORWEGIAN,         IDS_L_NORWEGIAN,
        sizeof(aslNorwegian) / sizeof(SUBLANGTABLE), aslNorwegian },
    { LANG_POLISH,          IDS_LANG_POLISH,            IDS_L_POLISH,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_PORTUGUESE,      IDS_LANG_PORTUGUESE,        IDS_L_PORTUGUESE,
  sizeof(aslPortuguese) / sizeof(SUBLANGTABLE), aslPortuguese },
    { LANG_RUSSIAN,         IDS_LANG_RUSSIAN,           IDS_L_RUSSIAN,
  sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_SLOVAK,          IDS_LANG_SLOVAK,            IDS_L_SLOVAK,
        sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_SPANISH,         IDS_LANG_SPANISH,           IDS_L_SPANISH,
        sizeof(aslSpanish) / sizeof(SUBLANGTABLE), aslSpanish },
    { LANG_SWEDISH,         IDS_LANG_SWEDISH,           IDS_L_SWEDISH,
  sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
    { LANG_TURKISH,         IDS_LANG_TURKISH,           IDS_L_TURKISH,
  sizeof(aslNeutral) / sizeof(SUBLANGTABLE), aslNeutral },
};

INT gcLanguages = sizeof(gaLangTable) / sizeof(LANGTABLE);


GROUPDLG.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 Header *******************************
* Module Name: groupdlg.c
*
* This module handles the "Group Controls" dialog, which changes the
* logical order and grouping of controls in the dialog.
*
*
* Functions:
*   OrderGroupDialog()
*   OrderDlgProc()
*   OrderDlgInit()
*   OrderDlgFillList()
*   OrderDlgLBWndProc()
*   OrderDlgInsertHitTest()
*   OrderDlgEnableControls()
*   OrderDlgSelChange()
*   OrderDlgMakeGroup()
*   OrderDlgMarkGroupEnds()
*   OrderDlgSetTabs()
*   OrderDlgClearTabs()
*   OrderDlgToggleTab()
*   OrderDlgReorder()
*   OrderDlgDrawItem()
*   OrderWindows()
*   IsListChanged()
*
*
* Comments:
*
* Note that once a control has either it's WS_TABSTOP or WS_GROUP style
* bit changed by this dialog, the style of the actual control in work
* mode is not changed.  This should not be a problem, however, because
* the appearance of controls does not change based on these styles, and
* the saved resource and the Test mode dialog WILL have the proper styles
* set in them.
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"
#include "dlghelp.h"

/*
 * Various constants for sizes of items in the list box (in pixels).
 * Note that if the size of the bitmaps in the .bmp files change,
 * these defines must be adjusted to match!
 */
#define CYORDERDLGLINE      18  // Height of a line.
#define CXTABBMP            10  // Width of the tabstop bmp.
#define CYTABBMP            16  // Height of the tabstop bmp.
#define CXTYPEBMP           16  // Width of a control type bitmap.
#define CYTYPEBMP           14  // Height of a control type bitmap.

/*
 * Structure for ordering the controls into a new order.
 */
typedef struct {
    INT wNewOrder;                  // Indexes in the new order.
    WORD fTaken:1;                  // TRUE if this item has been copied.
    WORD fSelected:1;               // TRUE if this item is selected.
} NEWORDER, *PNEWORDER;

STATICFN VOID OrderDlgInit(HWND hwnd);
STATICFN VOID OrderDlgFillList(VOID);
WINDOWPROC OrderDlgLBWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
STATICFN BOOL OrderDlgInsertHitTest(INT y, PINT piInsert);
STATICFN VOID OrderDlgEnableControls(VOID);
STATICFN VOID OrderDlgSelChange(VOID);
STATICFN VOID OrderDlgMakeGroup(VOID);
STATICFN VOID OrderDlgMarkGroupEnds(VOID);
STATICFN VOID OrderDlgSetTabs(VOID);
STATICFN VOID OrderDlgClearTabs(VOID);
STATICFN VOID OrderDlgToggleTab(INT y);
STATICFN VOID OrderDlgReorder(INT iInsert);
STATICFN VOID OrderDlgDrawItem(LPDRAWITEMSTRUCT lpdis);
STATICFN VOID OrderWindows(VOID);
STATICFN BOOL IsListChanged(VOID);

static HWND hwndOrderDlg;           // Window handle of the Order/Group dlg.
static HWND hwndOrderList;          // Window handle of the list box.
static NPCTYPE *anpcSave;           // Points to array of controls in old order.
static PDWORD aflStyleSave;         // Points to array of original styles.
static PINT aiSelItem;              // Points to array of selected items.
static BOOL fContiguousSel;         // TRUE if selection in LB is contiguous.
static PNEWORDER aNewOrder;         // Points to array with new control order.
static INT cSelItems;               // Count of selected items in the array.
static WNDPROC lpfnOldLBWndProc;    // Original list box window proc.

/************************************************************************
* OrderGroupDialog
*
* This function puts up the Order/Group dialog box.
* Any move is cancelled.
* The Order/Group dialog box is put up.
* The child windows are reordered and group and tabstop bits set.
* File change is noted.
*
************************************************************************/

VOID OrderGroupDialog(VOID)
{
    NPCTYPE npc;
    INT i;

    /*
     * Nothing to order.  This also protects some calculations below.
     */
    if (!cWindows)
        return;

    if (!(anpcSave = (NPCTYPE *)MyAlloc(cWindows * sizeof(NPCTYPE))))
        return;

    /*
     * Allocate an array of indexes for selected items.  Make it large
     * enough to handle selecting all of the items in the list.
     */
    if (!(aiSelItem = (PINT)MyAlloc(cWindows * sizeof(INT)))) {
        MyFree(anpcSave);
        return;
    }

    /*
     * Allocate an array to save the original styles in.
     */
    if (!(aflStyleSave = (PDWORD)MyAlloc(cWindows * sizeof(DWORD)))) {
        MyFree(anpcSave);
        MyFree(aiSelItem);
        return;
    }

    if (!(aNewOrder = (PNEWORDER)MyAlloc(cWindows * sizeof(NEWORDER)))) {
        MyFree(aflStyleSave);
        MyFree(anpcSave);
        MyFree(aiSelItem);
        return;
    }

    CancelSelection(TRUE);

    /*
     * Save the original order of the controls, and their styles.
     */
    for (i = 0, npc = npcHead; npc; i++, npc = npc->npcNext) {
        anpcSave[i] = npc;
        aflStyleSave[i] = npc->flStyle;
    }

    if (DlgBox(DID_ORDERGROUP, (WNDPROC)OrderDlgProc) == IDOK) {
        if (IsListChanged()) {
            gfResChged = gfDlgChanged = TRUE;
            ShowFileStatus(FALSE);
        }

        OrderWindows();
    }
    else {
        /*
         * Restore the linked list to the order that it originally was.
         */
        npcHead = anpcSave[0];
        for (i = 0; i < cWindows - 1; i++)
            (anpcSave[i])->npcNext = anpcSave[i + 1];
        (anpcSave[i])->npcNext = NULL;

        /*
         * Then restore the styles to the way that they were.
         */
        for (i = 0, npc = npcHead; npc; i++, npc = npc->npcNext)
            npc->flStyle = aflStyleSave[i];
    }

    MyFree(aNewOrder);
    MyFree(aflStyleSave);
    MyFree(aiSelItem);
    MyFree(anpcSave);
}

/************************************************************************
* OrderDlgProc
*
* This is the dialog function for the group ordering dialog box.
*
************************************************************************/

DIALOGPROC OrderDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            OrderDlgInit(hwnd);
            return TRUE;

        case WM_MEASUREITEM:
            ((LPMEASUREITEMSTRUCT)lParam)->itemHeight = CYORDERDLGLINE;
            return TRUE;

        case WM_DRAWITEM:
            OrderDlgDrawItem((LPDRAWITEMSTRUCT)lParam);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_ORDERLIST:
                    switch (HIWORD(wParam)) {
                        case LBN_SELCHANGE:
                            OrderDlgSelChange();
                            break;
                    }

                    break;

                case DID_ORDERMAKEGROUP:
                    OrderDlgMakeGroup();
                    break;

                case DID_ORDERSETTAB:
                    OrderDlgSetTabs();
                    break;

                case DID_ORDERCLEARTAB:
                    OrderDlgClearTabs();
                    break;

                case IDCANCEL:
                case IDOK:
                    EndDialog(hwnd, LOWORD(wParam));
                    break;

                case IDHELP:
                    WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                            HELPID_ORDERGROUP);
                    break;
            }

            return TRUE;

        default:
            return FALSE;
    }

    return FALSE;
}

/************************************************************************
* OrderDlgInit
*
* Initializes the Order/Group dialog.
*
* Arguments:
*     HWND hwnd = The dialog window handle.
*
************************************************************************/

STATICFN VOID OrderDlgInit(
    HWND hwnd)
{
    hwndOrderDlg = hwnd;
    hwndOrderList = GetDlgItem(hwnd, DID_ORDERLIST);

    lpfnOldLBWndProc = (WNDPROC)SetWindowLong(hwndOrderList,
            GWL_WNDPROC, (DWORD)OrderDlgLBWndProc);

    OrderDlgFillList();
    OrderDlgMarkGroupEnds();
    OrderDlgSelChange();

    CenterWindow(hwnd);
}

/************************************************************************
* OrderDlgFillList
*
* Fill the order listbox
*
************************************************************************/

STATICFN VOID OrderDlgFillList(VOID)
{
    NPCTYPE npc;

    SendMessage(hwndOrderList, LB_RESETCONTENT, 0, 0L);

    for (npc = npcHead; npc; npc = npc->npcNext)
        SendMessage(hwndOrderList, LB_INSERTSTRING, (WPARAM)-1, (DWORD)npc);
}

/************************************************************************
* OrderDlgLBWndProc
*
* window procedure for the left button
*
************************************************************************/

WINDOWPROC OrderDlgLBWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    INT iInsert;

    switch (msg) {
        case WM_SETCURSOR:
            /*
             * Defeat the system changing cursors on us in the client area.
             */
            if (LOWORD(lParam) == HTCLIENT)
                return TRUE;

            break;

        case WM_MOUSEMOVE:
            if (!(GetKeyState(VK_LBUTTON) & 0x8000) &&
                    OrderDlgInsertHitTest(HIWORD(lParam), &iInsert))
                SetCursor(hcurInsert);
            else
                SetCursor(hcurArrow);

            break;

        case WM_LBUTTONDOWN:
            if (OrderDlgInsertHitTest(HIWORD(lParam), &iInsert)) {
                OrderDlgReorder(iInsert);
                return FALSE;
            }

            break;

        case WM_LBUTTONDBLCLK:
            if (!OrderDlgInsertHitTest(HIWORD(lParam), &iInsert)) {
                OrderDlgToggleTab(HIWORD(lParam));
                return FALSE;
            }

            break;
    }

    return CallWindowProc((WNDPROC)lpfnOldLBWndProc,
            hwnd, msg, wParam, lParam);
}

/************************************************************************
* OrderDlgInsertHitTest
*
* Inserts item in list box
*
* Arguments:
*   INT - the y coordinate of the mouse hit
*   PINT - pointer to insertion point
*
* Returns:
*   Where the mouse hit in the listbox
*
************************************************************************/

STATICFN BOOL OrderDlgInsertHitTest(
    INT y,
    PINT piInsert)
{
    INT i;
    INT iPixel;
    INT iInsert;
    INT iLine;
    BOOL fInsertZone;

    /*
     * Cannot insert if nothing is selected.
     */
    if (!cSelItems)
        return FALSE;

    /*
     * Find which pixel it hit.
     */
    iPixel = y % CYORDERDLGLINE;

    /*
     * Determine if they are in the upper or lower insert zones.
     */
    if (iPixel < 3) {
        iLine = y / CYORDERDLGLINE;
        fInsertZone = TRUE;
    }
    else if (iPixel > CYORDERDLGLINE - 3) {
        iLine = (y / CYORDERDLGLINE) + 1;
        fInsertZone = TRUE;
    }
    else {
        fInsertZone = FALSE;
    }

    if (fInsertZone) {
        /*
         * Do some math, taking into account the top index of the
         * listbox, to determine which line they are inserting into.
         */
        iInsert = iLine + (INT)SendMessage(hwndOrderList,
                LB_GETTOPINDEX, 0, 0L);

        /*
 * If we are too far down the listbox, act as if we are not
         * in the insert zone.
         */
        if (iInsert > cWindows) {
            fInsertZone = FALSE;
        }
        else {
            /*
             * Check for whether the cursor was inside a selected
             * area.  If it is, we don't allow inserting there
             * (because it is a noop).  However, if the selection
             * is discontiguous, we always allow inserting, because
             * inserting at any point with a discontiguous selection
             * will always cause something to happen, even if it
             * is just gathering the selection together.
             */
            if (fContiguousSel) {
                for (i = 0; i < cSelItems; i++) {
                    if (aiSelItem[i] == iInsert ||
                            aiSelItem[i] == iInsert - 1)
                        return FALSE;
                }
            }

            *piInsert = iInsert;
        }
    }

    return fInsertZone;
}

/************************************************************************
* OrderDlgEnableControls
*
* Enable items in order dialog
*
************************************************************************/

STATICFN VOID OrderDlgEnableControls(VOID)
{
    NPCTYPE npc;
    BOOL fEnableSetTab = FALSE;
    BOOL fEnableClearTab = FALSE;
    INT i;

    if (cSelItems) {
        /*
         * Walk through all the selected items.  We will enable the
         * set/clear tab buttons based on whether there are any tabs
         * to set/clear in the current selection.
         */
        for (i = 0; i < cSelItems && (!fEnableSetTab || !fEnableClearTab); i++) {
            npc = (NPCTYPE)SendMessage(
                    hwndOrderList, LB_GETITEMDATA, aiSelItem[i], 0L);

            if (npc->flStyle & WS_TABSTOP)
                fEnableClearTab = TRUE;
            else
                fEnableSetTab = TRUE;
        }
    }

    /*
     * Normally, if there is a selection we enable "Make Group",
     * but if the selection is not contiguous, we disable it.
     */
    EnableWindow(GetDlgItem(hwndOrderDlg, DID_ORDERMAKEGROUP),
            cSelItems && fContiguousSel);

    EnableWindow(GetDlgItem(hwndOrderDlg, DID_ORDERSETTAB),
            fEnableSetTab);
    EnableWindow(GetDlgItem(hwndOrderDlg, DID_ORDERCLEARTAB),
            fEnableClearTab);
}

/************************************************************************
* OrderDlgSelChange
*
* get selection change in order dialog
*
************************************************************************/

STATICFN VOID OrderDlgSelChange(VOID)
{
    INT i;

    cSelItems = (INT)SendMessage(hwndOrderList, LB_GETSELITEMS,
            cWindows, (DWORD)aiSelItem);

    /*
     * Set a flag saying whether the selection is contiguous or not.
     */
    fContiguousSel = TRUE;
    if (cSelItems > 1) {
        for (i = 1; i < cSelItems; i++) {
            if (aiSelItem[i] != aiSelItem[i - 1] + 1) {
                fContiguousSel = FALSE;
                break;
            }
        }
    }

    OrderDlgEnableControls();
}

/************************************************************************
* OrderDlgMakeGroup
*
* Creates a group
*
************************************************************************/

STATICFN VOID OrderDlgMakeGroup(VOID)
{
    INT i;
    NPCTYPE npc;

    for (i = 0; i < cSelItems; i++) {
        npc = (NPCTYPE)SendMessage(
                hwndOrderList, LB_GETITEMDATA, aiSelItem[i], 0L);

        /*
         * Set the WS_GROUP style on the first selected control
         * and clear it from all others.
         */
        if (i == 0)
            npc->flStyle |= WS_GROUP;
        else
            npc->flStyle &= ~WS_GROUP;

        /*
         * Are we on the last selected item and is this item not the
         * very last item in the list?  If so, set the "group" style
         * on the next control.
         */
        if (i == cSelItems - 1 && aiSelItem[i] < cWindows - 1) {
            npc = (NPCTYPE)SendMessage(
                    hwndOrderList, LB_GETITEMDATA, aiSelItem[i] + 1, 0L);
            npc->flStyle |= WS_GROUP;
        }
    }

    OrderDlgMarkGroupEnds();
    OrderDlgEnableControls();
    InvalidateRect(hwndOrderList, NULL, FALSE);
}

/************************************************************************
* OrderDlgMarkGroupEnds
*
* Set the end for the items in the group
*
* Arguments:
*     HWND hwnd = The dialog window handle.
*
************************************************************************/

STATICFN VOID OrderDlgMarkGroupEnds(VOID)
{
    NPCTYPE npc;
    NPCTYPE npcPrev;

    for (npc = npcHead, npcPrev = NULL; npc;
            npcPrev = npc, npc = npc->npcNext) {
        npc->fGroupEnd = FALSE;

        if ((npc->flStyle & WS_GROUP) && npcPrev)
            npcPrev->fGroupEnd = TRUE;
    }

    if (npcPrev)
        npcPrev->fGroupEnd = TRUE;
}

/************************************************************************
* OrderDlgSetTabs
*
* Set WS_TABSTOP behavior for group
*
************************************************************************/

STATICFN VOID OrderDlgSetTabs(VOID)
{
    INT i;
    NPCTYPE npc;

    for (i = 0; i < cSelItems; i++) {
        npc = (NPCTYPE)SendMessage(hwndOrderList, LB_GETITEMDATA, aiSelItem[i], 0L);

        npc->flStyle |= WS_TABSTOP;
    }

    OrderDlgEnableControls();
    InvalidateRect(hwndOrderList, NULL, FALSE);
}

/************************************************************************
* OrderDlgClearTabs
*
* Clear WS_TABSTOPS for group items.
*
************************************************************************/

STATICFN VOID OrderDlgClearTabs(VOID)
{
    INT i;
    NPCTYPE npc;

    for (i = 0; i < cSelItems; i++) {
        npc = (NPCTYPE)SendMessage(hwndOrderList, LB_GETITEMDATA, aiSelItem[i], 0L);

        npc->flStyle &= ~WS_TABSTOP;
    }

    OrderDlgEnableControls();
    InvalidateRect(hwndOrderList, NULL, FALSE);
}

/************************************************************************
* OrderDlgToggleTab
*
* Toggle to WS_TABSTOP attribute for the selected item
*
* Arguments:
*   INT - the y coordinate of the item that was selected
*
************************************************************************/

STATICFN VOID OrderDlgToggleTab(
    INT y)
{
    NPCTYPE npc;
    RECT rcItem;
    INT iLine;

    /*
     * Determine which item was clicked on.
     */
    iLine = (y / CYORDERDLGLINE) +
            (INT)SendMessage(hwndOrderList, LB_GETTOPINDEX, 0, 0L);

    /*
     * If it is a valid item (it was not in the white space below
     * all the listbox items), then toggle the WS_TABSTOP style on
     * it and update the display.
     */
    if (iLine < cWindows) {
        npc = (NPCTYPE)SendMessage(hwndOrderList, LB_GETITEMDATA, iLine, 0L);
        npc->flStyle ^= WS_TABSTOP;

        OrderDlgEnableControls();
        SendMessage(hwndOrderList, LB_GETITEMRECT,
                iLine, (DWORD)(LPRECT)&rcItem);
        InvalidateRect(hwndOrderList, &rcItem, FALSE);
    }
}

/************************************************************************
* OrderDlgReorder
*
* Inserts a new item at the point indicated
*
* Arguments:
*   INT iInsert - Index of where to insert the selection.
*
************************************************************************/

STATICFN VOID OrderDlgReorder(
    INT iInsert)
{
    INT i;
    INT j;
    INT iNewSelStart;
    INT iNewSelEnd;
    INT iTopIndex;
    INT iNew = 0;
    NPCTYPE npc;
    NPCTYPE npcPrev;

    /*
     * If there is nothing selected or there is only one item, the
     * order cannot change so we just return.
     */
    if (!cSelItems || cWindows < 2)
        return;

    iTopIndex = (INT)SendMessage(hwndOrderList, LB_GETTOPINDEX, 0, 0L);

    for (i = 0; i < cWindows; i++) {
        aNewOrder[i].fTaken = FALSE;
        aNewOrder[i].fSelected = FALSE;
    }

    for (i = 0; i < cSelItems; i++)
        aNewOrder[aiSelItem[i]].fSelected = TRUE;

    for (j = 0; j < iInsert; j++) {
        if (aNewOrder[j].fSelected == FALSE)
            aNewOrder[iNew++].wNewOrder = j;
    }

    iNewSelStart = iNew;

    for (i = 0; i < cWindows; i++) {
        if (aNewOrder[i].fSelected) {
            aNewOrder[iNew++].wNewOrder = i;
            aNewOrder[i].fTaken = TRUE;
        }
    }

    iNewSelEnd = iNew - 1;

    for (; j < cWindows; j++) {
        if (!aNewOrder[j].fTaken)
            aNewOrder[iNew++].wNewOrder = j;
    }

    /*
     * Was the order changed at all?
     */
    for (i = 1; i < cWindows; i++)
        if (aNewOrder[i].wNewOrder != aNewOrder[i - 1].wNewOrder + 1)
            break;

    /*
     * No, get out because there is nothing to do.
     */
    if (i == cWindows)
        return;

    for (i = 0; i < cWindows; i++) {
        npc = (NPCTYPE)SendMessage(
                hwndOrderList, LB_GETITEMDATA, aNewOrder[i].wNewOrder, 0L);

        if (!i) {
            npcHead = npc;
            npcPrev = npc;
        }
        else {
            npcPrev->npcNext = npc;
            npcPrev = npc;
        }
    }

    npcPrev->npcNext = NULL;

    OrderDlgMarkGroupEnds();
    SendMessage(hwndOrderList, WM_SETREDRAW, FALSE, 0L);
    OrderDlgFillList();
    SendMessage(hwndOrderList, LB_SETTOPINDEX, iTopIndex, 0L);
    SendMessage(hwndOrderList, LB_SELITEMRANGE,
            TRUE, MAKELONG(iNewSelStart, iNewSelEnd));
    OrderDlgSelChange();
    SendMessage(hwndOrderList, WM_SETREDRAW, TRUE, 0L);
    InvalidateRect(hwndOrderList, NULL, FALSE);
}

/************************************************************************
* OrderDlgDrawItem
*
* draws the text for the group item
*
* Arguments:
*     LPDRAWITEMSTRUCT - pointer to the item
*
************************************************************************/

STATICFN VOID OrderDlgDrawItem(
    LPDRAWITEMSTRUCT lpdis)
{
    NPCTYPE npc;
    TCHAR szItem[CCHTEXTMAX];
    HBITMAP hbmCtrlType;
    HBITMAP hbmTab;
    HBITMAP hbmOld;

    npc = (NPCTYPE)lpdis->itemData;

    /*
     * Begin building the text string to draw.
     */
    *szItem = CHAR_NULL;

    if (npc->pwcd->iType == W_CUSTOM)
        wsprintf(szItem, L"'%s', ", npc->pwcd->pszClass);

    if (npc->text) {
        if (IsOrd(npc->text)) {
            IDToLabel(szItem + lstrlen(szItem), OrdID(npc->text), TRUE);
            lstrcat(szItem, L", ");
        }
        else {
            wsprintf(szItem + lstrlen(szItem), L"\"%s\", ", npc->text);
        }
    }

    IDToLabel(szItem + lstrlen(szItem), npc->id, TRUE);

    if (lpdis->itemState & ODS_SELECTED) {
        SetBkColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHT));
        SetTextColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
        hbmCtrlType = npc->pwcd->hbmCtrlTypeSel;
        hbmTab = hbmTabStopSel;
    }
    else {
        SetBkColor(lpdis->hDC, GetSysColor(COLOR_WINDOW));
        SetTextColor(lpdis->hDC, GetSysColor(COLOR_WINDOWTEXT));
        hbmCtrlType = npc->pwcd->hbmCtrlType;
        hbmTab = hbmTabStop;
    }

    /*
     * Draw the string (and paint the background).
     */
    ExtTextOut(lpdis->hDC,
        CXTABBMP + 2 + CXTYPEBMP + 2,
        lpdis->rcItem.top + (CYORDERDLGLINE - gcySysChar),
        ETO_OPAQUE | ETO_CLIPPED, &lpdis->rcItem,
        szItem, lstrlen(szItem), NULL);

    /*
     * Draw the group marker separator line.
     */
    if (npc->fGroupEnd) {
        SelectObject(lpdis->hDC, hpenDarkGray);
        MoveToEx(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.bottom - 1, NULL);
        LineTo(lpdis->hDC, lpdis->rcItem.right, lpdis->rcItem.bottom - 1);
    }

    /*
     * Draw the tabstop bitmap if necessary.
     */
    if (npc->flStyle & WS_TABSTOP) {
        hbmOld = SelectObject(ghDCMem, hbmTab);
        BitBlt(lpdis->hDC,
                0, lpdis->rcItem.top + ((CYORDERDLGLINE - CYTABBMP) / 2),
                CXTABBMP, CYTABBMP, ghDCMem, 0, 0, SRCCOPY);
        SelectObject(ghDCMem, hbmOld);
    }

    /*
     * Draw the control type bitmap.
     */
    hbmOld = SelectObject(ghDCMem, hbmCtrlType);
    BitBlt(lpdis->hDC,
            lpdis->rcItem.left + CXTABBMP + 2,
            lpdis->rcItem.top + ((CYORDERDLGLINE - CYTYPEBMP) / 2),
            CXTYPEBMP, CYTYPEBMP, ghDCMem, 0, 0, SRCCOPY);
    SelectObject(ghDCMem, hbmOld);

    /*
     * Draw the focus rectangle, if necessary.
     */
    if (lpdis->itemState & ODS_FOCUS)
        DrawFocusRect(lpdis->hDC, &lpdis->rcItem);
}

/************************************************************************
* OrderWindows
*
* Orders the controls in Windows' linked list of windows to be the
* same as the order in the linked list of CTYPEs.
*
* The Z order of the drag windows and the control windows is critical
* to all the painting, dragging and selection code working properly!
*
************************************************************************/

STATICFN VOID OrderWindows(VOID)
{
    register NPCTYPE npc;

    for (npc = npcHead; npc; npc = npc->npcNext) {
        /*
         * The control goes to the bottom of the list.
         */
        SetWindowPos(npc->hwnd, (HWND)1, 0, 0, 0, 0,
                SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW);

        /*
         * The drag window goes to the top of the list.
         */
        SetWindowPos(npc->hwndDrag, NULL, 0, 0, 0, 0,
                SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW);
    }
}

/************************************************************************
* IsListChanged
*
* This function returns TRUE if the current order of the linked list
* of controls is different than what it was when the Order/Group dialog
* was first entered, or if any of the styles were changed (tabs were
* set/cleared, groups were changed, etc.).
*
************************************************************************/

STATICFN BOOL IsListChanged(VOID)
{
    NPCTYPE *pnpcSave;
    NPCTYPE npc;
    INT i;

    pnpcSave = anpcSave;
    for (i = 0, npc = npcHead; npc; i++, npc = npc->npcNext, pnpcSave++)
        if (npc != *pnpcSave || npc->flStyle != aflStyleSave[i])
            return TRUE;

    return FALSE;
}


IDS.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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: ids.h
*
* Contains id values for the dialog editor.
*
****************************************************************************/

/*
 * String id's. ------------------------------------------------------------
 */

/*
 * The zero'th string id is a "null" case.  It maps to an empty string.
 */
#define IDS_NULL                            0

#define IDS_APPVERSION                      1
#define IDS_APPVERSIONMINOR                 2

/*
 * The following are actually starting string ids into the styles strings
 * for the appropriate class type.  If styles are added to the acs*
 * structures (all referenced by the acsd array), then these indexes will
 * all have to be adjusted, as well as the other string id's which follow
 * the IDS_IC_* ones.
 */
#define IDS_IC_BUTTON                       3
#define IDS_IC_SCROLLBAR                    16
#define IDS_IC_EDIT                         25
#define IDS_IC_STATIC                       37
#define IDS_IC_LISTBOX                      51
#define IDS_IC_COMBOBOX                     65
#define IDS_IC_DIALOG                       76
#define IDS_IC_WINDOW                       84
#define IDS_IC_EXSTYLE                      101

/*
 * Language and SubLanguage keywords.
 */
#define IDS_LANG_NEUTRAL                    107
#define IDS_LANG_ALBANIAN                   108
#define IDS_LANG_ARABIC                     109
#define IDS_LANG_BAHASA                     110
#define IDS_LANG_BULGARIAN                  111
#define IDS_LANG_CATALAN                    112
#define IDS_LANG_CHINESE                    113
#define IDS_LANG_CZECH                      114
#define IDS_LANG_DANISH                     115
#define IDS_LANG_DUTCH                      116
#define IDS_LANG_ENGLISH                    117
#define IDS_LANG_FINNISH                    118
#define IDS_LANG_FRENCH                     119
#define IDS_LANG_GERMAN                     120
#define IDS_LANG_GREEK                      121
#define IDS_LANG_HEBREW                     122
#define IDS_LANG_HUNGARIAN                  123
#define IDS_LANG_ICELANDIC                  124
#define IDS_LANG_ITALIAN                    125
#define IDS_LANG_JAPANESE                   126
#define IDS_LANG_KOREAN                     127
#define IDS_LANG_NORWEGIAN                  128
#define IDS_LANG_POLISH                     129
#define IDS_LANG_PORTUGUESE                 130
#define IDS_LANG_RHAETO_ROMAN               131
#define IDS_LANG_ROMANIAN                   132
#define IDS_LANG_RUSSIAN                    133
#define IDS_LANG_SERBO_CROATIAN             134
#define IDS_LANG_SLOVAK                     135
#define IDS_LANG_SPANISH                    136
#define IDS_LANG_SWEDISH                    137
#define IDS_LANG_THAI                       138
#define IDS_LANG_TURKISH                    139
#define IDS_LANG_URDU                       140

#define IDS_SUBLANG_DEFAULT                 141
#define IDS_SUBLANG_NEUTRAL                 142
#define IDS_SUBLANG_CHINESE_SIMPLIFIED      143
#define IDS_SUBLANG_CHINESE_TRADITIONAL     144
#define IDS_SUBLANG_DUTCH                   145
#define IDS_SUBLANG_DUTCH_BELGIAN           146
#define IDS_SUBLANG_ENGLISH_US              147
#define IDS_SUBLANG_ENGLISH_UK              148
#define IDS_SUBLANG_ENGLISH_AUS             149
#define IDS_SUBLANG_ENGLISH_CAN             150
#define IDS_SUBLANG_FRENCH                  151
#define IDS_SUBLANG_FRENCH_BELGIAN          152
#define IDS_SUBLANG_FRENCH_CANADIAN         153
#define IDS_SUBLANG_FRENCH_SWISS            154
#define IDS_SUBLANG_GERMAN                  155
#define IDS_SUBLANG_GERMAN_SWISS            156
#define IDS_SUBLANG_ITALIAN                 157
#define IDS_SUBLANG_ITALIAN_SWISS           158
#define IDS_SUBLANG_NORWEGIAN_BOKMAL        159
#define IDS_SUBLANG_NORWEGIAN_NYNORSK       160
#define IDS_SUBLANG_PORTUGUESE              161
#define IDS_SUBLANG_PORTUGUESE_BRAZILIAN    162
#define IDS_SUBLANG_SERBO_CROATIAN_CYRILLIC 163
#define IDS_SUBLANG_SERBO_CROATIAN_LATIN    164
#define IDS_SUBLANG_SPANISH                 165
#define IDS_SUBLANG_SPANISH_MEXICAN         166
#define IDS_SUBLANG_SPANISH_MODERN          167

/*
 * Miscellaneous tokens written to the .DLG file.
 */
#define IDS_IDOK                            168
#define IDS_IDCANCEL                        169
#define IDS_FIXED                           170
#define IDS_IMPURE                          171
#define IDS_PRELOAD                         172
#define IDS_DISCARDABLE                     173
#define IDS_BEGIN                           174
#define IDS_END                             175
#define IDS_DIALOG                          176
#define IDS_CONTROL                         177
#define IDS_NOT                             178
#define IDS_STYLE                           179
#define IDS_CAPTION                         180
#define IDS_FONT                            181
#define IDS_DLGINCLUDE                      182
#define IDS_CLASS                           183
#define IDS_MENU                            184
#define IDS_EXSTYLE                         185
#define IDS_CHARACTERISTICS                 186
#define IDS_VERSION                         187
#define IDS_LANGUAGE                        188
#define IDS_KEYRADIOBUTTON                  189
#define IDS_KEYCHECKBOX                     190
#define IDS_KEYDEFPUSHBUTTON                191
#define IDS_KEYPUSHBUTTON                   192
#define IDS_KEYEDITTEXT                     193
#define IDS_KEYICON                         194
#define IDS_KEYGROUPBOX                     195
#define IDS_KEYRTEXT                        196
#define IDS_KEYCTEXT                        197
#define IDS_KEYLTEXT                        198
#define IDS_KEYLISTBOX                      199
#define IDS_KEYCOMBOBOX                     200
#define IDS_KEYSCROLLBAR                    201
#define IDS_KEYAUTO3STATE                   202
#define IDS_KEYAUTOCHECKBOX                 203
#define IDS_KEYAUTORADIOBUTTON              204
#define IDS_KEYSTATE3                       205
#define IDS_KEYUSERBUTTON                   206

/*
 * Message string id's.
 */
#define IDS_DELETEDIALOG                    207
#define IDS_OUTOFMEMORY                     208
#define IDS_CANTCREATE                      209
#define IDS_SYMNOCHANGE                     210
#define IDS_IDSYMMISMATCH                   211
#define IDS_CLOSING                         212
#define IDS_BADRESFILE                      213
#define IDS_INCLCLOSING                     214
#define IDS_SYMEXISTS                       215
#define IDS_BADSYMBOLID                     216
#define IDS_LABELDUPID                      217
#define IDS_SELECTFIRST                     218
#define IDS_CTRLDUPID                       219
#define IDS_BADCUSTDLL                      220
#define IDS_NOCLIP                          221
#define IDS_INTERNAL                        222
#define IDS_NOMOUSE                         223
#define IDS_NOINIT                          224
#define IDS_GTZERO                          225
#define IDS_ICONNAMEHASBLANKS               226
#define IDS_IDUPIDS                         227
#define IDS_CREATECTRLERROR                 228
#define IDS_CANTOPENRES                     229
#define IDS_CONFIRMDISCARD                  230
#define IDS_SYMNOTFOUND                     231
#define IDS_NOCLASS                         232
#define IDS_POSITIVENUM                     233
#define IDS_MEMERROR                        234
#define IDS_DLGNAMEHASBLANKS                235
/*#define IDS_                                236 */
#define IDS_NODLGNAME                       237
#define IDS_CANTINITDLL                     238
#define IDS_NOICONNAME                      239
#define IDS_RESTOREDIALOG                   240
#define IDS_ZEROPOINTSIZE                   241
#define IDS_MINGTMAXSPACE                   242
#define IDS_CUSTCNTLINUSE                   243
#define IDS_CUSTALREADYLOADED               244
#define IDS_CANTLOADDLL                     245
#define IDS_DLLBADEXPORTS                   246
#define IDS_DLLBADCOUNT                     247

/*
 * Language and SubLanguage description strings.
 */
#define IDS_L_NEUTRAL                       248
#define IDS_L_ALBANIAN                      249
#define IDS_L_ARABIC                        250
#define IDS_L_BAHASA                        251
#define IDS_L_BULGARIAN                     252
#define IDS_L_CATALAN                       253
#define IDS_L_CHINESE                       254
#define IDS_L_CZECH                         255
#define IDS_L_DANISH                        256
#define IDS_L_DUTCH                         257
#define IDS_L_ENGLISH                       258
#define IDS_L_FINNISH                       259
#define IDS_L_FRENCH                        260
#define IDS_L_GERMAN                        261
#define IDS_L_GREEK                         262
#define IDS_L_HEBREW                        263
#define IDS_L_HUNGARIAN                     264
#define IDS_L_ICELANDIC                     265
#define IDS_L_ITALIAN                       266
#define IDS_L_JAPANESE                      267
#define IDS_L_KOREAN                        268
#define IDS_L_NORWEGIAN                     269
#define IDS_L_POLISH                        270
#define IDS_L_PORTUGUESE                    271
#define IDS_L_RHAETO_ROMAN                  272
#define IDS_L_ROMANIAN                      273
#define IDS_L_RUSSIAN                       274
#define IDS_L_SERBO_CROATIAN                275
#define IDS_L_SLOVAK                        276
#define IDS_L_SPANISH                       277
#define IDS_L_SWEDISH                       278
#define IDS_L_THAI                          279
#define IDS_L_TURKISH                       280
#define IDS_L_URDU                          281

#define IDS_SL_DEFAULT                      282
#define IDS_SL_NEUTRAL                      283
#define IDS_SL_CHINESE_SIMPLIFIED           284
#define IDS_SL_CHINESE_TRADITIONAL          285
#define IDS_SL_DUTCH                        286
#define IDS_SL_DUTCH_BELGIAN                287
#define IDS_SL_ENGLISH_US                   288
#define IDS_SL_ENGLISH_UK                   289
#define IDS_SL_ENGLISH_AUS                  290
#define IDS_SL_ENGLISH_CAN                  291
#define IDS_SL_FRENCH                       292
#define IDS_SL_FRENCH_BELGIAN               293
#define IDS_SL_FRENCH_CANADIAN              294
#define IDS_SL_FRENCH_SWISS                 295
#define IDS_SL_GERMAN                       296
#define IDS_SL_GERMAN_SWISS                 297
#define IDS_SL_ITALIAN                      298
#define IDS_SL_ITALIAN_SWISS                299
#define IDS_SL_NORWEGIAN_BOKMAL             300
#define IDS_SL_NORWEGIAN_NYNORSK            301
#define IDS_SL_PORTUGUESE                   302
#define IDS_SL_PORTUGUESE_BRAZILIAN         303
#define IDS_SL_SERBO_CROATIAN_CYRILLIC      304
#define IDS_SL_SERBO_CROATIAN_LATIN         305
#define IDS_SL_SPANISH                      306
#define IDS_SL_SPANISH_MEXICAN              307
#define IDS_SL_SPANISH_MODERN               308

/*
 * Miscellaneous string id's.
 */
#define IDS_GRID                            309
#define IDS_MARGIN                          310
#define IDS_CTRLSPACING                     311
#define IDS_PUSHSPACING                     312
#define IDS_DEFLBTEXT                       313
#define IDS_APPNAME                         314
#define IDS_DLGEDIT                         315
#define IDS_POUNDDEFINE                     316
#define IDS_UNTITLED                        317
#define IDS_UNUSED                          318
#define IDS_HELPFILE                        319
#define IDS_DEFSAVENAME                     320
#define IDS_TEMPEXT                         321
#define IDS_CYFMTSTR                        322
#define IDS_CXFMTSTR                        323
#define IDS_DEFRESFILESPECNAME              324
#define IDS_DEFRESFILESPEC                  325
#define IDS_DEFINCFILESPECNAME              326
#define IDS_DEFINCFILESPEC                  327
#define IDS_DEFDLLFILESPECNAME              328
#define IDS_DEFDLLFILESPEC                  329
#define IDS_RESOPENTITLE                    330
#define IDS_RESSAVETITLE                    331
#define IDS_INCOPENTITLE                    332
#define IDS_INCSAVETITLE                    333
#define IDS_DLLOPENTITLE                    334
#define IDS_DEFINE                          335
#define IDS_DEFTXTCHECKBOX                  336
#define IDS_DEFTXTGROUP                     337
#define IDS_DEFTXTPUSHBUTTON                338
#define IDS_DEFTXTRADIOBUTTON               339
#define IDS_DEFTXTTEXT                      340
#define IDS_DEFTXTDIALOG                    341
#define IDS_WCBUTTON                        342
#define IDS_WCSCROLLBAR                     343
#define IDS_WCEDIT                          344
#define IDS_WCSTATIC                        345
#define IDS_WCLISTBOX                       346
#define IDS_WCCOMBOBOX                      347
#define IDS_WCCUSTOM                        348
#define IDS_WCDIALOG                        349
#define IDS_DEFFONTNAME                     350
#define IDS_INCEXT                          351
#define IDS_RESEXT                          352
#define IDS_DLLEXT                          353
#define IDS_DOTH                            354
#define IDS_DOTRES                          355
#define IDS_DOTDLG                          356
#define IDS_DLGEDITINI                      357
#define IDS_SYMBOLLABEL                     358
#define IDS_TEXTLABEL                       359
#define IDS_CAPTIONLABEL                    360
#define IDS_DLGNAMELABEL                    361
#define IDS_DLGIDLABEL                      362
#define IDS_ICONNAMELABEL                   363
#define IDS_ICONIDLABEL                     364
#define IDS_WIDTH                           365
#define IDS_HEIGHT                          366

#define CSTRINGS                367     /* Count of strings in string table.*/

/*
 * Menu id's. --------------------------------------------------------------
 */

/*
 * File menu.
 */
#define MENU_NEWRES             7000
#define MENU_OPEN               7001
#define MENU_SAVE               7002
#define MENU_SAVEAS             7003
#define MENU_SETINCLUDE         7004
#define MENU_NEWCUST            7005
#define MENU_OPENCUST           7006
#define MENU_REMCUST            7007
#define MENU_EXIT               7008

/*
 * Edit menu.
 */
#define MENU_RESTOREDIALOG      7020
#define MENU_CUT                7021
#define MENU_COPY               7022
#define MENU_PASTE              7023
#define MENU_DELETE             7024
#define MENU_DUPLICATE          7025
#define MENU_SYMBOLS            7026
#define MENU_STYLES             7027
#define MENU_SIZETOTEXT         7028
#define MENU_NEWDIALOG          7029
#define MENU_SELECTDIALOG       7030

/*
 * Arrange menu.
 */
#define MENU_ALIGNLEFT          7040
#define MENU_ALIGNVERT          7041
#define MENU_ALIGNRIGHT         7042
#define MENU_ALIGNTOP           7043
#define MENU_ALIGNHORZ          7044
#define MENU_ALIGNBOTTOM        7045
#define MENU_SPACEHORZ          7046
#define MENU_SPACEVERT          7047
#define MENU_ARRSIZEWIDTH       7048
#define MENU_ARRSIZEHEIGHT      7049
#define MENU_ARRPUSHBOTTOM      7050
#define MENU_ARRPUSHRIGHT       7051
#define MENU_ORDERGROUP         7052
#define MENU_ARRSETTINGS        7053

/*
 * Options menu.
 */
#define MENU_TESTMODE           7060
#define MENU_HEXMODE            7061
#define MENU_TRANSLATE          7062
#define MENU_USENEWKEYWORDS     7063
#define MENU_SHOWTOOLBOX        7064

/*
 * Help menu.
 */
#define MENU_CONTENTS           7070
#define MENU_SEARCH             7071
#define MENU_ABOUT              7072

/*
 * Hidden menu commands (accessed by accelerators).
 */
#define MENU_HIDDEN_TOPROPBAR   7080
#define MENU_HIDDEN_TOTOOLBOX   7081

/*
 * Various resource id's. --------------------------------------------------
 */

#define IDICON_DLGEDIT          8000        // Application icon.
#define IDICON_ICON             8001        // Generic Icon control icon.
#define IDACCEL_MAIN            8002        // Accelerator table resource.
#define IDMENU_MAIN             8003        // Menu resource.

#define IDCUR_OUTSEL            8010
#define IDCUR_INSERT            8011
#define IDCUR_MOVE              8012
#define IDCUR_DROPTOOL          8013

#define IDBM_TABSTOP            8020
#define IDBM_DRAGHANDLE         8021
#define IDBM_DRAGHANDLE2        8022

/*
 * Alignment menu bitmaps.
 */
#define IDBM_ALEFT              8030
#define IDBM_AVERT              8031
#define IDBM_ARIGHT             8032
#define IDBM_ATOP               8033
#define IDBM_AHORZ              8034
#define IDBM_ABOTTOM            8035
#define IDBM_ASPCVERT           8036
#define IDBM_ASPCHORZ           8037
#define IDBM_ASZWIDTH           8038
#define IDBM_ASZHGHT            8039
#define IDBM_APBBOTTM           8040
#define IDBM_APBRIGHT           8041

/*
 * Toolbox button bitmaps.  The IDBM_TU* id's are for the "up"
 * (not depressed) bitmaps and the IDBM_TD* id's are for the "down"
 * (depressed) bitmaps.
 */
#define IDBM_TUPOINTR           8050
#define IDBM_TUTEXT             8051
#define IDBM_TUEDIT             8052
#define IDBM_TUGROUP            8053
#define IDBM_TUPUSH             8054
#define IDBM_TUCHECK            8055
#define IDBM_TURADIO            8056
#define IDBM_TUCOMBO            8057
#define IDBM_TULIST             8058
#define IDBM_TUHSCROL           8059
#define IDBM_TUVSCROL           8060
#define IDBM_TUFRAME            8061
#define IDBM_TURECT             8062
#define IDBM_TUICON             8063
#define IDBM_TUCUSTOM           8064

#define IDBM_TDPOINTR           8070
#define IDBM_TDTEXT             8071
#define IDBM_TDEDIT             8072
#define IDBM_TDGROUP            8073
#define IDBM_TDPUSH             8074
#define IDBM_TDCHECK            8075
#define IDBM_TDRADIO            8076
#define IDBM_TDCOMBO            8077
#define IDBM_TDLIST             8078
#define IDBM_TDHSCROL           8079
#define IDBM_TDVSCROL           8080
#define IDBM_TDFRAME            8081
#define IDBM_TDRECT             8082
#define IDBM_TDICON             8083
#define IDBM_TDCUSTOM           8084

/*
 * Control Type bitmaps for the Order/Group dialog.
 */
#define IDBM_CTTEXT             8090
#define IDBM_CTEDIT             8091
#define IDBM_CTGROUP            8092
#define IDBM_CTPUSH             8093
#define IDBM_CTCHECK            8094
#define IDBM_CTRADIO            8095
#define IDBM_CTCOMBO            8096
#define IDBM_CTLIST             8097
#define IDBM_CTHSCROL           8098
#define IDBM_CTVSCROL           8099
#define IDBM_CTFRAME            8100
#define IDBM_CTRECT             8101
#define IDBM_CTICON             8102
#define IDBM_CTCUSTOM           8103


INCLUDE.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 Header *******************************
* Module Name: include.c
*
* This module contains routines that manipulate the linked lists
* of labels (symbols plus id values).
*
* Functions:
*    AddLabel()
*    FindLabel()
*    FindID()
*    FindIDInRes()
*    DeleteLabel()
*    IsSymbol()
*    IDToLabel()
*    LabelToID()
*    FreeLabels()
*
* Comments:
*
* The following describes the linked lists of LABEL structures that
* contain all the symbols that are in the include file.  It is
* important that these structures and lists be maintained properly
* for the udpating of the include file to occur properly.
*
* The following fields are in the LABEL structure:
*
*   npNext   - points to * the next label in the list.  This is NULL
*              for the last link in the list.
*   pszLabel - points to a null terminated, allocated string that
*              has the symbol.  This needs to be free'd if the structure
*              is free'd.
*   id       - the current id value for this symbol.
*   idOrig   - the id value as read from the include file.  This
*              field is used to determine if the id value has been changed
*              or not, so it is the same as the id unless the user has
*              modified the id value of the symbol.
*   fpos     - the file pointer offset to the "#" in the "#define" in
*              the include file as it was read in.  This field is used
*              to determine where the "#define" line starts in the
*              file.  If the label is added by the user (it does not exist
*              in the include file) this field will be set to FPOS_MAX.
*   nValueOffset - the offset in bytes from the fpos value to the start of
*              the id value in the "#define" line in the include file.  This
*              will be ignored if fpos is set to FPOS_MAX.
*
* The order of the linked lists of labels are very important.  The order
* will be exactly the same as is read from the include file.  This
* allows any changes to be merged back out to the new  include file
* when it is saved.  If any labels are added by the user, they will be
* added to the end of the list.  The start of the new ones is detected
* by the first label with an fpos value of FPOS_MAX (which all the
* new ones should have).  Because the order of the new labels is not
* critical (they will be added to the end of the include file) the
* new labels are sorted by id value.  Because the id values given
* to dialogs and controls by default are ascending, this will tend to
* group dialogs labels and their associated control labels together.
*
* Linked lists of labels always come in pairs.  There is the linked
* list of current labels (ones read from the include file followed
* by labels added later), and there is also a separate linked list
* of "deleted" labels.  The deleted label list is required because
* when the include file is saved, the deleted labels must be removed
* from the include file, so the label structure for them (which
* contains their file offset and so on) must be kept around.  When
* the user deletes a label, it is removed from the current label
* linked list and added to the deleted label list.  The deleted label
* list MUST be kept in order by fpos, but if the label that is
* deleted is one that did not exist in the include file (its fpos
* was FPOS_MAX) then it does NOT have to be added to the deleted
* list, and can simply be free'd.  When the user adds a new label,
* the deleted list is searched first to see if the label was
* previously deleted.  If it was, it is removed from the deleted list
* and placed back in the current label list (sorted by fpos, of
* course).  If it is a new label, it is simply added to the new labels
* at the end of the list (sorted by id value).  This is why every
* function that takes a pointer to the head of a label list also
* takes a pointer to the head of a deleted label list.
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

/************************************************************************
* AddLabel
*
* This adds a symbol/label into the given include file list.  The deleted
* include list is first searched and if a deleted label is found with the
* same symbol, it is transfered back into the include list.  This is to
* handle the case where a user reads in an include file, deletes one of
* the labels then adds it back in later.
*
* The npLabelSkip parameter is for the special case of changing a
* label.  This is done by adding a new label then deleting the old
* one, so setting this parameter prevents a spurious "duplicate id"
* message during the add.
*
* The pfDups parameter can be used to set a flag when there is a
* duplicate symbol, or a symbol with the same id found in the include
* list.  If this parameter is NULL, nothing is returned and the appropriate
* error message is displayed if a dup is found.  If this parameter is not
* NULL, it is assumed to point to a BOOL that will be set to TRUE if either
* of these conditions is found.  The flag will NOT be set to FALSE if this
* condition is NOT found, so AddLabel can be used in a loop and when the
* loop is done, *pfDups will contain TRUE if there were any duplicates.
* Note that if pfDups is not NULL, the dup error messages will be supressed.
* This routine truncates pszLabel at the first space.  It can allocate
* memory for a LABEL and for its string. The pplHead and pplDelHead lists
* are updated.
*
* Arguments:
*     LPTSTR pszLabel      = The label to add.
*     INT id               = The id associated with rgchLabel.
*     DWORD fpos           = The file position in the include file where the
*                            "#define" for this label starts, or FPOS_MAX
*                            if the label was not read from an include file.
*     INT nValueOffset     = Offset from fpos where the id value begins.
*     NPLABEL *pplHead     = Pointer to the head of the include list to use.
*     NPLABEL *pplDelHead  = Pointer to the head of the deleted include list.
*     NPLABEL npLabelSkip  = If not NULL, points to a label to skip when
*                            checking for duplicates.
*     BOOL *pfDups         = Points to a BOOL that is set to TRUE if there
*                            is a duplicate symbol or id found.
*
* Returns:
*     Pointer to the allocated LABEL structure - NULL for an error.
*
************************************************************************/

NPLABEL AddLabel(
    LPTSTR pszLabel,
    INT id,
    DWORD fpos,
    INT nValueOffset,
    NPLABEL *pplHead,
    NPLABEL *pplDelHead,
    NPLABEL npLabelSkip,
    BOOL *pfDups)
{
    register NPLABEL npTmp;
    NPLABEL npLabel;
    NPLABEL npPrevLabel;
    BOOL fFoundDeleted = FALSE;

    /*
     * First check for a duplicate id or symbol.
     */
    for (npTmp = *pplHead; npTmp; npTmp = npTmp->npNext) {
        if ((npTmp->id == id || lstrcmp(pszLabel, npTmp->pszLabel) == 0) &&
                npTmp != npLabelSkip) {
            if (pfDups) {
                *pfDups = TRUE;
            }
            else {
                if (npTmp->id == id)
                    Message(MSG_LABELDUPID);
                else
                    Message(MSG_SYMEXISTS);
            }

            return NULL;
        }
    }

    /*
     * Search for this symbol in the deleted list first.
     */
    npPrevLabel = NULL;
    for (npLabel = *pplDelHead; npLabel; npLabel = npLabel->npNext) {
        if (lstrcmp(pszLabel, npLabel->pszLabel) == 0) {
            fFoundDeleted = TRUE;
            break;
        }

        npPrevLabel = npLabel;
    }

    /*
     * Was the label found in the deleted list?
     */
    if (fFoundDeleted) {
        /*
         * Close up the deleted list where the deleted label was.
         */
        if (npPrevLabel)
            npPrevLabel->npNext = npLabel->npNext;
        else
            *pplDelHead = npLabel->npNext;

        /*
         * Set the id in case the user is adding the same symbol
         * but with a different id.
         */
        npLabel->id = id;

        /*
         * Search for where the label should be inserted
         * based on its fpos.
         */
        npPrevLabel = NULL;
        for (npTmp = *pplHead; npTmp; npTmp = npTmp->npNext) {
            if (npTmp->fpos == FPOS_MAX || npTmp->fpos > npLabel->fpos)
                break;

            npPrevLabel = npTmp;
        }
    }
    else {
        /*
         * Label was not found in the deleted list.  Allocate, etc.
         */
        if (!(npLabel = (NPLABEL)MyAlloc(sizeof(LABEL))))
            return NULL;

        npLabel->id = id;
        npLabel->idOrig = id;
        npLabel->fpos = fpos;
        npLabel->nValueOffset = nValueOffset;

        if (!(npLabel->pszLabel =
                (LPTSTR)MyAlloc((lstrlen(pszLabel) + 1) * sizeof(TCHAR)))) {
            MyFree(npLabel);
            return NULL;
        }

        lstrcpy(npLabel->pszLabel, pszLabel);

        /*
         * Find where to insert the new label.  This will either be
         * at the end of the list, or in ascending numerical order
         * among the new labels.
         */
        npPrevLabel = NULL;
        for (npTmp = *pplHead;
                npTmp && (npTmp->fpos != FPOS_MAX || npTmp->id < id);
                npTmp = npTmp->npNext)
            npPrevLabel = npTmp;
    }

    /*
     * At this point, npLabel points to the label to add, either
     * transferred from the deleted list, or allocated fresh.
     * The variable npPrevLabel points to the label to insert
     * after, or is NULL to indicate that the new label should
     * be inserted at the head of the list.
     */

    /*
     * If this is the first label in the list, or if the
     * first label had a greater fpos than the new label,
     * insert the new label at the head of the list.
     */
    if (!npPrevLabel) {
        npLabel->npNext = *pplHead;
        *pplHead = npLabel;
    }
    /*
     * Otherwise, insert it either in the middle of the
     * list or at the end.
     */
    else {
        npLabel->npNext = npPrevLabel->npNext;
        npPrevLabel->npNext = npLabel;
    }

    return npLabel;
}

/************************************************************************
* FindLabel
*
* Tells you if the named label is in the given include label list.
*
* Arguments:
*     LPTSTR pszLabel = The label to find.
*     NPLABEL plHead  = Head of the include list to traverse.
*
* Returns:
*     NULL if the label is not found.
*     Pointer to label structure if the label was found.
*
*
************************************************************************/

NPLABEL FindLabel(
    LPTSTR pszLabel,
    NPLABEL plHead)
{
    NPLABEL npLabel;

    for (npLabel = plHead; npLabel; npLabel = npLabel->npNext) {
        if (lstrcmp(pszLabel, npLabel->pszLabel) == 0)
            break;
    }

    return npLabel;
}

/************************************************************************
* FindID
*
* Tells you if the named id is in the given include file buffer.
*
* Arguments:
*     INT id         = The id to find.
*     NPLABEL plHead = Head of the label list to use.
*
* Returns:
*     NULL if the id was not found.
*     Pointer to label struct if the id was found.
*
*
************************************************************************/

NPLABEL FindID(
    INT id,
    NPLABEL plHead)
{
    NPLABEL npLabel;

    for (npLabel = plHead; npLabel; npLabel = npLabel->npNext) {
        if (npLabel->id == id)
            break;
    }

    return npLabel;
}

/************************************************************************
* FindIDInRes
*
* Tells you if the named id is used by any control in the current
* resource list.  This also includes searching through the dialog
* currently being edited, if there is one.
*
* Arguments:
*   INT id = The id to find.
*
* Returns:
*   TRUE if the id was found, or FALSE if it was not.
*
*
************************************************************************/

BOOL FindIDInRes(
    INT id)
{
    INT cControls;
    PRESLINK prl;
    PRES pRes;
    PDIALOGBOXHEADER pdbh;
    PCONTROLDATA pcd;
    NPCTYPE npc;
    BOOL fFound = FALSE;

    /*
     * Is there a current dialog?  If so, search it first and
     * we will skip any image for it in the resource list (the
     * resource list is probably out of date, anyways).
     */
    if (gfEditingDlg) {
        /*
         * Is the id the same as the current dialog's name?
         */
        if (IsOrd(gcd.pszDlgName) && id == (INT)OrdID(gcd.pszDlgName))
            return TRUE;

        /*
         * Loop through the current controls, looking for an id match.
         */
        for (npc = npcHead; npc; npc = npc->npcNext)
            if (npc->id == id)
                return TRUE;
    }

    for (prl = gprlHead; prl && !fFound; prl = prl->prlNext) {
        /*
         * Is this a dialog resource and is it NOT the current
         * dialog being edited?  If it is the current dialog,
         * we skip it because it is probably out of date.
         */
        if (prl->fDlgResource && prl != gcd.prl) {
            if (IsOrd(prl->pszName) && id == (INT)OrdID(prl->pszName)) {
                fFound = TRUE;
            }
            else {
                pRes = (PRES)GlobalLock(prl->hRes);
                pdbh = (PDIALOGBOXHEADER)SkipResHeader(pRes);
                cControls = (INT)pdbh->NumberOfItems;
                pcd = SkipDialogBoxHeader(pdbh);
                while (cControls--) {
                    if (id == (INT)pcd->wId) {
                        fFound = TRUE;
                        break;
                    }

                    pcd = SkipControlData(pcd);
                }

                GlobalUnlock(prl->hRes);
            }
        }
    }

    return fFound;
}

/************************************************************************
* DeleteLabel
*
* Removes the LABEL with text pszLabel from the list of labels in
* pplHead, closing up the link, and might add it to the deleted list.
*
* If the label is one that exists in the include file (fpos is valid)
* then the label is added to the pplDelHead list in the proper position
* (sorted ascending by fpos).  If the label does not exist in the
* include file, there is no need to track it and it can be tossed.
*
* Arguments:
*     LPTSTR pszLabel     = The text of the label to delete.
*     NPLABEL *pplHead    = Pointer to the head of the include list to use.
*     NPLABEL *pplDelHead = Pointer to the head of the deleted include list.
*
* Side Effects:
*     Deletes from the pplHead list.
*     Can null *pplHead if the last label is deleted.
*     Can add to the pplDelHead list.
*     Can free the memory associated with the LABEL and its string.
*
*
************************************************************************/

VOID DeleteLabel(
    LPTSTR pszLabel,
    NPLABEL *pplHead,
    NPLABEL *pplDelHead)
{
    NPLABEL npLabel;
    NPLABEL npDelLabel;
    NPLABEL npPrevLabel;

    npPrevLabel = NULL;
    for (npLabel = *pplHead; npLabel; npLabel = npLabel->npNext) {
        if (lstrcmp(pszLabel, npLabel->pszLabel) == 0) {
            /*
             * Close up the linked list where the deleted label was.
             */
            if (npPrevLabel)
                npPrevLabel->npNext = npLabel->npNext;
            else
                *pplHead = npLabel->npNext;

            /*
             * Is this a label that is NOT in the include file?
             * If so, just toss it away.
             */
            if (npLabel->fpos == FPOS_MAX) {
                MyFree(npLabel->pszLabel);
                MyFree(npLabel);
            }
            /*
             * Otherwise, it must be added to the deleted list.
             */
            else {
                /*
                 * Search for where the label should be inserted
                 * based on its fpos.
                 */
                npPrevLabel = NULL;
                for (npDelLabel = *pplDelHead; npDelLabel;
                        npDelLabel = npDelLabel->npNext) {
                    if (npDelLabel->fpos > npLabel->fpos)
                        break;

                    npPrevLabel = npDelLabel;
                }

                /*
                 * If this is the first label in the deleted list, or
                 * if the first label had a greater fpos than the new
                 * label, insert the new label at the head of the list.
                 */
                if (!npPrevLabel) {
                    npLabel->npNext = *pplDelHead;
                    *pplDelHead = npLabel;
                }
                /*
                 * Otherwise, insert it either in the middle of the
                 * list or at the end.
                 */
                else {
                    npLabel->npNext = npPrevLabel->npNext;
                    npPrevLabel->npNext = npLabel;
                }
            }

            break;
        }

        npPrevLabel = npLabel;
    }
}

/****************************************************************************
* IsSymbol
*
* This routine returns TRUE if the given string is a valid "C" or "RC"
* identifier.
*
* Valid is:  First char is a letter or '_'.
*
****************************************************************************/

BOOL IsSymbol(
    LPTSTR pszSym)
{
    register TCHAR ch = *pszSym;

    return ((ch >= CHAR_CAP_A && ch <= CHAR_CAP_Z) ||
            (ch >= CHAR_A && ch <= CHAR_Z) ||
            (ch == CHAR_UNDERLINE));
}

/************************************************************************
* IDToLabel
*
* This function finds the label with the given id.
* The first LABEL in the list with the given id will be found.
* The label will be put in pchLabel.
* If the id was not found then the id is converted to an ascii
* representation and put in pchLabel.  This ascii representation
* will be in hex notation if hex mode is in effect (unless fHexOK
* is FALSE).
*
* This function special cases the IDOK and IDCANCEL id values.
* If there happens to be a label in the include file for these values
* then that label will be returned, but if not, either "IDOK" or
* "IDCANCEL" will be returned.
*
* Arguments:
*   LPTSTR pchLabel - Where to put the label.
*   INT id          - The id of the label to find or match.
*   BOOL fHexOK     - TRUE if hex representations of the id are allowed.
*
*
************************************************************************/

VOID IDToLabel(
    LPTSTR pchLabel,
    INT id,
    BOOL fHexOK)
{
    NPLABEL npLabel;

    npLabel = FindID(id, plInclude);

    if (npLabel) {
        lstrcpy(pchLabel, npLabel->pszLabel);
    }
    else {
        if (id == IDOK && !FindLabel(ids(IDS_IDOK), plInclude)) {
            lstrcpy(pchLabel, ids(IDS_IDOK));
        }
        else if (id == IDCANCEL && !FindLabel(ids(IDS_IDCANCEL), plInclude)) {
            lstrcpy(pchLabel, ids(IDS_IDCANCEL));
        }
        else {
            if (fHexOK)
                Myitoa(id, pchLabel);
            else
                itoaw(id, pchLabel, 10);
        }
    }
}

/************************************************************************
* LabelToID
*
* This function converts a label string to its associated id value.
* It first checks the labels in the current include file for a
* match.  If it is not found, it then checks for some special values,
* like "IDOK", "IDCANCEL" and "(Unused)".
*
* The return value will be TRUE of the label (symbol) was found, or
* FALSE if it was not.
*
* Arguments:
*   LPTSTR pszLabel - The symbol to search for.
*   PINT pID        - Where to return the associated id, if found.
*
************************************************************************/

BOOL LabelToID(
    LPTSTR pszLabel,
    PINT pID)
{
    INT id;
    NPLABEL npLabel;

    /*
     * Is it an existing label?
     */
    if (npLabel = FindLabel(pszLabel, plInclude)) {
        id = npLabel->id;
    }
    /*
     * Is it the "unused" symbol?
     */
    else if (lstrcmp(pszLabel, ids(IDS_UNUSED)) == 0) {
        id = IDUNUSED;
    }
    /*
     * How about the special IDOK entry?
     */
    else if (lstrcmp(pszLabel, ids(IDS_IDOK)) == 0) {
        id = IDOK;
    }
    /*
     * How about the special IDCANCEL entry?
     */
    else if (lstrcmp(pszLabel, ids(IDS_IDCANCEL)) == 0) {
        id = IDCANCEL;
    }
    else {
        return FALSE;
    }

    *pID = id;
    return TRUE;
}

/****************************************************************************
* FreeLabels
*
* This function frees the labels in the label list pointed to by
* nppLabels, including the strings.  When it is done, the label
* list head is set to NULL.
*
****************************************************************************/

VOID FreeLabels(
    NPLABEL *nppLabels)
{
    register NPLABEL npl;
    register NPLABEL nplTemp;

    npl = *nppLabels;

    while (npl) {
        MyFree(npl->pszLabel);

        nplTemp = npl->npNext;

        MyFree(npl);
        npl = nplTemp;
    }

    *nppLabels = NULL;
}


MENU.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 Header *******************************
* Module Name: menu.c
*
* Contains the main menu switching functions and the clipboard functions.
*
* Functions:
*   DialogMenu()
*   LoadMenuBitmaps()
*   FreeMenuBitmaps()
*   InitMenu()
*   MsgFilterHookFunc()
*   ShowHelp()
*   CopyToClipboard()
*   DlgInClipboard()
*   PasteFromClipboard()
*   GetHelpContext()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"

#include <string.h>

#define MM10PERINCH         254         // Tenths of a millimeter per inch.

typedef struct {
    WORD idm;
    INT idbm;
    HBITMAP hbm;
} BITMAPMENU;

STATICFN VOID CopyToClipboard(VOID);
STATICFN BOOL DlgInClipboard(VOID);
STATICFN VOID PasteFromClipboard(VOID);
STATICFN INT GetHelpContext(INT idSubject, PHELPMAP phmap);

static BITMAPMENU bmpmenuTable[] = {
    {MENU_ALIGNLEFT,        IDBM_ALEFT,         NULL },
    {MENU_ALIGNVERT,        IDBM_AVERT,         NULL },
    {MENU_ALIGNRIGHT,       IDBM_ARIGHT,        NULL },
    {MENU_ALIGNTOP,         IDBM_ATOP,          NULL },
    {MENU_ALIGNHORZ,        IDBM_AHORZ,         NULL },
    {MENU_ALIGNBOTTOM,      IDBM_ABOTTOM,       NULL },
    {MENU_SPACEVERT,        IDBM_ASPCVERT,      NULL },
    {MENU_SPACEHORZ,        IDBM_ASPCHORZ,      NULL },
    {MENU_ARRSIZEWIDTH,     IDBM_ASZWIDTH,      NULL },
    {MENU_ARRSIZEHEIGHT,    IDBM_ASZHGHT,       NULL },
    {MENU_ARRPUSHBOTTOM,    IDBM_APBBOTTM,      NULL },
    {MENU_ARRPUSHRIGHT,     IDBM_APBRIGHT,      NULL }
};

/************************************************************************
* DialogMenu
*
* This is the main switching function to send menu commands to the
* appropriate function.
*
* Arguments:
*   INT - the menu command
*
************************************************************************/

VOID DialogMenu(
    INT cmd)
{
    /*
     * Be sure any outstanding changes get applied without errors.
     */
    if (!StatusApplyChanges())
        return;

    switch (cmd) {

        /*
         * File menu ----------------------------------------------------
         */

        case MENU_NEWRES:
            if (DoWeSave(FILE_INCLUDE) == IDCANCEL ||
                    DoWeSave(FILE_RESOURCE) == IDCANCEL)
                break;

            FreeInclude();
            FreeRes();
            AddNewDialog();
            ShowFileStatus(TRUE);
            break;

        case MENU_OPEN:
            if (DoWeSave(FILE_INCLUDE) == IDCANCEL ||
                    DoWeSave(FILE_RESOURCE) == IDCANCEL)
                break;

            Open(FILE_RESOURCE);

            break;

        case MENU_SAVE:
            if (gfIncChged) {
                if (!Save(FILE_NOSHOW | FILE_INCLUDE))
                    break;
            }

            if (gfResChged)
                Save(FILE_NOSHOW | FILE_RESOURCE);

            break;

        case MENU_SAVEAS:
            /*
             * Save the include file, but only if there is one.
             */
            if (pszIncludeFile || plInclude) {
                if (!Save(FILE_NOSHOW | FILE_INCLUDE | FILE_SAVEAS))
                    break;
            }

            /*
             * Save the resource file.
             */
            Save(FILE_NOSHOW | FILE_RESOURCE | FILE_SAVEAS);

            break;

        case MENU_NEWCUST:
            DlgBox(DID_NEWCUST, (WNDPROC)NewCustDlgProc);
            break;

        case MENU_OPENCUST:
            OpenCustomDialog();
            break;

        case MENU_REMCUST:
            DlgBox(DID_REMCUST, (WNDPROC)RemCustDlgProc);
            break;

        case MENU_SETINCLUDE:
            if (DoWeSave(FILE_INCLUDE) != IDCANCEL)
                Open(FILE_INCLUDE);

            break;

        case MENU_EXIT:
            PostMessage(ghwndMain, WM_CLOSE, 0, 0L);
            break;

        /*
         * Edit menu ----------------------------------------------------
         */

        case MENU_RESTOREDIALOG:
            RestoreDialog();
            break;

        case MENU_CUT:
        case MENU_COPY:
            if (gfEditingDlg) {
                /*
                 * Save current stuff in clipboard.
                 */
                CopyToClipboard();

                /*
                 * Clear the selection if they did a "cut" instead of
                 * a "copy".
                 */
                if (cmd == MENU_CUT)
                    DeleteControl();
            }

            break;

        case MENU_PASTE:
            PasteFromClipboard();
            break;

        case MENU_DELETE:
            DeleteControl();
            break;

        case MENU_DUPLICATE:
            Duplicate();
            break;

        case MENU_SYMBOLS:
            ViewInclude();
            break;

        case MENU_STYLES:
            StylesDialog();
            break;

        case MENU_SIZETOTEXT:
            SizeToText();
            break;

        case MENU_NEWDIALOG:
            AddNewDialog();
            break;

        case MENU_SELECTDIALOG:
            SelectDialogDialog();
            break;

        /*
         * Arrange menu -------------------------------------------------
         */

        case MENU_ALIGNLEFT:
        case MENU_ALIGNVERT:
        case MENU_ALIGNRIGHT:
        case MENU_ALIGNTOP:
        case MENU_ALIGNHORZ:
        case MENU_ALIGNBOTTOM:
            AlignControls(cmd);
            break;

        case MENU_SPACEVERT:
        case MENU_SPACEHORZ:
            ArrangeSpacing(cmd);
            break;

        case MENU_ARRSIZEWIDTH:
        case MENU_ARRSIZEHEIGHT:
            ArrangeSize(cmd);
            break;

        case MENU_ARRPUSHBOTTOM:
        case MENU_ARRPUSHRIGHT:
            ArrangePushButtons(cmd);
            break;

        case MENU_ORDERGROUP:
            OrderGroupDialog();
            break;

        case MENU_ARRSETTINGS:
            ArrangeSettingsDialog();
            break;

        /*
         * Options menu -------------------------------------------------
         */

        case MENU_TESTMODE:
            if (gfTestMode)
                DestroyTestDialog();
            else
                CreateTestDialog();

            break;

        case MENU_HEXMODE:
            /*
             * Flip the flag, and update the status display so that
             * the id value will be displayed in the new format.
             */
            gfHexMode = gfHexMode ? FALSE : TRUE;
            StatusUpdate();
            break;

        case MENU_TRANSLATE:
            /*
             * Flip the flag, and set the enable state of the fields
             * in the status window.  Changing the translate mode can
             * effect whether they are allowed to change ids of controls.
             */
            gfTranslateMode = gfTranslateMode ? FALSE : TRUE;

            /*
             * If they turned on Translate mode, reset the tool and
             * hide the Toolbox.  Otherwise, show the toolbox if they
             * want it shown.
             */
            if (gfTranslateMode) {
                ToolboxShow(FALSE);
                ToolboxSelectTool(W_NOTHING, FALSE);
            }
            else if (gfShowToolbox) {
                ToolboxShow(TRUE);
            }

            StatusSetEnable();
            break;

        case MENU_USENEWKEYWORDS:
            /*
             * Flip the flag.
             */
            gfUseNewKeywords = gfUseNewKeywords ? FALSE : TRUE;
            break;

        case MENU_SHOWTOOLBOX:
            /*
             * Toggle the state of the Toolbox.
             */
            gfShowToolbox = gfShowToolbox ? FALSE : TRUE;
            ToolboxShow(gfShowToolbox);

            break;

        /*
         * Help menu ----------------------------------------------------
         */

        case MENU_CONTENTS:
            WinHelp(ghwndMain, gszHelpFile, HELP_CONTENTS, 0L);
            break;

        case MENU_SEARCH:
            /*
             * Tell winhelp to be sure this app's help file is current,
             * then invoke a search with an empty starting key.
             */
            WinHelp(ghwndMain, gszHelpFile, HELP_FORCEFILE, 0);
            WinHelp(ghwndMain, gszHelpFile, HELP_PARTIALKEY, (DWORD)szEmpty);
            break;

        case MENU_ABOUT:
            DlgBox(DID_ABOUT, (WNDPROC)AboutDlgProc);
            break;

        /*
         * Hidden menu commands (accessed by accelerators) --------------
         */

        case MENU_HIDDEN_TOTOOLBOX:
            if (ghwndToolbox && IsWindowVisible(ghwndToolbox))
                SetFocus(ghwndToolbox);

            break;

        case MENU_HIDDEN_TOPROPBAR:
            SetFocus(hwndStatus);
            break;
    }
}

/************************************************************************
* LoadMenuBitmaps
*
* This function loads and inserts the menu items that are bitmaps.
* This has to be done at runtime because windows does not have a
* way to specify bitmap menu items in the rc file.
*
* Arguments:
*   HMENU hMenu - The menu handle.
*
************************************************************************/

VOID LoadMenuBitmaps(
    HMENU hMenu)
{
    INT i;

    for (i = 0; i < sizeof(bmpmenuTable) / sizeof(BITMAPMENU); i++) {
        bmpmenuTable[i].hbm =
                LoadBitmap(ghInst, MAKEINTRESOURCE(bmpmenuTable[i].idbm));

        ModifyMenu(hMenu, bmpmenuTable[i].idm,
                MF_BYCOMMAND | MF_BITMAP, bmpmenuTable[i].idm,
                (LPTSTR)(DWORD)bmpmenuTable[i].hbm);
    }
}

/************************************************************************
* FreeMenuBitmaps
*
* This function frees the menu bitmaps that were loaded by
* LoadMenuBitmaps.  This function should be called only when
* the application is exiting, because it frees the bitmaps
* without removing them from the menu first.
*
************************************************************************/

VOID FreeMenuBitmaps(VOID)
{
    INT i;

    for (i = 0; i < sizeof(bmpmenuTable) / sizeof(BITMAPMENU); i++)
        if (bmpmenuTable[i].hbm)
            DeleteObject(bmpmenuTable[i].hbm);
}

/************************************************************************
* InitMenu
*
* This function grays/enables and checks/unchecks the menu items
* appropriately for the given state.
*
* Arguments:
*   HMENU hMenu - The menu handle.
*
************************************************************************/

VOID InitMenu(
    HMENU hMenu)
{
    register INT i;
    BOOL fEnable;
    NPCTYPE npc;
    HMENU hMenuArrange;

    MyEnableMenuItem(hMenu, MENU_NEWRES, !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_SAVE,
            (gfEditingDlg || gprlHead) && (gfResChged || gfIncChged));

    MyEnableMenuItem(hMenu, MENU_SAVEAS, gfEditingDlg || gprlHead);

    MyEnableMenuItem(hMenu, MENU_SETINCLUDE,
            (gfEditingDlg || gprlHead) && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_REMCUST, gpclHead);

    MyEnableMenuItem(hMenu, MENU_RESTOREDIALOG, gfDlgChanged && gcd.prl);

    MyEnableMenuItem(hMenu, MENU_CUT,
            gnpcSel && gfEditingDlg && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_COPY, gnpcSel && gfEditingDlg);

    MyEnableMenuItem(hMenu, MENU_PASTE, !gfTranslateMode &&
            IsClipboardFormatAvailable(fmtDlg) &&
            (gfEditingDlg || DlgInClipboard()));

    MyEnableMenuItem(hMenu, MENU_DELETE, gnpcSel && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_DUPLICATE, gnpcSel && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_SYMBOLS,
            (gfEditingDlg || gprlHead) && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_STYLES, gnpcSel && !gfTranslateMode);

    /*
     * For the "Size to text" menu command to be enabled, there
     * must be at least one control selected, and one of the
     * controls selected has to be able to be sized to its text.
     */
    fEnable = FALSE;
    if (gcSelected) {
        for (npc = npcHead; npc; npc = npc->npcNext) {
            if (npc->fSelected && npc->pwcd->fSizeToText) {
                fEnable = TRUE;
                break;
            }
        }
    }

    MyEnableMenuItem(hMenu, MENU_SIZETOTEXT, fEnable);

    MyEnableMenuItem(hMenu, MENU_NEWDIALOG, !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_SELECTDIALOG, gfEditingDlg || gprlHead);

    hMenuArrange = GetSubMenu(hMenu, MENUPOS_ARRANGE);

    MyEnableMenuItemByPos(hMenuArrange, MENUPOS_ARRANGEALIGN, gcSelected > 1);

    MyEnableMenuItemByPos(hMenuArrange, MENUPOS_ARRANGESPACE, gcSelected > 1);

    /*
     * For the "Same size" menu option to be enabled, there
     * must be more than one control selected, and they
     * must be sizeable controls.
     */
    fEnable = FALSE;
    if (gcSelected > 1) {
        for (i = 0, npc = npcHead; npc; npc = npc->npcNext) {
            if (npc->fSelected && npc->pwcd->fSizeable) {
                i++;

                if (i > 1) {
                    fEnable = TRUE;
                    break;
                }
            }
        }
    }

    MyEnableMenuItemByPos(hMenuArrange, MENUPOS_ARRANGESIZE, fEnable);

    /*
     * For the Arrange/Push buttons menu item to be enabled,
     * there must be a dialog up and it must have at least one
     * push button.  In addition, if there are control(s) other
     * than the dialog selected, at least one of the selected
     * controls must be a push button.
     */
    fEnable = FALSE;
    if (gfEditingDlg || gprlHead) {
        if (!gcSelected || gfDlgSelected) {
            for (npc = npcHead; npc; npc = npc->npcNext) {
                if (npc->pwcd->iType == W_PUSHBUTTON) {
                    fEnable = TRUE;
                    break;
                }
            }
        }
        else {
            for (npc = npcHead; npc; npc = npc->npcNext) {
                if (npc->pwcd->iType == W_PUSHBUTTON && npc->fSelected) {
                    fEnable = TRUE;
                    break;
                }
            }
        }
    }

    MyEnableMenuItemByPos(hMenuArrange, MENUPOS_ARRANGEPUSH, fEnable);

    MyEnableMenuItem(hMenu, MENU_ORDERGROUP,
            npcHead && !gfTranslateMode && !gfTestMode && cWindows > 1);

    MyEnableMenuItem(hMenu, MENU_TESTMODE, gfEditingDlg);
    MyCheckMenuItem(hMenu, MENU_TESTMODE, gfTestMode);

    MyEnableMenuItem(hMenu, MENU_HEXMODE, !gfTestMode);
    MyCheckMenuItem(hMenu, MENU_HEXMODE, gfHexMode);

    MyEnableMenuItem(hMenu, MENU_TRANSLATE, !gfTestMode);
    MyCheckMenuItem(hMenu, MENU_TRANSLATE, gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_USENEWKEYWORDS, !gfTestMode);
    MyCheckMenuItem(hMenu, MENU_USENEWKEYWORDS, gfUseNewKeywords);

    MyEnableMenuItem(hMenu, MENU_SHOWTOOLBOX, !gfTestMode && !gfTranslateMode);
    MyCheckMenuItem(hMenu, MENU_SHOWTOOLBOX, gfShowToolbox);
}

/************************************************************************
* CopyToClipboard
*
* Puts the current dialog and a bitmap image of it into the clipboard.
* Gives a dialog box resource to the Clipboard.
* Gives a bit map to the clipboard.
* Clears everything else out of clipboard.
*
************************************************************************/

STATICFN VOID CopyToClipboard(VOID)
{
    INT cbRes;
    HANDLE hResClip;
    PRES lpRes;
    PRES pRes;
    HDC hdcSrc;
    HDC hdcDst;
    RECT rc;
    HBITMAP hbm;

    /*
     * Store the current selection in a dialog resource.
     */
    if (!(pRes = AllocDialogResource(FALSE, TRUE)))
        return;

    /*
     * Allocate global memory for it.
     */
    cbRes = ResourceSize(pRes);
    if (!cbRes || !(hResClip = GlobalAlloc(GHND | GMEM_DDESHARE, cbRes))) {
        MyFree(pRes);
        Message(MSG_OUTOFMEMORY);
        return;
    }

    /*
     * Copy it to the global memory.
     */
    lpRes = (PRES)GlobalLock(hResClip);
    memcpy(lpRes, pRes, cbRes);
    GlobalUnlock(hResClip);
    MyFree(pRes);

    /*
     * Now place it in the clipboard.
     */
    if (OpenClipboard(ghwndMain)) {
        EmptyClipboard();
        SetClipboardData(fmtDlg, hResClip);

        /*
         * If the dialog is selected, place a bitmap image of it
         * in the clipboard also.  The drag handles will be removed
         * first and the dialog will be activated so that the
         * image looks proper.
         */
        if (gfDlgSelected) {
            CancelSelection(FALSE);
            SetActiveWindow(gcd.npc->hwnd);
            UpdateWindow(gcd.npc->hwnd);

            if (hdcSrc = GetDC(NULL)) {
                GetWindowRect(gcd.npc->hwnd, &rc);
                if (hbm = CreateCompatibleBitmap(hdcSrc,
                        rc.right - rc.left, rc.bottom - rc.top)) {
                    if (hdcDst = CreateCompatibleDC(hdcSrc)) {
                        /*
                         * Calculate the dimensions of the bitmap and
                         * convert them to tenths of a millimeter for
                         * setting the size with the SetBitmapDimensionEx
                         * call.  This allows programs like WinWord to
                         * retrieve the bitmap and know what size to
                         * display it as.
                         */
                        SetBitmapDimensionEx(hbm,
                                ((rc.right - rc.left) * MM10PERINCH) /
                                GetDeviceCaps(hdcSrc, LOGPIXELSX),
                                ((rc.bottom - rc.top) * MM10PERINCH) /
                                GetDeviceCaps(hdcSrc, LOGPIXELSY),
                                NULL);

                        SelectObject(hdcDst, hbm);
                        BitBlt(hdcDst, 0, 0,
                                rc.right - rc.left, rc.bottom - rc.top,
                                hdcSrc, rc.left, rc.top, SRCCOPY);
                        DeleteDC(hdcDst);
                        SetClipboardData(CF_BITMAP, hbm);
                    }
                    else {
                        DeleteObject(hbm);
                    }
                }

                ReleaseDC(NULL, hdcSrc);
            }

            SetActiveWindow(ghwndMain);
            SelectControl(gcd.npc, FALSE);
        }

        CloseClipboard();
    }
    else {
        Message(MSG_NOCLIP);
    }
}

/************************************************************************
* DlgInClipboard
*
* This function returns TRUE if there is data in the clipboard in the
* dialog format, and this data is for a complete dialog, not just for
* a group of controls.
*
************************************************************************/

STATICFN BOOL DlgInClipboard(VOID)
{
    HANDLE hClip;
    PRES pRes;
    PDIALOGBOXHEADER pdbh;
    BOOL fDlgResFound = FALSE;

    if (!OpenClipboard(ghwndMain))
        return FALSE;

    if (hClip = GetClipboardData(fmtDlg)) {
        pRes = (PRES)GlobalLock(hClip);

        /*
         * If cx is CONTROLS_ONLY, then we know that we only
         * want to copy the controls in the template, not the
         * entire dialog plus controls.
         */
        pdbh = (PDIALOGBOXHEADER)SkipResHeader(pRes);
        if (pdbh->cx != CONTROLS_ONLY)
            fDlgResFound = TRUE;

        GlobalUnlock(hClip);
    }

    CloseClipboard();

    return fDlgResFound;
}

/************************************************************************
* PasteFromClipboard
*
* This routine pastes any data from the clipboard into the current
* resource file.  If the data represents a complete dialog, a new dialog
* is added.  If it represents some controls, the operation to drop them
* into the current dialog is begun.
*
************************************************************************/

STATICFN VOID PasteFromClipboard(VOID)
{
    HANDLE hClip;
    PRES pResClip;
    PRES pResCopy;
    INT cbRes;

    if (!OpenClipboard(ghwndMain)) {
        Message(MSG_NOCLIP);
        return;
    }

    if (hClip = GetClipboardData(fmtDlg)) {
        pResClip = (PRES)GlobalLock(hClip);
        cbRes = ResourceSize(pResClip);

        /*
         * Make a copy of the clipboard data.  This needs to be done
         * because we may need to drag the new controls for a while,
         * and it is rude to leave the clipboard open that long.
         */
        if (pResCopy = (PRES)MyAlloc(cbRes)) {
            memcpy((PBYTE)pResCopy, (PBYTE)pResClip, cbRes);

            /*
             * Now duplicate the dialog or controls in the clipboard.
             * The pResCopy buffer does NOT need to be freed here, because
             * it will be freed after the drag operation is complete.
             */
            MakeCopyFromRes(pResCopy);
        }

        GlobalUnlock(hClip);
    }

    CloseClipboard();
}

/************************************************************************
* MsgFilterHookFunc
*
* This is the exported message filter function that is hooked into
* the message stream for detecting the pressing of the F1 key, at
* which time it calls up the appropriate help.
*
************************************************************************/

BOOL APIENTRY MsgFilterHookFunc(
    INT nCode,
    WPARAM wParam,
    LPMSG lpMsg)
{
    if ((nCode == MSGF_MENU || nCode == MSGF_DIALOGBOX) &&
            (lpMsg->message == WM_KEYDOWN && lpMsg->wParam == VK_F1)) {
        /*
         * Display help.
         */
        ShowHelp((nCode == MSGF_MENU) ? TRUE : FALSE);

        /*
         * Tell Windows to swallow this message.
         */
        return 1;
    }

    return CallNextHookEx(ghhkMsgFilter, nCode, wParam, (LONG)lpMsg);
}

/************************************************************************
* ShowHelp
*
* This function is called when the user has requested help.  It will
* look at the menu state (if fMenuHelp is TRUE) or which dialog
* is currently up to determine the help topic, then it calls WinHelp.
*
* Arguments:
*   BOOL fMenuHelp - TRUE if this help is for a menu (help was requested
*                    in the menu modal loop).  If FALSE, general help
*                    or help for a dialog is assumed.
*
************************************************************************/

VOID ShowHelp(
    BOOL fMenuHelp)
{
    INT nHelpContext = 0;
    HWND hwndFocus;

    if (fMenuHelp) {
        nHelpContext = GetHelpContext(gMenuSelected, gahmapMenu);
    }
    else {
        /*
         * Look for help for the current dialog.
         */
        if (gidCurrentDlg) {
            nHelpContext = GetHelpContext(gidCurrentDlg, gahmapDialog);
        }
        else {
            /*
             * There is no current dialog.  Is the window with the
             * focus a control on the Properties Bar?
             */
            if ((hwndFocus = GetFocus()) && IsChild(hwndStatus, hwndFocus))
                nHelpContext = GetHelpContext(DID_STATUS, gahmapDialog);
        }
    }

    /*
     * If there is help context, display it.  Otherwise display
     * the Contents screen.
     */
    if (nHelpContext)
        WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT, nHelpContext);
    else
        WinHelp(ghwndMain, gszHelpFile, HELP_CONTENTS, 0L);
}

/************************************************************************
* GetHelpContext
*
* This function takes a subject and returns its matching help
* context id from the given HELPMAP table.
*
* Arguments:
*   INT idSubject   - ID of the subject to find the help context for.
*   PHELPMAP phmap  - The help map table.  It is assumed that the
*                     last entry in the table has a NULL subject id.
*
************************************************************************/

STATICFN INT GetHelpContext(
    INT idSubject,
    PHELPMAP phmap)
{
    while (phmap->idSubject) {
        if (phmap->idSubject == idSubject)
            return phmap->HelpContext;

        phmap++;
    }

    return 0;
}


ADDCTRL.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 Header *******************************
* Module Name: addctrl.c
*
* Contains routines for adding (creating) and deleting controls.
*
* Functions:
*
*    AddNewDialog()
*    DropControl()
*    AddControl()
*    CreateControl()
*    CreateDlgFont()
*    MyGetCharDimensions()
*    AdjustDefaultSizes()
*    DeleteControl()
*    DeleteDialog()
*    DeleteControl2()
*    FreeCTYPE()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"

#include <stdlib.h>
#include <string.h>

STATICFN HFONT CreateDlgFont(HWND hwnd, LPTSTR pszFontName,
    INT nPointSize);
STATICFN INT MyGetCharDimensions(HWND hwnd, HFONT hFont,
    PTEXTMETRIC ptm);
STATICFN VOID AdjustDefaultSizes(VOID);
STATICFN VOID DeleteControl2(NPCTYPE npcDel);
STATICFN VOID FreeCTYPE(NPCTYPE npc);

int CALLBACK GetFontCharSetEnumFunc(LPLOGFONT,LPTEXTMETRIC,int,LPARAM);
STATICFN BYTE NEAR GetFontCharSet(LPTSTR);

/************************************************************************
* AddNewDialog
*
* High level function to add a new dialog to the current resource.
* Any existing dialog will be saved away in the resource buffer.
* The dialog is created at a default position and size with default
* styles.
*
************************************************************************/

VOID AddNewDialog(VOID)
{
    RECT rc;

    if (gfEditingDlg) {
        if (!SynchDialogResource())
            return;

        DeleteDialog(FALSE);
    }

    /*
     * Now drop a new dialog window.
     */
    SetRect(&rc, DEFDIALOGXPOS, DEFDIALOGYPOS,
            DEFDIALOGXPOS + awcd[W_DIALOG].cxDefault,
            DEFDIALOGYPOS + awcd[W_DIALOG].cyDefault);
    DropControl(&awcd[W_DIALOG], &rc);
}

/************************************************************************
* DropControl
*
* This function drops a new control of Type at the specified
* location.  The default style and text of the control is
* determined from the awcd table based on its type.  The control
* is selected after being dropped.
*
*
* Arguments:
*   PWINDOWCLASSDESC pwcd - Describes the type of new control.
*   PRECT prc             - Rectangle of the new control (in dialog units).
*
************************************************************************/

VOID DropControl(
    PWINDOWCLASSDESC pwcd,
    PRECT prc)
{
    ORDINAL ordIcon;
    ORDINAL ordDlg;
    LPTSTR pszText;
    NPCTYPE npcNew;
    INT idCtrl;
    DIALOGINFO di;

    /*
     * Get the next available id to use for the new control.
     */
    idCtrl = NextID((pwcd->iType == W_DIALOG) ? NEXTID_DIALOG : NEXTID_CONTROL,
            plInclude, 0);

    if (pwcd->iType == W_ICON) {
        /*
         * For icon controls, the text is really an ordinal or name
         * of the icon resource to display.  We get the next available
         * id (skipping the id we just got for the control itself) to
         * use as an ordinal.
         */
        WriteOrd(&ordIcon, NextID(NEXTID_CONTROL, plInclude, idCtrl));
        pszText = (LPTSTR)&ordIcon;
    }
    else {
        pszText = pwcd->pszTextDefault;
    }

    /*
     * Make the control.
     */
    if (pwcd->iType == W_DIALOG) {
        /*
         * Pick a default name for the dialog.
         */
        WriteOrd(&ordDlg, NextID(NEXTID_DIALOG, plInclude, 0));

        di.fResFlags = DEFDLGMEMFLAGS;
//      di.wLanguage = GetUserDefaultLangID();
di.wLanguage = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);
        di.pszClass = NULL;
        di.pszMenu = NULL;
        di.DataVersion = 0;
        di.Version = 0;
        di.Characteristics = 0;
        di.nPointSize = DEFPOINTSIZE;
        lstrcpy(di.szFontName, ids(IDS_DEFFONTNAME));

        npcNew = AddControl(pwcd, pszText,
                pwcd->flStyles, pwcd->flExtStyle, idCtrl,
                prc->left, prc->top,
                prc->right - prc->left, prc->bottom - prc->top,
                (LPTSTR)&ordDlg, &di);
    }
    else {
        npcNew = AddControl(pwcd, pszText,
                pwcd->flStyles, pwcd->flExtStyle, idCtrl,
                prc->left, prc->top,
                prc->right - prc->left, prc->bottom - prc->top,
                NULL, NULL);
    }

    if (!npcNew)
        return;

    /*
     * If we just dropped a dialog, we need to now show it.
     * It it was some other control, mark the dialog as having
     * been changed.
     */
    if (pwcd->iType == W_DIALOG) {
        ShowWindow(npcNew->hwnd, SW_SHOWNA);
        ToolboxOnTop();
    }
    else {
        gfDlgChanged = TRUE;
    }

    SelectControl(npcNew, FALSE);

    gfResChged = TRUE;
    ShowFileStatus(FALSE);

    /*
     * Now we determine if one of the fields in the status ribbon
     * should be given the focus initially.  The assumption is that
     * there are some things that a user will always want to change
     * when dropping a new control, such as the text in a push
     * button, for example.
     */
    idCtrl = 0;
    switch (pwcd->iType) {
        case W_ICON:
            /*
             * For icons, the first thing the user will
             * probably want to do is to change the name.
             */
            idCtrl = DID_STATUSNAME;
            break;

        default:
            /*
             * If this control has text, they will probably want
             * to change it.  This includes the caption if the
             * control is a dialog.
             */
            if (pwcd->fHasText)
                idCtrl = DID_STATUSTEXT;

            break;
    }

    if (idCtrl) {
        SendDlgItemMessage(hwndStatus, idCtrl,
 EM_SETSEL, (WPARAM)(0), (LONG)(-1));
        SetFocus(GetDlgItem(hwndStatus, idCtrl));
    }
}

/************************************************************************
* AddControl
*
* This function is used to add a new control.  CreateControl() does
* half the work.
*
* Arguments:
*   PWINDOWCLASSDESC pwcd - Window class structure.  Describes the
*                           type of control to add.
*   LPTSTR pszText        - Text for the new control.
*   DWORD style           - Style of the new control.
*   DWORD flExtStyle      - Extended style of the new control.
*   INT id                - ID for the new control.
*   INT x                 - X location of the new control.
*   INT y                 - Y location of the new control.
*   INT cx                - Width of the new control.
*   INT cy                - Height of the new control.
*   LPTSTR pszDlgName     - For dialogs, has dialog name.
*   PDIALOGINFO pdi       - Ptr to additional dialog info (NULL for controls).
*
* Returns:
*     A pointer to the CTYPE structure for the new control.
*     NULL if it couldn't create the control.
*
************************************************************************/

NPCTYPE AddControl(
    PWINDOWCLASSDESC pwcd,
    LPTSTR pszText,
    DWORD style,
    DWORD flExtStyle,
    INT id,
    INT x,
    INT y,
    INT cx,
    INT cy,
    LPTSTR pszDlgName,
    PDIALOGINFO pdi)
{
    NPCTYPE npcNew;
    NPCTYPE npcT;
    NPCTYPE *npnpcLast;
    HWND hwndBehind;

    if (!(npcNew = (NPCTYPE)MyAlloc(sizeof(CTYPE))))
        return NULL;

    /*
     * These are checked later if a failure occurs,
     * so we null them out now.
     */
    npcNew->hwnd = NULL;
    npcNew->hwndDrag = NULL;
    npcNew->text = NULL;

    /*
     * Set up some fields and create the control.
     */
    npcNew->npcNext = NULL;
    npcNew->pwcd = pwcd;
    npcNew->fSelected = FALSE;
    SetRect(&npcNew->rc, x, y, x + cx, y + cy);

    if (pwcd->iType == W_DIALOG)
        hwndBehind = (HWND)NULL;
    else
        hwndBehind = (HWND)1;

    if (!CreateControl(npcNew, pszText, style, flExtStyle, id, &npcNew->rc,
            hwndBehind, pdi))
        goto CreateFailed;

    /*
     * Create the drag window, unless this is the dialog.
     */
    if (pwcd->iType != W_DIALOG) {
        npcNew->hwndDrag = CreateWindow(
                szDragClass,
                NULL,
                WS_CHILD,
                0, 0, 0, 0,
                gcd.npc->hwnd,
                NULL,
                ghInst,
                NULL);

        /*
         * Store the CTYPE pointer into the control's drag window.
         * This will be used by PCFROMHWND later.
         */
        SETPCINTOHWND(npcNew->hwndDrag, npcNew);

        /*
         * Move the drag window to the top of the Z-Order.
         */
        SetWindowPos(npcNew->hwndDrag, NULL, 0, 0, 0, 0,
                SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW);

        SizeDragToControl(npcNew);
    }

    /*
     * Did we just create a dialog?
     */
    if (pwcd->iType == W_DIALOG) {
        /*
         * First, copy the new name (it can be an ordinal!).
         */
        if (!(gcd.pszDlgName = MyAlloc(NameOrdLen(pszDlgName))))
            goto CreateFailed;

        NameOrdCpy(gcd.pszDlgName, pszDlgName);

        /*
         * Now, setup some other globals.  We clear the gcd.prl pointer,
         * because we are assuming that this dialog was not created
         * from a res link (it was dropped instead).  The routines
         * that call AddControl when creating a dialog from a res
         * link are  responsible for setting this global later.
         */
        gcd.prl = NULL;
        gcd.npc = npcNew;
        gfEditingDlg = TRUE;
    }
    else {
        /*
         * Search for the last control in the list.
         */
        npnpcLast = &npcHead;
        for (npcT = npcHead; npcT; npcT = npcT->npcNext)
            npnpcLast = &npcT->npcNext;

        /*
         * Link in the new control at the end of the list.
         */
        *npnpcLast = npcNew;
        cWindows++;
    }

    return npcNew;

CreateFailed:
    FreeCTYPE(npcNew);
    return NULL;
}

/************************************************************************
* CreateControl
*
* Creates a control.  Some styles may be masked off of the actual
* control created.  This function can also create the dialog box.
*
* If the control created is the dialog box, it will not be made visible.
* This must be done by the caller.  This allows the caller to first add
* all the controls to the dialog before showing it.
*
* The x, y, cx and cy coordinates are all in dialog units.  For a
* type of W_DIALOG, this will be relative to the apps client.  For a
* control, this will be relative to the "client" area of the dialog.
*
* Arguments:
*   NPCTYPE npc       - The CTYPE pointer to the new control.  The hwnd
*                       fields of the npc will be set.
*   LPTSTR pszText    - The window text.
*   DWORD flStyle     - The style to use.
*   DWORD flExtStyle  - Extended style of the new control.
*   INT id            - ID for the control.
*   PRECT prc         - The size and location of the new control.
*   HWND hwndBehind   - Put new control behind this hwnd in Z-order.
*   PDIALOGINFO pdi   - Pointer to additional dialog info (NULL for controls).
*
* Returns:
*     Handle of the control created.
*     NULL if control was not created.
*
************************************************************************/

HWND CreateControl(
    NPCTYPE npc,
    LPTSTR pszText,
    DWORD flStyle,
    DWORD flExtStyle,
    INT id,
    PRECT prc,
    HWND hwndBehind,
    PDIALOGINFO pdi)
{
    HWND hwnd;
    HWND hwndChild;
    WNDPROC lpfnChild;
    RECT rcT;
    TEXTMETRIC tm;
    LPTSTR pszCreateClass;
    LPTSTR pszTextOld;
    INT iType = npc->pwcd->iType;

    /*
     * Set the text field.  Remember that it can be an ordinal
     * (for icon controls).
     */
    pszTextOld = npc->text;
    if (pszText && *pszText) {
        if (!(npc->text = MyAlloc(NameOrdLen(pszText))))
            return NULL;

        NameOrdCpy(npc->text, pszText);
    }
    else {
        npc->text = NULL;
    }

    /*
     * If there was text before on this control, free it now.
     * This should be done after the new text is allocated and
     * copied, because it is common to pass this routine the same
     * pointer, and we don't want to free the text before we have
     * copied it!
     */
    if (pszTextOld)
        MyFree(pszTextOld);

    /*
     * Also set some other values in the CTYPE structure.
     */
    npc->id = id;
    npc->flStyle = flStyle;
    npc->flExtStyle = flExtStyle;

    /*
     * If this is a dialog and it has the WS_CHILD style, remove
     * it and make it WS_POPUP instead.  This prevents some problems
     * when editing the dialog.
     */
    if (iType == W_DIALOG && (flStyle & WS_CHILD)) {
        flStyle &= ~WS_CHILD;
        flStyle |= WS_POPUP;
    }

    /*
     * If this is an emulated custom control, we always make it with the
     * default styles no matter what the user has specified.  If not,
     * remove any styles that can cause problems for a control of this
     * type, such as OWNERDRAW styles.
     */
    if (npc->pwcd->fEmulated)
        flStyle = awcd[W_CUSTOM].flStyles;
    else
        flStyle &= ~npc->pwcd->flStylesBad;

    if (iType == W_DIALOG) {
        /*
         * If the style includes the DS_MODALFRAME bit, set the appropriate
         * extended style bit.
         */
        if (flStyle & DS_MODALFRAME)
            flExtStyle |= WS_EX_DLGMODALFRAME;

        /*
         * Create the dialog, but don't show it yet.
         */
        hwnd = CreateWindowEx(
                flExtStyle,
                MAKEINTRESOURCE(DIALOGCLASS),
                npc->text,
                flStyle & ~WS_VISIBLE,
                0, 0, 0, 0,
                ghwndSubClient,
                0,
                ghInst,
                NULL);
    }
    else {
        /*
         * Get the size of the control.
         */
        rcT = *prc;

        /*
         * Map the dialog unit rectangle to window coords.
         */
        DUToWinRect(&rcT);

        /*
         * If this is an icon, the text field is actually going
         * to be an ordinal that has the resource id.  We must map this to
         * an id of our own or when we create the control it will cause a
         * "Resource not found" error.
         */
        if (iType == W_ICON)
            pszText = (LPTSTR)&gordIcon;
        else
            pszText = npc->text;

        /*
         * Get the class name to use.  In the case of custom controls,
         * if the control is emulated, use the special emulator class.
         * Otherwise, it is an installed custom control, and we can use
         * it's real class string.
         */
        if (iType == W_CUSTOM) {
            if (npc->pwcd->fEmulated)
                pszCreateClass = szCustomClass;
            else
                pszCreateClass = npc->pwcd->pszClass;
        }
        else {
            pszCreateClass = ids(acsd[awcd[iType].iClass].idsClass);
        }

        /*
         * Create the control.  We always create it visible in work mode,
         * even if the style says it isn't.
         */
        hwnd = CreateWindowEx(
                flExtStyle,
                pszCreateClass,
#ifdef JAPAN
                // pszText is ordnum for icon control.
                iType == W_ICON ? pszText : TEXT(""),
#else
                pszText,
#endif
                flStyle | WS_VISIBLE,
                rcT.left, rcT.top,
                rcT.right - rcT.left,
                rcT.bottom - rcT.top,
                gcd.npc->hwnd,
                0,
                ghInst,
                NULL);
#ifdef JAPAN
        // It isn't necessary for ICON control to handle accel in text.
        if( iType != W_ICON ) {
            TCHAR   szTmp[CCHTEXTMAX];

            KKExpandCopy(szTmp, pszText, CCHTEXTMAX);
            SetWindowText(hwnd, szTmp);
        }
#endif
    }

    if (!hwnd) {
        Message(MSG_CREATECTRLERROR, pszCreateClass);
        return NULL;
    }

    if (iType == W_DIALOG) {
        /*
         * Did they specify a font?
         */
        if (*pdi->szFontName) {
            gcd.hFont = CreateDlgFont(hwnd, pdi->szFontName, pdi->nPointSize);
            lstrcpy(gcd.di.szFontName, pdi->szFontName);
            gcd.di.nPointSize = pdi->nPointSize;
            gcd.fFontSpecified = TRUE;
        }
        else {
            gcd.hFont = NULL;
            *gcd.di.szFontName = CHAR_NULL;
            gcd.di.nPointSize = 0;
            gcd.fFontSpecified = FALSE;
        }

        /*
         * Get the dimensions of the font.  It is a possible case that
         * they specified a font but it could not be created, so in
         * that case we use the system font as well as if they didn't
         * specify a font at all.
         */
        if (gcd.hFont) {
            gcd.cxChar = MyGetCharDimensions(hwnd, gcd.hFont, &tm);
            gcd.cyChar = tm.tmHeight;
        }
        else {
            gcd.cxChar = gcxSysChar;
            gcd.cyChar = gcySysChar;
        }

        /*
         * Now that we know what font we are using, adjust some entries
         * in the awcd (array of window class data) table for default
         * sizing of controls.
         */
        AdjustDefaultSizes();
    }

    /*
     * If there is a valid user specified font, inform the control
     * of it.  Once the font has been set into the dialog, it
     * must not be destroyed, because the dialog window will
     * clean it up when it is destroyed.
     */
    if (gcd.hFont)
        SendMessage(hwnd, WM_SETFONT, (WPARAM)gcd.hFont, 0L);

    /*
     * Move the window into the requested Z-Order.
     */
    SetWindowPos(hwnd, hwndBehind, 0, 0, 0, 0,
            SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW);

    /*
     * Store the CTYPE pointer into the control's hwnd.
     * This will be used by PCFROMHWND later.
     */
    SETPCINTOHWND(hwnd, npc);

    /*
     * Subclass the control.
     */
    npc->pwcd->pfnOldWndProc =
            (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC,
            (iType == W_DIALOG) ? (DWORD)DialogCtrlWndProc :
            (DWORD)CtrlWndProc);

    /*
     * Be sure double-clicks are enabled for this control.
     */
    (UINT)SetClassLong((hwnd), GCL_STYLE, (LONG)((UINT)GetClassLong((hwnd), GCL_STYLE) | CS_DBLCLKS));

    /*
     * Subclass any children this control may have.
     */
    for (hwndChild = GetTopWindow(hwnd); hwndChild;
            hwndChild = GetNextWindow(hwndChild, GW_HWNDNEXT)) {
        lpfnChild = (WNDPROC)SetWindowLong(hwndChild,
                GWL_WNDPROC, (DWORD)ChildWndProc);
        SETCHILDPROC(hwndChild, lpfnChild);
    }

    /*
     * Did we just create a dialog?
     */
    if (iType == W_DIALOG) {
        /*
         * Now that the dialog is created, we can figure out how to
         * size it.  We start by mapping the dialog units to window
         * coordinates,
         */
        rcT = *prc;
        DUToWinRect(&rcT);

        /*
         * We now have the rectangle for the client area.  Expand it
         * to account for the frame controls.
         */
        AdjustWindowRectEx(&rcT, flStyle, FALSE, flExtStyle);

        /*
         * Now we can map the rect from the apps client area
         * to the desktop, then we set the dialogs position.
         */
        ClientToScreenRect(ghwndSubClient, &rcT);
        SetWindowPos(hwnd, NULL,
                rcT.left, rcT.top,
                rcT.right - rcT.left, rcT.bottom - rcT.top,
                SWP_NOZORDER | SWP_NOACTIVATE);

        SaveDlgClientRect(hwnd);

        /*
         * Save the class name, if any.
         */
        if (pdi->pszClass && *pdi->pszClass) {
            if (!(gcd.di.pszClass = MyAlloc(NameOrdLen(pdi->pszClass)))) {
                DestroyWindow(hwnd);
                return (HWND)NULL;
            }

            NameOrdCpy(gcd.di.pszClass, pdi->pszClass);
        }
        else {
            gcd.di.pszClass = NULL;
        }

        /*
         * Save the menu name, if any.
         */
        if (pdi->pszMenu && *pdi->pszMenu) {
            if (!(gcd.di.pszMenu = MyAlloc(NameOrdLen(pdi->pszMenu)))) {
                DestroyWindow(hwnd);
                return (HWND)NULL;
            }

            NameOrdCpy(gcd.di.pszMenu, pdi->pszMenu);
        }
        else {
            gcd.di.pszMenu = NULL;
        }

        /*
         * Set some other fields in the additional dialog info structure.
         */
        gcd.di.fResFlags = pdi->fResFlags;
        gcd.di.wLanguage = pdi->wLanguage;
        gcd.di.DataVersion = pdi->DataVersion;
        gcd.di.Version = pdi->Version;
        gcd.di.Characteristics = pdi->Characteristics;
    }

    npc->hwnd = hwnd;
    return hwnd;
}

/************************************************************************
* CreateDlgFont
*
* This function creates a font with the given face name and point size
* and returns a handle to it.
*
* Arguments:
*   HWND hwnd           - Dialog window handle.
*   LPTSTR pszFontName  - Name of the font (for example: "Helv").
*   INT nPointSize      - Point size of the font (for example: 8 or 12).
*
* Returns:
*   A handle to the created font, or NULL if it could not be created.
*
************************************************************************/

static BYTE bSystemCharset;

STATICFN HFONT CreateDlgFont(
    HWND hwnd,
    LPTSTR pszFontName,
    INT nPointSize)
{
    HFONT hFont;
    HFONT hFontOld;
    HDC hDC;
    LOGFONT lf;
    TCHAR szFaceName[LF_FACESIZE];
    CHARSETINFO csi;
    DWORD dw = GetACP();

    if (TranslateCharsetInfo((DWORD*)dw, &csi, TCI_SRCCODEPAGE))
bSystemCharset = csi.ciCharset;
    else
        bSystemCharset = ANSI_CHARSET;

    /*
     * Initialize the logical font structure.  Note that filling the
     * structure with zeros gives it all the default settings.
     */
    memset(&lf, 0, sizeof(LOGFONT));
    lf.lfHeight = (SHORT)-PointSizeToPixels(nPointSize);
    if ((lf.lfCharSet = GetFontCharSet(pszFontName)) != bSystemCharset)
        lf.lfWeight = FW_BOLD; // allow boldface on non ShiftJIS fonts
    lstrcpy(lf.lfFaceName, pszFontName);

    if (!(hFont = CreateFontIndirect(&lf)))
        return NULL;

    /*
     * If we didn't get the face name that was requested, delete the
     * new font and return NULL.  This will effectively select the
     * system font for this dialog.
     */
    hDC = GetDC(hwnd);
    if (hFontOld = SelectObject(hDC, hFont)) {
        GetTextFace(hDC, LF_FACESIZE, szFaceName);
        SelectObject(hDC, hFontOld);

        if (lstrcmpi(szFaceName, pszFontName) != 0) {
            DeleteObject(hFont);
            hFont = NULL;
        }
    }
    else {
        DeleteObject(hFont);
        hFont = NULL;
    }

    ReleaseDC(hwnd, hDC);

    return hFont;
}

/************************************************************************
* MyGetCharDimensions
*
* This function calculates the average character width of the given
* font for the given window.  This must be used instead of
* simply using the tmAveCharWidth field of the text metrics, because
* this value is not correct for proportional fonts.
*
* Arguments:
*   HWND hwnd       - The window handle.
*   HFONT hFont     - The font handle.
*   PTEXTMETRIC ptm - Where to return the text metrics.
*
* Returns:
*   The average character width.  The text metrics are returned in
*   the TEXTMETRIC structure pointed to by ptm.
*
************************************************************************/

STATICFN INT MyGetCharDimensions(
    HWND hwnd,
    HFONT hFont,
    PTEXTMETRIC ptm)
{
    register INT i;
    HDC hDC;
    SIZE size;
    INT iWidth;
    TCHAR szAveCharWidth[52];
    HFONT hFontOld;

    hDC = GetDC(hwnd);
    hFontOld = SelectObject(hDC, hFont);

    GetTextMetrics(hDC, ptm);

    /*
     * Is this a variable pitch font?
     */
    if (ptm->tmPitchAndFamily & 0x01) {
        for (i = 0; i < 26; i++)
            szAveCharWidth[i] = (TCHAR)(i + CHAR_A);

        for (i = 0; i < 26; i++)
            szAveCharWidth[i + 26] = (TCHAR)(i + CHAR_CAP_A);

        GetTextExtentPoint(hDC, szAveCharWidth, 52, &size);
        iWidth = (INT)size.cx / 26;

        //
        // Round it up.
        //
        iWidth = (iWidth + 1) / 2;
    }
    else {
        iWidth = ptm->tmAveCharWidth;
    }

    SelectObject(hDC, hFontOld);
    ReleaseDC(hwnd, hDC);

    return iWidth;
}

/************************************************************************
* AdjustDefaultSizes
*
* This functions adjusts some default size entries in the awcd (array of
* window class data) table.
* This must be done at run time, because the actual values depend on the
* system that dlgedit is being run on and the font of the current dialog.
*
* This function should be called any time that a dialog is created,
* or its font is changed.
*
************************************************************************/

STATICFN VOID AdjustDefaultSizes(VOID)
{
    awcd[W_ICON].cxDefault =
            (((GetSystemMetrics(SM_CXICON) * 4) * 2) + gcd.cxChar)
            / (gcd.cxChar * 2);

    awcd[W_ICON].cyDefault =
            (((GetSystemMetrics(SM_CYICON) * 8) * 2) + gcd.cyChar)
            / (gcd.cyChar * 2);

    awcd[W_VERTSCROLL].cxDefault =
            (((GetSystemMetrics(SM_CXVSCROLL) * 4) * 2) + gcd.cxChar)
            / (gcd.cxChar * 2);

    awcd[W_HORZSCROLL].cyDefault =
            (((GetSystemMetrics(SM_CYHSCROLL) * 8) * 2) + gcd.cyChar)
            / (gcd.cyChar * 2);
}

/************************************************************************
* DeleteControl
*
* This deletes all selected controls from the dialog being edited
* (or the dialog itself, if it is selected).
*
************************************************************************/

VOID DeleteControl(VOID)
{
    if (gfDlgSelected) {
        if (Message(MSG_DELETEDIALOG) == IDYES)
            /*
             * Delete the dialog, including the resource for it.
             */
            DeleteDialog(TRUE);
    }
    else {
        while (gnpcSel)
            DeleteControl2(gnpcSel);

        gfDlgChanged = TRUE;
    }

    gfResChged = TRUE;
    ShowFileStatus(FALSE);
    StatusUpdate();
    StatusSetEnable();
}

/************************************************************************
* DeleteControl2
*
* This deletes a control by destroying its window and removing it
* from the linked list of controls associated with the dialog box.
* The control is destroyed and the window is unlinked and the CTYPE free'd.
*
************************************************************************/

STATICFN VOID DeleteControl2(
    NPCTYPE npcDel)
{
    register NPCTYPE npcT;
    register NPCTYPE *npnpcLast;

    UnSelectControl(npcDel);

    /*
     * Search for the control, unlink it from the list and free it.
     */
    npcT = npcHead;
    npnpcLast = &npcHead;
    while (npcT) {
        if (npcT == npcDel) {
            *npnpcLast = npcT->npcNext;
            FreeCTYPE(npcT);
            cWindows--;
            break;
        }

        npnpcLast = &npcT->npcNext;
        npcT = npcT->npcNext;
    }
}

/************************************************************************
* DeleteDialog
*
* This deletes the dialog box being worked on and sets globals
* and the Status Window appropriately.  All CTYPEs are freed, the
* status window is updated and the dialog window is destroyed.
*
* Arguments:
*   BOOL fResAlso - If TRUE, delete the dialog resource also.
*
************************************************************************/

VOID DeleteDialog(
    BOOL fResAlso)
{
    register NPCTYPE npcT;
    register NPCTYPE npcNext;

    CancelSelection(FALSE);

    /*
     * If they requested that the dialog resource be deleted also,
     * do it first while some globals are still set.
     */
    if (fResAlso)
        DeleteDialogResource();

    /*
     * Hide the window for better painting speed.
     */
    ShowWindow(gcd.npc->hwnd, SW_HIDE);

    /*
     * Free all the controls.
     */
    npcT = npcHead;
    while (npcT) {
        npcNext = npcT->npcNext;
        FreeCTYPE(npcT);
        npcT = npcNext;
    }
    npcHead = NULL;
    cWindows = 0;

    /*
     * Free the dialog itself.
     */
    FreeCTYPE(gcd.npc);

    if (gcd.pszDlgName) {
        MyFree(gcd.pszDlgName);
        gcd.pszDlgName = NULL;
    }

    if (gcd.di.pszClass) {
        MyFree(gcd.di.pszClass);
        gcd.di.pszClass = NULL;
    }

    if (gcd.di.pszMenu) {
        MyFree(gcd.di.pszMenu);
        gcd.di.pszMenu = NULL;
    }

    if (gcd.hFont) {
        DeleteObject(gcd.hFont);
        gcd.hFont = NULL;
    }

    gcd.fFontSpecified = FALSE;
    *gcd.di.szFontName = CHAR_NULL;

    /*
     * Set these globals back to the system font values so that
     * workers like WinToDUPoint will still work.
     */
    gcd.cxChar = gcxSysChar;
    gcd.cyChar = gcySysChar;

    gcd.prl = NULL;

    gcd.npc = NULL;
    gfEditingDlg = FALSE;
    gfDlgChanged = FALSE;

    ToolboxSelectTool(W_NOTHING, FALSE);
    StatusUpdate();
    StatusSetEnable();
}

/************************************************************************
* FreeCTYPE
*
* This function frees an allocated CTYPE.  The associated control or
* dialog window is destroyed, and memory for the text and/or class
* is freed, followed by freeing the actual CTYPE structure itself.
*
* If the hwnd in the CTYPE is NULL, only the text (if not NULL) is
* assumed to be valid and will be freed, followed by the CTYPE structure
* itself.  This allows FreeCTYPE to be called when the CTYPE is only
* partially initialized.  This is a little dependant on the order that
* a CTYPE is allocated and initialized in AddControl().
*
* Arguments:
*     NPCTYPE npc = The CTYPE to free.
*
************************************************************************/

STATICFN VOID FreeCTYPE(
    NPCTYPE npc)
{
    if (npc->hwnd)
        DestroyWindow(npc->hwnd);

    if (npc->hwndDrag)
        DestroyWindow(npc->hwndDrag);

    if (npc->text)
        MyFree(npc->text);

    MyFree(npc);
}

/************************************************************************
*
*
*
************************************************************************/

int CALLBACK GetFontCharSetEnumFunc(LPLOGFONT lpLf,
    LPTEXTMETRIC lpTm,
    int nFontType,
    LPARAM lParam)
{
    LPBYTE lpB = (LPBYTE)lParam;
    *lpB = lpTm->tmCharSet;
    return 0; // no more enum
}

/************************************************************************
*
*
*
************************************************************************/

STATICFN BYTE NEAR GetFontCharSet(LPTSTR lpStr)
{
    HDC hDC;
    BYTE cbCharset = bSystemCharset;
    FONTENUMPROC lpEFCB = (FONTENUMPROC)MakeProcInstance(
                              (FARPROC)GetFontCharSetEnumFunc,
                              ghInst);
    if (hDC = GetDC(ghwndMain)) {
        EnumFonts(hDC, lpStr, lpEFCB, (LPARAM)&cbCharset);
        ReleaseDC(ghwndMain,hDC);
    }
    FreeProcInstance(lpEFCB);
    return cbCharset;
}

#ifdef JAPAN
/************************************************************************
* Copy strings to the buffer. Codes \036 and \037 are expanded to
* text strings "\036" and "\037" respectively. t-Yoshio
************************************************************************/

VOID KDExpandCopy(LPTSTR pszDest, LPTSTR pszSrc, WORD wLimit)
{
    int  i;
    LPTSTR p = pszSrc;

    wLimit--;
    for (i = 0; i < wLimit && p && *p; i++) {
        if (*p == 036 || *p == 037) {
            if (i < wLimit-4) {
                lstrcpy(&pszDest[i], (*p == 036) ? TEXT("\\036") : TEXT("\\037"));
                i += 3;
            } else {
                break;
                }
        } else {
            pszDest[i] = *p;
            }
        if (IsDBCSLeadByte((BYTE)*p)) {
            if (i == wLimit - 1) {
                break;
            }
            pszDest[++i] = *(p+1);
        }
        p = CharNext(p);
    }
    pszDest[i] = '\0';
}
#endif


RESLIST.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 Header *******************************
* Module Name: reslist.c
*
* Contains routines to manage the linked list of resources.
*
* Functions:
*   FindDialog()
*   AllocResLink()
*   RestoreDialog()
*   FreeRes()
*   FreeResList()
*   FreeResLink()
*   DeleteDialogResource()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

#include <stdlib.h>
#include <string.h>

/************************************************************************
* FindDialog
*
* This function steps through the linked list of resources looking
* for the dialog resource with the given name.  This name can be either
* a string or an ordinal.  Strings are compared without regard to case.
*
* When looking at the dialog names in the resources, if the dialog
* resource is the current one, it will compare the current name for the
* dialog instead of the name that is stored in the resource.  This is
* because the name in the resource could be out of date with respect
* to the current name of the dialog being edited.  This happens when
* the user changes the dialog's name, but has not yet done an action
* that causes the resource to be synched, such as a File/Save, for
* instance.
*
* Arguments:
*   LPTSTR pszDlgName    - Name or ordinal of the dialog to find.
*
* Returns:
*   TRUE if a dialog with that name is found, FALSE if not.
*
************************************************************************/

BOOL FindDialog(
    LPTSTR pszDlgName)
{
    PRESLINK prl;

    if (gfEditingDlg && NameOrdCmp(gcd.pszDlgName, pszDlgName) == 0)
        return TRUE;

    for (prl = gprlHead; prl; prl = prl->prlNext) {
        /*
         * Is this a dialog resource and do they compare?
         */
        if (prl->fDlgResource && prl != gcd.prl &&
                NameOrdCmp(prl->pszName, pszDlgName) == 0)
            break;
    }

    return prl ? TRUE : FALSE;
}

/************************************************************************
* AllocResLink
*
* This function allocates a new RESLINK structure for the linked list
* of resources.  It allocates local memory for the link, allocates
* and fills global memory with the given resource data and initializes
* the fields of the structure.  The link is not added to the list,
* however.
*
* Returns:
*   A pointer to the newly allocated RESLINK structure, or NULL if
*   an error occurs.
*
************************************************************************/

PRESLINK AllocResLink(
    PRES pRes)
{
    PRESLINK prl;
    PRES pResNew;
    PRES2 pRes2;
    LPTSTR pszName;
    INT cbName;
    LPTSTR pszType;

    if (!(prl = (PRESLINK)MyAlloc(sizeof(RESLINK))))
        return NULL;

    prl->prlNext = NULL;

    prl->cbRes = ResourceSize(pRes);
    if (!(prl->hRes = GlobalAlloc(GMEM_MOVEABLE, prl->cbRes))) {
        MyFree(prl);
        Message(MSG_OUTOFMEMORY);
        return NULL;
    }

    pResNew = (PRES)GlobalLock(prl->hRes);
    memcpy(pResNew, pRes, prl->cbRes);
    GlobalUnlock(prl->hRes);

    pszType = ResourceType(pRes);
    if (IsOrd(pszType) && OrdID(pszType) == ORDID_RT_DIALOG) {
        prl->fDlgResource = TRUE;
        pszName = ResourceName(pRes);
        cbName = NameOrdLen(pszName);

        if (!(prl->pszName = MyAlloc(cbName))) {
            GlobalFree(prl->hRes);
            MyFree(prl);
            return NULL;
        }

        NameOrdCpy(prl->pszName, pszName);

        pRes2 = ResourcePart2(pRes);
        prl->wLanguage = pRes2->LanguageId;
    }
    else {
        prl->fDlgResource = FALSE;
        prl->pszName = NULL;
        prl->wLanguage = 0;
    }

    return prl;
}

/****************************************************************************
* RestoreDialog
*
* This function is used to restore the current dialog to the condition
* that it was in just before it was last chosen to edit.
*
****************************************************************************/

VOID RestoreDialog(VOID)
{
    PRESLINK prlSave;

    if (Message(MSG_RESTOREDIALOG) == IDYES) {
        prlSave = gcd.prl;
        DeleteDialog(FALSE);

        ResLinkToDialog(prlSave);
    }
}

/****************************************************************************
* FreeRes
*
* This frees the entire list of resources and deletes the dialog box
* being edited.
*
****************************************************************************/

VOID FreeRes(VOID)
{
    CancelSelection(TRUE);

    if (gfEditingDlg)
        DeleteDialog(FALSE);

    FreeResList();

    pszResFile = NULL;
    gfResChged = FALSE;
}

/****************************************************************************
* FreeResList
*
* This function frees the entire resource list.
*
****************************************************************************/

VOID FreeResList(VOID)
{
    PRESLINK prl;
    PRESLINK prlNext;

    for (prl = gprlHead; prl; prl = prlNext) {
        prlNext = prl->prlNext;
        FreeResLink(prl);
    }

    gprlHead = NULL;
}

/****************************************************************************
* FreeResLink
*
* This frees a linked resource structure and everything that it
* contains.  It does not close up the linked list, however.
*
* Arguments:
*   PRESLINK prl - Points to the resource link to free.
*
****************************************************************************/

VOID FreeResLink(
    PRESLINK prl)
{
    if (prl->pszName)
        MyFree(prl->pszName);

    if (prl->hRes)
        GlobalFree(prl->hRes);

    MyFree(prl);
}

/************************************************************************
* DeleteDialogResource
*
* This function deletes the current dialog from the linked list of
* resources.  It handles the case where the current dialog is not
* yet in the list.
*
************************************************************************/

VOID DeleteDialogResource(VOID)
{
    PRESLINK prl;
    PRESLINK prlPrev;

    /*
     * Does a link for the current dialog exist?
     */
    if (gcd.prl) {
        /*
         * Find the existing link and get it's previous link.
         */
        for (prl = gprlHead, prlPrev = NULL; prl && prl != gcd.prl;
                prlPrev = prl, prl = prl->prlNext)
            ;

        /*
         * Close up the linked list.
         */
        if (prlPrev)
            prlPrev->prlNext = gcd.prl->prlNext;
        else
            gprlHead = gcd.prl->prlNext;

        /*
         * Delete the link.
         */
        FreeResLink(gcd.prl);
        gcd.prl = NULL;
    }
}

/****************************************************************************
*
* Stubs for the C runtime international calls that are not implemented yet.
* These should be removed once the C runtime international library
* is dropped to NT.
*
****************************************************************************/

LPWSTR itoaw(
    INT value,
    LPWSTR string,
    INT radix)
{
    CHAR szAnsi[17];

    itoa(value, szAnsi, radix);

    MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, string, 17);

    return string;
}

INT awtoi(
    LPWSTR string)
{
    CHAR szAnsi[17];
    BOOL fDefCharUsed;

    WideCharToMultiByte(CP_ACP, 0, string, -1, szAnsi, 17, NULL, &fDefCharUsed);

    return atoi(szAnsi);
}


RESTODLG.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 Header *******************************
* Module Name: restodlg.c
*
* Routines that take a dialog resource and create the dialog to edit, or
* the other way around.
*
* Fucntions:
*   SynchDialogResource()
*   AllocDialogResource()
*   ResLinkToDialog()
*   ResToDialog()
*   GetiClass()
*   Duplicate()
*   MakeCopyFromRes()
*   TypeFromClassStyle()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

STATICFN INT TypeFromClassStyle(INT iClass, DWORD flStyle);

/************************************************************************
* SynchDialogResource
*
* This routine synchronizes the resource buffer with the contents of
* the current dialog being edited.  This may involve deleting the old
* contents of the current dialog prior to adding the new data.
*
* It is ok to call this routine even if there is not an existing dialog
* being edited (it will just return) and it should be called before any
* operation that needs the in memory copy of the dialog to accurately
* reflect the contents of the current dialog, such as just before a
* save to disk.
*
* Returns:
*     TRUE if all goes well (includes the case where nothing was done).
*     FALSE if an error occurs updating the resource.
*
************************************************************************/

BOOL SynchDialogResource(VOID)
{
    PRES pRes;
    PRESLINK prl;
    PRESLINK prlNew;
    PRESLINK prlPrev;

    if (!gfEditingDlg)
        return TRUE;

    /*
     * Allocate a resource for the current dialog.
     */
    if (!(pRes = AllocDialogResource(FALSE, FALSE)))
        return FALSE;

    /*
     * Allocate a new link for it.
     */
    if (!(prlNew = AllocResLink(pRes)))
        return FALSE;

    /*
     * Free the local copy of the dialog resource now that the
     * link has been created (and the resource copied to global
     * memory).
     */
    MyFree(pRes);

    /*
     * Does a link for the dialog already exist?
     */
    if (gcd.prl) {
        /*
         * Find the existing link and get it's previous link.
         */
        for (prl = gprlHead, prlPrev = NULL; prl && prl != gcd.prl;
                prlPrev = prl, prl = prl->prlNext)
            ;

        /*
         * Start linking it in.
         */
        prlNew->prlNext = gcd.prl->prlNext;

        if (prlPrev)
            prlPrev->prlNext = prlNew;
        else
            gprlHead = prlNew;

        /*
         * Delete the old link now that it is replaced.
         */
        FreeResLink(gcd.prl);
    }
    else {
        /*
         * Search for the end of the list.  Get a pointer to the last link.
         */
        for (prl = gprlHead, prlPrev = NULL; prl;
                prlPrev = prl, prl = prl->prlNext)
            ;

        /*
         * Add the new link to the end of the list.
         */
        if (prlPrev)
            prlPrev->prlNext = prlNew;
        else
            gprlHead = prlNew;
    }

    /*
     * Update our global with the new link.  Clear the "dialog changed"
     * flag.
     */
    gcd.prl = prlNew;
    gfDlgChanged = FALSE;

    return TRUE;
}

/************************************************************************
* AllocDialogResource
*
* This function allocates memory and builds a resource file format
* image in it of the current dialog.
*
* Arguments:
*   BOOL fTestMode     - TRUE if a special test mode version of the current
*                        dialog should be created.
*   BOOL fClipboard    - If TRUE, only the selected control(s) will be
*                        placed in the resource.  This is used when putting
*                        controls or groups of controls into the clipboard.
*                        If the dialog is selected, this flag is ignored,
*                        because selecting the dialog implies all the
*                        controls will be written out also.
*
* Returns:
*     Pointer to the resource buffer.
*     NULL if unable to create the resource.
*
************************************************************************/

PRES AllocDialogResource(
    BOOL fTestMode,
    BOOL fClipboard)
{
    NPCTYPE npc;
    INT cControls;
    BOOL fSelectedOnly = FALSE;
    INT cbDlgName;
    INT cbCaption;
    INT cbPointSize;
    INT cbFontName;
    INT cbCD;
    INT cbResHeader;
    INT cbResData;
    INT cbResSize;
    INT cbMenuName;
    INT cbClass;
    INT cbText;
    INT cbAlloc;
    PBYTE pb;
    PRES pResBegin;
    PRES pResBegin2;
    LPTSTR pszClass;
    LPTSTR pszMenu;
    LPTSTR pszText;
    DWORD flStyle;
    PDIALOGBOXHEADER pdbh;
    PCONTROLDATA pcd;
    ORDINAL ordClass;

    cControls = cWindows;
    if (fClipboard && !gfDlgSelected) {
        fSelectedOnly = TRUE;
        for (cControls = 0, npc = npcHead; npc; npc = npc->npcNext)
            if (npc->fSelected)
                cControls++;
    }

    /*
     * If testing, don't allow a dialog to be created with any
     * special class, or with the real menu.
     */
    if (fTestMode) {
        pszClass = NULL;
        pszMenu = NULL;
    }
    else {
        pszClass = gcd.di.pszClass;
        pszMenu = gcd.di.pszMenu;
    }

    cbDlgName = NameOrdLen(gcd.pszDlgName);
    cbCaption = (gcd.npc->text) ?
            (lstrlen(gcd.npc->text) + 1) * sizeof(TCHAR) : sizeof(TCHAR);
    cbClass = pszClass ? NameOrdLen(pszClass) : sizeof(TCHAR);
    cbMenuName = pszMenu ? NameOrdLen(pszMenu) : sizeof(TCHAR);

    if (gcd.fFontSpecified) {
        cbPointSize = sizeof(WORD);
        cbFontName = (lstrlen(gcd.di.szFontName) + 1) * sizeof(TCHAR);
    }
    else {
        cbPointSize = cbFontName = 0;
    }

    /*
     * Calculate the size of the resource header.
     */
    cbResHeader = sizeof(RES) +             // The first fixed part.
            sizeof(ORDINAL) +               // The RT_DIALOG ordinal.
            cbDlgName;                      // The dialog's name.
    DWordAlign((PBYTE *)&cbResHeader);      // Pad for the dialog's name.
    cbResHeader += sizeof(RES2);            // The last fixed part.

    /*
     * Calculate the size of the resource data.  This will just include
     * the dialog box header right now.
     */
    cbResData = SIZEOF_DIALOGBOXHEADER +    // The first fixed part.
            cbMenuName +                    // The menu.
            cbClass +                       // The class.
            cbCaption +                     // The caption.
            cbPointSize +                   // The point size.
            cbFontName;                     // The font name.

    /*
     * Allocate some buffer space.  Be sure to round this up to a DWORD
     * boundary to allow space for padding if necessary, but don't round
     * cbResData field because it will need to be written into the header
     * later, and the value that is written is an exact size (not rounded
     * up).
     */
    cbAlloc = cbResSize = cbResHeader + cbResData;
    DWordAlign((PBYTE *)&cbAlloc);
    if (!(pResBegin = (PRES)MyAlloc(cbAlloc)))
        return NULL;

    /*
     * Write the resource header.
     */
    pdbh = (PDIALOGBOXHEADER)WriteResHeader(pResBegin, 0, ORDID_RT_DIALOG,
            gcd.pszDlgName, gcd.di.fResFlags, gcd.di.wLanguage,
            gcd.di.DataVersion, gcd.di.Version, gcd.di.Characteristics);

    /*
     * Write out the style.
     */
    flStyle = gcd.npc->flStyle;
    if (fTestMode) {
        flStyle &= ~awcd[W_DIALOG].flStylesTestBad;
        flStyle |= WS_VISIBLE;
    }

    pdbh->lStyle = flStyle;
    pdbh->lExtendedStyle = gcd.npc->flExtStyle;
    pdbh->NumberOfItems = (WORD)cControls;

    /*
     * Write the coordinates.
     *
     * If we are allocating a template that only has the selected controls
     * in it, we put the value of CONTROLS_ONLY in the "cx" field of the
     * dialog header.  This is what we will check when the user pastes
     * something from the clipboard into a dialog to determine whether
     * to paste the entire dialog, or only the controls within the dialog
     * item array.
     */
    pdbh->x = (WORD)gcd.npc->rc.left;
    pdbh->y = (WORD)gcd.npc->rc.top;

    if (fSelectedOnly)
        pdbh->cx = CONTROLS_ONLY;
    else
        pdbh->cx = (WORD)(gcd.npc->rc.right - gcd.npc->rc.left);

    pdbh->cy = (WORD)(gcd.npc->rc.bottom - gcd.npc->rc.top);

    pb = (PBYTE)pdbh + SIZEOF_DIALOGBOXHEADER;

    /*
     * Write the menu name if there is one (we always write at least a null.
     */
    pb = NameOrdCpy((LPTSTR)pb, pszMenu ? pszMenu : szEmpty);

    /*
     * Write the class if there is one (we always write at least a null.
     */
    pb = NameOrdCpy((LPTSTR)pb, pszClass ? pszClass : szEmpty);

    /*
     * Write the caption if there is one (we always write at least a null).
     */
    pb = WriteSz((LPTSTR)pb, gcd.npc->text ? gcd.npc->text : szEmpty);

    /*
     * Write out the font, if there is one specified.
     */
    if (gcd.fFontSpecified) {
        *(PWORD)pb = (WORD)gcd.di.nPointSize;
        pb += sizeof(WORD);

        pb = WriteSz((LPTSTR)pb, gcd.di.szFontName);
    }

    /*
     * Pad to a DWORD boundary.  This is ok even if there are no controls
     * that follow, because we were sure to allocate on an even dword
     * boundary above.
     */
    DWordPad(&pb);

    /*
     * Now do dialog items.
     */
    for (npc = npcHead; npc; npc = npc->npcNext) {
        /*
         * Skip the control if it is NOT selected and we only want the
         * selected controls.
         */
        if (fSelectedOnly && !npc->fSelected)
            continue;

        /*
         * If we are testing, we don't want to really create a control
         * with some funny class because it probably won't be found.
         * We will substitute our custom class emulator instead.
         */
        if (fTestMode && npc->pwcd->fEmulated)
            pszClass = szCustomClass;
        else
            pszClass = npc->pwcd->pszClass;

        /*
         * Get a pointer to the text.  If this is an icon control and
         * we are going into test mode, change the text field so that
         * it points to an ordinal for DlgEdit's icon to display, or
         * the icon resource will probably not be found when the dialog
         * is created.
         */
        pszText = npc->text;
        if (npc->pwcd->iType == W_ICON && fTestMode)
            pszText = (LPTSTR)&gordIcon;

        cbText = pszText ? NameOrdLen(pszText) : sizeof(TCHAR);
        cbClass = pszClass ? NameOrdLen(pszClass) : sizeof(ORDINAL);

        cbCD = SIZEOF_CONTROLDATA +         // The fixed portion.
                cbClass +                   // The class.
                cbText +                    // The text.
                sizeof(WORD);               // nExtraStuff field.

        /*
         * Since we are adding a new control, we dword align the
         * previous size of the resource data to ensure the new
         * control starts on a dword boundary.
         */
        DWordAlign((PBYTE *)&cbResSize);

        /*
         * Allocate room for this control.  This includes room for the
         * template structure, class, text and a byte for the cb field
         * for the create struct data.
         */
        cbAlloc = cbResSize + cbCD;
        DWordAlign((PBYTE *)&cbAlloc);
        pResBegin2 = (PRES)MyRealloc((PBYTE)pResBegin, cbAlloc);
        if (!pResBegin2) {
            MyFree(pResBegin);
            return NULL;
        }

        pResBegin = pResBegin2;
        pcd = (PCONTROLDATA)((PBYTE)pResBegin + cbResSize);
        cbResSize += cbCD;

        /*
         * Write the style.  If testing, remove any styles that can
         * cause problems, such as ownerdraw styles.  If testing and
         * this is an emulated custom control, always make it with
         * the default styles no matter what the user has specified.
         */
        flStyle = npc->flStyle;
        if (fTestMode) {
            if (npc->pwcd->fEmulated)
                flStyle = awcd[W_CUSTOM].flStyles;
            else
                flStyle &= ~npc->pwcd->flStylesTestBad;
        }

        pcd->lStyle = flStyle;
        pcd->lExtendedStyle = npc->flExtStyle;

        /*
         * Write the coordinates.
         */
        pcd->x = (WORD)npc->rc.left;
        pcd->y = (WORD)npc->rc.top;
        pcd->cx = (WORD)(npc->rc.right - npc->rc.left);
        pcd->cy = (WORD)(npc->rc.bottom - npc->rc.top);

        /*
         * Write the id.
         */
        pcd->wId = (WORD)npc->id;

        pb = (PBYTE)pcd + SIZEOF_CONTROLDATA;

        /*
         * Write the class.  This will be a string, except for the
         * predefined control classes, which all have an ordinal
         * value defined for them.
         */
        if (pszClass) {
            pb = NameOrdCpy((LPTSTR)pb, pszClass);
        }
        else {
            WriteOrd(&ordClass, acsd[awcd[npc->pwcd->iType].iClass].idOrd);
            pb = NameOrdCpy((LPTSTR)pb, (LPTSTR)&ordClass);
        }

        /*
         * Write the text.
         */
        pb = NameOrdCpy((LPTSTR)pb, pszText ? pszText : szEmpty);

        /*
         * Write out a zero because there are no additional bytes
         * of create struct data.
         */
        *(PWORD)pb = 0;
        pb += sizeof(WORD);

        /*
         * Pad to a DWORD boundary.  This is ok even if there are no more
         * controls, because we were sure to allocate on an even dword
         * boundary above.
         */
        DWordPad(&pb);
    }

    /*
     * Now go back and fill in the resource data size field.
     */
    pResBegin->DataSize = cbResSize - cbResHeader;

    return pResBegin;
}

/************************************************************************
* ResLinkToDialog
*
* This function is used to create a dialog out of a dialog resource
* that has been stored in the resource linked list.
*
* Arguments:
*   PRESLINK prl - Points to the link that describes the dialog to
*                  create.  It is assumed that the resource is a
*                  dialog resource.
*
************************************************************************/

VOID ResLinkToDialog(
    PRESLINK prl)
{
    PRES pRes;

    pRes = (PRES)GlobalLock(prl->hRes);
    ResToDialog(pRes, TRUE);
    GlobalUnlock(prl->hRes);

    /*
     * If the dialog was successfully created, remember which res link
     * it was created from.
     */
    if (gfEditingDlg)
        gcd.prl = prl;
}

/************************************************************************
* ResToDialog
*
* This function creates a dialog box, complete with controls,
* from a dialog resource template.
*
* Arguments:
*   PRES pRes        - Pointer to the dialog resource to use.
*   BOOL fDoDialog   - TRUE if a new dialog should be created, followed
*                      by the controls.  If this is FALSE, just the
*                      controls will be created and added to the current
*                      dialog.
*
* Returns:
*     TRUE on success, FALSE if an error occured.
*
************************************************************************/

BOOL ResToDialog(
    PRES pRes,
    BOOL fDoDialog)
{
    LPTSTR pszText;
    LPTSTR pszClass;
    INT x;
    INT y;
    INT cx;
    INT cy;
    INT id;
    INT iClass;
    INT cdit;
    INT Type;
    DWORD flStyle;
    DWORD flExtStyle;
    NPCTYPE npc;
    LPTSTR pszMenuName;
    LPTSTR pszFontName;
    INT nPointSize;
    LPTSTR pszDlgName;
    LPTSTR pszCaption;
    PDIALOGBOXHEADER pdbh;
    PCONTROLDATA pcd;
    PWINDOWCLASSDESC pwcd;
    PCUSTLINK pcl;
    PRES2 pRes2;
    DIALOGINFO di;
    CCINFO cci;

    /*
     * First check that the pointer is ok.
     */
    if (!pRes)
        return FALSE;

    pRes2 = ResourcePart2(pRes);
    pdbh = (PDIALOGBOXHEADER)SkipResHeader(pRes);

    /*
     * Parse out the dialog box header.
     * After this point, pcd is pointing to the first dialog control item.
     */
    pcd = ParseDialogBoxHeader(pdbh,
            &flStyle, &flExtStyle, &cdit, &x, &y, &cx, &cy,
            &pszMenuName, &pszClass, &pszCaption,
            &nPointSize, &pszFontName);

    /*
     * Are we pasting the entire dialog?
     */
    if (fDoDialog) {
        pszDlgName = ResourceName(pRes);

        /*
         * Determine the best base id for the dialog.
         */
        if (IsOrd(pszDlgName))
            id = OrdID(pszDlgName);
        else
            id = NextID(NEXTID_DIALOG, plInclude, 0);

        di.fResFlags = pRes2->MemoryFlags;
        di.wLanguage = pRes2->LanguageId;
        di.pszClass = pszClass;
        di.pszMenu = pszMenuName;
        di.DataVersion = pRes2->DataVersion;
        di.Version = pRes2->Version;
        di.Characteristics = pRes2->Characteristics;
        di.nPointSize = nPointSize;
        lstrcpy(di.szFontName, pszFontName ? pszFontName : szEmpty);

        /*
         * Create the dialog.
         */
        if (!AddControl(&awcd[W_DIALOG], pszCaption, flStyle, flExtStyle, id,
                x, y, cx, cy, pszDlgName, &di))
            return FALSE;
    }

    while (cdit--) {
        pcd = ParseControlData(pcd, &flStyle, &flExtStyle, &x, &y, &cx, &cy,
                &id, &pszClass, &pszText);

        /*
         * If we are not creating a new dialog, and the id in
         * the resource is already in use, we will use the next
         * available one instead.
         */
        if (!fDoDialog && !IsUniqueID(id))
            id = NextID(NEXTID_CONTROL, plInclude, 0);

        /*
         * Fix up the class.  If the class is a predefined ordinal type,
         * we will null out pszClass so it doesn't confuse AddControl
         * into thinking that there is a string class for this control.
         */
        iClass = GetiClass(pszClass);
        Type = TypeFromClassStyle(iClass, flStyle);
        if (IsOrd(pszClass))
            pszClass = NULL;

        if (Type == W_CUSTOM) {
            /*
             * Search the list of installed custom controls for one
             * that matches the class.
             */
            for (pcl = gpclHead;
                    pcl && lstrcmpi(pcl->pwcd->pszClass, pszClass) != 0;
                    pcl = pcl->pclNext)
                ;

            /*
             * Was a match found?
             */
            if (pcl) {
                pwcd = pcl->pwcd;
            }
            else {
                /*
                 * An existing custom control link for this class was
                 * not found.  We will add an emulated custom control
                 * to support it.  We assume the default style and size
                 * should be what this control has.
                 */
                lstrcpy(cci.szClass, pszClass);
                cci.flOptions = 0;
                *cci.szDesc = TEXT('\0');
                cci.cxDefault = cx;
                cci.cyDefault = cy;
                cci.flStyleDefault = flStyle;
                cci.flExtStyleDefault = flExtStyle;
                *cci.szTextDefault = TEXT('\0');
                cci.cStyleFlags = 0;
                cci.aStyleFlags = NULL;
                cci.lpfnStyle = NULL;
                cci.lpfnSizeToText = NULL;
                cci.dwReserved1 = 0;
                cci.dwReserved2 = 0;

                if (pcl = AddCustomLink(&cci, TRUE, FALSE, NULL, NULL))
                    pwcd = pcl->pwcd;
                else
                    /*
                     * Skip this control and continue creating the
                     * rest of the dialog.
                     */
                    continue;
            }
        }
        else {
            pwcd = &awcd[Type];
        }

        /*
         * If we are not creating the entire dialog (we allow existing
         * resource files to be a little messed up), and this control
         * is a default pushbutton, we will then loop through all the
         * existing controls checking for another default pushbutton.
         * If one is found, we convert the default pushbutton being
         * created into a normal pushbutton instead.  It is not allowed
         * to have more than one default pushbuttons in the same dialog.
         */
        if (!fDoDialog && Type == W_PUSHBUTTON &&
                (flStyle & BS_ALL) == BS_DEFPUSHBUTTON) {
            for (npc = npcHead; npc; npc = npc->npcNext) {
                if (npc->pwcd->iType == W_PUSHBUTTON &&
                        (npc->flStyle & BS_ALL) == BS_DEFPUSHBUTTON) {
                    flStyle = (flStyle & ~BS_ALL) | BS_PUSHBUTTON;
                    break;
                }
            }
        }

        npc = AddControl(pwcd, pszText, flStyle, flExtStyle, id,
                x, y, cx, cy, NULL, NULL);

        /*
         * If the control creation succeeded, and we are just adding
         * controls (not creating a whole new dialog), select the
         * controls as they are added, but don't do any drawing yet.
         */
        if (!fDoDialog && npc)
            SelectControl2(npc, TRUE);
    }

    /*
     * Update the selected rectangle.  This is normally done by
     * SelectControl2 but we told it not to so that the selection
     * could be done faster.  We also select the first control here.
     */
    if (!fDoDialog) {
        SetAnchorToFirstSel(TRUE);
        CalcSelectedRect();
    }

    ShowWindow(gcd.npc->hwnd, SW_SHOWNA);
    ToolboxOnTop();

    return TRUE;
}

/************************************************************************
* TypeFromClassStyle
*
* This function returns the type of a control (one of the W_ constants)
* based on the class in iClass and the style in flStyle.
*
* Arguments:
*     INT iClass    = The class of the control, as an IC_* defined constant.
*     DWORD flStyle = The style of the control.
*
* Returns:
*     The type of the control (W_* constant).
*     W_NOTHING is the error return.
*
************************************************************************/

STATICFN INT TypeFromClassStyle(
    INT iClass,
    DWORD flStyle)
{
    switch (iClass) {
        case IC_BUTTON:
            return rgmpiClsBtnType[flStyle & BS_ALL];

        case IC_EDIT:
            return W_EDIT;

        case IC_SCROLLBAR:
            return (flStyle & SBS_VERT) ? W_VERTSCROLL : W_HORZSCROLL;

        case IC_STATIC:
            return rgmpiClsStcType[flStyle & SS_ALL];

        case IC_LISTBOX:
            return W_LISTBOX;

        case IC_COMBOBOX:
            return W_COMBOBOX;

        case IC_CUSTOM:
            return W_CUSTOM;

        case IC_DIALOG:
            return W_DIALOG;

        default:
            return W_NOTHING;
    }
}

/************************************************************************
* GetiClass
*
* This function returns the class identifier number for the
* window class of the control, given the class string from the
* dialog template.
*
* An ordinal class is a special ordinal that is used to identify
* each of the standard control classes.  It is used in the
* dialog template to save space.  The class string passed in
* can be an ordinal and if so, it will be checked against these
* predefined ordinal class values for a match.
*
* Arguments:
*     LPTSTR pszClass - The class string or ordinal.
*
* Returns:
*     The class identifier, one of the IC_* symbols in dlgedit.h.
*     If the class cannot be determined, it assumes it is a custom
*     class and returns IC_CUSTOM.
*
************************************************************************/

INT GetiClass(
    LPTSTR pszClass)
{
    INT i;
    WORD idOrd;

    if (IsOrd(pszClass)) {
        idOrd = OrdID(pszClass);
        for (i = 0; i < IC_DIALOG; i++) {
            if (acsd[i].idOrd == idOrd)
                return i;
        }
    }
    else {
        for (i = 0; i < IC_DIALOG; i++) {
            if (lstrcmpi(ids(acsd[i].idsClass), pszClass) == 0)
                return i;
        }
    }

    /*
     * Not found.  Assume it is a user defined class.
     */
    return IC_CUSTOM;
}

/************************************************************************
* Duplicate
*
* This routine duplicates the current selection.
*
************************************************************************/

VOID Duplicate(VOID)
{
    PRES pRes;

    if (gcSelected) {
        /*
         * Store the current selection in a dialog resource.
         */
        if (!(pRes = AllocDialogResource(FALSE, TRUE)))
            return;

        MakeCopyFromRes(pRes);
    }
}

/************************************************************************
* MakeCopyFromRes
*
* This function uses the given dialog template to either add a new
* dialog to the current resource file, or drop controls from the
* template into the current dialog.  If copying a dialog, it is created
* right away.  If copying controls, an operation is begun to start
* tracking them to their final destination in the current dialog.
*
* The caller of this function should NOT free pRes.  This will
* be done either before the function returns, or after the drag
* operation is complete.
*
* Arguments:
*   PRES pRes - Points to the dialog resource that contains
*               the dialog or controls to make a copy of.
*
************************************************************************/

VOID MakeCopyFromRes(
    PRES pRes)
{
    PDIALOGBOXHEADER pdbh;
    PCONTROLDATA pcd;
    INT cControls;
    INT i;
    BOOL fFreeData = TRUE;
    INT iType;
    INT iClass;
    INT nBottom;
    INT nBottomLowest;

    gpResCopy = pRes;
    pdbh = (PDIALOGBOXHEADER)SkipResHeader(gpResCopy);

    /*
     * If cx is CONTROLS_ONLY, then we know that we only
     * want to copy the controls in the template, not
     * the entire dialog plus controls.
     */
    if (pdbh->cx == CONTROLS_ONLY) {
        /*
         * Begin copying in new controls into the current dialog.
         */
        cControls = pdbh->NumberOfItems;
        if (cControls) {
            /*
             * Seed the rectangle with impossible values.
             */
            SetRect(&grcCopy, 32000, 32000, -32000, -32000);
            nBottomLowest = 0;

            /*
             * Loop through all the controls, expanding the rectangle
             * to fit around all of them.
             */
            pcd = SkipDialogBoxHeader(pdbh);
            for (i = 0; i < cControls; i++) {
                iClass = GetiClass((LPTSTR)((PBYTE)pcd + SIZEOF_CONTROLDATA));
                iType = TypeFromClassStyle(iClass, pcd->lStyle);

                if (grcCopy.left > (INT)pcd->x)
                    grcCopy.left = (INT)pcd->x;

                if (grcCopy.top > (INT)pcd->y)
                    grcCopy.top = (INT)pcd->y;

                if (grcCopy.right < (INT)pcd->x + (INT)pcd->cx)
                    grcCopy.right = (INT)pcd->x + (INT)pcd->cx;

                nBottom = ((INT)pcd->y + (INT)pcd->cy) -
                        GetOverHang(iType, (INT)pcd->cy);
                if (nBottom > nBottomLowest)
                    nBottomLowest = nBottom;

                if (grcCopy.bottom < (INT)pcd->y + (INT)pcd->cy)
                    grcCopy.bottom = (INT)pcd->y + (INT)pcd->cy;

                pcd = SkipControlData(pcd);
            }

            /*
             * Begin dragging the new control(s).  Set a flag so that
             * the resource data is NOT free'd until after the drag
             * is finished.
             */
            DragNewBegin(grcCopy.right - grcCopy.left,
                    grcCopy.bottom - grcCopy.top,
                    grcCopy.bottom - nBottomLowest);
            fFreeData = FALSE;

}
    }
    else {
        /*
         * Begin copying in a new dialog, complete with controls.
         */
        if (SynchDialogResource()) {
            /*
             * Remove any existing dialog.
             */
            if (gfEditingDlg)
                DeleteDialog(FALSE);

            if (ResToDialog(gpResCopy, TRUE)) {
                SelectControl(gcd.npc, FALSE);
                gfResChged = TRUE;
                ShowFileStatus(FALSE);
            }
        }
    }

    if (fFreeData) {
        MyFree(gpResCopy);
        gpResCopy = NULL;
    }
}


RESUTIL.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 Header *******************************
* Module Name: resutil.c
*
* Contains utility functions for working with the Windows resource file.
*
* Functions:
*   ParseDialogBoxHeader()
*   ParseControlData()
*   DWordAlign()
*   DWordPad()
*   ResourceType)
*   ResourceName()
*   ResourcePart2()
*   ResourceSize()
*   SkipResHeader()
*   SkipSz()
*   SkipDialogBoxHeader()
*   SkipControlData()
*   NameOrdCpy()
*   NameOrdCmp()
*   NameOrdLen()
*   NameOrdDup()
*   StrToNameOrd()
*   WriteOrd()
*   WriteResHeader()
*   WriteSz()
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

#include <string.h>
#include <wchar.h>

/************************************************************************
* ParseDialogBoxHeader
*
*
* Arguments:
*   PDIALOGBOXHEADER pdbh - Points to dialog box header to parse.
*   PDWORD pflStyle       - Receives the style.
*   PDWORD pflExtStyle    - Receives the extended style.
*   PINT pcdit            - Receives the number of controls in the dialog.
*   PINT px               - Receives starting x location.
*   PINT py               - Receives starting y location.
*   PINT pcx              - Receives the width.
*   PINT pcy              - Receives the height.
*   LPTSTR *ppszMenuName  - Receives the menu name.
*   LPTSTR *ppszClass     - Receives the class name.
*   LPTSTR *ppszCaption   - Receives the caption.
*   PINT pPointSize       - Receives the point size.
*   LPTSTR *ppszFontName  - Receives the font name.
*
* Returns:
*   A pointer to the first dialog item past the dialog template header.
*
************************************************************************/

PCONTROLDATA ParseDialogBoxHeader(
    PDIALOGBOXHEADER pdbh,
    PDWORD pflStyle,
    PDWORD pflExtStyle,
    PINT pcdit,
    PINT px,
    PINT py,
    PINT pcx,
    PINT pcy,
    LPTSTR *ppszMenuName,
    LPTSTR *ppszClass,
    LPTSTR *ppszCaption,
    PINT pPointSize,
    LPTSTR *ppszFontName)
{
    BYTE UNALIGNED *pb;

    *pflStyle = pdbh->lStyle;
    *pflExtStyle = pdbh->lExtendedStyle;
    *pcdit = pdbh->NumberOfItems;
    *px = (SHORT)pdbh->x;
    *py = (SHORT)pdbh->y;
    *pcx = (SHORT)pdbh->cx;
    *pcy = (SHORT)pdbh->cy;

    pb = (PBYTE)pdbh + SIZEOF_DIALOGBOXHEADER;
    *ppszMenuName = (LPTSTR)pb;
    pb += NameOrdLen((LPTSTR)pb);

    *ppszClass = (LPTSTR)pb;
    pb += NameOrdLen((LPTSTR)pb);

    *ppszCaption = (LPTSTR)pb;
    pb += (lstrlen((LPTSTR)pb) + 1) * sizeof(TCHAR);

    /*
     * Does the template specify a font?
     */
    if (pdbh->lStyle & DS_SETFONT) {
        *pPointSize = (SHORT)(*(PWORD)pb);
        pb += sizeof(WORD);
        *ppszFontName = (LPTSTR)pb;
        pb += (lstrlen((LPTSTR)pb) + 1) * sizeof(TCHAR);
    }
    else {
        *pPointSize = 0;
        *ppszFontName = NULL;
    }

    DWordAlign((PBYTE *)&pb);

    return (PCONTROLDATA)pb;
}

/************************************************************************
* ParseControlData
*
*
* Arguments:
*   PCONTROLDATA pcd    - Points to the control data to parse.
*   PDWORD pflStyle     - Receives the control style.
*   PDWORD pflExtStyle  - Receives the extended control style.
*   PINT px             - Receives starting x location.
*   PINT py             - Receives starting y location.
*   PINT pcx            - Receives the width.
*   PINT pcy            - Receives the height.
*   PINT pid            - Receives the control id.
*   LPTSTR *ppszClass   - Receives the class name.
*   LPTSTR *ppszText    - Receives the text.
*
* Returns:
*   A pointer to the next dialog item past the given one.
*
************************************************************************/

PCONTROLDATA ParseControlData(
    PCONTROLDATA pcd,
    PDWORD pflStyle,
    PDWORD pflExtStyle,
    PINT px,
    PINT py,
    PINT pcx,
    PINT pcy,
    PINT pid,
    LPTSTR *ppszClass,
    LPTSTR *ppszText)
{
    BYTE UNALIGNED *pb;

    *pflStyle = pcd->lStyle;
    *pflExtStyle = pcd->lExtendedStyle;

    *px = (SHORT)pcd->x;
    *py = (SHORT)pcd->y;
    *pcx = (SHORT)pcd->cx;
    *pcy = (SHORT)pcd->cy;
    *pid = (SHORT)pcd->wId;

    pb = (PBYTE)pcd + SIZEOF_CONTROLDATA;
    *ppszClass = (LPTSTR)pb;
    pb += NameOrdLen((LPTSTR)pb);

    *ppszText = (LPTSTR)pb;
    pb += NameOrdLen((LPTSTR)pb);

    /*
     * Finally, skip the Create Struct Data.
     * After this, pb will be pointing to the next control.
     */
    pb += *(PWORD)pb + sizeof(WORD);

    DWordAlign((PBYTE *)&pb);

    return (PCONTROLDATA)pb;
}

/************************************************************************
* DWordAlign
*
* This function aligns the passed pointer to a DWORD boundary.
*
* Arguments:
*   PBYTE *ppb - Points to the pointer to align.
*
************************************************************************/

VOID DWordAlign(
    PBYTE *ppb)
{
    *ppb += (4 - (((WORD)(DWORD)*ppb) & 3)) % 4;
}

/************************************************************************
* DWordPad
*
* This function aligns the passed pointer to a DWORD boundary, padding
* with nulls as it goes.
*
* Arguments:
*   PBYTE *ppb - Points to the pointer to align.
*
************************************************************************/

VOID DWordPad(
    PBYTE *ppb)
{
    WORD cbytes;

    cbytes = (WORD)((4 - (((WORD)(DWORD)*ppb) & 3)) % 4);
    while (cbytes) {
        *((*ppb)++) = 0;
        cbytes--;
    }
}

/************************************************************************
* ResourceType
*
* This function returns a pointer to the type of the resource.
* The type can be either a string or an ordinal.
*
* Arguments:
*   PRES pRes - Points to the start of the resource.
*
* Returns:
*     Pointer to the type of the resource.
*
************************************************************************/

LPTSTR ResourceType(
    PRES pRes)
{
    /*
     * Skip past the two size fields.
     */
    return (LPTSTR)((PBYTE)pRes + sizeof(DWORD) + sizeof(DWORD));
}

/************************************************************************
* ResourceName
*
* This function returns a pointer to the name of the resource.
* The name can be either a string or an ordinal.
*
* Arguments:
*   PRES pRes - Points to the start of the resource.
*
* Returns:
*     Pointer to the name of the resource.
*
************************************************************************/

LPTSTR ResourceName(
    PRES pRes)
{
    PBYTE pb;

    /*
     * Skip past the two size fields.
     */
    pb = (PBYTE)pRes + sizeof(DWORD) + sizeof(DWORD);

    /*
     * Skip past the "Type" field to the name.
     */
    return (LPTSTR)SkipSz((LPTSTR)pb);
}

/************************************************************************
* ResourcePart2
*
* This function returns a pointer to the second half of the resource
* header.
*
* Arguments:
*   PRES pRes - Points to the start of the resource.
*
* Returns:
*   A pointer to the second part of the resource header.
*
************************************************************************/

PRES2 ResourcePart2(
    PRES pRes)
{
    PBYTE pb;

    /*
     * Skip past the first part of the resource header.
     */
    pb = (PBYTE)pRes + sizeof(RES);

    /*
     * Skip past the "Type" field to the name.
     */
    pb = SkipSz((LPTSTR)pb);

    /*
     * Skip past the name field also.
     */
    pb = SkipSz((LPTSTR)pb);
    DWordAlign(&pb);

    return (PRES2)pb;
}

/************************************************************************
* ResourceSize
*
* This returns the size of the given resource.
*
* Arguments:
*   PRES pRes - Points to the start of the resource.
*
* Returns:
*   Size of the resource, including the header.
*
************************************************************************/

DWORD ResourceSize(
    PRES pRes)
{
    return pRes->HeaderSize + pRes->DataSize;
}

/************************************************************************
* SkipResHeader
*
* This function returns a pointer to the start of the resource data,
* just past it's header.
*
* Arguments:
*   PRES pRes - Pointer to the resource.
*
************************************************************************/

PBYTE SkipResHeader(
    PRES pRes)
{
    return (PBYTE)pRes + pRes->HeaderSize;
}

/************************************************************************
* SkipSz
*
* This function skips past a string and returns a pointer to just
* past it.  It detects if the string is really an ordinal and skips
* past these also.
*
* Arguments:
*   LPTSTR pNameOrd - Pointer to the string/ordinal.
*
************************************************************************/

PBYTE SkipSz(
    LPTSTR pNameOrd)
{
    if (IsOrd(pNameOrd))
        pNameOrd = (LPTSTR)((PBYTE)pNameOrd + sizeof(ORDINAL));
    else
        pNameOrd += lstrlen(pNameOrd) + 1;

    return (PBYTE)pNameOrd;
}

/************************************************************************
* SkipDialogBoxHeader
*
* This function skips past a dialog template structure and returns
* a pointer to the first dialog item template just past it.
*
* Arguments:
*   PDIALOGBOXHEADER pdbh - Points to the dialog box header.
*
* Returns:
*   A pointer to the first dialog item control data in the resource,
*   just past the dialog box header that was skipped.
*
************************************************************************/

PCONTROLDATA SkipDialogBoxHeader(
    PDIALOGBOXHEADER pdbh)
{
    BYTE UNALIGNED *pb;

    /*
     * Skip the fixed portion.
     */
    pb = (PBYTE)pdbh + SIZEOF_DIALOGBOXHEADER;

    /*
     * Skip the menu.
     */
    pb += NameOrdLen((LPTSTR)pb);

    /*
     * Skip the class.
     */
    pb += NameOrdLen((LPTSTR)pb);

    /*
     * Skip the caption.
     */
    pb += (lstrlen((LPTSTR)pb) + 1) * sizeof(TCHAR);

    /*
     * Does the template specify a font?
     */
    if (pdbh->lStyle & DS_SETFONT) {
        pb += sizeof(WORD);
        pb += (lstrlen((LPTSTR)pb) + 1) * sizeof(TCHAR);
    }

    DWordAlign((PBYTE *)&pb);

    return (PCONTROLDATA)pb;
}

/************************************************************************
* SkipControlData
*
* This function skips past the given control data to the next control.
*
* Arguments:
*   PCONTROLDATA pcd - Points to the control data structure to skip.
*
* Returns:
*   A pointer to the next control data structure.
*
************************************************************************/

PCONTROLDATA SkipControlData(
    PCONTROLDATA pcd)
{
    BYTE UNALIGNED *pb;

    /*
     * Skip the fixed portion.
     */
    pb = (PBYTE)pcd + SIZEOF_CONTROLDATA;

    /*
     * Skip the class.
     */
    pb += NameOrdLen((LPTSTR)pb);

    /*
     * Skip the text.
     */
    pb += NameOrdLen((LPTSTR)pb);

    /*
     * Finally, skip the Create Struct Data.
     * After this, pb will be pointing to the next control.
     */
    pb += *(PWORD)pb + sizeof(WORD);

    DWordAlign((PBYTE *)&pb);

    return (PCONTROLDATA)pb;
}

/************************************************************************
* NameOrdCpy
*
* This function copies a string or ordinal.  This function needs to be
* used whenever a string could possibly be an ordinal.  It returns a
* pointer to the first byte after the copied name/ordinal.
*
* Arguments:
*   LPTSTR pNameOrdDest - The destination buffer.
*   LPTSTR pNameOrdSrc  - The source string or ordinal.
*
************************************************************************/

PBYTE NameOrdCpy(
    LPTSTR pNameOrdDest,
    LPTSTR pNameOrdSrc)
{
    if (IsOrd(pNameOrdSrc)) {
        memcpy((PBYTE)pNameOrdDest, (PBYTE)pNameOrdSrc, sizeof(ORDINAL));
        return (PBYTE)pNameOrdDest + sizeof(ORDINAL);
    }
    else {
        lstrcpy(pNameOrdDest, pNameOrdSrc);
        return (PBYTE)(pNameOrdDest + (lstrlen(pNameOrdDest) + 1));
    }
}

/************************************************************************
* NameOrdCmp
*
* This function compares two strings or ordinals.  It returns a
* zero if they are equal, or non-zero if they are not.  This
* follows the convention of lstrcmp(), but the return should
* not be relied upon to determine which is "greater" than
* the other.
*
* Arguments:
*   LPTSTR pNameOrd1 - The first string or ordinal.
*   LPTSTR pNameOrd2 - The second string or ordinal.
*
************************************************************************/

INT NameOrdCmp(
    LPTSTR pNameOrd1,
    LPTSTR pNameOrd2)
{
    BOOL fIsOrd1;
    BOOL fIsOrd2;

    fIsOrd1 = IsOrd(pNameOrd1);
    fIsOrd2 = IsOrd(pNameOrd2);

    if (fIsOrd1 != fIsOrd2)
        return 1;

    if (fIsOrd1)
        return memcmp((PBYTE)pNameOrd1, (PBYTE)pNameOrd2, sizeof(ORDINAL));
    else
        return lstrcmp(pNameOrd1, pNameOrd2);
}

/************************************************************************
* NameOrdLen
*
* This function returns the length of a string or ordinal.
* If the given name is a string, the length of the string
* plus the terminating null is returned.  Otherwise,
* the size of an ORDINAL structure is returned.
*
* The length returned is in bytes, not wide-chars.
*
* Arguments:
*   LPTSTR pNameOrd - The string or ordinal.
*
************************************************************************/

INT NameOrdLen(
    LPTSTR pNameOrd)
{
    if (IsOrd(pNameOrd))
        return sizeof(ORDINAL);
    else
        return (lstrlen(pNameOrd) + 1) * sizeof(TCHAR);
}

/****************************************************************************
* NameOrdDup
*
* This function allocates a copy of the given name or ordinal.
*
* Arguments:
*   LPTSTR pNameOrd - The name or ordinal to duplicate.
*
* Returns a pointer to the new copy if successful, NULL if it fails.
*
****************************************************************************/

LPTSTR NameOrdDup(
    LPTSTR pNameOrd)
{
    register INT iLen;
    LPTSTR psz;

    iLen = NameOrdLen(pNameOrd);

    if (!(psz = (LPTSTR)MyAlloc(iLen)))
        return NULL;

    NameOrdCpy(psz, pNameOrd);

    return psz;
}

/************************************************************************
* StrToNameOrd
*
* This function takes the given string, determines if it is
* all numeric and if so, converts it in place into an ordinal.
* It is used to convert the string from an edit field for
* a value that can be an ordinal, such as the dialog name or
* an icon's text.
*
* Note that the pszNameOrd buffer must be large enough for an
* ordinal in case the string gets converted to an ordinal.
*
* Arguments:
*   LPTSTR pszNameOrd  - On input, contains the string to possibly
*                        convert.  On output, it will contain the
*                        original string or the string converted to
*                        an ordinal.
*   BOOL fDecOnly      - TRUE if hex values and negative values (the
*                        string starts with a '-') are not allowed.
*                        This flag prevents these types of strings
*                        from being candidates for conversion to
*                        ordinals.
*
************************************************************************/

VOID StrToNameOrd(
    LPTSTR pszNameOrd,
    BOOL fDecOnly)
{
    register INT i;
    INT nOrd;
    INT nLen;

    /*
     * Empty string?
     */
    if (!(*pszNameOrd))
        return;

    nLen = lstrlen(pszNameOrd);

    /*
     * Is a hex value ok and does this appear to be a hex value?
     */
    if (!fDecOnly && pszNameOrd[0] == CHAR_0 &&
            (pszNameOrd[1] == CHAR_X || pszNameOrd[1] == CHAR_CAP_X)) {
        for (i = 2; i < nLen; i++) {
            if (!iswxdigit(pszNameOrd[i]))
                return;
        }

        nOrd =  axtoi(&pszNameOrd[2]);
    }
    else {
        /*
         * All characters must be numeric.  Negative numbers may
         * or may not be allowed, based on the fDecOnly flag.
         */
        for (i = 0; i < nLen; i++) {
            if (!iswdigit(pszNameOrd[i]) &&
                    (fDecOnly || i != 0 || pszNameOrd[0] != CHAR_MINUS))
                return;
        }

        nOrd = awtoi(pszNameOrd);
    }

    /*
     * Return the ordinal in the original buffer.
     */
    WriteOrd((PORDINAL)pszNameOrd, nOrd);
}

/************************************************************************
* WriteOrd
*
* This function writes out the given ordinal to the specified
* memory location.  It returns the first byte past the newly
* written ordinal.
*
* Arguments:
*   PORDINAL pMem   - Pointer to the location to write the ordinal.
*   INT nOrdinalID  - Ordinal ID to write.
*
************************************************************************/

PBYTE WriteOrd(
    PORDINAL pOrd,
    INT nOrdinalID)
{
    pOrd->wReserved = 0xffff;
    pOrd->wOrdID = (WORD)nOrdinalID;

    return (PBYTE)pOrd + sizeof(ORDINAL);
}

/************************************************************************
* WriteResHeader
*
* This function writes out a resource header to the memory location
* specified.
*
* Arguments:
*
*    PRES pRes            - pointer to the resource
*    DWORD DataSize       - size of the data
*    INT iResType         - resource type
*    LPTSTR pszResName    - resource name
*    WORD fResFlags       - resource flags
*    WORD LanguageId      - language id
*    DWORD DataVersion    - data version
*    DWORD Version        - version
*    DWORD Characteristics-characterstics
*
* Returns:
*    number of bytes written
*
************************************************************************/

PBYTE WriteResHeader(
    PRES pRes,
    DWORD DataSize,
    INT iResType,
    LPTSTR pszResName,
    WORD fResFlags,
    WORD LanguageId,
    DWORD DataVersion,
    DWORD Version,
    DWORD Characteristics)
{
    PBYTE pb;
    PRES2 pRes2;

    pb = (PBYTE)pRes + sizeof(RES);
    pb = WriteOrd((PORDINAL)pb, iResType);
    pb = NameOrdCpy((LPTSTR)pb, pszResName);
    DWordPad(&pb);

    pRes->DataSize = DataSize;
    pRes->HeaderSize = (pb - (PBYTE)pRes) + sizeof(RES2);

    pRes2 = (PRES2)pb;
    pRes2->DataVersion = DataVersion;
    pRes2->MemoryFlags = fResFlags;
    pRes2->LanguageId = LanguageId;
    pRes2->Version = Version;
    pRes2->Characteristics = Characteristics;

    return (PBYTE)pRes + pRes->HeaderSize;
}

/************************************************************************
* WriteSz
*
* This function writes out the string given to the specified
* memory location.  It returns the first byte past the newly
* written string.
*
* Arguments:
*   LPTSTR pszDest - Pointer to the location to write the string.
*   LPTSTR pszSrc  - The string to write.
*
************************************************************************/

PBYTE WriteSz(
    LPTSTR pszDest,
    LPTSTR pszSrc)
{
    while (*pszSrc)
        *pszDest++ = *pszSrc++;

    *pszDest++ = CHAR_NULL;

    return (PBYTE)pszDest;
}


RWDLG.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 Header *******************************
* Module Name: rwdlg.c
*
* Does the writing of .DLG files.
*
* Functions:
*    WriteDlg()
*    WriteDlgInclude()
*    WriteDialogHeader()
*    WriteDialogHeaderLanguage()
*    WriteControl()
*    WriteNameOrd()
*    WriteText()
*    WriteIDDlg()
*    GetControlKeyword()
*    WriteClass()
*    WriteStyles()
*    WriteClassStyle()
*    WriteCustomStyle()
*    WriteCoords()
*    WriteValue()
*    WriteHexWord()
*    WriteHexDWord()
*    WriteString()
*    WriteQuotedString()
*    WriteEscapedString()
*    WriteDlgChar()
*    WriteDlgFlush()
*    Tab()
*    NewLine()
*    Quote()
*    Comma()
*    Space()
*    ORSymbol()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

/*
 * Wrap lines before they go over this right margin.
 */
#define CCHRIGHTMARGIN      76

/*
 * Defines for the tabs and tab indent levels.
 */
#define CCHTABWIDTH         4           // Tabs are four spaces wide.
#define TABLEVELNONE        0           // No indent (at left margin).
#define TABLEVELCONTROL     1           // Indent to start of controls.
#define TABLEVELCONTROLDESC 5           // Indent to control description.

/*
 * Macro to set the current tab level.  The level is multiplied
 * by the tab width.
 */
#define SetTab(t)           (cTabStop = ((t)*CCHTABWIDTH))

/*
 * Macro that determines if the current position is the first
 * column for the current tab setting.
 */
#define AtFirstTabColumn()  ((cColumn == cTabStop) ? TRUE : FALSE)

STATICFN VOID WriteDlgInclude(LPTSTR pszFullDlgFile);
STATICFN PCONTROLDATA WriteDialogHeader(PRES pRes, PDIALOGBOXHEADER pdbh);
STATICFN VOID WriteDialogHeaderLanguage(WORD wLanguage);
STATICFN PCONTROLDATA WriteControl(PCONTROLDATA pcd);
STATICFN VOID WriteNameOrd(LPTSTR pszNameOrd);
STATICFN VOID WriteText(LPTSTR pszText);
STATICFN VOID WriteIDDlg(INT id, BOOL fHexOK);
STATICFN LPTSTR GetControlKeyword(INT iClass, DWORD flStyle,
    DWORD *pflStylePredef, DWORD *pflStyleDefault, BOOL *pfWriteText,
    BOOL *pfNotFound);
STATICFN VOID WriteClass(LPTSTR pszClass);
STATICFN BOOL WriteStyles(INT iClass, LPTSTR pszClass, DWORD flStyle,
    DWORD flStylePredef, DWORD flStyleDefault, PDWORD pflStyleLeft,
    BOOL fNullStyles, BOOL fCommaPrefix);
STATICFN BOOL WriteClassStyle(INT iClass, DWORD flStyle,
    DWORD flStylePredef, DWORD flStyleDefault, PDWORD pflStyleLeft,
    BOOL fPrevWritten, BOOL fNullStyles, BOOL fCommaPrefix);
STATICFN BOOL WriteCustomStyle(LPTSTR pszClass, DWORD flStyle,
    PDWORD pflStyleLeft);
STATICFN VOID WriteCoords(INT x, INT y, INT cx, INT cy);
STATICFN VOID WriteValue(INT n);
STATICFN VOID WriteHexWord(WORD w);
STATICFN VOID WriteHexDWord(DWORD dw);
STATICFN VOID WriteString(LPTSTR psz);
STATICFN VOID WriteQuotedString(LPTSTR psz);
STATICFN VOID WriteEscapedString(LPTSTR psz);
STATICFN VOID WriteDlgChar(TCHAR ch);
STATICFN VOID WriteDlgFlush(VOID);
STATICFN VOID Tab(VOID);
STATICFN VOID NewLine(VOID);
STATICFN VOID Quote(VOID);
STATICFN VOID Comma(VOID);
STATICFN VOID Space(VOID);
STATICFN VOID ORSymbol(VOID);

static INT cColumn;                 /* Current column in the line.      */
static INT cTabStop;                /* Current tabstop column.          */
static HANDLE hfDlg;                /* All workers write to this file.  */
static jmp_buf jbWriteDlg;          /* Capture the state for longjmp.   */

/************************************************************************
* WriteDlg
*
* This function writes the dialog boxes in the given resource to the
* hfWrite file in the .DLG file RC format.
*
* Arguments:
*    HANDLE - handle to the file
*    LPTSTR - pointer to the resource file name
*
************************************************************************/

BOOL WriteDlg(
    HANDLE hfWrite,
    LPTSTR pszFullDlgFile)
{
    HANDLE hResLocked = NULL;
    PRES pRes = NULL;
    PRESLINK prl;
    PDIALOGBOXHEADER pdbh;
    PCONTROLDATA pcd;
    INT cItems;

    /*
     * Set our error trap up.  The api setjmp will return a zero at first,
     * then if a write error occurs later and longjmp is called, it
     * will return non-zero and we will return the failure up to the
     * caller.  After this point, there must be no calls to allocate
     * memory, open files, etc., unless this trap has a way to detect
     * what happened and clean it up.  See the c-runtime help file for more
     * information about setjump/longjmp.
     */
    if (_setjmp(jbWriteDlg)) {
        /*
         * If the resource is locked, unlock it.
         */
        if (hResLocked)
            GlobalUnlock(hResLocked);

        return FALSE;
    }

    /*
     * Initialize our globals.  The hfDlg global is used so that hfWrite
     * doesn't have to be passed on the stack over and over.
     */
    hfDlg = hfWrite;
    cColumn = 0;
    SetTab(TABLEVELNONE);

    WriteDlgInclude(pszFullDlgFile);

    /*
     * Process each resource in the list.
     */
    for (prl = gprlHead; prl; prl = prl->prlNext) {
        /*
         * Skip if it is not a dialog resource.
         */
        if (!prl->fDlgResource)
            continue;

        /*
         * Set up pointers to this dialog resource.
         */
        pRes = (PRES)GlobalLock(prl->hRes);
        hResLocked = prl->hRes;

        pdbh = (PDIALOGBOXHEADER)SkipResHeader(pRes);

        NewLine();
        pcd = WriteDialogHeader(pRes, pdbh);

        WriteString(ids(IDS_BEGIN));
        NewLine();

        /*
         * Write the controls.
         */
        cItems = (INT)pdbh->NumberOfItems;
        while (cItems--)
            pcd = WriteControl(pcd);

        /*
         * Finish up dialog template.
         */
        WriteString(ids(IDS_END));
        NewLine();

        GlobalUnlock(prl->hRes);
        hResLocked = NULL;
    }

    /*
     * Flush any remaining characters in the write buffer.
     */
    WriteDlgFlush();

    return TRUE;
}

/************************************************************************
* WriteDlgInclude
*
* This routine writes out the "DLGINCLUDE" lines to the .DLG file.
*
* Arguments:
*    LPTSTR - dialog file name
*
************************************************************************/

STATICFN VOID WriteDlgInclude(
    LPTSTR pszFullDlgFile)
{
    if (pszIncludeFile) {
        WriteValue(ORDID_DLGINCLUDE_NAME);
        Space();
        WriteString(ids(IDS_DLGINCLUDE));
        Space();
        Quote();

        /*
         * If the include file is in a different directory than the resource
         * file, write the full path to it.  Otherwise, we just write the
         * include file name.
         */
        if (DifferentDirs(pszFullDlgFile, szFullIncludeFile))
            WriteEscapedString(szFullIncludeFile);
        else
            WriteEscapedString(pszIncludeFile);

        Quote();
        NewLine();
    }
}

/************************************************************************
* WriteDialogHeader
*
* Writes out the dialog header lines.
*
* Arguments:
*    PRES - pointer to the resource
*    PDIALOGBOXHEADER - pointer to the dialog box header
*
************************************************************************/

STATICFN PCONTROLDATA WriteDialogHeader(
    PRES pRes,
    PDIALOGBOXHEADER pdbh)
{
    DWORD flStyle;
    DWORD flExtStyle;
    DWORD flStyleLeft;
    INT cdit;
    INT x;
    INT y;
    INT cx;
    INT cy;
    LPTSTR pszMenuName;
    LPTSTR pszClass;
    LPTSTR pszCaption;
    INT nPointSize;
    LPTSTR pszFontName;
    PCONTROLDATA pcd;
    PRES2 pRes2;
    BOOL fWritten;

    pRes2 = ResourcePart2(pRes);

    WriteNameOrd(ResourceName(pRes));
    Space();
    WriteString(ids(IDS_DIALOG));

    if (pRes2->MemoryFlags & MMF_PRELOAD) {
        Space();
        WriteString(ids(IDS_PRELOAD));
    }

    if (!(pRes2->MemoryFlags & MMF_MOVEABLE)) {
        Space();
        WriteString(ids(IDS_FIXED));
    }

    if (!(pRes2->MemoryFlags & MMF_PURE)) {
        Space();
        WriteString(ids(IDS_IMPURE));
    }

    /*
     * Parse out the dialog template.
     */
    pcd = ParseDialogBoxHeader(pdbh, &flStyle, &flExtStyle, &cdit, &x, &y,
            &cx, &cy, &pszMenuName, &pszClass, &pszCaption,
            &nPointSize, &pszFontName);

    Space();
    WriteCoords(x, y, cx, cy);

    NewLine();

    /*
     * Write the language.
     */
    WriteDialogHeaderLanguage(pRes2->LanguageId);

    /*
     * Print out the "STYLE" line for the dialog.
     */
    WriteString(ids(IDS_STYLE));
    Space();
    SetTab(TABLEVELCONTROL);
    WriteStyles(IC_DIALOG, NULL, flStyle, 0L, 0L, &flStyleLeft, TRUE, FALSE);
    SetTab(TABLEVELNONE);
    NewLine();

    /*
     * Print out the "EXSTYLE" line for the dialog, if necessary.
     */
    if (flExtStyle) {
        WriteString(ids(IDS_EXSTYLE));
        Space();
        SetTab(TABLEVELCONTROL);

        fWritten = WriteClassStyle(IC_EXSTYLE, flExtStyle, 0L, 0L,
                &flStyleLeft, FALSE, TRUE, FALSE);

        /*
         * If there is anything left (styles that the dialog editor
         * does not know about) write it out as a hex constant.
         */
        if (flStyleLeft) {
            if (fWritten)
                ORSymbol();

            WriteHexDWord(flStyleLeft);
        }

        SetTab(TABLEVELNONE);
        NewLine();
    }

    /*
     * If it has a caption, print it out.
     */
    if (*pszCaption) {
        WriteString(ids(IDS_CAPTION));
        Space();
        WriteText(pszCaption);
        NewLine();
    }

    /*
     * If it has a font specified, print it out.
     */
    if (flStyle & DS_SETFONT) {
        WriteString(ids(IDS_FONT));
        Space();

        WriteValue(nPointSize);
        Comma();
        WriteQuotedString(pszFontName);
        NewLine();
    }

    /*
     * If it has a class specified, print it out.
     */
    if (*pszClass) {
        WriteString(ids(IDS_CLASS));
        Space();
        WriteText(pszClass);
        NewLine();
    }

    /*
     * If it has a menu specified, print it out.
     */
    if (*pszMenuName) {
        WriteString(ids(IDS_MENU));
        Space();
        WriteNameOrd(pszMenuName);
        NewLine();
    }

    if (pRes2->Version) {
        WriteString(ids(IDS_VERSION));
        Space();
        WriteValue(pRes2->Version);
        NewLine();
    }

    if (pRes2->Characteristics) {
        WriteString(ids(IDS_CHARACTERISTICS));
        Space();
        WriteValue(pRes2->Characteristics);
        NewLine();
    }

    return pcd;
}

/************************************************************************
* WriteDialogHeaderLanguage
*
* Writes out the dialog header "LANGUAGE" line.
*
* Arguments:
*    WORD - language to write out
*
************************************************************************/

STATICFN VOID WriteDialogHeaderLanguage(
    WORD wLanguage)
{
    WORD wPrimary;
    WORD wSubLang;
    INT i;
    INT j;
    INT idsLang;
    INT idsSubLang;

    WriteString(ids(IDS_LANGUAGE));
    Space();

    idsLang = 0;
    idsSubLang = 0;
    wPrimary = (WORD)PRIMARYLANGID(wLanguage);
    wSubLang = SUBLANGID(wLanguage);
    for (i = 0; i < gcLanguages; i++) {
        if (gaLangTable[i].wPrimary == wPrimary) {
            idsLang = gaLangTable[i].idsLang;

            for (j = 0; j < gaLangTable[i].cSubLangs; j++) {
                if (gaLangTable[i].asl[j].wSubLang == wSubLang) {
                    idsSubLang = gaLangTable[i].asl[j].idsSubLang;
                    break;
                }
            }

            break;
        }
    }

    if (idsLang)
        WriteString(ids(idsLang));
    else
        WriteHexWord(wPrimary);

    Comma();

    if (idsSubLang)
        WriteString(ids(idsSubLang));
    else
        WriteHexWord(wSubLang);

    NewLine();
}

/************************************************************************
* WriteControl
*
* Writes out a control line.
*
* Arguments:
*    PCONTROLDATA - pointer to the control data
*
************************************************************************/

STATICFN PCONTROLDATA WriteControl(
    PCONTROLDATA pcd)
{
    INT x;
    INT y;
    INT cx;
    INT cy;
    INT id;
    DWORD flStyle;
    DWORD flExtStyle;
    LPTSTR pszClass;
    LPTSTR pszText;
    INT iClass;
    LPTSTR pszKeyword;
    BOOL fWriteText;
    BOOL fNotFound;
    DWORD flStylePredef;
    DWORD flStyleDefault;
    DWORD flStyleLeft;
    BOOL fWritten;

    pcd = ParseControlData(pcd, &flStyle, &flExtStyle, &x, &y, &cx, &cy,
            &id, &pszClass, &pszText);

    /*
     * Determine the class of the control.
     */
    iClass = GetiClass(pszClass);

    /*
     * Determine if there are any predefined RC keywords that we
     * can use instead of the generic "CONTROL" keyword for this
     * style of control.
     */
    pszKeyword = GetControlKeyword(iClass, flStyle, &flStylePredef,
            &flStyleDefault, &fWriteText, &fNotFound);

    SetTab(TABLEVELCONTROL);
    Tab();
    WriteString(pszKeyword);
    SetTab(TABLEVELCONTROLDESC);
    Tab();

    /*
     * Write out the text field, if this type of control has one.
     */
    if (fWriteText) {
#ifdef JAPAN
        TCHAR   szTmp[CCHTEXTMAX];

        KDExpandCopy(szTmp, pszText, CCHTEXTMAX);
        WriteText(szTmp);
#else
        WriteText(pszText);
#endif
        Comma();
    }

    /*
     * Write out the id for the control.
     */
    WriteIDDlg(id, TRUE);

    /*
     * If we did not find a predefined keyword to use instead of "CONTROL",
     * we have to write out the fields in a different order, and specify
     * the class as well.
     */
    if (fNotFound) {
        WriteClass(pszClass);
        Comma();
        fWritten = WriteStyles(iClass, pszClass, flStyle, flStylePredef,
                flStyleDefault, &flStyleLeft, fNotFound, FALSE);

        if (!fWritten || flStyleLeft) {
            if (fWritten)
                ORSymbol();

            WriteHexWord(LOWORD(flStyleLeft));
        }

        Comma();
        WriteCoords(x, y, cx, cy);
    }
    else {
        Comma();
        WriteCoords(x, y, cx, cy);
        fWritten = WriteStyles(iClass, pszClass, flStyle, flStylePredef,
                flStyleDefault, &flStyleLeft, fNotFound, TRUE);

        if (flStyleLeft) {
            if (fWritten)
                ORSymbol();
            else
                Comma();

            WriteHexWord(LOWORD(flStyleLeft));
            fWritten = TRUE;
        }
    }

    /*
     * Write out the extended styles for the control, if necessary.
     */
    if (flExtStyle) {
        /*
         * If writing a predefined keyword (not CONTROL), and there
         * were no styles written out at the end of the line, write
         * a style of zero.  RC doesn't like consecutive comma's,
         * and we need to skip the styles field to get to the
         * extended styles field.
         */
        if (!fNotFound && !fWritten) {
            Comma();
            WriteValue(0);
        }

        Comma();

        fWritten = WriteClassStyle(IC_EXSTYLE, flExtStyle, 0L, 0L,
                &flStyleLeft, FALSE, TRUE, FALSE);

        /*
         * If there is anything left (styles that the dialog editor
         * does not know about) write it out as a hex constant.
         */
        if (flStyleLeft) {
            if (fWritten)
                ORSymbol();

            WriteHexDWord(flStyleLeft);
        }
    }

    SetTab(TABLEVELNONE);
    NewLine();

    return pcd;
}

/************************************************************************
* WriteNameOrd
*
* Writes out the name/ordinal.  Handles the case where the name
* is really an ordinal instead of a string.  When it is a string,
* it will not be quoted.
*
* This routine never writes the ordinal out in hex, because the
* items that it is intended to write are not parsed properly by
* the Windows RC.EXE if they are written in hex notation.
*
* Arguments:
*   LPTSTR pszNameOrd - The name/ordinal to write.
*
************************************************************************/

STATICFN VOID WriteNameOrd(
    LPTSTR pszNameOrd)
{
    if (IsOrd(pszNameOrd))
        /*
         * Write the name as a numeric ordinal.
         */
        WriteIDDlg(OrdID(pszNameOrd), FALSE);
    else
        WriteString(pszNameOrd);
}

/************************************************************************
* WriteText
*
* Writes out the text for a control or dialog.  This will either be
* an ordinal (icon's text field) or a quoted string.
*
* Arguments:
*    LPTSTR pszText - text to write out
*
************************************************************************/

STATICFN VOID WriteText(
    LPTSTR pszText)
{
    if (IsOrd(pszText))
        /*
         * Write the text as an ID.  Hex notation is allowed.
         */
        WriteIDDlg(OrdID(pszText), TRUE);
    else
        WriteQuotedString(pszText);
}

/************************************************************************
* WriteIDDlg
*
* Writes out the ID.  This may be written out as either a symbol
* or a numeric.
*
* Arguments:
*    INT id - id to write out
*    BOOL fHexOK - flag specifies whether the id is a hex numeric.
*
************************************************************************/

STATICFN VOID WriteIDDlg(
    INT id,
    BOOL fHexOK)
{
    TCHAR szID[CCHTEXTMAX];

    IDToLabel(szID, id, fHexOK);
    WriteString(szID);
}

/************************************************************************
* GetControlKeyword
*
* This routine does a lookup in the predefined RC keyword table
* associated with the given class for a keyword that can be used
* instead of "CONTROL".  The match is based on the style of the control
* that is passed in.  If a match is not found, it defaults all the
* returned values to use the "CONTROL" keyword.
*
* Arguments:
*   INT iClass              - The class of the control.
*   DWORD flStyle           - The style of the control.
*   DWORD *pflStylePredef   - Return for the bits of the predefined control
*                             (if found).  These can be removed later from
*                             the style flag.
*   DWORD *pflStyleDefault  - Return for the default styles.
*   BOOL *pfWriteText       - Return for the "Write Text" flag.  This will
*                             be TRUE if this control has a text field.
*   BOOL *pfNotFound        - Return for the "Not Found" flag.  This will
*                             be TRUE if no match was found and the "CONTROL"
*                             keyword was defaulted to.
*
* Returns:
*   A pointer to the control keyword to use.
*   If a match was found, *pflStylePredef is set to the bits for the match.
*       If not found, this is set to zero.
*   The default style bits for this keyword will be returned.
*   The "Write Text" flag will be set.
*   The "Not Found" flag will be set.
*
************************************************************************/

STATICFN LPTSTR GetControlKeyword(
    INT iClass,
    DWORD flStyle,
    DWORD *pflStylePredef,
    DWORD *pflStyleDefault,
    BOOL *pfWriteText,
    BOOL *pfNotFound)
{
    register INT i;
    INT iMax;
    PRCKEYWORD prckwd;

    if (gfUseNewKeywords && iClass != IC_UNKNOWN) {
        iMax = acsd[iClass].cKeywords;
        prckwd = acsd[iClass].parckwd;

        /*
         * Loop through all the keywords for this class.
         */
        for (i = 0; i < iMax; i++, prckwd++) {
            /*
             * Does the style (masked) exactly match the keywords style?
             */
            if ((flStyle & prckwd->flStyleMask) == prckwd->flStyle) {
                /*
                 * Yes.  Set the "Has Text" flag, we did find a match,
                 * put the found bits in the predefined style flag,
                 * set the default styles flag and return the found
                 * keyword.
                 */
                *pfWriteText = prckwd->fHasText;
                *pfNotFound = FALSE;
                *pflStylePredef = prckwd->flStyle;
                *pflStyleDefault = prckwd->flStyleDefault;
                return ids(prckwd->idsKeyword);
            }
        }
    }

    /*
     * A match was not found.  We must write text, we didn't find a
     * match, we will be using the "CONTROL" keyword and the default
     * styles that this keyword implies is the "child" and "visible"
     * bits (rc.exe OR's these styles in implicitly).
     */
    *pfWriteText = TRUE;
    *pfNotFound = TRUE;
    *pflStylePredef = 0L;
    *pflStyleDefault = WS_VISIBLE | WS_CHILD;
    return ids(IDS_CONTROL);
}

/************************************************************************
* WriteClass
*
* Writes out the class for a control.
*
* Arguments:
*    LPTSTR pszClass - pointer to the class string
*
************************************************************************/

STATICFN VOID WriteClass(
    LPTSTR pszClass)
{
    INT i;
    WORD idOrd;

    Comma();

    /*
     * Is this class a predefined type instead of a string?
     */
    if (IsOrd(pszClass)) {
        /*
         * Figure out which type it is and get the class string to
         * write.
         */
        idOrd = OrdID(pszClass);
        for (i = 0; i < IC_DIALOG; i++) {
            if (acsd[i].idOrd == idOrd) {
                pszClass = ids(acsd[i].idsClass);
                break;
            }
        }
    }

    WriteQuotedString(pszClass);
}

/************************************************************************
* WriteStyles
*
* This function writes the class and style info to the file
* for the control or dialog box in the RC format.
*
* Arguments:
*     INT iClass           = The class of the item.
*     LPTSTR pszClass      = Class name of the control.
*     DWORD flStyle        = The style of the item.
*     DWORD flStylePredef  = The styles bits implicit in the predefined
*                            keyword for this control.  This should be
*                            zero if this control doesn't have a predefined
*                            keyword for it.
*     DWORD flStyleDefault = The default styles implicit in the item.
*     PDWORD pflStyleLeft  = Where to  return any style bits that do not
*                            get written out.
*     BOOL fNullStyles     = TRUE if we should still write the style word
*                            even if the style flag is zero.
*     BOOL fCommaPrefix    = TRUE means that a comma will be written out
*                            before writing any styles.  If no styles
*                            are written, no comma will be written either.
*
* Returns:
*     TRUE => Something was written out.
*     FALSE => Nothing was written out.
*
************************************************************************/

STATICFN BOOL WriteStyles(
    INT iClass,
    LPTSTR pszClass,
    DWORD flStyle,
    DWORD flStylePredef,
    DWORD flStyleDefault,
    PDWORD pflStyleLeft,
    BOOL fNullStyles,
    BOOL fCommaPrefix)
{
    DWORD flStyleLeft;
    BOOL fWritten = FALSE;

    /*
     * Write the control specific styles.
     */
    if (iClass == IC_CUSTOM) {
        fWritten = WriteCustomStyle(pszClass, flStyle, &flStyleLeft);
    }
    else {
        fWritten = WriteClassStyle(iClass, flStyle, flStylePredef,
                flStyleDefault, &flStyleLeft, FALSE, fNullStyles,
                fCommaPrefix);
    }

    /*
     * If we are writing styles for the dialog, remove the WS_GROUP
     * and WS_TABSTOP bits from the style before proceeding.  This is
     * because the WS_MINIMIZEBOX and WS_MAXIMIZEBOX styles use the
     * same bits, and these keywords will have already been written
     * out by the preceding WriteClassStyle call if those bits are
     * present.
     */
    if (iClass == IC_DIALOG)
        flStyle &= ~(WS_GROUP | WS_TABSTOP);

    /*
     * Write the window styles that are common to the different
     * controls (the high word).
     */
    fWritten |= WriteClassStyle(IC_WINDOW, flStyleLeft, flStylePredef,
            flStyleDefault, &flStyleLeft, fWritten, fNullStyles, fCommaPrefix);

    /*
     * Pass back any styles that were not written.
     */
    *pflStyleLeft = flStyleLeft;

    return fWritten;
}

/************************************************************************
* WriteClassStyle
*
* This function writes the class style symbols to the file.  The styles
* to write out are passed in flStyle, and the styles that are implicitly
* set by this type of control already are passed in flStyleDefault.  The
* style keywords corresponding to the bits in flStyle are written out,
* separated by " | ", and any bits in flStyleDefault that are NOT set
* are written out preceded by a "NOT" to explicitly turn them off.  This
* is used in the case of the predefined RC keywords, which often have
* styles like WS_TABSTOP or WS_VISIBLE already implicit in them.  There
* is no need to explicitly specify them, but if they are not present, we
* must NOT them out.  The parameter flStylePredef contains the style bits
* that identified the predefined control keyword itself (if any) and
* thus are removed from the style before writing anything out.
*
* Arguments:
*     INT iClass           = The class of the control. See the
*                            IC_ constants defined in dlgedit.h.
*     DWORD flStyle        = The style of control.  This nails
*                            down the exact type of control.
*     DWORD flStylePredef  = The styles bits implicit in the predefined
*                            keyword for this control.  This should be
*                            zero if this control doesn't have a predefined
*                            keyword for it.
*     DWORD flStyleDefault = The default styles that are implicit with
*                            this control.  This will only be set if this
*                            control is using a predefined RC keyword. A
*                            value of zero means that there are no default
*                            styles implicitly specified.
*     PDWORD pflStyleLeft  = Where to  return any style bits that do not
*                            get written out.
*     BOOL fPrevWritten    = TRUE means a previous style symbol has
*                            been written and to put " | " before
*                            the next symbol.
*     BOOL fNullStyles     = TRUE if we should still write the style word
*                            even if the style flag is zero.  This is used
*                            to handle the case where a predefined keyword
*                            has been written out that implies a style that
*                            also happens to be zero.  Without this flag
*                            being FALSE the style flag implicit in the
*                            keyword would be redundantly written out again.
*                            In general, if we have written out a predefined
*                            keyword this flag should be FALSE.
*     BOOL fCommaPrefix    = TRUE means that a comma will be written out
*                            before writing any styles.  This will only
*                            happen if fPrevWritten is FALSE.  If no styles
*                            are written, no comma will be written either.
*
* Returns:
*     TRUE => Something was written out.
*     FALSE => Nothing was written out.
*
************************************************************************/

STATICFN BOOL WriteClassStyle(
    INT iClass,
    DWORD flStyle,
    DWORD flStylePredef,
    DWORD flStyleDefault,
    PDWORD pflStyleLeft,
    BOOL fPrevWritten,
    BOOL fNullStyles,
    BOOL fCommaPrefix)
{
    register WORD i;
    WORD iMax;
    DWORD flStyleMask;
    PCLASSSTYLE pcs;

    iMax = (WORD)acsd[iClass].cClassStyles;
    pcs = acsd[iClass].pacs;

    /*
     * Remove the bits that identified the predefined control keyword
     * from the style flag before proceeding.  For instance, if I already
     * am going to be writing out a "PUSHBUTTON", there is no reason
     * to write out the "BS_PUSHBUTTON" style.  If there is no predefined
     * control keyword, flStylePredef will be zero and this will do
     * nothing.
     */
    flStyle &= ~flStylePredef;

    /*
     * Go through all possible flags for this style.
     */
    for (i = 0; i < iMax; i++, pcs++) {
        flStyleMask = pcs->flStyleMask ? pcs->flStyleMask : pcs->flStyle;

        /*
         * Is this styles bits set?
         */
        if ((flStyle & flStyleMask) == pcs->flStyle) {
            /*
             * Remove these bits from the styles left.  Even if
             * we do not write them out, they are still accounted
             * for and can be removed from the styles remaining.
             */
            flStyle &= ~pcs->flStyle;

            /*
             * Skip this style if we don't want to write styles that are
             * zero, or if the style is already implicitly specified for
             * this control (a non-zero default style mask must be specified).
             */
            if ((!pcs->flStyle && !fNullStyles) ||
                    (flStyleDefault &&
                    (flStyleDefault & flStyleMask) == pcs->flStyle))
                continue;

            /*
             * If there is a string for this style, write it out, preceded
             * by an "|" symbol if necessary.
             */
            if (*ids(acsd[iClass].idsStylesStart + i)) {
                if (fPrevWritten) {
                    ORSymbol();
                }
                else {
                    if (fCommaPrefix)
                        Comma();

                    fPrevWritten = TRUE;
                }

                /*
                 * Write the string.
                 */
                WriteString(ids(acsd[iClass].idsStylesStart + i));
            }
        }
        /*
         * No the styles bit is not set.  Is it implicit in the keyword
         * being used?  If so, we need to explicitly NOT it
         * out in the dialog template.
         * Note that this should not be done in the case where the style
         * is zero.
         */
        else if (flStyleDefault &&
                (flStyleDefault & flStyleMask) == pcs->flStyle &&
                pcs->flStyle) {
            if (fPrevWritten) {
                ORSymbol();
            }
            else {
                if (fCommaPrefix)
                    Comma();

                fPrevWritten = TRUE;
            }

            WriteString(ids(IDS_NOT));
            Space();
            WriteString(ids(acsd[iClass].idsStylesStart + i));
        }
    }

    /*
     * Pass back the style bits that were not written out.
     */
    *pflStyleLeft = flStyle;

    return fPrevWritten;
}

/************************************************************************
* WriteCustomStyle
*
* Writes our the custom control information
*
* Arguments:
*    LPTSTR - class name of the control
*    DWORD - style of the control
*    PDWORD - styles left to be written
*
* Returns:
*    TRUE if custom styles were written out; otherwise, FALSE.
*
************************************************************************/

STATICFN BOOL WriteCustomStyle(
    LPTSTR pszClass,
    DWORD flStyle,
    PDWORD pflStyleLeft)
{
    PCUSTLINK pcl;
    LPCCSTYLEFLAG pStyleFlags;
    DWORD flStyleMask;
    INT i;
    BOOL fWritten = FALSE;

    /*
     * Search the list of installed custom controls for one
     * that matches the class.
     */
    for (pcl = gpclHead;
            pcl && lstrcmpi(pcl->pwcd->pszClass, pszClass) != 0;
            pcl = pcl->pclNext)
        ;

    /*
     * Was a match found and is this control from a DLL (not emulated)?
     */
    if (pcl && !pcl->pwcd->fEmulated) {
        for (i = 0, pStyleFlags = pcl->pwcd->aStyleFlags;
                i < pcl->pwcd->cStyleFlags;
                i++, pStyleFlags++) {
            flStyleMask = pStyleFlags->flStyleMask ?
                    pStyleFlags->flStyleMask : pStyleFlags->flStyle;

            /*
             * Is this styles bits set?
             */
            if ((flStyle & flStyleMask) == pStyleFlags->flStyle) {
                /*
                 * Remove these bits from the styles left.
                 */
                flStyle &= ~pStyleFlags->flStyle;

                if (fWritten)
                    ORSymbol();
                else
                    fWritten = TRUE;

                /*
                 * Write the string.
                 */
                WriteString(pStyleFlags->pszStyle);
            }
        }
    }

    /*
     * Return the styles that remain to be written.
     */
    *pflStyleLeft = flStyle;

    return fWritten;
}

/************************************************************************
* WriteCoords
*
* This function writes the coordinates out to the file as decimal
* ascii numbers separated by ", ".
*
* Arguments:
*     INT x, y, cx, cy = The coordinates.
*
************************************************************************/

STATICFN VOID WriteCoords(
    INT x,
    INT y,
    INT cx,
    INT cy)
{
    WriteValue(x);
    Comma();
    WriteValue(y);
    Comma();
    WriteValue(cx);
    Comma();
    WriteValue(cy);
}

/************************************************************************
* WriteValue
*
* This function writes the value of 'n' as a decimal ascii string to
* the file.
*
* Arguments:
*     INT n = The number to write.
*
************************************************************************/

STATICFN VOID WriteValue(
    INT n)
{
    TCHAR szNum[32];

    itoaw(n, szNum, 10);
    WriteString(szNum);
}

/************************************************************************
* WriteHexWord
*
* This function writes the value of 'w' as a hex constant to the file.
*
* Arguments:
*     WORD w - The word to write.
*
************************************************************************/

STATICFN VOID WriteHexWord(
    WORD w)
{
    TCHAR szNum[17];

    itoax(w, szNum);
    WriteString(szNum);
}

/************************************************************************
* WriteHexDWord
*
* This function writes the value of 'dw' as a hex constant to the file.
*
* Arguments:
*     DWORD dw - The dword to write.
*
************************************************************************/

STATICFN VOID WriteHexDWord(
    DWORD dw)
{
    TCHAR szNum[32];

    wsprintf(szNum, L"0x%8.8X", dw);
    WriteString(szNum);
}

/************************************************************************
* WriteString
*
* This function writes the given string to the file.  If the string
* would cause it to overflow the margin, a new line, with indenting
* to the current tab level, is forced before writing the string.
*
* Arguments:
*   LPTSTR psz = The string to write out.
*
************************************************************************/

STATICFN VOID WriteString(
    LPTSTR psz)
{
    register INT nLen;

    nLen = lstrlen(psz);

    if (!AtFirstTabColumn() && cColumn + nLen > CCHRIGHTMARGIN)
        NewLine();

    while (nLen--)
        WriteDlgChar(*psz++);
}

/************************************************************************
* WriteQuotedString
*
* This function writes the given string to the file.  If the string
* would cause it to overflow the margin, a new line, with indenting
* to the current tab level, is forced before writing the string.
* This function will also enclose the given string in double-quotes,
* and ensures that the string will not be broken when it is written.
* If there are any escape characters (backslashes or quotes) in the
* string, they will be escaped properly so that rc.exe can read them
* properly.
*
* Arguments:
*   LPTSTR psz = The string to write out.
*
************************************************************************/

STATICFN VOID WriteQuotedString(
    LPTSTR psz)
{
    register INT nLen;
    LPTSTR pszT;

    /*
     * Find the actual length of the string.  To do this, we must scan
     * for the characters that will be escaped later.
     */
    nLen = lstrlen(psz);
    pszT = psz;
    while (*pszT) {
        if (*pszT == CHAR_DBLQUOTE || *pszT == CHAR_BACKSLASH)
            nLen++;

        pszT = CharNext(pszT);
    }

    /*
     * Start a new line if necessary.  Add 2 for the quotes.
     */
    if (!AtFirstTabColumn() && cColumn + nLen + 2 > CCHRIGHTMARGIN)
        NewLine();

    Quote();
    WriteEscapedString(psz);
    Quote();
}

/************************************************************************
* WriteEscapedString
*
* This function writes the given string to the file.  It is different
* from WriteString in that it will add a '\' in front of other
* backslashes and a second double quote in front of double quotes.
* This is necessary when writing out a string which will be surrounded
* by quotes, such as the Text fields in the .DLG file.
*
* Arguments:
*   LPTSTR psz = The string to write out.
*
************************************************************************/

STATICFN VOID WriteEscapedString(
    LPTSTR psz)
{
    while (*psz) {
        if (*psz == CHAR_DBLQUOTE)
            WriteDlgChar(CHAR_DBLQUOTE);
        else if (*psz == CHAR_BACKSLASH)
#ifdef JAPAN
#ifndef UNICODE
#define wcsncmp     strncmp
#endif
            if ((wcsncmp(psz+1, TEXT("036"), 3)) &&
                (wcsncmp(psz+1, TEXT("037"), 3)))
#endif
            WriteDlgChar(CHAR_BACKSLASH);

        if(IsDBCSLeadByte((BYTE)*psz))
            WriteDlgChar(*psz++);
        WriteDlgChar(*psz++);
    }
}

/************************************************************************
* WriteDlgChar
*
* Low level function to do an actual character write to the file.
* Some buffering is done then _lwrite is called.
*
* Because it is buffered, before closing the file any remaining
* characters in the buffer must be flushed to disk using WriteDlgFlush.
*
* If an error occurs on the write, Throw will be called to jump back
* up to WriteDlg and return the failure to the caller.
*
* The globals gachWriteBuffer and cbWritePos are updated by this routine.
*
* Arguments:
*     TCHAR ch - The character to write.
*
* Returns:
*   If an error occurs on the _lwrite, the execution will be thrown
*   back to the WriteDlg function.  Otherwise, nothing is returned.
*
************************************************************************/

STATICFN VOID WriteDlgChar(
    TCHAR ch)
{
    INT cbWritten;

    gachWriteBuffer[cbWritePos++] = ch;

    /*
     * Is the buffer full?
     */
    if (cbWritePos == CCHFILEBUFFER) {
// The abWriteBuffer[] is too small for Japanese language.
        // Please don't assume WideChar_length/2 == MultiByte_length.
        // July 12, 1992 by ShigeO
        CHAR abWriteBuffer[CCHFILEBUFFER*2];
        BOOL fDefCharUsed;
        int  iRequired =

        WideCharToMultiByte(CP_ACP, 0, gachWriteBuffer, CCHFILEBUFFER,
                abWriteBuffer, CCHFILEBUFFER*2, NULL, &fDefCharUsed);

        cbWritten = (INT)_lwrite((HFILE)hfDlg, abWriteBuffer, iRequired);
        if (cbWritten != iRequired)
            longjmp(jbWriteDlg, 1);

        cbWritePos = 0;
    }

    /*
     * Update the current column counter.
     */
    if (ch == CHAR_RETURN || ch == CHAR_NEWLINE) {
        /*
         * Carriage return or newline resets column position to 0.
         */
        cColumn = 0;
    }
    else {
        cColumn++;
    }
}

/************************************************************************
* WriteDlgFlush
*
* This routine flushes the write buffer.  This must be done before
* the file is closed or data can be lost.
*
* The global cbWritePos is updated by this routine.
*
* Returns:
*   If an error occurs on the _lwrite, the execution will be thrown
*   back to the WriteDlg function.  Otherwise, nothing is returned.
*
************************************************************************/

STATICFN VOID WriteDlgFlush(VOID)
{
    INT cbWritten;

    /*
     * Are any bytes remaining in the buffer?
     */
    if (cbWritePos) {
// The abWriteBuffer[] is too small for Japanese language.
        // Please don't assume WideChar_length/2 == MultiByte_length.
        // July 12, 1992 by ShigeO
        CHAR abWriteBuffer[CCHFILEBUFFER*2];
        BOOL fDefCharUsed;
        int  iRequired =

        WideCharToMultiByte(CP_ACP, 0, gachWriteBuffer, cbWritePos,
                abWriteBuffer, CCHFILEBUFFER*2, NULL, &fDefCharUsed);

        cbWritten = (INT)_lwrite((HFILE)hfDlg, abWriteBuffer, iRequired);
        if (cbWritten != iRequired)
            longjmp(jbWriteDlg, 1);

        cbWritePos = 0;
    }
}

/****************************************************************************
* Tab
*
* Writes spaces up to the current tab level setting.
*
****************************************************************************/

STATICFN VOID Tab(VOID)
{
    while (cColumn < cTabStop)
        WriteDlgChar(CHAR_SPACE);
}

/****************************************************************************
* NewLine
*
* Begins a new line by writing a carriage return and linefeed.  Also
* indents the following line up to the current tab level.
*
****************************************************************************/

STATICFN VOID NewLine(VOID)
{
    WriteDlgChar(CHAR_RETURN);
    WriteDlgChar(CHAR_NEWLINE);
    Tab();
}

/****************************************************************************
* Quote
*
* Writes a double quote.
*
****************************************************************************/

STATICFN VOID Quote(VOID)
{
    WriteDlgChar(CHAR_DBLQUOTE);
}

/****************************************************************************
* Comma
*
* Writes a comma then a space.
*
****************************************************************************/

STATICFN VOID Comma(VOID)
{
    WriteDlgChar(CHAR_COMMA);
    WriteDlgChar(CHAR_SPACE);
}

/****************************************************************************
* Space
*
* Writes a space.
*
****************************************************************************/

STATICFN VOID Space(VOID)
{
    WriteDlgChar(CHAR_SPACE);
}

/****************************************************************************
* ORSymbol
*
* Writes " | ".
*
****************************************************************************/

STATICFN VOID ORSymbol(VOID)
{
    WriteDlgChar(CHAR_SPACE);
    WriteDlgChar(CHAR_ORSYMBOL);
    WriteDlgChar(CHAR_SPACE);
}


RWINC.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 Header *******************************
* Module Name: rwinc.c
*
* Does the include file reading and writing.
*
* Functions:
*
*    OpenIncludeFile()
*    FreeInclude()
*    WriteInc()
*    LoadIncludeFile()
*    GetChar()
*    ReadChar()
*    GetLabel()
*    GetValue()
*    GetWord()
*    FindDefine()
*    GetNextInc()
*    RWToOffset()
*    WriteIncChar()
*    WriteIncFlush()
*    WriteChangedInc()
*    WriteDeletedInc()
*    WriteAddedInc()
*    WriteSymbol()
*    WriteIDInc()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

#include <ctype.h>

/*
 * Field width that the symbol is printed within.  This indirectly
 * determines where the id value starts, because blanks are added
 * after the symbol is printed up to this width value.
 */
#define CCHSYMFIELDWIDTH    27

/*
 * Return codes from the file reading functions.
 */
#define READ_OK             1
#define READ_EOF            2
#define READ_BAD            3
#define READ_WRONG          4
#define BAD_POINTER         ((VOID *)0xFFFF)

/*
 * Return codes from the GetNextInc function.
 */
#define GNI_DONE            0
#define GNI_NOCHANGE        1
#define GNI_CHANGED         2
#define GNI_DELETED         3
#define GNI_ADDED           4

static BYTE abBuffer[CCHFILEBUFFER];    /* Buffer for read file data.       */
static TCHAR achBuffer[CCHFILEBUFFER];  /* Unicode buffer for data.         */
static INT cbBuf;                       /* Pointer into achBuffer.          */
static DWORD cchFile;                   /* Count of characters read.        */
static DWORD cchFileMax;                /* Max characters in file.          */
static DWORD fposLastDefine;            /* Saves location of "#define".     */
static DWORD fposWordStart;             /* Saves start of id value.         */
static BOOL fAtNewLine;                 /* At start or \r or \n.            */
static HANDLE hfInclude;                /* The current include file.        */

STATICFN BOOL LoadIncludeFile(VOID);
STATICFN LPTSTR GetChar(VOID);
STATICFN LPTSTR ReadChar(VOID);
STATICFN INT GetLabel(BOOL *pfDups);
STATICFN INT GetValue(PINT pnValue);
STATICFN INT GetWord(LPTSTR pch);
STATICFN INT FindDefine(VOID);
STATICFN INT GetNextInc(NPLABEL *pplReturn, BOOL fFirst);
STATICFN BOOL RWToOffset(HANDLE hfWrite, DWORD lOffset);
STATICFN BOOL WriteIncChar(HANDLE hfWrite, TCHAR ch);
STATICFN BOOL WriteIncFlush(HANDLE hfWrite);
STATICFN BOOL WriteChangedInc(HANDLE hfWrite, NPLABEL plInc);
STATICFN BOOL WriteDeletedInc(HANDLE hfWrite, NPLABEL plInc);
STATICFN BOOL WriteAddedInc(HANDLE hfWrite, NPLABEL plInc);
STATICFN BOOL WriteSymbol(HANDLE hfWrite, LPTSTR pszSymbol);
STATICFN BOOL WriteIDInc(HANDLE hfWrite, INT id);

/****************************************************************************
* OpenIncludeFile
*
* This function attempts to open and load the include file with name
* pointed to by pszOpenInclude.  If pszOpenInclude is just a file name, and
* not a path, then the path is taken from szFullLoadFile.  Otherwise
* pszOpenInclude itself is used.  The full pathname is put in
* szFullIncludeFile and pszIncludeFile is set to point to just the file
* name in it.
*
* If fDoOpen is TRUE, the file is opened.  If it is FALSE, it is assumed
* that hfInc contains the file handle to the opened include file and this
* handle is used.  In addition, the caller is responsible for closing
* any passed in file handle if an error occurs.
*
* Any existing includes are freed,  szFullIncludeFile is set to the full
* include path, pszIncludeFile is set to the filename portion of this full
* path and hfInclude will contain the file handle to the include file.
*
* Arguments:
*   LPTSTR pszOpenInclude - name of the include file to open.
*
* Returns:
*   If the load is successful, TRUE is returned.  Otherwise,
*   FALSE is returned.
*
****************************************************************************/

BOOL OpenIncludeFile(
    LPTSTR pszOpenInclude)
{
    TCHAR szFullIncludeFileTemp[CCHMAXPATH];
    HCURSOR hcurSave;
    BOOL fSuccess = FALSE;

    hcurSave = SetCursor(hcurWait);

    if (FileInPath(pszOpenInclude) == pszOpenInclude) {
        lstrcpy(szFullIncludeFileTemp, szFullResFile);
        lstrcpy(FileInPath(szFullIncludeFileTemp), pszOpenInclude);
    }
    else {
        lstrcpy(szFullIncludeFileTemp, pszOpenInclude);
    }

    /*
     * Close any existing include file and free memory.
     */
    FreeInclude();

    if ((hfInclude = CreateFile(szFullIncludeFileTemp, GENERIC_READ,
            FILE_SHARE_READ, NULL, OPEN_EXISTING,
            FILE_FLAG_SEQUENTIAL_SCAN, NULL)) != (HANDLE)-1) {
        if (LoadIncludeFile()) {
            lstrcpy(szFullIncludeFile, szFullIncludeFileTemp);
            pszIncludeFile = FileInPath(szFullIncludeFile);
            fSuccess = TRUE;
        }

        CloseHandle(hfInclude);
    }

    /*
     * Update the status windows symbol combo box.  Update other fields
     * also, in case the currently selected control's symbol was affected
     * by the reading of the new include file.
     */
    StatusFillSymbolList(plInclude);
    StatusUpdate();

    ShowFileStatus(TRUE);
    SetCursor(hcurSave);

    return fSuccess;
}

/************************************************************************
* LoadIncludeFile
*
* This function creates or adds to plInclude with all the #define
* statements in the file with handle hfInclude.
*
* Returns:
*     TRUE - Load succeeded.
*     FALSE - Load failed (a read error).
*
************************************************************************/

STATICFN BOOL LoadIncludeFile(VOID)
{
    INT RetCode;
    BOOL fDups = FALSE;

    /*
     * Set char count, get file cb.
     */
    cchFile = 0L;
    cchFileMax = GetFileSize((HANDLE)hfInclude, NULL);
    cbBuf = CCHFILEBUFFER;
    fAtNewLine = TRUE;

    /*
     * Loop through and extract all id definitions.
     */
    while ((RetCode = FindDefine()) != READ_EOF) {
        if (RetCode == READ_BAD || (RetCode = GetLabel(&fDups)) == READ_BAD) {
            Message(MSG_INTERNAL);
            return FALSE;
        }
    }

    /*
     * Warn the user if there were duplicate symbols,
     * or symbols with duplicate ids.
     */
    if (fDups)
        Message(MSG_IDUPIDS);

    return TRUE;
}

/****************************************************************************
* FindDefine
*
* This function looks for ^#define[\s\t], that "#define" at the start
* of a line and followed by a space or a tab.
*
* Returns:
*     READ_OK -> All OK & #define found.
*     READ_EOF -> All OK, but EOF found before #define.
*     READ_BAD = Failure on read.
*
****************************************************************************/

STATICFN INT FindDefine(VOID)
{
    LPTSTR pchIn;
    LPTSTR pchCmp;
    BOOL fLastAtNewLine;

tryagain:

    /*
     * Skip blank lines looking for a newline followed by a '#'.
     */
    while (TRUE) {
        fLastAtNewLine = fAtNewLine;
        pchIn = GetChar();

        if (pchIn == NULL)
            return READ_EOF;
        else if (pchIn == BAD_POINTER)
            return READ_BAD;
        else if (fLastAtNewLine && *pchIn == CHAR_POUND)
            break;
        else if (IsDBCSLeadByte((BYTE)*pchIn))
            pchIn = GetChar();
    }

    /*
     * At this point a newline followed by a '#' has been found.
     * Begin checking for "define".  Save away the file offset,
     * in case we have really found one.
     */
    fposLastDefine = cchFile - 1;
    pchCmp = ids(IDS_DEFINE);
    do {
        pchIn = GetChar();

        if (pchIn == BAD_POINTER)
            return READ_BAD;
        else if (pchIn == NULL || *pchIn != *pchCmp++)
            goto tryagain;
    } while (*pchCmp);

    /*
     * Finally, look for the trailing space or tab after the "#define".
     */
    pchIn = GetChar();
    if (pchIn == BAD_POINTER)
        return READ_BAD;
    else if (pchIn == NULL || (*pchIn != CHAR_SPACE && *pchIn != CHAR_TAB))
        goto tryagain;

    return READ_OK;
}

/************************************************************************
* GetLabel
*
* This function gets the next two words from the file hfInclude and treats
* them as a label and id, respectively.  It allocates another LABEL
* and string to hold this information.
*
* Arguments:
*   BOOL *pfDups = Points to a BOOL that will be set to TRUE if AddLabel
*                  finds a duplicate symbol or id.
*
* Returns:
*     READ_OK -> All OK.
*     READ_BAD = Failure on read.
*
************************************************************************/

STATICFN INT GetLabel(
    BOOL *pfDups)
{
    INT id;
    INT RetCode;
    TCHAR szLabel[CCHTEXTMAX];

    /*
     * Get string and ID at current position
     */
    switch (RetCode = GetWord(szLabel)) {
        case READ_OK:
            if ((RetCode = GetValue(&id)) == READ_OK) {
                AddLabel(szLabel, id, fposLastDefine,
                        (INT)(fposWordStart - fposLastDefine),
                        &plInclude, &plDelInclude, NULL, pfDups);
            }

            break;

        default:
            break;
    }

    return RetCode;
}

/************************************************************************
* GetWord
*
* This function uses GetChar to get the next word from the include
* file.  First it removes tabs and spaces, then it collects everything
* to the next white space.  Finally it null terminates the word.
*
* Arguments:
*     LPTSTR pch  - Where to put the word.
*
* Returns:
*     READ_OK - a word was found.
*     READ_EOF - EOF was found.
*     READ_BAD - Error on Read.
*     READ_WRONG - Found other than ' ' or '\t' followed by a letter,
*                   number or _, +, -.
*
************************************************************************/

STATICFN INT GetWord(
    LPTSTR pch)
{
    TCHAR ch;
    LPTSTR pchIn;

    /*
     * Skip spaces.
     */
    while ((pchIn = GetChar()) != NULL && pchIn != BAD_POINTER &&
                ((ch = *pchIn) == CHAR_SPACE || ch == CHAR_TAB))
        ;

    /*
     * Errors or EOF?
     */
    if (pchIn == NULL)
        return READ_EOF;
    else if (pchIn == BAD_POINTER)
        return READ_BAD;
    if (!iscsym(ch) && ch != CHAR_MINUS && ch != CHAR_PLUS)
        return READ_WRONG;

    /*
     * Save starting location of the word in the file.
     */
    fposWordStart = cchFile - 1;

    /*
     * Pick out the current word.
     */
    do {
        *pch++ = ch;
    } while ((pchIn = GetChar()) != NULL && pchIn != BAD_POINTER &&
            (ch = *pchIn) != CHAR_SPACE && ch != CHAR_TAB &&
            ch != CHAR_NEWLINE && ch != CHAR_RETURN);

    /*
     * Errors or EOF?
     */
    if (pchIn == NULL)
        return READ_WRONG;
    else if (pchIn == BAD_POINTER)
        return READ_BAD;

    /*
     * Null terminate the word.
     */
    *pch = (TCHAR)0;

    return READ_OK;
}

/************************************************************************
* GetChar
*
* This function returns a pointer to the next character in the
* stream hfInclude.  It calls ReadChar to do the actual work.
*
* As it is reading the stream, it will compress a comment sequence to
* a single space.  This means that from a slash+asterisk to the next
* asterisk+slash and from a pair of slashes to the end of the line all
* that will be returned is a single space character.
*
* Returns:
*     A pointer to next character in the stream hfInclude.
*     NULL => End of file.
*     BAD_POINTER => Problems reading file.
*
************************************************************************/

STATICFN LPTSTR GetChar(VOID)
{
    register LPTSTR pch;

    /*
     * Read the next character.
     */
    pch = ReadChar();
    if (pch == NULL || pch == BAD_POINTER)
        return pch;

    /*
     * Possibly starting a comment?
     */
    if (*pch == CHAR_SLASH) {
        /*
         * Starting a traditional comment?
         */
        if (*(pch + 1) == CHAR_ASTERISK) {
            /*
             * Read the '*'.
             */
            pch = ReadChar();
            if (pch == NULL || pch == BAD_POINTER)
                return pch;

            /*
             * Read until the next asterisk+slash is found.
             */
            do {
                pch = ReadChar();
                if (pch == NULL || pch == BAD_POINTER)
                    return pch;
            } while (*pch != CHAR_ASTERISK || *(pch + 1) != CHAR_SLASH);

            /*
             * Read the final '/'.
             */
            pch = ReadChar();
            if (pch == NULL || pch == BAD_POINTER)
                return pch;

            /*
             * Change it to a space.
             */
            *pch = CHAR_SPACE;
        }
        /*
         * Starting a single line comment?
         */
        else if (*(pch + 1) == CHAR_SLASH) {
            /*
             * Read up to the end of line.
             */
            do {
                pch = ReadChar();
                if (pch == NULL || pch == BAD_POINTER)
                    return pch;
            } while (*(pch + 1) != CHAR_RETURN && *(pch + 1) != CHAR_NEWLINE);

            /*
             * Convert the last character before the newline into a space.
             */
            *pch = CHAR_SPACE;
        }
    }

    return pch;
}

/************************************************************************
* ReadChar
*
* This function returns a pointer to the next character in the
* stream hfInclude, but does it in a buffered fashion.  That is, abBuffer
* is filled from hfInclude and pointers are returned to there.
* Note that after ReadChar is called, all previous pointers
* returned are meaningless.
*
* Returns:
*     A pointer to next character in the stream hfInclude.
*     NULL => End of file.
*     BAD_POINTER => Problems reading file.
*
* Comments:
*     May cause abBuffer to be filled from file with handle hfInclude.
*     cbBuf is changed.
*     cchFile is changed.
*     Sets fAtNewLine = TRUE if char returned is '\n' or '\r', or FALSE
*         otherwise.  Not changed unless a character is returned.
*
************************************************************************/

STATICFN LPTSTR ReadChar(VOID)
{
    register LPTSTR pch;
    INT cbRead;

    if (cchFile >= cchFileMax)
        return NULL;

    if (cbBuf >= CCHFILEBUFFER) {
        if ((cbRead = _lread((HFILE)hfInclude, abBuffer, CCHFILEBUFFER)) == -1)
            return BAD_POINTER;

        MultiByteToWideChar(CP_ACP, 0, abBuffer, cbRead, achBuffer,
                CCHFILEBUFFER);

        cbBuf = 0;
    }

    pch = achBuffer + cbBuf;
    cbBuf++;
    cchFile++;

    if (*pch == CHAR_DOSEOF) {
        cchFile = cchFileMax;
        return NULL;
    }

    fAtNewLine = (*pch == CHAR_RETURN || *pch == CHAR_NEWLINE) ? TRUE : FALSE;

    return pch;
}

/************************************************************************
* GetValue
*
* This function reads the next word in the file hfInclude with GetWord
* and converts that word to a number.
*
* If the second character of the word is an 'x' or 'X', the word is
* assumed to be a hex number and it is converted appropriately.
*
* Arguments:
*     npsValue - Where to put the value of the next word in file.
*
* Returns:
*     READ_OK - success.
*     READ_BAD - am error occured.
*     READ_WRONG - Something other than a number was found.
*
************************************************************************/

STATICFN INT GetValue(
    PINT pnValue)
{
    TCHAR achValue[CCHTEXTMAX];
    LPTSTR pch;
    INT RetValue;

    *pnValue = 0;
    if ((RetValue = GetWord(achValue)) != READ_OK)
        return RetValue;

    /*
     * Verify we have only a number.
     */
    pch = achValue;
    if (pch[1] == CHAR_CAP_X || pch[1] == CHAR_X) {
        if (*pch != CHAR_0) {
            RetValue = READ_WRONG;
        }
        else {
            for (pch += 2; *pch; pch++) {
                if (!iswxdigit(*pch)) {
                    RetValue = READ_WRONG;
                    break;
                }
            }

            if (RetValue == READ_OK)
                *pnValue = axtoi(&achValue[2]);
        }
    }
    else {
        if (!iswdigit(*pch) && *pch != CHAR_MINUS && *pch != CHAR_PLUS) {
            RetValue = READ_WRONG;
        }
        else {
            for (pch++; *pch; pch++) {
                if (!iswdigit(*pch)) {
                    RetValue = READ_WRONG;
                    break;
                }
            }

            if (RetValue == READ_OK)
                *pnValue = awtoi(achValue);
        }
    }

    return RetValue;
}

/************************************************************************
* FreeInclude
*
* This function frees the memory associated with an include file,
* sets global variables to match, and closes the currently open
* include file.  It frees plInclude, plDelInclude and all the LABELs in them.
* It sets gfIncChged to FALSE, sets pszIncludeFile to NULL, and
* closes any open include file.
*
************************************************************************/

VOID FreeInclude(VOID)
{
    FreeLabels(&plInclude);
    FreeLabels(&plDelInclude);
    gfIncChged = FALSE;
    pszIncludeFile = NULL;
}

/************************************************************************
* WriteInc
*
* This function writes the labels in plInclude to an include file.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
************************************************************************/

BOOL WriteInc(
    HANDLE hfWrite)
{
    INT nGNIRet;
    NPLABEL plInc;
    BOOL fEOF;

    /*
     * Is there an include file already specified?  If so,
     * open it.  If not, we are effectively at EOF now.
     */
    if (pszIncludeFile) {
        if ((hfInclude = CreateFile(szFullIncludeFile, GENERIC_READ,
                FILE_SHARE_READ, NULL, OPEN_EXISTING,
                FILE_FLAG_SEQUENTIAL_SCAN, NULL)) == (HANDLE)-1) {
            //if the include file is missing or locked...
            return FALSE;
        }

        fEOF = FALSE;
    }
    else {
        fEOF = TRUE;
    }

    cchFile = 0;
    cbWritePos = 0;
    cbBuf = CCHFILEBUFFER;
    fAtNewLine = TRUE;

    /*
     * Loop through all the includes.
     */
    nGNIRet = GetNextInc(&plInc, TRUE);
    while (nGNIRet != GNI_DONE) {
        switch (nGNIRet) {
            case GNI_NOCHANGE:
                break;

            case GNI_CHANGED:
                if (!WriteChangedInc(hfWrite, plInc))
                    return FALSE;

                break;

            case GNI_DELETED:
                if (!WriteDeletedInc(hfWrite, plInc))
                    return FALSE;

                break;

            case GNI_ADDED:
                /*
                 * The first time we reach an added label, we know that
                 * there are no more changed or deleted ones to handle
                 * so we read/write up to the end of the old include file.
                 * This only has to be done once.
                 */
                if (!fEOF) {
                    if (!RWToOffset(hfWrite, FPOS_MAX))
                        return FALSE;

                    fEOF = TRUE;

                    /*
                     * In the unlikely case that the read include file
                     * does not end with a carriage return and/or
                     * linefeed character, add them before beginning
                     * to write added labels.  This ensures that the
                     * first label added always starts on a new line.
                     */
                    if (!fAtNewLine) {
                        if (!WriteIncChar(hfWrite, CHAR_RETURN))
                            return FALSE;

                        if (!WriteIncChar(hfWrite, CHAR_NEWLINE))
                            return FALSE;
                    }
                }

                if (!WriteAddedInc(hfWrite, plInc))
                    return FALSE;

                break;
        }

        nGNIRet = GetNextInc(&plInc, FALSE);
    }

    /*
     * Write the rest of the file, if there is any left.
     */
    if (!fEOF)
        if (!RWToOffset(hfWrite, FPOS_MAX))
            return FALSE;

    /*
     * Flush any remaining characters in the write buffer.
     */
    if (!WriteIncFlush(hfWrite))
        return FALSE;

    /*
     * If we just opened the old include file, close it.
     */
    if (pszIncludeFile)
        CloseHandle(hfInclude);

    return TRUE;
}

/************************************************************************
* GetNextInc
*
* This routine will return the next label in the plInclude and plDelInclude
* linked lists, as well as the status of the returned label.
*
* The labels will be returned in order based upon their location in the
* lists, which is their order found in the include file if their fpos
* field is not FPOS_MAX.  This routine looks at the next label in both the
* plInclude and plDelInclude lists and returns the one with the lowest
* fpos.  Labels are returned from plInclude and plDelInclude in order of
* their fpos until all have been returned with a valid fpos.  After this,
* all new includes are returned from plInclude.
*
* Call it with fFirst equal to TRUE to initialize it.
*
* Arguments:
*   NPLABEL *pplReturn - label to return.
*   BOOL fFirst - TRUE if initializing.
*
* Returns:
*   GNI_DONE     - No more labels exist.
*   GNI_NOCHANGE - An existing label is being returned.
*   GNI_CHANGED  - An existing label with a changed id is being returned.
*   GNI_DELETED  - A deleted label is being returned.
*   GNI_ADDED    - An added label is being returned.
*
************************************************************************/

STATICFN INT GetNextInc(
    NPLABEL *pplReturn,
    BOOL fFirst)
{
    static NPLABEL plCur;
    static NPLABEL plDelCur;

    /*
     * Initialize if this is the first time.
     */
    if (fFirst) {
        plCur = plInclude;
        plDelCur = plDelInclude;
    }

    /*
     * Are we out of valid includes?
     */
    if (!plCur) {
        /*
         * If there are deleted ones left, return the next one.
         * Otherwise we are done.
         */
        if (plDelCur) {
            *pplReturn = plDelCur;
            plDelCur = plDelCur->npNext;
            return GNI_DELETED;
        }
        else {
            return GNI_DONE;
        }
    }
    /*
     * Have we reached the added includes (fpos == FPOS_MAX)?
     */
    else if (plCur->fpos == FPOS_MAX) {
        /*
         * If there are deleted ones remaining, return them first.
         * Otherwise, return the next added one.
         */
        if (plDelCur) {
            *pplReturn = plDelCur;
            plDelCur = plDelCur->npNext;
            return GNI_DELETED;
        }
        else {
            *pplReturn = plCur;
            plCur = plCur->npNext;
            return GNI_ADDED;
        }
    }
    else {
        /*
         * Return either the next label or the next deleted label,
         * based on whether there are any deleted labels and who
         * has the lowest file position (fpos).
         */
        if (plDelCur && plDelCur->fpos < plCur->fpos) {
            *pplReturn = plDelCur;
            plDelCur = plDelCur->npNext;
            return GNI_DELETED;
        }
        else {
            *pplReturn = plCur;
            plCur = plCur->npNext;
            /*
             * Return either GNI_CHANGE or GNI_NOCHANGE based on
             * whether the original id value has been changed.
             */
            return ((*pplReturn)->id == (*pplReturn)->idOrig) ?
                    GNI_NOCHANGE : GNI_CHANGED;
        }
    }
}

/************************************************************************
* RWToOffset
*
* This routine reads from the current include file and writes to the
* hfWrite file up to the lOffset position in the file.  If lOffset is
* set to FPOS_MAX, reads/writes are performed up to the end of the
* read file.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*   DWORD lOffset - where to write up to.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* Comments:
*   This routine relies on cchFile and cchFileMax to be properly updated
*   by the reading and writing routines.
*
************************************************************************/

STATICFN BOOL RWToOffset(
    HANDLE hfWrite,
    DWORD lOffset)
{
    LPTSTR pchIn;
    DWORD cbWrite;

    if (lOffset == FPOS_MAX)
        lOffset = cchFileMax;

    for (cbWrite = lOffset - cchFile; cbWrite; cbWrite--) {
        /*
         * NULL can be returned if there is an EOF character found in
         * the file.  This is not an error, and we will stop reading
         * and writing at this point.
         */
        if ((pchIn = ReadChar()) == NULL)
            return TRUE;

        /*
         * If BAD_POINTER is returned, there was an error reading the
         * include file.
         */
        if (pchIn == BAD_POINTER)
            return FALSE;

        /*
         * Write out the character.
         */
        if (!WriteIncChar(hfWrite, *pchIn))
            return FALSE;
    }

    return TRUE;
}

/************************************************************************
* WriteIncChar
*
* This routine writes a character (ch) to the hfWrite file, doing it in a
* buffered fashion.  Because it is buffered, before closing the file
* any remaining characters in the buffer must be "flushed" to disk.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*   TCHAR ch - character to write.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* Comments:
* The globals gachWriteBuffer and cbWritePos are updated by this routine.
*
************************************************************************/

STATICFN BOOL WriteIncChar(
    HANDLE hfWrite,
    TCHAR ch)
{
    INT cbWritten;

    gachWriteBuffer[cbWritePos++] = ch;

    /*
     * Is the buffer full?
     */
    if (cbWritePos == CCHFILEBUFFER) {
        CHAR abWriteBuffer[CCHFILEBUFFER];
        BOOL fDefCharUsed;

        WideCharToMultiByte(CP_ACP, 0, gachWriteBuffer, CCHFILEBUFFER,
                abWriteBuffer, CCHFILEBUFFER, NULL, &fDefCharUsed);

        cbWritten = (INT)_lwrite((HFILE)hfWrite, abWriteBuffer, cbWritePos);
        if (cbWritten != cbWritePos)
            return FALSE;

        cbWritePos = 0;
    }

    return TRUE;
}

/************************************************************************
* WriteIncFlush
*
* This routine flushes the write buffer.  This must be done before
* the file is closed or data can be lost.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* Comments:
*   The global cbWritePos is updated by this routine.
*
************************************************************************/

STATICFN BOOL WriteIncFlush(
    HANDLE hfWrite)
{
    INT cbWritten;

    /*
     * Are any bytes remaining in the buffer?
     */
    if (cbWritePos) {
        CHAR abWriteBuffer[CCHFILEBUFFER];
        BOOL fDefCharUsed;

        WideCharToMultiByte(CP_ACP, 0, gachWriteBuffer, cbWritePos,
                abWriteBuffer, CCHFILEBUFFER, NULL, &fDefCharUsed);

        cbWritten = (INT)_lwrite((HFILE)hfWrite, abWriteBuffer, cbWritePos);
        if (cbWritten != cbWritePos)
            return FALSE;

        cbWritePos = 0;
    }

    return TRUE;
}

/************************************************************************
* WriteChangedInc
*
* This routine writes out a label that has had its id changed since the
* include file was last read.
*
* Arguments:
*   HANDLE hfWrite - File to write to.
*   NPLABEL plInc  - Label to write.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* History:
*   03/13/90 Byron Dazey - Created.
************************************************************************/

STATICFN BOOL WriteChangedInc(
    HANDLE hfWrite,
    NPLABEL plInc)
{
    TCHAR ch;
    LPTSTR pchIn;

    if (!RWToOffset(hfWrite, plInc->fpos + plInc->nValueOffset))
        return FALSE;

    /*
     * Consume the old id value (up to the next space, tab,
     * beginning of a comment, newline or return).
     */
    while ((pchIn = ReadChar()) != NULL && pchIn != BAD_POINTER &&
            (ch = *pchIn) != CHAR_SPACE && ch != CHAR_TAB &&
            ch != CHAR_SLASH && ch != CHAR_NEWLINE && ch != CHAR_RETURN)
        ;

    /*
     * It is an error if ReadChar returns BAD_POINTER.  Note that it
     * is NOT an error if it reaches EOF (and returns NULL).
     */
    if (pchIn == BAD_POINTER)
        return FALSE;

    /*
     * Write the new one.
     */
    if (!WriteIDInc(hfWrite, plInc->id))
        return FALSE;

    /*
     * Remember to write the last character read after the old value.
     */
    if (pchIn != NULL)
        if (!WriteIncChar(hfWrite, *pchIn))
            return FALSE;

    return TRUE;
}

/************************************************************************
* WriteDeletedInc
*
* This routine deletes a label in the include file, closing up the
* space.  The entire line will be deleted, unless a comment is found
* after the id value.  If so, the comment and following characters will
* be left.
*
* Arguments:
*   HANDLE hfWrite - File to write to.
*   NPLABEL plInc  - Label to delete.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* History:
*   03/13/90 Byron Dazey - Created.
************************************************************************/

STATICFN BOOL WriteDeletedInc(
    HANDLE hfWrite,
    NPLABEL plInc)
{
    register INT i;
    TCHAR ch;
    LPTSTR pchIn;

    /*
     * Read and write up to the #define to be deleted.
     */
    if (!RWToOffset(hfWrite, plInc->fpos))
        return FALSE;

    /*
     * Consume up to the id value.
     */
    for (i = plInc->nValueOffset; i; i--)
        if ((pchIn = ReadChar()) == NULL || pchIn == BAD_POINTER)
            return FALSE;

    /*
     * Consume the id value and following characters up to the end of
     * the line or the beginning of a comment.
     */
    while ((pchIn = ReadChar()) != NULL && pchIn != BAD_POINTER &&
            (ch = *pchIn) != CHAR_NEWLINE && ch != CHAR_RETURN &&
            ch != CHAR_SLASH)
        ;

    if (pchIn == BAD_POINTER)
        return FALSE;

    /*
     * We are done if we have reached EOF.
     */
    if (pchIn == NULL)
        return TRUE;

    /*
     * If the beginning of a comment was found, be sure to write the
     * character back out and leave the rest of the comment.
     */
    if (ch == CHAR_SLASH) {
        if (!WriteIncChar(hfWrite, ch))
            return FALSE;
    }
    else {
        /*
         * At this point either a newline or a return was found
         * and we are going to consume it.  We also want to check
         * for a return following the newline, or a newline
         * following the return and consume it also.
         */
        if ((ch == CHAR_NEWLINE && *(pchIn + 1) == CHAR_RETURN) ||
                (ch == CHAR_RETURN && *(pchIn + 1) == CHAR_NEWLINE))
            if (ReadChar() == BAD_POINTER)
                return FALSE;
    }

    return TRUE;
}

/************************************************************************
* WriteAddedInc
*
* Adds a label to the new include file.
*
* Arguments:
*   HANDLE hfWrite - File to write to.
*   NPLABEL plInc  - Label to add.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
* History:
*   03/13/90 Byron Dazey - Created.
************************************************************************/

STATICFN BOOL WriteAddedInc(
    HANDLE hfWrite,
    NPLABEL plInc)
{
    register LPTSTR psz;

    /*
     * Write the "#define " string.
     */
    psz = ids(IDS_POUNDDEFINE);
    while (*psz)
        if (!WriteIncChar(hfWrite, *psz++))
            return FALSE;

    /*
     * Write the symbol, followed by a space.
     */
    if (!WriteSymbol(hfWrite, plInc->pszLabel))
        return FALSE;
    if (!WriteIncChar(hfWrite, CHAR_SPACE))
        return FALSE;

    /*
     * Write the id, followed by a carriage return and newline.
     */
    if (!WriteIDInc(hfWrite, plInc->id))
        return FALSE;
    if (!WriteIncChar(hfWrite, CHAR_RETURN))
        return FALSE;
    if (!WriteIncChar(hfWrite, CHAR_NEWLINE))
        return FALSE;

    return TRUE;
}

/************************************************************************
* WriteSymbol
*
* Writes out a "#define DID_xxx  " string to hfWrite.  If the symbol
* is smaller than CCHSYMFIELDWIDTH, it will be padded with spaces out
* to this width.
*
* Arguments:
*   HANDLE hfWrite - handle to the file to write to.
*   LPTSTR pszSymbol - symbol to write.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
************************************************************************/

STATICFN BOOL WriteSymbol(
    HANDLE hfWrite,
    LPTSTR pszSymbol)
{
    register INT cch;

    /*
     * Write the symbol.
     */
    cch = 0;
    while (*pszSymbol) {
        if (!WriteIncChar(hfWrite, *pszSymbol++))
            return FALSE;

        cch++;
    }

    /*
     * Pad the field with blanks out to CCHSYMFIELDWIDTH, if necessary.
     */
    if (cch < CCHSYMFIELDWIDTH) {
        cch = CCHSYMFIELDWIDTH - cch;
        while (cch--)
            if (!WriteIncChar(hfWrite, CHAR_SPACE))
                return FALSE;
    }

    return TRUE;
}

/************************************************************************
* WriteIDInc
*
* Writes out an id value to the hfWrite file.  The format will be in
* either hex or decimal, depending on the current mode.
*
* Arguments:
*   HANDLE hfWrite - File to write to.
*   INT id         - ID to write.
*
* Returns:
*   TRUE if successful, FALSE if not.
*
************************************************************************/

STATICFN BOOL WriteIDInc(
    HANDLE hfWrite,
    INT id)
{
    register LPTSTR psz;
    TCHAR szValue[CCHIDMAX + 1];

    Myitoa(id, szValue);

    psz = szValue;
    while (*psz)
        if (!WriteIncChar(hfWrite, *psz++))
            return FALSE;

    return TRUE;
}


RWRES.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 Header *******************************
* Module Name: rwres.c
*
* Does all the reading and writing of the .RES (resource) file.
*
* Functions:
*
*    OpenResFile()
*    WriteRes()
*    LoadResFile()
*    IsValidResFile()
*    SafeParseResHeader()
*    SafeNameOrdLen()
*    SafeDWordAlign()
*    WriteDlgIncludeRes()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

/*
 * The bytes in the special RT_RESOURCE32 type resource that is the
 * first resource in every Win32 format res file.  The first 8 bytes
 * in this resource's header were specially designed to be invalid
 * for a 16 bit format resource file, so that tools can determine
 * immediately if they are reading a 16 bit or a Win32 format res
 * file.
 */
static BYTE abResource32[] = {
    0x00, 0x00, 0x00, 0x00,                 // DataSize (0 bytes).
    0x20, 0x00, 0x00, 0x00,                 // HeaderSize (32 bytes).
    0xff, 0xff, 0x00, 0x00,                 // Type (RT_RESOURCE32).
    0xff, 0xff, 0x00, 0x00,                 // Name (ordinal 0).
    0x00, 0x00, 0x00, 0x00,                 // DataVersion
    0x00, 0x00,                             // MemoryFlags
    0x00, 0x00,                             // LanguageId
    0x00, 0x00, 0x00, 0x00,                 // Version
    0x00, 0x00, 0x00, 0x00                  // Characteristics
};

STATICFN BOOL LoadResFile(HANDLE hfRes, LPTSTR pszFullResFile,
    LPTSTR pszIncludeBuf);
STATICFN BOOL IsValidResFile(PRES pRes, INT cbFileSize);
STATICFN PRES SafeParseResHeader(PRES pRes, INT cbMaxSize);
STATICFN INT SafeNameOrdLen(LPTSTR psz, INT cbMaxLen);
STATICFN VOID SafeDWordAlign(PBYTE *ppb, PINT pcbMax);
STATICFN BOOL WriteDlgIncludeRes(HANDLE hfWrite, LPTSTR pszFullResFile);

/************************************************************************
* OpenResFile
*
* High level function to load the data in a resource file.  The
* function LoadResFile is called to do the actual work, after
* this code does some housekeeping.
*
* Arguments:
*     LPTSTR pszFullPath - The full path to the resource file.
*
* Returns:
*     TRUE if resource file was opened; otherwise, FALSE.
*
************************************************************************/

BOOL OpenResFile(
    LPTSTR pszFullPath)
{
    HCURSOR hcurSave;
    PRESLINK prl;
    PRESLINK prlSave;
    BOOL fSuccess = FALSE;
    INT cDlg;
    HANDLE hfRes;
    TCHAR szInclude[CCHMAXPATH];
    TCHAR szFullInclude[CCHMAXPATH];
    BOOL fIncOpened = FALSE;

    hcurSave = SetCursor(hcurWait);

    /*
     * Close any existing resource and include file and free memory.
     * It is assumed that if either had been changed, the user was asked
     * if they wanted to save them, because it is too late now.
     */
    FreeRes();
    FreeInclude();

    if ((hfRes = CreateFile(pszFullPath, GENERIC_READ,
            FILE_SHARE_READ, NULL, OPEN_EXISTING,
            FILE_FLAG_SEQUENTIAL_SCAN, NULL)) != (HANDLE)-1 &&
            LoadResFile(hfRes, pszFullPath, szInclude)) {
        lstrcpy(szFullResFile, pszFullPath);
        pszResFile = FileInPath(szFullResFile);

        ShowFileStatus(TRUE);

        /*
         * If there was a DLGINCLUDE resource found, try and open the
         * specified include file, after making sure that it is a
         * fully formed path.
         */
        if (*szInclude) {
            /*
             * Does the include filespec from the res file appear to
             * be a simple filename without a path?  If so, look for
             * it in the same directory that the res file is in.
             * Otherwise, assume it has a fully qualified path.
             */
            if (!HasPath(szInclude)) {
                lstrcpy(szFullInclude, szFullResFile);
                lstrcpy(FileInPath(szFullInclude), szInclude);
            }
            else {
                lstrcpy(szFullInclude, szInclude);
            }

            fIncOpened = OpenIncludeFile(szFullInclude);
        }

        /*
         * If there wasn't an include resource found, or there was
         * but it couldn't be opened, we want to ask the user for the
         * include file to use for this resource file.
         */
        if (!fIncOpened)
            Open(FILE_INCLUDE);

        /*
         * Start counting the dialogs in the resource, but stop at two.
         */
        cDlg = 0;
        for (cDlg = 0, prl = gprlHead; prl; prl = prl->prlNext) {
            if (prl->fDlgResource) {
                if (++cDlg > 1)
                    break;

                prlSave = prl;
            }
        }

        /*
         * If there are multiple dialogs, display the "Select Dialog"
         * dialog to ask the user which one they want to edit.  If
         * there is exactly one dialog, just go ahead and show it
         * initially.
         */
        if (cDlg == 1)
            ResLinkToDialog(prlSave);
        else if (cDlg > 1)
            SelectDialogDialog();

        fSuccess = TRUE;
    }

    if (hfRes != (HANDLE)-1)
        CloseHandle(hfRes);

    ShowFileStatus(TRUE);
    SetCursor(hcurSave);

    return fSuccess;
}

/************************************************************************
* LoadResFile
*
* Loads the resource file specified by the passed in file handle.
* This function first verifies that it is a valid resource file.
*
* Arguments:
*   HANDLE hfRes           - File handle to read from.
*   LPTSTR pszFullResFile  - Full name of resource file being loaded.
*   LPTSTR pszIncludeBuf   - Where to return the include file name, if
*                            a DLGINCLUDE resource is found in the res
*                            file.  If not, this buffer gets a null byte
*                            as its first character.
*
* Returns:
*   TRUE if load was successful; otherwise, FALSE is returned.
*
************************************************************************/

STATICFN BOOL LoadResFile(
    HANDLE hfRes,
    LPTSTR pszFullResFile,
    LPTSTR pszIncludeBuf)
{
    HANDLE hAllRes;
    PRES pRes;
    PRES pResAll;
    PRESLINK prl;
    PRESLINK prlT;
    INT cbRead;
    LPTSTR pszResType;
    DWORD cbFileSize;

    cbFileSize = GetFileSize((HANDLE)hfRes, NULL);

    if (!(hAllRes = GlobalAlloc(GMEM_MOVEABLE, cbFileSize))) {
        Message(MSG_OUTOFMEMORY);
        return FALSE;
    }

    *pszIncludeBuf = CHAR_NULL;
    pRes = pResAll = (PRES)GlobalLock(hAllRes);
    if ((cbRead = _lread((HFILE)hfRes, (LPSTR)pResAll, cbFileSize)) != -1 &&
            cbRead == (INT)cbFileSize) {
        if (!IsValidResFile(pResAll, cbFileSize)) {
            Message(MSG_BADRESFILE, pszFullResFile);
        }
        else do {
            pszResType = ResourceType(pRes);

            if (IsOrd(pszResType) && OrdID(pszResType) == ORDID_RT_DLGINCLUDE) {
                /*
                 * Pass back the include file name.  This resource
                 * will not be saved in the res list because it is
                 * going to be explicitly written out later if
                 * necessary.
                 */
                NameOrdCpy(pszIncludeBuf, (LPTSTR)SkipResHeader(pRes));
            }
            else if (IsOrd(pszResType) &&
                    OrdID(pszResType) == ORDID_RT_RESOURCE32) {
                /*
                 * This is the dummy resource that identifies a
                 * 32 bit resource file.  This resource should be
                 * skipped also.
                 */
            }
            else {
                /*
                 * This is some other kind of a resource.
                 * Save it in the resource list.
                 */
                if (!(prlT = AllocResLink(pRes))) {
                    FreeResList();
                    break;
                }

                if (!gprlHead) {
                    gprlHead = prl = prlT;
                }
                else {
                    prl->prlNext = prlT;
                    prl = prlT;
                }
            }

            /*
             * Move to the next resource.
             */
            pRes = (PRES)((PBYTE)pRes + pRes->HeaderSize + pRes->DataSize);
            DWordAlign((PBYTE *)&pRes);
        } while (pRes < (PRES)((PBYTE)pResAll + cbFileSize));
    }

    GlobalUnlock(hAllRes);
    GlobalFree(hAllRes);

    return (gprlHead ? TRUE : FALSE);
}

/************************************************************************
* IsValidResFile
*
* This function does some basic checks on the resource file in memory
* pointed to by pbRes.  It does this by walking through the resource
* checking for the resource header info and lengths.
*
* Arguments:
*   PRES pRes      - Pointer to the first resource in the file.
*   INT cbFileSize - Size of the file in memory.
*
* Returns:
*   TRUE if it is a valid resource file, FALSE if not.
*
************************************************************************/

STATICFN BOOL IsValidResFile(
    PRES pRes,
    INT cbFileSize)
{
    INT cbCurLoc = 0;
    PRES pResT;

    /*
     * The file is zero size.
     */
    if (!cbFileSize)
        return FALSE;

    pResT = pRes;
    while (cbCurLoc < cbFileSize) {
        /*
         * Check this resource for validity.
         */
        if (!(pResT = SafeParseResHeader(pResT, cbFileSize - cbCurLoc)))
            return FALSE;

        /*
         * Point just past the resource that was just checked.
         */
        cbCurLoc = (PBYTE)pResT - (PBYTE)pRes;
    }

    return (cbCurLoc == cbFileSize) ? TRUE : FALSE;
}

/************************************************************************
* SafeParseResHeader
*
* This function parses the specified resource header and returns a
* pointer to the next resource header in the resource file.  It does
* it in a safe manner, not touching memory beyond the maximum size
* specified.  If the resource header is somehow messed up and
* specifies a size that is larger than will fit in the given maximum
* size, NULL is returned.
*
* Arguments:
*   PRES pRes     - Pointer to the resource.
*   INT cbMaxSize - Maximum size the resource can be.
*
* Returns:
*   A pointer to just past this resource, or NULL if the resource
*   is larger than cbMaxSize.
*
************************************************************************/

STATICFN PRES SafeParseResHeader(
    PRES pRes,
    INT cbMaxSize)
{
    INT cbLen;
    DWORD cbDataSize;
    PBYTE pb;

    pb = (PBYTE)pRes;

    /*
     * There must be room for the first part of the resource header.
     */
    if (sizeof(RES) > cbMaxSize)
        return FALSE;

    pb += sizeof(RES);
    cbMaxSize -= sizeof(RES);

    /*
     * Parse the type field then skip over it.
     */
    cbLen = SafeNameOrdLen((LPTSTR)pb, cbMaxSize);
    if (cbLen > cbMaxSize)
        return NULL;

    pb += cbLen;
    cbMaxSize -= cbLen;

    /*
     * Parse the name field then skip over it.
     */
    cbLen = SafeNameOrdLen((LPTSTR)pb, cbMaxSize);
    if (cbLen > cbMaxSize)
        return NULL;

    pb += cbLen;
    cbMaxSize -= cbLen;

    SafeDWordAlign(&pb, &cbMaxSize);

    /*
     * There must be room for the second part of the resource header.
     */
    if (sizeof(RES2) > cbMaxSize)
        return FALSE;

    pb += sizeof(RES2);
    cbMaxSize -= sizeof(RES2);

    /*
     * The header size field must be valid.
     */
    if (pRes->HeaderSize != (DWORD)(pb - (PBYTE)pRes))
        return FALSE;

    /*
     * Calculate the size of the data, taking into account any
     * padding that may be at the end to make it DWORD aligned.
     */
    cbDataSize = pRes->DataSize;
    DWordAlign((PBYTE *)&cbDataSize);

    /*
     * There must be room enough left for the data.
     */
    if (cbDataSize > (DWORD)cbMaxSize)
        return FALSE;

    return (PRES)(pb + cbDataSize);
}

/************************************************************************
* SafeNameOrdLen
*
* This function returns the size of the specified name/ordinal.  It
* does it in a safe manner, not touching memory beyond the specified
* maximum size.  If it is a string and the terminating null is not
* found within cbMaxLen bytes, then cbMaxLen plus one is returned.
*
* Arguments:
*   LPTSTR psz   - Pointer to the name/ordinal.
*   INT cbMaxLen - Maximum length to probe.
*
* Returns:
*   The length of the ordinal if it is an ordinal, or the length
*   of the string (plus the null terminator) if it is a string.
*
************************************************************************/

STATICFN INT SafeNameOrdLen(
    LPTSTR psz,
    INT cbMaxLen)
{
    INT cbLen = 0;

    if (cbMaxLen == 0)
        return 1;

    if (IsOrd(psz))
        return sizeof(ORDINAL);

    for (cbLen = 0; cbLen < cbMaxLen && *psz; psz++, cbLen += sizeof(TCHAR))
        ;

    /*
     * Account for the null terminator.
     */
    cbLen += sizeof(TCHAR);

    return cbLen;
}

/************************************************************************
* SafeDWordAlign
*
* This function aligns the passed pointer to a DWORD boundary.  At the
* same time, it subtracts from the specified counter the amount that
* it had to add to the pointer, if any.
*
* Arguments:
*   PBYTE *ppb  - Points to the pointer to align.
*   PINT pcbMax - Points to the current count to decrement.
*
************************************************************************/

STATICFN VOID SafeDWordAlign(
    PBYTE *ppb,
    PINT pcbMax)
{
    INT cbAlign;

    cbAlign = (4 - (((WORD)(DWORD)*ppb) & 3)) % 4;
    *ppb += cbAlign;
    *pcbMax -= cbAlign;
}

/************************************************************************
* WriteRes
*
* Worker routine that does the actual writing out of the resource data.
*
* Arguments:
*   HANDLE hfWrite         - Resource file to write to.
*   LPTSTR pszFullResFile  - Full pathname to the resource file that
*                            is being written.
*
* Returns:
*   TRUE if successful; otherwise, FALSE.
*
************************************************************************/

BOOL WriteRes(
    HANDLE hfWrite,
    LPTSTR pszFullResFile)
{
    PRESLINK prl;
    PRES pRes;

    /*
     * Write the special RT_RESOURCE32 dummy resource to the beginning
     * of the resource file.  This resource is aligned, so no padding
     * needs to be done before writing the resource that follows it.
     */
    if (_lwrite((HFILE)hfWrite, abResource32, sizeof(abResource32)) == -1)
        return FALSE;

    /*
     * Write out any DLGINCLUDE resource there may be.
     */
    if (!WriteDlgIncludeRes(hfWrite, pszFullResFile))
        return FALSE;

    /*
     * Loop through all the resources.
     */
    for (prl = gprlHead; prl; prl = prl->prlNext) {
        if (!(pRes = (PRES)GlobalLock(prl->hRes)))
            return FALSE;

        /*
         * Write the actual data.
         */
        if (_lwrite((HFILE)hfWrite, (LPSTR)pRes, prl->cbRes) == -1)
            return FALSE;

        /*
         * Write pads out to the next DWORD boundary.
         */
        if (!WriteDWordPad(hfWrite, prl->cbRes))
            return FALSE;

        GlobalUnlock(prl->hRes);
    }

    return TRUE;
}

/************************************************************************
* WriteDlgIncludeRes
*
* Writes out a DLGINCLUDE resource to the specified resource file for
* the currently open include file.
*
* Arguments:
*   HANDLE hfWrite         - Resource file handle to write to.
*   LPTSTR pszFullResFile  - Full pathname to the resource file that
*                            is being written.
*
* Returns:
*   Number of characters written if the include resource was
*   written successfully (or there wasn't one to write) or -1
*   if an error occurred.
*
************************************************************************/

STATICFN BOOL WriteDlgIncludeRes(
    HANDLE hfWrite,
    LPTSTR pszFullResFile)
{
    INT cbResSize;
    INT cbDataSize;
    PRES pResBegin;
    PBYTE pb;
    INT cbWritten;
    LPTSTR pszInc;
    ORDINAL ordDlgIncName;
    BOOL fSuccess = FALSE;

    /*
     * No include file.  Do nothing (return success).
     */
    if (!pszIncludeFile)
        return TRUE;

    /*
     * If the include file is in a different directory than the resource
     * file, write the full path to it.  Otherwise, we just write the
     * include file name.
     */
    if (DifferentDirs(pszFullResFile, szFullIncludeFile))
        pszInc = szFullIncludeFile;
    else
        pszInc = pszIncludeFile;

    /*
     * The DLGINCLUDE resource name always is the same (a value of 1).
     */
    WriteOrd(&ordDlgIncName, ORDID_DLGINCLUDE_NAME);

    /*
     * Determine the size of the resource data.
     */
    cbDataSize = NameOrdLen(pszInc);

    /*
     * Determine the resource size.  Note that there is no need for
     * DWORD padding after the res header, because the header will
     * be aligned (there are no strings in it).
     */
    cbResSize = sizeof(RES) +                       // First part of res header.
            sizeof(ORDINAL) +                       // Type ordinal.
            sizeof(ORDINAL) +                       // Name ordinal.
            sizeof(RES2) +                          // Second half of header.
            cbDataSize;                             // Size of data.

    if (!(pResBegin = (PRES)MyAlloc(cbResSize)))
        return FALSE;

    /*
     * Write the resource header.
     */
    pb = WriteResHeader(pResBegin, cbDataSize, ORDID_RT_DLGINCLUDE,
            (LPTSTR)&ordDlgIncName, MMF_MOVEABLE | MMF_PURE | MMF_DISCARDABLE,
            0, 0, 0, 0);

    /*
     * Write the resource data.  This is simply the name
     * of the include file.
     */
    NameOrdCpy((LPTSTR)pb, pszInc);

    /*
     * Write the resource to the file.
     */
    cbWritten = _lwrite((HFILE)hfWrite, (LPSTR)pResBegin, cbResSize);

    if (cbWritten == cbResSize) {
        /*
         * Write pads out to the next DWORD boundary.
         */
        if (WriteDWordPad(hfWrite, cbWritten))
            fSuccess = TRUE;
    }

    MyFree(pResBegin);

    return fSuccess;
}


SELECT.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 Header *******************************
* Module Name: select.c
*
* Contains routines for selecting and positioning controls.
*
* Functions:
*
*    SelectControl()
*    SelectControl2()
*    RedrawSelection()
*    SetAnchorToFirstSel()
*    SelectNext()
*    SelectPrevious()
*    UnSelectControl()
*    CalcSelectedRect()
*    CancelSelection()
*    OutlineSelectBegin()
*    OutlineSelectDraw()
*    OutlineSelectCancel()
*    OutlineSelectEnd()
*    MyFrameRect()
*    MoveControl()
*    PositionControl()
*    RepositionDialog()
*    SaveDlgClientRect()
*    SizeToText()
*    AlignControls()
*    ArrangeSpacing()
*    ArrangeSize()
*    ArrangePushButtons()
*    InvalidateDlgHandles()
*    OutlineSelectHide()
*    OutlineSelectSetRect()
*    PositionControl2()
*    SizeCtrlToText()
*    QueryTextExtent()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

STATICFN VOID InvalidateDlgHandles(VOID);
STATICFN VOID OutlineSelectHide(VOID);
STATICFN VOID OutlineSelectSetRect(INT x, INT y);
STATICFN HANDLE PositionControl2(NPCTYPE npc, PRECT prc, HANDLE hwpi);
STATICFN BOOL SizeCtrlToText(NPCTYPE npc);
STATICFN INT QueryTextExtent(HWND hwnd, LPTSTR pszText, BOOL fWordBreak);

static POINT gptOutlineSelect;
static RECT grcOutlineSelect;
static RECT grcOutlineSelectLimit;
static BOOL gfOutlineSelectShown = FALSE;

/************************************************************************
* SelectControl
*
* This routine selects a control, showing its drag window and handles.
* If fCheckShift is TRUE and the shift key is down, this routine adds
* the control to the existing selection, unless the control is already
* selected, in which case it is removed from the existing selection.
*
* This routine handles the case where a controls is clicked on to select
* it, and this may cause other controls to be unselected.  If it is
* known for sure that a control should be selected or added to the
* existing selection, SelectControl2 can be used instead.
*
* Arguments:
*   NPCTYPE npc      = The control to select.
*   BOOL fCheckShift = TRUE if the state of the shift key should be
*                      taken into consideration.
*
* Returns:
*   The return will be FALSE if the control was just unselected.
*
************************************************************************/

BOOL SelectControl(
    NPCTYPE npc,
    BOOL fCheckShift)
{
    BOOL fShiftDown;
    BOOL fSelectDone = TRUE;

    if (npc->pwcd->iType == W_DIALOG) {
        if (gnpcSel == npc)
            return TRUE;

        CancelSelection(FALSE);
        SelectControl2(npc, FALSE);
    }
    else {
        if (fCheckShift)
            fShiftDown = (GetKeyState(VK_SHIFT) & 0x8000) ? TRUE : FALSE;
        else
            fShiftDown = FALSE;

        if (npc->fSelected) {
            /*
             * If the shift key is down, and they are NOT trying to
             * select the dialog, toggle the selection of this control
             * to off.
             */
            if (fShiftDown && npc->pwcd->iType != W_DIALOG) {
                UnSelectControl(npc);
                CalcSelectedRect();
                fSelectDone = FALSE;
            }
            else {
                if (gnpcSel == npc)
                    return TRUE;
                else
                    SelectControl2(npc, FALSE);
            }
        }
        else {
            /*
             * If they are NOT holding the shift key down, or the
             * dialog is selected, cancel the selection first.
             */
            if (!fShiftDown || gcd.npc->fSelected == TRUE)
                CancelSelection(FALSE);

            SelectControl2(npc, FALSE);
        }
    }

    StatusUpdate();
    StatusSetEnable();

    return fSelectDone;
}

/************************************************************************
* SelectControl2
*
* This routine is the worker for SelectControl.  It does the actual
* work to "select" a control, updating globals and showing the drag
* windows with handles.
*
* This routine handles the special case where we are selecting a
* control that is already selected.  The editor has the concept of
* a control being selected, as well as there being the currently
* selected control (pointed to by gnpcSel).  There can be the case
* where there are multiple controls selected, but only one will be
* the current selection (usually the last one clicked on).  This
* routine will never unselect other controls.  This must be done
* prior to here, if appropriate.
*
* Arguments:
*   NPCTYPE npc      = The control to make the current selection.
*   BOOL fDontUpdate = TRUE if the selection should NOT be redrawn
*                      after the specified control is added to it.
*                      This allows painting to be deferred until
*                      later if a number of controls are being
*                      selected in a loop.  It also does not call
*                      CalcSelectedRect (this MUST be done later
*                      for drags to work, however!).
* Comments:
*
* If fDontUpdate is TRUE, the selection will not be redrawn, and it
* is required that CalcSelectedRect be called before doing any drag
* operations.
*
************************************************************************/

VOID SelectControl2(
    NPCTYPE npc,
    BOOL fDontUpdate)
{
    BOOL fUpdate = FALSE;

    /*
     * Is the control already selected?
     */
    if (npc->fSelected) {
        /*
         * It is already selected (hwndDrag is visible).  If it is
         * not the current selection, we want all drag windows to
         * be redrawn in the proper order to update their appearance.
         */
        if (gnpcSel != npc)
            fUpdate = TRUE;
    }
    else {
        /*
         * The control is not yet selected.  If another control is
         * currently selected, we want all drag windows to be
         * updated so that their handle appearance gets updated.
         */
        if (gnpcSel)
            fUpdate = TRUE;

        /*
         * Flip its flag and add to the selected count.
         */
        npc->fSelected = TRUE;
        gcSelected++;
    }

    gnpcSel = npc;

    if (!fDontUpdate)
        CalcSelectedRect();

    if (npc->pwcd->iType == W_DIALOG) {
        gfDlgSelected = TRUE;
        InvalidateDlgHandles();
    }
    else {
        gfDlgSelected = FALSE;
        ShowWindow(npc->hwndDrag, SW_SHOW);

        if (fUpdate && !fDontUpdate)
            RedrawSelection();
    }
}

/************************************************************************
* RedrawSelection
*
* This function cause all the selected drag windows to be invalidated.
* This is necessary whenever one of them changes, because of the very
* touchy painting order that has to be maintained.  Without invalidating
* all of them as a unit, there are cases where handles do not get
* properly painted.
*
************************************************************************/

VOID RedrawSelection(VOID)
{
    NPCTYPE npc;

    if (!gcSelected) {
        return;
    }
    else if (gcSelected == 1) {
        InvalidateRect(gfDlgSelected ? gnpcSel->hwnd : gnpcSel->hwndDrag,
                NULL, TRUE);
    }
    else {
        for (npc = npcHead; npc; npc = npc->npcNext) {
            if (npc->fSelected)
                InvalidateRect(npc->hwndDrag, NULL, TRUE);
        }
    }
}

/************************************************************************
* SetAnchorToFirstSel
*
* This function makes the current selection (the anchor) be the
* first selected control.  It is used after making a group selection,
* and ensures that the control that ends up being the anchor is
* consistently the first one in Z-order.
*
* Arguments:
*   BOOL fDontUpdate = TRUE if the selection should NOT be redrawn.
*
************************************************************************/

VOID SetAnchorToFirstSel(
    BOOL fDontUpdate)
{
    NPCTYPE npc;

    if (gcSelected) {
        for (npc = npcHead; npc; npc = npc->npcNext) {
            if (npc->fSelected) {
                SelectControl2(npc, fDontUpdate);
                break;
            }
        }
    }
}

/************************************************************************
* SelectNext
*
* This selects the next control in the dialog box.  The enumeration
* includes the dialog box itself, and wraps around.
*
************************************************************************/

VOID SelectNext(VOID)
{
    NPCTYPE npcSelect;

    /*
     * Disable the tabbing functions if there is no dialog
     * or if the dialog is already selected and there are
     * no controls (the tabs would be a noop in this case).
     */
    if (!gfEditingDlg || (gfDlgSelected && !npcHead))
        return;

    /*
     * Is nothing selected?
     */
    if (!gnpcSel) {
        /*
         * Select the first control, unless there are none, in which
         * case select the dialog.
         */
        if (npcHead)
            npcSelect = npcHead;
        else
            npcSelect = gcd.npc;
    }
    else {
        /*
         * Is the dialog selected?
         */
        if (gfDlgSelected) {
            /*
             * Select the first control, unless there are none, in which
             * case do nothing.
             */
            if (npcHead)
                npcSelect = npcHead;
            else
                npcSelect = NULL;
        }
        else {
            /*
             * Find the current control.  If there is one after it,
             * select it, otherwise wrap around to the dialog and
             * select it.
             */
            if (gnpcSel->npcNext)
                npcSelect = gnpcSel->npcNext;
            else
                npcSelect = gcd.npc;
        }
    }

    if (npcSelect)
        SelectControl(npcSelect, FALSE);
}

/************************************************************************
* SelectPrevious
*
* This selects the previous control in the dialog box.  The enumeration
* includes the dialog box itself, and wraps around.
*
************************************************************************/

VOID SelectPrevious(VOID)
{
    NPCTYPE npc;
    NPCTYPE npcSelect;

    /*
     * Disable the tabbing functions if there is no dialog
     * or if the dialog is already selected and there are
     * no controls (the tabs would be a noop in this case).
     */
    if (!gfEditingDlg || (gfDlgSelected && !npcHead))
        return;

    /*
     * Is nothing selected?
     */
    if (!gnpcSel) {
        /*
         * Select the last control, unless there are none, in which
         * case select the dialog.
         */
        if (npcHead) {
            npc = npcHead;
            while (npc->npcNext)
                npc = npc->npcNext;

            npcSelect = npc;
        }
        else {
            npcSelect = gcd.npc;
        }
    }
    else {
        /*
         * Is the dialog selected?
         */
        if (gfDlgSelected) {
            /*
             * Select the last control, unless there are none, in which
             * case select nothing.
             */
            if (npcHead) {
                npc = npcHead;
                while (npc->npcNext)
                    npc = npc->npcNext;

                npcSelect = npc;
            }
            else {
                npcSelect = NULL;
            }
        }
        else {
            /*
             * If the first control is selected, select the dialog.
             * Otherwise hunt for and select the previous control.
             */
            if (npcHead == gnpcSel) {
                npcSelect = gcd.npc;
            }
            else {
                npc = npcHead;
                while (npc->npcNext != gnpcSel)
                    npc = npc->npcNext;

                npcSelect = npc;
            }
        }
    }

    if (npcSelect)
        SelectControl(npcSelect, FALSE);
}

/************************************************************************
* UnSelectControl
*
* This unselects the specified control, hiding its drag window and handles.
*
* Arguments:
*     NPCTYPE npc = The control to deselect.
*
************************************************************************/

VOID UnSelectControl(
    NPCTYPE npc)
{
    npc->fSelected = FALSE;
    gcSelected--;

    /*
     * We don't have a current selection if there are no selected
     * windows, or if the control we are unselecting was the current
     * selection.
     */
    if (!gcSelected || npc == gnpcSel)
        gnpcSel = NULL;

    if (npc->pwcd->iType == W_DIALOG) {
        gfDlgSelected = FALSE;
        InvalidateDlgHandles();
    }
    else {
        ShowWindow(npc->hwndDrag, SW_HIDE);
    }

    /*
     * Are there still some selected controls, and was the control
     * we just unselected the current selection?  If so, we need
     * to set the current selection to something.
     */
    if (gcSelected && !gnpcSel)
        SetAnchorToFirstSel(FALSE);
}

/************************************************************************
* InvalidateDlgHandles
*
* This function invalidates the handles for the dialog.  This is
* used as an optimization so that the entire dialog does not need
* to be invalidated just to force the handles to be drawn.
*
************************************************************************/

STATICFN VOID InvalidateDlgHandles(VOID)
{
    RECT rc;
    RECT rcClient;
    RECT rcFrame;
    POINT pt;
    INT xOffset;
    INT yOffset;

    /*
     * Redraw the dialog border.
     */
    SetWindowPos(gcd.npc->hwnd, NULL, 0, 0, 0, 0,
            SWP_DRAWFRAME | SWP_NOACTIVATE |
            SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);

    /*
     * Get the frame and client rectangles.
     */
    GetWindowRect(gcd.npc->hwnd, &rcFrame);
    GetClientRect(gcd.npc->hwnd, &rcClient);

    /*
     * Determine the offset from the frame origin to the client
     * origin.
     */
    pt.x = pt.y = 0;
    ClientToScreen(gcd.npc->hwnd, &pt);
    xOffset = rcFrame.left - pt.x;
    yOffset = rcFrame.top - pt.y;

    /*
     * Make the frame rectangle zero based.
     */
    OffsetRect(&rcFrame, -rcFrame.left, -rcFrame.top);

    rc.left = 0;
    rc.top = 0;
    rc.right = CHANDLESIZE;
    rc.bottom = CHANDLESIZE;
    OffsetRect(&rc, xOffset, yOffset);
    InvalidateRect(gcd.npc->hwnd, &rc, TRUE);

    rc.left = ((rcFrame.right + 1) / 2) - (CHANDLESIZE / 2);
    rc.top = 0;
    rc.right = rc.left + CHANDLESIZE;
    rc.bottom = CHANDLESIZE;
    OffsetRect(&rc, xOffset, yOffset);
    InvalidateRect(gcd.npc->hwnd, &rc, TRUE);

    rc.left = rcFrame.right - CHANDLESIZE;
    rc.top = 0;
    rc.right = rcFrame.right;
    rc.bottom = CHANDLESIZE;
    OffsetRect(&rc, xOffset, yOffset);
    InvalidateRect(gcd.npc->hwnd, &rc, TRUE);

    rc.left = rcFrame.right - CHANDLESIZE;
    rc.top = ((rcFrame.bottom + 1) / 2) - (CHANDLESIZE / 2);
    rc.right = rcFrame.right;
    rc.bottom = rc.top + CHANDLESIZE;
    OffsetRect(&rc, xOffset, yOffset);
    InvalidateRect(gcd.npc->hwnd, &rc, TRUE);

    rc.left = rcFrame.right - CHANDLESIZE;
    rc.top = rcFrame.bottom - CHANDLESIZE;
    rc.right = rcFrame.right;
    rc.bottom = rcFrame.bottom;
    OffsetRect(&rc, xOffset, yOffset);
    InvalidateRect(gcd.npc->hwnd, &rc, TRUE);

    rc.left = ((rcFrame.right + 1) / 2) - (CHANDLESIZE / 2);
    rc.top = rcFrame.bottom - CHANDLESIZE;
    rc.right = rc.left + CHANDLESIZE;
    rc.bottom = rcFrame.bottom;
    OffsetRect(&rc, xOffset, yOffset);
    InvalidateRect(gcd.npc->hwnd, &rc, TRUE);

    rc.left = 0;
    rc.top = rcFrame.bottom - CHANDLESIZE;
    rc.right = CHANDLESIZE;
    rc.bottom = rcFrame.bottom;
    OffsetRect(&rc, xOffset, yOffset);
    InvalidateRect(gcd.npc->hwnd, &rc, TRUE);

    rc.left = 0;
    rc.top = ((rcFrame.bottom + 1) / 2) - (CHANDLESIZE / 2);
    rc.right = CHANDLESIZE;
    rc.bottom = rc.top + CHANDLESIZE;
    OffsetRect(&rc, xOffset, yOffset);
    InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
}

/************************************************************************
* CalcSelectedRect
*
* This routine updates the gwrcSelected rectangle.  This is used during
* dragging operations.  It contains the minimum rectangle that spans
* all the selected controls.  If there are no selected controls, the
* contents of this global are not defined.  This routine must be called
* every time that a control is selected of unselected to keep this
* rectangle updated, or tracking will not work properly.
*
************************************************************************/

VOID CalcSelectedRect(VOID)
{
    NPCTYPE npc;
    INT nBottom;
    INT nBottomLowest;

    /*
     * Nothing is selected.  The rectangle values are considered
     * undefined, so we can quit.
     */
    if (!gcSelected)
        return;

    if (gcSelected == 1) {
        /*
         * Only one control is selected.  Set the rectangle to its
         * rectangle.  Note that since the dialog cannot be selected
         * along with other controls, this handles the case where the
         * dialog is selected.
         */
        grcSelected = gnpcSel->rc;
        gnOverHang = GetOverHang(gnpcSel->pwcd->iType,
                gnpcSel->rc.bottom - gnpcSel->rc.top);
    }
    else {
        /*
         * Seed the rectangle with impossible values.
         */
        SetRect(&grcSelected, 32000, 32000, -32000, -32000);
        nBottomLowest = 0;

        /*
         * Loop through all the controls, expanding the rectangle to
         * fit around all the selected controls.
         */
        for (npc = npcHead; npc; npc = npc->npcNext) {
            if (npc->fSelected) {
                if (npc->rc.left < grcSelected.left)
                    grcSelected.left = npc->rc.left;

                if (npc->rc.right > grcSelected.right)
                    grcSelected.right = npc->rc.right;

                if (npc->rc.top < grcSelected.top)
                    grcSelected.top = npc->rc.top;

                nBottom = npc->rc.bottom - GetOverHang(npc->pwcd->iType,
                        npc->rc.bottom - npc->rc.top);
                if (nBottom > nBottomLowest)
                    nBottomLowest = nBottom;

                if (npc->rc.bottom > grcSelected.bottom)
                    grcSelected.bottom = npc->rc.bottom;
            }
        }

        gnOverHang = grcSelected.bottom - nBottomLowest;
    }
}

/************************************************************************
* CancelSelection
*
* This unselects all selected controls.
*
* Arguments:
*   BOOL fUpdate - If TRUE, the status ribbon is updated.
*
************************************************************************/

VOID CancelSelection(
    BOOL fUpdate)
{
    if (gcSelected) {
        while (gcSelected)
            UnSelectControl(gnpcSel);

        if (fUpdate) {
            StatusUpdate();
            StatusSetEnable();
        }
    }
}

/************************************************************************
* OutlineSelectBegin
*
* This function begins an Outline Selection operation.  This will
* draw a tracking rectangle on the screen that can be used to enclose
* controls.  When the selection is completed, all the enclosed controls
* will be selected.
*
* The x and y coordinates are relative to the dialog window, not it's
* client.
*
* Arguments:
*   INT x   - Starting X location (window coords).
*   INT y   - Starting Y location (window coords).
*
************************************************************************/

VOID OutlineSelectBegin(
    INT x,
    INT y)
{
    /*
     * Always be sure the focus is where we want it, not on something
     * like the status ribbon or "Esc" won't work to cancel the tracking.
     */
    SetFocus(ghwndMain);

    /*
     * Remember the starting point.  It comes in coords relative to the
     * window, and the DC we are getting is one for the dialog's client,
     * so we have to map it from window coords to the client's coords.
     */
    gptOutlineSelect.x = x;
    gptOutlineSelect.y = y;
    MapDlgClientPoint(&gptOutlineSelect, FALSE);

    gState = STATE_SELECTING;
    ghwndTrackOver = gcd.npc->hwnd;
    ghDCTrack = GetDC(ghwndTrackOver);
    SetROP2(ghDCTrack, R2_NOT);

    /*
     * Get the rectangle for the client area of the dialog.  This is
     * used to limit the tracking.
     */
    GetClientRect(gcd.npc->hwnd, &grcOutlineSelectLimit);
    OutlineSelectDraw(x, y);

    /*
     * The mouse messages from now on will go to the dialog window,
     * so that the following routines can know that the mouse movement
     * points are relative to that window.
     */
    SetCapture(gcd.npc->hwnd);

    SetCursor(hcurOutSel);
}

/************************************************************************
* OutlineSelectDraw
*
* This routine draws the outline selection rectangle.  It is assumed
* that the window has been locked for update appropriately or this
* could leave garbage around.  The outline selection rectangle is
* drawn from the starting point in gptOutlineSelect to the given
* x,y location.
*
* Arguments:
*   INT x   - Mouse X location (window coords relative to the dialog).
*   INT y   - Mouse Y location (window coords relative to the dialog).
*
************************************************************************/

VOID OutlineSelectDraw(
    INT x,
    INT y)
{
    OutlineSelectHide();
    OutlineSelectSetRect(x, y);
    MyFrameRect(ghDCTrack, &grcOutlineSelect, DSTINVERT);
    gfOutlineSelectShown = TRUE;
}

/************************************************************************
* OutlineSelectHide
*
* This routine hides the current outline selection rectangle.
*
************************************************************************/

STATICFN VOID OutlineSelectHide(VOID)
{
    if (gfOutlineSelectShown) {
        MyFrameRect(ghDCTrack, &grcOutlineSelect, DSTINVERT);
        gfOutlineSelectShown = FALSE;
    }
}

/************************************************************************
* OutlineSelectSetRect
*
* This function takes an x,y point and makes a rectangle that goes
* from this point to the starting outline selection point.  The cases
* are handled where the new point has negative coordinates relative
* to the starting point, and the rectangle produced is limited to
* the rectangle in grcOutlineSelectLimit.
*
* The rectangle is placed into the global grcOutlineSelect.  The
* global point gwptOutlineSelect is assumed to have previously been
* set to the starting point of the outline selection.
*
* The x and y coordinates are relative to the dialog window, not it's
* client.
*
* Arguments:
*   INT x   - Mouse X location (window coords relative to the dialog).
*   INT y   - Mouse Y location (window coords relative to the dialog).
*
************************************************************************/

STATICFN VOID OutlineSelectSetRect(
    INT x,
    INT y)
{
    POINT pt;

    /*
     * The point is coming in relative to the upper left of the
     * dialog window.  It needs to be mapped to points relative
     * to the client of the dialog window.
     */
    pt.x = x;
    pt.y = y;
    MapDlgClientPoint(&pt, FALSE);

    if (pt.x > gptOutlineSelect.x) {
        grcOutlineSelect.left = gptOutlineSelect.x;
        grcOutlineSelect.right = pt.x;
    }
    else {
        grcOutlineSelect.left = pt.x;
        grcOutlineSelect.right = gptOutlineSelect.x;
    }

    if (pt.y > gptOutlineSelect.y) {
        grcOutlineSelect.top = gptOutlineSelect.y;
        grcOutlineSelect.bottom = pt.y;
    }
    else {
        grcOutlineSelect.top = pt.y;
        grcOutlineSelect.bottom = gptOutlineSelect.y;
    }

    if (grcOutlineSelect.left < grcOutlineSelectLimit.left)
        grcOutlineSelect.left = grcOutlineSelectLimit.left;

    if (grcOutlineSelect.right > grcOutlineSelectLimit.right)
        grcOutlineSelect.right = grcOutlineSelectLimit.right;

    if (grcOutlineSelect.top < grcOutlineSelectLimit.top)
        grcOutlineSelect.top = grcOutlineSelectLimit.top;

    if (grcOutlineSelect.bottom > grcOutlineSelectLimit.bottom)
        grcOutlineSelect.bottom = grcOutlineSelectLimit.bottom;
}

/************************************************************************
* OutlineSelectCancel
*
* This routine is used to cancel the display of the outline selection
* rectangle.
*
************************************************************************/

VOID OutlineSelectCancel(VOID)
{
    OutlineSelectHide();
    ReleaseDC(ghwndTrackOver, ghDCTrack);

    gState = STATE_NORMAL;
    ReleaseCapture();
    SetCursor(hcurArrow);
}

/************************************************************************
* OutlineSelectEnd
*
* This function completes an outline selection operation.  All the
* enclosed controls will be selected.  If the Shift key is down,
* the enclosed controls will be added to the selection, otherwise the
* current selection will be cancelled first.
*
* The current selection will only be cancelled if there is
* at least one new control enclosed.  This is so that simply clicking and
* releasing the mouse without enclosing any controls leaves the current
* selection alone.
*
* Arguments:
*   INT x   - Mouse X location (dialog client coords).
*   INT y   - Mouse Y location (dialog client coords).
*
************************************************************************/

VOID OutlineSelectEnd(
    INT x,
    INT y)
{
    NPCTYPE npc;
    BOOL fFirstOne = TRUE;

    OutlineSelectCancel();
    OutlineSelectSetRect(x, y);

    /*
     * If the mouse was not moved at all, consider this a request
     * to select the dialog instead of an outline selection operation.
     */
    if (grcOutlineSelect.left == grcOutlineSelect.right &&
            grcOutlineSelect.top == grcOutlineSelect.bottom) {
        SelectControl(gcd.npc, FALSE);
        return;
    }

    /*
     * Convert the selected rectangle to dialog units.
     */
    WinToDURect(&grcOutlineSelect);

    for (npc = npcHead; npc; npc = npc->npcNext) {
        /*
         * Do the rectangles intersect?
         */
        if (npc->rc.left < grcOutlineSelect.right &&
                grcOutlineSelect.left < npc->rc.right &&
                npc->rc.bottom > grcOutlineSelect.top &&
                grcOutlineSelect.bottom > npc->rc.top) {
            if (fFirstOne) {
                /*
                 * If the Shift key is not held down, cancel any outstanding
                 * selections.
                 */
                if (!(GetKeyState(VK_SHIFT) & 0x8000))
                    CancelSelection(FALSE);

                fFirstOne = FALSE;
            }

            /*
             * If the control is not selected, select it.
             */
            if (!npc->fSelected)
                SelectControl2(npc, TRUE);
        }
    }

    /*
     * Update some things if there was at least one control enclosed.
     */
    if (!fFirstOne) {
        SetAnchorToFirstSel(TRUE);
        StatusUpdate();
        StatusSetEnable();
        RedrawSelection();
        CalcSelectedRect();
    }
}

/************************************************************************
* MyFrameRect
*
* This function draws a one-pixel width rectangle using the given
* raster operation.
*
* Arguments:
*   HDC hDC     - DC to use.
*   PRECT prc   - Rectangle to draw the frame around.

*   DWORD dwRop - RasterOp to use (DSTINVERT, BLACKNESS, etc.).
*
************************************************************************/

VOID MyFrameRect(
    HDC hDC,
    PRECT prc,
    DWORD dwRop)
{
    INT x;
    INT y;
    POINT pt;

    x = prc->right  - (pt.x = prc->left);
    y = prc->bottom - (pt.y = prc->top);

    PatBlt(hDC, pt.x, pt.y, x, 1, dwRop);
    pt.y = prc->bottom - 1;
    PatBlt(hDC, pt.x, pt.y, x, 1, dwRop);
    pt.y = prc->top;
    PatBlt(hDC, pt.x, pt.y, 1, y, dwRop);
    pt.x = prc->right - 1;
    PatBlt(hDC, pt.x, pt.y, 1, y, dwRop);
}

/************************************************************************
* MoveControl
*
* This function moves the current control to the next grid boundary in
* the specified direction.
*
* Arguments:
*   WPARAM vKey - Virtual key code that was pressed.
*
************************************************************************/

VOID MoveControl(
    WPARAM vKey)
{
    RECT rc;
    INT dx;
    INT dy;

    /*
     * Nothing is selected.
     */
    if (!gcSelected)
        return;

    rc = grcSelected;

    switch (vKey) {
        case VK_UP:
            dx = 0;
            if (!(dy = -(rc.top % gcyGrid)))
                dy = -gcyGrid;
            break;

        case VK_DOWN:
            dx = 0;
            dy = gcyGrid - (rc.top % gcyGrid);
            break;

        case VK_RIGHT:
            dx = gcxGrid - (rc.left % gcxGrid);
            dy = 0;
            break;

        case VK_LEFT:
            if (!(dx = -(rc.left % gcxGrid)))
                dx = -gcxGrid;
            dy = 0;
            break;
    }

    OffsetRect(&rc, dx, dy);
    FitRectToBounds(&rc, gnOverHang, DRAG_CENTER, gfDlgSelected);
    gHandleHit = DRAG_CENTER;
    PositionControl(&rc);
}

/************************************************************************
* PositionControl
*
* This function positions and sizes the current control.  Both the control
* window and its associated drag window are moved at the same time.  The
* coordinates in the control, and the status window display are updated.
* The given rectangle is in dialog units, and it should have already been
* range limited and grid aligned as appropriate.
*
* Arguments:
*   NPWRECT nprc - The rectangle to size/position the control with.
*
************************************************************************/

VOID PositionControl(
    PRECT prc)
{
    INT cx;
    INT cy;
    RECT rcT;
    NPCTYPE npcT;
    HANDLE hwpi;

    if (gcSelected == 1) {
        /*
         * Did nothing change?
         */
        if (EqualRect(prc, &gnpcSel->rc))
            return;

        /*
         * Only a single control is selected.  Move it.
         */
        PositionControl2(gnpcSel, prc, NULL);

        /*
         * Is the dialog selected, being sized (not just moved), and
         * does it have at least one control?
         */
        if (gfDlgSelected && gHandleHit != DRAG_CENTER && npcHead) {
            cx = prc->left - grcSelected.left;
            cy = prc->top - grcSelected.top;

            /*
             * Did the top or left edge of the dialog move?
             */
            if (cx || cy) {
                /*
                 * Loop through all the controls.  Move all of them by
                 * the dialog movement delta.
                 */
                hwpi = BeginDeferWindowPos(cWindows * 2);
                for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
                    SetRect(&rcT, npcT->rc.left - cx, npcT->rc.top - cy,
                            npcT->rc.right - cx, npcT->rc.bottom - cy);
                    hwpi = PositionControl2(npcT, &rcT, hwpi);
                }
                EndDeferWindowPos(hwpi);
            }
        }
    }
    else {
        /*
         * Did nothing change?
         */
        if (EqualRect(prc, &grcSelected))
            return;

        /*
         * There is a group of controls selected.
         * Calculate how much the group rectangle was moved.
         * It is assumed that a group of controls cannot be
         * sized, only moved.
         */
        cx = prc->left - grcSelected.left;
        cy = prc->top - grcSelected.top;

        /*
         * Loop through all the controls.  Move all the selected
         * ones by the group delta.
         */
        hwpi = BeginDeferWindowPos(gcSelected * 2);
        for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
            if (npcT->fSelected) {
                SetRect(&rcT,
                        npcT->rc.left + cx, npcT->rc.top + cy,
                        npcT->rc.right + cx, npcT->rc.bottom + cy);
                GridizeRect(&rcT,
                        GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
                FitRectToBounds(&rcT,
                        GetOverHang(npcT->pwcd->iType,
                        npcT->rc.bottom - npcT->rc.top),
                        DRAG_CENTER, gfDlgSelected);
                hwpi = PositionControl2(npcT, &rcT, hwpi);
            }
        }
        EndDeferWindowPos(hwpi);
    }

    CalcSelectedRect();
    StatusSetCoords(&gnpcSel->rc);
    gfResChged = gfDlgChanged = TRUE;
    ShowFileStatus(FALSE);
}

/************************************************************************
* PositionControl2
*
* This function positions and sizes a single control.  Both the control
* window and its associated drag window are moved at the same time.  The
* coordinates in the control are updated.
*
* The given rectangle is in dialog units, and it should have already been
* range limited and grid aligned as appropriate.
*
* Arguments:
*   NPCTYPE npc - The control to position.
*   PRECT prc   - The rectangle to size/position the control with.
*   HANDLE hwpi - Handle that has been returned from a BeginDeferWindowPos
*                 call.  If this parameter is not NULL, the calls to
*                 position the control and drag window will use this
*                 handle.
*
* Returns:
*
* The return will be the hwpi handle that is currently being used.
* The variable that the caller is using must be updated with the
* return value each call, because each call to DeferWindowPos can
* possibly change this value.
*
************************************************************************/

STATICFN HANDLE PositionControl2(
    NPCTYPE npc,
    PRECT prc,
    HANDLE hwpi)
{
    RECT rc;
    RECT rcDrag;
    HANDLE hwpi2;

    /*
     * Make a local copy of the rectangle.
     */
    rc = *prc;

    /*
     * Start calculating the new position.  Begin by mapping the dialog
     * points to window coordinates.
     */
    DUToWinRect(&rc);

    if (npc->pwcd->iType == W_DIALOG) {
        InvalidateDlgHandles();

        AdjustWindowRectEx(&rc, npc->flStyle, FALSE,
                (npc->flStyle & DS_MODALFRAME) ?
                npc->flExtStyle | WS_EX_DLGMODALFRAME : npc->flExtStyle);
        ClientToScreenRect(ghwndSubClient, &rc);
        MoveWindow(npc->hwnd, rc.left, rc.top,
                rc.right - rc.left, rc.bottom - rc.top, TRUE);

        /*
         * Update the control structure with the new rectangle.
         */
        npc->rc = *prc;

        /*
         * Since this was the dialog that was just positioned, we need to
         * recalculate and save the size of its "client" area.
         */
        SaveDlgClientRect(npc->hwnd);
    }
    else {
        rcDrag = rc;
        InflateRect(&rcDrag, CHANDLESIZE / 2, CHANDLESIZE / 2);

        if (hwpi)
            hwpi2 = hwpi;
        else
            hwpi2 = BeginDeferWindowPos(2);

        hwpi2 = DeferWindowPos(hwpi2, npc->hwndDrag, NULL,
                rcDrag.left, rcDrag.top,
                rcDrag.right - rcDrag.left, rcDrag.bottom - rcDrag.top,
                SWP_NOACTIVATE | SWP_NOZORDER);

        hwpi2 = DeferWindowPos(hwpi2, npc->hwnd, NULL, rc.left, rc.top,
                rc.right - rc.left, rc.bottom - rc.top,
                SWP_NOACTIVATE | SWP_NOZORDER);

        if (!hwpi)
            EndDeferWindowPos(hwpi2);

        /*
         * Update the control structure with the new rectangle.
         */
        npc->rc = *prc;

        InvalidateRect(npc->hwndDrag, NULL, TRUE);
    }

    return hwpi2;
}

/************************************************************************
* RepositionDialog
*
* This routine forces the dialog to be moved to the location that
* is stored in it's npc rectangle.  This is necessary after the
* main application has been moved, because the dialog is relative
* to the app's window and does not automatically move with it.
*
************************************************************************/

VOID RepositionDialog(VOID)
{
    PositionControl2(gcd.npc, &gcd.npc->rc, NULL);
}

/************************************************************************
* SaveDlgClientRect
*
* This routine saves away a global that will contain the offset, in window
* coordinates, of the origin of the "client" area for the current dialog.
*
* Arguments:
*   HWND hwndDlg - The dialog window.
*
************************************************************************/

VOID SaveDlgClientRect(
    HWND hwndDlg)
{
    RECT rcFrame;
    POINT pt;

    GetWindowRect(hwndDlg, &rcFrame);
    GetClientRect(hwndDlg, &grcDlgClient);
    pt.x = pt.y = 0;
    ClientToScreen(hwndDlg, &pt);
    OffsetRect(&grcDlgClient, pt.x - rcFrame.left, pt.y - rcFrame.top);
}

/************************************************************************
* SizeToText
*
* This function will size all the selected controls to fit their text.
* This is to support the "Size to text" command of the "Edit" menu.
*
* Globals are updated appropriately if anything actually had to change.
*
************************************************************************/

VOID SizeToText(VOID)
{
    NPCTYPE npc;
    BOOL fSized = FALSE;

    /*
     * Loop through all the controls.
     */
    for (npc = npcHead; npc; npc = npc->npcNext) {
        /*
         * Is the control selected, and can it be sized to its text?
         */
        if (npc->fSelected && npc->pwcd->fSizeToText)
            fSized |= SizeCtrlToText(npc);
    }

    /*
     * Was anything modified?
     */
    if (fSized) {
        CalcSelectedRect();
        StatusSetCoords(&gnpcSel->rc);
        gfResChged = gfDlgChanged = TRUE;
        ShowFileStatus(FALSE);
    }
}

/************************************************************************
* SizeCtrlToText
*
* This function sizes a single control so that it just fits its text.
* This does not make sense for all controls (see the fSizeToText flag
* in the awcd array), and there are different rules for the different
* types of controls.
*
* Arguments:
*   NPCTYPE npc - The control to size.
*
* Returns:
*
* The return value is TRUE if the control was modified (sized) or
* FALSE if it was not.
*
************************************************************************/

STATICFN BOOL SizeCtrlToText(
    NPCTYPE npc)
{
    RECT rc;
    INT x;
    INT cxLowern;

    switch (npc->pwcd->iType) {
        case W_CHECKBOX:
            /*
             * Take the width of the text, plus some pixels for the square.
             */
            x = QueryTextExtent(npc->hwnd, npc->text, TRUE) + 20;
            x = MulDiv(x, 4, gcd.cxChar) + 1;
            break;

        case W_PUSHBUTTON:
            /*
             * The UITF definition of the size of a pushbutton says
             * that the left and right margins should be approximately
             * the width of a lowercase "n".  In any event, the width
             * cannot be less than the default size.
             */
            cxLowern = QueryTextExtent(npc->hwnd, L"n", FALSE);
            x = QueryTextExtent(npc->hwnd, npc->text, FALSE) + (2 * cxLowern);
            x = MulDiv(x, 4, gcd.cxChar);

            if (x < awcd[W_PUSHBUTTON].cxDefault)
                x = awcd[W_PUSHBUTTON].cxDefault;

            break;

        case W_RADIOBUTTON:
            /*
             * Take the width of the text, plus some for the circle.
             */
            x = QueryTextExtent(npc->hwnd, npc->text, TRUE) + 20;
            x = MulDiv(x, 4, gcd.cxChar) + 1;
            break;

        case W_TEXT:
            /*
             * Take the width of the text.
             */
            x = QueryTextExtent(npc->hwnd, npc->text, TRUE);
            x = MulDiv(x, 4, gcd.cxChar) + 1;
            break;

        case W_CUSTOM:
            /*
             * Call out to the custom control and let it decide
             * how wide the text should be.
             */
            x = CallCustomSizeToText(npc);
            break;

        default:
            x = -1;
            break;
    }

    /*
     * Does it need to be sized?
     */
    if (x != -1 && x != npc->rc.right - npc->rc.left) {
        /*
         * Now that we know the size we want the control, position
         * it to change that size.  Note that we do NOT gridize
         * the left edge here.  The user probably just wants the
         * right edge of the control to be adjusted to fit the new
         * text, and probably does not want the left edge shifting
         * on them.
         */
        rc = npc->rc;
        rc.right = rc.left + x;
        FitRectToBounds(&rc,
                GetOverHang(npc->pwcd->iType, npc->rc.bottom - npc->rc.top),
                DRAG_CENTER, gfDlgSelected);
        PositionControl2(npc, &rc, NULL);

        return TRUE;
    }

    return FALSE;
}

/************************************************************************
* QueryTextExtent
*
* This function takes a window handle and text, and will return the
* number of pixels that the specified text is wide in that window.
* It is used to determine how wide a control needs to be to display
* its text.
*
* The font set into the current dialog is taken into consideration
* when calculating the size.
*
* Arguments:
*   HWND hwnd       - The control window handle.
*   LPTSTR pszText  - The text of the control.
*   BOOL fWordBreak - TRUE if this text will be drawn with DT_WORDBREAK.
*
* Returns:
*
* The number of pixels wide the selected text is.
*
************************************************************************/

STATICFN INT QueryTextExtent(
    HWND hwnd,
    LPTSTR pszText,
    BOOL fWordBreak)
{
    HDC hDC;
    INT iHeight;
    RECT rc;
    INT nLen;
    HFONT hfontOld;

    if (!pszText || *pszText == CHAR_NULL)
        return 0;

    hDC = GetDC(hwnd);

    /*
     * If there is a valid font, select it into the DC.  Note that
     * we look at gcd.hFont instead of gcd.fFontSpecified, because
     * it is possible to specify a font for the dialog but not have
     * been able to create it.
     */
    if (gcd.hFont)
        hfontOld = SelectObject(hDC, gcd.hFont);

    /*
     * First, calculate the length of the text.
     */
    rc.left = rc.top = 0;
    rc.right = 10000;
    rc.bottom = 10000;
    nLen = lstrlen(pszText);
    DrawText(hDC, pszText, nLen, &rc,
            DT_CALCRECT | DT_NOCLIP | DT_EXPANDTABS);

    /*
     * First save the height of the line.  This works because the
     * DrawText call above with DT_CALCRECT will always draw on
     * a single line.  Then we move the upwards the rectangle to draw
     * in a large amount, so that it is outside the dimensions of
     * the control.  Finally, we do a real draw of the text,
     * increasing the width a little each time until we are able
     * to draw entirely on one line.  This is inefficient, but it does
     * ensure that the returned width will be enough to actually
     * draw the string.
     */
    if (fWordBreak) {
        iHeight = rc.bottom - rc.top;
        rc.top -= 10000;
        rc.bottom -= 10000;
        while (TRUE) {
            /*
             * Determine if we have enough width to draw on a single
             * line yet.
             */
            if (DrawText(hDC, pszText, nLen, &rc,
                    DT_NOCLIP | DT_EXPANDTABS | DT_WORDBREAK) == iHeight)
                break;

            /*
             * Nope, push the right margin out and try again...
             */
            rc.right++;
        }
    }

    if (gcd.hFont)
        SelectObject(hDC, hfontOld);

    ReleaseDC(hwnd, hDC);

    return rc.right - rc.left;
}

/************************************************************************
* AlignControls
*
* This function will align all the selected controls.  The point to
* align to is always taken from the currently selected control.
*
* In all cases, the resulting desired position of the control will be
* gridized and limited to the dialogs "client" area.  The size of the
* controls will not be changed.
*
* Arguments:
*   INT cmd - The alignment menu command.
*
*   The following are valid values for cmd:
*
*   MENU_ALIGNLEFT      - Align to the left edge.
*   MENU_ALIGNVERT      - Align to the center vertically.
*   MENU_ALIGNRIGHT     - Align to the right edge.
*   MENU_ALIGNTOP       - Align to the top edge.
*   MENU_ALIGNHORZ      - Align to the center horizontally.
*   MENU_ALIGNBOTTOM    - Align to the bottom edge.
*
************************************************************************/

VOID AlignControls(
    INT cmd)
{
    register INT sDelta;
    NPCTYPE npc;
    RECT rc;
    BOOL fMove;
    BOOL fModified = FALSE;

    /*
     * Loop through all the controls.  Align all the selected ones.
     */
    for (npc = npcHead; npc; npc = npc->npcNext) {
        if (npc->fSelected && npc != gnpcSel) {
            rc = npc->rc;
            fMove = FALSE;

            switch (cmd) {
                case MENU_ALIGNLEFT:
                    if (sDelta = gnpcSel->rc.left - rc.left) {
                        fMove = TRUE;
                        rc.left += sDelta;
                        rc.right += sDelta;
                    }

                    break;

                case MENU_ALIGNVERT:
                    if (sDelta =
                            (((gnpcSel->rc.right - gnpcSel->rc.left) / 2)
                            + gnpcSel->rc.left) -
                            (((rc.right - rc.left) / 2) +
                            rc.left)) {
                        fMove = TRUE;
                        rc.left += sDelta;
                        rc.right += sDelta;
                    }

                    break;

                case MENU_ALIGNRIGHT:
                    if (sDelta = gnpcSel->rc.right - rc.right) {
                        fMove = TRUE;
                        rc.left += sDelta;
                        rc.right += sDelta;
                    }

                    break;

                case MENU_ALIGNTOP:
                    if (sDelta = gnpcSel->rc.top - rc.top) {
                        fMove = TRUE;
                        rc.top += sDelta;
                        rc.bottom += sDelta;
                    }

                    break;

                case MENU_ALIGNHORZ:
                    if (sDelta =
                            (((gnpcSel->rc.bottom - gnpcSel->rc.top) / 2)
                            + gnpcSel->rc.top) -
                            (((rc.bottom - rc.top) / 2) +
                            rc.top)) {
                        fMove = TRUE;
                        rc.top += sDelta;
                        rc.bottom += sDelta;
                    }

                    break;

                case MENU_ALIGNBOTTOM:
                    if (sDelta = gnpcSel->rc.bottom - rc.bottom) {
                        fMove = TRUE;
                        rc.top += sDelta;
                        rc.bottom += sDelta;
                    }

                    break;
            }

            if (fMove) {
                GridizeRect(&rc,
                        GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
                FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
                        npc->rc.bottom - npc->rc.top),
                        DRAG_CENTER, FALSE);

                if (!EqualRect(&rc, &npc->rc)) {
                    PositionControl2(npc, &rc, NULL);
                    fModified = TRUE;
                }
            }
        }
    }

    if (fModified) {
        RedrawSelection();
        CalcSelectedRect();
        gfResChged = gfDlgChanged = TRUE;
        ShowFileStatus(FALSE);
        StatusUpdate();
    }
}

/************************************************************************
* ArrangeSpacing
*
* This function will evenly space all the selected controls.  The
* currently selected control is not moved (unless it has to be gridized)
* and any previous controls in Z order will be evenly spaced to the
* left or above the anchor, and all controls following in Z order will
* be evenly spaced below or to the right of the anchor.
*
* The spacing values used are gxSpace and gySpace.
*
* In all cases, the resulting desired position of the control will be
* gridized and limited to the dialogs "client" area.  The size of the
* controls is not changed.
*
* Arguments:
*   INT cmd - The Arrange/Even spacing menu command.
*
*   The following are valid values for cmd:
*
*   MENU_SPACEHORZ - Space the controls left and right.
*   MENU_SPACEVERT - Space all the controls up and down.
*
************************************************************************/

VOID ArrangeSpacing(
    INT cmd)
{
    NPCTYPE npc;
    RECT rc;
    BOOL fModified = FALSE;
    INT x;
    INT y;
    INT cPreceding;
    INT xTotalWidth;
    INT yTotalWidth;

    cPreceding = 0;
    xTotalWidth = 0;
    yTotalWidth = 0;
    for (npc = npcHead; npc; npc = npc->npcNext) {
        if (npc->fSelected) {
            if (npc == gnpcSel)
                break;

            cPreceding++;
            xTotalWidth += npc->rc.right - npc->rc.left;
            yTotalWidth += npc->rc.bottom - npc->rc.top;
        }
    }

    x = gnpcSel->rc.left;
    y = gnpcSel->rc.top;

    if (cPreceding) {
        x -= xTotalWidth + (gxSpace * cPreceding);
        y -= yTotalWidth + (gySpace * cPreceding);
    }

    /*
     * Loop through all the controls.  Space all the selected ones.
     */
    for (npc = npcHead; npc; npc = npc->npcNext) {
        if (npc->fSelected) {
            rc = npc->rc;

            switch (cmd) {
                case MENU_SPACEVERT:
                    rc.top = y;
                    rc.bottom = y + (npc->rc.bottom - npc->rc.top);
                    y = rc.bottom + gySpace;
                    break;

                case MENU_SPACEHORZ:
                    rc.left = x;
                    rc.right = x + (npc->rc.right - npc->rc.left);
                    x = rc.right + gxSpace;
                    break;
            }

            GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
            FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
                    npc->rc.bottom - npc->rc.top),
                    DRAG_CENTER, FALSE);

            if (!EqualRect(&rc, &npc->rc)) {
                PositionControl2(npc, &rc, NULL);
                fModified = TRUE;
            }
        }
    }

    if (fModified) {
        RedrawSelection();
        CalcSelectedRect();
        gfResChged = gfDlgChanged = TRUE;
        ShowFileStatus(FALSE);
        StatusUpdate();
    }
}

/************************************************************************
* ArrangeSize
*
* This function will evenly size all the selected controls.  The
* currently selected control determines the size that the other
* controls will be set to in the given dimension.
*
* In all cases, the resulting size of the control will be gridized and
* limited to the dialogs "client" area.
*
* Arguments:
*   INT cmd - The Arrange/Same size menu command.
*
* The following are valid values for cmd:
*
*   MENU_ARRSIZEWIDTH  - Size the widths of the controls.
*   MENU_ARRSIZEHEIGHT - Size the heights of the controls.
*
************************************************************************/

VOID ArrangeSize(
    INT cmd)
{
    NPCTYPE npc;
    RECT rc;
    BOOL fModified = FALSE;
    INT cx;
    INT cy;

    cx = gnpcSel->rc.right - gnpcSel->rc.left;
    cy = gnpcSel->rc.bottom - gnpcSel->rc.top;

    /*
     * Loop through all the controls, operating on the selected ones.
     */
    for (npc = npcHead; npc; npc = npc->npcNext) {
        /*
         * Is the control selected, and is it sizeable?
         */
        if (npc->fSelected && npc->pwcd->fSizeable) {
            rc = npc->rc;

            switch (cmd) {
                case MENU_ARRSIZEWIDTH:
                    rc.right = npc->rc.left + cx;
                    break;

                case MENU_ARRSIZEHEIGHT:
                    rc.top = npc->rc.bottom - cy;
                    break;
            }

            GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP |
                    GRIDIZE_RIGHT | GRIDIZE_BOTTOM);
            FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
                    npc->rc.bottom - npc->rc.top),
                    DRAG_CENTER, FALSE);

            if (!EqualRect(&rc, &npc->rc)) {
                PositionControl2(npc, &rc, NULL);
                fModified = TRUE;
            }
        }
    }

    if (fModified) {
        RedrawSelection();
        CalcSelectedRect();
        gfResChged = gfDlgChanged = TRUE;
        ShowFileStatus(FALSE);
        StatusUpdate();
    }
}

/************************************************************************
* ArrangePushButtons
*
* This function will arrange push buttons along either the bottom of
* the dialog or along the right side of the dialog.  It will operate
* on the selected buttons (which button is currently selected does not
* matter) but this function can also be used if buttons are not selected
* in a couple of special cases.  If either the dialog or nothing is
* selected, ALL the push buttons in the dialog will be arranged.
*
* The margin values used are gxMargin and gyMargin, and the spacing
* between buttons is gxMinPushSpace, gxMaxPushSpace and gyPushSpace.
*
* In all cases, the resulting desired position of the buttons will be
* gridized and limited to the dialogs "client" area.  The size of the
* push buttons is not changed.
*
* Arguments:
*   INT cmd - The Arrange/Push buttons menu command.
*
*   The following are valid values for cmd:
*
*   MENU_ARRPUSHBOTTOM - Arrange push buttons along the bottom.
*   MENU_ARRPUSHRIGHT  - Arrange push buttons along the right side.
*
************************************************************************/

VOID ArrangePushButtons(
    INT cmd)
{
    NPCTYPE npc;
    RECT rc;
    BOOL fModified = FALSE;
    INT x;                          // Note: These values must be signed.
    INT y;
    INT cxDlg;
    INT cButtons;
    INT xTotal;
    INT xInterSpace;

    switch (cmd) {
        case MENU_ARRPUSHBOTTOM:
            cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
            y = (gcd.npc->rc.bottom - gcd.npc->rc.top) - gyMargin;

            for (cButtons = 0, xTotal = 0, npc = npcHead; npc;
                    npc = npc->npcNext) {
                if (npc->pwcd->iType == W_PUSHBUTTON &&
                        (!gcSelected || gfDlgSelected || npc->fSelected)) {
                    cButtons++;
                    xTotal += npc->rc.right - npc->rc.left;
                }
            }

            if (cButtons == 1) {
                x = (cxDlg - xTotal) / 2;
                xInterSpace = 0;
            }
            else {
                xInterSpace = (cxDlg - xTotal) / (cButtons + 1);

                if (xInterSpace > gxMaxPushSpace)
                    xInterSpace = gxMaxPushSpace;
                else if (xInterSpace < gxMinPushSpace)
                    xInterSpace = gxMinPushSpace;

                x = (cxDlg - ((cButtons - 1) * xInterSpace) - xTotal) / 2;
                if (x < 0)
                    x = 0;

                if (x < gxMargin && xInterSpace > gxMinPushSpace) {
                    xInterSpace = (cxDlg - xTotal - (2 * gxMargin))
                            / (cButtons - 1);

                    if (xInterSpace < gxMinPushSpace)
                        xInterSpace = gxMinPushSpace;

                    x = (cxDlg - ((cButtons - 1) * xInterSpace) - xTotal)
                            / 2;
                    if (x < 0)
                        x = 0;
                }
            }

            break;

        case MENU_ARRPUSHRIGHT:
            x = (gcd.npc->rc.right - gcd.npc->rc.left) - gxMargin;
            y = gyMargin;
            break;
    }

    /*
     * Loop through all the controls.
     */
    for (npc = npcHead; npc; npc = npc->npcNext) {
        /*
         * We will arrange this control only if it is a pushbutton,
         * and only if one of the following is true: there are no
         * controls selected, or the dialog itself is selected, or
         * there are some controls selected and this pushbutton is
         * one of them.
         */
        if (npc->pwcd->iType == W_PUSHBUTTON &&
                (!gcSelected || gfDlgSelected || npc->fSelected)) {
            rc = npc->rc;

            switch (cmd) {
                case MENU_ARRPUSHBOTTOM:
                    rc.left = x;
                    rc.top = y - (npc->rc.bottom - npc->rc.top);
                    rc.bottom = y;
                    rc.right = rc.left + (npc->rc.right - npc->rc.left);

                    x = rc.right + xInterSpace;

                    break;

                case MENU_ARRPUSHRIGHT:
                    rc.left = x - (npc->rc.right - npc->rc.left);
                    rc.bottom = y + (npc->rc.bottom - npc->rc.top);
                    rc.right = x;
                    rc.top = y;

                    y = rc.bottom + gyPushSpace;

                    break;
            }

            GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
            FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
                    npc->rc.bottom - npc->rc.top),
                    DRAG_CENTER, FALSE);

            if (!EqualRect(&rc, &npc->rc)) {
                PositionControl2(npc, &rc, NULL);
                fModified = TRUE;
            }
        }
    }

    if (fModified) {
        if (gfDlgSelected || !gcSelected)
            InvalidateRect(gcd.npc->hwnd, NULL, TRUE);

        CalcSelectedRect();
        gfResChged = gfDlgChanged = TRUE;
        ShowFileStatus(FALSE);
        StatusUpdate();
    }
}


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.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: status.c
*
* Support for the Status window.
*
* Functions:
*
*    StatusDlgProc()
*    StatusApplyChanges()
*    StatusFillSymbolList()
*    StatusSetCoords()
*    StatusUpdate()
*    StatusSetEnable()
*    StatusInit()
*    StatusProcessCommand()
*    ValidateNewName()
*    ValidateNewID()
*    ApplyNewName()
*    ApplyNewID()
*    ApplyNewText()
*    StatusSetText()
*    StatusSetTextLabels()
*    StatusSetID()
*    StatusSetName()
*    StatusSetNameID()
*    StatusClearID()
*    StatusClearName()
*    StatusClear()
*    StatusShowFields()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"

STATICFN BOOL StatusInit(HWND hwnd);
STATICFN VOID StatusProcessCommand(HWND hwnd, INT idCtrl,
    INT NotifyCode);
STATICFN BOOL ValidateNewName(NPCTYPE npc, LPTSTR pszName,
    LPTSTR pszID, LPTSTR pszSym, PINT pidNew, BOOL *pfAddLabel);
STATICFN BOOL ValidateNewID(NPCTYPE npc, LPTSTR pszSym, LPTSTR pszID,
    PINT pidNew, BOOL *pfAddLabel);
STATICFN BOOL ApplyNewName(NPCTYPE npc, LPTSTR pszName,
    BOOL fAddLabel, LPTSTR pszSym, INT idNew);
STATICFN BOOL ApplyNewID(NPCTYPE npc, INT idNew, BOOL fAddLabel,
    LPTSTR pszSym);
STATICFN BOOL ApplyNewText(NPCTYPE npc, LPTSTR pszNewText);
STATICFN VOID StatusSetText(LPTSTR pszText, INT Type);
STATICFN VOID StatusSetTextLabels(INT Type, BOOL fIsOrd);
STATICFN VOID StatusSetID(INT id, BOOL fSymAlso);
STATICFN VOID StatusSetName(LPTSTR pszName, INT Type);
STATICFN VOID StatusSetNameID(INT id, INT Type);
STATICFN VOID StatusClearID(VOID);
STATICFN VOID StatusClearName(VOID);
STATICFN VOID StatusClear(VOID);
STATICFN VOID StatusShowFields(INT Type);

/*
 * TRUE if the user has changed the entry fields in the status window.
 * This will be reset to FALSE whenever new information is placed
 * into the fields.
 */
static BOOL gfStatusChanged = FALSE;

/*
 * These globals save the window positions of the upper and lower
 * combo boxes and edit controls.  This is used to position them
 * depending on what type of control is selected.
 */
static RECT grcTopCombo;
static RECT grcBottomCombo;
static RECT grcTopEdit;
static RECT grcBottomEdit;

/************************************************************************
* StatusDlgProc
*
* This is the dialog procedure for the Status ribbon window.
*
************************************************************************/

DIALOGPROC StatusDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            return StatusInit(hwnd);

        case WM_PAINT:
            {
                HDC hdc;
                RECT rc;
                PAINTSTRUCT ps;
                HPEN hpenWindowFrame;

                /*
                 * 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);

                SelectObject(hdc, hpenDarkGray);
                MoveToEx(hdc, rc.left, (rc.top + gcyStatus) - gcyBorder - 1, NULL);
                LineTo(hdc, rc.right, (rc.top + gcyStatus) - gcyBorder - 1);

                hpenWindowFrame = CreatePen(PS_SOLID, gcyBorder,
                        GetSysColor(COLOR_WINDOWFRAME));
                SelectObject(hdc, hpenWindowFrame);
                MoveToEx(hdc, rc.left, (rc.top + gcyStatus) - gcyBorder, NULL);
                LineTo(hdc, rc.right, (rc.top + gcyStatus) - gcyBorder);

                EndPaint(hwnd, &ps);
                DeleteObject(hpenWindowFrame);
            }

            break;

        case WM_CTLCOLORDLG:
        case WM_CTLCOLORLISTBOX:
        case WM_CTLCOLORSTATIC:
            switch ((WORD)(msg - WM_CTLCOLORMSGBOX)) {
                case CTLCOLOR_DLG:
                case CTLCOLOR_LISTBOX:
                    return (BOOL)GetStockObject(LTGRAY_BRUSH);

                case CTLCOLOR_STATIC:
                    SetBkColor((HDC)(wParam),
                            LIGHTGRAY);
                    return (BOOL)GetStockObject(LTGRAY_BRUSH);
            }

            return (BOOL)NULL;

        case WM_COMMAND:
            StatusProcessCommand(hwnd,
                    LOWORD(wParam),
                    HIWORD(wParam));
            break;

        default:
            return FALSE;
    }

    return FALSE;
}

/************************************************************************
* StatusInit
*
* Initializes the Status ribbon window.
*
* Arguments:
*     HWND hwnd = The window handle.
*
* Returns:
*
* TRUE
*
************************************************************************/

STATICFN BOOL StatusInit(
    HWND hwnd)
{
    /*
     * Set this global right away.  Other routines that will be called
     * before CreateDialog returns depend on this global.
     */
    hwndStatus = hwnd;

    GetChildRect(GetDlgItem(hwnd, DID_STATUSSYM), &grcTopCombo);
    GetChildRect(GetDlgItem(hwnd, DID_STATUSSYMID), &grcTopEdit);
    GetChildRect(GetDlgItem(hwnd, DID_STATUSNAME), &grcBottomCombo);
    GetChildRect(GetDlgItem(hwnd, DID_STATUSNAMEID), &grcBottomEdit);

    SendDlgItemMessage(hwnd, DID_STATUSSYM, EM_LIMITTEXT, CCHTEXTMAX, 0L);
    SendDlgItemMessage(hwnd, DID_STATUSNAME, EM_LIMITTEXT, CCHTEXTMAX, 0L);
    SendDlgItemMessage(hwnd, DID_STATUSSYMID, EM_LIMITTEXT, CCHIDMAX, 0L);
    SendDlgItemMessage(hwnd, DID_STATUSNAMEID, EM_LIMITTEXT, CCHIDMAX, 0L);
    SendDlgItemMessage(hwnd, DID_STATUSTEXT, EM_LIMITTEXT, CCHTEXTMAX, 0L);

    StatusFillSymbolList(plInclude);
    StatusUpdate();
    StatusSetEnable();

    /*
     * Return TRUE so that the dialog manager does NOT set the focus
     * for me.  This prevents the status window from initially having
     * the focus when the editor is started.
     */
    return TRUE;
}

/************************************************************************
* StatusProcessCommand
*
* Function that processes commands sent to the status window.
*
* Arguments:
*   HWND hwnd        - The window handle.
*   INT idCtrl       - The id of the control the WM_COMMAND is for.
*   INT NotifyCode   - The control's notification code.
*
*
************************************************************************/

STATICFN VOID StatusProcessCommand(
    HWND hwnd,
    INT idCtrl,
    INT NotifyCode)
{
    TCHAR szSym[CCHTEXTMAX];
    TCHAR szID[CCHIDMAX + 1];
    NPLABEL npLabel;
    INT id;
    INT nIndex;
    LPTSTR pszOldName;

    switch (idCtrl) {
        case DID_STATUSSYM:
            /*
             * Did the symbol edit field change and is
             * something selected?
             */
            if (gnpcSel && (NotifyCode == CBN_EDITCHANGE ||
                    NotifyCode == CBN_SELCHANGE)) {
                /*
                 * Get the symbol and begin looking for it.
                 */
                if (NotifyCode == CBN_EDITCHANGE) {
                    /*
                     * The edit field was typed into.  Get the
                     * new text from there.
                     */
                    GetDlgItemText(hwnd, DID_STATUSSYM, szSym, CCHTEXTMAX);
                }
                else {
                    /*
                     * A new string was selected from the list
                     * box.  Get it from the list box, because
                     * at this point the new text is not yet set
                     * into the edit control!
                     */
                    nIndex = (INT)SendDlgItemMessage(hwnd,
                            DID_STATUSSYM, CB_GETCURSEL, 0, 0L);

                    if (nIndex != CB_ERR)
                        SendDlgItemMessage(hwnd, DID_STATUSSYM, CB_GETLBTEXT,
                                nIndex, (DWORD)szSym);
                    else
                        *szSym = CHAR_NULL;
                }

                /*
                 * Convert the symbol field to the associated id value,
                 * taking into account the special IDOK values, etc.
                 */
                if (!LabelToID(szSym, &id)) {
                    /*
                     * The symbol was not found.
                     * If the symbol is not blank, and the
                     * id of the control is already taken
                     * by another label, fill the id field
                     * with the next available id.  Otherwise,
                     * fill the id field with the controls
                     * id value.  It is assumed here that the
                     * dialog cannot be selected if the symbol
                     * field was able to be changed.
                     */
                    if (*szSym && FindID(gnpcSel->id, plInclude))
                        id = NextID(NEXTID_CONTROL, plInclude, 0);
                    else
                        id = gnpcSel->id;
                }

                StatusSetID(id, FALSE);

                gfStatusChanged = TRUE;
            }

            break;

        case DID_STATUSNAME:
            /*
             * Did the name edit field change and is
             * something selected?
             */
            if (gnpcSel && (NotifyCode == CBN_EDITCHANGE ||
                    NotifyCode == CBN_SELCHANGE)) {
                /*
                 * Get the symbol and begin looking for it.
                 */
                if (NotifyCode == CBN_EDITCHANGE) {
                    /*
                     * The edit field was typed into.  Get the
                     * new text from there.
                     */
                    GetDlgItemText(hwnd, DID_STATUSNAME, szSym, CCHTEXTMAX);
                }
                else {
                    /*
                     * A new string was selected from the list
                     * box.  Get it from the list box, because
                     * at this point the new text is not yet set
                     * into the edit control!
                     */
                    nIndex = (INT)SendDlgItemMessage(hwnd,
                            DID_STATUSNAME, CB_GETCURSEL, 0, 0L);

                    if (nIndex != CB_ERR)
                        SendDlgItemMessage(hwnd, DID_STATUSNAME, CB_GETLBTEXT,
                                nIndex, (DWORD)szSym);
                    else
                        *szSym = CHAR_NULL;
                }

                /*
                 * Try and convert the name to an ordinal.
                 */
                StrToNameOrd(szSym, (gnpcSel->pwcd->iType == W_DIALOG) ?
                        TRUE : FALSE);

                /*
                 * Was the name converted to an ordinal?
                 */
                if (IsOrd(szSym)) {
                    id = OrdID(szSym);
                }
                /*
                 * Is it an existing label?
                 */
                else if (npLabel = FindLabel(szSym, plInclude)) {
                    id = npLabel->id;
                }
                else {
                    /*
                     * Get a pointer to the original name.
                     */
                    pszOldName = (gnpcSel->pwcd->iType == W_DIALOG) ?
                            gcd.pszDlgName : gnpcSel->text;

                    /*
                     * If the old name was originally an ordinal, and
                     * there was no corresponding label for it,
                     * assume that the user is trying to enter a
                     * define for it and leave it alone.  Otherwise,
                     * pick the next available id.  But if the user
                     * completely blanks out the field, leave it
                     * alone also (this is a benign case).
                     */
                    if (IsOrd(pszOldName) &&
                            (!*szSym || !FindID(OrdID(pszOldName), plInclude)))
                        id = OrdID(pszOldName);
                    else
                        id = NextID((gnpcSel->pwcd->iType == W_DIALOG) ?
                                NEXTID_DIALOG : NEXTID_CONTROL, plInclude, 0);
                }

                StatusSetNameID(id, gnpcSel->pwcd->iType);

                /*
                 * Change the labels to reflect that the entered name
                 * is an ID instead of a name.
                 */
                StatusSetTextLabels(gnpcSel->pwcd->iType, TRUE);

                gfStatusChanged = TRUE;
            }

            break;

        case DID_STATUSSYMID:
        case DID_STATUSNAMEID:
        case DID_STATUSTEXT:
            if (!gnpcSel)
                break;

            if (NotifyCode == EN_CHANGE)
                gfStatusChanged = TRUE;

            if (idCtrl == DID_STATUSNAMEID) {
                GetDlgItemText(hwnd, DID_STATUSNAME, szSym, CCHTEXTMAX);
                GetDlgItemText(hwnd, DID_STATUSNAMEID, szID, CCHIDMAX + 1);
                StrToNameOrd(szSym, (gnpcSel->pwcd->iType == W_DIALOG) ?
                        TRUE : FALSE);

                /*
                 * Change the labels to reflect whether the entered name
                 * is an ID or a name.  It is considered an id if the
                 * edit field has something in it, or if the name field
                 * is a valid label, or if the name field represents an
                 * ordinal (it is numeric).
                 */
                StatusSetTextLabels(gnpcSel->pwcd->iType,
                        (IsOrd(szSym) || *szID ||
                        FindLabel(szSym, plInclude)) ? TRUE : FALSE);
            }

            break;

        case IDOK:
            if (StatusApplyChanges())
                SetFocus(ghwndMain);

            break;

        case IDCANCEL:
            StatusUpdate();
            SetFocus(ghwndMain);
            break;
    }
}

/************************************************************************
* StatusApplyChanges
*
* Processes the Enter command from the "Status" bar window to apply
* the current value of the fields to the current control.
*
* Returns:
*
* TRUE is successful; otherwise, FALSE is returned.
*
************************************************************************/

BOOL StatusApplyChanges(VOID)
{
    TCHAR szText[CCHTEXTMAX];
    TCHAR szSym[CCHTEXTMAX];
    TCHAR szName[CCHTEXTMAX];
    TCHAR szSymID[CCHIDMAX + 1];
    TCHAR szNameID[CCHIDMAX + 1];
    TCHAR szNameSym[CCHTEXTMAX];
    INT idSymNew;
    INT idNameNew;
    BOOL fAddSymLabel;
    BOOL fAddNameLabel;
    BOOL fSuccess = FALSE;

    /*
     * Quit if nothing is selected, or if nothing was changed.
     */
    if (!gnpcSel || !gfStatusChanged)
        return TRUE;

    idSymNew = gnpcSel->id;

    switch (gnpcSel->pwcd->iType) {
        case W_DIALOG:
            GetDlgItemText(hwndStatus, DID_STATUSNAME, szName, CCHTEXTMAX);
            GetDlgItemText(hwndStatus, DID_STATUSNAMEID, szNameID, CCHIDMAX + 1);
            GetDlgItemText(hwndStatus, DID_STATUSTEXT, szText, CCHTEXTMAX);
            if (!ValidateNewName(gnpcSel, szName, szNameID, szNameSym,
                    &idNameNew, &fAddNameLabel))
                return FALSE;

            if (ApplyNewName(gnpcSel, szName, fAddNameLabel,
                    szNameSym, idNameNew) &&
                    ApplyNewText(gnpcSel, szText))
                fSuccess = TRUE;

            break;

        case W_ICON:
            GetDlgItemText(hwndStatus, DID_STATUSNAME, szName, CCHTEXTMAX);
            GetDlgItemText(hwndStatus, DID_STATUSNAMEID, szNameID, CCHIDMAX + 1);
            GetDlgItemText(hwndStatus, DID_STATUSSYM, szSym, CCHTEXTMAX);
            GetDlgItemText(hwndStatus, DID_STATUSSYMID, szSymID, CCHIDMAX + 1);
            if (!ValidateNewName(gnpcSel, szName, szNameID, szNameSym,
                    &idNameNew, &fAddNameLabel) ||
                    !ValidateNewID(gnpcSel, szSym, szSymID,
                    &idSymNew, &fAddSymLabel))
                return FALSE;

            if (ApplyNewID(gnpcSel, idSymNew, fAddSymLabel, szSym) &&
                    ApplyNewName(gnpcSel, szName, fAddNameLabel,
                    szNameSym, idNameNew))
                fSuccess = TRUE;

            break;

        default:
            GetDlgItemText(hwndStatus, DID_STATUSSYM, szSym, CCHTEXTMAX);
            GetDlgItemText(hwndStatus, DID_STATUSSYMID, szSymID, CCHIDMAX + 1);
            GetDlgItemText(hwndStatus, DID_STATUSTEXT, szText, CCHTEXTMAX);
#ifdef JAPAN
            {
                TCHAR   szTmp[CCHTEXTMAX];

                KKExpandCopy(szTmp, szText, CCHTEXTMAX);
                lstrcpy(szText, szTmp);
            }
#endif
            if (!ValidateNewID(gnpcSel, szSym, szSymID, &idSymNew, &fAddSymLabel))
                return FALSE;

            if (ApplyNewID(gnpcSel, idSymNew, fAddSymLabel, szSym) &&
                    ApplyNewText(gnpcSel, szText))
                fSuccess = TRUE;

            break;
    }

    if (fSuccess) {
        ShowFileStatus(FALSE);
        StatusUpdate();
    }

    return fSuccess;
}

/************************************************************************
* ValidateNewName
*
* Validates the new name from the processing of the OK command
* from the Status ribbon window.
*
* The name is considered valid if it does not have any blanks in it,
* it is not already used by another dialog in the resource file, and
* it is not an empty string.
*
* Arguments:
*   NPCTYPE npc      - Pointer to the control.
*   LPTSTR pszName   - The new name.
*   LPTSTR pszID     - The ID.
*   LPTSTR pszSym    - The symbol.
*   PINT pidNew      - Where to return the new ID if successful.
*   BOOL *pfAddLabel - Set to TRUE if this symbol/id should be added
*                      to the include list.  Not touched otherwise.
*
* Returns:
*   TRUE if the new name is valid, FALSE otherwise.
*
************************************************************************/

STATICFN BOOL ValidateNewName(
    NPCTYPE npc,
    LPTSTR pszName,
    LPTSTR pszID,
    LPTSTR pszSym,
    PINT pidNew,
    BOOL *pfAddLabel)
{
    NPLABEL npLabel;
    BOOL fIDEmpty = FALSE;
    BOOL fNameEmpty = FALSE;
    BOOL fAddLabel = FALSE;
    INT idNew;
    TCHAR szIDTemp[CCHIDMAX + 1];

    /*
     * Start by assuming that the label will NOT be added.
     */
    *pfAddLabel = FALSE;

    /*
     * Is the ID field non-blank?
     */
    if (*pszID) {
        /*
         * Is the id valid?
         */
        if (!IsValue(pszID)) {
            Message(MSG_BADSYMBOLID);
            SetFocus(GetDlgItem(hwndStatus, DID_STATUSNAMEID));
            return FALSE;
        }

        idNew = valtoi(pszID);
    }
    else {
        /*
         * The id field is blank.
         */
        fIDEmpty = TRUE;
    }

    /*
     * Is the name field blank?
     */
    if (!(*pszName))
        fNameEmpty = TRUE;

    if (fNameEmpty) {
        if (fIDEmpty) {
            Message((npc->pwcd->iType == W_DIALOG) ?
                    MSG_NODLGNAME : MSG_NOICONNAME);
            SetFocus(GetDlgItem(hwndStatus, DID_STATUSNAME));
            return FALSE;
        }
        else {
            WriteOrd((PORDINAL)pszName, idNew);
        }
    }
    else {
        /*
         * Error if there are imbedded blanks.
         */
        if (HasBlanks(pszName)) {
            Message((npc->pwcd->iType == W_DIALOG) ?
                    MSG_DLGNAMEHASBLANKS : MSG_ICONNAMEHASBLANKS);
            SetFocus(GetDlgItem(hwndStatus, DID_STATUSNAME));
            return FALSE;
        }

        /*
         * Convert the name to an ordinal, if appropriate.
         */
        StrToNameOrd(pszName, (npc->pwcd->iType == W_DIALOG) ? TRUE : FALSE);

        /*
         * If the name was translated to an ordinal, we are done.
         * Otherwise, keep trying to figure out whether it is a
         * name or a define.
         */
        if (!IsOrd(pszName)) {
            /*
             * Is the name a symbol in the current include list?
             */
            if (npLabel = FindLabel(pszName, plInclude)) {
                /*
                 * Yes.  If the id field was blank, just assume they
                 * wanted the corresponding id.
                 */
                if (fIDEmpty)
                    idNew = npLabel->id;

                /*
                 * If they somehow entered a valid define but tried to
                 * give it a different id value, show an error.
                 */
                if (npLabel->id != idNew) {
                    Myitoa(npLabel->id, szIDTemp);
                    Message(MSG_IDSYMMISMATCH, szIDTemp);
                    SetFocus(GetDlgItem(hwndStatus, DID_STATUSNAMEID));
                    return FALSE;
                }

                /*
                 * Make the name field into an ordinal.
                 */
                WriteOrd((PORDINAL)pszName, idNew);
            }
            else {
                /*
                 * The name was not found as a label.  Is the id field
                 * empty?
                 */
                if (fIDEmpty) {
                    /*
                     * Since the id field is empty, we can assume that
                     * what they entered is a name (not a define)
                     * and we are done.
                     */
                }
                else {
                    /*
                     * At this point we know the name field is not empty,
                     * and there is an id entered along with it.  We also
                     * know that the name (which we can now assume is
                     * a symbol) is not already found in the include file.
                     * We will check it for validity, copy it to the
                     * pszSym buffer, translate the name to an ordinal
                     * and set a flag saying that the sym/id pair should
                     * be added to the include list.
                     */
                    if (!IsSymbol(pszName)) {
                        Message(MSG_BADSYMBOLID);
                        SetFocus(GetDlgItem(hwndStatus, DID_STATUSNAME));
                        return FALSE;
                    }

                    /*
                     * Pass back the values.
                     */
                    lstrcpy(pszSym, pszName);
                    WriteOrd((PORDINAL)pszName, idNew);
                    *pidNew = idNew;

                    fAddLabel = TRUE;
                }
            }
        }
    }

    /*
     * Inform caller whether they should add this id/symbol
     * as a label to the include list.
     */
    *pfAddLabel = fAddLabel;

    /*
     * Return success.
     */
    return TRUE;
}

/************************************************************************
* ValidateNewID
*
* Validates the new ID from the processing of the OK command from
* the Status ribbon window.  It will return the new id in *pidNew.
* Note that the control is not actually updated by this routine.  This
* is so that if other validations fail nothing will have been done to the
* control.  It is assumed that ApplyNewID will be called later to do
* the actual update of the control.
*
* Arguments:
*   NPCTYPE npc      = Pointer to the control.
*   LPTSTR pszSym    = The symbol.
*   LPTSTR pszID     = The ID.
*   PINT pidNew      = Where to return the new ID if successful.
*   BOOL *pfAddLabel = Set to TRUE if this symbol/id should be added
*                      to the include list.  Not touched otherwise.
*
* Returns:
*   TRUE if successful, FALSE otherwise.  If TRUE is returned and they
*   changed the id, the new id will have been placed in *pidNew.
*
************************************************************************/

STATICFN BOOL ValidateNewID(
    NPCTYPE npc,
    LPTSTR pszSym,
    LPTSTR pszID,
    PINT pidNew,
    BOOL *pfAddLabel)
{
    NPLABEL npLabel;
    BOOL fIDEmpty = FALSE;
    BOOL fIDChanged = FALSE;
    BOOL fSymEmpty = FALSE;
    BOOL fSymChanged = FALSE;
    BOOL fAddLabel = FALSE;
    INT idNew;
    TCHAR szIDTemp[CCHIDMAX + 1];

    /*
     * Start by assuming that the label will NOT be added.
     */
    *pfAddLabel = FALSE;

    /*
     * If in translate mode, they cannot change the symbol or id,
     * and so it should not be validated.  Even if there was a
     * problem, they could not fix it.
     */
    if (gfTranslateMode)
        return TRUE;

    /*
     * Special case if they selected the "unused" label.
     * Blank out the symbol so it doesn't cause trouble
     * later, force the id to be zero, and then check if
     * the id was changed.
     */
    if (lstrcmp(pszSym, ids(IDS_UNUSED)) == 0) {
        pszSym = szEmpty;
        idNew = IDUNUSED;
        if (idNew != npc->id)
            fIDChanged = TRUE;
    }
    else if (lstrcmp(pszSym, ids(IDS_IDOK)) == 0 &&
            !FindLabel(ids(IDS_IDOK), plInclude)) {
        pszSym = szEmpty;
        idNew = IDOK;
        if (idNew != npc->id)
            fIDChanged = TRUE;
    }
    else if (lstrcmp(pszSym, ids(IDS_IDCANCEL)) == 0 &&
            !FindLabel(ids(IDS_IDCANCEL), plInclude)) {
        pszSym = szEmpty;
        idNew = IDCANCEL;
        if (idNew != npc->id)
            fIDChanged = TRUE;
    }
    /*
     * Is the ID field non-blank?
     */
    else if (*pszID) {
        /*
         * Is the id valid?
         */
        if (!IsValue(pszID)) {
            Message(MSG_BADSYMBOLID);
            SetFocus(GetDlgItem(hwndStatus, DID_STATUSSYMID));
            return FALSE;
        }

        /*
         * Did they change the id value?
         */
        idNew = valtoi(pszID);
        if (idNew != npc->id)
            fIDChanged = TRUE;
    }
    else {
        /*
         * The id field is blank.  This implies they changed it.
         */
        fIDEmpty = TRUE;
        fIDChanged = TRUE;
    }

    /*
     * Is the symbol field blank?
     */
    if (!(*pszSym))
        fSymEmpty = TRUE;

    /*
     * Determine if they have changed the symbol.
     * Did the original id have a symbol associated with it?
     */
    if (npLabel = FindID(npc->id, plInclude)) {
        if (lstrcmp(npLabel->pszLabel, pszSym) != 0)
            fSymChanged = TRUE;
    }
    else {
        /*
         * Since the original id did not have a symbol, if they
         * have entered a symbol it is changed.
         */
        if (!fSymEmpty)
            fSymChanged = TRUE;
    }

    /*
     * Quit if nothing changed.
     */
    if (!fSymChanged && !fIDChanged)
        return TRUE;

    /*
     * Is the symbol field empty?
     */
    if (fSymEmpty) {
        /*
         * If the id field is empty also, return an error.
         */
        if (fIDEmpty) {
            Message(MSG_BADSYMBOLID);
            SetFocus(GetDlgItem(hwndStatus, DID_STATUSSYMID));
            return FALSE;
        }
        else {
            /*
             * Otherwise, go on to the final test.  It doesn't matter
             * if this new id has a symbol or not.
             */
            goto CheckForDups;
        }
    }

    /*
     * At this point we know the symbol field is not empty.
     * Is it a valid symbol?
     */
    if (!IsSymbol(pszSym)) {
        Message(MSG_BADSYMBOLID);
        SetFocus(GetDlgItem(hwndStatus, DID_STATUSSYM));
        return FALSE;
    }

    /*
     * Does this symbol already exist?
     */
    if (npLabel = FindLabel(pszSym, plInclude)) {
        /*
         * Since the symbol exists, if they blanked the id field
         * be friendly and assume they want the matching id value.
         */
        if (fIDEmpty)

idNew = npLabel->id;

        /*
         * Does the id that is in the id field match the id of the
         * symbol they entered?
         */
        if (npLabel->id == idNew) {
            /*
             * Yes, go on to the final test.
             */
            goto CheckForDups;
        }
        else {
            /*
             * No, give them an error message saying that it has to.
             */
            Myitoa(npLabel->id, szIDTemp);
            Message(MSG_IDSYMMISMATCH, szIDTemp);
            SetFocus(GetDlgItem(hwndStatus, DID_STATUSSYMID));
            return FALSE;
        }
    }
    else {
        /*
         * Since the symbol doesn't exist, if they left the id field
         * blank, assume they want the next available id.
         */
        if (fIDEmpty)
            idNew = NextID(NEXTID_CONTROL, plInclude, 0);

        /*
         * They should add this id/symbol as a label to the
         * include list.
         */
        fAddLabel = TRUE;
        goto CheckForDups;
    }

CheckForDups:
    /*
     * If the id changed, it is not the special "unused" id,
     * and it is a duplicate of another id, don't allow it.
     */
    if (idNew != npc->id && !IsUniqueID(idNew)) {
        Message(MSG_CTRLDUPID);
        SetFocus(GetDlgItem(hwndStatus, DID_STATUSSYM));
        return FALSE;
    }
    else {
        /*
         * Pass back the new id value.
         */
        *pidNew = idNew;

        /*
         * Inform caller whether they should add this id/symbol
         * as a label to the include list.
         */
        *pfAddLabel = fAddLabel;

        /*
         * Return success.
         */
        return TRUE;
    }
}

/************************************************************************
* ApplyNewName
*
* Updates an icon control's or dialog's name with a new name.
*
* If fAddLabel is TRUE, pszSym is assumed to be a string that is
* the symbol to be added and idNew contains the value associated with
* it.  This will cause a new define to be added to the current include
* file.  In either event, pszName will contain either a new name or
* an ordinal that is to be the name.  For dialogs, the dialog name will
* be updated.  For icon controls, the icon's text is updated with the
* name/ordinal.
*
* Arguments:
*   NPCTYPE npc     = Pointer to the control.
*   LPTSTR pszName  = New name.  A string or ordinal.
*   BOOL fAddLabel  = TRUE if a new label for this id/name should be added.
*   LPTSTR pszSym   = The new symbol to add (only valid if fAddLabel is TRUE).
*   INT idNew       = The new ID (only valid if fAddLabel is TRUE).
*
* Returns:
*
* TRUE if successful; otherwise, FALSE is returned.
*
************************************************************************/

STATICFN BOOL ApplyNewName(
    NPCTYPE npc,
    LPTSTR pszName,
    BOOL fAddLabel,
    LPTSTR pszSym,
    INT idNew)
{
    LPTSTR psz;

    if (fAddLabel) {
        /*
         * Go ahead and quietly add the label for them.
         */
        if (AddLabel(pszSym, idNew, FPOS_MAX, 0, &plInclude,
                &plDelInclude, NULL, NULL)) {
            /*
             * Return the controls new id, update the status
             * windows symbol combo box, mark the fact that
             * we have changed the include file, and return
             * success.
             */
            gfIncChged = TRUE;
            StatusFillSymbolList(plInclude);
        }
        else {
            /*
             * An error occurred on the AddLabel.  The most likely
             * cause of this is if they are trying to add a symbol
             * with a duplicate id.
             */
            SetFocus(GetDlgItem(hwndStatus, DID_STATUSNAMEID));
            return FALSE;
        }
    }

    switch (npc->pwcd->iType) {
        case W_ICON:
            /*
             * The resource name for an icon is stored in it's text.
             */
            ApplyNewText(npc, pszName);
            break;

        case W_DIALOG:
            /*
             * We are done if the name was not changed.
             */
            if (NameOrdCmp(gcd.pszDlgName, pszName) == 0)
                break;

            /*
             * Allocate room for the new name (it can be an ordinal!).
             */
            if (!(psz = MyAlloc(NameOrdLen(pszName))))
                return FALSE;

            NameOrdCpy(psz, pszName);

            if (gcd.pszDlgName)
                MyFree(gcd.pszDlgName);

            gcd.pszDlgName = psz;

            /*
             * If the new name is an ordinal, update the base id of
             * the dialog with it.
             */
            if (IsOrd(gcd.pszDlgName))
                gcd.npc->id = OrdID(gcd.pszDlgName);

            /*
             * Finally, set the "changed" flag.
             */
            gfResChged = gfDlgChanged = TRUE;

            break;
    }

    return TRUE;
}

/************************************************************************
* ApplyNewID
*
* Updates the control with the new id.
*
* Arguments:
*   NPCTYPE npc    = Pointer to the control.
*   INT idNew      = The ID.
*   BOOL fAddLabel = TRUE if a new label for this id/symbol should be added.
*   LPTSTR pszSym  = Symbol to add if fAddLabel is TRUE.
*
* Returns:
*
* TRUE if successful; otherwise, FALSE is returned.
*
************************************************************************/

STATICFN BOOL ApplyNewID(
    NPCTYPE npc,
    INT idNew,
    BOOL fAddLabel,
    LPTSTR pszSym)
{
    if (fAddLabel) {
        /*
         * Go ahead and quietly add the label for them.
         */
        if (AddLabel(pszSym, idNew, FPOS_MAX, 0, &plInclude,
                &plDelInclude, NULL, NULL)) {
            /*
             * Return the controls new id, update the status
             * windows symbol combo box, mark the fact that
             * we have changed the include file, and return
             * success.
             */
            gfIncChged = TRUE;
            StatusFillSymbolList(plInclude);
        }
        else {
            /*
             * An error occurred on the AddLabel.  The most likely
             * cause of this is if they are trying to add a symbol
             * with a duplicate id.
             */
            SetFocus(GetDlgItem(hwndStatus, DID_STATUSSYMID));
            return FALSE;
        }
    }

    /*
     * Return if the id wasn't changed.
     */
    if (npc->id == idNew)
        return TRUE;

    /*
     * Update the controls id and set the resource changed flag.
     */
    npc->id = idNew;
    gfResChged = gfDlgChanged = TRUE;
    return TRUE;
}

/************************************************************************
* ApplyNewText
*
* Sets the new text from the processing of the OK command from
* the Status ribbon window.  If the text was changed, the selected
* control will have its text updated.
*
* Arguments:
*   NPCTYPE npc       = Pointer to the control.
*   LPTSTR pszNewText = The new text.
*
* Returns:
*
* TRUE if successful, FALSE otherwise.
*
************************************************************************/

STATICFN BOOL ApplyNewText(
    NPCTYPE npc,
    LPTSTR pszNewText)
{
    INT cb;
    LPTSTR psz;

    /*
     * Did the text change?
     */
    if ((npc->text == NULL && *pszNewText) ||
            (npc->text != NULL && NameOrdCmp(npc->text, pszNewText) != 0)) {
        if (*pszNewText) {
            cb = NameOrdLen(pszNewText);

            if (npc->text == NULL)
                psz = MyAlloc(cb);
            else
                psz = MyRealloc(npc->text, cb);

            if (psz == NULL) {
                return FALSE;
            }
            else {
                NameOrdCpy(psz, pszNewText);
                npc->text = psz;
            }
        }
        else {
            if (npc->text) {
                MyFree(npc->text);
                npc->text = NULL;
            }
        }

        /*
         * Change the text of the control, except for W_ICON controls
         * because they have resource names.  In that case, we don't
         * want to change the text of the actual control because
         * the resource would probably not be found.
         */
        if (npc->pwcd->iType != W_ICON) {
            SetWindowText(npc->hwnd, pszNewText);

            /*
             * Redraw the control after the text changed.  This is
             * necessary because changing the text probably caused
             * the drag handles to get overwritten.
             */
            if (npc->pwcd->iType == W_DIALOG)
                /*
                 * Redraw the frame, which just overwrote the drag
                 * handles when the text was changed.
                 */
                SetWindowPos(npc->hwnd, NULL, 0, 0, 0, 0,
                        SWP_DRAWFRAME | SWP_NOACTIVATE | SWP_NOMOVE |
                        SWP_NOSIZE | SWP_NOZORDER);
            else
                /*
                 * Redraw the control(s).
                 */
                RedrawSelection();
        }

        gfResChged = gfDlgChanged = TRUE;
    }

    return TRUE;
}

/************************************************************************
* StatusFillSymbolList
*
* This function fills the Symbol and Name combo boxes with
* the given list of labels.
*
* Arguments:
*   NPLABEL plHead - pointer to the list of labels.
*
************************************************************************/

VOID StatusFillSymbolList(
    NPLABEL plHead)
{
    NPLABEL npLabel;
    HWND hwndSymCB;
    HWND hwndNameCB;
    HCURSOR hcurSave;

    hcurSave = SetCursor(hcurWait);

    /*
     * Get the handles to the combo boxes and clear out all items.
     */
    hwndSymCB = GetDlgItem(hwndStatus, DID_STATUSSYM);
    hwndNameCB = GetDlgItem(hwndStatus, DID_STATUSNAME);
    SendMessage(hwndSymCB, CB_RESETCONTENT, 0, 0L);
    SendMessage(hwndNameCB, CB_RESETCONTENT, 0, 0L);

    /*
     * Fill the combo boxes with the items.
     */
    for (npLabel = plHead; npLabel; npLabel = npLabel->npNext) {
        SendMessage(hwndSymCB, CB_ADDSTRING, 0, (DWORD)npLabel->pszLabel);
        SendMessage(hwndNameCB, CB_ADDSTRING, 0, (DWORD)npLabel->pszLabel);
    }

    /*
     * Put some special entries into the symbol combo.  Note that we allow
     * the user to have defined IDOK and IDCANCEL to be something else,
     * in which case we will NOT add these special entries after all.
     */
    SendMessage(hwndSymCB, CB_ADDSTRING, 0, (DWORD)ids(IDS_UNUSED));

    if (!FindLabel(ids(IDS_IDOK), plInclude))
        SendMessage(hwndSymCB, CB_ADDSTRING, 0, (DWORD)ids(IDS_IDOK));

    if (!FindLabel(ids(IDS_IDCANCEL), plInclude))
        SendMessage(hwndSymCB, CB_ADDSTRING, 0, (DWORD)ids(IDS_IDCANCEL));

    SetCursor(hcurSave);
}

/************************************************************************
* StatusSetCoords
*
* This function sets the coordinates in the status display
* to the given rectangle.
*
* Arguments:
*    PRECT prc - coordinates.
*
************************************************************************/

VOID StatusSetCoords(
    PRECT prc)
{
    static INT xSave = 0x7FFF;
    static INT ySave = 0x7FFF;
    static INT cxSave = 0x7FFF;
    static INT cySave = 0x7FFF;
    static INT x2Save = 0x7FFF;
    static INT y2Save = 0x7FFF;
    TCHAR szBuf[CCHTEXTMAX];

    if (prc) {
        if (prc->bottom - prc->top != cySave) {
            /*
             *  Save it for the next time.
             */
            cySave = prc->bottom - prc->top;

            wsprintf(szBuf, ids(IDS_CYFMTSTR), cySave);
            SetDlgItemText(hwndStatus, DID_STATUSCY, szBuf);
        }

        if (prc->right != x2Save || prc->bottom != y2Save) {
            /*
             *  Save them for the next time.
             */
            x2Save = prc->right;
            y2Save = prc->bottom;

            wsprintf(szBuf, L"(%d, %d)", x2Save, y2Save);
            SetDlgItemText(hwndStatus, DID_STATUSX2Y2, szBuf);
        }

        if (prc->left != xSave || prc->top != ySave) {
            /*
             *  Save them for the next time.
             */
            xSave = prc->left;
            ySave = prc->top;

            wsprintf(szBuf, L"(%d, %d)", xSave, ySave);
            SetDlgItemText(hwndStatus, DID_STATUSXY, szBuf);
        }

        if (prc->right - prc->left != cxSave) {
            /*
             *  Save it for the next time.
             */
            cxSave = prc->right - prc->left;

            wsprintf(szBuf, ids(IDS_CXFMTSTR), cxSave);
            SetDlgItemText(hwndStatus, DID_STATUSCX, szBuf);
        }
    }
    else {
        /*
         * Clear the fields.
         */
        SetDlgItemText(hwndStatus, DID_STATUSXY, szEmpty);
        SetDlgItemText(hwndStatus, DID_STATUSX2Y2, szEmpty);
        SetDlgItemText(hwndStatus, DID_STATUSCX, szEmpty);
        SetDlgItemText(hwndStatus, DID_STATUSCY, szEmpty);

        /*
         * Reset the cache variables so that the next "set" of
         * the coords is sure to update the display fields.
         */
        xSave =
        ySave =
        cxSave =
        cySave =
        x2Save =
        y2Save = 0x7FFF;
    }
}

/************************************************************************
* StatusSetText
*
* This function sets the "Text" field in the status display
* to the given text.
*
* Arguments:
*   LPTSTR pszText - The text to display.  This can be NULL.
*   INT Type       - Type of control.  One of the W_* constants.
*
************************************************************************/

STATICFN VOID StatusSetText(
    LPTSTR pszText,
    INT Type)
{
    /*
     * Protect against getting a null value.
     */
    if (!pszText)
        pszText = szEmpty;

    /*
     * If this is an icon control, set the name instead (the text
     * of an icon is really a resource name).
     */
    if (Type == W_ICON)
        StatusSetName(pszText, Type);
    else
#ifdef JAPAN
    {
        TCHAR   szTmp[CCHTEXTMAX];

        KDExpandCopy(szTmp, pszText, CCHTEXTMAX);
        SetDlgItemText(hwndStatus, DID_STATUSTEXT, szTmp);
    }
#else
        SetDlgItemText(hwndStatus, DID_STATUSTEXT, pszText);
#endif
}

/************************************************************************
* StatusSetTextLabels
*
* This function sets the labels of the descriptive text fields in
* Status window based on the type of control that is selected.
*
* Arguments:
*   INT Type    - Type of control.  One of the W_* constants.
*   BOOL fIsOrd - TRUE if the name is an ordinal.  This is ignored
*                 unless Type is W_DIALOG or W_ICON.
*
************************************************************************/

STATICFN VOID StatusSetTextLabels(
    INT Type,
    BOOL fIsOrd)
{
    WORD ids1;
    WORD ids2;

    switch (Type) {
        case W_DIALOG:
            ids1 = (WORD)(fIsOrd ? IDS_DLGIDLABEL : IDS_DLGNAMELABEL);
            ids2 = IDS_CAPTIONLABEL;
            break;

        case W_ICON:
            ids1 = IDS_SYMBOLLABEL;
            ids2 = (WORD)(fIsOrd ? IDS_ICONIDLABEL : IDS_ICONNAMELABEL);
            break;

        default:
            ids1 = IDS_SYMBOLLABEL;
            ids2 = IDS_TEXTLABEL;
            break;
    }

    SetDlgItemText(hwndStatus, DID_STATUSLABEL1, ids(ids1));
    SetDlgItemText(hwndStatus, DID_STATUSLABEL2, ids(ids2));
}

/************************************************************************
* StatusSetID
*
* Updates the id field of the given label.
*
* Arguments:
*     INT id        = The id.
*     BOOL fSymAlso = Find the corresponding label and update the
*                     symbol field also.
*
************************************************************************/

STATICFN VOID StatusSetID(
    INT id,
    BOOL fSymAlso)
{
    TCHAR szValue[CCHIDMAX + 1];
    NPLABEL npLabel;
    LPTSTR pszLabel;

    Myitoa(id, szValue);
    SetDlgItemText(hwndStatus, DID_STATUSSYMID, szValue);

    if (fSymAlso) {
        /*
         * If a matching label is found, the text is used.
         * Otherwise, if the id is zero, we use the "unused"
         * label.  Otherwise, we just use an empty string.
         */
        if (npLabel = FindID(id, plInclude))
            pszLabel = npLabel->pszLabel;
        else if (id == IDUNUSED)
            pszLabel = ids(IDS_UNUSED);
        else if (id == IDOK && !FindLabel(ids(IDS_IDOK), plInclude))
            pszLabel = ids(IDS_IDOK);
        else if (id == IDCANCEL && !FindLabel(ids(IDS_IDCANCEL), plInclude))
            pszLabel = ids(IDS_IDCANCEL);
        else
            pszLabel = szEmpty;

        SetDlgItemText(hwndStatus, DID_STATUSSYM, pszLabel);
    }
}

/************************************************************************
* StatusSetName
*
* This function sets the "Res. Name" field in the status display
* to the given text.
*
* Arguments:
*
*   LPTSTR pszName - The name to display.  NULL values and ordinals
*                    are handled properly.
*   INT Type       - Type of control.  One of the W_* constants.
*
************************************************************************/

STATICFN VOID StatusSetName(
    LPTSTR pszName,
    INT Type)
{
    NPLABEL npLabel;

    /*
     * Protect against getting a null value.
     */
    if (!pszName)
        pszName = szEmpty;

    /*
     * Does the name represent an ordinal?  If so, set the nameid
     * field to the value and fill in the name field with the
     * associated symbol, if there is one.
     *
     * Note that this does NOT produce a hex value in Hex Mode for
     * dialog names, because rc.exe does not parse hex ordinals
     * for dialog names.
     */
    if (IsOrd(pszName)) {
        StatusSetNameID(OrdID(pszName), Type);

        if (npLabel = FindID(OrdID(pszName), plInclude))
            pszName = npLabel->pszLabel;
        else
            pszName = szEmpty;
    }
    else {
        SetDlgItemText(hwndStatus, DID_STATUSNAMEID, szEmpty);
    }

    SetDlgItemText(hwndStatus, DID_STATUSNAME, pszName);
}

/************************************************************************
* StatusSetNameID
*
* This function sets the id edit field associated with the
* "Res. Name" field in the status display
*
* Arguments:
*
*   INT id   - The id to set into the field.
*   INT Type - Type of control.  One of the W_* constants.
*
************************************************************************/

STATICFN VOID StatusSetNameID(
    INT id,
    INT Type)
{
    TCHAR szValue[CCHIDMAX + 1];

    /*
     * If the current control is a dialog, do NOT produce a hex
     * value, even if the hex mode is on.  RC.Exe doesn't recognize
     * hex values for the dialog name as ordinals.
     */
    if (Type == W_DIALOG)
        itoaw(id, szValue, 10);
    else
        Myitoa(id, szValue);

    SetDlgItemText(hwndStatus, DID_STATUSNAMEID, szValue);
}

/************************************************************************
* StatusClearID
*
* Clears the id of the status window..
*
************************************************************************/

STATICFN VOID StatusClearID(VOID)
{
    SetDlgItemText(hwndStatus, DID_STATUSSYM, szEmpty);
    SetDlgItemText(hwndStatus, DID_STATUSSYMID, szEmpty);
}

/************************************************************************
* StatusClearName
*
* Clears the name of the status window.
*
************************************************************************/

STATICFN VOID StatusClearName(VOID)
{
    SetDlgItemText(hwndStatus, DID_STATUSNAME, szEmpty);
    SetDlgItemText(hwndStatus, DID_STATUSNAMEID, szEmpty);
}

/************************************************************************
* StatusClear
*
* Clears the status window.
*
************************************************************************/

STATICFN VOID StatusClear(VOID)
{
    StatusSetCoords(NULL);
    StatusSetText(NULL, W_NOTHING);
    StatusShowFields(W_NOTHING);
    StatusClearID();
    StatusClearName();
    gfStatusChanged = FALSE;
}

/************************************************************************
* StatusUpdate
*
* Updates the status window to the current selection.
*
************************************************************************/

VOID StatusUpdate(VOID)
{
    if (gnpcSel) {
        StatusSetCoords(&gnpcSel->rc);
        StatusSetText(gnpcSel->text, gnpcSel->pwcd->iType);

        if (gfDlgSelected)
            StatusSetName(gcd.pszDlgName, W_DIALOG);
        else
            StatusSetID(gnpcSel->id, TRUE);

        StatusShowFields(gnpcSel->pwcd->iType);

        gfStatusChanged = FALSE;
    }
    else {
        StatusClear();
    }
}

/************************************************************************
* StatusShowFields
*
* This function shows and hides fields in the Status ribbon based on
* whether the dialog is currently selected or not.  This is necessary
* because dialogs have a name, whereas controls do not, and controls
* have an id that dialogs do not have, etc.
*
* It will also call StatusSetTextLabels to set the text for the
* descriptive labels in front of the fields.
*
* This function should be called whenever it is possible that the current
* selection has been changed.
*
* Arguments:
*   INT Type - Type of control that is selected (W_* constant).
*
************************************************************************/

STATICFN VOID StatusShowFields(
    INT Type)
{
    /*
     * This static caches the type of the selected control.
     */
    static INT TypeSave = W_NOTHING;

    /*
     * We only have special cases for the dialog and for icon controls.
     * All other types are treated the same (we use W_CHECKBOX arbitrarily).
     * This prevents some repainting when switching between controls that
     * have the same layout of fields in the status window.
     */
    if (Type != W_DIALOG && Type != W_ICON)
        Type = W_CHECKBOX;

    if (Type != TypeSave) {
        switch (Type) {
            case W_DIALOG:
                MoveWindow(GetDlgItem(hwndStatus, DID_STATUSNAME),
                        grcTopCombo.left, grcTopCombo.top,
                        grcTopCombo.right - grcTopCombo.left,
                        grcTopCombo.bottom - grcTopCombo.top, TRUE);
                MoveWindow(GetDlgItem(hwndStatus, DID_STATUSNAMEID),
                        grcTopEdit.left, grcTopEdit.top,
                        grcTopEdit.right - grcTopEdit.left,
                        grcTopEdit.bottom - grcTopEdit.top, TRUE);

                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSNAME), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSNAMEID), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSTEXT), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSSYM), SW_HIDE);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSSYMID), SW_HIDE);

                StatusSetTextLabels(Type, IsOrd(gcd.pszDlgName));

                break;

            case W_ICON:
                MoveWindow(GetDlgItem(hwndStatus, DID_STATUSNAME),
                        grcBottomCombo.left, grcBottomCombo.top,
                        grcBottomCombo.right - grcBottomCombo.left,
                        grcBottomCombo.bottom - grcBottomCombo.top, TRUE);
                MoveWindow(GetDlgItem(hwndStatus, DID_STATUSNAMEID),
                        grcBottomEdit.left, grcBottomEdit.top,
                        grcBottomEdit.right - grcBottomEdit.left,
                        grcBottomEdit.bottom - grcBottomEdit.top, TRUE);

                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSNAME), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSNAMEID), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSTEXT), SW_HIDE);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSSYM), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSSYMID), SW_SHOW);

                StatusSetTextLabels(Type,
                        gnpcSel->text ? IsOrd(gnpcSel->text) : FALSE);

                break;

            default:
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSTEXT), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSSYM), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSSYMID), SW_SHOW);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSNAME), SW_HIDE);
                ShowWindow(GetDlgItem(hwndStatus, DID_STATUSNAMEID), SW_HIDE);

                StatusSetTextLabels(Type, FALSE);

                break;
        }

        TypeSave = Type;
    }
}

/************************************************************************
* StatusSetEnable
*
* This routine sets the enable state of the editable controls in the
* status window based on various state globals.
*
* The controls will be disabled on the following conditions:
*
*   1. Nothing is selected.
*   2. We are in Test mode.
*   3. One of the editors own dialogs is up (gfDisabled == TRUE).
*
* In addition, some controls will always be disabled if in Translate
* mode, and the text field will be disabled if this control cannot
* have text.
*
************************************************************************/

VOID StatusSetEnable(VOID)
{
    BOOL fEnableText = TRUE;
    BOOL fEnableSym = TRUE;
    BOOL fEnableName = TRUE;

    if (!gnpcSel || gfTestMode || gfDisabled)
        fEnableText = fEnableSym = fEnableName = FALSE;

    /*
     * Disable the text field if this control cannot have text.
     */
    if (gnpcSel && !gnpcSel->pwcd->fHasText)
        fEnableText = FALSE;

    /*
     * If the dialog is selected and the style does not include
     * a caption, disable the text field.
     */
    if (gfDlgSelected && (gnpcSel->flStyle & WS_CAPTION) != WS_CAPTION)
        fEnableText = FALSE;

    /*
     * Always disable the symbol and name fields if Translating.
     */
    if (gfTranslateMode)
        fEnableSym = fEnableName = FALSE;

    EnableWindow(GetDlgItem(hwndStatus, DID_STATUSTEXT), fEnableText);

    EnableWindow(GetDlgItem(hwndStatus, DID_STATUSSYM), fEnableSym);
    EnableWindow(GetDlgItem(hwndStatus, DID_STATUSSYMID), fEnableSym);

    EnableWindow(GetDlgItem(hwndStatus, DID_STATUSNAME), fEnableName);
    EnableWindow(GetDlgItem(hwndStatus, DID_STATUSNAMEID), fEnableName);
}
#ifdef JAPAN
/************************************************************************
* Copy strings to the buffer. text strings "\036" and "\037" are expanded to
* Codes \036 and \037 respectively.
************************************************************************/

VOID KKExpandCopy(LPTSTR pszDest, LPTSTR pszSrc, WORD wLimit)
{
    int  i;
    LPTSTR p = pszSrc;
#if defined(DBCS) && !defined(UNICODE)
#define wcsncmp     strncmp
#endif

    wLimit--;
    for (i = 0; i < wLimit && p && *p; i++) {
        if (*p == TEXT('\\')) {
            if(!wcsncmp(p+1, TEXT("036"), 3)) {
                pszDest[i] = 036;
                p+=4;
                continue;
            }
            else if(!wcsncmp(p+1, TEXT("037"), 3)) {
                pszDest[i] = 037;
                p+=4;
                continue;
           }
        }
        pszDest[i] = *p;
        if (IsDBCSLeadByte((BYTE)*p)) {
            if (i == wLimit - 1) {
                break;
            }
            pszDest[++i] = *(p+1);
        }
        p = CharNext(p);
    }
    pszDest[i] = '\0';
}
#endif


STYLES.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 Header *******************************
* Module Name: styles.c
*
* Handles the control styles selection, including the styles dialogs.
*
* Functions:
*
*    StylesDialog()
*    GenericStylesDlgProc()
*    CheckBoxStylesDlgProc()
*    RadioButtonStylesDlgProc()
*    PushButtonStylesDlgProc()
*    ComboBoxStylesDlgProc()
*    EditStylesDlgProc()
*    ListBoxStylesDlgProc()
*    CustomStylesDlgProc()
*    DialogStylesDlgProc()
*    FontNameEnumFunc()
*    PointSizeEnumFunc()
*    EnableComboBoxStyles()
*    EnableEditStyles()
*    EnableListBoxStyles()
*    SetCustomStylesField()
*    GetCustomStylesField()
*    EnableDialogStyles()
*    FillFontNameCombo()
*    FillPointSizeCombo()
*    AddToPointSizeCombo()
*    FillLanguageCombo()
*    FillSubLanguageCombo()
*    CheckStyleBoxes()
*    QueryCheckedStyles()
*    StylesHelp()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"

STATICFN VOID EnableComboBoxStyles(HWND hwnd, INT idCtrl);
STATICFN VOID EnableEditStyles(HWND hwnd, INT idCtrl);
STATICFN VOID EnableListBoxStyles(HWND hwnd, INT idCtrl);
STATICFN VOID SetCustomStylesField(HWND hwnd, DWORD flStyle);
STATICFN DWORD GetCustomStylesField(HWND hwnd);
STATICFN VOID EnableDialogStyles(HWND hwnd, INT idCtrl);
STATICFN VOID FillFontNameCombo(HWND hwndDlg);
STATICFN VOID FillPointSizeCombo(HWND hwndDlg, LPTSTR pszFaceName);
STATICFN VOID AddToPointSizeCombo(HWND hwndCombo, INT nPointSize);
STATICFN VOID FillLanguageCombo(HWND hwndDlg);
STATICFN VOID FillSubLanguageCombo(HWND hwndDlg, INT iLang);
STATICFN VOID CheckStyleBoxes(HWND hwnd, INT iClass, DWORD flStyle);
STATICFN VOID QueryCheckedStyles(HWND hwnd, INT iClass, DWORD *pflStyle);
STATICFN VOID StylesHelp(VOID);

/*
 * Global pointer to the CTYPE for the control or dialog whose styles
 * are being worked on.  All the styles dialog procs and workers use
 * this pointer.
 */
static NPCTYPE npcStyles;

/*
 * Globals that receive the new styles the user selected.
 */
static DWORD flStyleNew;
static DWORD flExtStyleNew;
static LPTSTR pszTextNew;
static DIALOGINFO diNew;

/************************************************************************
* StylesDialog
*
* Displays the appropriate styles dialog for the currently selected
* control.  If the user OK's the changes, this function sets the
* style of the control.
*
************************************************************************/

VOID StylesDialog(VOID)
{
    NPCTYPE npc;
    HWND hwndOld;
    INT fDlgResult;
    BOOL fChanged = FALSE;
    BOOL fFontChanged = FALSE;
    TCHAR szClassNew[CCHTEXTMAX];
    TCHAR szMenuNew[CCHTEXTMAX];
    TCHAR szTextNew[CCHTEXTMAX];

    /*
     * Quit if nothing was selected, or if we are in translate mode.
     */
    if (!gnpcSel || gfTranslateMode)
        return;

    /*
     * Set globals that the styles dialogs and worker routines will use.
     */
    npcStyles = gnpcSel;
    flStyleNew = npcStyles->flStyle;
    flExtStyleNew = npcStyles->flExtStyle;

    if (npcStyles->text)
        NameOrdCpy(szTextNew, npcStyles->text);
    else
        *szTextNew = CHAR_NULL;

    pszTextNew = szTextNew;

    /*
     * Set some other globals if this is the dialog instead of a control.
     */
    if (gfDlgSelected) {
        diNew.fResFlags = gcd.di.fResFlags;
        diNew.wLanguage = gcd.di.wLanguage;
        diNew.DataVersion = gcd.di.DataVersion;
        diNew.Version = gcd.di.Version;
        diNew.Characteristics = gcd.di.Characteristics;

        lstrcpy(diNew.szFontName, gcd.di.szFontName);
        diNew.nPointSize = gcd.di.nPointSize;

        diNew.pszClass = szClassNew;
        if (gcd.di.pszClass)
            NameOrdCpy(szClassNew, gcd.di.pszClass);
        else
            *szClassNew = CHAR_NULL;

        diNew.pszMenu = szMenuNew;
        if (gcd.di.pszMenu)
            NameOrdCpy(szMenuNew, gcd.di.pszMenu);
        else
            *szMenuNew = CHAR_NULL;
    }

    /*
     * Is this a custom control that has a styles proc to use?
     */
    if (npcStyles->pwcd->iType == W_CUSTOM && npcStyles->pwcd->lpfnStyle) {
        fDlgResult = CallCustomStyle(npcStyles, &flStyleNew, &flExtStyleNew,
            szTextNew);
    }
    else {
        /*
         * Show the appropriate styles dialog.
         */
        fDlgResult = DlgBox(npcStyles->pwcd->idStylesDialog,
                (WNDPROC)npcStyles->pwcd->pfnStylesDlgProc);
    }

    if (fDlgResult == IDOK) {
        /*
         * Now go through and determine if anything was really changed.
         */
        if (npcStyles->flStyle != flStyleNew ||
                npcStyles->flExtStyle != flExtStyleNew ||
                NameOrdCmp(npcStyles->text ?
                npcStyles->text : szEmpty, szTextNew) != 0)
            fChanged = TRUE;

        /*
         * If this is the dialog, check if some other things were changed.
         */
        if (gfDlgSelected) {
            if (gcd.di.fResFlags != diNew.fResFlags ||
                    gcd.di.wLanguage != diNew.wLanguage ||
                    NameOrdCmp(gcd.di.pszClass ?
                    gcd.di.pszClass : szEmpty, diNew.pszClass) != 0 ||
                    NameOrdCmp(gcd.di.pszMenu ?
                    gcd.di.pszMenu : szEmpty, diNew.pszMenu) != 0)
                fChanged = TRUE;

            if (lstrcmp(gcd.di.szFontName, diNew.szFontName) != 0 ||
                    (*diNew.szFontName &&
                    gcd.di.nPointSize != diNew.nPointSize))
                fChanged = fFontChanged = TRUE;
        }
    }

    /*
     * Did something change?
     */
    if (fChanged) {
        if (gfDlgSelected) {
            hwndOld = npcStyles->hwnd;
            CreateControl(npcStyles, pszTextNew, flStyleNew, flExtStyleNew,
                    npcStyles->id, &npcStyles->rc, (HWND)NULL, &diNew);

            /*
             * Create all the control windows in the new dialog.
             * They must be created (not just moved over by changing
             * the parent and owner) because some controls have
             * allocated memory on the old dialogs heap, and this
             * heap will become invalid after the old dialog
             * is destroyed below.
             */
            for (npc = npcHead; npc; npc = npc->npcNext) {
                /*
                 * If this is an icon control and the dialog font
                 * was just changed, we need to resize the control
                 * based on the new default icon size.
                 */
                if (npc->pwcd->iType == W_ICON && fFontChanged) {
                    npc->rc.right = npc->rc.left + awcd[W_ICON].cxDefault;
                    npc->rc.bottom = npc->rc.top + awcd[W_ICON].cyDefault;
                }

                CreateControl(npc, npc->text, npc->flStyle, npc->flExtStyle,
                        npc->id, &npc->rc, (HWND)NULL, NULL);
            }

            /*
             * Now move all the drag windows over to the new dialog.
             * This must be done after creating all the controls
             * because of the touchy Z-order that the drag windows
             * and the controls must have for painting and selection
             * of the drag windows to work properly.  Note that we
             * rely on SetParent to add the window at
             * the TOP in Z-order.
             */
            for (npc = npcHead; npc; npc = npc->npcNext) {
                SetParent(npc->hwndDrag, npcStyles->hwnd);

                /*
                 * Adjust the position of the drag window.
                 */
                SizeDragToControl(npc);
            }

            ShowWindow(npcStyles->hwnd, SW_SHOWNA);
            ToolboxOnTop();
            DestroyWindow(hwndOld);
        }
        else {
            hwndOld = npcStyles->hwnd;

            if (CreateControl(npcStyles, pszTextNew, flStyleNew, flExtStyleNew,
                    npcStyles->id, &npcStyles->rc, hwndOld, NULL)) {
                /*
                 * Get rid of the old control window.
                 */
                DestroyWindow(hwndOld);

                /*
                 * Adjust the size and position of its drag window.
                 */
                SizeDragToControl(npcStyles);
            }
        }

        gfResChged = gfDlgChanged = TRUE;
        ShowFileStatus(FALSE);
        StatusUpdate();
        StatusSetEnable();
    }
}

/************************************************************************
* GenericStylesDlgProc
*
* Dialog procedure for styles.
*
************************************************************************/

DIALOGPROC GenericStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            CheckStyleBoxes(hwnd, npcStyles->pwcd->iClass,
                    npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                    QueryCheckedStyles(hwnd, npcStyles->pwcd->iClass,
                            &flStyleNew);
                    QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);
                    EndDialog(hwnd, IDOK);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* CheckBoxStylesDlgProc
*
* Dialog procedure for checkboxes.
*
************************************************************************/

DIALOGPROC CheckBoxStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    DWORD dwType;
    BOOL f3State;
    BOOL fAuto;

    switch (msg) {
        case WM_INITDIALOG:
            CheckStyleBoxes(hwnd, npcStyles->pwcd->iClass,
                    npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            dwType = npcStyles->flStyle & BS_ALL;
            if (dwType == BS_AUTOCHECKBOX || dwType == BS_AUTO3STATE)
                CheckDlgButton(hwnd, DID_BS_AUTOXXX, 1);

            if (dwType == BS_3STATE || dwType == BS_AUTO3STATE)
                CheckDlgButton(hwnd, DID_BS_3STATE, 1);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                    QueryCheckedStyles(hwnd, npcStyles->pwcd->iClass,
                            &flStyleNew);
                    QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);

                    fAuto = IsDlgButtonChecked(hwnd, DID_BS_AUTOXXX);
                    f3State = IsDlgButtonChecked(hwnd, DID_BS_3STATE);
                    flStyleNew &= ~BS_ALL;
                    if (fAuto) {
                        if (f3State)
                            flStyleNew |= BS_AUTO3STATE;
                        else
                            flStyleNew |= BS_AUTOCHECKBOX;
                    }
                    else {
                        if (f3State)
                            flStyleNew |= BS_3STATE;
                        else
                            flStyleNew |= BS_CHECKBOX;
                    }

                    EndDialog(hwnd, IDOK);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* RadioButtonStylesDlgProc
*
* Dialog box procedure for radio buttons.
*
************************************************************************/

DIALOGPROC RadioButtonStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            CheckStyleBoxes(hwnd, npcStyles->pwcd->iClass,
                    npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            if ((npcStyles->flStyle & BS_ALL) == BS_AUTORADIOBUTTON)
                CheckDlgButton(hwnd, DID_BS_AUTOXXX, 1);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                    QueryCheckedStyles(hwnd, npcStyles->pwcd->iClass,
                            &flStyleNew);
                    QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);

                    flStyleNew &= ~BS_ALL;
                    if (IsDlgButtonChecked(hwnd, DID_BS_AUTOXXX))
                        flStyleNew |= BS_AUTORADIOBUTTON;
                    else
                        flStyleNew |= BS_RADIOBUTTON;

                    EndDialog(hwnd, IDOK);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* PushButtonStylesDlgProc
*
* We do not normally allow more than one default push button in a
* dialog. but if this button is already a default button, we must
* allow them to change it to a normal one, even if there is already
* another default button in the dialog.  Note that this condition
* would normally never happen, unless they read in a res file with
* this condition already.
*
************************************************************************/

DIALOGPROC PushButtonStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    NPCTYPE npc;

    switch (msg) {
        case WM_INITDIALOG:
            CheckStyleBoxes(hwnd, npcStyles->pwcd->iClass,
                    npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            /*
             * Only test for possibly disabling the "default"
             * checkbox if the current control does not have the
             * "default" style.  If it does, we must always allow
             * them to turn it off.
             */
            if ((npcStyles->flStyle & BS_ALL) != BS_DEFPUSHBUTTON) {
                /*
                 * Loop through all the controls.  If any pushbutton
                 * is found with the "default" style, we disable the
                 * "Default" checkbox in the styles dialog.
                 */
                for (npc = npcHead; npc; npc = npc->npcNext)
                    if ((npc->pwcd->iType == W_PUSHBUTTON) &&
                            (npc->flStyle & BS_ALL) == BS_DEFPUSHBUTTON) {
                        EnableWindow(GetDlgItem(hwnd, DID_BS_DEFPUSHBUTTON),
                                FALSE);
                        break;
                    }
            }

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                    QueryCheckedStyles(hwnd, npcStyles->pwcd->iClass,
                            &flStyleNew);
                    QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);

                    EndDialog(hwnd, IDOK);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* ComboBoxStylesDlgProc
*
* Dialog procedure for combo boxes.
*
************************************************************************/

DIALOGPROC ComboBoxStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            CheckStyleBoxes(hwnd, npcStyles->pwcd->iClass,
                    npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            EnableComboBoxStyles(hwnd, 0);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_CBS_OWNERDRAWFIXED:
                case DID_CBS_OWNERDRAWVARIABLE:
                    if (HIWORD(wParam) == BN_CLICKED)
                        EnableComboBoxStyles(hwnd,
                                LOWORD(wParam));

                    return TRUE;

                case IDOK:
                    QueryCheckedStyles(hwnd, npcStyles->pwcd->iClass,
                            &flStyleNew);
                    QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);
                    EndDialog(hwnd, IDOK);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* EnableComboBoxStyles
*
* Checks/unchecks, disables/enables various checkboxes that are
* mutually exclusive and/or dependant for the Combo Box Styles dialog.
*
* Arguments:
*   HWND hwnd   - Dialog window handle.
*   INT idCtrl  - ID of the control that was clicked on.
*
************************************************************************/

STATICFN VOID EnableComboBoxStyles(
    HWND hwnd,
    INT idCtrl)
{
    BOOL fFixedChecked;
    BOOL fVariableChecked;

    fFixedChecked = IsDlgButtonChecked(hwnd, DID_CBS_OWNERDRAWFIXED);
    fVariableChecked = IsDlgButtonChecked(hwnd, DID_CBS_OWNERDRAWVARIABLE);

    if (fFixedChecked || fVariableChecked) {
        EnableWindow(GetDlgItem(hwnd, DID_CBS_HASSTRINGS), TRUE);
    }
    else {
        EnableWindow(GetDlgItem(hwnd, DID_CBS_HASSTRINGS), FALSE);
        CheckDlgButton(hwnd, DID_CBS_HASSTRINGS, 0);
    }

    switch (idCtrl) {
        case DID_CBS_OWNERDRAWFIXED:
            if (fFixedChecked)
                CheckDlgButton(hwnd, DID_CBS_OWNERDRAWVARIABLE, 0);

            break;

        case DID_CBS_OWNERDRAWVARIABLE:
            if (fVariableChecked)
                CheckDlgButton(hwnd, DID_CBS_OWNERDRAWFIXED, 0);

            break;
    }
}

/************************************************************************
* EditStylesDlgProc
*
* Dialog procedure for edit boxes.
*
************************************************************************/

DIALOGPROC EditStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            CheckStyleBoxes(hwnd, npcStyles->pwcd->iClass,
                    npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            EnableEditStyles(hwnd, 0);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_ES_UPPERCASE:
                case DID_ES_LOWERCASE:
                case DID_ES_MULTILINE:
                    if (HIWORD(wParam) == BN_CLICKED)
                        EnableEditStyles(hwnd,
                                LOWORD(wParam));

                    return TRUE;

                case IDOK:
                    QueryCheckedStyles(hwnd, npcStyles->pwcd->iClass,
                            &flStyleNew);
                    QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);
                    EndDialog(hwnd, IDOK);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* EnableEditStyles
*
* Checks/unchecks, disables/enables various checkboxes that are
* mutually exclusive and/or dependant for the Edit Field Styles dialog.
*
* Arguments:
*   HWND hwnd  - Dialog window handle.
*   INT idCtrl - ID of the control that was clicked on.
*
************************************************************************/

STATICFN VOID EnableEditStyles(
    HWND hwnd,
    INT idCtrl)
{
    if (IsDlgButtonChecked(hwnd, DID_ES_MULTILINE)) {
        EnableWindow(GetDlgItem(hwnd, DID_ES_CENTER), TRUE);
        EnableWindow(GetDlgItem(hwnd, DID_ES_RIGHT), TRUE);
        EnableWindow(GetDlgItem(hwnd, DID_WS_VSCROLL), TRUE);
        EnableWindow(GetDlgItem(hwnd, DID_ES_AUTOVSCROLL), TRUE);
        EnableWindow(GetDlgItem(hwnd, DID_WS_HSCROLL), TRUE);
    }
    else {
        EnableWindow(GetDlgItem(hwnd, DID_ES_CENTER), FALSE);
        EnableWindow(GetDlgItem(hwnd, DID_ES_RIGHT), FALSE);
        EnableWindow(GetDlgItem(hwnd, DID_WS_VSCROLL), FALSE);
        EnableWindow(GetDlgItem(hwnd, DID_ES_AUTOVSCROLL), FALSE);
        EnableWindow(GetDlgItem(hwnd, DID_WS_HSCROLL), FALSE);

        CheckDlgButton(hwnd, DID_ES_LEFT, 1);
        CheckDlgButton(hwnd, DID_ES_CENTER, 0);
        CheckDlgButton(hwnd, DID_ES_RIGHT, 0);
        CheckDlgButton(hwnd, DID_WS_VSCROLL, 0);
        CheckDlgButton(hwnd, DID_ES_AUTOVSCROLL, 0);
        CheckDlgButton(hwnd, DID_WS_HSCROLL, 0);
    }

    if (idCtrl == DID_ES_UPPERCASE) {
        if (IsDlgButtonChecked(hwnd, DID_ES_UPPERCASE))
            CheckDlgButton(hwnd, DID_ES_LOWERCASE, 0);
    }
    else if (idCtrl == DID_ES_LOWERCASE) {
        if (IsDlgButtonChecked(hwnd, DID_ES_LOWERCASE))
            CheckDlgButton(hwnd, DID_ES_UPPERCASE, 0);
    }
}

/************************************************************************
* ListBoxStylesDlgProc
*
* Dialog procedure for list boxes.
*
************************************************************************/

DIALOGPROC ListBoxStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            CheckStyleBoxes(hwnd, npcStyles->pwcd->iClass,
                    npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            EnableListBoxStyles(hwnd, 0);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_LBS_STANDARD:
                case DID_LBS_NOTIFY:
                case DID_LBS_SORT:
                case DID_WS_VSCROLL:
                case DID_WS_BORDER:
                case DID_LBS_MULTIPLESEL:
                case DID_LBS_EXTENDEDSEL:
                case DID_LBS_OWNERDRAWFIXED:
                case DID_LBS_OWNERDRAWVARIABLE:
                case DID_LBS_NODATA:
                case DID_LBS_HASSTRINGS:
                    if (HIWORD(wParam) == BN_CLICKED)
                        EnableListBoxStyles(hwnd,
                                LOWORD(wParam));

                    return TRUE;

                case IDOK:
                    QueryCheckedStyles(hwnd, npcStyles->pwcd->iClass,
                            &flStyleNew);
                    QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);
                    EndDialog(hwnd, IDOK);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* EnableListBoxStyles
*
* Checks/unchecks, disables/enables various checkboxes that are
* mutually exclusive and/or dependant for the List Box Styles dialog.
*
* Arguments:
*   HWND hwnd  - Dialog window handle.
*   INT idCtrl - ID of the control that was clicked on.
*
************************************************************************/

STATICFN VOID EnableListBoxStyles(
    HWND hwnd,
    INT idCtrl)
{
    WORD fCheckState;
    BOOL fFixedChecked;
    BOOL fVariableChecked;

    fFixedChecked = IsDlgButtonChecked(hwnd, DID_LBS_OWNERDRAWFIXED);
    fVariableChecked = IsDlgButtonChecked(hwnd, DID_LBS_OWNERDRAWVARIABLE);

    if (fFixedChecked || fVariableChecked) {
        EnableWindow(GetDlgItem(hwnd, DID_LBS_HASSTRINGS), TRUE);
    }
    else {
        EnableWindow(GetDlgItem(hwnd, DID_LBS_HASSTRINGS), FALSE);
        CheckDlgButton(hwnd, DID_LBS_HASSTRINGS, 0);
    }

    EnableWindow(GetDlgItem(hwnd, DID_LBS_NODATA), fFixedChecked);

    switch (idCtrl) {
        case DID_LBS_STANDARD:
            fCheckState = (WORD)(IsDlgButtonChecked(hwnd, DID_LBS_STANDARD)
                    ? 1 : 0);
            CheckDlgButton(hwnd, DID_LBS_NOTIFY, fCheckState);
            CheckDlgButton(hwnd, DID_LBS_SORT, fCheckState);
            CheckDlgButton(hwnd, DID_WS_VSCROLL, fCheckState);
            CheckDlgButton(hwnd, DID_WS_BORDER, fCheckState);

            if (fCheckState)
                CheckDlgButton(hwnd, DID_LBS_NODATA, 0);

            break;

        case DID_LBS_OWNERDRAWFIXED:
            if (fFixedChecked)
                CheckDlgButton(hwnd, DID_LBS_OWNERDRAWVARIABLE, 0);
            else
                CheckDlgButton(hwnd, DID_LBS_NODATA, 0);

            break;

        case DID_LBS_OWNERDRAWVARIABLE:
            if (fVariableChecked) {
                CheckDlgButton(hwnd, DID_LBS_OWNERDRAWFIXED, 0);
                CheckDlgButton(hwnd, DID_LBS_NODATA, 0);
                EnableWindow(GetDlgItem(hwnd, DID_LBS_NODATA), FALSE);
            }

            break;

        case DID_LBS_MULTIPLESEL:
            if (IsDlgButtonChecked(hwnd, DID_LBS_MULTIPLESEL))
                CheckDlgButton(hwnd, DID_LBS_EXTENDEDSEL, 0);

            break;

        case DID_LBS_EXTENDEDSEL:
            if (IsDlgButtonChecked(hwnd, DID_LBS_EXTENDEDSEL))
                CheckDlgButton(hwnd, DID_LBS_MULTIPLESEL, 0);

            break;

        case DID_LBS_NODATA:
            if (IsDlgButtonChecked(hwnd, DID_LBS_NODATA)) {
                CheckDlgButton(hwnd, DID_LBS_SORT, 0);
                CheckDlgButton(hwnd, DID_LBS_HASSTRINGS, 0);
                CheckDlgButton(hwnd, DID_LBS_STANDARD, 0);
            }

            break;

        case DID_LBS_HASSTRINGS:
            if (IsDlgButtonChecked(hwnd, DID_LBS_HASSTRINGS))
                CheckDlgButton(hwnd, DID_LBS_NODATA, 0);

            break;

        default:
            if (!IsDlgButtonChecked(hwnd, DID_LBS_NOTIFY) ||
                    !IsDlgButtonChecked(hwnd, DID_LBS_SORT) ||
                    !IsDlgButtonChecked(hwnd, DID_WS_VSCROLL) ||
                    !IsDlgButtonChecked(hwnd, DID_WS_BORDER))
                fCheckState = 0;
            else
                fCheckState = 1;

            CheckDlgButton(hwnd, DID_LBS_STANDARD, fCheckState);

            if (IsDlgButtonChecked(hwnd, DID_LBS_SORT) ||

IsDlgButtonChecked(hwnd, DID_LBS_HASSTRINGS))
                CheckDlgButton(hwnd, DID_LBS_NODATA, 0);

            break;
    }
}

/************************************************************************
* CustomStylesDlgProc
*
* Dialog procedure for custom controls.
*
************************************************************************/

DIALOGPROC CustomStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            SetDlgItemText(hwnd, DID_CUSTOMSTYLESCLASS,
                    npcStyles->pwcd->pszClass);
            SendDlgItemMessage(hwnd, DID_CUSTOMSTYLESSTYLES, EM_LIMITTEXT,
                    CCHHEXLONGMAX, 0L);
            SetCustomStylesField(hwnd, npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_CUSTOMSTYLESSTYLES:
                    if (HIWORD(wParam) == EN_CHANGE) {
                        flStyleNew = GetCustomStylesField(hwnd);
                        CheckStyleBoxes(hwnd, IC_WINDOW, flStyleNew);
                    }

                    break;

                case DID_WS_VISIBLE:
                case DID_WS_DISABLED:
                case DID_WS_GROUP:
                case DID_WS_TABSTOP:
                    if (HIWORD(wParam) == BN_CLICKED) {
                        flStyleNew = GetCustomStylesField(hwnd);
                        QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);
                        SetCustomStylesField(hwnd, flStyleNew);
                    }

                    break;

                case IDOK:
                    flStyleNew = GetCustomStylesField(hwnd);

                    EndDialog(hwnd, IDOK);

                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* SetCustomStylesField
*
* Sets the style bits in a custom control.
*
* Arguments:
*   HWND hwnd - handle to the custom control
*   DWORD flStyle - style of the custom control
*
************************************************************************/

STATICFN VOID SetCustomStylesField(
    HWND hwnd,
    DWORD flStyle)
{
    TCHAR szBuf[32];

    wsprintf(szBuf, L"%#.8lx", flStyle);
    SetDlgItemText(hwnd, DID_CUSTOMSTYLESSTYLES, szBuf);
}

/************************************************************************
* GetCustomStylesField
*
* Gets the style bits of a custom control.
*
* Arguments:
*   HWND hwnd - handle to the custom control.
*
* Returns:
*
* The style bits specified for the custom control.
*
************************************************************************/

STATICFN DWORD GetCustomStylesField(
    HWND hwnd)
{
    TCHAR szBuf[CCHTEXTMAX];

    GetDlgItemText(hwnd, DID_CUSTOMSTYLESSTYLES, szBuf, CCHTEXTMAX);

    return valtoi(szBuf);
}

/************************************************************************
* DialogStylesDlgProc
*
* Dialog procedure for a dialog box.
*
************************************************************************/

DIALOGPROC DialogStylesDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    DWORD flResFlagsNew;
    INT nPointSize;
    INT iLang;
    INT iSubLang;
    TCHAR szFontName[LF_FACESIZE];
    INT nIndex;

    switch (msg) {
        case WM_INITDIALOG:
            CheckStyleBoxes(hwnd, IC_RESFLAGS, (DWORD)diNew.fResFlags);
            CheckStyleBoxes(hwnd, IC_DIALOG, npcStyles->flStyle);
            CheckStyleBoxes(hwnd, IC_WINDOW, npcStyles->flStyle);

            if (IsDlgButtonChecked(hwnd, DID_WS_CAPTION)) {
                CheckDlgButton(hwnd, DID_WS_BORDER, 1);
                CheckDlgButton(hwnd, DID_WS_DLGFRAME, 1);
            }

            FillFontNameCombo(hwnd);
            FillLanguageCombo(hwnd);

            if (IsOrd(diNew.pszClass))
                SetDlgItemInt(hwnd, DID_DLGSTYLECLASS,
                        OrdID(diNew.pszClass), FALSE);
            else
                SetDlgItemText(hwnd, DID_DLGSTYLECLASS, diNew.pszClass);

            if (IsOrd(diNew.pszMenu))
                SetDlgItemInt(hwnd, DID_DLGSTYLEMENU,
                        OrdID(diNew.pszMenu), FALSE);
            else
                SetDlgItemText(hwnd, DID_DLGSTYLEMENU, diNew.pszMenu);

            EnableDialogStyles(hwnd, 0);

            CenterWindow(hwnd);

            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_WS_BORDER:
                case DID_WS_DLGFRAME:
                case DID_WS_CAPTION:
                case DID_WS_POPUP:
                case DID_WS_CHILD:
                    if (HIWORD(wParam) == BN_CLICKED)
                        EnableDialogStyles(hwnd,
                                LOWORD(wParam));

                    return TRUE;

                case DID_DLGSTYLEFONTNAME:
                    /*
                     * Did the font name combo change?
                     */
                    if (HIWORD(wParam) == CBN_EDITCHANGE ||
                            HIWORD(wParam) == CBN_SELCHANGE) {
                        /*
                         * Get the font name and begin looking for it.
                         */
                        if (HIWORD(wParam) == CBN_EDITCHANGE) {
                            /*
                             * The edit field was typed into.  Get the
                             * new text from there.
                             */
                            GetDlgItemText(hwnd, DID_DLGSTYLEFONTNAME,
                                    szFontName, LF_FACESIZE);
                        }
                        else {
                            /*
                             * A new string was selected from the list
                             * box.  Get it from the list box, because
                             * at this point the new text is not yet set
                             * into the edit control!
                             */
                            nIndex = (INT)SendDlgItemMessage(hwnd,
                                    DID_DLGSTYLEFONTNAME, CB_GETCURSEL, 0, 0L);

                            if (nIndex != CB_ERR)
                                SendDlgItemMessage(hwnd,
                                        DID_DLGSTYLEFONTNAME, CB_GETLBTEXT,
                                        nIndex, (DWORD)szFontName);
                            else
                                *szFontName = CHAR_NULL;
                        }

                        FillPointSizeCombo(hwnd, szFontName);
                    }

                    return TRUE;

                case DID_DLGSTYLELANG:
                    /*
                     * Did the language combo change?
                     */
                    if (HIWORD(wParam) == CBN_SELCHANGE) {
                        nIndex = (INT)SendDlgItemMessage(hwnd,
                                DID_DLGSTYLELANG, CB_GETCURSEL, 0, 0L);
                        iLang = (INT)SendDlgItemMessage(hwnd,
                                DID_DLGSTYLELANG, CB_GETITEMDATA, nIndex, 0);
                        FillSubLanguageCombo(hwnd, iLang);
                    }

                    return TRUE;

                case IDOK:
                    /*
                     * If they have entered a font name and an empty
                     * or zero point size, display an error.
                     */
                    nPointSize = GetDlgItemInt(
                            hwnd, DID_DLGSTYLEPOINTSIZE, NULL, FALSE);
                    if (!nPointSize &&
                            SendDlgItemMessage(hwnd,
                            DID_DLGSTYLEFONTNAME, WM_GETTEXTLENGTH, 0, 0L)) {
                        Message(MSG_ZEROPOINTSIZE);
                        SetFocus(GetDlgItem(hwnd, DID_DLGSTYLEPOINTSIZE));
                        return TRUE;
                    }

                    GetDlgItemText(hwnd, DID_DLGSTYLEFONTNAME,
                            diNew.szFontName, LF_FACESIZE);
                    diNew.nPointSize = nPointSize;

                    /*
                     * Get the Language.
                     */
                    nIndex = (INT)SendDlgItemMessage(hwnd,
                            DID_DLGSTYLELANG, CB_GETCURSEL, 0, 0L);
                    iLang = (INT)SendDlgItemMessage(hwnd,
                            DID_DLGSTYLELANG, CB_GETITEMDATA,
                            nIndex, 0);
                    nIndex = (INT)SendDlgItemMessage(hwnd,
                            DID_DLGSTYLESUBLANG, CB_GETCURSEL, 0, 0L);
                    iSubLang = (INT)SendDlgItemMessage(hwnd,
                            DID_DLGSTYLESUBLANG, CB_GETITEMDATA,
                            nIndex, 0);
                    diNew.wLanguage = MAKELANGID(gaLangTable[iLang].wPrimary,
                            gaLangTable[iLang].asl[iSubLang].wSubLang);

                    /*
                     * Get the resource flags.  We need to use a temporary
                     * long variable because QueryCheckedStyles requires
                     * a long.
                     */
                    flResFlagsNew = diNew.fResFlags;
                    QueryCheckedStyles(hwnd, IC_RESFLAGS, &flResFlagsNew);
                    diNew.fResFlags = (WORD)flResFlagsNew;

                    QueryCheckedStyles(hwnd, IC_DIALOG, &flStyleNew);
                    QueryCheckedStyles(hwnd, IC_WINDOW, &flStyleNew);

                    /*
                     * Set the DS_SETFONT style, if they specified
                     * a font.
                     */
                    if (*diNew.szFontName)
                        flStyleNew |= DS_SETFONT;
                    else
                        flStyleNew &= ~DS_SETFONT;

                    GetDlgItemText(hwnd, DID_DLGSTYLECLASS,
                            diNew.pszClass, CCHTEXTMAX);

                    /*
                     * Convert the class to an ordinal, if necessary.
                     */
                    StrToNameOrd(diNew.pszClass, FALSE);

                    GetDlgItemText(hwnd, DID_DLGSTYLEMENU,
                            diNew.pszMenu, CCHTEXTMAX);

                    /*
                     * Convert the menu name to an ordinal, if necessary.
                     */
                    StrToNameOrd(diNew.pszMenu, FALSE);

                    /*
                     * If they just removed the caption style,
                     * clear the dialog's caption text at the
                     * same time.
                     */
                    if ((npcStyles->flStyle & WS_CAPTION) == WS_CAPTION &&
                            (flStyleNew & WS_CAPTION) != WS_CAPTION)
                        *pszTextNew = CHAR_NULL;

                    EndDialog(hwnd, IDOK);
                    return TRUE;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    return TRUE;

                case IDHELP:
                    StylesHelp();
                    break;
            }

            return FALSE;

        default:
            return FALSE;
    }
}

/************************************************************************
* EnableDialogStyles
*
* Checks and unchecks various checkboxes that are mutually exclusive
* for the Dialog Styles dialog.
*
* Arguments:
*   HWND hwnd  - Dialog window handle.
*   INT idCtrl - ID of the control that was clicked on.
*
************************************************************************/

STATICFN VOID EnableDialogStyles(
    HWND hwnd,
    INT idCtrl)
{
    switch (idCtrl) {
        case DID_WS_CAPTION:
            if (IsDlgButtonChecked(hwnd, DID_WS_CAPTION)) {
                CheckDlgButton(hwnd, DID_WS_BORDER, 1);
                CheckDlgButton(hwnd, DID_WS_DLGFRAME, 1);
            }
            else {
                CheckDlgButton(hwnd, DID_WS_BORDER, 0);
                CheckDlgButton(hwnd, DID_WS_DLGFRAME, 0);
            }

            break;

        case DID_WS_BORDER:
        case DID_WS_DLGFRAME:
            if (IsDlgButtonChecked(hwnd, DID_WS_BORDER) &&
                    IsDlgButtonChecked(hwnd, DID_WS_DLGFRAME))
                CheckDlgButton(hwnd, DID_WS_CAPTION, 1);
            else
                CheckDlgButton(hwnd, DID_WS_CAPTION, 0);

            break;

        case DID_WS_CHILD:
            if (IsDlgButtonChecked(hwnd, DID_WS_CHILD))
                CheckDlgButton(hwnd, DID_WS_POPUP, 0);

            break;

        case DID_WS_POPUP:
            if (IsDlgButtonChecked(hwnd, DID_WS_POPUP))
                CheckDlgButton(hwnd, DID_WS_CHILD, 0);

            break;
    }
}

/************************************************************************
* FillFontNameCombo
*
* Fills combo box with available fonts for dialog.
*
* Arguments:
*   HWND hwndDlg - Dialog window handle.
*
************************************************************************/

STATICFN VOID FillFontNameCombo(
    HWND hwndDlg)
{
    HDC hDC;
    HWND hwndCombo;
    TCHAR szName1[LF_FACESIZE];
    TCHAR szName2[LF_FACESIZE];
    LPTSTR pszName;
    LPTSTR pszNameLast;
    LPTSTR pszNameTemp;
    INT iIndex;
    INT iItems;

    hwndCombo = GetDlgItem(hwndDlg, DID_DLGSTYLEFONTNAME);

    if (hDC = GetDC(ghwndMain)) {
        EnumFonts(hDC, NULL, (FONTENUMPROC) FontNameEnumFunc, (LPARAM)&hwndCombo);
        ReleaseDC(ghwndMain, hDC);
    }

    /*
     * Strip out any duplicate names in the combobox.  This routine
     * relies on the items being sorted first.
     */
    iItems = (INT)SendMessage(hwndCombo, CB_GETCOUNT, 0, 0);
    *szName1 = CHAR_NULL;
    *szName2 = CHAR_NULL;
    pszName = szName1;
    pszNameLast = szName2;
    for (iIndex = 0; iIndex < iItems;) {
        /*
         * Get the text of the next item.
         */
        SendMessage(hwndCombo, CB_GETLBTEXT, iIndex, (DWORD)pszName);

        /*
         * If it matches the previous item, delete it.  Otherwise,
         * flip the buffers to save the current items text and
         * go on to the next item.
         */
        if (lstrcmp(pszName, pszNameLast) == 0) {
            SendMessage(hwndCombo, CB_DELETESTRING, iIndex, 0);
            iItems--;
        }
        else {
            pszNameTemp = pszNameLast;
            pszNameLast = pszName;
            pszName = pszNameTemp;
            iIndex++;
        }
    }

    /*
     * Initialize the font fields.  The order the fields are set
     * is important, because setting the face name clears out the
     * point size combo.
     */
    SetDlgItemText(hwndDlg, DID_DLGSTYLEFONTNAME, diNew.szFontName);
    FillPointSizeCombo(hwndDlg, diNew.szFontName);
}

/************************************************************************
* FontNameEnumFunc
*
* Enumeration function that adds all the font face names to the
* Font Face Name combo box in the Dialog Styles dialog.
*
* Arguments:
*   LPLOGFONT lpLogFont - pointer to font structure
*   LPTEXTMETRIC lpTextMetric - pointer to textmetric struct for font
*   INT nFontType - type of font
*   LPVOID lpData - font data
*
************************************************************************/

BOOL APIENTRY FontNameEnumFunc(
    LPLOGFONT lpLogFont,
    LPTEXTMETRIC lpTextMetric,
    INT nFontType,
    LPVOID lpData)
{
    /*
     * Add this name to the combo box.
     */
    SendMessage(*((LPHWND)lpData), CB_ADDSTRING, 0,
            (DWORD)lpLogFont->lfFaceName);

    /*
     * Keep on going...
     */
    return TRUE;
}

/************************************************************************
* FillPointSizeCombo
*
* This function fills the Point Size combobox with the point sizes
* that are available for the given face name.  It should be called
* whenever the Font Name combobox is changed to keep them in sync.
*
* Arguments:
*   HWND hwndDlg        - Dialog window handle.
*   LPTSTR pszFaceName  - Face name for the selected font.
*
************************************************************************/

STATICFN VOID FillPointSizeCombo(
    HWND hwndDlg,
    LPTSTR pszFaceName)
{
    HDC hDC;
    HWND hwndCombo;

    hwndCombo = GetDlgItem(hwndDlg, DID_DLGSTYLEPOINTSIZE);
    SendMessage(hwndCombo, CB_RESETCONTENT, 0, 0L);

    if (*pszFaceName && (hDC = GetDC(ghwndMain))) {
        EnumFonts(hDC, pszFaceName, (FONTENUMPROC) PointSizeEnumFunc, (LPARAM)&hwndCombo);
        ReleaseDC(ghwndMain, hDC);
    }

    /*
     * Select a default one.  This is the point size that is currently
     * selected if the face name is the current one, or else it is the
     * first point size in the list.
     */
    if (gcd.fFontSpecified && lstrcmp(pszFaceName, gcd.di.szFontName) == 0)
        SetDlgItemInt(hwndDlg, DID_DLGSTYLEPOINTSIZE, gcd.di.nPointSize, FALSE);
    else
        SendDlgItemMessage(hwndDlg, DID_DLGSTYLEPOINTSIZE,
                CB_SETCURSEL, 0, 0L);
}

/************************************************************************
* PointSizeEnumFunc
*
* Enumeration function that adds all the point sizes to the
* Pt. Size combo box in the Dialog Styles dialog.
*
* Arguments:
*   LPLOGFONT lpLogFont - pointer to font structure
*   LPTEXTMETRIC lpTextMetric - pointer to textmetric struct for font
*   INT nFontType - type of font
*   LPVOID lpData - font data
*
************************************************************************/

BOOL APIENTRY PointSizeEnumFunc(
    LPLOGFONT lpLogFont,
    LPTEXTMETRIC lpTextMetric,
    INT nFontType,
    LPVOID lpData)
{
    HWND hwndCombo;
    INT nPointSize;

    hwndCombo = *((LPHWND)lpData);

    if (nFontType == RASTER_FONTTYPE) {
        /*
         * Convert the pixels to point size.  Note that because of the
         * definition of the tmHeight field, the tmInternalLeading has
         * to be subtracted from it before converting to get the proper
         * font point size.  This is done automatically by the Windows
         * CreateFont call if you pass in a nHeight parameter that is
         * negative, so be aware of this when doing the reverse calculation
         * to create a font of the proper height!
         */
        nPointSize = PixelsToPointSize(
                lpTextMetric->tmHeight - lpTextMetric->tmInternalLeading);

        AddToPointSizeCombo(hwndCombo, nPointSize);
    }
    else {
        /*
         * For scalable (TrueType, ATM or vector) fonts, add the
         * common point sizes.  This list was pulled out of the
         ( commdlg.dll Font dialog.
         */
        AddToPointSizeCombo(hwndCombo, 8);
        AddToPointSizeCombo(hwndCombo, 9);
        AddToPointSizeCombo(hwndCombo, 10);
        AddToPointSizeCombo(hwndCombo, 11);
        AddToPointSizeCombo(hwndCombo, 12);
        AddToPointSizeCombo(hwndCombo, 14);
        AddToPointSizeCombo(hwndCombo, 16);
        AddToPointSizeCombo(hwndCombo, 18);
        AddToPointSizeCombo(hwndCombo, 20);
        AddToPointSizeCombo(hwndCombo, 22);
        AddToPointSizeCombo(hwndCombo, 24);
        AddToPointSizeCombo(hwndCombo, 26);
        AddToPointSizeCombo(hwndCombo, 28);
        AddToPointSizeCombo(hwndCombo, 36);
        AddToPointSizeCombo(hwndCombo, 48);
        AddToPointSizeCombo(hwndCombo, 72);
    }

    /*
     * Keep on going...
     */
    return TRUE;
}

/************************************************************************
* AddToPointSizeCombo
*
* This function adds a point size to the point size combobox.
* It does not allow duplicate point sizes, and the sizes will
* be inserted in order.
*
* Arguments:
*   HWND hwndCombo - The combobox window handle.
*   INT nPointSize - The point size to add.
*
************************************************************************/

STATICFN VOID AddToPointSizeCombo(
    HWND hwndCombo,
    INT nPointSize)
{
    TCHAR szPointSize[31];
    INT nPoints2;
    INT iIndex;
    INT iIndexAdd;
    INT iItems;

    iItems = (INT)SendMessage(hwndCombo, CB_GETCOUNT, 0, 0);
    for (iIndex = 0, iIndexAdd = -1; iIndex < iItems; iIndex++) {
        nPoints2 = (INT)SendMessage(hwndCombo, CB_GETITEMDATA, iIndex, 0);

        if (nPoints2 == nPointSize) {
            /*
             * A duplicate was found.  Skip this one.
             */
            return;
        }
        else if (nPoints2 > nPointSize) {
            iIndexAdd = iIndex;
            break;
        }
    }

    /*
     * Add this point size to the combo box.
     */
    itoaw(nPointSize, szPointSize, 10);
    iIndex = (INT)SendMessage(hwndCombo, CB_INSERTSTRING,
            iIndexAdd, (DWORD)szPointSize);
    SendMessage(hwndCombo, CB_SETITEMDATA, iIndex, (DWORD)nPointSize);
}

/************************************************************************
* FillLanguageCombo
*
* This function fills the Language combobox with the known languages.
*
* Arguments:
*   HWND hwndDlg - Dialog window handle.
*
************************************************************************/

STATICFN VOID FillLanguageCombo(
    HWND hwndDlg)
{
    HWND hwndCombo;
    INT i;
    INT iIndex;
    INT iSel;
    INT iLang;
    WORD wPrimary;

    hwndCombo = GetDlgItem(hwndDlg, DID_DLGSTYLELANG);
    SendMessage(hwndCombo, CB_RESETCONTENT, 0, 0L);

    for (i = 0; i < gcLanguages; i++) {
        iIndex = (INT)SendMessage(hwndCombo, CB_ADDSTRING,
                0, (DWORD)ids(gaLangTable[i].idsLangDesc));
        SendMessage(hwndCombo, CB_SETITEMDATA, iIndex, (DWORD)i);
    }

    wPrimary = (WORD)PRIMARYLANGID(diNew.wLanguage);
    for (i = 0, iSel = 0; i < gcLanguages; i++) {
        iLang = (INT)SendMessage(hwndCombo, CB_GETITEMDATA, i, 0);

        if (gaLangTable[iLang].wPrimary == wPrimary) {
            iSel = i;
            break;
        }
    }

    SendMessage(hwndCombo, CB_SETCURSEL, iSel, 0L);

    FillSubLanguageCombo(hwndDlg,
            (INT)SendMessage(hwndCombo, CB_GETITEMDATA, iSel, 0));
}

/************************************************************************
* FillSubLanguageCombo
*
* This function fills the Sub-Language combobox with the sub-languages
* for the specified language.
*
* Arguments:
*   HWND hwndDlg - Dialog window handle.
*   INT iLang    - Index to the language in the language table.
*
************************************************************************/

STATICFN VOID FillSubLanguageCombo(
    HWND hwndDlg,
    INT iLang)
{
    HWND hwndCombo;
    INT i;
    INT iIndex;
    INT iSel = 0;
    WORD wSubLang;

    hwndCombo = GetDlgItem(hwndDlg, DID_DLGSTYLESUBLANG);
    SendMessage(hwndCombo, CB_RESETCONTENT, 0, 0L);

    for (i = 0; i < gaLangTable[iLang].cSubLangs; i++) {
        iIndex = (INT)SendMessage(hwndCombo, CB_ADDSTRING, 0,
                (DWORD)ids(gaLangTable[iLang].asl[i].idsSubLangDesc));
        SendMessage(hwndCombo, CB_SETITEMDATA, iIndex, (DWORD)i);
    }

    /*
     * Is this the language set for the dialog?  If so, find the
     * sublanguage and make that the default.
     */
    if (gaLangTable[iLang].wPrimary == (WORD)PRIMARYLANGID(diNew.wLanguage)) {
        wSubLang = SUBLANGID(diNew.wLanguage);
        for (i = 0; i < gaLangTable[iLang].cSubLangs; i++) {
            iIndex = (INT)SendMessage(hwndCombo, CB_GETITEMDATA, i, 0);
            if (wSubLang == gaLangTable[iLang].asl[iIndex].wSubLang) {
                iSel = i;
                break;
            }
        }
    }

    SendMessage(hwndCombo, CB_SETCURSEL, iSel, 0L);
}

/************************************************************************
* CheckStyleBoxes
*
* This function takes the given style and checks the appropriate
* check boxes and radio buttons in the styles dialog.  The iClass
* determines the lookup table to use.
*
* Arguments:
*   HWND hwnd     - Dialog window handle.
*   INT iClass    - Control class (determines the style lookup table).
*   DWORD flStyle - Style of the control.
*
************************************************************************/

STATICFN VOID CheckStyleBoxes(
    HWND hwnd,
    INT iClass,
    DWORD flStyle)
{
    register INT i;
    PCLASSSTYLE pcs;
    HWND hwndControl;
    DWORD flStyleMask;

    i = acsd[iClass].cClassStyles;
    pcs = acsd[iClass].pacs;

    while (i--) {
        /*
         * Is there a DID_* defined for this style?
         */
        if (pcs->idControl) {
            /*
             * Does the dialog have a control with this id?
             */
            if (hwndControl = GetDlgItem(hwnd, pcs->idControl)) {
                flStyleMask =
                        pcs->flStyleMask ? pcs->flStyleMask : pcs->flStyle;

                /*
                 * If there is a match, check the box.  Otherwise,
                 * uncheck it.
                 */
                SendMessage(hwndControl, BM_SETCHECK,
                        ((flStyle & flStyleMask) == pcs->flStyle) ? 1 : 0,
                        0L);
            }
        }

        pcs++;
    }
}

/************************************************************************
* QueryCheckedStyles
*
* This function returns the new style that the user has selected from
* dialog.  It reads all the checkboxes and builds up the style.
* Upon entry, the DWORD that is at pflStyle should be set to the
* original style for the control.  Chosen bits will be masked off
* and set as appropriate.  This allows bits that are not settable
* from within this styles dialog to be left untouched.
*
* Arguments:
*   HWND hwnd       - Dialog window handle.
*   INT iClass      - Control class (determines the style lookup table).
*   DWORD *pflStyle - Where to return the style of the control.  What
*                     this points to should initially have the original
*                     styles of the control.
*
************************************************************************/

STATICFN VOID QueryCheckedStyles(
    HWND hwnd,
    INT iClass,
    DWORD *pflStyle)
{
    register INT i;
    PCLASSSTYLE pcs;
    HWND hwndControl;
    DWORD flStyleMask;
    DWORD flStyle;

    /*
     * The first step is to strip off all bits that may be changed by
     * the current dialog.
     */
    flStyle = *pflStyle;
    i = acsd[iClass].cClassStyles;
    pcs = acsd[iClass].pacs;
    while (i--) {
        /*
         * Is this a style that is settable by a dialog, and does the
         * current dialog have this style control?
         */
        if (pcs->idControl && GetDlgItem(hwnd, pcs->idControl)) {
            flStyleMask =
                    pcs->flStyleMask ? pcs->flStyleMask : pcs->flStyle;

            /*
             * Strip off all bits in the mask for this style.
             */
            flStyle &= ~flStyleMask;
        }

        pcs++;
    }

    /*
     * Now we go through all bits that may be set and set any that the
     * user has selected.
     */
    i = acsd[iClass].cClassStyles;
    pcs = acsd[iClass].pacs;
    while (i--) {
        if (pcs->idControl &&
                (hwndControl = GetDlgItem(hwnd, pcs->idControl))) {
            if (SendMessage(hwndControl, BM_GETCHECK, 0, 0L))
                flStyle |= pcs->flStyle;
        }

        pcs++;
    }

    *pflStyle = flStyle;
}

/************************************************************************
* StylesHelp
*
* This function shows the appropriate help context from any of the
* styles dialogs.  It uses the type of control in npcStyles to
* determine what help to show.
*
************************************************************************/

STATICFN VOID StylesHelp(VOID)
{
    WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
            npcStyles->pwcd->HelpContext);
}


TOOLBOX.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 Header *******************************
* Module Name: toolbox.c
*
* Contains routines that handle the toolbox.
*
* Functions:
*
*    ToolboxShow()
*    ToolboxOnTop()
*    ToolboxWndProc()
*    ToolBtnWndProc()
*    ToolboxSelectTool()
*    ToolboxCreate()
*    ToolboxDrawBitmap()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

#include "dialogs.h"

#define TOOLBOXMARGIN   2       // Pixels around the buttons in the Toolbox.
#define TOOLBOXCOLUMNS  2       // Columns in the Toolbox.

/*
 * Style of the toolbox window.
 */
#define TOOLBOXSTYLE    (WS_POPUP | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU)

STATICFN VOID ToolboxCreate(VOID);
STATICFN VOID ToolboxDrawBitmap(HDC hDC, INT type);

/*
 * Dimensions of a tool button bitmap.
 */
static INT cxToolBtn;
static INT cyToolBtn;

/****************************************************************************
* ToolboxShow
*
* This function shows or hides the toolbox window.  It will create
* the Toolbox if necessary.
*
* Arguments:
*   BOOL fShow - whether to show or hide the toolbox window.
*
****************************************************************************/

VOID ToolboxShow(
    BOOL fShow)
{
    if (fShow) {
        /*
         * Don't allow a toolbox to be shown in Translate mode.
         */
        if (gfTranslateMode)
            return;

        /*
         * Create it if it doesn't exist yet.
         */
        if (!ghwndToolbox)
            ToolboxCreate();

        if (ghwndToolbox)
            ShowWindow(ghwndToolbox, SW_SHOWNA);
    }
    else {
        if (ghwndToolbox)
            ShowWindow(ghwndToolbox, SW_HIDE);
    }
}

/****************************************************************************
* ToolboxOnTop
*
* This function positions the toolbox window on top.  It needs to be
* called any time that a new dialog window is created to be sure the
* dialog does not cover the toolbox.
*
* It can be called even if the toolbox is not created yet (it will
* be a noop in that case).
*
****************************************************************************/

VOID ToolboxOnTop(VOID)
{
   if (ghwndToolbox) {
       SetWindowPos(ghwndToolbox, NULL, 0, 0, 0, 0,
               SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
   }
}

/****************************************************************************
* ToolboxCreate
*
* This function creates the toolbox window.
*
****************************************************************************/

STATICFN VOID ToolboxCreate(VOID)
{
    BITMAP bmp;
    INT i;
    INT x;
    INT y;
    INT cx;
    INT cy;
    INT cxDummy;
    INT cyDummy;
    RECT rc;
    RECT rcSubClient;
    BOOL fMaximized;

    /*
     * Load the bitmaps.
     */
    if (!(ghbmPointerToolUp = LoadBitmap(ghInst,
            MAKEINTRESOURCE(IDBM_TUPOINTR))) ||
            !(ghbmPointerToolDown = LoadBitmap(ghInst,
            MAKEINTRESOURCE(IDBM_TDPOINTR))))
        return;

    for (i = 0; i < CCONTROLS; i++) {
        if (!(awcd[i].hbmToolBtnUp = LoadBitmap(ghInst,
                MAKEINTRESOURCE(awcd[i].idbmToolBtnUp))))
            return;

        if (!(awcd[i].hbmToolBtnDown = LoadBitmap(ghInst,
                MAKEINTRESOURCE(awcd[i].idbmToolBtnDown))))
            return;
    }

    /*
     * Get the dimensions of the tool button bitmaps.
     */
    GetObject(awcd[0].hbmToolBtnUp, sizeof(BITMAP), &bmp);
    cxToolBtn = bmp.bmWidth;
    cyToolBtn = bmp.bmHeight;

    /*
     * Calculate the required window size for the client area
     * size we want.  The size leaves room for a margin, and
     * assumes that adjacent buttons overlap their borders by
     * one pixel.
     */
    rc.left = 0;
    rc.top = 0;
    rc.right = TOOLBOXMARGIN + ((cxToolBtn - 1) * 2) + 1 + TOOLBOXMARGIN;
    rc.bottom = TOOLBOXMARGIN + ((cyToolBtn - 1) *
            ((CCONTROLS / 2) + 1)) + 1 + TOOLBOXMARGIN;
    AdjustWindowRect(&rc, TOOLBOXSTYLE, FALSE);
    cx = rc.right - rc.left;
    cy = rc.bottom - rc.top;

    /*
     * Get the saved position of the Toolbox.  Note that we throw away
     * the size fields, because we just calculated the required size.
     */
    if (!ReadWindowPos(szTBPos, &x, &y, &cxDummy, &cyDummy, &fMaximized)) {
        /*
         * The previous position of the Toolbox  couldn't be found.
         * Position the toolbox to the upper right corner of the
         * "client" area of the editor, but make sure it is completely
         * visible.
         */
        GetWindowRect(ghwndSubClient, &rcSubClient);
        x = rcSubClient.right - cx - (2 * TOOLBOXMARGIN);
        y = rcSubClient.top + (2 * TOOLBOXMARGIN);
        SetRect(&rc, x, y, x + cx, y + cy);
        FitRectToScreen(&rc);
        x = rc.left;
        y = rc.top;
    }

    /*
     * Create the toolbox window.
     */
    if (!(ghwndToolbox = CreateWindow(szToolboxClass, NULL, TOOLBOXSTYLE,
            x, y, cx, cy, ghwndMain, NULL, ghInst, NULL)))
        return;

    /*
     * Create the Pointer (W_NOTHING) button.
     */
    CreateWindow(szToolBtnClass, NULL,
            WS_CHILD | WS_VISIBLE,
            TOOLBOXMARGIN, TOOLBOXMARGIN, (cxToolBtn * 2) - 1, cyToolBtn,
            ghwndToolbox, (HMENU)W_NOTHING, ghInst, NULL);

    /*
     * Create the other buttons.
     */
    x = TOOLBOXMARGIN;
    y = TOOLBOXMARGIN + cyToolBtn - 1;
    for (i = 0; i < CCONTROLS; i++) {
        CreateWindow(szToolBtnClass, NULL,
                WS_CHILD | WS_VISIBLE,
                x, y, cxToolBtn, cyToolBtn,
                ghwndToolbox, (HMENU)i, ghInst, NULL);

        if (x == TOOLBOXMARGIN) {
            x += cxToolBtn - 1;
        }
        else {
            x = TOOLBOXMARGIN;
            y += cyToolBtn - 1;
        }
    }
}

/****************************************************************************
* ToolboxWndProc
*
* This is the window procedure for the toolbox window.
*
****************************************************************************/

WINDOWPROC ToolboxWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_CREATE:
            {
                HMENU hmenu = GetSystemMenu(hwnd, FALSE);

                RemoveMenu(hmenu, 7, MF_BYPOSITION);    // Second separator.
                RemoveMenu(hmenu, 5, MF_BYPOSITION);    // First separator.

                RemoveMenu(hmenu, SC_RESTORE, MF_BYCOMMAND);
                RemoveMenu(hmenu, SC_SIZE, MF_BYCOMMAND);
                RemoveMenu(hmenu, SC_MINIMIZE, MF_BYCOMMAND);
                RemoveMenu(hmenu, SC_MAXIMIZE, MF_BYCOMMAND);
                RemoveMenu(hmenu, SC_TASKLIST, MF_BYCOMMAND);
            }

            return 0;

        case WM_KEYDOWN:
            {
                INT iToolNext;

                switch (wParam) {
                    case VK_UP:
                        if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                (GetKeyState(VK_CONTROL) & 0x8000))
                            break;

                        /*
                         * Go up a row, but don't go beyond the top.
                         */
                        iToolNext = gCurTool - TOOLBOXCOLUMNS;
                        if (iToolNext < 0)
                            iToolNext = W_NOTHING;

                        ToolboxSelectTool(iToolNext, FALSE);

                        break;

                    case VK_DOWN:
                        if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                (GetKeyState(VK_CONTROL) & 0x8000))
                            break;

                        if (gCurTool == W_NOTHING) {
                            iToolNext = 0;
                        }
                        else {
                            /*
                             * Go down a row, but don't go beyond the bottom.
                             */
                            iToolNext = gCurTool + TOOLBOXCOLUMNS;
                            if (iToolNext >= CCONTROLS)
                                break;
                        }

                        ToolboxSelectTool(iToolNext, FALSE);

                        break;

                    case VK_LEFT:
                        if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                (GetKeyState(VK_CONTROL) & 0x8000))
                            break;

                        if (gCurTool == W_NOTHING ||
                                !(gCurTool % TOOLBOXCOLUMNS))
                            break;

                        /*
                         * Go left a column.
                         */
                        ToolboxSelectTool(gCurTool - 1, FALSE);

                        break;

                    case VK_RIGHT:
                        if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                (GetKeyState(VK_CONTROL) & 0x8000))
                            break;

                        if (gCurTool == W_NOTHING ||
                                (gCurTool % TOOLBOXCOLUMNS) ==
                                TOOLBOXCOLUMNS - 1)
                            break;

                        /*
                         * Go right a column.
                         */
                        ToolboxSelectTool(gCurTool + 1, FALSE);

                        break;

                    case VK_TAB:
                        if (GetKeyState(VK_CONTROL) & 0x8000)
                            break;

                        /*
                         * Is the shift key pressed also?
                         */
                        if (GetKeyState(VK_SHIFT) & 0x8000) {
                            if (gCurTool == W_NOTHING)
                                iToolNext = CCONTROLS - 1;
                            else if (gCurTool == 0)
                                iToolNext = W_NOTHING;
                            else
                                iToolNext = gCurTool - 1;
                        }
                        else {
                            if (gCurTool == W_NOTHING)
                                iToolNext = 0;
                            else if (gCurTool == CCONTROLS - 1)
                                iToolNext = W_NOTHING;
                            else
                                iToolNext = gCurTool + 1;
                        }

                        ToolboxSelectTool(iToolNext, FALSE);

                        break;

                    case VK_END:
                        if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                (GetKeyState(VK_CONTROL) & 0x8000))
                            break;

                        ToolboxSelectTool(CCONTROLS - 1, FALSE);

                        break;

                    case VK_HOME:
                    case VK_ESCAPE:
                        if ((GetKeyState(VK_SHIFT) & 0x8000) ||
                                (GetKeyState(VK_CONTROL) & 0x8000))
                            break;

                        ToolboxSelectTool(W_NOTHING, FALSE);

                        break;
                }
            }

            break;

        case WM_ACTIVATE:
            if (LOWORD(wParam))
                gidCurrentDlg = DID_TOOLBOX;

            break;

        case WM_CLOSE:
            /*
             * The user closed the toolbox from the system menu.
             * Hide the toolbox (we don't actually destroy it so
             * that it will appear in the same spot when they show
             * it again).
             */
            ToolboxShow(FALSE);
            gfShowToolbox = FALSE;
            break;

        case WM_DESTROY:
            {
                INT i;
                RECT rc;

                DeleteObject(ghbmPointerToolUp);
                ghbmPointerToolUp = NULL;
                DeleteObject(ghbmPointerToolDown);
                ghbmPointerToolDown = NULL;

                for (i = 0; i < CCONTROLS; i++) {
                    DeleteObject(awcd[i].hbmToolBtnUp);
                    awcd[i].hbmToolBtnUp = NULL;
                    DeleteObject(awcd[i].hbmToolBtnDown);
                    awcd[i].hbmToolBtnDown = NULL;
                }

                /*
                 * Save the position of the toolbox.
                 */
                GetWindowRect(hwnd, &rc);
                WriteWindowPos(&rc, FALSE, szTBPos);

                /*
                 * Null out the global window handle for the toolbox
                 * for safety's sake.
                 */
                ghwndToolbox = NULL;
            }

            break;

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

    return 0;
}

/****************************************************************************
* ToolBtnWndProc
*
* This is the window procedure for the buttons in the toolbox window.
*
****************************************************************************/

WINDOWPROC ToolBtnWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_LBUTTONDOWN:
            /*
             * Be sure any outstanding changes get applied
             * without errors.
             */
            if (!StatusApplyChanges())
                return TRUE;

            /*
             * Select the tool that was clicked on.  If the Ctrl
             * key is down, lock the tool also.
             */
            ToolboxSelectTool((UINT)GetWindowLong((hwnd), GWL_ID),
                    (GetKeyState(VK_CONTROL) & 0x8000) ? TRUE : FALSE);

            break;

        case WM_PAINT:
            {
                HDC hDC;
                PAINTSTRUCT ps;

                hDC = BeginPaint(hwnd, &ps);
                ToolboxDrawBitmap(hDC, (UINT)GetWindowLong((hwnd), GWL_ID));
                EndPaint(hwnd, &ps);
            }

            break;

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

    return 0;
}

/****************************************************************************
* ToolboxDrawBitmap
*
* Draws the current tool bitmap.
*
* Arguments:
*   HDC hDC - handle to the DC for the toolbox window.
*   INR type - type of bitmap to draw.
*
****************************************************************************/

STATICFN VOID ToolboxDrawBitmap(
    HDC hDC,
    INT type)
{
    HDC hMemDC;
    HBITMAP hbm;
    HBITMAP hbmOld;
    INT cxBitmap;

    if (type == W_NOTHING) {
        hbm = (type == gCurTool) ? ghbmPointerToolDown : ghbmPointerToolUp;

        /*
         * Note that the size of the Pointer tool is twice the width
         * of the other bitmaps, but less one pixel.  This is because
         * the other tools overlap their adjacent borders.
         */
        cxBitmap = (cxToolBtn * 2) - 1;
    }
    else {
        hbm = (type == gCurTool) ?
                awcd[type].hbmToolBtnDown : awcd[type].hbmToolBtnUp;
        cxBitmap = cxToolBtn;
    }

    /*
     * Draw the image.
     */
    hMemDC = CreateCompatibleDC(hDC);
    hbmOld = SelectObject(hMemDC, hbm);
    BitBlt(hDC, 0, 0, cxBitmap, cyToolBtn, hMemDC, 0, 0, SRCCOPY);
    SelectObject(hMemDC, hbmOld);
    DeleteDC(hMemDC);
}

/****************************************************************************
* ToolboxSelectTool
*
* This function selects a tool to be the current tool.
*
* Arguments:
*   INT type   - Type of control (one of the W_* defines).
*   BOOL fLock - TRUE if the tool should be locked down.
*
****************************************************************************/

VOID ToolboxSelectTool(
    INT type,
    BOOL fLock)
{
    PWINDOWCLASSDESC pwcd;

    if (gCurTool != type) {
        /*
         * Set the current wcd global for the current tool type.
         * This will point to the WINDOWCLASSDESC structure of the
         * current tool.  If the Custom tool was selected, the user
         * is asked which of the installed custom controls that they
         * really want.
         */
        if (type == W_CUSTOM) {
            /*
             * There are no custom controls installed.  Beep and
             * return without doing anything.
             */
            if (!gpclHead) {
                MessageBeep(0);
                return;
            }

            /*
             * If there are multiple custom controls installed,
             * ask the user which one they want.  Note that they
             * can press Cancel and return NULL!
             */
            if (gpclHead->pclNext) {
                if (!(pwcd = SelCustDialog()))
                    return;

                gpwcdCurTool = pwcd;
            }
            else {
                /*
                 * Since there is only one type of custom control
                 * installed, there is no need to ask the user
                 * which one they want.
                 */
                gpwcdCurTool = gpclHead->pwcd;
            }
        }
        else {
            gpwcdCurTool = (type == W_NOTHING) ? NULL : &awcd[type];
        }

        /*
         * Force the previous and current buttons to repaint.
         */
        if (ghwndToolbox) {
            InvalidateRect(GetDlgItem(ghwndToolbox, gCurTool), NULL, FALSE);
            InvalidateRect(GetDlgItem(ghwndToolbox, type), NULL, FALSE);
        }

        /*
         * Set the current tool type global.  This will be W_CUSTOM for
         * all custom controls.
         */
        gCurTool = type;
    }

    gfToolLocked = fLock;
}


UTIL.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 Header *******************************
* Module Name: util.c
*
* Contains miscellaneous utility functions for dlgedit.
*
* Functions:
*   MyAlloc()
*   MyRealloc()
*   MyFree()
*   IsValue()
*   HasBlanks()
*   valtoi()
*   axtoi()
*   Myitoa()
*   itoax()
*   IsUniqueID()
*   NextID()
*   Message()
*   ClientToScreenRect()
*   ScreenToClientRect()
*   DUToWinPoint()
*   WinToDUPoint()
*   DUToWinRect()
*   WinToDURect()
*   MapDlgClientPoint()
*   MapWindowPoint()
*   MyMapWindowRect()
*   GetChildRect()
*   CenterWindow()
*   FitRectToScreen()
*   ids()
*   PixelsToPointSize()
*   PointSizeToPixels()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"

#include <stdarg.h>
#include <ctype.h>

#define CBOVERHEAD      (sizeof(INT)+sizeof(INT)+sizeof(INT))
#define MEMSIGHEAD      0x1234
#define MEMSIGTAIL      0x5678

STATICFN BOOL IDUsedByCtrl(INT id);

/****************************************************************************
* MyAlloc
*
* Does a local alloc.
*
* Arguments:
*   INT cbAlloc - number of bytes to allocate.
*
* Returns:
*
* A pointer to the memory if succesful; otherwise, NULL.
*
****************************************************************************/

VOID *MyAlloc(
    INT cbAlloc)
{
    register HANDLE hMem;

    if (hMem = LocalAlloc(LMEM_FIXED, cbAlloc)) {
        return (VOID *)hMem;
    }
    else {
        MessageBeep(0);
        Message(MSG_OUTOFMEMORY);

        return NULL;
    }
}

/****************************************************************************
* MyRealloc
*
* reallocates memory and returns a pointer to the memory block.
*
****************************************************************************/

VOID *MyRealloc(
    VOID *npMem,
    INT cbNewAlloc)
{
    npMem = (VOID *)LocalReAlloc((HANDLE)npMem, cbNewAlloc, LMEM_MOVEABLE);

    if (!npMem) {
        MessageBeep(0);
        Message(MSG_OUTOFMEMORY);

        return NULL;
    }

    return npMem;
}

/****************************************************************************
* MyFree
*
* Frees the specified memory.
*
****************************************************************************/

VOID *MyFree(
    VOID *npMem)
{
    if (LocalFree((HANDLE)npMem)) {
        MessageBeep(0);
        Message(MSG_MEMERROR);

        return npMem;
    }

    return NULL;
}

/************************************************************************
* IsValue
*
* This function tells you if the string you give it represents a
* valid value or not.  For this purpose, a valid value can only
* have the ascii characters from '0' to '9' with possibly the
* first character being '-'.  Or be a Hex Number, with 0x preceeding
* it.
*
* Arguments:
*     LPTSTR pszValue  = The string to test.
*
* Returns:
*     (0 == 0) if szValue represents a value.
*     (c == 0) if szValue does not represent a value where c is
*               non-zero.
*
*
************************************************************************/

BOOL IsValue(
    LPTSTR pszValue)
{
    INT i;

    if (pszValue[0] == CHAR_0 &&
            (pszValue[1] == CHAR_X || pszValue[1] == CHAR_CAP_X)) {
        for (i = 2; iswxdigit(pszValue[i]); i++)
            ;
    }
    else {
        for (i = 0; iswdigit(pszValue[i]) ||
                (i == 0 && pszValue[i] == CHAR_MINUS); i++)
            ;
    }

    return (pszValue[i] == 0);
}

/************************************************************************
* HasBlanks
*
* This function returns TRUE if the given string has imbedded
* blanks in it.
*
* Arguments:
*   LPTSTR psz - String to check.
*
*
************************************************************************/

BOOL HasBlanks(
    LPTSTR psz)
{
    while (*psz)
        if (*psz == CHAR_SPACE)
            return TRUE;
        else
            psz = CharNext(psz);

    return FALSE;
}

/************************************************************************
* valtoi
*
* Takes a string and returns its integer representation.
* This function handles both hex ("0x1234") and decimal ("1234")
* strings transparently.
*
* Arguments:
*   LPTSTR pszValue = The string to convert.
*
*
************************************************************************/

INT valtoi(
    LPTSTR pszValue)
{
    return (pszValue[0] == CHAR_0 &&
            (pszValue[1] == CHAR_CAP_X || pszValue[1] == CHAR_X)) ?
            axtoi(&pszValue[2]) : awtoi(pszValue);
}

/************************************************************************
* axtoi
*
* This function converts a null terminated ascii string for a
* hex number to its integer value.  Should just be the number
* with no preceeding "0x" or trailing "H".  Garbage will result
* if there are non-hex digits in the string.  Hex digits are:
* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, a, b, c, d, e, f.
* Non-hex digits will be treated like '0'.
*
* Arguments:
*     LPTSTR pch  = The null terminated hex string.
*
* Returns:
*     The integer value represented by the given ascii string.
*
************************************************************************/

INT axtoi(
    LPTSTR pch)
{
    register TCHAR ch;
    register INT n = 0;

    while((ch = *pch++) != 0) {
        if (iswdigit(ch))
            ch -= CHAR_0;
        else if (ch >= CHAR_CAP_A && ch <= CHAR_CAP_F)
            ch += (TCHAR)(10 - CHAR_CAP_A);
        else if (ch >= CHAR_A && ch <= CHAR_F)
            ch += (TCHAR)(10 - CHAR_A);
        else
            ch = (TCHAR)0;

        n = 16 * n + ch;
    }

    return n;
}

/************************************************************************
* Myitoa
*
* This function converts a word to an ascii string.  It builds either
* a decimal string or a hex string, based on whether Hex Mode is on
* when it is called.
*
* Arguments:
*   INT n      - The number to convert.
*   LPTSTR psz - The buffer to put the string in, should have at least
*                17 bytes to take the string.
*
************************************************************************/

VOID Myitoa(
    INT n,
    LPTSTR psz)
{
    if (gfHexMode)
        itoax(n, psz);
    else
        itoaw(n, psz, 10);
}

/************************************************************************
* itoax
*
* This function converts an int, 'n', to an ascii string
* representing it as a 4 digit hex number with "0x" preceeding it.
*
* Arguments:
*   INT n          = The number to convert.
*   LPTSTR pszBuff = The buffer to put the string in, must
*                    be at least 7 characters long to take the string.
*
*
************************************************************************/

VOID itoax(
    INT n,
    LPTSTR pszBuff)
{
    INT i;
    INT j;

    pszBuff[0] = CHAR_0;
    pszBuff[1] = CHAR_X;

    for (i = 5; i > 1; i--) {
        j = n & 15;

        if (j > 9)
            pszBuff[i] = (TCHAR)(j + (CHAR_A - 10));
        else
            pszBuff[i] = (TCHAR)(j + CHAR_0);

        n = n >> 4;
    }

    pszBuff[6] = CHAR_NULL;
}

/************************************************************************
* NextID
*
* This function returns the next available id.
*
* For dialogs, it starts at 100 and increments by 100.  It will not
* return a value until it finds one that begins a range that is
* not used by any control in any of the dialogs in the res list.
* In other words, it is guaranteed that the number returned and
* the range of the next 99 numbers are not used by any control in
* any dialog in the current res file.
*
* When returning a new id for a control, it usually starts at the
* dialog base, but if there are any controls, it starts at one higher
* than the id of the last control in the dialog that does not have
* one of the special-cased ids (the unused id, IDOK or IDCANCEL).
* It will find the first available id above this.
*
* When returning a default id for a new label, it starts at the dialog
* base (or 100 if there is not a dialog being edited) and starts
* searching for the first available one.  It guarantees that the
* id returned is not used by any control in the current dialog, or
* any other label, or any control in the entire resource list.
*
* Arguments:
*   INT idType      - The type of id desired:
*                     NEXTID_DIALOG  = ID for a new dialog.
*                     NEXTID_CONTROL = ID for a new control.
*                     NEXTID_LABEL   = ID for a new label.
*   NPLABEL plHead  - The current label list to check for conflicts with.
*   INT idExclude   - An id that you specifically want to skip over.
*                     Set to zero if you don't care.
*
* Returns:
*     The "next" unused id value.
*
*
************************************************************************/

INT NextID(
    INT idType,
    NPLABEL plHead,
    INT idExclude)
{
    INT id;

    if (idType == NEXTID_CONTROL) {
        /*
         * Start at the base from the dialog plus one.  It is
         * assumed that this routine will not be called for an
         * id for a control if there is not a dialog being
         * edited first.
         */
        id = gcd.npc->id + 1;

        /*
         * Keep looping until an unused id is found.
         */
        while (!IsUniqueID(id) || FindID(id, plHead) || id == idExclude)
            id++;
    }
    else if (idType == NEXTID_DIALOG) {
        /*
         * Start at 100.
         */
        id = 100;

        /*
         * Keep looping by hundreds until an unused id is found.
         */
        while (!IsUniqueID(id) || FindID(id, plHead) || id == idExclude)
            id += 100;
    }
    else {
        /*
         * We are looking for a default id for a new label.  Start
         * at the dialog base, if there is a dialog being edited.
         */
        if (gfEditingDlg)
            id = gcd.npc->id + 1;
        else
            id = 100;

        /*
         * Keep looping until an unused id is found.  The id should
         * not be used by any control in the current dialog, any
         * other label already, or any control in the res file.
         */
        while (FindID(id, plHead) || FindIDInRes(id) || id == idExclude)
            id++;
    }

    /*
     * We found an unused one.  Return it.
     */
    return id;
}

/************************************************************************
* IDUsedByCtrl
*
* This function returns TRUE if the given ID is used by any control
* in the current dialog.  This also counts the text field of W_ICON
* controls, if they are ordinals.
*
* Arguments:
*   INT id = The ID to look for.
*
* Returns:
*   TRUE if the id is used, FALSE if not.
*
*
************************************************************************/

STATICFN BOOL IDUsedByCtrl(
    INT id)
{
    register NPCTYPE npc;

    for (npc = npcHead; npc; npc = npc->npcNext) {
        if (npc->id == id ||
                (npc->pwcd->iType == W_ICON &&
                npc->text &&
                IsOrd(npc->text) &&
                id == (INT)OrdID(npc->text)))
            return TRUE;
    }

    return FALSE;
}

/************************************************************************
* IsUniqueID
*
* This function returns TRUE if the given id is unique.  A unique
* id is either the special "unused" id value, or it is an id that
* is not already assigned to any other control in the current dialog
* and it is not assigned to any other dialog in the current res list.
*
* Note that this routine does NOT look for duplicates in the include
* file of this id, only for ids that have been used by other controls
* or dialogs already.
*
* Arguments:
*   INT id = The id to verify is unique.
*
* Returns:
*   TRUE if the id is "unique", FALSE if it is not.
*
*
************************************************************************/

BOOL IsUniqueID(
    INT id)
{
    ORDINAL ord;

    /*
     * If the id is the special unused id, it is considered unique.
     */
    if (id == IDUNUSED)
        return TRUE;

    /*
     * Not unique if another control in the dialog has the same id.
     */
    if (IDUsedByCtrl(id))
        return FALSE;

    /*
     * Not unique if another dialog has the same id.
     */
    WriteOrd(&ord, id);
    if (FindDialog((LPTSTR)&ord))
        return FALSE;

    return TRUE;
}

/************************************************************************
* Message
*
* This function puts up a message box with a string indexed by idMsg.
*
* Returns:
*     What MessageBox returns.
*
************************************************************************/

INT Message(
    INT idMsg,
    ...)
{
    va_list marker;
    INT RetCode;
    TCHAR szT[CCHTEXTMAX];
    BOOL fDisabledSave;

    va_start(marker, idMsg);
    wvsprintf(szT, ids(gamdMessages[idMsg].ids), marker);

    fDisabledSave = gfDisabled;
    gfDisabled = TRUE;
    RetCode = MessageBox(NULL, szT, ids(IDS_DLGEDIT),
            (WORD)(gamdMessages[idMsg].fMessageBox | MB_TASKMODAL));
    gfDisabled = fDisabledSave;

    va_end(marker);

    return RetCode;
}

/************************************************************************
* ClientToScreenRect
*
* This function converts the coordinates in a rectangle from points
* relative to the client area into points that are relative to the
* screen.
*
* Arguments:
*   HWND hwnd - Window handle for the conversion.
*   PRECT prc - Pointer to the rectangle to convert.
*
*
************************************************************************/

VOID ClientToScreenRect(
    HWND hwnd,
    PRECT prc)
{
    ClientToScreen(hwnd, (PPOINT)prc);
    ClientToScreen(hwnd, ((PPOINT)prc) + 1);
}

/************************************************************************
* ScreenToClientRect
*
* This function converts the coordinates in a rectangle from points
* relative to the screen into points that are relative to the given
* window's client area.
*
* Arguments:
*   HWND hwnd - Window handle for the conversion.
*   PRECT prc - Pointer to the rectangle to convert.
*
*
************************************************************************/

VOID ScreenToClientRect(
    HWND hwnd,
    PRECT prc)
{
    ScreenToClient(hwnd, (PPOINT)prc);
    ScreenToClient(hwnd, ((PPOINT)prc) + 1);
}

/************************************************************************
* DUToWinPoint
*
* This function converts the coordinates in the given point from
* dialog units (DU's) to window units for the current dialog.
*
* Arguments:
*   PPOINT ppt - Pointer to the point to convert.
*
*
************************************************************************/

VOID DUToWinPoint(
    PPOINT ppt)
{
    ppt->x = MulDiv(ppt->x, gcd.cxChar, 4);
    ppt->y = MulDiv(ppt->y, gcd.cyChar, 8);
}

/************************************************************************
* WinToDUPoint
*
* This function converts the coordinates in the given point from
* window points to dialog units (DU's) for the current dialog.
*
* Arguments:
*   PPOINT ppt - Pointer to the point to convert.
*
*
************************************************************************/

VOID WinToDUPoint(
    PPOINT ppt)
{
    ppt->x = MulDiv(ppt->x, 4, gcd.cxChar);
    ppt->y = MulDiv(ppt->y, 8, gcd.cyChar);
}

/************************************************************************
* DUToWinRect
*
* This function converts the coordinates in a rectangle from
* dialog units for the current dialog to window units.
*
* Arguments:
*   PRECT prc - Pointer to the rectangle to convert.
*
*
************************************************************************/

VOID DUToWinRect(
    PRECT prc)
{
    DUToWinPoint((PPOINT)prc);
    DUToWinPoint(((PPOINT)prc) + 1);
}

/************************************************************************
* WinToDURect
*
* This function converts the coordinates in a rectangle from
* window units to dialog units for the current dialog.
*
* Arguments:
*   PRECT prc - Pointer to the rectangle to convert.
*
*
************************************************************************/

VOID WinToDURect(
    PRECT prc)
{
    WinToDUPoint((PPOINT)prc);
    WinToDUPoint(((PPOINT)prc) + 1);
}

/************************************************************************
* MapDlgClientPoint
*
* This function converts client points to be relative to the window
* origin instead, or the other way around.  If fFromClient is TRUE,
* the point is considered to be relative to the client origin in
* the dialog, and will be converted to a point relative to the
* window origin instead.
*
* If fFromClient is FALSE, the point is considered to be relative
* to the window origin, and will be mapped to a point that is
* relative to the client origin.
*
* This function assumes that the global grcDlgClient has been
* previously calculated.  It should only be called to map points
* for the current dialog being edited (for which grcDlgClient has
* been calculated).
*
* Arguments:
*   PPOINT ppt       - Pointer to the point to convert.
*   BOOL fFromClient - TRUE if the point is relative to the client origin.
*
*
************************************************************************/

VOID MapDlgClientPoint(
    PPOINT ppt,
    BOOL fFromClient)
{
    if (fFromClient) {
        ppt->x += grcDlgClient.left;
        ppt->y += grcDlgClient.top;
    }
    else {
        ppt->x -= grcDlgClient.left;
        ppt->y -= grcDlgClient.top;
    }
}

/************************************************************************
* MapWindowPoint
*
* This function maps a point from one window to another.  The point
* given is in window coordinates (not client coordinates) and is
* mapped so that it is relative to the destination window.
*
* Arguments:
*   HWND hwndFrom   - Source window.
*   HWND hwndTo     - Destination window.
*   PPOINT ppt      - Pointer to the point to convert.
*
* Comments:
* In Win 3.1 (and NT) the MapWindowPoints call can be used and this one can be removed.
* It is only needed here for compatibility with Win 3.0.
*
************************************************************************/

VOID MapWindowPoint(
    HWND hwndFrom,
    HWND hwndTo,
    PPOINT ppt)
{
    RECT rcFrom;
    RECT rcTo;

    GetWindowRect(hwndFrom, &rcFrom);
    GetWindowRect(hwndTo, &rcTo);

    ppt->x += rcFrom.left - rcTo.left;
    ppt->y += rcFrom.top - rcTo.top;
}

/************************************************************************
* MyMapWindowRect
*
* This function maps a rectangle from one window to another.  The rectangle
* given is in window coordinates (not client coordinates) and is
* mapped so that it is relative to the destination window.
*
* Arguments:
*   HWND hwndFrom   - Source window.
*   HWND hwndTo     - Destination window.
*   PRECT prc       - Pointer to the rectangle to convert.
*
* Comments:
* In Win 3.1 (and NT) the MapWindowRect call can be used and this one can be removed.
* It is only needed here for compatibility with Win 3.0.
************************************************************************/

VOID MyMapWindowRect(
    HWND hwndFrom,
    HWND hwndTo,
    PRECT prc)
{
    RECT rcFrom;
    RECT rcTo;

    GetWindowRect(hwndFrom, &rcFrom);
    GetWindowRect(hwndTo, &rcTo);

    OffsetRect(prc, rcFrom.left - rcTo.left, rcFrom.top - rcTo.top);
}

/************************************************************************
* GetChildRect
*
* This function returns the client rectangle for a given child control,
* mapped to its parent window.
*
* Arguments:
*   HWND hwndChild - Child window.
*   PRECT prc      - Where to return the rectangle.
*
************************************************************************/

VOID GetChildRect(
    HWND hwndChild,
    PRECT prc)
{
    HWND hwndParent;

    hwndParent = GetParent(hwndChild);
    GetClientRect(hwndChild, prc);
    ClientToScreenRect(hwndChild, prc);
    ScreenToClientRect(hwndParent, prc);
}

/************************************************************************
* CenterWindow
*
* This function centers the given window over its owner.  It ensures
* that the window is entirely within the visible screen, however.
* If the window does not have an owner, it is centered over the
* desktop.
*
* Arguments:
*   HWND hwnd - The window to center.
*
************************************************************************/

VOID CenterWindow(
    HWND hwnd)
{
    RECT rc;
    RECT rcOwner;
    RECT rcCenter;
    HWND hwndOwner;

    GetWindowRect(hwnd, &rc);

    if (!(hwndOwner = GetWindow(hwnd, GW_OWNER)))
        hwndOwner = GetDesktopWindow();

    GetWindowRect(hwndOwner, &rcOwner);

    /*
     *  Calculate the starting x,y for the new
     *  window so that it would be centered.
     */
    rcCenter.left = rcOwner.left +
            (((rcOwner.right - rcOwner.left) -
            (rc.right - rc.left))
            / 2);

    rcCenter.top = rcOwner.top +
            (((rcOwner.bottom - rcOwner.top) -
            (rc.bottom - rc.top))
            / 2);

    rcCenter.right = rcCenter.left + (rc.right - rc.left);
    rcCenter.bottom = rcCenter.top + (rc.bottom - rc.top);

    FitRectToScreen(&rcCenter);

    SetWindowPos(hwnd, NULL, rcCenter.left, rcCenter.top, 0, 0,
            SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
}

/************************************************************************
* FitRectToScreen
*
* This function ensures that the given rectangle is entirely within
* the visible screen, adjusting it if necessary.
*
* Arguments:
*   PRECT prc - The rectangle.
*
************************************************************************/

VOID FitRectToScreen(
    PRECT prc)
{
    INT cxScreen;
    INT cyScreen;
    INT delta;

    cxScreen = GetSystemMetrics(SM_CXSCREEN);
    cyScreen = GetSystemMetrics(SM_CYSCREEN);

    if (prc->right > cxScreen) {
        delta = prc->right - prc->left;
        prc->right = cxScreen;
        prc->left = prc->right - delta;
    }

    if (prc->left < 0) {
        delta = prc->right - prc->left;
        prc->left = 0;
        prc->right = prc->left + delta;
    }

    if (prc->bottom > cyScreen) {
        delta = prc->bottom - prc->top;
        prc->bottom = cyScreen;
        prc->top = prc->bottom - delta;
    }

    if (prc->top < 0) {
        delta = prc->bottom - prc->top;
        prc->top = 0;
        prc->bottom = prc->top + delta;
    }
}

/************************************************************************
* ids
*
* This function will return a string, given the string id.  If this is
* the first time that the string has been retrieved, memory will be
* allocated for it and it will be loaded.  After it is loaded once, it
* is then cached in a LPTSTR array and is available for later without
* having to load it again.
*
* Arguments:
*   UINT idString - String ID of the string to retrieve.
*
************************************************************************/

LPTSTR ids(
    UINT idString)
{
    static LPTSTR apsz[CSTRINGS];       // String resource array cache.
    LPTSTR psz;
    INT cch;

    if (apsz[idString])
        return apsz[idString];

    if (!(psz = MyAlloc(CCHTEXTMAX * sizeof(TCHAR))))
        return szEmpty;

    if (!(cch = LoadString(ghInst, idString, psz, CCHTEXTMAX))) {
        MyFree(psz);
        return szEmpty;
    }

    apsz[idString] = psz = MyRealloc(psz, (cch + 1) * sizeof(TCHAR));

    return (psz ? psz : szEmpty);
}

/************************************************************************
* PixelsToPointSize
*
* This function takes a font height in pixels and converts it to
* the equivalent point size.  Note that the pixel height of a font
* is actually the tmHeight field of the TEXTMETRIC structure minus
* the tmInternalLeading value.
*
* This function relies on the global gcyPixelsPerInch having been
* set before it is called.
*
* Arguments:
*   INT nPixels - Pixel size to convert to point size.
*
************************************************************************/

INT PixelsToPointSize(
    INT nPixels)
{
    return MulDiv(nPixels, 72, gcyPixelsPerInch);
}

/************************************************************************
* PointSizeToPixels
*
* This function takes a given point size and converts it to the
* equivalent pixel text height.  This value can be placed in
* the TEXTMETRIC structure's tmHeight field if it is made negative
* first.  This will cause a CreateFont call to automatically
* subtract the internal leading value before creating the font.
*
* This function relies on the global gcyPixelsPerInch having been
* set before it is called.
*
* Arguments:
*   INT nPointSize - Point size to convert to pixels.
*
************************************************************************/

INT PointSizeToPixels(
    INT nPointSize)
{
    return MulDiv(nPointSize, gcyPixelsPerInch, 72);
}


VIEWINC.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 Header *******************************
* Module Name: viewinc.c
*
* Manages the Symbols dialog box (view include file).
*
* Functions:
*   ViewInclude()
*   ViewIncludeDlgProc()
*   ViewIncInit()
*   FillIncludeLB()
*   AddItemToIncLB()
*   SelectDefItem()
*   FillEditsFromLB()
*   SetIncButtonEnable()
*   ViewIncAdd()
*   ViewIncDelete()
*   ViewIncChange()
*   CopyLabels()
*   ViewIncCancel()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"
#include "dlghelp.h"

#include <string.h>

/*
 * Tabstop in the list box.
 */
#define DEFSYMBOLTABSTOP    146

static NPLABEL plNewInclude = NULL;     /* Pointer to new include data. */
static NPLABEL plNewDelInclude = NULL;  /* Pointer to new deleted incs. */
static BOOL fNewIncChanged;             /* TRUE if new incs are changed.*/

STATICFN BOOL ViewIncInit(HWND hwnd);
STATICFN VOID FillIncludeLB(HWND hwnd, BOOL fUnusedOnly,
    BOOL fSelectAnchor);
STATICFN INT AddItemToIncLB(NPLABEL npLabel, HWND hwndLB);
STATICFN VOID SelectDefItem(HWND hwnd, INT lastItem);
STATICFN VOID FillEditsFromLB(HWND hwnd);
STATICFN VOID SetIncButtonEnable(HWND hwnd);
STATICFN BOOL ViewIncAdd(HWND hwnd);
STATICFN BOOL ViewIncDelete(HWND hwnd);
STATICFN BOOL ViewIncChange(HWND hwnd);
STATICFN BOOL CopyLabels(NPLABEL plSrc, NPLABEL *pplDest);
STATICFN VOID ViewIncCancel(HWND hwnd);

/************************************************************************
* ViewInclude
*
* This function sets up for the View include dialog box, and invokes
* it.
*
************************************************************************/

VOID ViewInclude(VOID)
{
    if (CopyLabels(plInclude, &plNewInclude) &&
            CopyLabels(plDelInclude, &plNewDelInclude)) {
        fNewIncChanged = FALSE;
        if (DlgBox(DID_SYMBOLS, (WNDPROC)ViewIncludeDlgProc) == IDOK)
            /*
             * Update the status window in case the currently selected
             * controls id was one of the labels changed.
             */
            StatusUpdate();
    }
}

/************************************************************************
* ViewIncludeDlgProc
*
* This is the View Include dialog procedure.
*
* Comments:
*     May change the list of LABELs in plNewInclude, including their
*       strings and including more or less memory.
*     May put up a message box.
*
************************************************************************/

DIALOGPROC ViewIncludeDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            return ViewIncInit(hwnd);

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case DID_SYMBOLSADD:
                    ViewIncAdd(hwnd);
                    break;

                case DID_SYMBOLSDELETE:
                    ViewIncDelete(hwnd);
                    break;

                case DID_SYMBOLSCHANGE:
                    ViewIncChange(hwnd);
                    break;

                case DID_SYMBOLSLIST:
                    /*
                     * Make edit controls reflect the listbox selection.
                     */
                    if (HIWORD(wParam) == LBN_SELCHANGE)
                        FillEditsFromLB(hwnd);

                    break;

                case DID_SYMBOLSUNUSED:
                    if (HIWORD(wParam) == BN_CLICKED) {
                        if (IsDlgButtonChecked(hwnd, DID_SYMBOLSUNUSED))
                            FillIncludeLB(hwnd, TRUE, FALSE);
                        else
                            FillIncludeLB(hwnd, FALSE, FALSE);
                    }

                    break;

                case IDOK:
                    FreeLabels(&plInclude);
                    FreeLabels(&plDelInclude);
                    plInclude = plNewInclude;
                    plDelInclude = plNewDelInclude;

                    if (fNewIncChanged) {
                        gfIncChged = TRUE;
                        ShowFileStatus(FALSE);

                        /*
                         * Update the status windows symbol and name
                         * combo boxes.
                         */
                        StatusFillSymbolList(plInclude);
                    }

                    EndDialog(hwnd, IDOK);
                    break;

                case IDCANCEL:
                    ViewIncCancel(hwnd);
                    break;

                case IDHELP:
                    WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT,
                            HELPID_SYMBOLS);
                    break;
            }

            return TRUE;

        default:
            return FALSE;
    }
}

/************************************************************************
* ViewIncInit
*
* Processes the WM_INITDIALOG message for the View Include dialog procedure.
*
* Argements:
*   HWND hwnd - handle to the dialog
*
* Returns:
*   FALSE
*
************************************************************************/

STATICFN BOOL ViewIncInit(
    HWND hwnd)
{
    INT nTabStops = DEFSYMBOLTABSTOP;

    SendDlgItemMessage(hwnd, DID_SYMBOLSEDITID, EM_LIMITTEXT, CCHIDMAX, 0L);

    SendDlgItemMessage(hwnd, DID_SYMBOLSLIST, LB_SETTABSTOPS, 1,
            (DWORD)&nTabStops);

    FillIncludeLB(hwnd, FALSE, TRUE);

    /*
     * Disable some controls if Translating.
     */
    if (gfTranslateMode) {
        EnableWindow(GetDlgItem(hwnd, DID_SYMBOLSEDITSYM), FALSE);
        EnableWindow(GetDlgItem(hwnd, DID_SYMBOLSEDITID), FALSE);
        EnableWindow(GetDlgItem(hwnd, DID_SYMBOLSADD), FALSE);
    }

    CenterWindow(hwnd);

    /*
     * Yes, we changed the focus...
     */
    return FALSE;
}

/************************************************************************
* FillIncludeLB
*
* Fills the include listbox with the include items.
*
* Arguments:
*   HWND hwnd - handle to the include dialog.
*   BOOL fUnusedOnly - unused items?
*   BOOL fSelectAnchor - anchor control?
*
************************************************************************/

STATICFN VOID FillIncludeLB(
    HWND hwnd,
    BOOL fUnusedOnly,
    BOOL fSelectAnchor)
{
    NPLABEL npLabel;
    HWND hwndLB;
    INT iSelect;
    INT cIncs = 0;

    hwndLB = GetDlgItem(hwnd, DID_SYMBOLSLIST);

    SendMessage(hwndLB, WM_SETREDRAW, FALSE, 0L);

    /*
     * Delete any existing items from the listbox.
     */
    SendMessage(hwndLB, LB_RESETCONTENT, 0, 0L);

    /*
     * Fill the list box with the items.
     */
    for (npLabel = plNewInclude; npLabel; npLabel = npLabel->npNext) {
        if (!fUnusedOnly || !FindIDInRes(npLabel->id)) {
            AddItemToIncLB(npLabel, hwndLB);
            cIncs++;
        }
    }

    /*
     * Are there any items in the listbox?
     */
    if (cIncs) {
        /*
         * If there is a currently selected control, search for the
         * symbol that corresponds to it.  This will be the default
         * selected control.  If there is not a currently selected
         * control, select the first symbol in the listbox.
         *
         * Only select the anchor control if fSelectAnchor is TRUE,
         * however.  Also, if the dialog is selected, it does not
         * have an id so we skip this case as well.
         */
        iSelect = 0;
        if (fSelectAnchor && gnpcSel && !gfDlgSelected) {
            if (npLabel = FindID(gnpcSel->id, plNewInclude)) {
                /*
                 * Search the list box for the symbol.
                 */
                iSelect = (INT)SendMessage(hwndLB, LB_FINDSTRING,
                        (WPARAM)-1, (DWORD)npLabel->pszLabel);

                if (iSelect == LB_ERR)
                    iSelect = 0;
            }
        }

        SendMessage(hwndLB, LB_SETCURSEL, iSelect, 0L);

        /*
         * Set the focus to the listbox initially (so arrow keys work).
         */
        SetFocus(hwndLB);
    }
    else {
        SetFocus(GetDlgItem(hwnd, DID_SYMBOLSEDITSYM));
    }

    SendMessage(hwndLB, WM_SETREDRAW, TRUE, 0L);
    InvalidateRect(hwndLB, NULL, FALSE);

    FillEditsFromLB(hwnd);
    SetIncButtonEnable(hwnd);
}

/****************************************************************************
* AddItemToIncLB
*
* Adds a symbol and id to the View Include listbox and associates it's
* label pointer with the added item.
*
* Arguments:
*   NPLABEL npLabel - list of label pointers.
*   HWND hwnd LB - handle to the listbox
*
* Returns:
*
* List box id of the newly added item.
*
****************************************************************************/

STATICFN INT AddItemToIncLB(
    NPLABEL npLabel,
    HWND hwndLB)
{
    INT idTemp;
    TCHAR szBuf[CCHTEXTMAX];
    LPTSTR psz;

    /*
     * Start building the string to add.  Take the label and tack on
     * a tab character.
     */
    lstrcpy(szBuf, npLabel->pszLabel);
    psz = szBuf + lstrlen(szBuf);
    *psz++ = CHAR_TAB;

    /*
     * Now add the id to the end, using the current hex mode.
     */
    Myitoa(npLabel->id, psz);
    idTemp = (INT)SendMessage(hwndLB, LB_ADDSTRING, 0, (DWORD)szBuf);
    SendMessage(hwndLB, LB_SETITEMDATA, idTemp, (DWORD)npLabel);

    return idTemp;
}

/****************************************************************************
* SelectDefItem
*
* Select an item in the listbox near lastItem.
*
* Arguments:
*   HWND hwnd - handle to the dialog
*   INT lastItem - lastitem to select.
*
****************************************************************************/

STATICFN VOID SelectDefItem(
    HWND hwnd,
    INT lastItem)
{
    INT cItems;
    HWND hwndLB;

    hwndLB = GetDlgItem(hwnd, DID_SYMBOLSLIST);

    if ((cItems = (INT)SendMessage(hwndLB, LB_GETCOUNT, 0, 0L))
            != LB_ERR && cItems > 0)
        SendMessage(hwndLB, LB_SETCURSEL, lastItem >= cItems ?
                (cItems - 1) : lastItem, 0L);

    FillEditsFromLB(hwnd);
}

/****************************************************************************
* FillEditsFromLB
*
* Fill the edit controls from the selected item in the listbox.
*
* Arguments:
*   HWND hwnd - handle to the dialog.
*
****************************************************************************/

STATICFN VOID FillEditsFromLB(
    HWND hwnd)
{
    TCHAR szID[CCHIDMAX + 1];
    INT iItem;
    NPLABEL npLabel;
    HWND hwndLB;
    HWND hwndID;
    HWND hwndSym;

    hwndLB = GetDlgItem(hwnd, DID_SYMBOLSLIST);
    hwndSym = GetDlgItem(hwnd, DID_SYMBOLSEDITSYM);
    hwndID = GetDlgItem(hwnd, DID_SYMBOLSEDITID);

    /*
     * Is there a selected item?
     */
    if ((iItem = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L))
            != LB_ERR) {
        npLabel = (NPLABEL)SendMessage(hwndLB, LB_GETITEMDATA,
                iItem, 0L);
        SetWindowText(hwndSym, npLabel->pszLabel);

        Myitoa(npLabel->id, szID);
        SetWindowText(hwndID, szID);
    }
    else {
        /*
         * No, clear the fields.
         */
        SetWindowText(hwndSym, szEmpty);
        SetWindowText(hwndID, szEmpty);
    }
}

/****************************************************************************
* SetIncButtonEnable
*
* Enable/disable the Symbols change and delete buttons.
*
* Arguments:
*   HWND hwnd - handle to the dialog.
*
****************************************************************************/

STATICFN VOID SetIncButtonEnable(
    HWND hwnd)
{
    BOOL fEnable = TRUE;

    /*
     * If the list box doesn't have items, or we are in translate mode,
     * disable the Delete and Change buttons.
     */
    if (gfTranslateMode || SendDlgItemMessage(hwnd, DID_SYMBOLSLIST,
            LB_GETCOUNT, 0, 0L) == 0)
        fEnable = FALSE;

    EnableWindow(GetDlgItem(hwnd, DID_SYMBOLSDELETE), fEnable);
    EnableWindow(GetDlgItem(hwnd, DID_SYMBOLSCHANGE), fEnable);
}

/************************************************************************
* ViewIncAdd
*
* Processes the "Add" command for the View Include dialog procedure.
*
* Arguments:
*   HWND hwnd - handle to the dialog.
*
************************************************************************/

STATICFN BOOL ViewIncAdd(
    HWND hwnd)
{
    TCHAR szSym[CCHTEXTMAX];
    TCHAR szID[CCHIDMAX + 1];
    HWND hwndLB;
    INT idNew;
    NPLABEL npLabel;

    /*
     * Get current symbol & ID.
     */
    GetDlgItemText(hwnd, DID_SYMBOLSEDITID, szID, CCHIDMAX + 1);
    GetDlgItemText(hwnd, DID_SYMBOLSEDITSYM, szSym, CCHTEXTMAX);

    /*
     * If they didn't specify a new id as well as a new symbol,
     * pick a default number.
     */
    if (*szID == CHAR_NULL)
        Myitoa(NextID(NEXTID_LABEL, plNewInclude, 0), szID);

    /*
     * Validate them.
     */
    if (!IsSymbol(szSym) || !IsValue(szID)) {
        Message(MSG_BADSYMBOLID);
        return FALSE;
    }

    idNew = valtoi(szID);

    if (!(npLabel = AddLabel(szSym, idNew, FPOS_MAX, 0,
            &plNewInclude, &plNewDelInclude, NULL, NULL)))
        return FALSE;

    fNewIncChanged = TRUE;

    /*
     * Add the new symbol to the listbox, but not if they only want to
     * show unused id's and this id is in use.
     */
    if (!IsDlgButtonChecked(hwnd, DID_SYMBOLSUNUSED) ||
            !FindIDInRes(idNew)) {
        hwndLB = GetDlgItem(hwnd, DID_SYMBOLSLIST);
        SendMessage(hwndLB, LB_SETCURSEL,
                AddItemToIncLB(npLabel, hwndLB), 0L);
    }

    SetIncButtonEnable(hwnd);
    FillEditsFromLB(hwnd);

    return TRUE;
}

/************************************************************************
* ViewIncDelete
*
* Processes the "Delete" command for the View Include dialog procedure.
*
* Arguments:
*   HWND hwnd - handle to the dialog.
*
************************************************************************/

STATICFN BOOL ViewIncDelete(
    HWND hwnd)
{
    TCHAR szSym[CCHTEXTMAX];
    HWND hwndLB;
    INT iItem;

    /*
     * Get current symbol and listbox hwnd.
     */
    GetDlgItemText(hwnd, DID_SYMBOLSEDITSYM, szSym, CCHTEXTMAX);
    hwndLB = GetDlgItem(hwnd, DID_SYMBOLSLIST);

    /*
     * Search the list box for the symbol.  This will probably be
     * the same as the selection, but if they type in a symbol
     * then it will not be.
     */
    iItem = (INT)SendMessage(hwndLB, LB_FINDSTRING, (WPARAM)-1, (DWORD)szSym);

    /*
     * Fail if the symbol was not found.
     */
    if (iItem == LB_ERR) {
        Message(MSG_SYMNOTFOUND);
        return FALSE;
    }

    DeleteLabel(szSym, &plNewInclude, &plNewDelInclude);
    fNewIncChanged = TRUE;

    SendMessage(hwndLB, LB_DELETESTRING, iItem, 0L);
    SelectDefItem(hwnd, iItem);
    SetIncButtonEnable(hwnd);

    return TRUE;
}

/************************************************************************
* ViewIncChange
*
* Processes the "Change" command for the View Include dialog procedure.
*
* Arguments:
*   HWND hwnd - handle to the dialog.
*
************************************************************************/

STATICFN BOOL ViewIncChange(
    HWND hwnd)
{
    TCHAR szSym[CCHTEXTMAX];
    TCHAR szID[CCHIDMAX + 1];
    HWND hwndLB;
    NPLABEL npLabel;
    NPLABEL npLabelNew;
    INT idNew;
    INT iItem;

    /*
     * Get current symbol & ID.
     */
    GetDlgItemText(hwnd, DID_SYMBOLSEDITID, szID, CCHIDMAX + 1);
    GetDlgItemText(hwnd, DID_SYMBOLSEDITSYM, szSym, CCHTEXTMAX);

    /*
     * Validate them.
     */
    if (!IsSymbol(szSym) || !IsValue(szID)) {
        Message(MSG_BADSYMBOLID);
        return FALSE;
    }

    hwndLB = GetDlgItem(hwnd, DID_SYMBOLSLIST);

    /*
     * Make sure a selection is made.
     */
    if ((iItem = (INT)SendMessage(hwndLB, LB_GETCURSEL, 0, 0L))
            == LB_ERR) {
        Message(MSG_SELECTFIRST);
        return FALSE;
    }

    /*
     * Get the item handle.
     */
    npLabel = (NPLABEL)SendMessage(hwndLB, LB_GETITEMDATA, iItem, 0L);

    /*
     * Check if the symbol is changing.
     */
    idNew = valtoi(szID);
    if (lstrcmp(npLabel->pszLabel, szSym) != 0) {
        if (!(npLabelNew = AddLabel(szSym, idNew, FPOS_MAX, 0,
                &plNewInclude, &plNewDelInclude, npLabel, NULL)))
            return FALSE;

        DeleteLabel(npLabel->pszLabel, &plNewInclude, &plNewDelInclude);
        npLabel = npLabelNew;
    }
    /*
     * The symbol didn't change.  Did the id change?
     */
    else if (idNew != npLabel->id) {
        /*
         * First check for a duplicate id.
         */
        if (FindID(idNew, plNewInclude)) {
            Message(MSG_LABELDUPID);
            return FALSE;
        }

        npLabel->id = idNew;
    }
    else {
        /*
         * Nothing changed.
         */
        Message(MSG_SYMNOCHANGE);
        return FALSE;
    }

    fNewIncChanged = TRUE;
    SendMessage(hwndLB, WM_SETREDRAW, FALSE, 0L);
    SendMessage(hwndLB, LB_DELETESTRING, iItem, 0L);

    /*
     * Add the changed symbol to the listbox, but not if they only want to
     * show unused id's and this id is in use.
     */
    if (!IsDlgButtonChecked(hwnd, DID_SYMBOLSUNUSED) || !FindIDInRes(idNew))
        SendMessage(hwndLB, LB_SETCURSEL,
                AddItemToIncLB(npLabel, hwndLB), 0L);

    SendMessage(hwndLB, WM_SETREDRAW, TRUE, 0L);
    InvalidateRect(hwndLB, NULL, FALSE);
    FillEditsFromLB(hwnd);
    SetIncButtonEnable(hwnd);

    return TRUE;
}

/****************************************************************************
* CopyLabels
*
* This function creates a copy of the LABEL structure list in plSrc,
* including copying all strings.  This is put in local memory and
* the head pointer is returned in *pplDest.
*
* Arguments:
*   NPLABEL plSrc - label list source.
*   NPLABEL pplDest - label list destination.
*
* Returns:
*
*  TRUE if all went well, FALSE if there was a problem.
*
* Comments:
*
* Locally allocates a copy of plSrc and its strings.
*
* Caution: If pplDest points to a valid list it must be freed before
*          calling this function.
*
****************************************************************************/

STATICFN BOOL CopyLabels(
    NPLABEL plSrc,
    NPLABEL *pplDest)
{
    NPLABEL plNew;
    NPLABEL plPrev;

    plPrev = NULL;
    *pplDest = NULL;
    while (plSrc) {
        if (!(plNew = (NPLABEL)MyAlloc(sizeof(LABEL)))) {
            FreeLabels(pplDest);
            return FALSE;
        }

        /*
         * Are we on the first one?
         */
        if (*pplDest == NULL)
            *pplDest = plNew;
        else
            plPrev->npNext = plNew;

        /*
         * Start by copying the whole label structure.
         */
        memcpy((PBYTE)plNew, (PBYTE)plSrc, sizeof(LABEL));

        /*
         * Make a private copy of the pszLabel string.
         */
        if (!(plNew->pszLabel =
                MyAlloc((lstrlen(plSrc->pszLabel) + 1) * sizeof(TCHAR)))) {
            MyFree(plNew);
            FreeLabels(pplDest);
            return FALSE;
        }

        lstrcpy(plNew->pszLabel, plSrc->pszLabel);

        plNew->npNext = NULL;           /* In case this is the last one.*/
        plPrev = plNew;                 /* Save so we can update npNext.*/
        plSrc = plSrc->npNext;          /* Get next one to copy.        */
    }

    return TRUE;
}

/************************************************************************
* ViewIncCancel
*
* Called when cancelling the Symbols dialog box.
*
* Argements:
*    HWND hwnd - handle to the dialog
*
************************************************************************/

STATICFN VOID ViewIncCancel(
    HWND hwnd)
{
    /*
     * If they changed anything, confirm that they
     * want to throw away the changes.
     */
    if (fNewIncChanged &&
            Message(MSG_CONFIRMDISCARD) != IDYES)
        return;

    /*
     * Free up the temporary label lists and get out.
     */
    FreeLabels(&plNewInclude);
    FreeLabels(&plNewDelInclude);

    /*
     * Flag the dialog to be dismissed.
     */
    EndDialog(hwnd, IDCANCEL);
}


README.TXT

File Server Monitor

The NETWATCH tool allows you to monitor the file server activities on your
workstation, and on remote servers. It demonstrates how an application can
programmatically gather this information.


NET.C

/*
 *  net.c
 *
 *  Purpose:
 *      net functions
 *
 *  Owner:
 *      MikeSart
 */
#define UNICODE 1

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <string.h>
#include <time.h>
#include <lm.h>
#include "netwatch.h"
#include "rcids.h"

ASSERTDATA

// From lmcons.h:
//  NNLEN: max share name
//  UNLEN: max user name
//  MAXCOMMENTSZ: max comment length
//  PATHLEN: max path

// From datetime.c
TCHAR*PutTime(time_t tm_t, TCHAR *szStr, UINT cch);
TCHAR*PutDate(time_t tm_t, TCHAR *szStr, UINT cch);
TCHAR*PutCounterTime(DWORD dw, TCHAR *szStr, UINT cch);

/*
 *Useful macros
 */
// Should we consider szShare hidden?
#define FIsShareHidden(_szShare) \
((_szShare)[lstrlen(_szShare) - 1] == TEXT('$'))
// Is the lParam in the TV_ITEM a pointer to TVDATA or just a type?
#define FIsLParamTVData(_lp) \
(((_lp) + 1) > 1)

/*
 *Globules
 */
SCONST TCHARszFmtNum[]      = TEXT("%lu");

/*
 *Typedefs
 */
typedef struct tagTVDATA
{
DWORDdwType;
} TVDATA;

typedef struct tagTVDATASHARE
{
DWORDdwType;// TYPE_SHARE, TYPE_USER, or TYPE_FILE
DWORDdwShareType;// STYPE_IPC, etc.
TCHARszShareName[NNLEN + 1];// e$, etc.

TCHARszServerName[UNCLEN + 1];
} TVDATASHARE;

typedef struct tagTVDATAUSER
{
DWORDdwType;// TYPE_SHARE, TYPE_USER, or TYPE_FILE
TCHARszUserName[UNLEN + 1];// BillG, etc.
TCHARszNetName[UNCLEN + 1];// \\BILLG1, etc.

TVDATASHARE*pTVDataShare;// Parent data
} TVDATAUSER;

typedef struct tagTVDATAFILE
{
DWORDdwType;// TYPE_SHARE, TYPE_USER, or TYPE_FILE
DWORDdwFileId;// fi3_id

TVDATAUSER*pTVDataUser;// Parent data
} TVDATAFILE;

/*
 *Treeview nonsense
 */

/*
 *TreeView_FindExactString
 *
 *Purpose:
 *Find a szText starting from hItemStart
 *
 *Arguments:
 *hParent, hItemStart, szText
 *
 *Returns:
 *hItem or NULL
 */
HTREEITEM
TreeView_FindExactString(HWND hwndTV, HTREEITEM hParent, HTREEITEM hItemStart,
TCHAR *szText)
{
TCHARszT[cchTVSzMax];

// Start looking one node after us
hItemStart = hItemStart ? TreeView_GetNextSibling(hwndTV, hItemStart) :
TreeView_GetChild(hwndTV, hParent);

// Loop through looking for that baby
while(hItemStart)
{
// Get the text
if(FTreeView_GetString(hwndTV, hItemStart, szT, cchTVSzMax))
{
UINT nRet = lstrcmp(szText, szT);

// Strings should be in the treeview control like:
//   aaaa$
//   bbbb$
//   zzzz$
//   bbbb
//   ffff

// So, if the strings match, return em
// Else szText is larger than szT and we're past the hidden shares
// so the item shouldn't be here
if(!nRet)
return hItemStart;
else if((nRet < 0) && !FIsShareHidden(szT))
return NULL;
}

hItemStart = TreeView_GetNextSibling(hwndTV, hItemStart);
}

return hItemStart;
}

/*
 *FTreeView_DeleteRange
 *
 *Purpose: Delete hItemStart+1 to hItemEnd-1
 *hItemStart
 *hItemStart+1
 *...
 *HItemEnd-1
 *HItemEnd
 *
 *Arguments:
 *hwndTV, hItemStart, hItemEnd
 *
 *Returns:
 *TRUE - success, FALSE - failure
 */
BOOL
FTreeView_DeleteRange(HWND hwndTV, HTREEITEM hParent, HTREEITEM hItemStart,
HTREEITEM hItemEnd)
{
// If NULL, we want the first item, else we want hItemStart + 1
hItemStart = hItemStart ? TreeView_GetNextSibling(hwndTV, hItemStart) :
TreeView_GetChild(hwndTV, hParent);

while(hItemStart && (hItemStart != hItemEnd))
{
HTREEITEMhItemT;

hItemT = TreeView_GetNextSibling(hwndTV, hItemStart);
TreeView_DeleteItem(hwndTV, hItemStart);
hItemStart = hItemT;
}

return TRUE;
}

/*
 *TreeView_TVReplacelParam
 *
 *Purpose:
 *Replace an lparam and free any associated memory
 *
 *Arguments:
 *hItem to replace and lParamNew
 *
 *Returns:
 *TRUE - success, FALSE - failed
 */
BOOL
TreeView_TVReplacelParam(HWND hwndTV, HTREEITEM hItem, LPARAM lParamNew)
{
TV_ITEMtvItem;
LPARAMlParamOld;

tvItem.mask = TVIF_PARAM;
tvItem.hItem = hItem;
TreeView_GetItem(hwndTV, &tvItem);
lParamOld = tvItem.lParam;

tvItem.lParam = lParamNew;
TreeView_SetItem(hwndTV, &tvItem);

if(FIsLParamTVData(lParamOld))
GlobalFreePtr((TVDATA *)lParamOld);
return TRUE;
}

/*
 *TreeView_TVDataInsert
 *
 *Purpose:
 *Try to be smart when inserting TV items
 *
 *Arguments:
 *ptvis to insert
 *
 *Returns:
 *hItem of newly inserted item
 */
HTREEITEM
TreeView_TVDataInsert(HWND hwndTV, TV_INSERTSTRUCT *ptvis)
{
HTREEITEMhItem;

// See if this item exists anywhere already
hItem = TreeView_FindExactString(hwndTV, ptvis->hParent,
ptvis->hInsertAfter, ptvis->item.pszText);

// It does: del everything from the last inserted node to right before us
if(hItem)
{
OutputDbgStr("TreeView_TVDataInsert found '%ls'", ptvis->item.pszText);
FTreeView_DeleteRange(hwndTV, ptvis->hParent, ptvis->hInsertAfter,
hItem);
TreeView_TVReplacelParam(hwndTV, hItem, ptvis->item.lParam);
ptvis->hInsertAfter = hItem;
return hItem;
}

// ok, it's a new string, insert it
// If this is the first one, make sure it's the first one
OutputDbgStr("TreeView_TVDataInsert inserting '%ls'", ptvis->item.pszText);
if(!ptvis->hInsertAfter)
ptvis->hInsertAfter = TVI_FIRST;
hItem = TreeView_InsertItem(hwndTV, ptvis);
// Store away the last item we've inserted
ptvis->hInsertAfter = hItem;

return hItem;
}

/*
 *TreeView_FreeItemData
 *
 *Purpose:
 *Free our alloc'd lParam (called via DELETEITEM)
 *
 *Arguments:
 *ptvItem of item being deleted
 *
 *Returns:
 *nada
 */
VOID
TreeView_FreeItemData(TV_ITEM *ptvItem)
{
if(FIsLParamTVData(ptvItem->lParam))
{
OutputDbgStr("TreeView_FreeItemData '%d'",
((TVDATA *)ptvItem->lParam)->dwType);
GlobalFreePtr((TVDATA *)ptvItem->lParam);
ptvItem->lParam = 0;
}
}

/*
 *TreeView_GetSelectedItemData
 *
 *Purpose:
 *Get type and lParam of selected item
 *
 *Arguments:
 *pdwType
 *
 *Returns:
 *lParam of selected item
 */
LPARAM
TreeView_GetSelectedItemData(HWND hwndTV, DWORD *pdwType)
{
HTREEITEMhItem;
TV_ITEMtvItem;

*pdwType = TYPE_ERROR;
if(!(hItem = TreeView_GetSelection(hwndTV)))
return 0;

tvItem.mask = TVIF_PARAM;
tvItem.hItem = hItem;
tvItem.lParam = TYPE_ERROR;
TreeView_GetItem(hwndTV, &tvItem);

*pdwType = FIsLParamTVData(tvItem.lParam) ?
((TVDATA *)tvItem.lParam)->dwType : tvItem.lParam;
return tvItem.lParam;
}

/*
 *Sorting routines
 */

/*
 *CompareShareNames
 *
 *Purpose:
 *compare two share names being 'smart' about hidden shares
 *
 *Arguments:
 *share1, share2
 *
 *Returns:
 *-1, 0, 1
 */
int
CompareShareNames(TCHAR *szShareName1, TCHAR *szShareName2)
{
BOOLfShare1Hidden;
BOOLfShare2Hidden;

fShare1Hidden = FIsShareHidden(szShareName1);
fShare2Hidden = FIsShareHidden(szShareName2);

// If they're the same type, just string compare them
if(fShare1Hidden == fShare2Hidden)
return lstrcmpi(szShareName1, szShareName2);

// Otherwise we want hidden shares to go first
return fShare1Hidden ? -1 : 1;
}

/*
 *QSortCallbacks
 *
 *Purpose:
 *called from qsort to figure what goes where
 *
 *Arguments:
 *elements to compare
 *
 *Returns:
 *< 0: elem1 less than elem2
 *= 0: elem1 equivalent to elem2
 *> 0: elem1 greater than elem2
 */
int __cdecl
CompareShareNamesQSortCallback(const void *elem1, const void *elem2)
{
return CompareShareNames(((SHARE_INFO_2 *)elem1)->shi2_netname,
((SHARE_INFO_2 *)elem2)->shi2_netname);
}

int __cdecl
CompareUserNamesQSortCallback(const void *elem1, const void *elem2)
{
return lstrcmp(((CONNECTION_INFO_1 *)elem1)->coni1_username,
((CONNECTION_INFO_1 *)elem2)->coni1_username);
}

int __cdecl
CompareFileNamesQSortCallback(const void *elem1, const void *elem2)
{
return lstrcmp(((FILE_INFO_3 *)elem1)->fi3_pathname,
((FILE_INFO_3 *)elem2)->fi3_pathname);
}

/*
 *Enumeration routines
 */

/*
 *FilesEnum
 *
 *Purpose:
 *Enumerate the open files on a sharepath, username, and server
 *
 *Arguments:
 *all kinds of stuff
 *
 *Returns:
 *nas
 */
NET_API_STATUS
FilesEnum(HWND hwndTV, HTREEITEM hParent, LPTSTR szServerName, TCHAR *username,
TCHAR *sharepath, TVDATAUSER *pTVDataUser)
{
NET_API_STATUSnas;
TV_INSERTSTRUCTtvis;
FILE_INFO_3*pfi3;
FILE_INFO_3*fi3 = NULL;
DWORDdwentriesread;
DWORDdwtotalentries;
TCHAR*psharepathend;

// Some stuff doesn't change
tvis.hInsertAfter = NULL;
tvis.hParent = hParent;
tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;

// Reasoning: if you are passed c:\ as a sharepath, sometimes c: is opened
// as execute. But since we would be qualifying paths on c:\, it won't show
// c: as being open.  So - remove the last \ if it's there.
if(sharepath && sharepath[0])
{
psharepathend = sharepath + lstrlen(sharepath) - 1;
if(*psharepathend == TEXT('\\'))
*psharepathend = '\0';
}

nas = NetFileEnum(szServerName, sharepath, username, 3,
(LPBYTE *)&fi3, MAX_PREFERRED_LENGTH,
&dwentriesread, &dwtotalentries, NULL);
if(nas || !dwentriesread)
goto err;

qsort(fi3, dwentriesread, sizeof(fi3[0]), CompareFileNamesQSortCallback);
for(pfi3 = fi3; dwentriesread; pfi3++, dwentriesread--)
{
TVDATAFILE*pTVDataFile;
UINTnBitmap = BMP_READ;

// And our bmp is...
if((pfi3->fi3_permissions & PERM_FILE_WRITE) ||
(pfi3->fi3_permissions & PERM_FILE_CREATE))
nBitmap = BMP_WRITE;

// Things to remember for properties, delete, etc.
if(pTVDataFile = (TVDATAFILE *)GlobalAllocPtr(GHND, sizeof(TVDATAFILE)))
{
pTVDataFile->dwType = TYPE_FILE;
pTVDataFile->dwFileId = pfi3->fi3_id;
pTVDataFile->pTVDataUser = pTVDataUser;
}

// Insert that baby
tvis.item.lParam = (LPARAM)pTVDataFile;
tvis.item.pszText = pfi3->fi3_pathname;
tvis.item.iImage = tvis.item.iSelectedImage = nBitmap;
TreeView_TVDataInsert(hwndTV, &tvis);
}

err:
// Whack any stragglers hanging at the end
FTreeView_DeleteRange(hwndTV, hParent, tvis.hInsertAfter, NULL);

NetApiBufferFree(fi3);
if(nas)
AddErrorStringToTV(hwndTV, hParent, IDS_ERRENUMFILES, nas);
return nas;
}

/*
 *UsersEnum
 *
 *Purpose:
 *Enumerate the users on a sharepath and server
 *
 *Arguments:
 *lots o garbage
 *
 *Returns:
 *nas
 */
NET_API_STATUS
UsersEnum(HWND hwndTV, HTREEITEM hParent, LPTSTR szServerName, TCHAR *sharename,
TCHAR *sharepath, TVDATASHARE *pTVDataShare)
{
NET_API_STATUSnas;
TV_INSERTSTRUCTtvis;
HTREEITEMhItem;
CONNECTION_INFO_1*pci1;
CONNECTION_INFO_1*ci1 = NULL;
DWORDdwentriesread;
DWORDdwtotalentries;
static TCHARszFmtUser[cchSzMax + 1];
TCHARszUserT[NNLEN + UNLEN + cchSzMax + 1];

// Some stuff doesn't change
tvis.hInsertAfter = NULL;
tvis.hParent = hParent;
tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;

if(!szFmtUser[0])
lstrcpy(szFmtUser, szFromIDS1(IDS_FMTUSER));

nas = NetConnectionEnum(szServerName, sharename, 1, (LPBYTE *)&ci1,
MAX_PREFERRED_LENGTH, &dwentriesread, &dwtotalentries, NULL);
if(nas || !dwentriesread)
goto err;

qsort(ci1, dwentriesread, sizeof(ci1[0]), CompareUserNamesQSortCallback);
for(pci1 = ci1; dwentriesread; pci1++, dwentriesread--)
{
TCHAR*netname;
TCHAR*username;
TVDATAUSER*pTVDataUser;

username = pci1->coni1_username;
netname = pci1->coni1_netname;
if(!username || !username[0])
username = szNil;
else
CharLower(username);
if(!netname || !netname[0])
netname = szNil;
else
CharUpper(netname);

if(lstrlen(netname) > CNLEN)
netname[CNLEN] = 0;
if(lstrlen(username) > UNLEN)
username[UNLEN] = 0;

wsprintf(szUserT, szFmtUser, username, netname);

// Things to remember for properties, delete, etc.
if(pTVDataUser = (TVDATAUSER *)GlobalAllocPtr(GHND, sizeof(TVDATAUSER)))
{
pTVDataUser->dwType = TYPE_USER;
lstrcpy(pTVDataUser->szUserName, username);
pTVDataUser->szNetName[0] = pTVDataUser->szNetName[1] = '\\';
lstrcpy(&pTVDataUser->szNetName[2], netname);
pTVDataUser->pTVDataShare = pTVDataShare;
}

tvis.item.lParam = (LPARAM)pTVDataUser;
tvis.item.pszText = szUserT;
tvis.item.iImage = tvis.item.iSelectedImage = BMP_USER;
hItem = TreeView_TVDataInsert(hwndTV, &tvis);

// If we haven't run into any errors and the user wants to see files
if(pci1->coni1_num_opens &&
unMenuFlags[IDM_SHOWFILES & 0xff] == MF_CHECKED && !nas)
{
// Enum the files
nas = FilesEnum(hwndTV, hItem, szServerName, username,
sharepath, pTVDataUser);
// make sure we're expanded
TreeView_Expand(hwndTV, hItem, TVE_EXPAND);
}
else
{
// Delete all our children
FTreeView_DeleteRange(hwndTV, hItem, NULL, NULL);
}
}

// reset any filesenum errors we may have encountered
nas = 0;

err:
// Whack any stragglers hanging at the end
FTreeView_DeleteRange(hwndTV, hParent, tvis.hInsertAfter, NULL);

NetApiBufferFree(ci1);
if(nas)
AddErrorStringToTV(hwndTV, hParent, IDS_ERRENUMUSERS, nas);
return nas;
}

/*
 *SharesEnum
 *
 *Purpose:
 *Enumerate the shares on a server
 *
 *Arguments:
 *server to enum
 *
 *Returns:
 *nas
 */
#define offsetof(s, m)  (size_t)&(((s *)0)->m)
#define shoffset(l, m)  (offsetof(SHARE_INFO_##l, m))
NET_API_STATUS
SharesEnum(HWND hwndTV, HTREEITEM hParent, LPTSTR szServerName, UINT *pcUsers,
INT nLevel)
{
NET_API_STATUSnas;
TV_INSERTSTRUCTtvis;
HTREEITEMhItem;
SHARE_INFO_2*pshi2;
UINTcbStruct;
SHARE_INFO_2*shi2 = NULL;
DWORDdwentriesread;
DWORDdwtotalentries;
SCONST TCHARszEndSep[] = TEXT(")");
SCONST TCHARszBegSep[] = TEXT(" (");
// +3 for the begsep and endsep
TCHARszShareT[NNLEN + MAXCOMMENTSZ + 3 + 1];

Assert(nLevel == 1 || nLevel == 2);
// We assume the first three parts of these structs are the same. Make sure.
// Using cbStruct here cause the compiler loses the entire line if I don't?
Assert((cbStruct = shoffset(1, shi1_netname)) == shoffset(2, shi2_netname));
Assert((cbStruct = shoffset(1, shi1_type)) == shoffset(2, shi2_type));
Assert((cbStruct = shoffset(1, shi1_remark)) == shoffset(2, shi2_remark));

cbStruct = nLevel == 2 ? sizeof(SHARE_INFO_2) : sizeof(SHARE_INFO_1);
// WARNING: don't use the following members of shi2 without checking nLevel!
//shi2_permissions;
//shi2_max_uses;
//shi2_current_uses
//shi2_path;
//shi2_passwd;

// Some stuff doesn't change
tvis.hInsertAfter = NULL;
tvis.hParent = hParent;
tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;

nas = NetShareEnum(szServerName, nLevel, (LPBYTE *)&shi2,
MAX_PREFERRED_LENGTH, &dwentriesread, &dwtotalentries, NULL);
if(nas || !dwentriesread)
goto err;

// Sort the shares and then cruise through adding em all to the TV
qsort(shi2, dwentriesread, cbStruct, CompareShareNamesQSortCallback);
for(pshi2 = shi2; dwentriesread; ((LPBYTE)pshi2) += cbStruct, dwentriesread--)
{
TCHAR*sharename;
TCHAR*shareremark;
UINTnCurrentUses;
TVDATASHARE*pTVDataShare;

sharename = pshi2->shi2_netname;
shareremark = pshi2->shi2_remark;
nCurrentUses = (nLevel == 2) ? pshi2->shi2_current_uses : 0;

if(!sharename)
continue;
if(!shareremark)
shareremark = szNil;

if(lstrlen(sharename) > NNLEN)
sharename[NNLEN] = 0;
if(lstrlen(shareremark) > MAXCOMMENTSZ)
shareremark[MAXCOMMENTSZ] = 0;

// skip hidden shares
if((unMenuFlags[IDM_SHOWHIDDEN & 0xff] == MF_UNCHECKED) &&
FIsShareHidden(sharename))
continue;

// Keep a running total
*pcUsers += nCurrentUses;

// If we have any users connected or the onlyshowinuse isn't checked
if(nCurrentUses || (unMenuFlags[IDM_SHOWINUSE & 0xff] != MF_CHECKED))
{
int nBitmap = ((pshi2->shi2_type & 0xf) == STYPE_IPC) ?
BMP_IPC : BMP_SHARE;

// Copy over the share name and comment
lstrcpy(szShareT, sharename);
if(shareremark[0])
{
lstrcat(szShareT, szBegSep);
lstrcat(szShareT, shareremark);
lstrcat(szShareT, szEndSep);
}

// Things to remember for properties, delete, etc.
if(pTVDataShare = (TVDATASHARE *)GlobalAllocPtr(GHND,
sizeof(TVDATASHARE)))
{
pTVDataShare->dwType = TYPE_SHARE;
pTVDataShare->dwShareType = pshi2->shi2_type;
lstrcpy(pTVDataShare->szShareName, sharename);
lstrcpy(pTVDataShare->szServerName, szServerName);
}

tvis.item.lParam = (LPARAM)pTVDataShare;
tvis.item.pszText = szShareT;
tvis.item.iImage = tvis.item.iSelectedImage = nBitmap;
hItem = TreeView_TVDataInsert(hwndTV, &tvis);

// If we previously got an error enuming users, don't waste our time
// trying again.
if(!nas && nCurrentUses)
{
Assert(nLevel == 2);
nas = UsersEnum(hwndTV, hItem, szServerName,
sharename, pshi2->shi2_path, pTVDataShare);
// Make sure we're showing it all
TreeView_Expand(hwndTV, hItem, TVE_EXPAND);
}
else
{
// Delete all our children
FTreeView_DeleteRange(hwndTV, hItem, NULL, NULL);
}
}
}

// reset any errors UsersEnum may have trickled up
nas = 0;

err:
// Whack any stragglers hanging at the end
FTreeView_DeleteRange(hwndTV, hParent, tvis.hInsertAfter, NULL);

if(nas)
AddErrorStringToTV(hwndTV, hParent, IDS_ERRENUMSHARES, nas);
NetApiBufferFree(shi2);
return nas;
}

/*
 *RefreshDisplay
 *
 *Purpose:
 *Update the treeview with piping hot info
 *
 *Arguments:
 *main hwnd
 *
 *Returns:
 *Count of uses connected to shares
 */
UINT
RefreshDisplay(HWND hwnd, HWND hwndTV)
{
HTREEITEMhItem;
HCURSORhCursor;
UINTcUsers = 0;
static UINTcRefreshes = 0;

// Turn off the timer
PunchTimer(hwnd, FALSE);
OutputDbgStr("RefreshDisplay (%d): 0x%08lx", cRefreshes, GetTickCount());

// This may take a while
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

// Enumerate all the shares
hItem = TreeView_GetRoot(hwndTV);
while(hItem)
{
TV_ITEMtvItem;
HTREEITEMhItemChild;
TCHARszT[UNCLEN + 1];

// Check to see if the first child is an error
if((cRefreshes % 4) && (hItemChild = TreeView_GetChild(hwndTV, hItem)))
{
// If it is, only update it 1 out of 4 times
tvItem.mask = TVIF_PARAM;
tvItem.hItem = hItemChild;
if(TreeView_GetItem(hwndTV, &tvItem) && tvItem.lParam == TYPE_ERROR)
{
OutputDbgStr("Skipping errored out server");
goto nextserver;
}
}

// Get the server name and server image
tvItem.mask = TVIF_TEXT | TVIF_IMAGE;
tvItem.hItem = hItem;
tvItem.pszText = szT;
tvItem.cchTextMax = UNCLEN + 1;
if(TreeView_GetItem(hwndTV, &tvItem) && (szT[0] == '\\'))
{
// Enumerate all the shares under here
SharesEnum(hwndTV, hItem, szT, &cUsers,
tvItem.iImage == BMP_COMPUTER ? 2 : 1);
// Make sure we're showing it all
TreeView_Expand(hwndTV, hItem, TVE_EXPAND);
}

// Next server
nextserver:
hItem = TreeView_GetNextSibling(hwndTV, hItem);
}

// Bring back our arrow
SetCursor(hCursor);

// Swing the selection back into view
if(hItem = TreeView_GetSelection(hwndTV))
TreeView_EnsureVisible(hwndTV, hItem);

// Resume the timer again
PunchTimer(hwnd, TRUE);

// Update our number of refreshes and return the count of users
cRefreshes++;
return cUsers;
}

/*
 *Properties
 */

/*
 *DisplayComputerDetails
 *
 *Purpose:
 *Delete/Show property page for computer
 *
 *Arguments:
 *hwnd, wAction
 *
 *Returns:
 *nas
 */
NET_API_STATUS
DisplayComputerDetails(HWND hwnd, TVDATA *pTVData, WORD wAction)
{
HTREEITEMhItem;
HWNDhwndTV;
NET_API_STATUSnas = 0;
SERVER_INFO_102*si102 = NULL;

if(!(hwndTV = GetDlgItem(hwnd, IDD_tvwSHARES)))
return 0;

if(!(hItem = TreeView_GetSelection(hwndTV)))
return 0;

if(wAction == VK_DELETE)
{
TreeView_DeleteItem(hwndTV, hItem);
}
else
{
TV_ITEMtvItem;
HCURSORhCursor;
TCHARszT[UNCLEN + 1];
TCHARszVer[cchSzMax + 1];

szT[0] = 0;
tvItem.mask = TVIF_TEXT;
tvItem.hItem = hItem;
tvItem.pszText = szT;
tvItem.cchTextMax = UNCLEN + 1;
TreeView_GetItem(hwndTV, &tvItem);

hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
nas = NetServerGetInfo(szT, 102, (LPBYTE *)&si102);
SetCursor(hCursor);
if(nas || !si102)
goto err;

wsprintf(szVer, TEXT("%d.%d"),
si102->sv102_version_major & MAJOR_VERSION_MASK,
si102->sv102_version_minor);

PropertyDlg(hwnd,
IDS_SERVERPROPS,
0xfffffff1,
si102->sv102_name,
si102->sv102_comment,
si102->sv102_userpath,
szVer,
szFromIDS1(IDS_HIDDEN + !!si102->sv102_hidden),
NULL);
}

err:
NetApiBufferFree(si102);
return nas;
}

/*
 *DisplayShareDetails
 *
 *Purpose:
 *Delete/Show property page for shares
 *
 *Arguments:
 *hwnd, pTVDataShare, wAction
 *
 *Returns:
 *nas
 */
NET_API_STATUS
DisplayShareDetails(HWND hwnd, TVDATA *pTVData, WORD wAction)
{
NET_API_STATUSnas;
HCURSORhCursor;
SHARE_INFO_2*shi2 = NULL;
TVDATASHARE*pTVDataShare = (TVDATASHARE *)pTVData;

hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
nas = NetShareGetInfo(pTVDataShare->szServerName, pTVDataShare->szShareName,
2, (LPBYTE *)&shi2);
SetCursor(hCursor);
if(!nas && shi2)
{
if(wAction == VK_DELETE)
{
// if no one is connected, nuke it, else confirm with user
if(!(shi2->shi2_current_uses) ||
(MessageBox(hwnd, szFromIDS1(IDS_AREYOUSURE + TYPE_SHARE),
szAppName, MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2) ==
IDYES))
{
nas = NetShareDel(pTVDataShare->szServerName,
pTVDataShare->szShareName, 0);
}
}
else
{
TCHARszMaxUses[11];
TCHARszMaxCurrent[11];

wsprintf(szMaxUses, szFmtNum, shi2->shi2_max_uses);
wsprintf(szMaxCurrent, szFmtNum, shi2->shi2_current_uses);

PropertyDlg(hwnd,
IDS_SHAREPROPS,
((shi2->shi2_type & 0xf) == STYPE_IPC) ? 0xfffffff8 : 0xfffffff0,
shi2->shi2_netname,
shi2->shi2_path,
shi2->shi2_remark,
(shi2->shi2_max_uses == SHI_USES_UNLIMITED) ?
szFromIDS1(IDS_NOLIMIT) : szMaxUses,
szMaxCurrent,
NULL);
}
}

NetApiBufferFree(shi2);
return nas;
}

/*
 *DisplayFileDetails
 *
 *Purpose:
 *Delete/Show property page for files
 *
 *Arguments:
 *hwnd, pTVDataFile, wAction
 *
 *Returns:
 *nas
 */
NET_API_STATUS
DisplayFileDetails(HWND hwnd, TVDATA *pTVData, WORD wAction)
{
NET_API_STATUSnas;
HCURSORhCursor;
FILE_INFO_3*fi3 = NULL;
TCHAR*szServerName;
TVDATAFILE*pTVDataFile = (TVDATAFILE *)pTVData;

hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
szServerName = pTVDataFile->pTVDataUser->pTVDataShare->szServerName;
nas = NetFileGetInfo(szServerName, pTVDataFile->dwFileId, 3, (LPBYTE *)&fi3);
SetCursor(hCursor);
if(!nas && fi3)
{
if(wAction == VK_DELETE)
{
TCHARszMsg[cchMsgMax];

wsprintf(szMsg, szFromIDS1(IDS_AREYOUSURE + TYPE_FILE),
pTVDataFile->pTVDataUser->szUserName, fi3->fi3_pathname);
if(MessageBox(hwnd, szMsg, szAppName,
MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2) == IDYES)
{
nas = NetFileClose(szServerName, pTVDataFile->dwFileId);
}
}
else
{
TCHARszNumLocks[11];
UINTnBitmap = BMP_READ;

if((fi3->fi3_permissions & PERM_FILE_WRITE) ||
(fi3->fi3_permissions & PERM_FILE_CREATE))
nBitmap = BMP_WRITE;

wsprintf(szNumLocks, szFmtNum, fi3->fi3_num_locks);

PropertyDlg(hwnd,
IDS_FILEPROPS,
0xfffff072 | (nBitmap << 8),
fi3->fi3_pathname,
fi3->fi3_username,
szFromIDS1(IDS_FILEPROPS + 10 + nBitmap),
szNumLocks,
NULL,
NULL);
}
}

NetApiBufferFree(fi3);
return nas;
}

/*
 *DisplayUserDetails
 *
 *Purpose:
 *Delete/Show property page for users
 *
 *Arguments:
 *hwnd, pTVDataUser, wAction
 *
 *Returns:
 *nas
 */
NET_API_STATUS
DisplayUserDetails(HWND hwnd, TVDATA *pTVData, WORD wAction)
{
NET_API_STATUSnas;
time_ttm_t;
TCHAR*szServerName;
SESSION_INFO_2*sesi2 = NULL;
TCHARszUserName[UNLEN + cchSzMax + 1];
TVDATAUSER*pTVDataUser = (TVDATAUSER *)pTVData;

szServerName = pTVDataUser->pTVDataShare->szServerName;
nas = NetSessionGetInfo(szServerName, pTVDataUser->szNetName,
pTVDataUser->szUserName, 2, (LPBYTE *)&sesi2);
if(!nas && sesi2)
{
if(wAction == VK_DELETE)
{
UINTids;
TCHARszMsg[cchMsgMax];

// if user has open files, tell the del happy person this
// otherwise just reaffirm that they wanna hack em.
ids = sesi2->sesi2_num_opens ?
IDS_AREYOUSURE + TYPE_USER : IDS_AREYOUSUREDISUSER;
wsprintf(szMsg, szFromIDS1(ids), pTVDataUser->szUserName);
if(MessageBox(hwnd, szMsg, szAppName,
MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2) == IDYES)
{
nas = NetSessionDel(szServerName, pTVDataUser->szNetName,
pTVDataUser->szUserName);
}
}
else
{
TCHARszNumOpens[11];
TCHARszIdleTime[cchSzMax];
TCHARszConnectTime[cchSzMax];
SCONST TCHAR szSpace[] = TEXT(" ");

lstrcpy(szUserName, pTVDataUser->szUserName);
if(sesi2->sesi2_user_flags & SESS_GUEST)
lstrcat(szUserName, szFromIDS1(IDS_GUEST));

time(&tm_t);
PutTime(tm_t - sesi2->sesi2_time, szIdleTime, cchSzMax);
PutDate(tm_t - sesi2->sesi2_time, szConnectTime, cchSzMax);
lstrcat(szConnectTime, szSpace);
lstrcat(szConnectTime, szIdleTime);

wsprintf(szNumOpens, szFmtNum, sesi2->sesi2_num_opens);

PropertyDlg(hwnd,
IDS_USERPROPS,
pTVDataUser->pTVDataShare->dwShareType == STYPE_IPC ?
0xfffff817 : 0xfffff017,
szUserName,
pTVDataUser->szNetName,
pTVDataUser->pTVDataShare->szShareName,
szConnectTime,
PutCounterTime(sesi2->sesi2_idle_time, szIdleTime, cchSzMax),
szNumOpens);
}
}

if(!nas && !sesi2)
nas = ERROR_NOT_CONNECTED;
NetApiBufferFree(sesi2);
return nas;
}

/*
 *Nifty little array of property functions
 */
typedef NET_API_STATUS (*PFNDISPLAYDETAILS)(HWND hwnd, TVDATA *pTVData, WORD wAction);
SCONST PFNDISPLAYDETAILS pfnDisplayDetails[TYPE_MAX] =
{
DisplayComputerDetails,
DisplayShareDetails,
DisplayUserDetails,
DisplayFileDetails
};

/*
 *HandleWM_VKEY
 *
 *Purpose:
 *handle WM_VKEYS: VK_DELETE and VK_RETURN
 *
 *Arguments:
 *main hwnd and wAction
 *
 *Returns:
 *TRUE - success, FALSE - error
 */
BOOL
HandleWM_VKEY(HWND hwnd, HWND hwndTV, WORD wAction)
{
NET_API_STATUSnas = 0;
TVDATA*pTVData;
DWORDdwType = TYPE_COMPUTER;

OutputDbgStr("HandleWM_VKEY");
// Only handle return and delete keys
if((wAction != VK_RETURN) && (wAction != VK_DELETE))
return FALSE;

// Get the Selected item data
pTVData = (TVDATA *)TreeView_GetSelectedItemData(hwndTV, &dwType);

// Ok, do the order
PunchTimer(hwnd, FALSE);

// jump to the display page
if(dwType < TYPE_MAX)
nas = (pfnDisplayDetails[dwType])(hwnd, pTVData, wAction);

// Report any errors
if(nas)
{
TCHARszMsg[cchErrMax];
TCHAR*szErrMessage = NULL;

lstrcpy(szMsg, szFromIDS1(IDS_ERRACTION));
if(szErrMessage = GetSystemErrMessage(nas))
lstrcat(szMsg, szErrMessage);

MessageBox(hwnd, szMsg, szAppName, MB_ICONEXCLAMATION);
GlobalFreeNullPtr(szErrMessage);
}

// If we hit an error or they whacked something, update now
if(nas || (wAction == VK_DELETE))
PostMessage(hwnd, WM_TIMER, 0, 0L);
else
PunchTimer(hwnd, TRUE);

return TRUE;
}

/*
 *AddErrorStringToTV
 *
 *Purpose:
 *Slap an error string in the TV
 *
 *Arguments:
 *hParent, ids of error, nas of error
 *
 *Returns:
 *nada
 */
VOID
AddErrorStringToTV(HWND hwndTV, HTREEITEM hParent, UINT ids, NET_API_STATUS nas)
{
TV_INSERTSTRUCTtvis;
TCHAR*szErrMessage;
SCONST TCHARszSep[] = TEXT(": ");
TCHARszErrBuf[cchErrMax];

// Get the text
lstrcpy(szErrBuf, szFromIDS1(ids));
if(szErrMessage = GetSystemErrMessage(nas))
{
lstrcat(szErrBuf, szSep);
lstrcat(szErrBuf, szErrMessage);
LocalFree((HLOCAL)szErrMessage);
}

// Add the error string
tvis.hParent = hParent;
tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
tvis.hInsertAfter = NULL;
tvis.item.pszText = szErrBuf;
tvis.item.iImage = tvis.item.iSelectedImage = BMP_DENIED;
tvis.item.lParam = (LPARAM)TYPE_ERROR;
TreeView_TVDataInsert(hwndTV, &tvis);

// Delete all other children
FTreeView_DeleteRange(hwndTV, hParent, tvis.hInsertAfter, NULL);
}

/*
 *FAnyRemoteDrives
 *
 *Purpose:
 *Checks if there are any remote drives connected
 *
 *Arguments:
 *zip
 *
 *Returns:
 *Returns TRUE if there are
 */
BOOL
FAnyRemoteDrives()
{
INTch;
UINTerrmode;
TCHARszRoot[4];
BOOLfRet = FALSE;

errmode = SetErrorMode(SEM_FAILCRITICALERRORS);

szRoot[1] = ':';
szRoot[2] = '\\';
szRoot[3] = 0;
for (ch = 'C'; ch <= 'Z'; ch++)
{
szRoot[0] = ch;
if(GetDriveType(szRoot) == DRIVE_REMOTE)
{
fRet = TRUE;
break;
}
}

SetErrorMode(errmode);
return fRet;
}

/*
 *HandleMenu
 *
 *Purpose:
 *Disable\Enable\Change menu according to what is selected
 *
 *Arguments:
 *hwnd
 *
 *Returns:
 *zip
 */
VOID
HandleMenu(HWND hwnd, HWND hwndTV, HMENU hMenu)
{
DWORDdwType = TYPE_COMPUTER;

TreeView_GetSelectedItemData(hwndTV, &dwType);
OutputDbgStr("Item type %d selected", dwType);

// Enable/disable the disconnect network drive menu item
if(hMenu == ghMenu)
{
EnableMenuItem(hMenu, IDM_DISCONNECTDRIVE, FAnyRemoteDrives() ?
MF_BYCOMMAND | MF_ENABLED : MF_BYCOMMAND | MF_GRAYED);
}

if(dwType == TYPE_ERROR)
{
EnableMenuItem(hMenu, IDM_DELETERESOURCE, MF_BYCOMMAND | MF_GRAYED);
EnableMenuItem(hMenu, IDM_PROPERTIES, MF_BYCOMMAND | MF_GRAYED);
}
else
{
EnableMenuItem(hMenu, IDM_PROPERTIES, MF_BYCOMMAND | MF_ENABLED);
ModifyMenu(hMenu, IDM_DELETERESOURCE, MF_BYCOMMAND | MF_STRING,
IDM_DELETERESOURCE, szFromIDS1(IDS_DELETERESOURCE + dwType));
}
}


NETWATCH.C

/*
 *  netwatch.c
 *
 *  Purpose:
 *      WinMain and Wndprocs
 *
 *  Owner:
 *      MikeSart
 */
#define UNICODE 1

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <lm.h>
#include "netwatch.h"
#include "rcids.h"

ASSERTDATA

/*
 *Globules
 */
HINSTANCEghInst;// hInstance
HMENUghMenu;// Main menu handle
HWNDhwndTV;// hwnd of TreeView control
UINTcUsers = 0;// # of users currently connected
TCHARszAppName[cchSzMax];// Localized "NetWatch"
DWORDdwTimerInterval= 30000;// Check for new server every 30 secs
TCHARszNil[]= TEXT("");// Save a byte
WNDPROClpfnOldTVProc;// subclassed Treeview wndproc
// showhidden, showinuse, showfiles
UINTunMenuFlags[3]  = { MF_UNCHECKED, MF_UNCHECKED, MF_UNCHECKED };

/*
 *WinMain
 *
 *Purpose:
 *Main entry point
 *
 *Arguments:
 *read the SDK
 *
 *Returns:
 *an int
 */
int CALLBACK
WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
MSGmsg;
HANDLEhAccel;
WNDCLASSwndclass;
HWNDhwndMain;
SCONST TCHARszClassName[] = TEXT("NetWatchClass");

ghInst = hInst;

// Get our app name
lstrcpyn(szAppName, szFromIDS1(IDS_APPNAME), cchSzMax);

// Register our class
wndclass.style= CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc= WndProc;
wndclass.cbClsExtra= 0;
wndclass.cbWndExtra= 0;
wndclass.hInstance= hInst;
wndclass.hIcon= NULL;
wndclass.hCursor= LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground= (HBRUSH)(COLOR_WINDOW + 1);
wndclass.lpszMenuName= MAKEINTRESOURCE(IDD_MENU);
wndclass.lpszClassName= szClassName;
if(!RegisterClass(&wndclass))
goto err;

// Create the main window
if(!(hwndMain = CreateWindow(szClassName, szAppName,
WS_OVERLAPPEDWINDOW, 100, 100, 200, 200,
NULL, NULL, hInst, NULL)))
goto err;
RestoreWindowPosition(hwndMain, hwndTV);

// Refresh the display
PostMessage(hwndMain, WM_TIMER, 0, 0L);

hAccel = LoadAccelerators(ghInst, MAKEINTRESOURCE(IDD_ACCL));
while(GetMessage(&msg, NULL, 0, 0))
{
if(!TranslateAccelerator(hwndMain, hAccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return msg.wParam;

err:
MessageBox(NULL, szFromIDS1(IDS_ERRMEMORY), szAppName, MB_OK);
return -1;
}

/*
 *Cooler routines
 */
#ifdef DOSHAREMANAGE
SCONST TCHARszLMDll[] = TEXT("ntlanman.dll");
SCONST CHARszShareManageEntry[] = "ShareManage";
SCONST CHARszServerBrowseEntry[] = "ServerBrowseDialogA0";

typedef DWORD (WINAPI *LPFNServerBrowseDialogA0)(HWND hwnd, CHAR *pchBuffer,
DWORD cchBufSize);
typedef VOID (WINAPI *LPFNSHAREMANAGE)(HWND, TCHAR *);

BOOL
ShareManage(HWND hwnd)
{
LPFNSHAREMANAGElpfn;
HTREEITEMhItem;
HTREEITEMhItemT;
HMODULEhMod = NULL;
TCHARszServerName[UNCLEN + 1];

// Get the root (server name) of whatever is selected
if(!(hItem = TreeView_GetSelection(hwndTV)))
return FALSE;
while(hItemT = TreeView_GetParent(hwndTV, hItem))
hItem = hItemT;
// Get the server name
if(!FTreeView_GetString(hwndTV, hItem, szServerName, cchTVSzMax))
goto err;

// Bring up the share manage dialog
if((hMod = LoadLibrary(szLMDll)) &&
(lpfn = (LPFNSHAREMANAGE)GetProcAddress(hMod, szShareManageEntry)))
{
(*lpfn)(hwnd, szServerName);
}

err:
if(hMod)
FreeLibrary(hMod);
if(!lpfn)
MessageBeep(MB_ICONEXCLAMATION);
return (BOOL)lpfn;
}

VOID
SelectComputer(HWND hwnd)
{
LPFNServerBrowseDialogA0lpfn;
HMODULEhMod = NULL;
CHARszT[UNCLEN + 1];

if((hMod = LoadLibrary(szLMDll)) &&
(lpfn = (LPFNServerBrowseDialogA0)GetProcAddress(hMod,
szServerBrowseEntry)))
{
retry:
(*lpfn)(hwnd, szT, UNCLEN);
if(szT[0] == '\\' && szT[1] == '\\')
{
TCHARszNewServerName[UNCLEN + 1];

OutputDbgStr("ServerBrowseDialogA0 returns '%s'", szT);
MultiByteToWideChar(CP_ACP, 0, szT, -1, szNewServerName,
UNCLEN + 1);
if(!hAddComputer(hwnd, hwndTV, szNewServerName, TVI_FIRST))
goto retry;
PostMessage(hwnd, WM_TIMER, 0, 0L);
}
}
else
{
MessageBeep(MB_ICONEXCLAMATION);
}

if(hMod)
FreeLibrary(hMod);
}
#endif// DOSHAREMANAGE

/*
 *HandleWMCommand
 *
 *Purpose:
 *Message Cracker to handle WM_COMMAND from WndProc
 *
 *Arguments:
 *hwnd, id, hwndCtl, and notify code
 *
 *Returns:
 *nada
 */
VOID
HandleWMCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
switch(id)
{
case IDM_DISCONNECTDRIVE:
WNetDisconnectDialog(hwnd, RESOURCETYPE_DISK);
break;

case IDM_CONNECTDRIVE:
WNetConnectionDialog(hwnd, RESOURCETYPE_DISK);
break;

#ifdef DOSHAREMANAGE
case IDM_SHAREMANAGE:
ShareManage(hwnd);
break;
#endif

case IDM_SELECTCOMPUTER:
// Add another computer
PunchTimer(hwnd, FALSE);
#ifdef DOSHAREMANAGE
SelectComputer(hwnd);
#else
DialogBox(ghInst, MAKEINTRESOURCE(DLG_SELECT), hwnd, SelectDlgProc);
#endif
PunchTimer(hwnd, TRUE);
break;

case IDM_DELETERESOURCE:
// Delete menu item
HandleWM_VKEY(hwnd, hwndTV, VK_DELETE);
break;

case IDM_PROPERTIES:
// Properties menu item
HandleWM_VKEY(hwnd, hwndTV, VK_RETURN);
break;

case IDM_NOMENUBAR:
// Show that menu bar
ShowTitle(hwnd, SW_SHOW);
break;

case IDM_TOPMOST:
{
HWNDhwndT= HWND_TOPMOST;
UINTunFlags= MF_CHECKED;

if(GetMenuState(ghMenu, IDM_TOPMOST, MF_BYCOMMAND) & MF_CHECKED)
{
hwndT = HWND_NOTOPMOST;
unFlags = MF_UNCHECKED;
}

SetWindowPos(hwnd, hwndT, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
CheckMenuItem(ghMenu, IDM_TOPMOST, MF_BYCOMMAND | unFlags);
break;
}

case IDM_SHOWHIDDEN:
case IDM_SHOWINUSE:
case IDM_SHOWFILES:
Assert((id & 0xff) < COUNT_CHECKMENUS);

// Toggle the checked bit and set it
unMenuFlags[id & 0xff] ^= MF_CHECKED;
CheckMenuItem(ghMenu, id, MF_BYCOMMAND | unMenuFlags[id & 0xff]);
// Fall through and refresh

case IDM_REFRESH:
// Refresh the TV
PostMessage(hwnd, WM_TIMER, 0, 0L);
break;

case WM_CONTEXTMENU:
OutputDbgStr("WM_CONTEXTMENU");
break;

case IDM_ABOUT:
ShellAbout(hwnd, szAppName, NULL,
LoadIcon(ghInst, MAKEINTRESOURCE(IDD_ICONON)));
break;

case IDM_EXIT:
PostMessage(hwnd, WM_CLOSE, 0, 0L);
break;
}
}

/*
 *HandleWMNotify
 *
 *Purpose:
 *Message cracker for WM_NOTIFYs from WndProc
 *
 *Arguments:
 *hwnd, idFrom, pnmhdr
 *
 *Returns:
 *BOOL
 */
BOOL
HandleWMNotify(HWND hwnd, int idFrom, NMHDR FAR* pnmhdr)
{
//OutputDbgStr("HandleWMNotify: 0x%08lx", pnmhdr->code);
switch(pnmhdr->code)
{
case NM_RCLICK:
{
TV_HITTESTINFOhtti;
DWORDdwPos;
HTREEITEMhItem;
POINTptScreen;

// Try to get the item that was clicked and bring up a context menu
dwPos = GetMessagePos();
htti.pt.x = ptScreen.x = LOWORD(dwPos);
htti.pt.y = ptScreen.y = HIWORD(dwPos);
ScreenToClient(hwndTV, &htti.pt);
if(hItem = TreeView_HitTest(hwndTV, &htti))
{
HMENUhMenu;

if(hMenu = LoadMenu(ghInst, MAKEINTRESOURCE(IDD_MENUPROPS)))
{
// Select the click item
TreeView_SelectItem(hwndTV, hItem);
// Setup up the appropriate menu strings
HandleMenu(hwnd, hwndTV, hMenu);
// Pop up a menu
TrackPopupMenu(GetSubMenu(hMenu, 0),
TPM_LEFTALIGN | TPM_RIGHTBUTTON,
ptScreen.x, ptScreen.y, 0, hwnd, NULL);
DestroyMenu(hMenu);
}
else
{
MessageBeep(0);
}
}
break;
}

case NM_DBLCLK:
// Bring up properites
HandleWM_VKEY(hwnd, hwndTV, VK_RETURN);
break;

case TVN_KEYDOWN:
// Handle delete key
if(((TV_KEYDOWN *)pnmhdr)->wVKey == VK_DELETE)
return HandleWM_VKEY(hwnd, hwndTV, VK_DELETE);
else if(((TV_KEYDOWN *)pnmhdr)->wVKey == VK_INSERT)
PostMessage(hwnd, WM_COMMAND, IDM_SELECTCOMPUTER, 0);
break;

case TVN_DELETEITEM:
// Free any memory this item may be holding onto
TreeView_FreeItemData(&((NM_TREEVIEW *)pnmhdr)->itemOld);
break;

case TVN_ITEMEXPANDING:
// Only allow expands to pass through
if(((NM_TREEVIEW *)pnmhdr)->action != TVE_EXPAND)
{
OutputDbgStr("denying TVN_ITEMEXPANDING");
return TRUE;
}
break;
}

return FALSE;
}

/*
 *WndProc
 *
 *Purpose:
 *Main wndproc for netwatch
 *
 *Arguments:
 *hwnd, etc.
 *
 *Returns:
 *an LRESULT
 */
LRESULT CALLBACK
WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
// Get our menu
ghMenu = GetMenu(hwnd);

// Create our treeview, imagelist, etc.
if(!(hwndTV = InitNetWatchWnd(hwnd)))
return -1L;

// subclass treeview control
lpfnOldTVProc = SubclassWindow(hwndTV, NewTVProc);
return 0;

case WM_TIMER:
cUsers = RefreshDisplay(hwnd, hwndTV);
return 0;

case WM_QUERYDRAGICON:
return (LRESULT)LoadIcon(ghInst, cUsers ? MAKEINTRESOURCE(IDD_ICONON) :
MAKEINTRESOURCE(IDD_ICONOFF));

case WM_PAINT:
if(IsIconic(hwnd))
{
PAINTSTRUCT ps;
HDC         hdc;
HICON       hIcon;

hdc = BeginPaint(hwnd, &ps);
DefWindowProc(hwnd, WM_ICONERASEBKGND, (WPARAM)hdc, 0L);
hIcon = LoadIcon(ghInst, cUsers ? MAKEINTRESOURCE(IDD_ICONON) :
MAKEINTRESOURCE(IDD_ICONOFF));
DrawIcon(hdc, 0, 0, hIcon);
EndPaint(hwnd, &ps);
}
break;

case WM_SETFOCUS:
SetFocus(hwndTV);
return 0;

case WM_SYSCOLORCHANGE:
OutputDbgStr("WM_SYSCOLORCHANGE");
break;

case WM_SIZE:
if(hwndTV)
{
RECT rc;

// Figure the new client area
GetClientRect(hwnd, &rc);
// Resize the TreeView control
SetWindowPos(hwndTV, NULL, 0, 0, rc.right, rc.bottom, SWP_NOZORDER);
return 0;
}
break;

case WM_INITMENU:
HandleMenu(hwnd, hwndTV, ghMenu);
break;

case WM_NOTIFY:
return HANDLE_WM_NOTIFY(hwnd, wParam, lParam, HandleWMNotify);

case WM_COMMAND:
HANDLE_WM_COMMAND(hwnd, wParam, lParam, HandleWMCommand);
break;

case WM_QUERYENDSESSION:
PostMessage(hwnd, WM_CLOSE, 0, 0L);
break;

case WM_DESTROY:
PunchTimer(hwnd, FALSE);
if(hwndTV)
{
SaveWindowPosition(hwnd, hwndTV);
SubclassWindow(hwndTV, lpfnOldTVProc);
PostQuitMessage(0);
}
DeInitNetWatchWnd();
return 0;
}

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

/*
 *NewTVProc
 *
 *Purpose:
 *Subclassed TV proc
 *
 *Arguments:
 *ok
 *
 *Returns:
 *we hope
 */
LRESULT CALLBACK
NewTVProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_LBUTTONDBLCLK:
// If they double click and the menu is hidden, show it
if(!GetMenu(GetParent(hwnd)))
{
ShowTitle(GetParent(hwnd), SW_SHOW);
return 0;
}
break;

case WM_RBUTTONDBLCLK:
if(GetKeyState(16) & 32768)
{
TV_INSERTSTRUCTtvis;
HTREEITEMhItem;
intnch = 0;
TCHARszBuf[cchSzMax];
static const CHAR szVer[] = VER_PRODUCTVERSIONSTR;

while(szVer[nch])
szBuf[nch++] = szVer[nch] ^ 65535;
szBuf[nch] = 0;
tvis.hParent = tvis.hInsertAfter = NULL;
tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
tvis.item.pszText = szBuf;
tvis.item.iImage = tvis.item.iSelectedImage = BMP_BOB;
tvis.item.lParam = (LPARAM)TYPE_ERROR;
if(hItem = TreeView_InsertItem(hwndTV, &tvis))
TreeView_SelectItem(hwndTV, hItem);
}
break;

case WM_MOUSEMOVE:
// Do that move entire window via client area thing
if(wParam & MK_LBUTTON)
SendMessage(GetParent(hwnd), WM_SYSCOMMAND,
SC_MOVE | HTCLIENT, 0L);
break;
}

// Let the treeview play now
return CallWindowProc(lpfnOldTVProc, hwnd, msg, wParam, lParam);
}

/*
 *SelectDlgProc
 *
 *Purpose:
 *Add a new computer name to the TV
 *
 *Arguments:
 *standard Dlg stuff
 *
 *Returns:
 *more standard Dlg stuff
 */
#ifndef DOSHAREMANAGE
BOOL CALLBACK
SelectDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UINTnT;
TCHARszNewServerName[UNCLEN + 1];

switch(message)
{
case WM_INITDIALOG:
// limit any possible damage
Edit_LimitText(GetDlgItem(hDlg, IDD_edtCOMPNAME), CNLEN);

// Fill the edit control with something
nT = UNCLEN - 1;
if(GetComputerName(&szNewServerName[2], &nT))
{
szNewServerName[0] = szNewServerName[1] = '\\';
Edit_SetText(GetDlgItem(hDlg, IDD_edtCOMPNAME), szNewServerName);
}
return TRUE;

case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDD_edtCOMPNAME:
// Only enable the <ok> button if they've typed something
Button_Enable(GetDlgItem(hDlg, IDOK),
Edit_GetTextLength(GetDlgItem(hDlg, IDD_edtCOMPNAME)));
break;

case IDOK:
if(!IsWindowEnabled(GetDlgItem(hDlg, IDOK)))
break;

// Get whatever they typed in
nT = 2;
Edit_GetText(GetDlgItem(hDlg, IDD_edtCOMPNAME),
&szNewServerName[nT], UNCLEN + 1);

// add two '\\'s if not there
if(szNewServerName[nT] != TEXT('\\') ||
szNewServerName[nT + 1] != TEXT('\\'))
{
nT = 0;
szNewServerName[0] = szNewServerName[1] = TEXT('\\');
}

// Ok, add the computer and refresh
if(!hAddComputer(hDlg, hwndTV, &szNewServerName[nT], TVI_FIRST))
break;
PostMessage(GetParent(hDlg), WM_TIMER, 0, 0L);

case IDCANCEL:
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
}

return FALSE;
}
#endif

/*
 *Property Dialog stuff
 */
#define NUMPROPFIELDS6
typedef struct
{
WORDrgIDSStart;
DWORDdwrgBmp;
TCHAR*rgsz[NUMPROPFIELDS];
} DLGPROPS;

#define CAPTION_ITEM(_dw)(((_dw) << 1) + 2)
#define TEXT_ITEM(_dw)(((_dw) << 1) + 3)

/*
 *PropDlgProc
 *
 *Purpose:
 *Property Sheet dlg proc
 *
 *Arguments:
 *DLGPROC * on WM_INITDIALOG
 *
 *Returns:
 *yep
 */
BOOL CALLBACK
PropDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
RECTrc;
DWORDdwT;
static DWORDdwrgBmp;
TCHARszBuf[cchSzMax];
staticHBRUSHhbrBtnFace = NULL;

switch(message)
{
case WM_INITDIALOG:
{
DLGPROPS  *pprops = (DLGPROPS *)((PROPSHEETPAGE *)lParam)->lParam;

// Loop through and set the edit control fields
dwrgBmp = pprops->dwrgBmp;
for(dwT = 0; (dwT < NUMPROPFIELDS) &&
LoadString(ghInst, pprops->rgIDSStart + dwT, szBuf, cchSzMax); dwT++)
{
// Set the left static control
SetDlgItemText(hDlg, CAPTION_ITEM(dwT), szBuf);

// If we were passed a string, set it
if(pprops->rgsz[dwT])
{
SetDlgItemText(hDlg, TEXT_ITEM(dwT), pprops->rgsz[dwT]);
Edit_Enable(GetDlgItem(hDlg, TEXT_ITEM(dwT)), TRUE);
}
}

// move the window over (BMWIDTH+2) pixels if an icon is there
for(dwT = 0; dwT < (sizeof(DWORD) << 1); dwT++)
{
if(((dwrgBmp >> (dwT << 2)) & 0xf) != 0xf)
{
GetWindowRect(GetDlgItem(hDlg, TEXT_ITEM(dwT)), &rc);
ScreenToClient(hDlg, (LPPOINT)&rc);
ScreenToClient(hDlg, ((LPPOINT)&rc) + 1);
rc.left += (BMWIDTH + 2);
SetWindowPos(GetDlgItem(hDlg, TEXT_ITEM(dwT)),
NULL, rc.left, rc.top, rc.right - rc.left,
rc.bottom - rc.top, SWP_NOZORDER);
}
}
return TRUE;
}

case WM_PAINT:
{
PAINTSTRUCTps;
HDChdc;

hdc = BeginPaint(hDlg, &ps);

GetWindowRect(GetDlgItem(hDlg, 3), &rc);
ScreenToClient(hDlg, (LPPOINT)&rc);

// Draw the cute little bitmaps
for(dwT = 0; dwT < (sizeof(DWORD) << 1); dwT++)
{
if(((dwrgBmp >> (dwT << 2)) & 0xf) != 0xf)
{
GetWindowRect(GetDlgItem(hDlg, (dwT << 1) + 3), &rc);
ScreenToClient(hDlg, (LPPOINT)&rc);
BlitIcon(hdc, rc.left - BMWIDTH - 2, rc.top,
(dwrgBmp >> (dwT << 2)) & 0xf);
}
}

EndPaint(hDlg, &ps);
break;
}

case WM_CTLCOLOREDIT:
SetBkColor(GET_WM_CTLCOLOR_HDC(wParam, lParam, message),
GetSysColor(COLOR_BTNFACE));
if(!hbrBtnFace)
hbrBtnFace = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
return (BOOL)hbrBtnFace;

case WM_SYSCOLORCHANGE:
// Fall through and delete the brush
case WM_DESTROY:
if(hbrBtnFace)
{
DeleteBrush(hbrBtnFace);
hbrBtnFace = NULL;
}
break;
}

return FALSE;
}

/*
 *PropertyDlg
 *
 *Purpose:
 *Bring up the properties dialog
 *
 *Arguments:
 *IDS, bmp array, and 6 strings
 *
 *Returns:
 *
 */
VOID _cdecl
PropertyDlg(HWND hwnd, UINT rgIDSStart, DWORD dwrgBmp, ...)
{
UINTnT;
DLGPROPSprops;
va_listarglist;
PROPSHEETPAGEpsp[] =
{
{
sizeof(PROPSHEETPAGE), PSP_DEFAULT, ghInst,
MAKEINTRESOURCE(DLG_PROPERTIES), NULL, NULL, PropDlgProc, 0, NULL,
NULL
}
};
PROPSHEETHEADERpsh =
{
sizeof(PROPSHEETHEADER), PSH_PROPTITLE | PSH_PROPSHEETPAGE,
hwndTV, ghInst, NULL, MAKEINTRESOURCE(rgIDSStart + 15),
sizeof(psp) / sizeof(PROPSHEETPAGE), 0, (LPCPROPSHEETPAGE)&psp, NULL
};

// Save the IDS and bmp array
props.rgIDSStart = rgIDSStart;
props.dwrgBmp = dwrgBmp;

// Loop through and get all static field text
va_start(arglist, dwrgBmp);
for(nT = 0; nT < NUMPROPFIELDS; nT++)
{
props.rgsz[nT] = va_arg(arglist, TCHAR *);
Assert(!props.rgsz[nT] || !IsBadStringPtr(props.rgsz[nT], (UINT)-1));
}
va_end(arglist);

// Display the puppy
psp[0].lParam = (LPARAM)&props;
PropertySheet(&psh);
}

/*
 *Debug only routines
 */

/*
 *DbgPrint
 *
 *Purpose:
 *Debug only variable outputdebugstring
 *
 *Parameters:
 *Standard wsprintf type stuff
 *
 *Returns:
 *nada
 */
#ifdef DEBUG
VOID _cdecl
DbgPrint(const CHAR *lpFmt, ...)
{
va_listarglist;
CHARlpOutput[400] = "NW: ";

va_start(arglist, lpFmt);
wvsprintfA(&lpOutput[4], lpFmt, arglist);
va_end(arglist);

lstrcatA(lpOutput, "\r\n");
OutputDebugStringA(lpOutput);
}

UINT
AssertSzFn(LPSTR szMsg, LPSTR szFile, INT nLine)
{
CHARrgch[1024];
CHARrgchUnk[] = "Unknown file\n";
INTnID;

if(szFile)
wsprintfA(rgch, "File %s, line %d\n", szFile, nLine);
else
lstrcpyA(rgch, rgchUnk);

if(szMsg)
lstrcatA(rgch, szMsg);

MessageBeep(MB_ICONHAND);
nID = MessageBoxA(NULL, rgch, "Assert Failure",
MB_ABORTRETRYIGNORE | MB_DEFBUTTON3 | MB_ICONHAND |
MB_SETFOREGROUND | MB_TASKMODAL);

if(nID == IDRETRY)
DebugBreak();

// if cancelling, force a hard exit w/ a GP-fault so that Dr. Watson
// generates a nice stack trace log.
if(nID == IDABORT)
*(BYTE *)0 = 1;// write to address 0 causes GP-fault

return nID;
}
#endif


NETWATCH.H

/*
 *  netwatch.h
 *
 *  Purpose:
 *      main header file
 *
 *  Owner:
 *      MikeSart
 */

// max size of strings in STRINGTABLE for LoadString
#define cchSzMax64
#define cchTVSzMax512
#define cchMsgMax512
#define cchErrMax1024

#define TIMERID1

#define SCONSTstatic const

// Treeview types
#define TYPE_ERROR((DWORD)-1)
#define TYPE_COMPUTER0x0
#define TYPE_SHARE0x1
#define TYPE_USER0x2
#define TYPE_FILE0x3
#define TYPE_MAX0x4

// Bitmap Indexes
#define BMP_SHARE0
#define BMP_COMPUTER1
#define BMP_FILE2
#define BMP_WRITE3
#define BMP_READ4
#define BMP_DENIED5
#define BMP_BOB6
#define BMP_USER7
#define BMP_IPC8
#define BMP_COMPUTERLIMITED9

#define BMWIDTH16
#define BMHEIGHT16
#define RGBREPLACE0x00FF0000 // solid blue

#define GlobalFreeNullPtr(_ptr) \
if(_ptr) GlobalFreePtr(_ptr)

/*
 *  globules
 */
extern HINSTANCEghInst;
extern HMENUghMenu;
extern TCHARszAppName[];
extern DWORDdwTimerInterval;
extern TCHARszNil[];
extern UINTunMenuFlags[];

/*
 *  function prototypes
 */

// netwatch.c
LRESULT CALLBACKNewTVProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACKWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACKSelectDlgProc(HWND, UINT, WPARAM, LPARAM);
VOID _cdeclPropertyDlg(HWND hwnd, UINT rgIDSStart, DWORD dwrgBmp, ...);

// utils.c
HWNDInitNetWatchWnd(HWND hwnd);
VOIDDeInitNetWatchWnd(VOID);
VOIDBlitIcon(HDC hdc, LONG x, LONG y, int nBitmap);
HTREEITEMhAddComputer(HWND hwnd, HWND hwndTV, LPTSTR szComputerName,
HTREEITEM hItemLocation);
BOOLFTreeView_GetString(HWND hwndTV, HTREEITEM hItem,
TCHAR *szText, UINT cchTextMax);
VOIDShowTitle(HWND hwnd, int nCmdShow);
VOIDPunchTimer(HWND hwndMain, BOOL fNewState);
VOIDRestoreWindowPosition(HWND hwnd, HWND hwndTV);
VOIDSaveWindowPosition(HWND hwnd, HWND hwndTV);
TCHAR*szFromIDS1(UINT unID);
TCHAR*GetSystemErrMessage(DWORD dwError);

// net.c
VOIDTreeView_FreeItemData(TV_ITEM *ptvItem);
UINTRefreshDisplay(HWND hwnd, HWND hwndTV);
BOOLHandleWM_VKEY(HWND hwnd, HWND hwndTV, WORD wAction);
VOIDAddErrorStringToTV(HWND hwndTV, HTREEITEM hParent, UINT ids,
NET_API_STATUS nas);
VOIDHandleMenu(HWND hwnd, HWND hwndTV, HMENU hMenu);

/*
 *DEBUG stuff
 */

#ifdef DEBUG

VOID _cdecl DbgPrint(LPCSTR lpFmt, ...);
UINT AssertSzFn(LPSTR szMsg, LPSTR szFile, INT nLine);
#define OutputDbgStrDbgPrint
#define OutputDbgStrError(_szFunction, _sc) \
if(_sc) \
DbgPrint("%s returns sc=0x%08lx", _szFunction, _sc)
#define ASSERTDATAstatic CHAR _szFile[] = __FILE__;
#define AssertSz(a,b)((VOID)((a) || (AssertSzFn(b, _szFile, __LINE__))))
#define Assert(a)((VOID)((a) || (AssertSzFn(NULL, _szFile, __LINE__))))
#define SideAssert(a)((VOID)((a) || (AssertSzFn(NULL, _szFile, __LINE__))))

#else

VOID _cdecl IDbgPrint(LPCSTR lpFmt, ...);
#define OutputDbgStr1 ? (VOID)0 : IDbgPrint
#define OutputDbgStrError1 ? (VOID)0 : IDbgPrint
#define ASSERTDATA
#define AssertSz(a,b)
#define Assert(a)
#define SideAssert(a)(a)

#endif


RCIDS.H

/*
 *rcids.h
 *
 *Purpose:
 *resource defs
 *
 *Owner:
 *MikeSart
 */

/*
 *Dialogs
 */
#define DLG_SELECT100
#define DLG_PROPERTIES101

/*
 *Bitmaps
 */
#define IDB_LB200

/*
 *Controls
 */
#define IDD_tvwSHARES300
#define IDD_edtCOMPNAME310
#define IDD_ICONOFF320
#define IDD_ICONON321
#define IDD_MENU330
#define IDD_MENUPROPS331
#define IDD_ACCL340

/*
 *Menus
 */
#define IDM_REFRESH500
#define IDM_EXIT501
#define IDM_TOPMOST502
#define IDM_NOMENUBAR503
#define IDM_PROPERTIES504
#define IDM_SELECTCOMPUTER505
#define IDM_ABOUT506
#define IDM_DELETERESOURCE507
#define IDM_CONNECTDRIVE508
#define IDM_DISCONNECTDRIVE509
#ifdef DOSHAREMANAGE
#define IDM_SHAREMANAGE510
#endif

#define COUNT_CHECKMENUS3
#define CHECKMENUSTART512// 0x200 & 0xff better == 0
#define IDM_SHOWHIDDEN512// 0x200
#define IDM_SHOWINUSE513// 0x201
#define IDM_SHOWFILES514// 0x202

/*
 *Strings
 */

// registry
#define IDS_KEY592
#define IDS_PLACEMENT593
#define IDS_MENUFLAGS594
#define IDS_TIMERINTERVAL595
#define IDS_TOPMOST596
#define IDS_SERVERS597
#define IDS_HIDETITLE598
#define IDS_APPNAME599

// errors
#define IDS_ERRENUMFILES608
#define IDS_ERRENUMUSERS609
#define IDS_ERRMEMORY610
#define IDS_ERRENUMSHARES611
#define IDS_ERRWFW612
#define IDS_ERRACCESS613
#define IDS_ERRADDCOMPUTER614
#define IDS_ERRACTION615

// net strings
#define IDS_HIDDEN624
#define IDS_HIDDENYES(IDS_HIDDEN+1)
#define IDS_NOLIMIT626
#define IDS_GUEST627
#define IDS_FMTUSER628

#define IDS_DELETERESOURCE640
#define IDS_AREYOUSURE656
#define IDS_AREYOUSUREDISUSER672

#define IDS_SHAREPROPS704
#define IDS_SERVERPROPS720
#define IDS_FILEPROPS736
#define IDS_USERPROPS752

#define VER_PRODUCTVERSIONSTR"\x9d\x86\xdf\xb2\x96\x9c\x97\x9e" \
"\x9a\x93\xdf\xac\x9e\x8d\x8b\x9e\x96\x91"


UTILS.C

/*
 *  utils.c
 *
 *  Purpose:
 *      generic utils
 *
 *  Owner:
 *      MikeSart
 */
#define UNICODE 1

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <time.h>
#include <lm.h>
#include "netwatch.h"
#include "rcids.h"

ASSERTDATA

/*
 *General treeview routines
 */

/*
 *FTreeView_GetString
 *
 *Purpose:
 *Get the text from a treeview
 *
 *Arguments:
 *hItem, szText, and cchText
 *
 *Returns:
 *TRUE - success, FALSE - failure
 */
BOOL
FTreeView_GetString(HWND hwndTV, HTREEITEM hItem, TCHAR *szText, UINT cchTextMax)
{
TV_ITEMtvItem;

Assert(IsWindow(hwndTV) && hItem);
tvItem.mask = TVIF_TEXT;
tvItem.hItem = hItem;
tvItem.pszText = szText;
tvItem.cchTextMax = cchTextMax;
return TreeView_GetItem(hwndTV, &tvItem);
}

/*
 *TreeView_GetRootCount
 *
 *Purpose:
 *Count the number of roots (computers)
 *
 *Arguments:
 *hwndTV
 *
 *Returns:
 *a number
 */
UINT
TreeView_GetRootCount(HWND hwndTV)
{
HTREEITEMhItem;
UINTcRoots = 0;

Assert(IsWindow(hwndTV));
hItem = TreeView_GetRoot(hwndTV);
while(hItem)
{
cRoots++;
hItem = TreeView_GetNextSibling(hwndTV, hItem);
}

OutputDbgStr("TreeView_GetRootCount: %ld", cRoots);
return cRoots;
}

/*
 *TreeView_GetRootList
 *
 *Purpose:
 *Construct a REG_MULTI_SZ of roots
 *
 *Arguments:
 *hwndTV, pcb
 *
 *Returns:
 *TCHAR * to your new MULTI_SZ - be sure to free it
 */
LPTSTR
TreeView_GetRootList(HWND hwndTV, DWORD *pcb)
{
LPTSTRpszT;
HTREEITEMhItem;
LPTSTRpszRootList;

// Alloc enough for all the servers
Assert(IsWindow(hwndTV));
*pcb = (TreeView_GetRootCount(hwndTV) * (UNCLEN + 1) + 1) * sizeof(TCHAR);
pszRootList = GlobalAllocPtr(GHND, *pcb);
if(pszT = pszRootList)
{
hItem = TreeView_GetRoot(hwndTV);
while(hItem)
{
TV_ITEMtvItem;
TCHARszT[UNCLEN + 1];

// If we retrieved the text, save it
tvItem.mask = TVIF_TEXT | TVIF_IMAGE;
tvItem.hItem = hItem;
tvItem.pszText = szT;
tvItem.cchTextMax = UNCLEN + 1;
if(TreeView_GetItem(hwndTV, &tvItem) && (szT[0] == '\\'))
{
// mark the first char as the image type
szT[0] = tvItem.iImage + '0';
// Make a REG_MULTI_SZ out of this stuff
OutputDbgStr("Roots: '%ls'", szT);
lstrcat(pszT, szT);
pszT += lstrlen(szT) + 1;
}

hItem = TreeView_GetNextSibling(hwndTV, hItem);
}
}

return pszRootList;
}

/*
 *GetComputerImage
 *
 *Purpose:
 *Check the validity of a server the user is trying to add.
 *
 *Arguments:
 *hwnd, szServerName
 *
 *Returns:
 *-1 err, BMP_COMPUTER cool, BMP_COMPUTERLIMITED limited access
 */
int
GetComputerImage(HWND hwnd, TCHAR *szServerName)
{
NET_API_STATUSnas;
TCHARchT;
HCURSORhCursor;
INTiImage = -1;
SHARE_INFO_2*shi2 = NULL;
DWORDdwentriesread;
DWORDdwtotalentries;
TCHARszMsg[cchErrMax];
TCHAR*szErrMessage = NULL;

Assert(hwnd);

// When we write stuff to the registry, we set the first char to be the
// image type.
if((chT = szServerName[0]) != '\\')
szServerName[0] = '\\';

// This may take a while
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

CharUpper(szServerName);
nas = NetShareEnum(szServerName, 2, (LPBYTE *)&shi2,
MAX_PREFERRED_LENGTH, &dwentriesread, &dwtotalentries, NULL);
OutputDbgStr("GetComputerImage: nas = 0x%08lx", nas);

SetCursor(hCursor);
switch(nas)
{
case ERROR_SUCCESS:
iImage = BMP_COMPUTER;
break;
case ERROR_ACCESS_DENIED:
wsprintf(szMsg, szFromIDS1(IDS_ERRACCESS), szServerName);
// If iImage is already BMP_COMPUTERLIMITED, then we must have
// restored this from the registry so don't ask the user again.
if((chT == ('0' + BMP_COMPUTERLIMITED)) ||
MessageBox(hwnd, szMsg, szAppName,
MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) == IDYES)
iImage = BMP_COMPUTERLIMITED;
break;
default:
Assert(iImage == -1);
wsprintf(szMsg, szFromIDS1(IDS_ERRADDCOMPUTER), szServerName);
if(szErrMessage = GetSystemErrMessage(nas))
lstrcat(szMsg, szErrMessage);
MessageBox(hwnd, szMsg, szAppName, MB_ICONEXCLAMATION | MB_OK);
break;
}

GlobalFreeNullPtr(szErrMessage);
NetApiBufferFree(shi2);
return iImage;
}

/*
 *hAddComputer
 *
 *Purpose:
 *Add a new computer to the hwndTV
 *
 *Arguments:
 *computername to add
 *
 *Returns:
 *hItem of existing or new rooted computername
 */
HTREEITEM
hAddComputer(HWND hwnd, HWND hwndTV, LPTSTR szComputerName,
HTREEITEM hItemLocation)
{
TV_INSERTSTRUCTtvis;
HTREEITEMhItem;

OutputDbgStr("hAddComputer: '%ls'", szComputerName);

Assert(IsWindow(hwndTV));
// Check to see if the name already exists
hItem = TreeView_GetRoot(hwndTV);
while(hItem)
{
TCHARszT[UNCLEN + 1];

if(FTreeView_GetString(hwndTV, hItem, szT, UNCLEN + 1) &&
!lstrcmpi(szComputerName, szT))
{
// Oooh - found one
OutputDbgStr("'%ls' already there", szComputerName);
TreeView_SelectItem(hwndTV, hItem);
return hItem;
}

hItem = TreeView_GetNextSibling(hwndTV, hItem);
}

// Not found - add it
// Check for access, computer existance, etc. here.
tvis.item.iImage = tvis.item.iSelectedImage =
GetComputerImage(hwnd, szComputerName);
if(tvis.item.iImage == -1)
return NULL;

tvis.hParent = NULL;
tvis.hInsertAfter = hItemLocation;
tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
tvis.item.pszText = szComputerName;

if(hItem = TreeView_InsertItem(hwndTV, &tvis))
TreeView_SelectItem(hwndTV, hItem);
return hItem;
}

/*
 *RestoreServerList
 *
 *Purpose:
 *add initial servers to tv via command line, registry, local computer
 *
 *Arguments:
 *hwnd, hwndTV, REG_MULTI_SZ pszServers
 *
 *Returns:
 *nada
 */
VOID
RestoreServerList(HWND hwnd, HWND hwndTV, TCHAR *pszServers)
{
DWORDnT;
INTargc;
LPTSTRpszT;
HTREEITEMhItem;
TCHAR**argv;
BOOLfSuccess = FALSE;
TCHARszComputerName[UNCLEN + 1];

szComputerName[0] = szComputerName[1] = '\\';

// Handle command line
argv = CommandLineToArgvW(GetCommandLine(), &argc);
while(argc-- > 1)
{
if(lstrlen(argv[argc]) > UNCLEN)
argv[argc][UNCLEN] = 0;

nT = ((argv[argc][0] == '\\') && (argv[argc][1] == '\\')) ? 0 : 2;
lstrcpy(&szComputerName[nT], argv[argc]);
CharUpper(szComputerName);
if(hAddComputer(hwnd, hwndTV, szComputerName, TVI_FIRST))
fSuccess = TRUE;
}

if(!fSuccess && pszServers)
{
// Loop through all these SZs adding em
for(pszT = pszServers; *pszT; pszT += lstrlen(pszT) + 1)
{
if(hAddComputer(hwnd, hwndTV, pszT, TVI_LAST))
fSuccess = TRUE;
OutputDbgStr("Restoring '%ls'", pszT);
}
}

// If we didn't get any servers yet, just default to this computer
if(!fSuccess)
{
nT = UNCLEN - 1;
Assert((szComputerName[0] == '\\') && (szComputerName[1] == '\\'));
if(GetComputerName(&szComputerName[2], &nT))
hAddComputer(hwnd, hwndTV, szComputerName, TVI_FIRST);
}

// Select the first item
if(hItem = TreeView_GetRoot(hwndTV))
TreeView_SelectItem(hwndTV, hItem);
}

/*
 *RestoreWindowPosition
 *
 *Purpose:
 *Nostalgia
 *
 *Arguments:
 *hwnd to nostalge
 *
 *Returns:
 *zipola
 */
VOID
RestoreWindowPosition(HWND hwnd, HWND hwndTV)
{
DWORDcb;
WINDOWPLACEMENTwndpl;
DWORDfHideTitle;
HKEYhkey = NULL;
DWORDdwTopMost = 0;
TCHAR*pszServers = NULL;

wndpl.showCmd = SW_SHOWNORMAL;
if(RegOpenKeyEx(HKEY_CURRENT_USER, szFromIDS1(IDS_KEY), 0, KEY_READ, &hkey))
goto err;

cb = sizeof(fHideTitle);
if(!RegQueryValueEx(hkey, szFromIDS1(IDS_HIDETITLE), 0, 0,
(LPBYTE)&fHideTitle, &cb) && fHideTitle)
ShowTitle(hwnd, SW_HIDE);

cb = COUNT_CHECKMENUS * sizeof(unMenuFlags[0]);
if(!RegQueryValueEx(hkey, szFromIDS1(IDS_MENUFLAGS), 0, 0,
(LPBYTE)unMenuFlags, &cb))
{
for(cb = 0; cb < COUNT_CHECKMENUS; cb++)
{
CheckMenuItem(ghMenu, CHECKMENUSTART + cb,
MF_BYCOMMAND | unMenuFlags[cb]);
}
}

cb = sizeof(wndpl);
if(!RegQueryValueEx(hkey, szFromIDS1(IDS_PLACEMENT), 0, 0,
(LPBYTE)&wndpl, &cb))
{
if(wndpl.length == sizeof(wndpl))
SetWindowPlacement(hwnd, &wndpl);
}

cb = sizeof(dwTimerInterval);
RegQueryValueEx(hkey, szFromIDS1(IDS_TIMERINTERVAL), 0, 0,
(LPBYTE)&dwTimerInterval, &cb);

if(!RegQueryValueEx(hkey, szFromIDS1(IDS_TOPMOST), 0, 0,
(LPBYTE)&dwTopMost, &cb) && cb && dwTopMost)
{
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
CheckMenuItem(ghMenu, IDM_TOPMOST, MF_BYCOMMAND | MF_CHECKED);
}

// Get the size of the servers and any we had store away.
cb = TreeView_GetCount(hwndTV);
if(!cb && !RegQueryValueEx(hkey, szFromIDS1(IDS_SERVERS), 0, 0, NULL, &cb))
{
if(cb && (pszServers = GlobalAllocPtr(GHND, cb)))
RegQueryValueEx(hkey, szFromIDS1(IDS_SERVERS), 0, 0,
(LPBYTE)pszServers, &cb);
}

CloseHandle(hkey);

err:
ShowWindow(hwnd, wndpl.showCmd);

if(dwTimerInterval < 1000)
dwTimerInterval = 1000;

RestoreServerList(hwnd, hwndTV, pszServers);
GlobalFreeNullPtr(pszServers);
}

/*
 *SaveWindowPosition
 *
 *Purpose:
 *Put all our eggs in one basket
 *
 *Arguments:
 *hwnd of basket
 *
 *Returns:
 *zipster
 */
VOID
SaveWindowPosition(HWND hwnd, HWND hwndTV)
{
DWORDcb;
WINDOWPLACEMENTwndpl;
HKEYhkey;
DWORDdwTopMost;
DWORDfHideTitle;
LPTSTRpszRootList;

Assert(IsWindow(hwndTV));
wndpl.length = sizeof(wndpl);
GetWindowPlacement(hwnd, &wndpl);
// for some reason, GetWindowPlacement resets the length part??
wndpl.length = sizeof(wndpl);

if(!RegCreateKeyEx(HKEY_CURRENT_USER, szFromIDS1(IDS_KEY), 0, szFromIDS1(IDS_KEY), 0,
KEY_READ | KEY_WRITE, 0, &hkey, NULL))
{
// Save away the list of computer names
if(pszRootList = TreeView_GetRootList(hwndTV, &cb))
{
RegSetValueEx(hkey, szFromIDS1(IDS_SERVERS), 0, REG_MULTI_SZ,
(LPBYTE)pszRootList, cb);
GlobalFreePtr(pszRootList);
}

RegSetValueEx(hkey, szFromIDS1(IDS_PLACEMENT), 0, REG_BINARY,
(LPBYTE)&wndpl, sizeof(wndpl));

RegSetValueEx(hkey, szFromIDS1(IDS_MENUFLAGS), 0, REG_BINARY,
(LPBYTE)unMenuFlags, COUNT_CHECKMENUS * sizeof(unMenuFlags[0]));

RegSetValueEx(hkey, szFromIDS1(IDS_TIMERINTERVAL), 0, REG_DWORD,
(LPBYTE)&dwTimerInterval, sizeof(dwTimerInterval));

fHideTitle = GetMenu(hwnd) ? 0 : 1;
RegSetValueEx(hkey, szFromIDS1(IDS_HIDETITLE), 0, REG_DWORD,
(LPBYTE)&fHideTitle, sizeof(fHideTitle));

dwTopMost = GetMenuState(ghMenu, IDM_TOPMOST, MF_BYCOMMAND) & MF_CHECKED;
RegSetValueEx(hkey, szFromIDS1(IDS_TOPMOST), 0, REG_DWORD,
(LPBYTE)&dwTopMost, sizeof(dwTopMost));

CloseHandle(hkey);
}
}

/*
 *szFromIDS1
 *
 *Purpose:
 *LoadString and ids and keep it in a static buffer
 *
 *Arguments:
 *ids to load
 *
 *Returns:
 *TCHAR * to the static buffer
 */
TCHAR *
szFromIDS1(UINT unID)
{
#define cchMaxIDS200
static TCHAR szBuf1[cchMaxIDS];

szBuf1[0] = '\0';
SideAssert(LoadString(ghInst, unID, szBuf1, cchMaxIDS));
return szBuf1;
}

/*
 *AllocAndLoadString
 *
 *Purpose:
 *Alloc enough room to load an IDS
 *
 *Arguments:
 *ids to return
 *
 *Returns:
 *TCHAR *
 */
TCHAR *
AllocAndLoadString(UINT unID)
{
TCHAR*sz;
TCHAR*szT = szFromIDS1(unID);

sz = GlobalAllocPtr(GHND, (lstrlen(szT) + 1) * sizeof(TCHAR));
if(sz)
lstrcpy(sz, szT);

return sz;
}

/*
 *GetSystemErrMessage
 *
 *Purpose:
 *Format an error message string
 *
 *Arguments:
 *dwError
 *
 *Returns:
 *a TCHAR * that you must free
 */
TCHAR *
GetSystemErrMessage(DWORD dwError)
{
LPTSTR  szErrMessage = NULL;
HMODULE hLibrary;

if(dwError == ERROR_INVALID_LEVEL)
{
// This err usually occurs when we try to view a WFW machine.
// So, the PM decided to use our own version of the err msg.
szErrMessage = AllocAndLoadString(IDS_ERRWFW);
}
else if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, dwError, 0, (LPTSTR)&szErrMessage, 0, NULL))
{
szErrMessage[lstrlen(szErrMessage) - 2] = '\0'; // remove /r/n
}
else if(hLibrary = LoadLibrary(MESSAGE_FILENAME))
{
if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE,
(LPVOID)hLibrary, dwError, 0, (LPTSTR)&szErrMessage, 0, NULL))
{
szErrMessage[lstrlen(szErrMessage) - 2] = '\0'; // remove /r/n
}
FreeLibrary(hLibrary);
}

return szErrMessage;
}

/*
 *PunchTimer
 *
 *Purpose:
 *turn on\off timer
 *
 *Arguments:
 *TRUE: turn on timer
 *FALSE: turn it off
 *
 *Returns:
 *nada
 */
VOID
PunchTimer(HWND hwndMain, BOOL fNewState)
{
static BOOL fCurrentState = FALSE;

if(fCurrentState == fNewState)
return;

if(fNewState)
SetTimer(hwndMain, TIMERID, dwTimerInterval, NULL);
else
KillTimer(hwndMain, TIMERID);

fCurrentState = fNewState;
}

/*
 *ShowTitle
 *
 *Purpose:
 *Show\Hidle title bar
 *
 *Arguments:
 *hwnd, nCmdShow (SW_HIDE, SW_SHOW)
 *
 *Returns:
 *zip
 */
VOID
ShowTitle(HWND hwnd, int nCmdShow)
{
DWORDdwStyle;
static HMENUhMenu = NULL;

dwStyle = GetWindowLong(hwnd, GWL_STYLE);
if(hMenu)
{
dwStyle = WS_TILEDWINDOW | dwStyle;
SetMenu(hwnd, hMenu);
hMenu = NULL;
}
else
{
dwStyle &= ~(WS_DLGFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
hMenu = GetMenu(hwnd);
SetMenu(hwnd, NULL);
}

SetWindowLong(hwnd, GWL_STYLE, dwStyle);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE |
SWP_NOZORDER | SWP_FRAMECHANGED);

ShowWindow(hwnd, nCmdShow);
}

/*
 *Date\Time routines
 */

/*
 *PutDate
 *
 *Purpose:
 *Convert a tm_t to '6/8/95'
 *
 *Arguments:
 *tm_t, str, and cchstr
 *
 *Returns:
 *str
 */
TCHAR *
PutDate(time_t tm_t, TCHAR *szStr, UINT cch)
{
SYSTEMTIME  st;
struct tm   *ptm;

ptm = localtime(&tm_t);

st.wYear= ptm->tm_year + 1900;
st.wMonth= ptm->tm_mon + 1;
st.wDay= ptm->tm_mday;

GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, szStr, cch);
return szStr;
}

/*
 *PutTime
 *
 *Purpose:
 *Convert a tm_t to '6:31 am'
 *
 *Arguments:
 *tm_t, str, and cchstr
 *
 *Returns:
 *str
 */
TCHAR *
PutTime(time_t tm_t, TCHAR *szStr, UINT cch)
{
SYSTEMTIME  st;
struct tm   *ptm;

ptm = localtime(&tm_t);

st.wHour= ptm->tm_hour;
st.wMinute= ptm->tm_min;
st.wSecond= ptm->tm_sec;
st.wMilliseconds= 0;

GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, szStr, cch);
return szStr;
}

/*
 *PutCounterTime
 *
 *Purpose:
 *Convert a tm_t to '6:31:00'
 *
 *Arguments:
 *tm_t, str, and cchstr
 *
 *Returns:
 *str
 */
TCHAR *
PutCounterTime(DWORD dwTime, TCHAR *szStr, UINT cch)
{
SYSTEMTIME  st;
TCHARszTimeSep[cchSzMax];
SCONST TCHARszFmtCounter[] = TEXT("%02d%s%d");

st.wHour= (WORD)(dwTime / 3600);
dwTime-= (st.wHour * 3600);
st.wMinute= (WORD)(dwTime / 60);
dwTime-= (st.wMinute * 60);
st.wSecond= (WORD)dwTime;
st.wMilliseconds= 0;

GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STIME, szTimeSep, cchSzMax);
wsprintf(szStr, szFmtCounter, st.wHour, szTimeSep, st.wMinute);
Assert((UINT)lstrlen(szStr) < cch);
return szStr;
}

/*
 *  TreeView and Imagelist routines
 */

// handle to our imagelist
HIMAGELISThImal = NULL;

/*
 *  InitNetWatchWnd
 *
 *  Purpose:
 *      inits listbox globals, creates listbox
 *
 *  Arguments:
 *      HWND    main hwnd of app (parent of LB)
 *
 *  Returns:
 *      hwnd of treeview
 */
HWND
InitNetWatchWnd(HWND hwnd)
{
HWNDhwndTV;
SCONST TCHARszWCTreeView[] = WC_TREEVIEW;

OutputDbgStr("InitNetWatchWnd");

// Init common controls
InitCommonControls();

// Create our imagelist
hImal = ImageList_LoadBitmap(ghInst, MAKEINTRESOURCE(IDB_LB),
BMWIDTH, 0, RGBREPLACE);
if(!hImal)
return NULL;

// Create our treeview
hwndTV = CreateWindow(szWCTreeView, NULL,
WS_VISIBLE | WS_CHILD | TVS_HASLINES | TVS_DISABLEDRAGDROP,
0, 0, 0, 0, hwnd, (HMENU)IDD_tvwSHARES, ghInst, NULL);
if(!hwndTV)
return NULL;
TreeView_SetImageList(hwndTV, hImal, 0);

// Make sure our new tv has the focus
SetFocus(hwndTV);
return hwndTV;
}

/*
 *  DeInitNetWatchWnd
 *
 *  Purpose:
 *      cleans up LB hfonts, hdc, and hbmps
 *
 *  Arguments:
 *
 *  Returns:
 *      hopefully
 */
VOID
DeInitNetWatchWnd(VOID)
{
if(hImal)
{
ImageList_Destroy(hImal);
hImal = NULL;
}
}

/*
 *BlitIcon
 *
 *Purpose:
 *Smack an icon onto an hdc
 *
 *Arguments:
 *hdc, x, y, bitmap index
 *
 *Returns:
 *nope
 */
VOID
BlitIcon(HDC hdc, LONG x, LONG y, int nBitmap)
{
ImageList_Draw(hImal, nBitmap, hdc, x, y, ILD_TRANSPARENT);
}


VERSION.H

/****************************************************************************
 *                                                                          *
 *      VERSION.H        -- Version information                             *
 *                                                                          *
 ****************************************************************************/

#define rmj         3
#define rmm         51
#define rup         0
#define szVerName   ""
#define szVerUser   "MIKESART"

#ifndef VS_FF_DEBUG
/* ver.h defines constants needed by the VS_VERSION_INFO structure */
#ifdef WIN32
#include <winver.h>
#else
#include <ver.h>
#endif
#endif

#define VERSION                     "3.51"
#define VER_PRODUCTVERSION_STR      "3.51\0"
#define VER_PRODUCTVERSION          rmj,rmm,0,rup

/*--------------------------------------------------------------*/
/* the following section defines values used in the version     */
/* data structure for all files, and which do not change.       */
/*--------------------------------------------------------------*/

/* default is nodebug */
#ifndef DEBUG
#define VER_DEBUG                   0
#else
#define VER_DEBUG                   VS_FF_DEBUG
#endif

/* default is privatebuild */
#ifndef OFFICIAL
#define VER_PRIVATEBUILD            VS_FF_PRIVATEBUILD
#else
#define VER_PRIVATEBUILD            0
#endif

/* default is prerelease */
#ifndef FINAL
#define VER_PRERELEASE              VS_FF_PRERELEASE
#else
#define VER_PRERELEASE              0
#endif

#ifdef DLL
#define VER_FILETYPE                VFT_DLL
#else
#define VER_FILETYPE                VFT_APP
#endif
#define VER_FILESUBTYPE             VFT_UNKNOWN

#define VER_FILEFLAGSMASK           VS_FFI_FILEFLAGSMASK
#ifdef WIN32
#define VER_FILEOS                  VOS_NT_WINDOWS32
#else
#define VER_FILEOS                  VOS_DOS_WINDOWS16
#endif
#define VER_FILEFLAGS               (VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG)

#define VER_LEGALCOPYRIGHT_YEARS    "1991-1995"
#define VER_COMPANYNAME_STR         "Microsoft Corporation\0"
#define VER_PRODUCTNAME_STR         "Microsoft\256 Windows(TM) NT Operating System\0"
#define VER_LEGALTRADEMARKS_STR     \
"Microsoft\256 is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation.\0"


README.TXT

Performance Monitor

The PERFMON tool allows you to view information about the resources being
used by the operating system. It is the quintessential sample of how an
application can get performance data from the registry.


ADDLINE.H

void /*static*/ OnComputerChanged (HDLG hDlg) ;

BOOL AddLine (HWND hWndParent,
              PPERFSYSTEM *ppSystemFirstView,
              PLINEVISUAL pLineVisual,
              LPTSTR pCurrentLine,
              int iLineTypeToAdd) ;

BOOL EditLine (HWND hWndParent,
               PPERFSYSTEM *ppSystemFirstView,
               PLINE pLineToEdit,
               int iLineTypeToEdit) ;


ADDLOG.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"
#include "utils.h"
#include "log.h"
#include "pmemory.h"    // for MemoryXXX (malloc-like) routines
#include "perfdata.h"
#include "perfmops.h"
#include "system.h"     // for SystemGet
#include "playback.h"   // for PlayingBackLog & DataFromIndexPosition
#include "pmhelpid.h"   // Help IDs

//==========================================================================//
//                               External Data                                //
//==========================================================================//

extern HWND          hWndLogEntries ;

BOOL                 ComputerChange ;
static   PPERFDATA   pPerfData ;

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

BOOL /* static */ LogComputerChanged (HDLG hDlg)
   {  // LogComputerChanged
   PLOG           pLog ;
   HWND           hWndObjects ;
   PPERFSYSTEM    pSysInfo;
   DWORD          iObjectType ;
   DWORD          iObjectNum ;

   pLog = LogData (hDlg) ;
   hWndObjects = DialogControl (hDlg, IDD_ADDLOGOBJECT) ;

   if (!pPerfData)
      {
      LBReset (hWndObjects) ;
      return FALSE ;
      }

   SetHourglassCursor() ;

   pSysInfo = GetComputer (hDlg,
                           IDD_ADDLOGCOMPUTER,
                           TRUE,
                           &pPerfData,
                           &pLog->pSystemFirst) ;

   if (!pPerfData || !pSysInfo)
      {
      LBReset (hWndObjects) ;
      SetArrowCursor() ;
      return FALSE ;
      }
   else
      {
      LBLoadObjects (hWndObjects,
                     pPerfData,
                     pSysInfo,
                     pLog->dwDetailLevel,
                     NULL,
                     FALSE) ;

      iObjectNum = (DWORD) LBNumItems (hWndObjects) ;
      for (iObjectType = 0 ;
           iObjectType < iObjectNum ;
           iObjectType++)
         {  // for
         LBSetSelection (hWndObjects, iObjectType) ;
         }  // for

      }

   ComputerChange = FALSE ;

   SetArrowCursor() ;

   return TRUE ;
   }  // LogComputerChanged

void /*static*/ OnLogDestroy (HDLG hDlg)
   {  // OnLogDestroy

   dwCurrentDlgID = 0 ;

   if (!PlayingBackLog ())
      MemoryFree (pPerfData) ;

   bAddLineInProgress = FALSE ;
   }  // OnLogDestroy

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void /* static */ OnInitAddLogDialog (HDLG hDlg)
   {  // OnInitAddLogDialog
   TCHAR          szRemoteComputerName[MAX_COMPUTERNAME_LENGTH + 3] ;
   int            iIndex ;
   PLOGENTRY      pLogEntry ;
   LPTSTR         pComputerName ;

   bAddLineInProgress = TRUE ;
   if (PlayingBackLog())
      {
      pPerfData = DataFromIndexPosition (&(PlaybackLog.StartIndexPos), NULL) ;
      GetPerfComputerName(pPerfData, szRemoteComputerName);
      DialogSetString (hDlg, IDD_ADDLOGCOMPUTER, szRemoteComputerName);
      }
   else
      {
      pPerfData = MemoryAllocate (STARTING_SYSINFO_SIZE) ;

//Try to use computer specified on command line (if any), otherwise local computer
      pComputerName = CmdLineComputerName[0] ?
         CmdLineComputerName :
         LocalComputerName ;

      iIndex = LBSelection (hWndLogEntries) ;
      if (iIndex != LB_ERR)
         {
         pLogEntry = (PLOGENTRY) LBData(hWndLogEntries, iIndex) ;

         if (pLogEntry && pLogEntry != (PLOGENTRY)LB_ERR)
            {
            pComputerName = pLogEntry->szComputer ;
            }
         }

      DialogSetString (hDlg, IDD_ADDLOGCOMPUTER, pComputerName) ;
      }

   LogComputerChanged (hDlg) ;

   DialogEnable (hDlg, IDD_ADDLOGOBJECTTEXT, TRUE) ;
   DialogEnable (hDlg, IDD_ADDLOGOBJECT, TRUE) ;

   WindowCenter (hDlg) ;

   dwCurrentDlgID = HC_PM_idDlgEditAddToLog ;

   }  // OnInitAddLogDialog

void /* static */ OnLogComputer (HDLG hDlg)
/*
   Effect:        Put up the select Domain/Computer dialog. Allow the user
                  to select the computer. Check for user entering a domain
                  instead. Place the selected computer in the readonly
                  edit box.
*/
   {  // OnLogComputer
   TCHAR          szComputer [MAX_SYSTEM_NAME_LENGTH + 1] ;

   if (ChooseComputer (hDlg, szComputer))
      {
      DialogSetString (hDlg, IDD_ADDLOGCOMPUTER, szComputer) ;
      LogComputerChanged (hDlg) ;
      }  // if
   }  // OnLogComputer

void /* static */ OnAddToLog (HDLG hDlg)
/*
   Effect:        Perform all actions needed when the user clicks on
                  the add button. In particular, determine if the required
                  fields of the dialog have valid values. If so, go ahead
                  and add a LOGENTRY record in the log. If the computer
                  being logged is not already logged, add a LOGSYSTEMENTRY
                  as well.

   Assert:        We have valid values for computer and object, since we
                  always control these fields.
*/
   {  // OnAddToLog
   TCHAR          szComputer [MAX_SYSTEM_NAME_LENGTH + 1] ;
   TCHAR          szObjectType [PerfObjectLen + 1] ;
   DWORD          iObjectType ;
   DWORD          iObjectNum ;
   HWND           hWndObjectTypes ;
   PPERF_OBJECT_TYPE    pObject ;
   PLOG           pLog ;

   pLog = LogData (hWndLog) ;

   DialogText (hDlg, IDD_ADDLOGCOMPUTER, szComputer) ;

   hWndObjectTypes = DialogControl(hDlg, IDD_ADDLOGOBJECT) ;

   iObjectNum = (DWORD) LBNumItems (hWndObjectTypes) ;

   LBSetRedraw (hWndLogEntries, FALSE) ;

   for (iObjectType = 0 ;
        iObjectType < iObjectNum ;
        iObjectType++)
      {  // for
      // NOTE: for now, we don't check for duplicate lines
      if (LBSelected (hWndObjectTypes, iObjectType))
         {  // if
         LBString (hWndObjectTypes, iObjectType, szObjectType) ;
         pObject = (PPERF_OBJECT_TYPE) LBData (hWndObjectTypes, iObjectType) ;

         // eliminate duplicates here
         if (LogFindEntry(szComputer, pObject->ObjectNameTitleIndex) ==
             LOG_ENTRY_NOT_FOUND)
            {
            LogAddEntry (hWndLogEntries,
                         szComputer,
                         szObjectType,
                         pObject->ObjectNameTitleIndex,
                         FALSE) ;
            }
         }  // if
      }  // for

   LBSetRedraw (hWndLogEntries, TRUE) ;

   DialogSetText (hDlg, IDD_ADDLOGDONE, IDS_DONE) ;

   if (pLog->iStatus == iPMStatusCollecting)
      {
      LogWriteSystemCounterNames (hWndLog, pLog) ;
      }
   }  // OnAddToLog

int FAR WINAPI AddLogDlgProc (HWND hDlg, WORD msg,
                              WPARAM wParam, LONG lParam)
   {  // AddLogDlg
   switch(msg)
      {
      case WM_INITDIALOG:
         OnInitAddLogDialog (hDlg) ;
         SetFocus (DialogControl (hDlg, IDD_ADDLOGADD)) ;
         return(FALSE);

      case WM_COMMAND:

         switch(LOWORD(wParam))
            {
            case IDD_CANCEL:
               EndDialog(hDlg,0);
               return(TRUE);

            case IDD_OK:
            case IDD_ADDLOGADD:

               if (ComputerChange)
                  {
                  TCHAR    szComputer [MAX_SYSTEM_NAME_LENGTH + 3] ;

                  DialogText (hDlg, IDD_ADDLOGCOMPUTER, szComputer) ;
                  LogComputerChanged (hDlg) ;
                  }
               else
                  {
                  SetHourglassCursor() ;
                  OnAddToLog (hDlg) ;
                  SetArrowCursor() ;
                  }
               break ;

            case IDD_ADDLOGDONE:
               EndDialog (hDlg, 0) ;
               break ;

            case IDD_ADDLOGELLIPSES:
               SetHourglassCursor() ;
               OnLogComputer (hDlg) ;
               SetArrowCursor() ;
               break ;

            case IDD_ADDLOGCOMPUTER:
               if (HIWORD (wParam) == EN_UPDATE)
                  {
                  ComputerChange = TRUE ;
                  }
               break ;

            case IDD_ADDLOGHELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            case IDD_ADDLOGOBJECT:
               if (ComputerChange)
                  {
                  TCHAR    szComputer [MAX_SYSTEM_NAME_LENGTH + 3] ;

                  DialogText (hDlg, IDD_ADDLOGCOMPUTER, szComputer) ;
                  LogComputerChanged (hDlg) ;
                  }
               break ;

            default:
               break ;
            }
         break;

      case WM_DESTROY:
         OnLogDestroy (hDlg) ;
         break ;

      default:
         break;
      }

   return (FALSE);
   }  // AddLogDlgProc

BOOL AddLog (HWND hWndParent)
   {  // AddLog
   if (DialogBox (hInstance, idDlgAddLog,
                  hWndParent, (DLGPROC) AddLogDlgProc))
      {  // if
      return (TRUE) ;
      }  // if
   return (FALSE) ;
   }  // AddLog


ADDLOG.H

BOOL AddLog (HWND hWndParent) ;


ALERT.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdio.h>
#include "perfmon.h"
#include "alert.h"         // External declarations for this file

#include "addline.h"       // for AddLine
#include "fileutil.h"      // for FileRead
#include "legend.h"
#include "line.h"
#include "pmemory.h"       // for MemoryXXX (mallloc-type) routines
#include "owndraw.h"       // for OwnerDraw macros
#include "perfdata.h"      // for UpdateLines
#include "perfmops.h"      // for SystemAdd
#include "playback.h"      // for PlaybackLines
#include "status.h"        // for StatusUpdateAlerts
#include "system.h"        // for SystemGet
#include "utils.h"
#include "menuids.h"       // for IDM_VIEWALERT
#include "fileopen.h"      // for FileGetName
#include "counters.h"      // for CounterEntry

#include <lmcons.h>
#include <lmmsg.h>
#include <lmerr.h>

#define MSG_ALERT_OCCURRED               ((DWORD)0x400007D0L)
#define MSG_ALERT_SYSTEM                 ((DWORD)0x400007D1L)

#define WM_SEND_NETWORK_ALERT (WM_USER + 101)

//==========================================================================//
//                                  Typedefs                                //
//==========================================================================//

typedef NET_API_STATUS
(*pNetMessageBufferSend) (
    IN  LPTSTR  servername,
    IN  LPTSTR  msgname,
    IN  LPTSTR  fromname,
    IN  LPBYTE  buf,
    IN  DWORD   buflen
    );

typedef struct ALERTENTRYSTRUCT
   {
   SYSTEMTIME     SystemTime ;
   PLINE          pLine ;
   FLOAT          eValue ;
   BOOL           bOver ;
   FLOAT          eAlertValue ;
   LPTSTR         lpszInstance ;
   LPTSTR         lpszParent ;
   INT            StringWidth ;
   } ALERTENTRY ;

typedef ALERTENTRY *PALERTENTRY ;

static HBRUSH  hRedBrush ;

//====
//====

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define szNumberFormat         TEXT("%12.3f")
#define szMediumnNumberFormat  TEXT("%12.0f")
#define szLargeNumberFormat    TEXT("%12.4e")
#define eNumber                ((FLOAT) 99999999.999)
#define eLargeNumber           ((FLOAT) 999999999999.0)

#define szNumberPrototype        TEXT("99999999.999")

#define szConditionFormat        TEXT("  %c  ")
#define szConditionPrototype     TEXT("  >  ")

#define szDatePrototype          TEXT("12/31/1999   ")
#define szTimePrototype          TEXT("12:34:56.9 pm  ")

#define ALERTLOGMAXITEMS         1000

#define szAlertFormat TEXT("%s %s %s %c %s %s,  %s,  %s,  %s,  %s")

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define AlertItemTopMargin()     (yBorderHeight)

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//
INT ExportAlertLine (PLINE pLine, FLOAT eValue, SYSTEMTIME *pSystemTime, HANDLE hExportFile) ;

#if 0
PALERT AlertData (HWND hWndAlert)
   {
   return (&Alert) ;
   }
#endif

void AlertFormatFloat (LPTSTR lpValueBuf, FLOAT eValue)
   {
   if (eValue <= eNumber)
      {
      TSPRINTF (lpValueBuf, szNumberFormat, eValue) ;
      }
   else if (eValue <= eLargeNumber)
      {
      TSPRINTF (lpValueBuf, szMediumnNumberFormat, eValue) ;
      }
   else
      {
      TSPRINTF (lpValueBuf, szLargeNumberFormat, eValue) ;
      }
   ConvertDecimalPoint (lpValueBuf) ;

   }  // AlertFormatFloat

PALERT AllocateAlertData (HWND hWndAlert)
   {
   PALERT           pAlert ;

   pAlert = AlertData (hWndAlert) ;

   pAlert->hWnd = hWndAlert ;
   pAlert->hAlertListBox = DialogControl (hWndAlert, IDD_ALERTLOG) ;
   pAlert->iStatus = iPMStatusClosed ;
   pAlert->bManualRefresh = FALSE ;
   pAlert->bModified = FALSE ;

   pAlert->Visual.iColorIndex = 0 ;
   pAlert->Visual.iWidthIndex = -1 ;
   pAlert->Visual.iStyleIndex = -1 ;

   pAlert->iIntervalMSecs = iDefaultAlertIntervalSecs * 1000 ;
   pAlert->pSystemFirst = NULL ;
   pAlert->pLineFirst = NULL ;

   pAlert->MessageName[0] = TEXT('\0') ;

   pAlert->bLegendOn = TRUE ;

   return (pAlert) ;
   }  // AllocateAlertData

void FreeAlertData (PALERT pAlert)
   {  // FreeAlertData
   }  // FreeAlertData

BOOL SetAlertTimer (PALERT pAlert)
   {
   if (pAlert->iStatus == iPMStatusCollecting)
      KillTimer (pAlert->hWnd, AlertTimerID) ;

   pAlert->iStatus = iPMStatusCollecting ;
   SetTimer (pAlert->hWnd, AlertTimerID, pAlert->iIntervalMSecs, NULL) ;
   return (TRUE) ;
   }

BOOL ClearAlertTimer (PALERT pAlert)
   {  // ClearAlertTimer
   if (!PlayingBackLog())
      {
      KillTimer (pAlert->hWnd, AlertTimerID) ;
      }
   pAlert->iStatus = iPMStatusClosed ;

   return (TRUE) ;
   }  // ClearAlertTimer

BOOL AlertExec (LPTSTR lpszImageName, LPTSTR lpszCommandLine)
/*
   Effect:        WinExec is considered obsolete. We're supposed to use
                  CreateProcess, which allows considerably more control.
                  For perfmon, we only execute a program when an alert
                  occurs, and we really don't know anything about the
                  program, so can't really do much.  We just set some
                  defaults and go.

   Called By:     SignalAlert only.
*/
   {  // ExecProcess
   STARTUPINFO    si ;
   PROCESS_INFORMATION  pi ;
   int            StringLen ;
   TCHAR          TempBuffer [ 5 * FilePathLen ] ;
   BOOL           RetCode;

   memset (&si, 0, sizeof (STARTUPINFO)) ;
   si.cb = sizeof (STARTUPINFO) ;
   si.dwFlags = STARTF_USESHOWWINDOW ;
   si.wShowWindow = SW_SHOWNOACTIVATE ;
   memset (&pi, 0, sizeof (PROCESS_INFORMATION)) ;

   lstrcpy (TempBuffer, lpszImageName) ;
   StringLen = lstrlen (TempBuffer) ;
   TempBuffer [StringLen] = TEXT(' ') ;
   StringLen++ ;
   lstrcpy (&TempBuffer[StringLen], lpszCommandLine) ;

   // DETACHED_PROCESS is needed to get rid of the ugly console window
   // that SQL guys have been bitching..
   RetCode = CreateProcess (NULL, TempBuffer,
                          NULL, NULL, FALSE,
                          (DWORD) NORMAL_PRIORITY_CLASS,
                          NULL, NULL,
                          &si, &pi) ;
   if (RetCode)
      {
      if (pi.hProcess && pi.hProcess != INVALID_HANDLE_VALUE)
         {
         CloseHandle (pi.hProcess) ;
         }
      if (pi.hThread && pi.hThread != INVALID_HANDLE_VALUE)
         {
         CloseHandle (pi.hThread) ;
         }
      }

   return RetCode ;

   }  // ExecProcess

BOOL SendNetworkMessage (LPTSTR pText, DWORD TextLen, LPTSTR pMessageName)
{
   NET_API_STATUS NetStatus;
   HANDLE dllHandle ;
   pNetMessageBufferSend SendFunction ;

   //
   // Dynamically link to netapi32.dll.  If it's not there just return.  Return
   // TRUE so we won't try to send this alert again.
   //

   dllHandle = LoadLibrary(TEXT("NetApi32.Dll")) ;
   if ( !dllHandle || dllHandle == INVALID_HANDLE_VALUE )
      {
      return(TRUE) ;
      }

   //
   // Get the address of the service's main entry point.  This
   // entry point has a well-known name.
   //

   SendFunction = (pNetMessageBufferSend)GetProcAddress(
      dllHandle, "NetMessageBufferSend") ;

   if (SendFunction == NULL)
      {
      FreeLibrary (dllHandle) ;
      return(TRUE) ;
      }

   NetStatus = (*SendFunction) (NULL, pMessageName,
      NULL, (LPBYTE)pText, TextLen * sizeof(TCHAR)) ;
   if (NetStatus != NERR_Success)
      {
      FreeLibrary (dllHandle) ;
      return (FALSE) ;
      }

   FreeLibrary (dllHandle) ;

   return (TRUE) ;
}

// this is the child thread used to send out network alert message.
// This thread is created at init time and is destroyed at close time
void NetAlertHandler (LPVOID *pDummy)
   {
   MSG      msg;

   while (GetMessage (&msg, NULL, 0, 0))
      {
      // we are only interested in this message
      if (LOWORD(msg.message) == WM_SEND_NETWORK_ALERT)
         {
         SendNetworkMessage ((LPTSTR)(msg.wParam),
            lstrlen ((LPTSTR)(msg.wParam)),
            (LPTSTR)(msg.lParam)) ;
         MemoryFree ((LPTSTR)(msg.wParam)) ;
         }
      }
   }  // NetAlertHandler

void SignalAlert (HWND hWnd,
                  HWND hWndAlerts,
                  PLINE pLine,
                  FLOAT eValue,
                  SYSTEMTIME *pSystemTime,
                  LPTSTR pSystemName,
                  DWORD dwSystemState)
/*
   Effect:        Perform any actions necessary when a given alert line's
                  condition is true. In particular, add the alert to the
                  alert log. Also, depending on the user's wishes, signal
                  a network alert or beep or run a program.

                  If we are not viewing the alert screen, add one alert to
                  the unviewed list.
*/
   {
   int            iIndex ;
   PALERT         pAlert ;
   PALERTENTRY    pAlertEntry ;
   TCHAR          szTime [20] ;
   TCHAR          szDate [20] ;
   TCHAR          szInstance [256] ;
   TCHAR          szParent [256] ;
   TCHAR          szText [256 * 4] ;
   TCHAR          eValueBuf [40] ;
   TCHAR          eAlertValueBuf [40] ;
   TCHAR          eAlertValueBuf1 [42] ;
   FLOAT          eLocalValue ;
   DWORD          TextSize ;
   LPTSTR         lpAlertMsg ;
   LPTSTR         lpEventLog[7] ;
   TCHAR          NullBuff [2] ;

   NullBuff [0] = NullBuff [1] = TEXT('\0') ;

   pAlert = AlertData (hWnd) ;

   pAlertEntry = MemoryAllocate (sizeof (ALERTENTRY)) ;
   if (!pAlertEntry)
      {
      return ;
      }
   pAlertEntry->SystemTime = *pSystemTime ;
   pAlertEntry->pLine= pLine ;
   pAlertEntry->eValue = eValue ;
   if (pLine)
      {
      pAlertEntry->bOver = pLine->bAlertOver ;
      pAlertEntry->eAlertValue = pLine->eAlertValue ;
      }

   //=============================//
   // Determine Instance, Parent  //
   //=============================//

   // It's possible that there will be no instance, therefore
   // the lnInstanceName would be NULL.

   if (pLine && pLine->lnObject.NumInstances > 0)
      {
      // Test for the parent object instance name title index.
      // If there is one, it implies that there will be a valid
      // Parent Object Name and a valid Parent Object Instance Name.

      // If the Parent Object title index is 0 then
      // just display the instance name.

      lstrcpy (szInstance, pLine->lnInstanceName) ;
      if (pLine->lnInstanceDef.ParentObjectTitleIndex && pLine->lnPINName)
         {
         // Get the Parent Object Name.
         lstrcpy (szParent, pLine->lnPINName) ;
         }
      else
         {
         szParent[0] = TEXT(' ') ;
         szParent[1] = TEXT('\0') ;
         }
      }
   else
      {
      if (pLine)
         {
         szInstance[0] = TEXT(' ') ;
         szInstance[1] = TEXT('\0') ;
         szParent[0] = TEXT(' ') ;
         szParent[1] = TEXT('\0') ;
         }
      else
         {
         // this is a system down/reconnect alert
         StringLoad (
            dwSystemState == SYSTEM_DOWN ?
               IDS_SYSTEM_DOWN : IDS_SYSTEM_UP,
            szInstance) ;
         lstrcpy (szParent, pSystemName) ;
         }
      }

   pAlertEntry->lpszInstance = StringAllocate (szInstance) ;
   pAlertEntry->lpszParent = StringAllocate (szParent) ;

   //=============================//
   // Add alert to Alert Log      //
   //=============================//

   if (LBNumItems (hWndAlerts) >= ALERTLOGMAXITEMS)
      LBDelete (hWndAlerts, 0) ;

   iIndex = LBAdd (hWndAlerts, (LPARAM) pAlertEntry) ;
   LBSetSelection (hWndAlerts, iIndex) ;

   // no need to check other things if we
   // are playing back log
   if (PlayingBackLog())
      {
      return ;
      }

   //=============================//
   // Update Status Line          //
   //=============================//

   if (iPerfmonView != IDM_VIEWALERT)
      {
      if (pAlert->bSwitchToAlert)
         {
         SendMessage (hWndMain, WM_COMMAND, (LONG)IDM_VIEWALERT, 0L) ;
         }
      else
         {
         // if iUnviewedAlerts is over 100, we will display "++"
         // so, no need to keep updating the icon...
         if (iUnviewedAlerts < 100)
            {
            iUnviewedAlerts ++ ;
            if (pLine)
               {
               crLastUnviewedAlert = pLine->Visual.crColor ;
               }
            StatusUpdateIcons (hWndStatus) ;
            }
         }
      }

   //===================================//
   //  Check if we need to do anything  //
   //===================================//
   szText[0] = TEXT('\0') ;
   if ((pAlert->bNetworkAlert && pAlert->MessageName[0])
               ||
       (pLine) &&
       (pLine->lpszAlertProgram &&
       (pLine->bEveryTime || !pLine->bAlerted))
               ||
       (pAlert->bEventLog))
      {
      // format the alert line to be exported
      SystemTimeDateString (pSystemTime, szDate) ;
      SystemTimeTimeString (pSystemTime, szTime, TRUE) ;

      if (pLine)
         {
         AlertFormatFloat (eValueBuf, pAlertEntry->eValue) ;

         eLocalValue = pAlertEntry->eAlertValue ;
         if (eLocalValue < (FLOAT) 0.0)
            {
            eLocalValue = -eLocalValue ;
            }
         AlertFormatFloat (eAlertValueBuf, pAlertEntry->eAlertValue) ;

         TSPRINTF (szText,
            szAlertFormat,
            szDate,
            szTime,
            eValueBuf,
            (pLine->bAlertOver ? TEXT('>') : TEXT('<')),
            eAlertValueBuf,
            pLine->lnCounterName,
            szInstance,
            szParent,
            pLine->lnObjectName,
            pLine->lnSystemName) ;
         }
      else
         {
         lstrcpy (eValueBuf, DashLine) ;
         lstrcpy (eAlertValueBuf, eValueBuf) ;
         TSPRINTF (szText,
            szAlertFormat,
            szDate,
            szTime,
            eValueBuf,
            TEXT(' '),
            eAlertValueBuf,
            szInstance,             // system up/down message
            NullBuff,
            NullBuff,
            NullBuff,
            szParent) ;
         }

      TextSize = sizeof(TCHAR) * (lstrlen(szText)+1) ;
      }

   if (szText[0] == TEXT('\0'))
      {
      // nothing to do
      return ;
      }

   //=============================//
   // Network Alert?              //
   //=============================//

   SetHourglassCursor() ;

   if (pAlert->bNetworkAlert && pAlert->MessageName[0])
      {  // if

      if (pAlert->dwNetAlertThreadID)
         {
         // use thread to send the network alert.
         // the memory will be released by the child thread when done
         lpAlertMsg =
            (LPTSTR) MemoryAllocate (TextSize) ;
         if (lpAlertMsg)
            {
            lstrcpy (lpAlertMsg, szText) ;
            PostThreadMessage (pAlert->dwNetAlertThreadID,
               WM_SEND_NETWORK_ALERT,
               (WPARAM)lpAlertMsg,
               (LPARAM)pAlert->MessageName) ;
            }
         }
      else
         {
         // no thread available, use the slow way to send the network alert
         SendNetworkMessage (szText,
            (DWORD) lstrlen(szText),
            pAlert->MessageName) ;
         }
      }

   //=============================//
   // Run Program?                //
   //=============================//

   if (pLine &&
       pLine->lpszAlertProgram &&
       (pLine->bEveryTime || !pLine->bAlerted))
      {
      AlertExec (pLine->lpszAlertProgram, szText) ;
      pLine->bAlerted = TRUE ;
      }

   //===================================//
   // Log event to Application Log?     //
   //===================================//
   if (pAlert->bEventLog)
      {
      if (hEventLog)
         {
         if (pLine)
            {
            lpEventLog[0] = pLine->lnSystemName ;
            lpEventLog[1] = pLine->lnObjectName ;
            lpEventLog[2] = pLine->lnCounterName ;
            lpEventLog[3] = szInstance ;
            lpEventLog[4] = szParent ;
            lpEventLog[5] = eValueBuf ;
            eAlertValueBuf1[0] = pLine->bAlertOver ? TEXT('>') : TEXT('<') ;
            eAlertValueBuf1[1] = TEXT(' ') ;
            lstrcpy (&eAlertValueBuf1[2], eAlertValueBuf) ;
            lpEventLog[6] = eAlertValueBuf1 ;
            }
         else
            {
            lpEventLog[0] = szParent ;
            lpEventLog[1] = szInstance ;
            }

         ReportEvent (hEventLog,
            (WORD)EVENTLOG_INFORMATION_TYPE,
            (WORD)0,
            (DWORD) (pLine ? MSG_ALERT_OCCURRED : MSG_ALERT_SYSTEM),
            (PSID)NULL,
            (WORD) (pLine ? 7 : 2),
            (DWORD)TextSize,
            (LPCTSTR *)lpEventLog,
            (LPVOID)(szText)) ;
         }
      }

   SetArrowCursor() ;

   }  // SignalAlert

BOOL AlertCondition (PLINE pLine, FLOAT eValue)
/*
   Effect:        Return whether the alert test passed for line pLine,
                  with current data value eValue.

   Internals:     Don't *ever* say (bFoo == bBar), as non-FALSE values
                  could be represented by any nonzero number.  Use
                  BoolEqual or equivalent.
*/
   {  // AlertCondition
   BOOL           bOver ;

   bOver = eValue > pLine->eAlertValue ;

   return (BoolEqual (bOver, pLine->bAlertOver)) ;
   }  // AlertCondition

INT static CheckAlerts (HWND hWnd,
                        HWND hWndAlerts,
                        SYSTEMTIME *pSystemTime,
                        PLINE pLineFirst,
                        HANDLE hExportFile,
                        PPERFSYSTEM pSystemFirst)
   {  // CheckAlerts
   FLOAT          eValue ;
   PLINE          pLine ;
   BOOL           bAnyAlerts ;
   INT            ErrCode = 0 ;
   PPERFSYSTEM    pSystem ;

   bAnyAlerts = FALSE ;
   if (!PlayingBackLog())
      {
      LBSetRedraw (hWndAlerts, FALSE) ;

      // check for system up/down
      for (pSystem = pSystemFirst ;
         pSystem ;
         pSystem = pSystem->pSystemNext)
         {
         if (pSystem->dwSystemState == SYSTEM_DOWN)
            {
            pSystem->dwSystemState = SYSTEM_DOWN_RPT ;
            SignalAlert (hWnd,
               hWndAlerts,
               NULL,
               (FLOAT) 0.0,
               pSystemTime,
               pSystem->sysName,
               SYSTEM_DOWN) ;
            }
         else if (pSystem->dwSystemState == SYSTEM_RECONNECT)
            {
            pSystem->dwSystemState = SYSTEM_RECONNECT_RPT ;
            SignalAlert (hWnd,
               hWndAlerts,
               NULL,
               (FLOAT) 0.0,
               pSystemTime,
               pSystem->sysName,
               SYSTEM_RECONNECT) ;
            }
         }
      }  // !PlayingBackLog()

   for (pLine = pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {
      if (pLine->bFirstTime)
         {
         // skip until we have collect enough samples for the first data
         continue ;
         }

      // Get the new value for this line.
      eValue = CounterEntry (pLine) ;
      if (AlertCondition (pLine, eValue))
         {
         bAnyAlerts = TRUE ;

         // the case that hExportFile is !NULL is when playingback log and that the
         // listbox is overflowed with alert.  In this case, we have to
         // walk the log file again to re-generate all the alerts.
         if (hExportFile)
            {
            ErrCode = ExportAlertLine (pLine, eValue, pSystemTime, hExportFile) ;
            if (ErrCode)
               {
               break ;
               }
            }
         else
            {
            SignalAlert (hWnd,
               hWndAlerts,
               pLine,
               eValue,
               pSystemTime,
               NULL,
               0) ;
            }
         }
      }  // for

   if (!PlayingBackLog())
      {
      LBSetRedraw (hWndAlerts, TRUE) ;
      }

   return (ErrCode) ;
   }  // CheckAlerts

void DrawAlertEntry (HWND hWnd,
                     PALERT pAlert,
                     PALERTENTRY pAlertEntry,
                     LPDRAWITEMSTRUCT lpDI,
                     HDC hDC)
   {  // DrawAlertEntry
   PLINE          pLine ;
   RECT           rectUpdate ;

   TCHAR          szTime [20] ;
   TCHAR          szDate [20] ;
   TCHAR          szText [256] ;

   HBRUSH         hBrushPrevious ;
   FLOAT          eLocalValue ;
   COLORREF       preBkColor ;
   COLORREF       preTextColor ;

   pLine = pAlertEntry->pLine ;

   SystemTimeDateString (&(pAlertEntry->SystemTime), szDate) ;
   SystemTimeTimeString (&(pAlertEntry->SystemTime), szTime, TRUE) ;

   if (DISelected (lpDI))
      {  // if
      preTextColor = SetTextColor (hDC, GetSysColor (COLOR_HIGHLIGHTTEXT)) ;
      preBkColor = SetBkColor (hDC, GetSysColor (COLOR_HIGHLIGHT)) ;
      }  // if

   //=============================//
   // Draw Color Dot              //
   //=============================//

   rectUpdate.left = 0 ;
   rectUpdate.top = lpDI->rcItem.top ;
   rectUpdate.right = pAlert->xColorWidth ;
   rectUpdate.bottom = lpDI->rcItem.bottom ;

   ExtTextOut (hDC, rectUpdate.left, rectUpdate.top,
               ETO_CLIPPED | ETO_OPAQUE,
               &rectUpdate,
               NULL, 0,
               NULL) ;

   if (pLine)
      {
      hBrushPrevious = SelectBrush (hDC, pLine->hBrush) ;
      }
   else
      {
      if (hRedBrush == NULL)
         {
         hRedBrush = CreateSolidBrush (RGB (0xff, 0x00, 0x00)) ;
         }
      hBrushPrevious = SelectBrush (hDC, hRedBrush) ;
      }

   Ellipse (hDC,
            rectUpdate.left + 2,
            rectUpdate.top + 2,
            rectUpdate.right - 2,
            rectUpdate.bottom - 2) ;

   SelectBrush (hDC, hBrushPrevious) ;

   //=============================//
   // Draw Date                   //
   //=============================//

   rectUpdate.left = rectUpdate.right ;
   rectUpdate.right = rectUpdate.left + pAlert->xDateWidth ;

   ExtTextOut (hDC, rectUpdate.left, rectUpdate.top,
               ETO_CLIPPED | ETO_OPAQUE,
               &rectUpdate,
               szDate, lstrlen (szDate),
               NULL) ;

   //=============================//
   // Draw Time                   //
   //=============================//

   rectUpdate.left = rectUpdate.right ;
   rectUpdate.right = rectUpdate.left + pAlert->xTimeWidth ;

   ExtTextOut (hDC, rectUpdate.left, rectUpdate.top,
               ETO_CLIPPED | ETO_OPAQUE,
               &rectUpdate,
               szTime, lstrlen (szTime),
               NULL) ;

   //=============================//
   // Draw Alert Value            //
   //=============================//

   SetTextAlign (hDC, TA_RIGHT) ;

   rectUpdate.left = rectUpdate.right ;
   rectUpdate.right = rectUpdate.left + pAlert->xNumberWidth ;

   if (pLine)
      {
      AlertFormatFloat (szText, pAlertEntry->eValue) ;
      }
   else
      {
      lstrcpy (szText, DashLine) ;
      }

   ExtTextOut (hDC, rectUpdate.right, rectUpdate.top,
               ETO_CLIPPED | ETO_OPAQUE,
               &rectUpdate,
               szText, lstrlen (szText),
               NULL) ;

   //=============================//
   // Draw Alert Condition        //
   //=============================//

   rectUpdate.left = rectUpdate.right ;
   rectUpdate.right = rectUpdate.left + pAlert->xConditionWidth ;

   TSPRINTF (szText, szConditionFormat,
      pLine ?
         (pAlertEntry->bOver ? TEXT('>') : TEXT('<')) :
         TEXT(' ')
      ) ;

   ExtTextOut (hDC, rectUpdate.right, rectUpdate.top,
               ETO_CLIPPED | ETO_OPAQUE,
               &rectUpdate,
               szText, lstrlen (szText),
               NULL) ;

   //=============================//
   // Draw Trigger Value          //
   //=============================//

   rectUpdate.left = rectUpdate.right ;
   rectUpdate.right = rectUpdate.left + pAlert->xNumberWidth ;

   if (pLine)
      {
      eLocalValue = pAlertEntry->eAlertValue ;
      if (eLocalValue < (FLOAT) 0.0)
         {
         eLocalValue = -eLocalValue ;
         }
      AlertFormatFloat (szText, pAlertEntry->eAlertValue) ;
      }
   else
      {
      lstrcpy (szText, DashLine) ;
      }

   ExtTextOut (hDC, rectUpdate.right, rectUpdate.top,
               ETO_CLIPPED | ETO_OPAQUE,
               &rectUpdate,
               szText, lstrlen (szText),
               NULL) ;

   //=============================//
   // Draw Rest                   //
   //=============================//

   SetTextAlign (hDC, TA_LEFT) ;

   rectUpdate.left = rectUpdate.right ;
   rectUpdate.right = 10000 ;

   if (pLine)
      {
      TSPRINTF (szText,
         TEXT("    %s,  %s,  %s,  %s,  %s"),
         pLine->lnCounterName,
         pAlertEntry->lpszInstance,
         pAlertEntry->lpszParent,
         pLine->lnObjectName,
         pLine->lnSystemName) ;
      }
   else
      {
      TSPRINTF (szText,
         TEXT("    %s, , , ,  %s"),
         pAlertEntry->lpszInstance,
         pAlertEntry->lpszParent) ;
      }

   ExtTextOut (hDC, rectUpdate.left, rectUpdate.top,
               ETO_OPAQUE,
               &rectUpdate,
               szText, lstrlen (szText),
               NULL) ;

   // check if we need to bring-up or resize the horiz scrollbar
   if (pAlertEntry->StringWidth == 0)
      {
      pAlertEntry->StringWidth = TextWidth (hDC, szText) + xScrollWidth +
                  rectUpdate.left ;
      }

   if (pAlertEntry->StringWidth > pAlert->xTextExtent)
      {
      pAlert->xTextExtent = pAlertEntry->StringWidth ;
      LBSetHorzExtent (pAlert->hAlertListBox, pAlertEntry->StringWidth) ;
      }

   if (DISelected (lpDI))
      {  // if
      preTextColor = SetTextColor (hDC, preTextColor) ;
      preBkColor = SetBkColor (hDC, preBkColor) ;
      }  // if
   }  // DrawAlertEntry

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnDrawItem (HWND hWnd,
                        LPDRAWITEMSTRUCT lpDI)
   {  // OnDrawItem
   HFONT          hFontPrevious ;
   HDC            hDC ;
   PALERT         pAlert ;
   PALERTENTRY    pAlertEntry ;
//   PLINESTRUCT    pLine ;
   int            iLBIndex ;

   hDC = lpDI->hDC ;
   iLBIndex = DIIndex (lpDI) ;

   pAlert = AlertData (hWnd) ;

   if (iLBIndex == -1)
      {
      pAlertEntry = NULL ;
      }
   else
      {
      pAlertEntry = (PALERTENTRY) LBData (pAlert->hAlertListBox, iLBIndex) ;
      if (pAlertEntry == (PALERTENTRY) LB_ERR)
         {
         pAlertEntry = NULL ;
         }
      }

   //=============================//
   // Draw Legend Item            //
   //=============================//

   if (pAlertEntry)
      {
      hFontPrevious = SelectFont (hDC, pAlert->hFontItems) ;
      DrawAlertEntry (hWnd, pAlert, pAlertEntry, lpDI, hDC) ;
      SelectFont (hDC, hFontPrevious) ;
      }

   //=============================//
   // Draw Focus                  //
   //=============================//

   if (DIFocus (lpDI))
      DrawFocusRect (hDC, &(lpDI->rcItem)) ;

   }  // OnDrawItem

int static OnCtlColor (HWND hDlg,
                       HDC hDC)
   {
   SetTextColor (hDC, crBlack) ;
   SetBkColor (hDC, crLightGray) ;
   return ((int) hbLightGray) ;
   }

void AlertCreateThread (PALERT pAlert)

{
   pAlert->hNetAlertThread = CreateThread(NULL, (DWORD)1024L,
      (LPTHREAD_START_ROUTINE)NetAlertHandler,
      NULL, (DWORD)0, &(pAlert->dwNetAlertThreadID)) ;

   if (!(pAlert->hNetAlertThread))
      {
      // CreateThread failure, set its ID to zero
      // so we will not use the thread
      pAlert->dwNetAlertThreadID = 0 ;
      }
   else
      {
      SetThreadPriority (pAlert->hNetAlertThread, THREAD_PRIORITY_HIGHEST) ;
      }
   }

void static OnInitDialog (HWND hDlg)
   {
   HDC            hDC ;
   PALERT         pAlert ;

   iUnviewedAlerts = 0 ;

   pAlert = AllocateAlertData (hDlg) ;
   if (!pAlert)
      return ;

   pAlert->iStatus = iPMStatusClosed ;
   pAlert->hFontItems = hFontScales ;

   hDC = GetDC (hDlg) ;
   SelectFont (hDC, pAlert->hFontItems) ;

   pAlert->yItemHeight = FontHeight (hDC, TRUE) + 2 * AlertItemTopMargin () ;

   pAlert->xColorWidth = pAlert->yItemHeight ;
   pAlert->xDateWidth = TextWidth (hDC, szDatePrototype) ;
   pAlert->xTimeWidth = TextWidth (hDC, szTimePrototype) ;
   pAlert->xNumberWidth = TextWidth (hDC, szNumberPrototype) ;
   pAlert->xConditionWidth = TextWidth (hDC, szConditionPrototype) ;

   // no Horz. scroll bar to begin with
   pAlert->xTextExtent = 0 ;
   pAlert->hNetAlertThread = 0;
#if 0
   pAlert->hNetAlertThread = CreateThread(NULL, (DWORD)1024L,
      (LPTHREAD_START_ROUTINE)NetAlertHandler,
      NULL, (DWORD)0, &(pAlert->dwNetAlertThreadID)) ;

   if (!(pAlert->hNetAlertThread))
      {
      // CreateThread failure, set its ID to zero
      // so we will not use the thread
      pAlert->dwNetAlertThreadID = 0 ;
      }
   else
      {
      SetThreadPriority (pAlert->hNetAlertThread, THREAD_PRIORITY_HIGHEST) ;
      }
#endif
   ReleaseDC (hDlg, hDC) ;

   hWndAlertLegend = DialogControl (hDlg, IDD_ALERTLEGEND) ;
   UpdateAlertDisplay (hDlg) ;
   }

void static OnMeasureItem (HWND hWnd,
                           LPMEASUREITEMSTRUCT lpMI)
/*
   Note:          Since we have an LB_OWNERDRAWFIXED item in the alert
                  dialog, we get this message *before* the WM_INITDIALOG
                  message.  Therefore we can't rely on any of the values
                  set in that message handler.
*/
   {  // OnMeasureItem
   HDC            hDC ;

   hDC = GetDC (hWnd) ;
   SelectFont (hDC, hFontScales) ;

   lpMI->itemHeight = FontHeight (hDC, TRUE) + 2 * AlertItemTopMargin () ;

   ReleaseDC (hWnd, hDC) ;
   }  // OnMeasureItem

void static OnDeleteItem (HDLG hDlg,
                          WPARAM wControlID,
                          LPDELETEITEMSTRUCT lpDI)
   {  // OnDeleteItem
   PALERTENTRY    pAlertEntry ;

   pAlertEntry = (PALERTENTRY) lpDI->itemData ;

   MemoryFree (pAlertEntry->lpszParent) ;
   MemoryFree (pAlertEntry->lpszInstance) ;

   MemoryFree (pAlertEntry) ;
   }  // OnDeleteItem

void static OnSize (HWND hDlg,
                    int xWidth,
                    int yHeight)
   {  // OnSize
   SizeAlertComponents (hDlg) ;
   }

void static OnDestroy (HWND hWnd)
/*
   Effect:        Perform any actions necessary when an AlertDisplay window
                  is being destroyed. In particular, free the instance
                  data for the log.

                  Since we really only have one log window and one global
                  log data structure, we don't free the structure. We do,
                  however, delete the objects allocated within the structure.
*/
   {  // OnDestroy
   PALERT           pAlert ;

   pAlert = AlertData (hWnd) ;
   FreeAlertData (pAlert) ;

   if (pAlert->dwNetAlertThreadID)
      {
      CloseHandle (pAlert->hNetAlertThread) ;
      }
   }  // OnDestroy

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL AlertInitializeApplication (void)
   {  // AlertInitializeApplication
   return (TRUE) ;
   }  // AlertInitializeApplication

int APIENTRY AlertDisplayDlgProc (HWND hDlg,
                                  unsigned iMessage,
                                  WPARAM wParam,
                                  LONG lParam)
/*
   Note:          This function must be exported in the application's
                  linker-definition file, perfmon.def.
*/
   {  // AlertDisplayDlgProc
//   HDC            hDC ;

   switch (iMessage)
      {
      case WM_CTLCOLORDLG:
      case WM_CTLCOLOREDIT:
      case WM_CTLCOLORBTN:
      case WM_CTLCOLORSTATIC:
         return (OnCtlColor (hDlg, (HDC) wParam)) ;
         break ;

      case WM_DELETEITEM:
         OnDeleteItem (hDlg, wParam, (LPDELETEITEMSTRUCT) lParam) ;
         break ;

      case WM_DRAWITEM:
         OnDrawItem (hDlg, (LPDRAWITEMSTRUCT) lParam) ;
         break ;

      case WM_INITDIALOG:
         OnInitDialog (hDlg) ;
         break ;

      case WM_LBUTTONDOWN:
         DoWindowDrag (hDlg, lParam) ;
         break ;

      case WM_LBUTTONDBLCLK:
         SendMessage (hWndMain, WM_LBUTTONDBLCLK, wParam, lParam) ;
         break ;

      case WM_MEASUREITEM:
         OnMeasureItem (hDlg, (LPMEASUREITEMSTRUCT) lParam) ;
         break ;

      case WM_SIZE:
         OnSize (hDlg, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      case WM_TIMER:
         AlertTimer (hDlg, FALSE) ;
         break ;

      case WM_DESTROY:
         OnDestroy (hDlg) ;
         return (FALSE) ;
         break ;

      default:
         return (FALSE) ;
      } // switch

   return (TRUE) ;
   }  // AlertDisplayDlgProc

HWND CreateAlertWindow (HWND hWndParent)
/*
   Effect:        Create the Alert window. This window is a child of
                  hWndMain.

   Note:          We dont worry about the size here, as this window
                  will be resized whenever the main window is resized.

*/
   {  // CreateAlertWindow
   HWND           hWnd ;
   hWnd = CreateDialog (hInstance,
                        MAKEINTRESOURCE (idDlgAlertDisplay),
                        hWndParent,
                        (DLGPROC) AlertDisplayDlgProc) ;

   return (hWnd) ;
   }  // CreateAlertWindow

void UpdateAlertDisplay (HWND hWnd)
/*
   Effect:        Set the values for the various controls in the Alert
                  display.

   Called By:     OnInitDialog, any other routines that change these
                  values.
*/
   {  // UpdateAlertDisplay
   PALERT         pAlert ;

   pAlert = AlertData (hWnd) ;

   DialogSetInterval (hWnd, IDD_ALERTINTERVAL, pAlert->iIntervalMSecs) ;
   }  // UpdateAlertDisplay

BOOL AlertInsertLine (HWND hWnd, PLINE pLine)
   {
   PALERT         pAlert ;
   PLINE          pLineEquivalent ;

   pAlert = AlertData (hWnd) ;
   pAlert->bModified = TRUE ;

   pLineEquivalent = FindEquivalentLine (pLine, pAlert->pLineFirst) ;
   if (pLineEquivalent)
      {
      LINESTRUCT  tempLine ;

      tempLine = *pLineEquivalent ;

      // copy the new alert line attributes
      pLineEquivalent->Visual = pLine->Visual ;
      pLineEquivalent->bAlertOver = pLine->bAlertOver ;
      pLineEquivalent->eAlertValue = pLine->eAlertValue ;
      pLineEquivalent->bEveryTime = pLine->bEveryTime ;

      pLineEquivalent->lpszAlertProgram = pLine->lpszAlertProgram ;
      pLine->lpszAlertProgram = tempLine.lpszAlertProgram ;

      pLineEquivalent->hBrush = pLine->hBrush ;
      pLine->hBrush = tempLine.hBrush ;

      if (PlayingBackLog ())
         {
         PlaybackAlert (hWnd, 0) ;
         WindowInvalidate (hWnd) ;
         }

      return (FALSE) ;
      }
   else
      {
      SystemAdd (&pAlert->pSystemFirst, pLine->lnSystemName) ;

      LineAppend (&pAlert->pLineFirst, pLine) ;

      LegendAddItem (hWndAlertLegend, pLine) ;

      if (!bDelayAddAction)
         {
         SizeAlertComponents (hWndAlert) ;
         LegendSetSelection (hWndAlertLegend,
                             LegendNumItems (hWndAlertLegend) - 1) ;
         }
      }

   if (!bDelayAddAction)
      {
      if (PlayingBackLog ())
         {
         PlaybackAlert (hWnd, 0) ;
         WindowInvalidate (hWnd) ;
         }

      else if (pAlert->iStatus == iPMStatusClosed)
         SetAlertTimer (pAlert) ;
      }

   return (TRUE) ;
   }  // AlertInsertLine

void AlertAddAction ()
   {
   PALERT           pAlert ;

   pAlert = AlertData (hWndAlert) ;

   SizeAlertComponents (hWndAlert) ;
   LegendSetSelection (hWndAlertLegend,
      LegendNumItems (hWndAlertLegend) - 1) ;

   if (PlayingBackLog ())
      {
      PlaybackAlert (hWndAlert, 0) ;
      WindowInvalidate (hWndAlert) ;
      }
   else if (pAlert->iStatus == iPMStatusClosed)
      SetAlertTimer (pAlert) ;
   }

void SizeAlertComponents (HWND hDlg)
   {  // SizeAlertComponents
   RECT           rectClient ;
   int            xWidth, yHeight ;
   int            yLegendHeight ;
   int            yLegendTextHeight ;
   int            yLogHeight ;
   int            yLogTextHeight ;
   int            yIntervalHeight ;
   int            xIntervalTextWidth ;
   int            StartYPos ;
   PALERT         pAlert = AlertData(hDlg) ;

   GetClientRect (hDlg, &rectClient) ;
   xWidth = rectClient.right ;
   yHeight = rectClient.bottom ;

   if (pAlert->bLegendOn)
      {
      yLegendHeight = LegendHeight (hWndAlertLegend, yHeight) ;
      }
   else
      {
      yLegendHeight = 0 ;
      }

   if (yHeight < 7 * xScrollWidth)
      {
      // too small, just display the alert logs and hide all other
      // items
      DialogShow (hDlg, IDD_ALERTLEGEND, FALSE) ;
      DialogShow (hDlg, IDD_ALERTLEGENDTEXT, FALSE) ;

      DialogShow (hDlg, IDD_ALERTINTERVAL, FALSE) ;
      DialogShow (hDlg, IDD_ALERTINTERVALTEXT, FALSE) ;

      yLogTextHeight = DialogHeight (hDlg, IDD_ALERTLOGTEXT) ;

      if (yHeight - yLogTextHeight > 3 * xScrollWidth)
         {
         DialogMove (hDlg, IDD_ALERTLOGTEXT,
                     xScrollWidth,
                     xScrollWidth / 2,
                     NOCHANGE, NOCHANGE) ;
         yLogTextHeight += xScrollWidth / 2 ;
         DialogShow (hDlg, IDD_ALERTLOGTEXT, TRUE) ;
         }
      else
         {
         yLogTextHeight = 0 ;
         DialogShow (hDlg, IDD_ALERTLOGTEXT, FALSE) ;
         }
      DialogMove (hDlg, IDD_ALERTLOG,
                  xScrollWidth,
                  xScrollWidth / 2 + yLogTextHeight,
                  xWidth - 2 * xScrollWidth,
                  yHeight - xScrollWidth) ;
      }
   else if (yHeight <= 2 * yLegendHeight + 5 * xScrollWidth)
      {
      if (pAlert->bLegendOn)
         {
         yLegendHeight = min (yLegendHeight,
            (yHeight - xScrollWidth) / 2) ;
         }
      else
         {
         yLegendHeight = 0 ;
         }

      yLogHeight = yHeight - yLegendHeight - xScrollWidth - 2 ;

      DialogShow (hDlg, IDD_ALERTLEGENDTEXT, FALSE) ;
      DialogShow (hDlg, IDD_ALERTINTERVAL, FALSE) ;
      DialogShow (hDlg, IDD_ALERTINTERVALTEXT, FALSE) ;

      yLogTextHeight = DialogHeight (hDlg, IDD_ALERTLOGTEXT) ;
      if (yLogHeight - yLogTextHeight > 3 * xScrollWidth)
         {
         DialogMove (hDlg, IDD_ALERTLOGTEXT,
                     xScrollWidth,
                     xScrollWidth / 2,
                     NOCHANGE, NOCHANGE) ;
         yLogTextHeight += xScrollWidth / 2 ;
         DialogShow (hDlg, IDD_ALERTLOGTEXT, TRUE) ;
         }
      else
         {
         yLogTextHeight = 0 ;
         DialogShow (hDlg, IDD_ALERTLOGTEXT, FALSE) ;
         }

      DialogMove (hDlg, IDD_ALERTLOG,
                  xScrollWidth,
                  xScrollWidth / 2 + yLogTextHeight,
                  xWidth - 2 * xScrollWidth,
                  yLogHeight - yLogTextHeight) ;

      DialogMove (hDlg, IDD_ALERTLEGEND,
                  xScrollWidth,
                  yLogHeight + xScrollWidth - 2,
                  xWidth - 2 * xScrollWidth,
                  yLegendHeight) ;

      DialogShow (hDlg, IDD_ALERTLEGEND, pAlert->bLegendOn ? TRUE : FALSE) ;
      }
   else
      {
      if (pAlert->bLegendOn)
         {
         DialogMove (hDlg, IDD_ALERTLEGEND,
            xScrollWidth, yHeight - xScrollWidth / 2 - yLegendHeight,
            xWidth - 2  * xScrollWidth,
            yLegendHeight) ;
         DialogMove (hDlg, IDD_ALERTLEGENDTEXT,
            xScrollWidth,
            DialogYPos (hDlg, IDD_ALERTLEGEND) - xScrollWidth,
            NOCHANGE, NOCHANGE) ;

         yLegendTextHeight = DialogYPos (hDlg, IDD_ALERTLEGENDTEXT) ;
         }
      else
         {
         yLegendTextHeight = yHeight - xScrollWidth / 2 - yLegendHeight ;
         }

      yLogTextHeight = DialogHeight (hDlg, IDD_ALERTLOGTEXT) ;
      yIntervalHeight = DialogHeight (hDlg, IDD_ALERTINTERVAL) ;
      yLogHeight = yLegendTextHeight - 4 * xScrollWidth ;

      if (yLogHeight < 2 * xScrollWidth)
         {
         yLogHeight = yLegendTextHeight - yLogTextHeight - xScrollWidth ;
         }
      DialogMove (hDlg, IDD_ALERTLOG,
                  xScrollWidth,
                  yLegendTextHeight - yLogHeight - xScrollWidth / 2,
                  xWidth - 2 * xScrollWidth,
                  yLogHeight) ;
      DialogMove (hDlg, IDD_ALERTLOGTEXT,
                  xScrollWidth,
                  yLogTextHeight = DialogYPos (hDlg, IDD_ALERTLOG) - xScrollWidth,
                  xWidth - 2 * xScrollWidth, NOCHANGE) ;

      DialogShow (hDlg, IDD_ALERTLEGEND, pAlert->bLegendOn ? TRUE : FALSE) ;
      DialogShow (hDlg, IDD_ALERTLEGENDTEXT, pAlert->bLegendOn ? TRUE : FALSE) ;
      DialogShow (hDlg, IDD_ALERTLOGTEXT, TRUE) ;

      if (yLogTextHeight >= yIntervalHeight + xScrollWidth)
         {
         StartYPos = (yLogTextHeight - yIntervalHeight) / 2 ;
         xIntervalTextWidth = DialogWidth (hDlg, IDD_ALERTINTERVALTEXT) ;
         DialogMove (hDlg, IDD_ALERTINTERVALTEXT,
                     xScrollWidth,
                     StartYPos + 1,
                     NOCHANGE, NOCHANGE) ;
         DialogMove (hDlg, IDD_ALERTINTERVAL,
                     xScrollWidth + xIntervalTextWidth + 4,
                     StartYPos,
                     NOCHANGE, NOCHANGE) ;

         DialogShow (hDlg, IDD_ALERTINTERVAL, TRUE) ;
         DialogShow (hDlg, IDD_ALERTINTERVALTEXT, TRUE) ;
         }
      else
         {
         DialogShow (hDlg, IDD_ALERTINTERVAL, FALSE) ;
         DialogShow (hDlg, IDD_ALERTINTERVALTEXT, FALSE) ;
         }
      }

   WindowInvalidate (hDlg) ;
   }  // SizeAlertComponents

INT PlaybackAlert (HWND hWndAlert, HANDLE hExportFile)
   {  // PlaybackAlert
   PALERT         pAlert ;
   LOGPOSITION    lp ;
   PLOGINDEX      pLogIndex ;
   SYSTEMTIME     SystemTime ;
   SYSTEMTIME     PreviousSystemTime ;
   BOOL           bFirstTime = TRUE ;
   INT            ErrCode = 0 ;
   int            iDisplayTics ;
   DWORD          TimeDiff ;

   pAlert = AlertData (hWndAlert) ;

   if (!pAlert->pLineFirst)
      {
      // nothing to check
      return ErrCode;
      }

   lp = PlaybackLog.StartIndexPos ;
   iDisplayTics = PlaybackLog.iSelectedTics;

   if (!hExportFile)
      {
      LBReset (pAlert->hAlertListBox) ;
      LBSetRedraw (pAlert->hAlertListBox, FALSE) ;
      }

   while (iDisplayTics) {

      pLogIndex = IndexFromPosition (&lp) ;
      if (pLogIndex)
         SystemTime = pLogIndex->SystemTime ;
      else
         GetLocalTime (&SystemTime) ;

      if (!bFirstTime)
         {
         // check if it is time to do the alert checking
         TimeDiff = (DWORD) SystemTimeDifference (&PreviousSystemTime, &SystemTime) ;
         if (TimeDiff * 1000 >= pAlert->iIntervalMSecs)
            {
            PlaybackLines (pAlert->pSystemFirst,
                           pAlert->pLineFirst,
                           lp.iPosition) ;
            ErrCode = CheckAlerts (hWndAlert,
                            pAlert->hAlertListBox,
                            &SystemTime,
                            pAlert->pLineFirst,
                            hExportFile,
                            NULL) ;
            if (ErrCode)
               {
               break ;
               }

            PreviousSystemTime = SystemTime ;
            }
         }
      else
         {
         // setup the data for the first time
         bFirstTime = FALSE ;
         PreviousSystemTime = SystemTime ;
         PlaybackLines (pAlert->pSystemFirst,
                        pAlert->pLineFirst,
                        lp.iPosition) ;
         }

      if (!NextIndexPosition (&lp, FALSE))
         break;

      iDisplayTics-- ;
      }

   if (!hExportFile)
      {
      LBSetRedraw (pAlert->hAlertListBox, TRUE) ;
      }

   return (ErrCode) ;
   }  // PlaybackAlert

#if 0
PLINESTRUCT CurrentAlertLine (HWND hWndAlert)
   {  // CurrentAlertLine
   UNREFERENCED_PARAMETER (hWndAlert) ;

   return (LegendCurrentLine (hWndAlertLegend)) ;
   }  // CurrentAlertLine
#endif

BOOL AddAlert (HWND hWndParent)
   {
   PALERT         pAlert ;
   PLINE          pCurrentLine ;

   pAlert = AlertData (hWndAlert) ;
   pCurrentLine = CurrentAlertLine (hWndAlert) ;

   return (AddLine (hWndParent,
                    &(pAlert->pSystemFirst),
                    &(pAlert->Visual),
                    pCurrentLine ? pCurrentLine->lnSystemName : NULL,
                    LineTypeAlert)) ;
   }

BOOL EditAlert (HWND hWndParent)
   {  // EditAlert
   PALERT        pAlert ;
   BOOL          bOK ;

   pAlert = AlertData (hWndAlert) ;

   bOK = EditLine (hWndParent,
                   &(pAlert->pSystemFirst),
                   CurrentAlertLine (hWndAlert),
                   LineTypeAlert) ;

   if (bOK && PlayingBackLog())
      {
      //re-do the alert using the new condition
      PlaybackAlert (hWndAlert, 0) ;
      WindowInvalidate (hWndAlert) ;
      }

   return (bOK) ;
   }  // EditAlert

// RemoveLineFromAlertListBox is called when we are deleting a line
// while monitoring current activity.  We have to clear all the alert
// entries of this line because we are already doing this when
// playing back from log.  Moreover, we are using the line structure
// while drawing the item.
//
void RemoveLineFromAlertListBox (PALERT pAlert, PLINE pLine)
   {
   int            iIndex ;
   int            iNumOfAlerts ;
   PALERTENTRY    pAlertEntry ;

   iNumOfAlerts = LBNumItems (pAlert->hAlertListBox) ;

   if (iNumOfAlerts == 0 || iNumOfAlerts == (int) LB_ERR)
      {
      return ;
      }

   LBSetRedraw (pAlert->hAlertListBox, FALSE) ;

   // go thru the listbox from bottom to top
   for (iIndex = iNumOfAlerts - 1; iIndex >= 0; iIndex-- )
      {
      pAlertEntry = (PALERTENTRY) LBData (pAlert->hAlertListBox, iIndex) ;
      if (pAlertEntry != (PALERTENTRY) NULL && pAlertEntry)
         {
         if (pAlertEntry->pLine == pLine)
            {
            // remove it from the alert listbox.
            LBDelete (pAlert->hAlertListBox, iIndex) ;
            }
         }
      }
   LBSetRedraw (pAlert->hAlertListBox, TRUE) ;
   }

BOOL AlertDeleteLine (HWND hWndAlert,
                      PLINE pLine)
/*
   Effect:        Delete the line pLine from the alerts associated with
                  window hWnd.  Return whether the line could be deleted.
*/
   {  // DeleteAlert
   PALERT         pAlert ;

   pAlert = AlertData (hWndAlert) ;
   pAlert->bModified = TRUE ;

   LineRemove (&pAlert->pLineFirst, pLine) ;
   LegendDeleteItem (hWndAlertLegend, pLine) ;

   if (!pAlert->pLineFirst)
      {
      // no need to collect data
      ClearAlertTimer (pAlert) ;

      // clear legend
      ClearLegend (hWndAlertLegend) ;

      // reset visual data
      pAlert->Visual.iColorIndex = 0 ;
      pAlert->Visual.iWidthIndex = 0 ;
      pAlert->Visual.iStyleIndex = 0 ;
      }
   else
      {
      BuildValueListForSystems (
         pAlert->pSystemFirst,
         pAlert->pLineFirst) ;
      }

   if (!PlayingBackLog())
      {
      // delete any alert entry for this line
      RemoveLineFromAlertListBox (pAlert, pLine) ;
      }

   SizeAlertComponents (hWndAlert) ;

   if (PlayingBackLog ())
      {
      if (pAlert->pLineFirst)
         PlaybackAlert (hWndAlert, 0) ;
      else
         {
         LBReset (pAlert->hAlertListBox) ;
         }

      WindowInvalidate (hWndAlert) ;
      }

   return (TRUE) ;
   }  // DeleteAlert

BOOL ToggleAlertRefresh (HWND hWnd)
   {  // ToggleAlertRefresh
   PALERT        pAlert ;

   pAlert = AlertData (hWnd) ;

   if (pAlert->bManualRefresh)
      SetAlertTimer (pAlert) ;
   else
      ClearAlertTimer (pAlert) ;

   pAlert->bManualRefresh = !pAlert->bManualRefresh ;
   return (pAlert->bManualRefresh) ;
   }  // ToggleAlertRefresh

BOOL AlertRefresh (HWND hWnd)
   {  // ToggleAlertRefresh
   PALERT        pAlert ;

   pAlert = AlertData (hWnd) ;

   return (pAlert->bManualRefresh) ;
   }  // AlertRefresh

void AlertTimer (HWND hWnd, BOOL bForce)
/*
   Effect:        Perform all actions neccesary when an alert timer tick
                  or manual refresh occurs. In particular, get the current
                  values for each line in the alert window, and compare
                  the value against the alert conditions. For each alert
                  that may have occured, call SignalAlert.

   Called By:     AlertWndProc, in response to a WM_TIMER message.
                  OnCommand, in response to a IDM_REFRESHALERT notification.
*/
   {  // AlertTimer
   PALERT         pAlert ;
   SYSTEMTIME     SystemTime ;

   pAlert = AlertData (hWnd) ;

   if (PlayingBackLog ())
      return ;

   if (bForce || !pAlert->bManualRefresh)
      {  // if
      UpdateLines (&(pAlert->pSystemFirst), pAlert->pLineFirst) ;
      GetLocalTime (&SystemTime) ;
      CheckAlerts (hWnd,
                   pAlert->hAlertListBox,
                   &SystemTime,
                   pAlert->pLineFirst,
                   FALSE,
                   pAlert->pSystemFirst) ;
      }  // if
   }  // AlertTimer

BOOL OpenAlertVer1 (HANDLE hFile,
                    DISKALERT *pDiskAlert,
                    PALERT pAlert,
                    DWORD dwMinorVersion)

   {  // OpenAlertVer1
   bDelayAddAction = TRUE ;
   pAlert->Visual = pDiskAlert->Visual ;
   pAlert->iIntervalMSecs = pDiskAlert->dwIntervalSecs ;
   if (dwMinorVersion < 3)
      {
      pAlert->iIntervalMSecs *= 1000 ;
      }

   pAlert->bNetworkAlert = pDiskAlert->bNetworkAlert ;
   pAlert->bManualRefresh = pDiskAlert->bManualRefresh ;
   pAlert->bSwitchToAlert = pDiskAlert->bSwitchToAlert ;

   if (dwMinorVersion >= 2)
      {
      lstrcpy (pAlert->MessageName, pDiskAlert->MessageName) ;
      }

   pAlert->bLegendOn = TRUE ;

   if (dwMinorVersion >= 4)
      {
      if (dwMinorVersion == 4)
         pAlert->bEventLog = pDiskAlert->MiscOptions ;
      else
         {
         pAlert->bEventLog = pDiskAlert->MiscOptions & 0x01 ;
         pAlert->bLegendOn = pDiskAlert->MiscOptions & 0x02 ;
         }
      }
   else
      {
      pAlert->bEventLog = FALSE ;

      // have to move the file pointer back for old pma file
      FileSeekCurrent (hFile, -((int) (sizeof (pDiskAlert->MiscOptions)))) ;
      }

   if (pAlert->bNetworkAlert && pAlert->hNetAlertThread == 0)
      {
      AlertCreateThread (pAlert) ;
      }

   ReadLines (hFile, pDiskAlert->dwNumLines,
               &(pAlert->pSystemFirst), &(pAlert->pLineFirst), IDM_VIEWALERT) ;

   bDelayAddAction = FALSE ;

   AlertAddAction () ;

   return (TRUE) ;
   }  // OpenAlertVer1

BOOL OpenAlert (HWND hWndAlert,
                HANDLE hFile,
                DWORD dwMajorVersion,
                DWORD dwMinorVersion,
                BOOL bAlertFile)
   {  // OpenAlert
   PALERT         pAlert ;
   DISKALERT      DiskAlert ;
   BOOL           bSuccess = TRUE ;

   pAlert = AlertData (hWndAlert) ;
   if (!pAlert)
      {
      bSuccess = FALSE ;
      goto Exit0 ;
      }

   if (!FileRead (hFile, &DiskAlert, sizeof (DISKALERT)))
      {
      bSuccess = FALSE ;
      goto Exit0 ;
      }

   switch (dwMajorVersion)
      {
      case (1):

         SetHourglassCursor() ;

         ResetAlertView (hWndAlert) ;

         OpenAlertVer1 (hFile, &DiskAlert, pAlert, dwMinorVersion) ;

         // change to alert view if we are opening a
         // alert file
         if (bAlertFile && iPerfmonView != IDM_VIEWALERT)
            {
            SendMessage (hWndMain, WM_COMMAND, (LONG)IDM_VIEWALERT, 0L) ;
            }

         if (iPerfmonView == IDM_VIEWALERT)
            {
            SetPerfmonOptions (&DiskAlert.perfmonOptions) ;
            }
         UpdateAlertDisplay (hWndAlert) ;

         SetArrowCursor() ;

         break ;
      }  // switch

Exit0:

   if (bAlertFile)
      {
      CloseHandle (hFile) ;
      }

   return (bSuccess) ;
   }  // OpenAlert

void ResetAlertView (HWND hWndAlert)
   {
   PALERT         pAlert ;

   pAlert = AlertData (hWndAlert) ;
   if (!pAlert)
      return ;

   ChangeSaveFileName (NULL, IDM_VIEWALERT) ;

   if (pAlert->pSystemFirst)
      {
      ResetAlert (hWndAlert) ;
      }
   }  // ResetAlertView

void ResetAlert (HWND hWndAlert)
   {  // ResetAlert
   PALERT         pAlert ;

   pAlert = AlertData (hWndAlert) ;
   if (!pAlert)
      return ;

   ClearAlertTimer (pAlert) ;

   ClearLegend (hWndAlertLegend) ;
   if (pAlert->pLineFirst)
      {
      FreeLines (pAlert->pLineFirst) ;
      pAlert->pLineFirst = NULL ;
      }

   if (pAlert->pSystemFirst)
      {
      FreeSystems (pAlert->pSystemFirst) ;
      pAlert->pSystemFirst = NULL ;
      }

   pAlert->bModified = FALSE ;

   // reset visual data
   pAlert->Visual.iColorIndex = 0 ;
   pAlert->Visual.iWidthIndex = 0 ;
   pAlert->Visual.iStyleIndex = 0 ;

   iUnviewedAlerts = 0 ;
   if (iPerfmonView != IDM_VIEWALERT)
      {
      StatusUpdateIcons (hWndStatus) ;
      }

   // remove the horiz. scrollbar
   pAlert->xTextExtent = 0 ;
   LBSetHorzExtent (pAlert->hAlertListBox, pAlert->xTextExtent) ;

   LBReset (pAlert->hAlertListBox) ;
   SizeAlertComponents (hWndAlert) ;

//   WindowInvalidate (hWndAlert) ;
   }  // ResetAlert

void ClearAlertDisplay (HWND hWnd)
   {
   PALERT         pAlert ;

   pAlert = AlertData (hWnd) ;

   // remove the horiz. scrollbar
   pAlert->xTextExtent = 0 ;
   LBSetHorzExtent (pAlert->hAlertListBox, pAlert->xTextExtent) ;

   LBReset (pAlert->hAlertListBox) ;
   }

BOOL SaveAlert (HWND hWndAlert, HANDLE hInputFile, BOOL bGetFileName)
   {
   PALERT         pAlert ;
   PLINE          pLine ;
   HANDLE         hFile ;
   DISKALERT      DiskAlert ;
   PERFFILEHEADER FileHeader ;
   TCHAR          szFileName [256] ;
   BOOL           newFileName = FALSE ;

   if (hInputFile)
      {
      // use the input file handle if it is available
      // this is the case for saving workspace data
      hFile = hInputFile ;
      }
   else
      {
      if (pAlertFullFileName)
         {
         lstrcpy (szFileName, pAlertFullFileName) ;
         }
      if (bGetFileName || pAlertFullFileName == NULL)
         {
         if (!FileGetName (hWndAlert, IDS_ALERTFILE, szFileName))
            {
            return (FALSE) ;
            }
         newFileName = TRUE ;
         }

      hFile = FileHandleCreate (szFileName) ;

      if (hFile && newFileName)
         {
         ChangeSaveFileName (szFileName, IDM_VIEWALERT) ;
         }
      else if (!hFile)
         {
         DlgErrorBox (hWndAlert, ERR_CANT_OPEN, szFileName) ;
         }
      }

   if (!hFile)
      return (FALSE) ;

   pAlert = AlertData (hWndAlert) ;
   if (!pAlert)
      {
      if (!hInputFile)
         {
         CloseHandle (hFile) ;
         }
      return (FALSE) ;

}

   if (!hInputFile)
      {
      memset (&FileHeader, 0, sizeof (FileHeader)) ;
      lstrcpy (FileHeader.szSignature, szPerfAlertSignature) ;
      FileHeader.dwMajorVersion = AlertMajorVersion ;
      FileHeader.dwMinorVersion = AlertMinorVersion ;

      if (!FileWrite (hFile, &FileHeader, sizeof (PERFFILEHEADER)))
         {
         goto Exit0 ;
         }
      }

   DiskAlert.Visual = pAlert->Visual ;
   DiskAlert.dwIntervalSecs = pAlert->iIntervalMSecs ;
   DiskAlert.dwNumLines = NumLines (pAlert->pLineFirst) ;

   // fill in misc alert options
   DiskAlert.MiscOptions = 0 ;
   if (pAlert->bEventLog)
      DiskAlert.MiscOptions = 0x01 ;
   if (pAlert->bLegendOn)
      DiskAlert.MiscOptions += 0x02 ;

//   DiskAlert.bEventLog = pAlert->bEventLog ;
   DiskAlert.bNetworkAlert = pAlert->bNetworkAlert ;
   DiskAlert.bSwitchToAlert = pAlert->bSwitchToAlert ;
   DiskAlert.bManualRefresh = pAlert->bManualRefresh ;
   DiskAlert.perfmonOptions = Options ;
   lstrcpy (DiskAlert.MessageName, pAlert->MessageName) ;
   if (!FileWrite (hFile, &DiskAlert, sizeof (DISKALERT)))
      {
      goto Exit0 ;
      }

   for (pLine = pAlert->pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for
      if (!WriteLine (pLine, hFile))
         {
         goto Exit0 ;
         }
      }  // for

   if (!hInputFile)
      {
      CloseHandle (hFile) ;
      }

   return (TRUE) ;

Exit0:
   if (!hInputFile)
      {
      CloseHandle (hFile) ;

      // only need to report error if not workspace
      DlgErrorBox (hWndAlert, ERR_SETTING_FILE, szFileName) ;
      }
   return (FALSE) ;
   }

BOOL ExportAlertEntry (HANDLE hFile, PALERTENTRY pAlertEntry)
{
   TCHAR          UnicodeBuff [LongTextLen] ;
   CHAR           TempBuff [LongTextLen * 2] ;
   int            StringLen ;
   PLINE          pLine ;

   pLine = pAlertEntry->pLine ;

   // export the alert date-time

   strcpy (TempBuff, LineEndStr) ;
   StringLen = strlen (TempBuff) ;
   SystemTimeDateString (&(pAlertEntry->SystemTime), UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;

   strcat (TempBuff, pDelimiter) ;
   SystemTimeTimeString (&(pAlertEntry->SystemTime), UnicodeBuff, FALSE) ;
   StringLen = strlen (TempBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   // export alert value and trigger condition

   if (pLine)
      {
      TSPRINTF (UnicodeBuff, szNumberFormat, pAlertEntry->eValue) ;
      ConvertDecimalPoint (UnicodeBuff) ;
      ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
      strcat (TempBuff, pDelimiter) ;
      StringLen = strlen (TempBuff) ;
      TempBuff[StringLen] = pAlertEntry->bOver ? '>' : '<' ;
      StringLen++ ;
      TSPRINTF (UnicodeBuff, szNumberFormat, pAlertEntry->eAlertValue) ;
      ConvertDecimalPoint (UnicodeBuff) ;
      ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
      strcat (TempBuff, pDelimiter) ;
      }
   else
      {
      strcpy (TempBuff, pDelimiter) ;
      strcat (TempBuff, pDelimiter) ;
      }
   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   // export Counter, Instance, & Parent names

   if (pLine)
      {
      ConvertUnicodeStr (TempBuff, pLine->lnCounterName) ;
      }
   else
      {
      // system up/down message is stored in lpszInstance.
      ConvertUnicodeStr (TempBuff, pAlertEntry->lpszInstance) ;
      }

   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   if (pLine && !(strempty(pAlertEntry->lpszInstance)))
      {
      ConvertUnicodeStr (&TempBuff[StringLen], pAlertEntry->lpszInstance) ;
      }

   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   if (pLine && !(strempty(pAlertEntry->lpszParent)))
      {
      ConvertUnicodeStr (&TempBuff[StringLen], pAlertEntry->lpszParent) ;
      }
   strcat (TempBuff, pDelimiter) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   // export object, & computer names
   TempBuff[0] = '\0' ;
   if (pLine)
      {
      ConvertUnicodeStr (TempBuff, pLine->lnObjectName) ;
      }
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   if (pLine)
      {
      ConvertUnicodeStr (&TempBuff[StringLen], pLine->lnSystemName) ;
      }
   else
      {
      // system name for the system that is up or down is in
      // lpszParent.
      ConvertUnicodeStr (&TempBuff[StringLen], pAlertEntry->lpszParent) ;
      }

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   return (TRUE) ;

Exit0:
   return (FALSE) ;

}  // ExportAlertEntry

INT ExportAlertLine (PLINE pLine, FLOAT eValue, SYSTEMTIME *pSystemTime, HANDLE hExportFile)
{
   ALERTENTRY     AlertEntry ;
   TCHAR          szInstance [256] ;
   TCHAR          szParent [256] ;
   INT            ErrCode = 0 ;

   AlertEntry.SystemTime = *pSystemTime ;
   AlertEntry.pLine= pLine ;
   AlertEntry.eValue = eValue ;
   AlertEntry.bOver = pLine->bAlertOver ;
   AlertEntry.eAlertValue = pLine->eAlertValue ;

   //=============================//
   // Determine Instance, Parent  //
   //=============================//

   // It's possible that there will be no instance, therefore
   // the lnInstanceName would be NULL.

   if (pLine->lnObject.NumInstances > 0)
      {
      // Test for the parent object instance name title index.
      // If there is one, it implies that there will be a valid
      // Parent Object Name and a valid Parent Object Instance Name.

      // If the Parent Object title index is 0 then
      // just display the instance name.

      lstrcpy (szInstance, pLine->lnInstanceName) ;
      if (pLine->lnInstanceDef.ParentObjectTitleIndex && pLine->lnPINName)
         {
         // Get the Parent Object Name.
         lstrcpy (szParent, pLine->lnPINName) ;
         }
      else
         {
         szParent[0] = TEXT(' ') ;
         szParent[1] = TEXT('\0') ;
         }
      }
   else
      {
      szInstance[0] = TEXT(' ') ;
      szInstance[1] = TEXT('\0') ;
      szParent[0] = TEXT(' ') ;
      szParent[1] = TEXT('\0') ;
      }

   AlertEntry.lpszInstance = szInstance ;
   AlertEntry.lpszParent = szParent ;

   if (!ExportAlertEntry (hExportFile, &AlertEntry))
      {
      ErrCode = ERR_EXPORT_FILE ;
      }

   return ErrCode ;
 }

BOOL ExportAlertLabels (HANDLE hFile)
{
   TCHAR          UnicodeBuff [LongTextLen] ;
   CHAR           TempBuff [LongTextLen * 2] ;
   int            StringLen ;

   strcpy (TempBuff, LineEndStr) ;
   StringLen = strlen (TempBuff) ;
   StringLoad (IDS_EXPORT_DATE, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   StringLoad (IDS_EXPORT_TIME, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   StringLoad (IDS_LABELVALUE, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   StringLoad (IDS_ALERT_TRIGGER, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   StringLoad (IDS_COUNTERNAME, UnicodeBuff) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   StringLoad (IDS_INSTNAME, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   StringLoad (IDS_PARENT, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   StringLoad (IDS_OBJNAME, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   StringLoad (IDS_LABELSYSTEM, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   return (TRUE) ;

Exit0:
   return (FALSE) ;

}  // ExportAlertLabels

void ExportAlert (void)
{
   PALERT         pAlert ;
   HANDLE         hFile = 0 ;
   HWND           hWndAlerts ;
   PALERTENTRY    pAlertEntry ;
   int            AlertTotal ;
   int            iIndex ;
   LPTSTR         pFileName = NULL ;
   INT            ErrCode = 0 ;

   if (!(pAlert = AlertData (hWndAlert)))
      {
      return ;
      }

   // see if there is anything to export..
   if (!(pAlert->pLineFirst))
      {
      return ;
      }

   if (!(hWndAlerts = pAlert->hAlertListBox))
      {
      return ;
      }

   AlertTotal = LBNumItems (hWndAlerts) ;
   if (AlertTotal == LB_ERR || AlertTotal == 0)
      {
      return ;
      }

   SetHourglassCursor() ;

   if (ErrCode = ExportFileOpen (hWndAlert, &hFile, pAlert->iIntervalMSecs, &pFileName))
      {
      goto Exit0 ;
      }

   if (!pFileName)
      {
      // the case when user cancel.
      goto Exit0 ;
      }

   // export the column labels
   if (!ExportAlertLabels (hFile))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }
   if (AlertTotal < ALERTLOGMAXITEMS || !PlayingBackLog())
      {
      for (iIndex = 0 ; iIndex < AlertTotal ; iIndex++)
         {
         // get the alert data
         pAlertEntry = (PALERTENTRY) LBData (hWndAlerts, iIndex) ;

         if (!pAlertEntry || pAlertEntry == (PALERTENTRY)LB_ERR)
            {
            // skip this entry if we hit an error
            continue ;
            }

         // export the alert line
         if (!ExportAlertEntry (hFile, pAlertEntry))
            {
            ErrCode = ERR_EXPORT_FILE ;
            break ;
            }
         }
      }
   else
      {
      // we are playingback log and that the listbox does not
      // contain all the alerts.  In this case, have to walk the
      // log file to re-generate all the alerts.
      ErrCode = PlaybackAlert (hWndAlert, hFile) ;
      }

Exit0:

   SetArrowCursor() ;

   if (hFile)
      {
      CloseHandle (hFile) ;
      }

   if (pFileName)
      {
      if (ErrCode)
         {
         DlgErrorBox (hWndAlert, ErrCode, pFileName) ;
         }

      MemoryFree (pFileName) ;
      }

}  // ExportAlert


ALERT.H

#define iDefaultAlertIntervalSecs  5

#if 0
PALERT AlertData (HWND hWndAlert) ;
#endif
#define AlertData(hWndAlert)        \
   (&Alert)

BOOL AlertInitializeApplication (void) ;

void ClearAlertDisplay (HWND hWnd) ;

HWND CreateAlertWindow (HWND hWndParent) ;

void UpdateAlertDisplay (HWND hWnd) ;

BOOL AlertInsertLine (HWND hWnd, PLINE pLine) ;

BOOL AlertDeleteLine (HWND hWnd, PLINE pLine) ;

void SizeAlertComponents (HWND hDlg) ;

INT PlaybackAlert (HWND hWndAlert, HANDLE hExportFile) ;

#if 0
PLINESTRUCT CurrentAlertLine (HWND hWndAlert) ;
#endif
#define CurrentAlertLine(hWndAlert)       \
   (LegendCurrentLine (hWndAlertLegend))

BOOL AddAlert (HWND hWndParent) ;

BOOL EditAlert (HWND hWndParent) ;

BOOL AlertRefresh (HWND hWnd) ;
BOOL ToggleAlertRefresh (HWND hWnd) ;

void AlertTimer (HWND hWnd, BOOL bForce) ;

BOOL SetAlertTimer (PALERT pAlert) ;

BOOL OpenAlert (HWND hWndAlert,
                HANDLE hFile,
                DWORD dwMajorVersion,
                DWORD dwMinorVersion,
                BOOL bAlertFile) ;

void ResetAlert (HWND hWndAlert) ;
void ResetAlertView (HWND hWndAlert) ;

BOOL SaveAlert (HWND hWndAlert, HANDLE hInputFile, BOOL bGetFileName) ;

void ExportAlert (void) ;

void AlertAddAction (void) ;

void AlertCreateThread (PALERT pAlert) ;


BOOKMARK.C

#include "perfmon.h"
#include "bookmark.h"   // External declarations for this file

#include "log.h"        // for LogWriteBookmark
#include "utils.h"      // for WindowCenter

#include "pmhelpid.h"   // Help IDs

void static OnInitDialog (HDLG hDlg)
   {
   dwCurrentDlgID = HC_PM_idDlgOptionBookMark ;
   EditSetLimit (DialogControl (hDlg, IDD_BOOKMARKCOMMENT),
                 BookmarkCommentLen-1) ;
   WindowCenter (hDlg) ;
   }

void static OnOK (HDLG hDlg)
   {
   TCHAR          szComment [BookmarkCommentLen + 1] ;

   DialogText (hDlg, IDD_BOOKMARKCOMMENT, szComment) ;
   LogWriteBookmark (hWndLog, szComment) ;
   EndDialog (hDlg, 1) ;
   }

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

int FAR WINAPI BookmarkDlgProc (HWND hDlg,
                                unsigned iMessage,
                                WPARAM wParam,
                                LPARAM lParam)
   {
   BOOL           bHandled ;

   bHandled = TRUE ;
   switch (iMessage)
      {
      case WM_INITDIALOG:
         OnInitDialog (hDlg) ;
         return  (TRUE) ;

      case WM_CLOSE:
         dwCurrentDlgID = 0 ;
         EndDialog (hDlg, 0) ;
         break ;

      case WM_COMMAND:
         switch(wParam)
            {
            case IDD_OK:
               dwCurrentDlgID = 0 ;
               OnOK (hDlg) ;
               break ;

            case IDD_CANCEL:
               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, 0) ;
               break ;

            case IDD_BOOKMARKHELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            default:
               bHandled = FALSE ;
               break;
            }
         break;

      default:
            bHandled = FALSE ;
         break ;
      }  // switch

   return (bHandled) ;
   }  // BookmarkDlgProc

BOOL AddBookmark (HWND hWndParent)
   {  // AddBookmark
   return (DialogBox (hInstance, idDlgAddBookmark, hWndParent,
                   BookmarkDlgProc)) ;
   }  // AddBookmark

void BookmarkAppend (PPBOOKMARK ppBookmarkFirst,
                     PBOOKMARK pBookmarkNew)
   {  // BookmarkAppend
   PBOOKMARK      pBookmark ;

   if (!*ppBookmarkFirst)
      *ppBookmarkFirst = pBookmarkNew ;
   else
      {  // else
      for (pBookmark = *ppBookmarkFirst ;
           pBookmark->pBookmarkNext ;
           pBookmark = pBookmark->pBookmarkNext)
         /* nothing */ ;
      pBookmark->pBookmarkNext = pBookmarkNew ;
      }  // else
   }  // BookmarkAppend


BOOKMARK.H

BOOL AddBookmark (HWND hWndParent) ;

void BookmarkAppend (PPBOOKMARK ppBookmarkFirst,
                     PBOOKMARK pBookmarkNew) ;


CHOSECOM.C

/*****************************************************************************
 *
 *  ChoseCom.c - This module handles the Dialog user interactions for the
 *    choose computers within a log file
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 ****************************************************************************/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"       // basic defns, windows.h

#include "dlgs.h"          // common dialog control IDs
#include "playback.h"      // for PlayingBackLog
#include "pmhelpid.h"      // Help IDs
#include "utils.h"         // for CallWinHelp

static LPTSTR  lpChooseComputerText ;
static DWORD   TextLength ;

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnInitDialog (HDLG hDlg)
   {
   // build the listbox of computers wintin the log file
   BuildLogComputerList (hDlg, IDD_CHOOSECOMPUTERLISTBOX) ;

   // set the scroll limit on the edit box
   EditSetLimit (GetDlgItem(hDlg, IDD_CHOOSECOMPUTERNAME), TextLength-1) ;

   dwCurrentDlgID = HC_PM_idDlgLogComputerList ;

   WindowCenter (hDlg) ;
   }  // OnInitDialog

void static OnOK (HDLG hDlg)
   {

   GetDlgItemText (hDlg,
      IDD_CHOOSECOMPUTERNAME,
      lpChooseComputerText,
      TextLength-1) ;

   }  // OnOK

void OnComputerSelectionChanged (HWND hDlg)
   {
   TCHAR localComputerName [MAX_COMPUTERNAME_LENGTH + 3] ;
   int   SelectedIndex ;
   HWND  hWndLB = GetDlgItem (hDlg, IDD_CHOOSECOMPUTERLISTBOX) ;

   // get the listbox selection and put it in the editbox
   SelectedIndex = LBSelection (hWndLB) ;
   if (SelectedIndex != LB_ERR)
      {
      localComputerName[0] = TEXT('\0') ;
      if (LBString (hWndLB, SelectedIndex, localComputerName) != LB_ERR &&
         localComputerName[0])
         {
         SetDlgItemText (hDlg, IDD_CHOOSECOMPUTERNAME, localComputerName) ;
         }
      }
   }  // OnComputerSelectionChanged

BOOL FAR PASCAL ChooseLogComputerDlgProc(HWND hDlg, WORD msg, DWORD wParam, LONG lParam)
   {

   switch (msg)
      {
      case WM_INITDIALOG:
         OnInitDialog (hDlg) ;
         break ;

      case WM_COMMAND:
         switch (LOWORD(wParam))
            {
            case IDOK:
               OnOK (hDlg) ;
               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, TRUE) ;
               return (TRUE) ;
               break ;

            case IDCANCEL:
               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, FALSE) ;
               return (TRUE) ;

            case ID_HELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            case IDD_CHOOSECOMPUTERLISTBOX:
               if (HIWORD (wParam) == LBN_SELCHANGE)
                  OnComputerSelectionChanged (hDlg) ;
               break ;

            default:
               break;

            }
         break ;

      default:
         break ;
      }

   return (FALSE) ;
   }  // ChooseLogComputerDlgProc

BOOL GetLogFileComputer (HWND hWndParent, LPTSTR lpComputerName, DWORD BufferSize)
   {
   BOOL  bSuccess ;
   DWORD LocalDlgID = dwCurrentDlgID ;

   // initialize some globals
   *lpComputerName = TEXT('\0') ;
   lpChooseComputerText = lpComputerName ;
   TextLength = BufferSize ;

   bSuccess = DialogBox (hInstance,
      idDlgChooseComputer,
      hWndParent,
      (DLGPROC)ChooseLogComputerDlgProc) ;

   dwCurrentDlgID = LocalDlgID ;

   if (*lpComputerName == '\0')
      {
      bSuccess = FALSE ;
      }

   return (bSuccess) ;
   }  // GetLogFileComputer


COMMAND.C

/*
==============================================================================

  Application:

            Microsoft Windows NT (TM) Performance Monitor

  File:
            Command.c -- PerfmonCommand routine and helpers.

            This file contains the PerfmonCommand routine, which handles
            all of the user's menu selections.

  Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
            Microsoft Confidential.
==============================================================================
*/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "stdio.h"
#include "perfmon.h"
#include "command.h"    // External declarations for this file
#include <shellapi.h>   // for ShellAbout

#include "addlog.h"     // for AddLog
#include "alert.h"      // for CurrentAlertLine
#include "bookmark.h"   // for AddBookmark
#include "cderr.h"
#include "datasrc.h"    // for DisplayDataSourceOptions
#include "dialogs.h"
#include "dispoptn.h"   // for DisplayDisplayOptions
#include "fileopen.h"   // for FileOpen
#include "grafdata.h"   // for ChartDeleteLine ClearGraphDisplay
#include "grafdisp.h"   // for ToggleGraphRefresh
#include "graph.h"      // for SizeGraphComponents
#include "init.h"       // for PerfmonClose
#include "legend.h"
#include "log.h"        // for LogTimer
#include "logoptns.h"
#include "playback.h"
#include "report.h"     // for CurrentReportItem
#include "rptoptns.h"   // for DisplayReportOptions
#include "status.h"     // for StatusUpdateIcons
#include "timefrm.h"    // for SetTimeframe
#include "toolbar.h"    // for ToolbarDepressButton
#include "utils.h"
#include "perfmops.h"   // for SaveWorkspace

int static deltax ;
int static deltay ;

#define ABOUT_TIMER_ID 10

int FAR WINAPI AboutDlgProc (HWND hDlg,
                             unsigned iMessage,
                             WPARAM wParam,
                             LPARAM lParam)
   {
   BOOL           bHandled ;

   bHandled = TRUE ;
   switch (iMessage)
      {
      case WM_INITDIALOG:
         deltax = 0 ;
         deltay = 0 ;
         dwCurrentDlgID = 0 ;
         SetTimer(hDlg, ABOUT_TIMER_ID, 1000, NULL) ;
         WindowCenter (hDlg) ;
         return (TRUE) ;

      case WM_TIMER:
         deltax += 2 ;
         if (deltax > 60)
            deltax = 0 ;

         deltay += 5 ;
         if (deltay > 60)
            deltay = 0 ;

         WindowInvalidate (DialogControl (hDlg, 524)) ;
         break ;

      case WM_DRAWITEM:
         {
         int xPos, yPos ;
         LPDRAWITEMSTRUCT lpItem ;

         lpItem = (LPDRAWITEMSTRUCT) lParam ;
         xPos = lpItem->rcItem.left + deltax ;
         yPos = lpItem->rcItem.top + deltay ;
         DrawIcon (lpItem->hDC, xPos, yPos, hIcon) ;
         }
         break ;

      case WM_CLOSE:
         dwCurrentDlgID = 0 ;
         KillTimer (hDlg, ABOUT_TIMER_ID) ;
         EndDialog (hDlg, 1) ;
         break ;

      case WM_COMMAND:
         switch(wParam)
            {
            case IDD_OK:
               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, 1) ;
               break ;

            default:
               bHandled = FALSE ;
               break;
            }
         break;

      default:
            bHandled = FALSE ;
         break ;
      }  // switch

   return (bHandled) ;
   }  // AboutDlgProc

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void ChangeView (HWND hWnd,
                 int iNewView)
   {  // ChangeView
   HMENU hMenu = GetMenu (hWnd) ;
   BOOL  bViewChart, bViewAlert, bViewLog, bViewReport ;

#ifdef ADVANCED_PERFMON
   iPerfmonView = iNewView ;
   bViewChart = bViewAlert = bViewLog = bViewReport = FALSE;

   switch (iNewView)
      {
      case IDM_VIEWCHART:
         bViewChart = TRUE ;
         break ;

      case IDM_VIEWALERT:
         bViewAlert = TRUE ;
         break ;

      case IDM_VIEWLOG:
         bViewLog = TRUE ;
         break ;

      case IDM_VIEWREPORT:
         bViewReport = TRUE ;
         break ;
      }

   WindowShow (hWndGraph, bViewChart) ;
   WindowShow (hWndAlert, bViewAlert) ;
   WindowShow (hWndLog, bViewLog) ;
   WindowShow (hWndReport, bViewReport) ;

   // set focus on the Legend window
   if (iPerfmonView == IDM_VIEWCHART)
      {
      SetFocus (hWndGraphLegend) ;
      }
   else if (iPerfmonView == IDM_VIEWALERT)
      {
      SetFocus (hWndAlertLegend) ;
      }
   else if (iPerfmonView == IDM_VIEWLOG)
      {
      SetFocus (hWndLogEntries) ;
      }
   else if (iPerfmonView == IDM_VIEWREPORT)
      {
      SetFocus (hWndReport) ;
      }

   if (hMenu)
      {
      MenuCheck (hMenu, IDM_VIEWCHART, bViewChart) ;
      MenuCheck (hMenu, IDM_VIEWALERT, bViewAlert) ;
      MenuCheck (hMenu, IDM_VIEWLOG, bViewLog) ;
      MenuCheck (hMenu, IDM_VIEWREPORT, bViewReport) ;

      // show the Title bar
      ShowPerfmonWindowText () ;
      }

   ToolbarDepressButton (hWndToolbar, ChartTool, bViewChart) ;
   ToolbarDepressButton (hWndToolbar, AlertTool, bViewAlert) ;
   ToolbarDepressButton (hWndToolbar, LogTool, bViewLog) ;
   ToolbarDepressButton (hWndToolbar, ReportTool, bViewReport) ;
#else
   // only Chart view in Perfmon Lite
   iPerfmonView = IDM_VIEWCHART ;
   WindowShow (hWndGraph, TRUE) ;
#endif

   DrawMenuBar(hWnd) ;
   StatusLineReady (hWndStatus) ;
   }  // ChangeView

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void ViewChart (HWND hWnd)
   {
   if (Options.bMenubar)
      SetMenu (hWnd, hMenuChart) ;
   ChangeView (hWnd, IDM_VIEWCHART) ;
   }

void ViewAlert (HWND hWnd)
   {
   iUnviewedAlerts = 0 ;
   StatusUpdateIcons (hWndStatus) ;
   if (Options.bMenubar)
      SetMenu (hWnd, hMenuAlert) ;
   ChangeView (hWnd, IDM_VIEWALERT) ;
   }

void ViewLog (HWND hWnd)
   {
   if (Options.bMenubar)
      SetMenu (hWnd, hMenuLog) ;
   ChangeView (hWnd, IDM_VIEWLOG) ;
   }

void ViewReport (HWND hWnd)
   {
   if (Options.bMenubar)
      SetMenu (hWnd, hMenuReport) ;
   ChangeView (hWnd, IDM_VIEWREPORT) ;
   }

#ifdef KEEP_MANUALREFRESH
void ToggleRefresh (HWND hWnd)
   {
   BOOL           bRefresh ;

   switch (iPerfmonView)
      {
      case IDM_VIEWCHART:
         bRefresh = ToggleGraphRefresh (hWndGraph) ;
         break ;

      case IDM_VIEWALERT:
         bRefresh = ToggleAlertRefresh (hWndAlert) ;
         break ;

      case IDM_VIEWLOG:
         bRefresh = ToggleLogRefresh (hWndLog) ;
         break ;

      case IDM_VIEWREPORT:
         bRefresh = ToggleReportRefresh (hWndReport) ;
         break ;
      }  // switch

   MenuCheck (GetMenu (hWnd), IDM_OPTIONSMANUALREFRESH, bRefresh) ;
   }  // ToggleRefresh
#endif

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL PerfmonCommand (HWND hWnd,
                     WPARAM wParam,
                     LPARAM lParam)
/*
   Effect:        Respond to the user's menu selection, found in wParam.
                  In particular, branch to the appropriate OnXXX function
                  to perform the action associated with each command.

   Called By:     MainWndProc (perfmon.c), in response to a WM_COMMAND
                  message.
*/
   {  // PerfmonCommand
   PLINESTRUCT    pLine ;
   BOOL           bPrepareMenu = TRUE ;

   switch (LOWORD (wParam))
      {

      //=============================//
      // Toolbar Commands            //
      //=============================//

      case IDM_TOOLBARADD:
         bPrepareMenu = FALSE ;

         switch (iPerfmonView)
            {
            case IDM_VIEWCHART:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITADDCHART, lParam) ;
               break ;

            case IDM_VIEWALERT:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITADDALERT, lParam) ;
               break ;

            case IDM_VIEWLOG:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITADDLOG, lParam) ;
               break ;

            case IDM_VIEWREPORT:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITADDREPORT, lParam) ;
               break ;
            }  // switch
         break ;

      case IDM_TOOLBARMODIFY:
         bPrepareMenu = FALSE ;

         switch (iPerfmonView)
            {  // switch
            case IDM_VIEWCHART:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITMODIFYCHART, lParam) ;
               break ;

            case IDM_VIEWALERT:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITMODIFYALERT, lParam) ;
               break ;

            case IDM_VIEWREPORT:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITMODIFYREPORT, lParam) ;
               break ;
            }  // switch
         break ;

      case IDM_TOOLBARDELETE:
         bPrepareMenu = FALSE ;

         switch (iPerfmonView)
            {
            case IDM_VIEWCHART:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITDELETECHART, lParam) ;
               break ;

            case IDM_VIEWALERT:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITDELETEALERT, lParam) ;
               break ;

            case IDM_VIEWLOG:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITDELETELOG, lParam) ;
               break ;

            case IDM_VIEWREPORT:
               SendMessage (hWnd, WM_COMMAND, IDM_EDITDELETEREPORT, lParam) ;
               break ;
            }  // switch
         break ;

      case IDM_TOOLBARREFRESH:
         bPrepareMenu = FALSE ;

         switch (iPerfmonView)
            {
            case IDM_VIEWCHART:
               SendMessage (hWnd, WM_COMMAND, IDM_OPTIONSREFRESHNOWCHART, lParam) ;
               break ;

            case IDM_VIEWALERT:
               SendMessage (hWnd, WM_COMMAND, IDM_OPTIONSREFRESHNOWALERT, lParam) ;
               break ;

            case IDM_VIEWLOG:
               SendMessage (hWnd, WM_COMMAND, IDM_OPTIONSREFRESHNOWLOG, lParam) ;
               break ;

            case IDM_VIEWREPORT:
               SendMessage (hWnd, WM_COMMAND, IDM_OPTIONSREFRESHNOWREPORT, lParam) ;
               break ;
            }  // switch
         break ;

      case IDM_TOOLBAROPTIONS:
         bPrepareMenu = FALSE ;

         switch (iPerfmonView)
            {  // switch
            case IDM_VIEWCHART:
               SendMessage (hWnd, WM_COMMAND, IDM_OPTIONSCHART, lParam) ;
               break ;

            case IDM_VIEWALERT:
               SendMessage (hWnd, WM_COMMAND, IDM_OPTIONSALERT, lParam) ;
               break ;

            case IDM_VIEWLOG:
               SendMessage (hWnd, WM_COMMAND, IDM_OPTIONSLOG, lParam) ;
               break ;

            case IDM_VIEWREPORT:
               SendMessage (hWnd, WM_COMMAND, IDM_OPTIONSREPORT, lParam) ;
               break ;
            }  // switch
         break ;

      //=============================//
      // "File" Commands             //
      //=============================//

      case IDM_FILENEWCHART:
//         if (QuerySaveChart (hWnd, pGraphs))
            ResetGraphView (hWndGraph) ;
         break ;

      case IDM_FILENEWALERT:
//         if (QuerySaveAlert (hWnd, hWndAlert))
            ResetAlertView (hWndAlert) ;
         break ;

      case IDM_FILENEWLOG:
         ResetLogView (hWndLog) ;
         break ;

      case IDM_FILENEWREPORT:
         ResetReportView (hWndReport) ;
         break ;

      case IDM_FILEOPENCHART:
         FileOpen (hWndGraph, IDS_CHARTFILE, NULL) ;
         break ;

      case IDM_FILEOPENALERT:
         FileOpen (hWndAlert, IDS_ALERTFILE, NULL) ;
         break ;

      case IDM_FILEOPENLOG:
         FileOpen (hWndLog, IDS_LOGFILE, NULL) ;
         break ;

      case IDM_FILEOPENREPORT:
         FileOpen (hWndReport, IDS_REPORTFILE, NULL) ;
         break ;

      case IDM_FILESAVECHART:
      case IDM_FILESAVEASCHART:
         bPrepareMenu = FALSE ;
         SaveChart (hWndGraph, 0,
            (LOWORD (wParam) == IDM_FILESAVEASCHART) ? TRUE : FALSE) ;
         break;

      case IDM_FILESAVEALERT:
      case IDM_FILESAVEASALERT:
         bPrepareMenu = FALSE ;
         SaveAlert (hWndAlert, 0,
            (LOWORD (wParam) == IDM_FILESAVEASALERT) ? TRUE : FALSE) ;
         break ;

      case IDM_FILESAVELOG:
      case IDM_FILESAVEASLOG:
         bPrepareMenu = FALSE ;
         SaveLog (hWndLog, 0,
            (LOWORD (wParam) == IDM_FILESAVEASLOG) ? TRUE : FALSE) ;
         break ;

      case IDM_FILESAVEREPORT:
      case IDM_FILESAVEASREPORT:
         bPrepareMenu = FALSE ;
         SaveReport (hWndReport, 0,
            (LOWORD (wParam) == IDM_FILESAVEASREPORT) ? TRUE : FALSE) ;
         break ;

      case IDM_FILESAVEWORKSPACE:
         bPrepareMenu = FALSE ;
         SaveWorkspace () ;
         break ;

      case IDM_FILEEXPORTCHART:
         bPrepareMenu = FALSE ;
         ExportChart () ;
         break ;

      case IDM_FILEEXPORTALERT:
         bPrepareMenu = FALSE ;
         ExportAlert () ;
         break ;

      case IDM_FILEEXPORTLOG:
         bPrepareMenu = FALSE ;
         ExportLog () ;
         break ;

      case IDM_FILEEXPORTREPORT:
         bPrepareMenu = FALSE ;
         ExportReport () ;
         break ;

#ifdef KEEP_PRINT
      case IDM_FILEPRINTCHART:
          PrintChart (hWnd, pGraphs) ;
          break ;

      case IDM_FILEPRINTREPORT:
         PrintReport (hWnd, hWndReport) ;
         break ;
#endif

      case IDM_FILEEXIT:
         PerfmonClose (hWnd) ;
         bPrepareMenu = FALSE ;
         break ;

      //=============================//
      // "Edit" Commands             //
      //=============================//

      case IDM_EDITADDCHART:
         AddChart (hWnd) ;
         break;

      case IDM_EDITADDALERT:
         AddAlert (hWnd) ;
         break;

      case IDM_EDITADDLOG:
         AddLog (hWnd) ;
         break ;

      case IDM_EDITADDREPORT:
         AddReport (hWnd) ;
         break ;

      case IDM_EDITCLEARCHART :
         ClearGraphDisplay (pGraphs) ;
         break ;

      case IDM_EDITCLEARALERT:
         ClearAlertDisplay (hWndAlert) ;
         break ;

      case IDM_EDITCLEARREPORT:
         ClearReportDisplay (hWndReport) ;
         break ;

      case IDM_EDITDELETECHART:
         pLine = CurrentGraphLine (hWndGraph) ;
         if (pLine)
            ChartDeleteLine(pGraphs, pLine) ;
         break ;

      case IDM_EDITDELETEALERT:
         pLine = CurrentAlertLine (hWndAlert) ;
         if (pLine)
            AlertDeleteLine (hWndAlert, pLine) ;
         break ;

      case IDM_EDITDELETELOG:
         LogDeleteEntry (hWndLog) ;
         break ;

      case IDM_EDITDELETEREPORT:
         if (CurrentReportItem (hWndReport))
            ReportDeleteItem (hWndReport) ;
         break ;

      case IDM_EDITMODIFYCHART:
         EditChart (hWnd) ;
         break ;

      case IDM_EDITMODIFYALERT:
         EditAlert (hWnd) ;
         break ;

      case IDM_EDITTIMEWINDOW:
         if (PlayingBackLog())
            {
            SetTimeframe (hWnd) ;
            }
         break ;

      //=============================//
      // "View" Commands             //
      //=============================//

      case IDM_VIEWCHART:
         if (iPerfmonView != IDM_VIEWCHART)
            {
            ViewChart (hWnd) ;
            }
         else
            {
            bPrepareMenu = FALSE ;
            ToolbarDepressButton (hWndToolbar, ChartTool, TRUE) ;
            }
         break ;

      case IDM_VIEWALERT:
         if (iPerfmonView != IDM_VIEWALERT)
            {
            ViewAlert (hWnd) ;
            }
         else
            {
            bPrepareMenu = FALSE ;
            ToolbarDepressButton (hWndToolbar, AlertTool, TRUE) ;
            }
         break ;

      case IDM_VIEWLOG:
         if (iPerfmonView != IDM_VIEWLOG)
            {
            ViewLog (hWnd) ;
            }
         else
            {
            bPrepareMenu = FALSE ;
            ToolbarDepressButton (hWndToolbar, LogTool, TRUE) ;
            }
         break ;

      case IDM_VIEWREPORT:
         if (iPerfmonView != IDM_VIEWREPORT)
            {
            ViewReport (hWnd) ;
            }
         else
            {
            bPrepareMenu = FALSE ;
            ToolbarDepressButton (hWndToolbar, ReportTool, TRUE) ;
            }
         break ;

      //=============================//
      // "Options" Commands          //
      //=============================//

      case IDM_OPTIONSCHART:
       DialogBox(hInstance, idDlgChartOptions, hWnd,
                (DLGPROC)GraphOptionDlg);
       break;

      case IDM_OPTIONSALERT:
         DisplayAlertOptions (hWnd, hWndAlert) ;
      break;

      case IDM_OPTIONSLOG:
         DisplayLogOptions (hWnd, hWndLog) ;
         break ;

      case IDM_OPTIONSREPORT:
         if (!PlayingBackLog())
             DisplayReportOptions (hWnd, hWndReport) ;
         break ;

   case IDM_OPTIONSBOOKMARK:
         bPrepareMenu = FALSE ;
         AddBookmark (hWnd) ;
         break;

#ifdef KEEP_DISPLAY_OPTION
      case IDM_OPTIONSDISPLAY:
         DisplayDisplayOptions (hWnd) ;
         break ;
#endif

      case IDM_OPTIONSDISPLAYMENU:
         // ShowPerfmonMenu will update Options.bMenubar..
         ShowPerfmonMenu (!Options.bMenubar) ;
         break ;

      case IDM_OPTIONSDISPLAYTOOL:
         Options.bToolbar = !Options.bToolbar ;
         SizePerfmonComponents () ;
         break ;

      case IDM_OPTIONSDISPLAYSTATUS:
         Options.bStatusbar = !Options.bStatusbar ;
         SizePerfmonComponents () ;
         break ;

      case IDM_OPTIONSDISPLAYONTOP:
         Options.bAlwaysOnTop = !Options.bAlwaysOnTop ;
         WindowSetTopmost (hWndMain, Options.bAlwaysOnTop) ;
         break ;

      case IDM_OPTIONSDATASOURCE:
         DisplayDataSourceOptions (hWnd) ;
         break ;

#ifdef KEEP_MANUALREFRESH
      case IDM_OPTIONSMANUALREFRESH:
         bPrepareMenu = FALSE ;
         if (!PlayingBackLog())
            ToggleRefresh (hWnd) ;
         break ;
#endif

      case IDM_OPTIONSREFRESHNOWCHART:
         bPrepareMenu = FALSE ;
         if (!PlayingBackLog())
            GraphTimer (hWndGraph, TRUE) ;
         break ;

      case IDM_OPTIONSREFRESHNOWALERT:
         bPrepareMenu = FALSE ;
         if (!PlayingBackLog())
            AlertTimer (hWndAlert, TRUE) ;
         break ;

      case IDM_OPTIONSREFRESHNOWLOG:
         bPrepareMenu = FALSE ;
         if (!PlayingBackLog())
            LogTimer (hWndLog, TRUE) ;
         break ;

      case IDM_OPTIONSREFRESHNOWREPORT:
         bPrepareMenu = FALSE ;
         if (!PlayingBackLog())
            ReportTimer (hWndReport, TRUE) ;
         break ;

      case IDM_OPTIONSLEGENDONOFF:
         bPrepareMenu = FALSE ;
         if (iPerfmonView == IDM_VIEWCHART)
            {
            pGraphs->gOptions.bLegendChecked =
               !pGraphs->gOptions.bLegendChecked ;
            SizeGraphComponents (hWndGraph) ;
            WindowInvalidate (hWndGraph) ;
            }
         else if (iPerfmonView == IDM_VIEWALERT)
            {
            PALERT pAlert = AlertData (hWnd) ;

            pAlert->bLegendOn = !pAlert->bLegendOn ;

            SizeAlertComponents (hWndAlert) ;
            WindowInvalidate (hWndAlert) ;
            }
         break ;

      //=============================//
      // "Help" Commands             //
      //=============================//

      case IDM_HELPABOUT:
         {
         TCHAR          szApplication [WindowCaptionLen] ;

         bPrepareMenu = FALSE ;

         if (GetKeyState(VK_SHIFT) < 0 && GetKeyState(VK_CONTROL) < 0)
            {
            DialogBox (hInstance, idDlgAbout, hWndMain, AboutDlgProc) ;
            }
         else
            {
            StringLoad (IDS_APPNAME, szApplication) ;
            ShellAbout (hWnd, szApplication, NULL, hIcon) ;
            }
         }
         break ;

      //======================================//
      //  Generic messages from ACCELERATORS  //
      //======================================//
      case IDM_FILEOPENFILE:
         bPrepareMenu = FALSE ;
         switch (iPerfmonView)
            {  // switch
            case IDM_VIEWCHART:
               SendMessage (hWnd, WM_COMMAND, IDM_FILEOPENCHART, lParam) ;
               break ;

            case IDM_VIEWALERT:
               SendMessage (hWnd, WM_COMMAND, IDM_FILEOPENALERT, lParam) ;
               break ;

            case IDM_VIEWLOG:
               SendMessage (hWnd, WM_COMMAND, IDM_FILEOPENLOG, lParam) ;
               break ;

            case IDM_VIEWREPORT:
               SendMessage (hWnd, WM_COMMAND, IDM_FILEOPENREPORT, lParam) ;
               break ;
            }  // switch
         break ;

      case IDM_FILESAVEFILE:
         bPrepareMenu = FALSE ;
         switch (iPerfmonView)
            {  // switch
            case IDM_VIEWCHART:
               SendMessage (hWnd, WM_COMMAND, IDM_FILESAVECHART, lParam) ;
               break ;

            case IDM_VIEWALERT:
               SendMessage (hWnd, WM_COMMAND, IDM_FILESAVEALERT, lParam) ;
               break ;

            case IDM_VIEWLOG:
               SendMessage (hWnd, WM_COMMAND, IDM_FILESAVELOG, lParam) ;
               break ;

            case IDM_VIEWREPORT:
               SendMessage (hWnd, WM_COMMAND, IDM_FILESAVEREPORT, lParam) ;
               break ;
            }  // switch
         break ;

      case IDM_FILESAVEASFILE:
         bPrepareMenu = FALSE ;
         switch (iPerfmonView)
            {  // switch
            case IDM_VIEWCHART:
               SendMessage (hWnd, WM_COMMAND, IDM_FILESAVEASCHART, lParam) ;
               break ;

            case IDM_VIEWALERT:
               SendMessage (hWnd, WM_COMMAND, IDM_FILESAVEASALERT, lParam) ;
               break ;

            case IDM_VIEWLOG:
               SendMessage (hWnd, WM_COMMAND, IDM_FILESAVEASLOG, lParam) ;
               break ;

            case IDM_VIEWREPORT:
               SendMessage (hWnd, WM_COMMAND, IDM_FILESAVEASREPORT, lParam) ;
               break ;
            }  // switch
         break ;

      case IDM_CHARTHIGHLIGHTON:
         bPrepareMenu = FALSE ;
         if (iPerfmonView == IDM_VIEWCHART)
            {
            ChartHighlight () ;
            }
         break ;

      case IDM_TOOLBARID:
         // msg from the toolbar control
         bPrepareMenu = FALSE ;
         OnToolbarHit (wParam, lParam) ;
         break ;

      case IDM_HELPCONTENTS:
         {
         TCHAR NullStr [2] ;

         NullStr[0] = TEXT('\0') ;
         bPrepareMenu = FALSE ;
         WinHelp (hWndMain, pszHelpFile, HELP_INDEX, (DWORD)(NullStr)) ;
         }
         break ;

      case IDM_HELPSEARCH:
         {
         TCHAR NullStr [2] ;

         NullStr[0] = TEXT('\0') ;
         bPrepareMenu = FALSE ;
         WinHelp (hWndMain, pszHelpFile, HELP_PARTIALKEY, (DWORD)(NullStr)) ;
         }
         break ;

      case IDM_HELPHELP:
         {
         TCHAR NullStr [2] ;

         NullStr[0] = TEXT('\0') ;
         bPrepareMenu = FALSE ;
         WinHelp (hWndMain, pszHelpFile, HELP_HELPONHELP, (DWORD)(NullStr)) ;
         }
         break ;

      default:
         return (FALSE) ;
      }  // switch

   if (bPrepareMenu)
      {
      PrepareMenu (GetMenu (hWnd)) ;
      }

   return (TRUE) ;
   }  // PerfmonCommand

void PrepareMenu (HMENU hMenu)
   {  // PrepareMenu
   BOOL           bPlayingLog ;
   BOOL           bCurrentLine ;
   BOOL           bManualRefresh ;
   BOOL           bLogCollecting ;
   BOOL           bRefresh ;

   // hMenu is NULL when the menu bar display option is off.
   // In that case, we still have to enable/disable all tool buttons
   // So, I have commented out the next 2 lines...
//   if (!hMenu)
//      return ;

   bLogCollecting = LogCollecting (hWndLog) ;
   bPlayingLog = PlayingBackLog () ;

   switch (iPerfmonView)
      {
      case IDM_VIEWCHART:
         bCurrentLine = (CurrentGraphLine (hWndGraph) != NULL) ;
         bRefresh = GraphRefresh (hWndGraph) ;
         bManualRefresh = !bPlayingLog && bCurrentLine ;
         if (hMenu)
            {
            MenuCheck (hMenu, IDM_VIEWCHART, TRUE) ;
            MenuEnableItem (hMenu, IDM_FILEEXPORTCHART, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_EDITMODIFYCHART, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_EDITDELETECHART, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_OPTIONSREFRESHNOWCHART, bManualRefresh) ;
            MenuEnableItem (hMenu, IDM_EDITCLEARCHART, !bPlayingLog && bCurrentLine) ;
            }
         break ;

      case IDM_VIEWALERT:
         bCurrentLine = (CurrentAlertLine (hWndAlert) != NULL) ;
         bRefresh = AlertRefresh (hWndAlert) ;
         bManualRefresh = !bPlayingLog && bCurrentLine ;
         if (hMenu)
            {
            MenuCheck (hMenu, IDM_VIEWALERT, TRUE) ;
            MenuEnableItem (hMenu, IDM_FILEEXPORTALERT, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_EDITMODIFYALERT, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_EDITDELETEALERT, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_EDITCLEARALERT, !bPlayingLog && bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_OPTIONSREFRESHNOWALERT, bManualRefresh) ;
            }
         break ;

      case IDM_VIEWLOG:
         bCurrentLine = AnyLogLine() ;
         bRefresh = LogRefresh (hWndLog) ;
         bManualRefresh = !bPlayingLog && bLogCollecting ;
         if (hMenu)
            {
            MenuCheck (hMenu, IDM_VIEWLOG, TRUE) ;
            MenuEnableItem (hMenu, IDM_FILEEXPORTLOG, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_EDITDELETELOG, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_OPTIONSREFRESHNOWLOG , bManualRefresh) ;
            }
         break ;

      case IDM_VIEWREPORT:
         bCurrentLine = CurrentReportItem (hWndReport) ;
         bRefresh = ReportRefresh (hWndReport) ;
         bManualRefresh = !bPlayingLog && bCurrentLine ;
         if (hMenu)
            {
            MenuCheck (hMenu, IDM_VIEWREPORT, TRUE) ;
            MenuEnableItem (hMenu, IDM_FILEEXPORTREPORT, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_EDITMODIFYREPORT, FALSE) ;
            MenuEnableItem (hMenu, IDM_EDITDELETEREPORT, bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_EDITCLEARREPORT, !bPlayingLog && bCurrentLine) ;
            MenuEnableItem (hMenu, IDM_OPTIONSREFRESHNOWREPORT, bManualRefresh) ;
            }
         break ;
      }  // switch

   ToolbarEnableButton (hWndToolbar, EditTool,
                        bCurrentLine &&
                        (iPerfmonView != IDM_VIEWREPORT &&
                         iPerfmonView != IDM_VIEWLOG)) ;

   ToolbarEnableButton (hWndToolbar, DeleteTool, bCurrentLine) ;

   ToolbarEnableButton (hWndToolbar, RefreshTool, bManualRefresh) ;

   // None of the alert or report options make sense when playing back a log.
#if 0
   ToolbarEnableButton (hWndToolbar,
                        OptionsTool,
                        !bPlayingLog ||
                        (iPerfmonView != IDM_VIEWREPORT &&
                         iPerfmonView != IDM_VIEWALERT)) ;
#endif
   ToolbarEnableButton (hWndToolbar,
                        OptionsTool,
                        !bPlayingLog ||

iPerfmonView != IDM_VIEWREPORT) ;

   ToolbarEnableButton (hWndToolbar, BookmarkTool, bLogCollecting) ;

   if (hMenu)
      {
//      MenuEnableItem (hMenu, IDM_OPTIONSMANUALREFRESH, !bPlayingLog) ;
//      MenuCheck (hMenu, IDM_OPTIONSMANUALREFRESH, bRefresh) ;
      MenuEnableItem (hMenu, IDM_EDITTIMEWINDOW, bPlayingLog) ;
//      MenuEnableItem (hMenu, IDM_OPTIONSALERT, !bPlayingLog) ;
      MenuEnableItem (hMenu, IDM_OPTIONSREPORT, !bPlayingLog) ;
      MenuEnableItem (hMenu, IDM_OPTIONSBOOKMARK, bLogCollecting) ;

      // check/uncheck all the display options
      MenuCheck (hMenu, IDM_OPTIONSDISPLAYMENU, Options.bMenubar) ;
      MenuCheck (hMenu, IDM_OPTIONSDISPLAYTOOL, Options.bToolbar) ;
      MenuCheck (hMenu, IDM_OPTIONSDISPLAYSTATUS, Options.bStatusbar) ;
      MenuCheck (hMenu, IDM_OPTIONSDISPLAYONTOP, Options.bAlwaysOnTop) ;
      }
   }  // PrepareMenu


COMMAND.H

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL PerfmonCommand (HWND hwnd,
                     WPARAM wParam,
                     LPARAM lParam) ;

void PrepareMenu (HMENU hMenu) ;

void ViewChart (HWND hWnd) ;

void ViewLog (HWND hWnd) ;

void ViewAlert (HWND hWnd) ;

void ViewReport (HWND hWnd) ;


COUNTERS.C

/*++ BUILD Version: 0001    // Increment this if a change has global effects

Copyright (c) 1992-1996   Microsoft Corporation

Module Name:

    counters.c

Abstract:

    This module contains the routines to calculate "DataPoint" values from
    the registry data.

    The algoritms were lifted from RussBls's "Data.C" in winmeter.

    All the math is done in floating point to get the correct results, at
    the sacrifice of efficiency on a 386 with not 387. We can always
    revisit these routines later.

Revision History:

    Bob Watson  11/04/92
        -- modified calculations to use more integer math and "early
            exits" to improve efficiency on slower & non-coprocessor
            machines
--*/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"       // perfmon include files
#include "counters.h"      // Exported declarations for this file

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define INVERT             PERF_COUNTER_TIMER_INV
#define NS100_INVERT       PERF_100NSEC_TIMER_INV
#define NS100              PERF_100NSEC_TIMER
#define TIMER_MULTI        PERF_COUNTER_MULTI_TIMER
#define TIMER_MULTI_INVERT PERF_COUNTER_MULTI_TIMER_INV
#define NS100_MULTI        PERF_100NSEC_MULTI_TIMER
#define NS100_MULTI_INVERT PERF_100NSEC_MULTI_TIMER_INV

#define FRACTION 1
#define BULK     1

#define TOO_BIG   (FLOAT)1500000000

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

FLOAT
eGetTimeInterval(
    IN LONGLONG *pliCurrentTime,
    IN LONGLONG *pliPreviousTime,
    IN LONGLONG *pliFreq
)
/*++

Routine Description:

    Get the difference between the current and previous time counts,
        then divide by the frequency.

Arguments:

    IN pCurrentTime
    IN pPreviousTime
        used to compute the duration of this sample (the time between
        samples

    IN pliFreq
        # of  counts (clock ticks) per second

Return Value:

    Floating point representation of Time Interval (seconds)
--*/
{
    FLOAT   eTimeDifference;
    FLOAT   eFreq;
    FLOAT   eTimeInterval ;

    LONGLONG liDifference;

    // Get the number of counts that have occured since the last sample

    liDifference = *pliCurrentTime - *pliPreviousTime;

    if (liDifference <= (LONGLONG)0) {
        return (FLOAT) 0.0f;
    } else {
        eTimeDifference = (FLOAT)liDifference;

        // Get the counts per second

        eFreq = (FLOAT)(*pliFreq) ;
        if (eFreq <= 0.0f)
           return (FLOAT) 0.0f;

        // Get the time since the last sample.

        eTimeInterval = eTimeDifference / eFreq ;

        return (eTimeInterval) ;
    }
} // eGetTimeInterval

FLOAT
Counter_Counter_Common(
    IN PLINESTRUCT pLineStruct,
    IN INT iType
)
/*++

Routine Description:

    Take the difference between the current and previous counts
        then divide by the time interval

Arguments:

    IN pLineStruct
        Line structure containing data to perform computations on

    IN iType
        Counter Type

Return Value:

    Floating point representation of outcome
--*/
{
    FLOAT   eTimeInterval;
    FLOAT   eDifference;
    FLOAT   eCount ;
    BOOL    bValueDrop = FALSE ;

    LONGLONG   liDifference;

    if (iType != BULK) {

        // check if it is too big to be a wrap-around case
        if (pLineStruct->lnaCounterValue[0] <
            pLineStruct->lnaOldCounterValue[0])
           {
           if (pLineStruct->lnaCounterValue[0] -
               pLineStruct->lnaOldCounterValue[0] > (DWORD)0x00ffff0000)
              {
              return (FLOAT) 0.0f;
              }
           bValueDrop = TRUE ;
           }

        liDifference = pLineStruct->lnaCounterValue[0] -
                       pLineStruct->lnaOldCounterValue[0];

        liDifference &= (DWORD)(0x0ffffffff);

    } else {
        liDifference = pLineStruct->lnaCounterValue[0] -
                       pLineStruct->lnaOldCounterValue[0];
    }

    if (liDifference <= (LONGLONG) 0) {
        return (FLOAT) 0.0f;
    } else {
        eTimeInterval = eGetTimeInterval(&pLineStruct->lnNewTime,
                                        &pLineStruct->lnOldTime,
                                        &pLineStruct->lnPerfFreq) ;
        if (eTimeInterval <= 0.0f) {
            return (FLOAT) 0.0f;
        } else {
            eDifference = (FLOAT)(liDifference);

            eCount         = eDifference / eTimeInterval ;

            if (bValueDrop && eCount > (FLOAT) TOO_BIG) {
                // ignore this bogus data since it is too big for
                // the wrap-around case
                eCount = (FLOAT) 0.0f ;
            }
            return(eCount) ;
        }
    }
} // Counter_Counter_Common

FLOAT
Counter_Average_Timer(
    IN PLINESTRUCT pLineStruct
)
/*++

Routine Description:

    Take the differences between the current and previous times and counts
    divide the time interval by the counts multiply by 10,000,000 (convert
    from 100 nsec to sec)

Arguments:

    IN pLineStruct
        Line structure containing data to perform computations on

Return Value:

    Floating point representation of outcome
--*/
{
    FLOAT   eTimeInterval;
    FLOAT   eCount;

    LONGLONG    liDifference;

    // Get the current and previous counts.

    liDifference = (DWORD)pLineStruct->lnaCounterValue[1] -
            (DWORD)pLineStruct->lnaOldCounterValue[1];

    if ( liDifference <= 0) {
        return (FLOAT) 0.0f;
    } else {
        // Get the amount of time that has passed since the last sample
        eTimeInterval = eGetTimeInterval(&pLineStruct->lnaCounterValue[0],
                                            &pLineStruct->lnaOldCounterValue[0],
                                            &pLineStruct->lnPerfFreq) ;

        if (eTimeInterval < 0.0f) { // return 0 if negative time has passed
            return (0.0f);
        } else {
            // Get the number of counts in this time interval.
            eCount = eTimeInterval / (FLOAT)(liDifference);
            return(eCount) ;
        }
    }
} //Counter_Average_Timer

FLOAT
Counter_Average_Bulk(
    IN PLINESTRUCT pLineStruct
)
/*++

Routine Description:

    Take the differences between the current and previous byte counts and
    operation counts divide the bulk count by the operation counts

Arguments:

    IN pLineStruct
        Line structure containing data to perform computations on

Return Value:

    Floating point representation of outcome
--*/
{
    FLOAT   eBulkDelta;
    FLOAT   eDifference;
    FLOAT   eCount;

    LONGLONG liDifference;
    LONGLONG liBulkDelta;

    // Get the bulk count increment since the last sample

    liBulkDelta = pLineStruct->lnaCounterValue[0] -
            pLineStruct->lnaOldCounterValue[0];

    if (liBulkDelta <= (LONGLONG) 0) {
        return (FLOAT) 0.0f;
    } else {
        // Get the current and previous counts.
        liDifference = (DWORD)pLineStruct->lnaCounterValue[1] -
                (DWORD) pLineStruct->lnaOldCounterValue[1];
        liDifference &= (DWORD) (0x0ffffffff);

        // Get the number of counts in this time interval.

        if ( liDifference <= (LONGLONG) 0) {
            // Counter value invalid
            return (FLOAT) 0.0f;
        } else {
            eBulkDelta = (FLOAT) (liBulkDelta);
            eDifference = (FLOAT) (liDifference);
            eCount = eBulkDelta / eDifference ;

            // Scale the value to up to 1 second

            return(eCount) ;
        }
    }
} // Counter_Average_Bulk

FLOAT
Counter_Timer_Common(
    IN  PLINESTRUCT pLineStruct,
    IN  INT iType
)
/*++

Routine Description:

    Take the difference between the current and previous counts,
        Normalize the count (counts per interval)
        divide by the time interval (count = % of interval)
        if (invert)
            subtract from 1 (the normalized size of an interval)
        multiply by 100 (convert to a percentage)
        this value from 100.

Arguments:

    IN pLineStruct
        Line structure containing data to perform computations on

    IN iType
        Counter Type

Return Value:

    Floating point representation of outcome
--*/
{
    FLOAT   eTimeInterval;
    FLOAT   eDifference;
    FLOAT   eFreq;
    FLOAT   eFraction;
    FLOAT   eMultiBase;
    FLOAT   eCount ;

    LONGLONG   liTimeInterval;
    LONGLONG   liDifference;

    // Get the amount of time that has passed since the last sample

    if (iType == NS100 ||
        iType == NS100_INVERT ||
        iType == NS100_MULTI ||
        iType == NS100_MULTI_INVERT) {
            liTimeInterval = pLineStruct->lnNewTime100Ns -
                pLineStruct->lnOldTime100Ns ;
            eTimeInterval = (FLOAT) (liTimeInterval);
    } else {
            eTimeInterval = eGetTimeInterval(&pLineStruct->lnNewTime,
                                            &pLineStruct->lnOldTime,
                                            &pLineStruct->lnPerfFreq) ;
    }

    if (eTimeInterval <= 0.0f)
       return (FLOAT) 0.0f;

    // Get the current and previous counts.

    liDifference = pLineStruct->lnaCounterValue[0] -
            pLineStruct->lnaOldCounterValue[0] ;

    // Get the number of counts in this time interval.
    // (1, 2, 3 or any number of seconds could have gone by since
    // the last sample)

    eDifference = (FLOAT) (liDifference) ;

    if (iType == 0 || iType == INVERT)
    {
        // Get the counts per interval (second)

        eFreq = (FLOAT) (pLineStruct->lnPerfFreq) ;
        if (eFreq <= 0.0f)
           return (FLOAT) 0.0f;

        // Calculate the fraction of the counts that are used by whatever
        // we are measuring

        eFraction = eDifference / eFreq ;
    }
    else
    {
        eFraction = eDifference ;
    }

    // Calculate the fraction of time used by what were measuring.

    eCount = eFraction / eTimeInterval ;

    // If this is  an inverted count take care of the inversion.

    if (iType == INVERT || iType == NS100_INVERT)
        eCount = (FLOAT) 1.0 - eCount ;

    // If this is  an inverted multi count take care of the inversion.

    if (iType == TIMER_MULTI_INVERT || iType == NS100_MULTI_INVERT) {
        eMultiBase  = (FLOAT)pLineStruct->lnaCounterValue[1] ;
        eCount = (FLOAT) eMultiBase - eCount ;
    }

    // Scale the value to up to 100.

    eCount *= 100.0f ;

    if (eCount < 0.0f) eCount = 0.0f ;

    if (eCount > 100.0f &&
        iType != NS100_MULTI &&
        iType != NS100_MULTI_INVERT &&
        iType != TIMER_MULTI &&
        iType != TIMER_MULTI_INVERT) {

        eCount = 100.0f;
    }

    return(eCount) ;
} // Counter_Timer_Common

FLOAT
Counter_Raw_Fraction(
    IN PLINESTRUCT pLineStruct
)
/*++

Routine Description:

    Evaluate a raw fraction (no time, just two values: Numerator and
        Denominator) and multiply by 100 (to make a percentage;

Arguments:

    IN pLineStruct
        Line structure containing data to perform computations on

Return Value:

    Floating point representation of outcome
--*/
{
    FLOAT   eCount ;

    LONGLONG   liNumerator;

    if ( pLineStruct->lnaCounterValue[0] == 0 ||
            pLineStruct->lnaCounterValue[1] == 0 ) {
        // invalid value
        return (0.0f);
    } else {
        liNumerator = pLineStruct->lnaCounterValue[0] * 100;
        eCount = (FLOAT) (liNumerator)  /
                 (FLOAT) pLineStruct->lnaCounterValue[1];
        return(eCount) ;
    }
} // Counter_Raw_Fraction

FLOAT
eElapsedTime(
    PLINESTRUCT pLineStruct,
    INT iType
)
/*++

Routine Description:

    Converts 100NS elapsed time to fractional seconds

Arguments:

    IN pLineStruct
        Line structure containing data to perform computations on

    IN iType
        Unused.

Return Value:

    Floating point representation of elapsed time in seconds
--*/
{
    FLOAT   eSeconds ;

    LONGLONG   liDifference;

    if (pLineStruct->lnaCounterValue[0] <= (LONGLONG) 0) {
        // no data [start time = 0] so return 0
        return (FLOAT) 0.0f;
    } else {
        LONGLONG PerfFreq;

        PerfFreq = *(LONGLONG UNALIGNED *)(&pLineStruct->lnObject.PerfFreq) ;

        // otherwise compute difference between current time and start time
        liDifference =
            pLineStruct->lnNewTime - pLineStruct->lnaCounterValue[0];

        if (liDifference <= (LONGLONG) 0 ||
            PerfFreq <= 0) {
            return (FLOAT) 0.0f;
        } else {
            // convert to fractional seconds using object counter
            eSeconds = (FLOAT) (liDifference) /
                (FLOAT) (PerfFreq);

            return (eSeconds);
        }
    }

} // eElapsedTime

FLOAT
Sample_Common(
    PLINESTRUCT pLineStruct,
    INT iType
)
/*++

Routine Description:

    Divites "Top" differenced by Base Difference

Arguments:

    IN pLineStruct
        Line structure containing data to perform computations on

    IN iType
        Counter Type

Return Value:

    Floating point representation of outcome
--*/
{
    FLOAT   eCount ;

    LONG    lDifference;
    LONG    lBaseDifference;

    lDifference = (DWORD)pLineStruct->lnaCounterValue[0] -
        (DWORD)pLineStruct->lnaOldCounterValue[0] ;
    lDifference &= (DWORD) (0x0ffffffff);

    if (lDifference <= 0) {
        return (FLOAT) 0.0f;
    } else {
        lBaseDifference = (DWORD)pLineStruct->lnaCounterValue[1] -
            (DWORD)pLineStruct->lnaOldCounterValue[1] ;

        if ( lBaseDifference <= 0 ) {
            // invalid value
            return (0.0f);
        } else {
            eCount = (FLOAT)lDifference / (FLOAT)lBaseDifference ;

            if (iType == FRACTION) {
                eCount *= (FLOAT) 100.0f ;
            }
            return(eCount) ;
        }
    }
} // Sample_Common

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

/*****************************************************************************
 * Counter_Counter - Take the difference between the current and previous
 *                   counts then divide by the time interval
 ****************************************************************************/
#define Counter_Counter(pLineStruct)      \
        Counter_Counter_Common(pLineStruct, 0)
#if 0
FLOAT Counter_Counter(PLINESTRUCT pLineStruct)
{
        return Counter_Counter_Common(pLineStruct, 0) ;
}
#endif

/*****************************************************************************
 * Counter_Bulk    - Take the difference between the current and previous
 *                   counts then divide by the time interval
 *                   Same as a Counter_counter except it uses lognlong
 ****************************************************************************/
#define Counter_Bulk(pLineStruct)         \
        Counter_Counter_Common(pLineStruct, BULK)
#if 0
FLOAT Counter_Bulk(PLINESTRUCT pLineStruct)
{
        return Counter_Counter_Common(pLineStruct, BULK) ;
}
#endif

/*****************************************************************************
 * Counter_Timer100Ns -
 *
 *      Need to review with RussBl exactly what he is doing here.
 ****************************************************************************/
#define Counter_Timer100Ns(pLineStruct)     \
        Counter_Timer_Common(pLineStruct, NS100)
#if 0
FLOAT Counter_Timer100Ns(PLINESTRUCT pLineStruct)
{
        return Counter_Timer_Common(pLineStruct, NS100) ;
}
#endif

/*****************************************************************************
 * Counter_Timer100Ns_Inv -
 *
 *      Need to review with RussBl exactly what he is doing here.
 ****************************************************************************/
#define Counter_Timer100Ns_Inv(pLineStruct)     \
        Counter_Timer_Common(pLineStruct, NS100_INVERT)
#if 0
FLOAT Counter_Timer100Ns_Inv(PLINESTRUCT pLineStruct)
{
        return Counter_Timer_Common(pLineStruct, NS100_INVERT) ;

}
#endif

/*****************************************************************************
 * Counter_Timer_Multi -
 *
 *      Need to review with RussBl exactly what he is doing here.
 ****************************************************************************/
#define Counter_Timer_Multi(pLineStruct)     \
        Counter_Timer_Common(pLineStruct, TIMER_MULTI)
#if 0
FLOAT Counter_Timer_Multi(PLINESTRUCT pLineStruct)
{
        return Counter_Timer_Common(pLineStruct, TIMER_MULTI) ;
}
#endif

/*****************************************************************************
 * Counter_Timer_Multi_Inv -
 *
 *      Need to review with RussBl exactly what he is doing here.
 ****************************************************************************/
#define Counter_Timer_Multi_Inv(pLineStruct)       \
        Counter_Timer_Common(pLineStruct, TIMER_MULTI_INVERT)
#if 0
FLOAT Counter_Timer_Multi_Inv(PLINESTRUCT pLineStruct)
{
        return Counter_Timer_Common(pLineStruct, TIMER_MULTI_INVERT) ;
}
#endif

/*****************************************************************************
 * Counter_Timer100Ns_Multi -
 *
 *      Need to review with RussBl exactly what he is doing here.
 ****************************************************************************/
#define Counter_Timer100Ns_Multi(pLineStruct)     \
        Counter_Timer_Common(pLineStruct, NS100_MULTI)
#if 0
FLOAT Counter_Timer100Ns_Multi(PLINESTRUCT pLineStruct)
{
        return Counter_Timer_Common(pLineStruct, NS100_MULTI) ;
}
#endif

/*****************************************************************************
 * Counter_Timer100Ns_Multi_Inv -
 *
 *      Need to review with RussBl exactly what he is doing here.
 ****************************************************************************/
#define Counter_Timer100Ns_Multi_Inv(pLineStruct)    \
        Counter_Timer_Common(pLineStruct, NS100_MULTI_INVERT)
#if 0
FLOAT Counter_Timer100Ns_Multi_Inv(PLINESTRUCT pLineStruct)
{
        return Counter_Timer_Common(pLineStruct, NS100_MULTI_INVERT) ;
}
#endif

/*****************************************************************************
 * Counter_Timer - Take the difference between the current and previous
 *                 counts,
 *                 Normalize the count (counts per interval)
 *                 divide by the time interval (count = % of interval)
 *                 multiply by 100 (convert to a percentage)
 *                 this value from 100.
 ****************************************************************************/
#define Counter_Timer(pLineStruct)       \
        Counter_Timer_Common(pLineStruct, 0)
#if 0
FLOAT Counter_Timer(PLINESTRUCT pLineStruct)
{
        return Counter_Timer_Common(pLineStruct, 0) ;
}
#endif

/*****************************************************************************
 * Counter_Timer_Inv - Take the difference between the current and previous
 *                     counts,
 *                     Normalize the count (counts per interval)
 *                     divide by the time interval (count = % of interval)
 *                     subtract from 1 (the normalized size of an interval)
 *                     multiply by 100 (convert to a percentage)
 *                     this value from 100.
 ****************************************************************************/
#define Counter_Timer_Inv(pLineStruct)         \
      Counter_Timer_Common(pLineStruct, INVERT)
#if 0
FLOAT Counter_Timer_Inv(PLINESTRUCT pLineStruct)
{
        return Counter_Timer_Common(pLineStruct, INVERT) ;
}
#endif

/*****************************************************************************
 * Sample_Counter -
 ****************************************************************************/
#define Sample_Counter(pLineStruct)      \
      Sample_Common(pLineStruct, 0)
#if 0
FLOAT Sample_Counter(PLINESTRUCT pLineStruct)
{
        return Sample_Common(pLineStruct, 0) ;
}
#endif

/*****************************************************************************
 * Sample_Fraction -
 ****************************************************************************/
#define Sample_Fraction(pLineStruct)     \
     Sample_Common(pLineStruct, FRACTION)
#if 0
FLOAT Sample_Fraction(PLINESTRUCT pLineStruct)
{
        return Sample_Common(pLineStruct, FRACTION) ;
}
#endif

/*****************************************************************************
 * Counter_Rawcount - This is just a raw count.
 ****************************************************************************/
#define Counter_Rawcount(pLineStruct)     \
   ((FLOAT) ((DWORD) (pLineStruct->lnaCounterValue[0])))
#if 0
FLOAT Counter_Rawcount(PLINESTRUCT pLineStruct)
   {
   return((FLOAT) (pLineStruct->lnaCounterValue[0])) ;
   }
#endif

/*****************************************************************************
 * Counter_Large_Rawcount - This is just a raw count.
 ****************************************************************************/
#define Counter_Large_Rawcount(pLineStruct)     \
   ((FLOAT) (pLineStruct->lnaCounterValue[0]))

/*****************************************************************************
 * Counter_Elapsed_Time -
 ****************************************************************************/
#define Counter_Elapsed_Time(pLineStruct)         \
    eElapsedTime (pLineStruct, 0)
#if 0
FLOAT Counter_Elapsed_Time (PLINESTRUCT pLineStruct)
{
    return eElapsedTime (pLineStruct, 0);
}
#endif

/*****************************************************************************
 * Counter_Null - The counters that return nothing go here.
 ****************************************************************************/
#define Counter_Null(pline)        \
        ((FLOAT) 0.0)
#if 0
FLOAT Counter_Null(PLINESTRUCT pline)
{
        return((FLOAT) 0.0);
        pline;
}
#endif

FLOAT
CounterEntry (
    PLINESTRUCT pLine
)
{
    switch (pLine->lnCounterType) {
        case  PERF_COUNTER_COUNTER:
            return Counter_Counter (pLine);

        case  PERF_COUNTER_TIMER:
            return Counter_Timer (pLine);

        case  PERF_COUNTER_QUEUELEN_TYPE:
            return Counter_Queuelen(pLine);

        case  PERF_COUNTER_BULK_COUNT:
            return Counter_Bulk (pLine);

        case  PERF_COUNTER_TEXT:
            return Counter_Null (pLine);

        case  PERF_COUNTER_RAWCOUNT:
        case  PERF_COUNTER_RAWCOUNT_HEX:
            return Counter_Rawcount(pLine);

        case  PERF_COUNTER_LARGE_RAWCOUNT:
        case  PERF_COUNTER_LARGE_RAWCOUNT_HEX:
            return Counter_Large_Rawcount(pLine);

        case  PERF_SAMPLE_FRACTION:
            return Sample_Fraction(pLine);

        case  PERF_SAMPLE_COUNTER:
            return Sample_Counter (pLine);

        case  PERF_COUNTER_NODATA:
            return Counter_Null (pLine);

        case  PERF_COUNTER_TIMER_INV:
            return Counter_Timer_Inv (pLine);

        case  PERF_RAW_BASE:
//      case  PERF_SAMPLE_BASE:
//      case  PERF_AVERAGE_BASE:
            return Counter_Null (pLine);

        case  PERF_AVERAGE_TIMER:
            return Counter_Average_Timer (pLine);

        case  PERF_AVERAGE_BULK:
            return Counter_Average_Bulk (pLine);

        case  PERF_100NSEC_TIMER:
            return Counter_Timer100Ns (pLine);

        case  PERF_100NSEC_TIMER_INV:
            return Counter_Timer100Ns_Inv (pLine);

        case  PERF_COUNTER_MULTI_TIMER:
            return Counter_Timer_Multi (pLine);

        case  PERF_COUNTER_MULTI_TIMER_INV:
            return Counter_Timer_Multi_Inv (pLine);

        case  PERF_COUNTER_MULTI_BASE:
            return Counter_Null (pLine);

        case  PERF_100NSEC_MULTI_TIMER:
            return Counter_Timer100Ns_Multi (pLine);

        case  PERF_100NSEC_MULTI_TIMER_INV:
            return Counter_Timer100Ns_Multi_Inv (pLine);

        case  PERF_RAW_FRACTION:
            return Counter_Raw_Fraction (pLine);

        case  PERF_ELAPSED_TIME:
            return Counter_Elapsed_Time (pLine);

        default:
            return Counter_Null (pLine);

    }
}

BOOL
IsCounterSupported (
    DWORD dwCounterType
)
{
    switch (dwCounterType) {
// supported counters
        case  PERF_COUNTER_COUNTER:
        case  PERF_COUNTER_TIMER:
        case  PERF_COUNTER_QUEUELEN_TYPE:
        case  PERF_COUNTER_BULK_COUNT:
        case  PERF_COUNTER_RAWCOUNT:
        case  PERF_COUNTER_RAWCOUNT_HEX:
        case  PERF_COUNTER_LARGE_RAWCOUNT:
        case  PERF_COUNTER_LARGE_RAWCOUNT_HEX:
        case  PERF_SAMPLE_FRACTION:
        case  PERF_SAMPLE_COUNTER:
        case  PERF_COUNTER_TIMER_INV:
        case  PERF_AVERAGE_TIMER:
        case  PERF_AVERAGE_BULK:
        case  PERF_100NSEC_TIMER:
        case  PERF_100NSEC_TIMER_INV:
        case  PERF_COUNTER_MULTI_TIMER:
        case  PERF_COUNTER_MULTI_TIMER_INV:
        case  PERF_100NSEC_MULTI_TIMER:
        case  PERF_100NSEC_MULTI_TIMER_INV:
        case  PERF_RAW_FRACTION:
        case  PERF_ELAPSED_TIME:
            return TRUE;

// unsupported counters
        case  PERF_COUNTER_TEXT:
        case  PERF_COUNTER_NODATA:
        case  PERF_RAW_BASE:
//      case  PERF_SAMPLE_BASE:
//      case  PERF_AVERAGE_BASE:
        case  PERF_COUNTER_MULTI_BASE:
        default:
            return FALSE;

    }
}


COUNTERS.H

FLOAT CounterEntry (PLINESTRUCT pLine);
BOOL IsCounterSupported ( DWORD dwCounterType );

FLOAT Counter_Counter(PLINESTRUCT pline);
FLOAT Counter_Bulk(PLINESTRUCT pline);
FLOAT Counter_Timer(PLINESTRUCT pline);
FLOAT Counter_Queuelen(PLINESTRUCT pline);
FLOAT Counter_Text(PLINESTRUCT pline);
FLOAT Counter_Rawcount(PLINESTRUCT pline);
FLOAT Sample_Fraction(PLINESTRUCT pline);
FLOAT Sample_Counter(PLINESTRUCT pline);
FLOAT Counter_Timer_Inv(PLINESTRUCT pline);
FLOAT Counter_Timer100Ns(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Timer100Ns_Inv(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Timer_Multi(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Timer_Multi_Inv(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Timer100Ns_Multi(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Timer100Ns_Multi_Inv(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Average_Timer(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Average_Bulk(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Raw_Fraction(PLINESTRUCT pLineStruct) ;
FLOAT Counter_Elapsed_Time (PLINESTRUCT pLineStruct);

FLOAT Counter_Null(PLINESTRUCT pline);


DATASRC.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"
#include "datasrc.h"       // External declarations for this file

#include "fileutil.h"      // for FileErrorMessageBox (whatever)
#include "grafdata.h"      // for ResetGraph
#include "alert.h"         // for ResetAlert
#include "log.h"           // for ResetLog
#include "report.h"        // for ResetReport
#include "playback.h"
#include "status.h"
#include "utils.h"
#include "pmhelpid.h"      // Help IDs
#include "fileopen.h"      // FileOpneHookProc
#include "pmemory.h"       // for MemoryAllocate & MemoryFree
#include "perfmops.h"      // for ShowPerfmonWindowText

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

BOOL           bIgnoreFirstChange ;
BOOL           bDataSourceNow ;
BOOL           bDataSourcePrevious ;
BOOL           bLogFileNameChanged ;

LPTSTR           pszLogFilePath ;
LPTSTR           pszLogFileTitle ;
//TCHAR          szLogFilePath [FilePathLen + 1] ;
//TCHAR          szLogFileTitle [FilePathLen + 1] ;

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void static UpdateLogName (HDLG hDlg)
   {
   DialogSetString (hDlg, IDD_DATASOURCEFILENAME, pszLogFilePath) ;
//   DialogSetString (hDlg, IDD_DATASOURCEFILENAME, pszLogFileTitle) ;
   }

void OnChangeLog (HWND hWndParent)
   {  // OnChangeLog
   OPENFILENAME   ofn ;
   TCHAR          szOpenLog [WindowCaptionLen + 1] ;
   TCHAR          aszOpenFilter[LongTextLen] ;
   TCHAR          szMyLogFilePath [FilePathLen + 1] ;
   int            StringLength ;
   DWORD          SaveCurrentDlgID = dwCurrentDlgID ;

   //=============================//
   // Get Log File                //
   //=============================//

   StringLoad (IDS_OPENLOG, szOpenLog) ;
   StringLoad (IDS_SAVELOGFILEEXT, szMyLogFilePath) ;

   // load the log file extension
   LoadString (hInstance, IDS_SAVELOGFILE, aszOpenFilter,
      sizeof(aszOpenFilter) / sizeof(TCHAR)) ;
   StringLength = lstrlen (aszOpenFilter) + 1 ;
   LoadString (hInstance, IDS_SAVELOGFILEEXT,
      &aszOpenFilter[StringLength],
      sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
   StringLength += lstrlen (&aszOpenFilter[StringLength]) ;

   // setup the end strings
   aszOpenFilter[StringLength+1] = aszOpenFilter[StringLength+2] = TEXT('\0') ;

   ofn.lStructSize = sizeof (OPENFILENAME) ;
   ofn.hwndOwner = hWndParent ;
   ofn.hInstance = hInstance ;
   ofn.lpstrFilter = aszOpenFilter ;
   ofn.lpstrCustomFilter = NULL ;
   ofn.nMaxCustFilter = 0 ;
   ofn.nFilterIndex = 1;
   ofn.lpstrFile = szMyLogFilePath ;
   ofn.nMaxFile = FilePathLen * sizeof (TCHAR) ;
   ofn.lpstrFileTitle = pszLogFileTitle ;
   ofn.nMaxFileTitle = FilePathLen * sizeof (TCHAR) ;
   ofn.lpstrInitialDir = NULL ;
   ofn.lpstrTitle = szOpenLog ;
   ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY |
               OFN_PATHMUSTEXIST | OFN_SHOWHELP  | OFN_ENABLEHOOK ;
   ofn.lCustData = 0L ;
   ofn.lpfnHook = (LPOFNHOOKPROC) FileOpenHookProc ;
   ofn.lpstrDefExt = (LPTSTR)NULL;

   dwCurrentDlgID  = HC_PM_idDlgOptionOpenLogFile ;
   if (GetOpenFileName (&ofn))
      {
      if (!strsame(pszLogFilePath, szMyLogFilePath))
         {
         int    FileNameOffset ;
         LPTSTR pFileName ;

         bLogFileNameChanged |= TRUE ;
         lstrcpy (pszLogFilePath, szMyLogFilePath) ;
         lstrcpy (pszLogFileTitle, ofn.lpstrFileTitle) ;

         pFileName = ExtractFileName (szMyLogFilePath) ;
         if (pFileName != szMyLogFilePath)
            {
            FileNameOffset = pFileName - szMyLogFilePath ;
            szMyLogFilePath[FileNameOffset] = TEXT('\0') ;
            SetCurrentDirectory (szMyLogFilePath) ;
            }
         UpdateLogName (hWndParent) ;
         }
      }

   // restore the global before exit
   dwCurrentDlgID  = SaveCurrentDlgID ;
   }  // OnChangeLog

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnInitDialog (HDLG hDlg)
   {
   bLogFileNameChanged = FALSE ;

   bIgnoreFirstChange = TRUE ;

   bDataSourcePrevious = bDataSourceNow = !PlayingBackLog () ;

   CheckRadioButton (hDlg, IDD_DATASOURCENOW, IDD_DATASOURCEFILE,
                     bDataSourceNow ? IDD_DATASOURCENOW : IDD_DATASOURCEFILE) ;
   UpdateLogName (hDlg) ;

   EditSetLimit (GetDlgItem(hDlg, IDD_DATASOURCEFILENAME),
      FilePathLen - 1) ;

   WindowCenter (hDlg) ;

   dwCurrentDlgID = HC_PM_idDlgOptionDataFrom ;
   }

void /*static*/ OnDataSourceOK (HDLG hDlg)
   {  // OnOK
   BOOL     bHaveResetPerfmon ;
   INT      RetCode = 0 ;

   bHaveResetPerfmon = FALSE;
   if (!BoolEqual (bDataSourceNow, bDataSourcePrevious) ||
       (bLogFileNameChanged && !bDataSourceNow) )
      {
      if (PlayingBackLog () && bDataSourceNow | bLogFileNameChanged)
         {
         CloseInputLog (hWndMain) ;
         bHaveResetPerfmon = TRUE ;
         }

      if (!bDataSourceNow)
         {
         if (!bHaveResetPerfmon)
            {
            ResetGraphView (hWndGraph) ;
            ResetAlertView (hWndAlert) ;
            ResetLogView (hWndLog) ;
            ResetReportView (hWndReport) ;

            if (pWorkSpaceFullFileName)
               {
               MemoryFree (pWorkSpaceFullFileName) ;
               pWorkSpaceFileName = NULL ;
               pWorkSpaceFullFileName = NULL ;
               }
            ShowPerfmonWindowText () ;
            }

         GetDlgItemText (hDlg, IDD_DATASOURCEFILENAME,
            pszLogFilePath, FilePathLen - 1) ;
         lstrcpy (pszLogFileTitle, pszLogFilePath);

         if (RetCode = OpenPlayback (pszLogFilePath, pszLogFileTitle))
            {
            DlgErrorBox (hDlg, RetCode, pszLogFileTitle) ;
            }
         }

      StatusLineReady (hWndStatus) ;
      }

   if (!BoolEqual (bDataSourceNow, bDataSourcePrevious))
      {
      if (bDataSourceNow)
         {
         // Set Priority high
         SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS) ;
         SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ;
         }
      else
         {
         // Use a lower priority for Playing back log
         SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS) ;
         SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL) ;
         }
      }

   if (RetCode == 0)
      {
      EndDialog (hDlg, 1) ;
      }
   }  // OnOK

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

int FAR WINAPI DataSourceDlgProc (HWND hDlg,
                                  unsigned iMessage,
                                  WPARAM wParam,
                                  LPARAM lParam)
   {
   BOOL           bHandled ;

   bHandled = TRUE ;
   switch (iMessage)
      {
      case WM_INITDIALOG:
         OnInitDialog (hDlg) ;
         return  (TRUE) ;

      case WM_CLOSE:
         dwCurrentDlgID = 0 ;
         EndDialog (hDlg, 0) ;
         break ;

      case WM_COMMAND:
         switch(LOWORD(wParam))
            {
            case IDD_DATASOURCEFILENAME:
               if (bIgnoreFirstChange)
                  {
                  bIgnoreFirstChange = FALSE;
                  }

               else if (HIWORD(wParam) == EN_UPDATE && !bLogFileNameChanged)
                  {
                  bLogFileNameChanged = TRUE;
                  CheckRadioButton (hDlg,
                                  IDD_DATASOURCENOW,
                                  IDD_DATASOURCEFILE,
                                  IDD_DATASOURCEFILE) ;
                  bDataSourceNow = FALSE ;
                  }
               break ;

            case IDD_DATASOURCECHANGEFILE:
               OnChangeLog (hDlg) ;
               if (bLogFileNameChanged)
                  {
                  CheckRadioButton (hDlg,
                                    IDD_DATASOURCENOW,
                                    IDD_DATASOURCEFILE,
                                    IDD_DATASOURCEFILE) ;
                  bDataSourceNow = FALSE ;
                  }
               break ;

            case IDD_DATASOURCEFILE:
               bDataSourceNow = FALSE ;
               break ;

            case IDD_DATASOURCENOW:
               bDataSourceNow = TRUE ;
               break ;

            case IDD_OK:
               dwCurrentDlgID = 0 ;
               OnDataSourceOK (hDlg) ;
               break ;

            case IDD_CANCEL:
               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, 0) ;
               break ;

            case IDD_DATASOURCEHELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            default:
               bHandled = FALSE ;
               break;
            }
         break;

      default:
            bHandled = FALSE ;
         break ;
      }  // switch

   return (bHandled) ;
   }  // DataSourceDlgProc

BOOL DisplayDataSourceOptions (HWND hWndParent)
/*
   Effect:        Put up Perfmon's Data Source Options Display dialog,
                  which allows
                  the user to select the source of data input: real
                  time or log file.
*/
   {  // DisplayDisplayOptions
   BOOL     retCode ;

   pszLogFilePath  = (LPTSTR) MemoryAllocate (FilePathLen * sizeof(TCHAR)) ;
   pszLogFileTitle = (LPTSTR) MemoryAllocate (FilePathLen * sizeof(TCHAR)) ;

   lstrcpy (pszLogFilePath, PlaybackLog.szFilePath) ;
   lstrcpy (pszLogFileTitle, PlaybackLog.szFileTitle) ;

   retCode = DialogBox (hInstance, idDlgDataSource,
                      hWndParent, (DLGPROC) DataSourceDlgProc) ;

   MemoryFree (pszLogFilePath) ;
   MemoryFree (pszLogFileTitle) ;

   return (retCode) ;

   }  // DisplayDisplayOptions


DATASRC.H

BOOL DisplayDataSourceOptions (HWND hWndParent) ;


DIALOGS.C

/*****************************************************************************
 *
 *  Dialogs.c - This module handles the Menu and Dialog user interactions.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 ****************************************************************************/

#include <stdio.h>
#include <wchar.h>   // for swscanf

#include "perfmon.h"
#include "perfmops.h"      // for ConvertDecimalPoint

#include "alert.h"     // for SetAlertTimer, AlertData
#include "graph.h"
#include "cderr.h"
#include "utils.h"
#include "playback.h"  // for PlayingBackLog
#include "grafdisp.h"  // for ToggleGraphRefresh
#include "pmhelpid.h"  // Help IDs

BOOL          LocalManualRefresh ;

/***************************************************************************/
BOOL FAR PASCAL GraphOptionDlg(HWND hDlg, WORD msg, DWORD wParam, LONG lParam)
/***************************************************************************/
{
static GRAPH_OPTIONS goLocalCopy ;

INT            iTimeMilliseconds ;
TCHAR          szBuff[MiscTextLen] ;
PGRAPHSTRUCT   lgraph;

   lParam ;
   lgraph = pGraphs;

   switch(msg)
      {

      case WM_INITDIALOG:

         dwCurrentDlgID = HC_PM_idDlgOptionChart ;

         // Init the Radio button, Check boxes and text fields.

         goLocalCopy.iGraphOrHistogram =
            lgraph->gOptions.iGraphOrHistogram ;
         if (lgraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
            CheckRadioButton(hDlg, ID_GRAPH, ID_HISTOGRAM, ID_GRAPH) ;
         else
            CheckRadioButton(hDlg, ID_GRAPH, ID_HISTOGRAM, ID_HISTOGRAM) ;

         CheckDlgButton(hDlg, ID_LEGEND, lgraph->gOptions.bLegendChecked) ;
         if (!(lgraph->gOptions.bLegendChecked))
            {
            // can't display valuebar w/o legend
            DialogEnable (hDlg, IDD_CHARTOPTIONSVALUEBAR, FALSE) ;
            }

         CheckDlgButton(hDlg, ID_LABELS, lgraph->gOptions.bLabelsChecked) ;
         CheckDlgButton(hDlg, ID_VERT_GRID, lgraph->gOptions.bVertGridChecked) ;
         CheckDlgButton(hDlg, ID_HORZ_GRID, lgraph->gOptions.bHorzGridChecked) ;
         CheckDlgButton(hDlg, IDD_CHARTOPTIONSVALUEBAR,
            lgraph->gOptions.bStatusBarChecked) ;

         TSPRINTF(szBuff, TEXT("%d"), lgraph->gOptions.iVertMax) ;
         SendDlgItemMessage(hDlg, ID_VERT_MAX, WM_SETTEXT, 0, (LONG) szBuff) ;

         TSPRINTF(szBuff, TEXT("%3.3f"), lgraph->gOptions.eTimeInterval) ;
         ConvertDecimalPoint (szBuff) ;
         SendDlgItemMessage(hDlg, IDD_CHARTOPTIONSINTERVAL, WM_SETTEXT, 0, (LONG) szBuff) ;

         // Pickup a local copy of the Graph Options.

         goLocalCopy = lgraph->gOptions ;
         LocalManualRefresh = lgraph->bManualRefresh ;

         if (PlayingBackLog())
            {
            DialogEnable (hDlg, IDD_CHARTOPTIONSPERIODIC, FALSE) ;
            DialogEnable (hDlg, IDD_CHARTOPTIONSUPDATETEXT, FALSE) ;
            DialogEnable (hDlg, IDD_CHARTOPTIONSMANUALREFRESH, FALSE) ;
            }
         else
            {
            CheckRadioButton (hDlg,
               IDD_CHARTOPTIONSMANUALREFRESH,
               IDD_CHARTOPTIONSPERIODIC,
               LocalManualRefresh ? IDD_CHARTOPTIONSMANUALREFRESH :
               IDD_CHARTOPTIONSPERIODIC) ;
            }

         if (lgraph->bManualRefresh || PlayingBackLog())
            {
            DialogEnable (hDlg, IDD_CHARTOPTIONSINTERVALTEXT, FALSE) ;
            DialogEnable (hDlg, IDD_CHARTOPTIONSINTERVAL, FALSE) ;
            }
         else
            {
            DialogEnable (hDlg, IDD_CHARTOPTIONSINTERVALTEXT, TRUE) ;
            DialogEnable (hDlg, IDD_CHARTOPTIONSINTERVAL, TRUE) ;
            }

         EditSetLimit (GetDlgItem(hDlg, ID_VERT_MAX),
            sizeof(szBuff) / sizeof(TCHAR) - 1) ;

         EditSetLimit (GetDlgItem(hDlg, IDD_CHARTOPTIONSINTERVAL),
            ShortTextLen) ;

         WindowCenter (hDlg) ;
         return(TRUE);

      case WM_COMMAND:
         switch(LOWORD(wParam))
            {
            case ID_VERT_MAX:
               if (HIWORD(wParam) == EN_CHANGE)
                  {
                  DialogText(hDlg, ID_VERT_MAX, szBuff) ;
                  swscanf(szBuff, TEXT("%d"), &goLocalCopy.iVertMax) ;
                  }
               break ;

            case IDD_CHARTOPTIONSINTERVAL:
               if (HIWORD(wParam) == EN_CHANGE)
                  {
                  goLocalCopy.eTimeInterval =
                     DialogFloat (hDlg, IDD_CHARTOPTIONSINTERVAL, NULL) ;
                  }
               break ;

            case IDD_CHARTOPTIONSPERIODIC:
            case IDD_CHARTOPTIONSMANUALREFRESH:
               // check if the Manual refresh is currently checked.
               // Then toggle the ManualRefresh button
               LocalManualRefresh =
                  (LOWORD(wParam) == IDD_CHARTOPTIONSMANUALREFRESH) ;

               CheckRadioButton (hDlg,
                  IDD_CHARTOPTIONSMANUALREFRESH,
                  IDD_CHARTOPTIONSPERIODIC,
                  LocalManualRefresh ? IDD_CHARTOPTIONSMANUALREFRESH :
                  IDD_CHARTOPTIONSPERIODIC) ;

               // gray out time interval if necessary...
               DialogEnable (hDlg, IDD_CHARTOPTIONSINTERVALTEXT,
                  !LocalManualRefresh) ;
               DialogEnable (hDlg, IDD_CHARTOPTIONSINTERVAL,
                  !LocalManualRefresh) ;
               break ;

            case IDD_CHARTOPTIONSVALUEBAR:
               if (goLocalCopy.bStatusBarChecked == TRUE)
                  goLocalCopy.bStatusBarChecked = FALSE ;
               else
                  goLocalCopy.bStatusBarChecked = TRUE ;
               break ;

            case ID_LEGEND:
               if (goLocalCopy.bLegendChecked == TRUE)
                  goLocalCopy.bLegendChecked = FALSE ;
               else
                  goLocalCopy.bLegendChecked = TRUE ;

               DialogEnable (hDlg, IDD_CHARTOPTIONSVALUEBAR,
                  goLocalCopy.bLegendChecked) ;

               break ;

            case ID_LABELS:
               if (goLocalCopy.bLabelsChecked == TRUE)
                  goLocalCopy.bLabelsChecked = FALSE ;
               else
                  goLocalCopy.bLabelsChecked = TRUE ;
               break ;

            case ID_VERT_GRID:
               if (goLocalCopy.bVertGridChecked == TRUE)
                  goLocalCopy.bVertGridChecked = FALSE ;
               else
                  goLocalCopy.bVertGridChecked = TRUE ;
               break ;

            case ID_HORZ_GRID:
               if (goLocalCopy.bHorzGridChecked == TRUE)
                  goLocalCopy.bHorzGridChecked = FALSE ;
               else
                  goLocalCopy.bHorzGridChecked = TRUE ;
               break ;

            case ID_GRAPH:
            case ID_HISTOGRAM:
               if (LOWORD(wParam) == ID_GRAPH)
                  {
                  goLocalCopy.iGraphOrHistogram = LINE_GRAPH ;
                  }
               else
                  {
                  goLocalCopy.iGraphOrHistogram = BAR_GRAPH ;
                  }
               CheckRadioButton(hDlg, ID_GRAPH, ID_HISTOGRAM,
                  goLocalCopy.iGraphOrHistogram == LINE_GRAPH ?
                  ID_GRAPH : ID_HISTOGRAM) ;

               break ;

            case IDOK:
               //  verify some numeric entries first
               if (goLocalCopy.iVertMax > MAX_VERTICAL ||
                   goLocalCopy.iVertMax < MIN_VERTICAL)
                  {
                  DlgErrorBox (hDlg, ERR_BADVERTMAX) ;
                  SetFocus (DialogControl (hDlg, ID_VERT_MAX)) ;
                  EditSetTextEndPos (hDlg, ID_VERT_MAX) ;
                  return (FALSE) ;
                  break ;
                  }
               if (goLocalCopy.eTimeInterval > MAX_INTERVALSEC ||
                   goLocalCopy.eTimeInterval < MIN_INTERVALSEC)
                  {
                  DlgErrorBox (hDlg, ERR_BADTIMEINTERVAL) ;
                  SetFocus (DialogControl (hDlg, IDD_CHARTOPTIONSINTERVAL)) ;
                  EditSetTextEndPos (hDlg, IDD_CHARTOPTIONSINTERVAL) ;
                  return (FALSE) ;
                  break ;
                  }

               // We need to send a size message to the main window
               // so it can setup the redraw of the graph and legend.

               lgraph->gOptions.bLegendChecked    = goLocalCopy.bLegendChecked ;
               lgraph->gOptions.bStatusBarChecked = goLocalCopy.bStatusBarChecked ;

               if (lgraph->gOptions.eTimeInterval != goLocalCopy.eTimeInterval
                  && !LocalManualRefresh)
                  {
                  iTimeMilliseconds = (INT) (goLocalCopy.eTimeInterval * (FLOAT) 1000.0) ;
                  pGraphs->gInterval = iTimeMilliseconds ;
                  if (!PlayingBackLog())
                     {
                     SetGraphTimer(pGraphs) ;
                     }
                  lgraph->bManualRefresh = LocalManualRefresh ;

                  }
               else if (LocalManualRefresh != lgraph->bManualRefresh)
                  {
                  ToggleGraphRefresh (hWndGraph) ;
                  }
               // Assign the local copy of the graph options to the
               // global copy.

               lgraph->gOptions = goLocalCopy ;

               SizeGraphComponents (hWndGraph) ;
               WindowInvalidate (hWndGraph) ;
               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, 1) ;
               return (TRUE) ;
               break ;

            case IDCANCEL:
               dwCurrentDlgID = 0 ;
               EndDialog(hDlg,0);
               return(TRUE);

            case ID_HELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            default:
               break;
            }
         break;

      default:
         break;

      }
   return(FALSE);
   }

DWORD          iIntervalMSecs ;

void static OnAlertOptionDlgInit (HWND hDlg, PALERT pAlert)
   {
   BOOL           EnableFlag ;

   iIntervalMSecs = pAlert->iIntervalMSecs ;

   LocalManualRefresh = pAlert->bManualRefresh ;

   if (PlayingBackLog())
      {
      // When playingback log, disable all controls except
      // the alert interval
      //
      EnableFlag = TRUE ;
      DialogEnable (hDlg, IDD_ALERTOPTIONSMANUALREFRESH, FALSE) ;
      DialogEnable (hDlg, IDD_ALERTOPTIONSPOPUP, FALSE) ;
      DialogEnable (hDlg, IDD_ALERTOPTIONSNETWORKALERT, FALSE) ;
      DialogEnable (hDlg, IDD_ALERTOPTIONSNETGROUPTEXT, FALSE) ;
      DialogEnable (hDlg, IDD_ALERTOPTIONSMSGNAME, FALSE) ;
      DialogEnable (hDlg, IDD_ALERTOPTIONSMSGNAMETEXT, FALSE) ;
      }
   else
      {
      EnableFlag = !LocalManualRefresh ;
      DialogEnable (hDlg, IDD_ALERTOPTIONSMSGNAME, pAlert->bNetworkAlert) ;
      DialogEnable (hDlg, IDD_ALERTOPTIONSMSGNAMETEXT, pAlert->bNetworkAlert) ;
      }

   // setup the Update time interval group items
   DialogSetInterval (hDlg, IDD_ALERTOPTIONSINTERVAL, iIntervalMSecs) ;
   DialogEnable (hDlg, IDD_ALERTOPTIONSINTERVAL, EnableFlag) ;
   DialogEnable (hDlg, IDD_ALERTOPTIONSINTERVALTEXT, EnableFlag) ;
   CheckRadioButton(hDlg, IDD_ALERTOPTIONSMANUALREFRESH,
      IDD_ALERTOPTIONSPERIODIC,
      !EnableFlag ? IDD_ALERTOPTIONSMANUALREFRESH : IDD_ALERTOPTIONSPERIODIC) ;

   CheckDlgButton (hDlg, IDD_ALERTOPTIONSEVENTLOG, pAlert->bEventLog) ;
   CheckDlgButton (hDlg, IDD_ALERTOPTIONSPOPUP, pAlert->bSwitchToAlert) ;
   CheckDlgButton (hDlg, IDD_ALERTOPTIONSNETWORKALERT, pAlert->bNetworkAlert) ;

   if (pAlert->MessageName[0])
      {
      DialogSetString (hDlg, IDD_ALERTOPTIONSMSGNAME, pAlert->MessageName) ;
      }

   EditSetLimit (GetDlgItem(hDlg, IDD_ALERTOPTIONSMSGNAME),
      sizeof(pAlert->MessageName)/sizeof(TCHAR) - 1) ;

   dwCurrentDlgID = HC_PM_idDlgOptionAlert ;

   WindowCenter (hDlg) ;

   }  // OnAlertOptionDlgInit

BOOL FAR PASCAL AlertOptionDlg (HWND hDlg, WORD msg,
                                DWORD wParam, LONG lParam)
   {

   PALERT         pAlert ;

   switch(msg)
      {

      case WM_INITDIALOG:
         pAlert = (PALERT) lParam ;
         OnAlertOptionDlgInit (hDlg, pAlert) ;
         return(TRUE);

   case WM_COMMAND:
         switch(LOWORD(wParam))
            {
            case IDOK:
               {
               FLOAT eTimeInterval ;

               pAlert = AlertData (hWndAlert) ;

               eTimeInterval = DialogFloat (hDlg, IDD_ALERTOPTIONSINTERVAL, NULL) ;

               if (eTimeInterval > MAX_INTERVALSEC ||
                   eTimeInterval < MIN_INTERVALSEC)
                  {
                  DlgErrorBox (hDlg, ERR_BADTIMEINTERVAL) ;
                  SetFocus (DialogControl (hDlg, IDD_ALERTOPTIONSINTERVAL)) ;
                  EditSetTextEndPos (hDlg, IDD_ALERTOPTIONSINTERVAL) ;
                  return (FALSE) ;
                  break ;
                  }

               eTimeInterval = eTimeInterval * (FLOAT) 1000.0 +
                   (FLOAT) 0.5 ;

               iIntervalMSecs = (DWORD) eTimeInterval ;

               pAlert->bNetworkAlert =
                  IsDlgButtonChecked (hDlg, IDD_ALERTOPTIONSNETWORKALERT) ;

               pAlert->bSwitchToAlert =
                  IsDlgButtonChecked (hDlg, IDD_ALERTOPTIONSPOPUP) ;

               pAlert->bEventLog =
                  IsDlgButtonChecked (hDlg, IDD_ALERTOPTIONSEVENTLOG) ;

               if (!PlayingBackLog())
                  {
                  DialogText (hDlg,
                     IDD_ALERTOPTIONSMSGNAME,
                     pAlert->MessageName) ;
                  }

               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, 1) ;
               }
               break ;

            case IDCANCEL:
               dwCurrentDlgID = 0 ;
               EndDialog(hDlg,0);
               return(TRUE);

            case IDD_ALERTOPTIONSPERIODIC:
            case IDD_ALERTOPTIONSMANUALREFRESH:

               if (!PlayingBackLog())
                  {
                  // check if the Manual refresh is currently checked.
                  // Then toggle the ManualRefresh button
                  LocalManualRefresh =
                     (LOWORD(wParam) == IDD_ALERTOPTIONSMANUALREFRESH) ;

                  CheckRadioButton(hDlg,
                     IDD_ALERTOPTIONSMANUALREFRESH,
                     IDD_ALERTOPTIONSPERIODIC,
                     LocalManualRefresh ? IDD_ALERTOPTIONSMANUALREFRESH :
                     IDD_ALERTOPTIONSPERIODIC) ;

                     DialogEnable (hDlg,
                        IDD_ALERTOPTIONSINTERVAL,
                        !LocalManualRefresh) ;

                     DialogEnable (hDlg,
                        IDD_ALERTOPTIONSINTERVALTEXT,
                        !LocalManualRefresh) ;
                  }

               break ;
#if 0
            case IDD_ALERTOPTIONSPOPUP:
               bSwitchToAlert = !bSwitchToAlert ;
               CheckDlgButton (hDlg, IDD_ALERTOPTIONSPOPUP, bSwitchToAlert) ;
               break;
#endif

            case IDD_ALERTOPTIONSNETWORKALERT:
               {
               BOOL  bNetworkAlert ;

               bNetworkAlert =
                  IsDlgButtonChecked (hDlg, IDD_ALERTOPTIONSNETWORKALERT) ;

               DialogEnable (hDlg, IDD_ALERTOPTIONSMSGNAME, bNetworkAlert) ;
               DialogEnable (hDlg, IDD_ALERTOPTIONSMSGNAMETEXT, bNetworkAlert) ;

               if (bNetworkAlert)
                  {
                  SetFocus (GetDlgItem(hDlg, IDD_ALERTOPTIONSMSGNAME)) ;
                  SendDlgItemMessage(hDlg, IDD_ALERTOPTIONSMSGNAME,
                     EM_SETSEL, 0, 16) ;
                  }
               }
               break;

            case IDD_DISPLAYHELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            default:
               break;
            }
         break;

   default:
      break;

      }

   return (FALSE);
   }  // AlertOptionDlg

BOOL DisplayAlertOptions (HWND hWndParent,
                           HWND hWndAlert)
   {  // DisplayAlertOptions
   PALERT        pAlert ;

   pAlert = AlertData (hWndParent) ;

   if (DialogBoxParam (hInstance, idDlgAlertOptions,
                       hWndParent, (DLGPROC) AlertOptionDlg,
                       (LPARAM) pAlert))
      {  // if

      if (pAlert->bNetworkAlert && pAlert->hNetAlertThread == 0)
         {
         AlertCreateThread (pAlert) ;
         }

      if (PlayingBackLog())
         {
         if (pAlert->iIntervalMSecs != iIntervalMSecs)
            {
            // a new time interval, re-do the backing back log
            // using the new value...
            pAlert->iIntervalMSecs = iIntervalMSecs ;
            DialogSetInterval (hWndAlert, IDD_ALERTINTERVAL, iIntervalMSecs) ;
            if (pAlert->pLineFirst)
               {
               PlaybackAlert (hWndAlert, 0) ;
               WindowInvalidate (hWndAlert) ;
               }
            }
         }
      else if (LocalManualRefresh != pAlert->bManualRefresh)
         {
         if (!LocalManualRefresh)
            {
            pAlert->iIntervalMSecs = iIntervalMSecs ;
            DialogSetInterval (hWndAlert, IDD_ALERTINTERVAL, iIntervalMSecs) ;
            }
         ToggleAlertRefresh (hWndAlert) ;
         }
      else if (!LocalManualRefresh)
         {
         pAlert->iIntervalMSecs = iIntervalMSecs ;
         SetAlertTimer (pAlert) ;
         DialogSetInterval (hWndAlert, IDD_ALERTINTERVAL, iIntervalMSecs) ;
         }
      }  // if

   return (TRUE) ;
   }  // DisplayAlertOptions


DIALOGS.H

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL FAR PASCAL GraphOptionDlg(HWND hDlg, WORD msg, DWORD wParam, LONG lParam);

BOOL FAR PASCAL BookmarkDlg(HWND hDlg, WORD msg, DWORD wParam, LONG lParam);

BOOL FAR PASCAL AboutDlg(HWND hDlg, WORD msg, DWORD wParam, LONG lParam) ;

BOOL DisplayAlertOptions (HWND hWndParent,
                          HWND hWndAlert) ;


DISPOPTN.H

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL DisplayDisplayOptions (HWND hWndParent) ;


DLGIDS.H

#define IDD_OK             IDOK
#define IDD_CANCEL         IDCANCEL

/*BUTTON ID'S                */

#define ID_ADD    10
#define ID_START    11
#define ID_STOP     12

#define ID_COMPUTER 14
#define ID_OBJECT    15
#define ID_INSTANCE 16
#define ID_COUNTER 17
#define ID_FILE     21

#define ID_TIME     26

#define ID_MAX_ENTRIES 32

#define ID_THRESHOLD 35
#define ID_FOREGR    36
#define ID_BACKGR    37
#define ID_TIME_LINE 38

#define ID_POPUP    40
#define ID_ELLIPSIS 42
#define ID_TIME_SCROLL 43
#define ID_BOOKMARK_IN 45

#define ID_TIME_IN 49
#define ID_FILE_IN 50
#define ID_MAX_IN    51
#define ID_NUM_IN    52

#define ID_VERTICAL 54
#define ID_CCOLOR    55
#define ID_ENTER_SCALE 56
#define ID_ENTER_VERT 57
#define ID_ENTER_INTR 58
#define ID_LOGTIME       62

#define ID_HELP          112
#define ID_LEGEND        201
#define ID_MENU          202
#define ID_LABELS        203
#define ID_VERT_GRID     204
#define ID_HORZ_GRID     205
#define ID_VERT_MAX      208
#define ID_GRAPH         211
#define ID_HISTOGRAM     212
#define ID_COMPUTER_EDIT_FIELD      301

#define ID_INTERVAL                 402

//=============================//
// Log Display Dialog          //
//=============================//

#define IDD_LOGFILETEXT          3001
#define IDD_LOGFILE              3002
#define IDD_LOGSTATUSTEXT        3003
#define IDD_LOGSTATUS            3004
#define IDD_LOGINTERVALTEXT      3005
#define IDD_LOGINTERVAL          3006
#define IDD_LOGSIZETEXT          3007
#define IDD_LOGSIZE              3008
#define IDD_LOGENTRIESTEXT       3009
#define IDD_LOGENTRIES           3010

//=============================//
// Log Options Dialog          //
//=============================//

#define IDD_LOGOPTSTART             3101
#define IDD_LOGOPTPAUSE             3102
#define IDD_LOGOPTINTERVALTEXT      3103
#define IDD_LOGOPTINTERVAL          3104
#define IDD_LOGOPTIONSMANUALREFRESH 3105
#define IDD_LOGOPTIONSPERIODIC      3106

//=============================//
// Add Log Dialog              //
//=============================//

#define IDD_ADDLOGCOMPUTER       3201
#define IDD_ADDLOGOBJECTTEXT     3202
#define IDD_ADDLOGOBJECT         3203
#define IDD_ADDLOGELLIPSES       3204
#define IDD_ADDLOGADD            3205
#define IDD_ADDLOGDONE           3206
#define IDD_ADDLOGHELP           3207

//=============================//
// Timeframe Dialog            //
//=============================//

#define IDD_TIMEFRAMETIMELINE    3301
#define IDD_TIMEFRAMEHELP        3302
#define IDD_TIMEFRAMESTOPTIME    3303
#define IDD_TIMEFRAMESTOPDATE    3304
#define IDD_TIMEFRAMESTARTDATE   3305
#define IDD_TIMEFRAMESTARTTIME   3306
#define IDD_TIMEFRAMESETSTART    3307
#define IDD_TIMEFRAMESTOP        3308
#define IDD_TIMEFRAMEBOOKMARKS   3309
#define IDD_TIMEFRAMESETSTOP     3310
#define IDD_TIMEFRAMEBOOKMARKGRP 3311

//=============================//
// Alert Display Dialog        //
//=============================//

#define IDD_ALERTLOGTEXT         3401
#define IDD_ALERTLOG             3402
#define IDD_ALERTLEGENDTEXT      3403
#define IDD_ALERTLEGEND          3404
#define IDD_ALERTINTERVALTEXT    3405
#define IDD_ALERTINTERVAL        3406

//=============================//
// Add Line Dialog             //
//=============================//

#define IDD_ADDLINECOMPUTERTEXT        3501
#define IDD_ADDLINECOMPUTER            3502
#define IDD_ADDLINEELLIPSES            3503
#define IDD_ADDLINEOBJECTTEXT          3504
#define IDD_ADDLINEOBJECT              3505
#define IDD_ADDLINECOUNTERTEXT         3506
#define IDD_ADDLINECOUNTER             3507
#define IDD_ADDLINEINSTANCETEXT        3508
#define IDD_ADDLINEINSTANCE            3509

#define IDD_ADDLINECOLORTEXT           3510
#define IDD_ADDLINECOLOR               3511
#define IDD_ADDLINESCALETEXT           3512
#define IDD_ADDLINESCALE               3513
#define IDD_ADDLINEWIDTHTEXT           3514
#define IDD_ADDLINEWIDTH               3515
#define IDD_ADDLINESTYLETEXT           3516
#define IDD_ADDLINESTYLE               3517

#define IDD_ADDLINEADD                 3521
#define IDD_ADDLINEHELP                3523

#define IDD_ADDLINEIFGROUP             3531
#define IDD_ADDLINEIFOVER              3532
#define IDD_ADDLINEIFUNDER             3533
#define IDD_ADDLINEIFVALUE             3534

#define IDD_ADDLINEPROGRAMGROUP        3535
#define IDD_ADDLINEPROGRAM             3536
#define IDD_ADDLINEPROGRAMFIRSTTIME    3537
#define IDD_ADDLINEPROGRAMEVERYTIME    3538

#define IDD_ADDLINEEXPANDEXPLAIN       3541
#define IDD_ADDLINEEXPLAINGROUP        3542
#define IDD_ADDLINEEXPLAIN             3543

//=============================//
// Report Options Dialog       //
//=============================//

#define IDD_REPORTOPTIONSINTERVALTEXT  3601
#define IDD_REPORTOPTIONSINTERVAL      3602
#define IDD_REPORTOPTIONSMANUALREFRESH 3603
#define IDD_REPORTOPTIONSPERIODIC      3604

//=============================//
// Display Options Dialog      //
//=============================//

#define IDD_DISPLAYMENU                3701
#define IDD_DISPLAYTOOLBAR             3702
#define IDD_DISPLAYSTATUS              3703
#define IDD_DISPLAYONTOP               3704
#define IDD_DISPLAYHELP                3705

//=============================//
// Data Source Dialog          //
//=============================//

#define IDD_DATASOURCENOW              3801
#define IDD_DATASOURCEFILE             3802
#define IDD_DATASOURCECHANGEFILE       3803
#define IDD_DATASOURCEHELP             3804
#define IDD_DATASOURCEFILENAME         3805

//=============================//
// Alert Options Dialog        //
//=============================//

#define IDD_ALERTOPTIONSINTERVAL       3901
#define IDD_ALERTOPTIONSBEEP           3902
#define IDD_ALERTOPTIONSNUMBEEPS       3903
#define IDD_ALERTOPTIONSPOPUP          3904
#define IDD_ALERTOPTIONSEVENTLOG       3905
#define IDD_ALERTOPTIONSNETWORKALERT   3906
#define IDD_ALERTOPTIONSMSGNAME        3907
#define IDD_ALERTOPTIONSMSGNAMETEXT    3908
#define IDD_ALERTOPTIONSMANUALREFRESH  3909
#define IDD_ALERTOPTIONSPERIODIC       3911
#define IDD_ALERTOPTIONSINTERVALTEXT   3912
#define IDD_ALERTOPTIONSNETGROUPTEXT   3913

//=============================//
// Chart Options Dialog        //
//=============================//

#define IDD_CHARTOPTIONSINTERVAL       4001
#define IDD_CHARTOPTIONSINTERVALTEXT   4002
#define IDD_CHARTOPTIONSVALUEBAR       4003
#define IDD_CHARTOPTIONSMANUALREFRESH  4004
#define IDD_CHARTOPTIONSPERIODIC       4005
#define IDD_CHARTOPTIONSUPDATETEXT     4006

//=============================//
// Bookmark Options Dialog     //
//=============================//

#define IDD_BOOKMARKCOMMENT            4101
#define IDD_BOOKMARKLIST               4102
#define IDD_BOOKMARKHELP               4103

//=========================//
// Export Options Dialog   //
//=========================//

#define IDD_EXPORTCOMMAS               4201
#define IDD_EXPORTTAB                  4202
#define IDD_EXPORTDELIMITERTEXT        4203
#define IDD_EXPORTHELP                 4204

//==========================//
//   Generic Menu items     //
// (used by ACCELERATORS)   //
//==========================//
#define IDM_FILEOPENFILE               4701
#define IDM_FILESAVEFILE               4702
#define IDM_FILESAVEASFILE             4703
#define IDM_CHARTHIGHLIGHTON           4704

//=============//
// Toolbar ID  //
//=============//
#define IDM_TOOLBARID                  4801
#define WM_F1DOWN                      WM_USER+100

//===============================//
// Select Computer from log file //
//===============================//
#define IDD_CHOOSECOMPUTERTEXT         4901
#define IDD_CHOOSECOMPUTERTEXT1        4902
#define IDD_CHOOSECOMPUTERNAME         4903
#define IDD_CHOOSECOMPUTERLISTBOX      4904


EXPORT.C

#include "perfmon.h"
#include <lmcons.h>
#include <lmerr.h>
#include <lmapibuf.h>
#include <lmwksta.h>
// #include <uiexport.h>
#include <stdio.h>         // for sprintf
#include "utils.h"

#include "perfmops.h"      // for SystemTimeDateString
#include "fileopen.h"      // for FileGetName
#include "fileutil.h"      // for FileRead etc
#include "playback.h"      // for PlayingBackLog & LogPositionSystemTime
#include "dlgs.h"          // common dialog control IDs
#include "pmhelpid.h"      // Help IDs

// This routine opens the export file and put in the header info.
// It is used by ExportChart, ExportAlert, & ExportReport.
INT  ExportFileOpen (HWND hWnd, HANDLE *phFile, int IntervalMSecs, LPTSTR *ppFileName)
{
   CHAR           TempBuff [LongTextLen * 2] ;

   TCHAR          UnicodeBuff [LongTextLen] ;
   TCHAR          UnicodeBuff1 [MiscTextLen] ;
   SYSTEMTIME     SystemTime ;
   int            StringLen ;
   INT            ErrCode = 0 ;
   FLOAT          eIntervalSecs ;

   // defined and setup in status.c
   extern TCHAR   szCurrentActivity [] ;
   extern TCHAR   szStatusFormat [] ;

   *phFile = 0 ;

   if (!FileGetName (hWnd, IDS_EXPORTFILE, UnicodeBuff))
      {
      // user cancel
      goto Exit0 ;
      }

   *ppFileName = StringAllocate (UnicodeBuff) ;

   // open the file..
   if (!(*phFile = FileHandleCreate (UnicodeBuff)))
      {
      // can't open the file
      ErrCode = ERR_CANT_OPEN ;
      goto Exit0 ;
      }

   // export header
   StringLoad (IDS_REPORT_HEADER, UnicodeBuff) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   StringLen = strlen (TempBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], LocalComputerName) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (*phFile, TempBuff, strlen(TempBuff)))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   // export today's date time
   GetLocalTime (&SystemTime) ;

   StringLoad (IDS_EXPORT_DATE, UnicodeBuff) ;
   StringLen = lstrlen (UnicodeBuff) ;
   UnicodeBuff[StringLen] = TEXT(':') ;
   UnicodeBuff[StringLen+1] = TEXT(' ') ;
   SystemTimeDateString (&SystemTime, &UnicodeBuff[StringLen+2]) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (*phFile, TempBuff, strlen(TempBuff)))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   StringLoad (IDS_EXPORT_TIME, UnicodeBuff) ;
   StringLen = lstrlen (UnicodeBuff) ;
   UnicodeBuff[StringLen] = TEXT(':') ;
   UnicodeBuff[StringLen+1] = TEXT(' ') ;
   SystemTimeTimeString (&SystemTime, &UnicodeBuff[StringLen+2], FALSE) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (*phFile, TempBuff, strlen(TempBuff)))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   // export data source
   TSPRINTF (UnicodeBuff, szStatusFormat,
      PlayingBackLog () ?
      PlaybackLog.szFileTitle : szCurrentActivity) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (*phFile, TempBuff, strlen(TempBuff)))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   if (!PlayingBackLog())
      {

      eIntervalSecs = (FLOAT)IntervalMSecs / (FLOAT) 1000.0 ;
      StringLoad (IDS_CHARTINT_FORMAT, UnicodeBuff1) ;
      TSPRINTF (UnicodeBuff, UnicodeBuff1, eIntervalSecs) ;
      ConvertDecimalPoint (UnicodeBuff) ;
      ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
      strcat (TempBuff, LineEndStr) ;

      if (!FileWrite (*phFile, TempBuff, strlen(TempBuff)))
         {

         ErrCode = ERR_EXPORT_FILE ;
         goto Exit0 ;
         }
      }
   else
      {
      // export the log start and stop date/time
      StringLoad (IDS_START_TEXT, UnicodeBuff) ;
      StringLen = lstrlen (UnicodeBuff) ;
      LogPositionSystemTime (&(PlaybackLog.StartIndexPos), &SystemTime) ;
      SystemTimeDateString (&SystemTime, &UnicodeBuff[StringLen]) ;
      StringLen = lstrlen (UnicodeBuff) ;
      UnicodeBuff[StringLen] = TEXT(' ') ;
      StringLen++ ;
      SystemTimeTimeString (&SystemTime, &UnicodeBuff[StringLen], FALSE) ;
      ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
      strcat (TempBuff, LineEndStr) ;

      if (!FileWrite (*phFile, TempBuff, strlen(TempBuff)))
         {

         ErrCode = ERR_EXPORT_FILE ;
         goto Exit0 ;
         }

      StringLoad (IDS_STOP_TEXT, UnicodeBuff) ;
      StringLen = lstrlen (UnicodeBuff) ;
      LogPositionSystemTime (&(PlaybackLog.StopIndexPos), &SystemTime) ;
      SystemTimeDateString (&SystemTime, &UnicodeBuff[StringLen]) ;
      StringLen = lstrlen (UnicodeBuff) ;
      UnicodeBuff[StringLen] = TEXT(' ') ;
      StringLen++ ;
      SystemTimeTimeString (&SystemTime, &UnicodeBuff[StringLen], FALSE) ;
      ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
      strcat (TempBuff, LineEndStr) ;

      if (!FileWrite (*phFile, TempBuff, strlen(TempBuff)))
         {
         ErrCode = ERR_EXPORT_FILE ;
         goto Exit0 ;
         }

      if (hWnd == hWndAlert)
         {
         eIntervalSecs = (FLOAT)IntervalMSecs / (FLOAT) 1000.0 ;
         StringLoad (IDS_CHARTINT_FORMAT, UnicodeBuff1) ;
         TSPRINTF (UnicodeBuff, UnicodeBuff1, eIntervalSecs) ;
         ConvertDecimalPoint (UnicodeBuff) ;
         ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
         strcat (TempBuff, LineEndStr) ;

         if (!FileWrite (*phFile, TempBuff, strlen(TempBuff)))
            {
            ErrCode = ERR_EXPORT_FILE ;
            goto Exit0 ;
            }
         }

      }

   return (0) ;

Exit0:

   return (ErrCode) ;

}  // ExportFileOpen

BOOL APIENTRY ExportOptionsHookProc (HWND hDlg,
                                     UINT iMessage,
                                     WPARAM wParam,
                                     LPARAM lParam)

{
   BOOL           bHandled ;

   bHandled = TRUE ;
   switch (iMessage)
      {
      case WM_INITDIALOG:
         CheckRadioButton (hDlg, IDD_EXPORTCOMMAS, IDD_EXPORTTAB,
            pDelimiter == TabStr ? IDD_EXPORTTAB : IDD_EXPORTCOMMAS ) ;

         WindowCenter (hDlg) ;
         break ;

      case WM_COMMAND:
         switch (LOWORD(wParam))
            {  // switch
            case IDD_EXPORTCOMMAS:
            case IDD_EXPORTTAB:
               // toggle between the 2 radio buttons..
               CheckRadioButton (hDlg, IDD_EXPORTCOMMAS, IDD_EXPORTTAB,
                   LOWORD(wParam) == IDD_EXPORTTAB ?
                     IDD_EXPORTTAB : IDD_EXPORTCOMMAS ) ;
               break ;

            case IDD_OK:
               pDelimiter = IsDlgButtonChecked (hDlg, IDD_EXPORTCOMMAS) ?
                  CommasStr : TabStr ;
               bHandled = FALSE ;

               break ;

            case IDD_EXPORTHELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            case cmb1:
               if (HIWORD (wParam) == CBN_SELCHANGE)
                  {
                  int   iFileIndex ;
                  HWND  hWndCBox = (HWND) lParam ;

                  // a diff. selection from the file type, change
                  // the delimiter accordingly.
                  iFileIndex = CBSelection (hWndCBox) ;
                  CheckRadioButton (hDlg, IDD_EXPORTCOMMAS, IDD_EXPORTTAB,
                     iFileIndex == 0 ?
                     IDD_EXPORTTAB : IDD_EXPORTCOMMAS ) ;

                  }
               else
                  {
                  bHandled = FALSE ;
                  }
               break ;

            default:
               bHandled = FALSE ;
            }  // switch
         break;

      default:
         bHandled = FALSE ;
         break;
      }  // switch

   return (bHandled) ;

}  // ExportOptionsHookProc


FILEOPEN.C

#include "perfmon.h"
#include "cderr.h"

#include "alert.h"      // for OpenAlert
#include "fileutil.h"   // for FileOpen, FileRead
#include "grafdata.h"   // for OpenChart
#include "log.h"        // for OpenLog
#include "report.h"     // for OpenReport
#include "utils.h"      // for strempty
#include "perfmops.h"   // for OpenWorkspace
#include "pmhelpid.h"   // Help IDs
#include <dlgs.h>       // for pshHelp

#define OptionsOFNStyle \
   (OFN_HIDEREADONLY | OFN_SHOWHELP | OFN_OVERWRITEPROMPT | OFN_ENABLEHOOK)

#define ExportOptionsOFNStyle                   \
   (OFN_ENABLETEMPLATE | OFN_HIDEREADONLY |     \
    OFN_SHOWHELP | OFN_ENABLEHOOK | OFN_OVERWRITEPROMPT)

extern BOOL APIENTRY ExportOptionsHookProc (HWND hDlg, UINT iMessage,
                                            WPARAM wParam, LPARAM lParam) ;

BOOL APIENTRY FileOpenHookProc (HWND hDlg,
                                UINT iMessage,
                                WPARAM wParam,
                                LPARAM lParam)
{
   BOOL     bHandled = FALSE ;

   // only intercept the Help button and bring up our WinHelp data
   if (iMessage == WM_COMMAND && wParam == pshHelp)
      {
      CallWinHelp (dwCurrentDlgID) ;
      bHandled = TRUE ;
      }
   else if (iMessage == WM_INITDIALOG)
      {
      WindowCenter (hDlg) ;
      bHandled = TRUE ;
      }

   return (bHandled) ;
}

BOOL FileOpen (HWND hWndParent, int nStringResourceID, LPTSTR lpInputFileName)
   {
   OPENFILENAME   ofn ;
   TCHAR          szFileSpec [FilePathLen] ;
   TCHAR          szFileTitle [FilePathLen] ;
   TCHAR          szDialogTitle [FilePathLen] ;
   HANDLE         hFile ;
   PERFFILEHEADER FileHeader ;

   TCHAR          aszOpenFilter[LongTextLen] ;
   int            StringLength ;
   BOOL           retCode ;
   LPTSTR         pFileName = NULL ;

   if (strempty(lpInputFileName))
      {

      dwCurrentDlgID = HC_PM_idDlgFileOpen ;

      // get the file extension strings
      LoadString (hInstance, nStringResourceID, aszOpenFilter,
         sizeof(aszOpenFilter) / sizeof(TCHAR)) ;
      StringLength = lstrlen (aszOpenFilter) + 1 ;
      LoadString (hInstance, nStringResourceID+1,
         &aszOpenFilter[StringLength],
         sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
      StringLength += lstrlen (&aszOpenFilter[StringLength]) + 1 ;

#ifdef ADVANCED_PERFMON
      // get workspace file extension strings
      LoadString (hInstance, IDS_WORKSPACEFILE,
         &aszOpenFilter[StringLength],
         sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
      StringLength += lstrlen (&aszOpenFilter[StringLength]) + 1 ;
      LoadString (hInstance, IDS_WORKSPACEFILEEXT,
         &aszOpenFilter[StringLength],
         sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
      StringLength += lstrlen (&aszOpenFilter[StringLength]) + 1;
#endif

      // get all file extension strings
      LoadString (hInstance, IDS_ALLFILES,
         &aszOpenFilter[StringLength],
         sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
      StringLength += lstrlen (&aszOpenFilter[StringLength]) + 1 ;
      LoadString (hInstance, IDS_ALLFILESEXT,
         &aszOpenFilter[StringLength],
         sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
      StringLength += lstrlen (&aszOpenFilter[StringLength]) ;

      // setup the end strings
      aszOpenFilter[StringLength+1] = aszOpenFilter[StringLength+2] = TEXT('\0') ;

      strclr (szFileSpec) ;
      strclr (szFileTitle) ;

      StringLoad (IDS_FILEOPEN_TITLE, szDialogTitle) ;
      memset (&ofn, 0, sizeof(OPENFILENAME)) ;
      ofn.lStructSize = sizeof(OPENFILENAME) ;
      ofn.hwndOwner = hWndParent ;
      ofn.hInstance = hInstance;
      ofn.lpstrTitle = szDialogTitle ;
      ofn.lpstrFilter = aszOpenFilter ;
      ofn.nFilterIndex = 1L ;

      ofn.lpstrFile = szFileSpec;
      ofn.nMaxFile = sizeof(szFileSpec);
      ofn.lpstrFileTitle = szFileTitle;
      ofn.nMaxFileTitle = sizeof(szFileTitle);
      ofn.Flags = OFN_HIDEREADONLY | OFN_SHOWHELP | OFN_FILEMUSTEXIST | OFN_ENABLEHOOK ;
      ofn.lpfnHook = (LPOFNHOOKPROC) FileOpenHookProc ;

      if (!GetOpenFileName(&ofn))
         {
         dwCurrentDlgID = 0 ;
         return (FALSE) ;
         }

      dwCurrentDlgID = 0 ;

      hFile = FileHandleOpen (szFileSpec) ;
      pFileName = szFileSpec ;

      }  // NULL lpFileName

   else
      {
      // open the input file
      hFile = FileHandleOpen (lpInputFileName) ;
      if (hFile && hFile != INVALID_HANDLE_VALUE &&
          SearchPath (NULL, lpInputFileName, NULL,
            sizeof(szFileSpec)/sizeof(TCHAR) - 1,
            szFileSpec, &pFileName))
         {
         pFileName = szFileSpec ;
         }
      else
         {
         pFileName = NULL ;
         }
      }

   if (!hFile || hFile == INVALID_HANDLE_VALUE)
      {
      return (FALSE) ;
      }

   if (!FileRead (hFile, &FileHeader, sizeof (FileHeader)))
      {
      CloseHandle (hFile) ;
      if (strempty(lpInputFileName))
         {
         DlgErrorBox (hWndParent, ERR_BAD_SETTING_FILE, pFileName) ;
         }
      return (FALSE) ;
      }

   //=============================//
   // Chart File?                 //
   //=============================//

   if (strsame (FileHeader.szSignature, szPerfChartSignature))
      {
      retCode = OpenChart (hWndGraph,
                           hFile,
                           FileHeader.dwMajorVersion,
                           FileHeader.dwMinorVersion,
                           TRUE) ;
      if (retCode)
         {
         ChangeSaveFileName (pFileName, IDM_VIEWCHART) ;
         }
      else
         {
         goto ErrExit ;
         }
      return (retCode) ;
      }

#ifdef ADVANCED_PERFMON
   //=============================//
   // Alert File?                 //
   //=============================//

   if (strsame (FileHeader.szSignature, szPerfAlertSignature))
      {
      retCode = OpenAlert (hWndAlert,
                           hFile,
                           FileHeader.dwMajorVersion,
                           FileHeader.dwMinorVersion,
                           TRUE) ;
      if (retCode)
         {
         ChangeSaveFileName (pFileName, IDM_VIEWALERT) ;
         }
      else
         {
         goto ErrExit ;
         }

      return (retCode) ;
      }

   //=============================//
   // Log File?                   //
   //=============================//

   if (strsame (FileHeader.szSignature, szPerfLogSignature))
      {
      retCode = OpenLog (hWndLog,
                         hFile,
                         FileHeader.dwMajorVersion,
                         FileHeader.dwMinorVersion,
                         TRUE) ;
      if (retCode)
         {
         ChangeSaveFileName (pFileName, IDM_VIEWLOG) ;
         }
      else
         {
         goto ErrExit ;
         }

      return (retCode) ;
      }

   //=============================//
   // Report File?                //
   //=============================//

   if (strsame (FileHeader.szSignature, szPerfReportSignature))
      {
      retCode = OpenReport (hWndReport,
                            hFile,
                            FileHeader.dwMajorVersion,
                            FileHeader.dwMinorVersion,
                            TRUE) ;
      if (retCode)
         {
         ChangeSaveFileName (pFileName, IDM_VIEWREPORT) ;
         }
      else
         {
         goto ErrExit ;
         }

      return (retCode) ;
      }

   //=============================//
   // Workspace File?             //
   //=============================//

   if (strsame (FileHeader.szSignature, szPerfWorkspaceSignature))
      {
      retCode = OpenWorkspace (hFile,
                               FileHeader.dwMajorVersion,
                               FileHeader.dwMinorVersion) ;
      if (retCode)
         {
         ChangeSaveFileName (pFileName, IDM_WORKSPACE) ;
         return (TRUE) ;
         }
      else
         {
         goto ErrExit ;
         }
      }
#endif

   //=============================//
   // Unknown file type           //
   //=============================//
   CloseHandle (hFile) ;

ErrExit:

   DlgErrorBox (hWndParent, ERR_BAD_SETTING_FILE, pFileName) ;
   return (FALSE) ;
   }  // FileOpen

BOOL FileGetName (HWND hWndParent, int nStringResourceID, LPTSTR lpFileName)
   {
   OPENFILENAME   ofn ;
   TCHAR          szFileSpec [FilePathLen] ;
   TCHAR          szFileTitle [FilePathLen] ;
   TCHAR          szDialogTitle [FilePathLen] ;
   TCHAR          aszOpenFilter[LongTextLen] ;
   int            StringLength ;

   if (lpFileName)
      {

      if (nStringResourceID != IDS_EXPORTFILE)
         {
         // get the file extension strings
         LoadString (hInstance, nStringResourceID,
            aszOpenFilter,
            sizeof(aszOpenFilter) / sizeof(TCHAR) ) ;
         StringLength = lstrlen (aszOpenFilter) + 1 ;
         LoadString (hInstance, nStringResourceID+1,
            &aszOpenFilter[StringLength],
            sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;

         StringLength += lstrlen (&aszOpenFilter[StringLength]) + 1 ;
         // get all file extension strings
         LoadString (hInstance, IDS_ALLFILES,
            &aszOpenFilter[StringLength],
            sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
         StringLength += lstrlen (&aszOpenFilter[StringLength]) + 1 ;
         LoadString (hInstance, IDS_ALLFILESEXT,
            &aszOpenFilter[StringLength],
            sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
         }
      else
         {
         // get the Export file extension based on the current delimiter
         int   FirstExtensionID, SecondExtensionID ;

         if (pDelimiter == TabStr)
            {
            FirstExtensionID = IDS_EXPORTFILETSV ;
            SecondExtensionID = IDS_EXPORTFILE ;
            }
         else
            {
            FirstExtensionID = IDS_EXPORTFILE ;
            SecondExtensionID = IDS_EXPORTFILETSV ;
            }

         LoadString (hInstance, FirstExtensionID,
            aszOpenFilter,
            sizeof(aszOpenFilter) / sizeof(TCHAR) ) ;
         StringLength = lstrlen (aszOpenFilter) + 1 ;
         LoadString (hInstance, FirstExtensionID+1,
            &aszOpenFilter[StringLength],
            sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;

         StringLength += lstrlen (&aszOpenFilter[StringLength]) + 1 ;
         // get all file extension strings
         LoadString (hInstance, SecondExtensionID,
            &aszOpenFilter[StringLength],
            sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;
         StringLength += lstrlen (&aszOpenFilter[StringLength]) + 1 ;
         LoadString (hInstance, SecondExtensionID+1,
            &aszOpenFilter[StringLength],
            sizeof(aszOpenFilter) / sizeof(TCHAR) - StringLength) ;

         }

      // setup the end strings
      StringLength += lstrlen (&aszOpenFilter[StringLength]) ;
      aszOpenFilter[StringLength+1] = aszOpenFilter[StringLength+2] = TEXT('\0') ;

      strclr (szFileSpec) ;
      strclr (szFileTitle) ;

      if (nStringResourceID == IDS_EXPORTFILE)
         {
         dwCurrentDlgID = HC_PM_idDlgFileExport ;
         StringLoad (IDS_EXPORTAS_TITLE, szDialogTitle) ;
         }
      else if (nStringResourceID == IDS_WORKSPACEFILE)
         {
         dwCurrentDlgID = HC_PM_idDlgFileSaveWorkSpace ;
         StringLoad (IDS_SAVEASW_TITLE, szDialogTitle) ;
         }
      else
         {
         dwCurrentDlgID = HC_PM_idDlgFileSaveAs ;
         StringLoad (IDS_SAVEAS_TITLE, szDialogTitle) ;
         }

      memset (&ofn, 0, sizeof(OPENFILENAME)) ;
      ofn.lStructSize = sizeof(OPENFILENAME) ;
      ofn.hwndOwner = hWndParent ;
      ofn.hInstance = hInstance;
      ofn.lpstrTitle = szDialogTitle ;
      ofn.lpstrFilter = aszOpenFilter ;
      ofn.nFilterIndex = 1L ;

      ofn.lpstrFile = szFileSpec;
      ofn.nMaxFile = sizeof(szFileSpec);
      ofn.lpstrFileTitle = szFileTitle;
      ofn.nMaxFileTitle = sizeof(szFileTitle);

      if (nStringResourceID == IDS_EXPORTFILE)
         {
         ofn.Flags = ExportOptionsOFNStyle ;
         ofn.lpfnHook = (LPOFNHOOKPROC) ExportOptionsHookProc ;
         ofn.lpTemplateName = idDlgExportOptions ;
         }
      else
         {
         ofn.Flags = OptionsOFNStyle ;
         ofn.lpfnHook = (LPOFNHOOKPROC) FileOpenHookProc ;
         }

      if (!GetSaveFileName(&ofn))
         {
         dwCurrentDlgID = 0 ;
         return (FALSE) ;
         }
      dwCurrentDlgID = 0 ;
      }
   else
      {
      return (FALSE) ;
      }

   lstrcpy (lpFileName, ofn.lpstrFile) ;

   return (TRUE) ;
   } // FileGetName


FILEOPEN.H

BOOL FileOpen (HWND hWndParent, int nStringResourceID, LPTSTR lpInputFileName) ;
BOOL FileGetName (HWND hWndParent, int nStringResourceID, LPTSTR lpFileName) ;

BOOL APIENTRY FileOpenHookProc (HWND hDlg, UINT iMessage,
                                WPARAM wParam, LPARAM lParam) ;


FILESAVE.C

#include "perfmon.h"


FILEUTIL.C

//#include <string.h>
//#include <tchar.h>

#include <stdio.h>
#include "perfmon.h"
#include "fileutil.h"
#include "utils.h"

#include <string.h>     // for strncpy
#ifdef UNICODE
#define _tcsrchrwcsrchr
#else
#define _tcsrchrstrrchr
#endif

#define DRIVE_DELIMITER          TEXT(':')
#define DIRECTORY_DELIMITER      TEXT('\\')
#define EXTENSION_DELIMITER      TEXT('.')

#if 0
VOID FileErrorMessageBox(HWND hWnd, LPTSTR lpszFileName, DWORD ErrorCode)
   {
   TCHAR szErrorMessage[FILE_ERROR_MESSAGE_SIZE] ;
   TCHAR szErrorMessageTemplate[FILE_ERROR_MESSAGE_SIZE] ;

   StringLoad (IDS_FILE_ERROR, szErrorMessageTemplate) ;
   TSPRINTF((LPTSTR)szErrorMessage,
            (LPTSTR)szErrorMessageTemplate,
            lpszFileName,
            ErrorCode) ;

   MessageBox (hWnd, (LPTSTR)szErrorMessage, NULL,
              MB_OK | MB_ICONSTOP | MB_TASKMODAL);
   return ;
   }
#endif

BOOL FileRead (HANDLE hFile,
               LPMEMORY lpMemory,
               DWORD nAmtToRead)
   {  // FileRead
   BOOL           bSuccess ;
   DWORD          nAmtRead ;

   bSuccess = ReadFile (hFile, lpMemory, nAmtToRead, &nAmtRead, NULL) ;
   return (bSuccess && (nAmtRead == nAmtToRead)) ;
   }  // FileRead

BOOL FileWrite (HANDLE hFile,
                LPMEMORY lpMemory,
                DWORD nAmtToWrite)
   {  // FileWrite
   BOOL           bSuccess ;
   DWORD          nAmtWritten ;

   bSuccess = WriteFile (hFile, lpMemory, nAmtToWrite, &nAmtWritten, NULL) ;
   return (bSuccess && (nAmtWritten == nAmtToWrite)) ;
   }  // FileWrite

#if 0
HANDLE FileHandleOpen (LPTSTR lpszFilePath)
   {  // FileHandleOpen
   return ((HANDLE) CreateFile (lpszFilePath,
                                GENERIC_READ |
                                GENERIC_WRITE,
                                FILE_SHARE_READ |
                                FILE_SHARE_WRITE,
                                NULL,
                                OPEN_EXISTING,
                                0,
                                NULL)) ;
   }  // FileHandleOpen

HANDLE FileHandleCreate (LPTSTR lpszFilePath)
   {  // FileHandleCreate
   return ((HANDLE) CreateFile (lpszFilePath,
                               GENERIC_READ | GENERIC_WRITE,
                               FILE_SHARE_READ,
                               NULL,
                               CREATE_ALWAYS,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL)) ;
   }  // FileHandleCreate

long FileSeekEnd (HANDLE hFile,
                  long lAmtToMove)
   {  // FileSeekEnd
   return (SetFilePointer (hFile, lAmtToMove, NULL, FILE_END)) ;
   }  // FileSeekEnd

long FileSeekBegin (HANDLE hFile,
                    long lAmtToMove)
   {  // FileSeekBegin
   return (SetFilePointer (hFile, lAmtToMove, NULL, FILE_BEGIN)) ;
   }  // FileSeekBegin

long FileSeekCurrent (HANDLE hFile,
                      long lAmtToMove)
   {  // FileSeekCurrent
   return  (SetFilePointer (hFile, lAmtToMove, NULL, FILE_CURRENT)) ;
   }  // FileSeekCurrent

long FileTell (HANDLE hFile)
   {  // FileTell
   return (SetFilePointer (hFile, 0, NULL, FILE_CURRENT)) ;
   }  // FileTell
#endif

LPMEMORY FileMap (HANDLE hFile, HANDLE *phMapHandle)
/*
   To Do:         Error reporting!!
*/
   {  // FileMap
   HANDLE         hMapping ;

   *phMapHandle = 0 ;
   hMapping = CreateFileMapping (hFile, NULL, PAGE_READONLY,
                                 0, 0, NULL) ;
   if (!hMapping)
      return (NULL) ;

   *phMapHandle = hMapping ;
   return (MapViewOfFile (hMapping, FILE_MAP_READ, 0, 0, 0)) ;
   }  // FileMap

BOOL FileUnMap (LPVOID pBase, HANDLE hMapping)
/*
   To Do:         Error reporting!!
*/
   {  // FileUnMap
   UnmapViewOfFile(pBase) ;
   CloseHandle (hMapping) ;
   return (TRUE) ;
   }  // FileUnMap

void FileNameExtension (LPTSTR lpszSpec,
                        LPTSTR lpszFileNameExtension)
/*
   Effect:        Return the name and extension portion only of lpszSpec
                  int lpszFileNameExtension.

   Assert:        lpszFileNameExtension is large enough to hold a name,
                  delimiter, extension, and terminating null character.
*/
   {  // FileNameExtension
   LPTSTR          lpszDelimiter ;

   lpszDelimiter = _tcsrchr ((LPCTSTR)lpszSpec, (TCHAR)DIRECTORY_DELIMITER) ;
   if (!lpszDelimiter)
      lpszDelimiter = _tcsrchr ((LPCTSTR)lpszSpec, (TCHAR)DRIVE_DELIMITER) ;

   lstrcpy (lpszFileNameExtension,
           lpszDelimiter ? ++lpszDelimiter : lpszSpec) ;
   }  // FileNameExtension

void FileDriveDirectory (LPTSTR lpszFileSpec,
                         LPTSTR lpszDirectory)
/*
   Effect:        Extract the drive and directory from the file
                  specification lpszFileSpec, and return the it in
                  lpszDirectory.

   Internals:     Copy the the whole spec to lpszDirectory. Use lstrrchr
                  to find the *last* directory delimiter ('\') and
                  truncate the string right after that.

   Note:          This function assumes that the specification lpszFileSpec
                  is fairly complete, in that it contains both a directory
                  and a file name.
*/
   {  // FileDriveDirectory
   LPTSTR          lpszDelimiter ;

   lstrcpy (lpszDirectory, lpszFileSpec) ;
   lpszDelimiter = _tcsrchr ((LPCTSTR)lpszDirectory, (TCHAR)DIRECTORY_DELIMITER) ;
   if (lpszDelimiter)
      *(++lpszDelimiter) = TEXT('\0') ;
   }  // FileDriveDirectory


FILEUTIL.H

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

#define FILE_ERROR_MESSAGE_SIZE 256

VOID FileErrorMessageBox(HWND hWnd,
                         LPTSTR lpszFileName,
                         DWORD ErrorCode) ;

BOOL FileRead (HANDLE hFile,
               LPMEMORY lpMemory,
               DWORD nAmtToRead) ;

BOOL FileWrite (HANDLE hFile,
                LPMEMORY lpMemory,
                DWORD nAmtToWrite) ;

#define FileSeekBegin(hFile, lAmtToMove) \
   SetFilePointer (hFile, lAmtToMove, NULL, FILE_BEGIN)

#define FileSeekEnd(hFile, lAmtToMove) \
   SetFilePointer (hFile, lAmtToMove, NULL, FILE_END)

#define FileSeekCurrent(hFile, lAmtToMove) \
   SetFilePointer (hFile, lAmtToMove, NULL, FILE_CURRENT)

#define FileTell(hFile) \
   SetFilePointer (hFile, 0, NULL, FILE_CURRENT)

#define FileHandleOpen(lpszFilePath)         \
   (HANDLE) CreateFile (lpszFilePath,        \
      GENERIC_READ | GENERIC_WRITE,          \
      FILE_SHARE_READ,                       \
      NULL, OPEN_EXISTING,                   \
      FILE_ATTRIBUTE_NORMAL, NULL)

#define FileHandleReadOnly(lpszFilePath)     \
   (HANDLE) CreateFile (lpszFilePath,        \
      GENERIC_READ ,                         \
      FILE_SHARE_READ,                       \
      NULL, OPEN_EXISTING,                   \
      FILE_ATTRIBUTE_NORMAL, NULL)

#define FileHandleCreate(lpszFilePath)       \
   (HANDLE) CreateFile (lpszFilePath,        \
      GENERIC_READ | GENERIC_WRITE,          \
      FILE_SHARE_READ,                       \
      NULL, CREATE_ALWAYS,                   \
      FILE_ATTRIBUTE_NORMAL, NULL)

int FileHandleSeekCurrent (HANDLE hFile,
                           int iAmtToMove,
                           LPTSTR lpszFilePath) ;

int FileHandleSeekStart (HANDLE hFile,
                         int iAmtToMove,
                         LPTSTR lpszFilePath) ;

BOOL FileHandleWrite (HANDLE hFile,
                      LPMEMORY lpBuffer,
                      int cbWrite,
                      LPTSTR lpszFilePath) ;

LPMEMORY FileMap (HANDLE hFile, HANDLE *phMapHandle) ;

BOOL FileUnMap (LPVOID pBase, HANDLE hMapHandle) ;

void FileDriveDirectory (LPTSTR lpszFileSpec,
                         LPTSTR lpszDirectory) ;

void FileNameExtension (LPTSTR lpszSpec,
                        LPTSTR lpszFileNameExtension) ;


GLOBALS.H

#ifdef DEFINE_GLOBALS

#define GLOBAL

// #include "counters.h"

// initialize some of the globals
// only perfmon.c will define DEFINE_GLOBALS

int     aiIntervals [] = { 1, 5, 15, 30, 60, 120, 300, 600, 3600 } ;

// setup CVS delimiter and line ending used by Export routines
CHAR           CommasStr[] = "," ;
CHAR           TabStr[] = "\t" ;
CHAR           *pDelimiter = TabStr ;
CHAR           LineEndStr[] = "\n" ;
TCHAR          DashLine[] = TEXT("- - - -") ;

// PDFN           CounterFuncEntry = CounterEntry ;

#else
// only perfmon.c define DEFINE_GLOBALS,
// all other references to them as extern
#define GLOBAL extern

#define  NumIntervals   9
GLOBAL   int            aiIntervals [] ;

// setup CVS delimiter and line ending used by Export routines
GLOBAL   CHAR           CommasStr[] ;
GLOBAL   CHAR           TabStr[] ;
GLOBAL   CHAR           *pDelimiter ;
GLOBAL   CHAR           LineEndStr[] ;
GLOBAL   TCHAR          DashLine[] ;
#endif

//=============================//
// Graph Data Information      //
//=============================//

// GLOBAL   PPERFSYSTEM    pSysInfo ;
GLOBAL   PGRAPHSTRUCT   pGraphs;

//=============================//
// Font Information            //
//=============================//

GLOBAL   HFONT          hFontScales ;
GLOBAL   HFONT          hFontScalesBold ;
GLOBAL   LONG           HalfTextHeight;

#ifdef KEEP_PRINT
GLOBAL   HFONT          hFontPrinterScales ;
GLOBAL   HFONT          hFontPrinterScalesBold ;
#endif

//=============================//
// Control Information         //
//=============================//

GLOBAL   INT            iPerfmonView ;
GLOBAL   LANGID         iLanguage ;
GLOBAL   LANGID         iEnglishLanguage ;
GLOBAL   OPTIONS        Options ;

GLOBAL   HICON          hIcon ;
GLOBAL   HANDLE         hInstance;
GLOBAL   HANDLE         hAccelerators ;

GLOBAL   HANDLE         hEventLog ;

GLOBAL   HMENU          hMenuChart ;
GLOBAL   HMENU          hMenuAlert ;
GLOBAL   HMENU          hMenuLog ;
GLOBAL   HMENU          hMenuReport ;

//=============================//
// Windows                     //
//=============================//

GLOBAL   HWND    hWndMain ;
GLOBAL   HWND    hWndGraph ;
GLOBAL   HWND    hWndLog ;
GLOBAL   HWND    hWndAlert ;
GLOBAL   HWND    hWndReport ;

GLOBAL   HWND    hWndGraphDisplay ;
GLOBAL   HWND    hWndGraphLegend ;
GLOBAL   HWND    hWndGraphStatus ;

GLOBAL   HWND    hWndAlertLegend ;
GLOBAL   HWND    hWndLogEntries ;

GLOBAL   HWND    hWndToolbar ;
GLOBAL   HWND    hWndStatus ;

//=============================//
// System Metrics              //
//=============================//

GLOBAL   int     xScreenWidth ;
GLOBAL   int     yScreenHeight ;

GLOBAL   int     xBorderWidth ;
GLOBAL   int     yBorderHeight ;

GLOBAL   int     xScrollWidth ;
GLOBAL   int     yScrollHeight ;

GLOBAL   int     xScrollThumbWidth ;
GLOBAL   int     yScrollThumbHeight ;

GLOBAL   int     xDlgBorderWidth ;
GLOBAL   int     yDlgBorderHeight ;

GLOBAL   int     MinimumSize ;

//=============================//
// Miscellaneous               //
//=============================//

GLOBAL   int            iUnviewedAlerts ;
GLOBAL   COLORREF       crLastUnviewedAlert ;

GLOBAL   LPTSTR         pChartFileName ;
GLOBAL   LPTSTR         pChartFullFileName ;
GLOBAL   LPTSTR         pAlertFileName ;
GLOBAL   LPTSTR         pAlertFullFileName ;
GLOBAL   LPTSTR         pLogFileName ;
GLOBAL   LPTSTR         pLogFullFileName ;
GLOBAL   LPTSTR         pReportFileName ;
GLOBAL   LPTSTR         pReportFullFileName ;
GLOBAL   LPTSTR         pWorkSpaceFileName ;
GLOBAL   LPTSTR         pWorkSpaceFullFileName ;

// globals for perfmornance improvements

// frequently used GDI objects
GLOBAL   UINT     ColorBtnFace ;  // for concave/convex button painting
GLOBAL   HBRUSH   hBrushFace ;    // for concave/convex button painting
GLOBAL   HPEN     hPenHighlight ; // for concave/convex button painting
GLOBAL   HPEN     hPenShadow ;    // for concave/convex button painting
GLOBAL   HPEN     hWhitePen ;     // for chart highlighting
GLOBAL   HANDLE   hbLightGray ;   // for painting the background

// bPerfmonIconic is TRUE when perfmon is minimized.
// Thus, we don't need to update chart or report view until
// it is not iconized
GLOBAL   BOOL     bPerfmonIconic ;

// bAddLineInPorgress is TRUE when Addline dialog is up.  It is used
// in freeing unused system during data collecting. (But not while
// addline dialog is still up)
GLOBAL   BOOL     bAddLineInProgress ;

// bDelayAddAction is TRUE when reading setting files or adding more
// than 1 counter.  This is to delay some of the costly screen adjustments
// until we have added all the lines.
GLOBAL   BOOL     bDelayAddAction ;

// bExplainTxtButtonHit is TRUE when the ExplainText button in addline
// dialog is clicked.  This is to save time and memory for fetching the
// help text, during BuildNameTable(), unless it is needed.
GLOBAL   BOOL     bExplainTextButtonHit ;

// globals used for WinHelp
GLOBAL   DWORD          dwCurrentDlgID ;
GLOBAL   DWORD          dwCurrentMenuID ;
GLOBAL   LPTSTR         pszHelpFile ;

// Following includes space for trailing NULL and preceeding \\'s
GLOBAL   TCHAR  LocalComputerName[MAX_COMPUTERNAME_LENGTH + 3];
GLOBALTCHAR CmdLineComputerName[MAX_COMPUTERNAME_LENGTH + 3];

// Flag to indicate if we need to close local machine
GLOBAL   BOOL           bCloseLocalMachine ;

// Timeout for data collection thread in msec
GLOBAL   DWORD          DataTimeOut ;

// 20 sec for the data thread timeout
#define  DEFAULT_DATA_TIMEOUT    20000L

//=============================//
// Log Playback Information    //
//=============================//

GLOBAL   PLAYBACKLOG    PlaybackLog ;

GLOBAL   REPORT         Report ;
GLOBAL   ALERT          Alert ;
GLOBAL   LOG            Log ;


GRAFDATA.C

/*****************************************************************************
 *
 *  Grafdata.c - This module handles the non-drawing functions of the graph,
 *      such as allocating linked structures and their memory, freeing it,
 *      unlinking, starting and stopping the timer,
 *      setting up the first graph (CPU), and all the numeric functions for
 *      the different counter types.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *
 ****************************************************************************/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdio.h>      // for sprintf

#include "perfmon.h"       // main perfmon declarations
#include "grafdata.h"      // external declarations for this file
#include <float.h>         // for FLT_MAX constant

#include "addline.h"       // for AddLine, EditLine
#include "counters.h"      // for CounterEntry
#include "graph.h"         // for SizeGraphComponents
#include "pmemory.h"       // for MemoryXXX (mallloc-type) routines
#include "perfdata.h"      // for UpdateLines
#include "playback.h"      // for PlayingBackLog, PlaybackLines
#include "legend.h"
#include "system.h"        // for SystemGet
#include "utils.h"
#include "line.h"          // for LineFree
#include "valuebar.h"      // for StatusTimer

#include "fileopen.h"      // for FileGetName
#include "fileutil.h"      // for FileRead...
#include "menuids.h"       // for IDM_VIEWCHART
#include "perfmops.h"      // for ExportFileHeader
#include "status.h"        // for StatusLineReady

extern BOOL SaveFileHandler(HWND hWnd,DWORD type) ;

// this macro is used in doing a simple DDA (Digital Differential Analyzer)
// * 10 + 5 is to make the result round up with .5
#define DDA_DISTRIBUTE(TotalTics, numOfData) \
   ((TotalTics * 10 / numOfData) + 5) / 10

#define szSmallValueFormat         TEXT("%10.3f")
#define szLargeValueFormat         TEXT("%10.0f")

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

/****************************************************************************
 * eUpdateMinMaxAve -
 ****************************************************************************/
void eUpdateMinMaxAve (FLOAT eValue, PLINESTRUCT pLineStruct, INT iValidValues,
   INT iTotalValidPoints, INT iDataPoint, INT gMaxPoints)
{
INT     i ;
INT     iDataNum = iTotalValidPoints ;
FLOAT   eMin,
        eMax,
        eSum,
        eNewValue ;

   eMax = eMin = eValue ;

   if (PlayingBackLog())
      {
      eSum = (FLOAT) 0.0 ;
      }
   else
      {
      eSum = eValue ;
      }

   if (iValidValues == iTotalValidPoints)
      {
      for (i=0 ; i < iValidValues ; i++)
         {
         if (i == iDataPoint)
            {
            // skip the data point we are going to replace
            continue ;
            }

         eNewValue = pLineStruct->lnValues[i] ;

         eSum += eNewValue ;

         if (eNewValue > eMax)
            {
            eMax = eNewValue ;
            }

         if (eNewValue < eMin)
            {
            eMin = eNewValue ;
            }
         }
      }
   else
      {
      // special case when we start the new line in the middle of the chart
      for (i = iDataPoint, iTotalValidPoints-- ;
          iTotalValidPoints > 0 ;
          iTotalValidPoints-- )
         {
         i-- ;
         if (i < 0)
            {
            // for the wrap-around case..
            i = gMaxPoints - 1 ;
            }

         if (i == iDataPoint)
            {
            // skip the data point we are going to replace
            continue ;
            }
         eNewValue = pLineStruct->lnValues[i] ;

         eSum += eNewValue ;

         if (eNewValue > eMax)
            {
            eMax = eNewValue ;
            }

         if (eNewValue < eMin)
            {
            eMin = eNewValue ;
            }
         }
      }

   pLineStruct->lnMinValue = eMin ;
   pLineStruct->lnMaxValue = eMax ;

   if (iDataNum)
      {
      pLineStruct->lnAveValue = eSum / (FLOAT) iDataNum ;
      }
   else
      {
      pLineStruct->lnAveValue = (FLOAT) 0.0 ;
      }
}

// UpdateValueBarData is used to update the value bar data
// It is called when the user switches to a diff. line from
// the legned window.
VOID UpdateValueBarData (PGRAPHSTRUCT pGraph)
   {
   PLINESTRUCT    pCurrentGraphLine ;
   INT            KnownValue,
                  MaxValues,
                  iValidValues,
                  iDataPoint ;
   FLOAT          eValue ;
   pCurrentGraphLine = CurrentGraphLine (hWndGraph) ;

   if (!pCurrentGraphLine || pCurrentGraphLine->bFirstTime)
      {
      // we have not collect enough samples
      return ;
      }

   KnownValue = pGraph->gKnownValue ;
   MaxValues = pGraph->gMaxValues ;

   // The valid values is the number of valid entries in the
   // data buffer.  After we wrap the buffer, all the values are
   // valid.
   iValidValues = pGraph->gTimeLine.iValidValues ;

   // Get the index to the data point we are updating.

   iDataPoint = KnownValue % MaxValues ;
   eValue = pCurrentGraphLine->lnValues[iDataPoint] ;

   // get the statistical data for this line
   eUpdateMinMaxAve(eValue, pCurrentGraphLine,
      iValidValues, pCurrentGraphLine->lnValidValues,
      iDataPoint, MaxValues) ;
   }  // UpdateValueBarData

VOID UpdateLGData (PGRAPHSTRUCT pGraph)
   {
   PLINESTRUCT    pLine ;
   PLINESTRUCT    pCurrentGraphLine ;
   INT            KnownValue,
                  MaxValues,
                  iValidValues,
                  iDataPoint ;
   FLOAT          eValue ;
   // Known Value is the where data is placed in the buffer.
   pGraph->gKnownValue++;

   KnownValue = pGraph->gKnownValue ;

   // Update the high water mark for valid data in the lnValues
   // (aka DataPoint) buffer.

   MaxValues = pGraph->gMaxValues ;

   // The valid values is the number of valid entries in the
   // data buffer.  After we wrap the buffer, all the values are
   // valid.
   iValidValues = pGraph->gTimeLine.iValidValues ;

   if (iValidValues < MaxValues)
       iValidValues = (KnownValue % MaxValues) + 1 ;

   pGraph->gTimeLine.iValidValues = iValidValues ;

   // Get the index to the data point we are updating.

   iDataPoint = KnownValue % MaxValues ;

   // loop through lines,
   // If one of the lines is highlighted then do the calculations
   // for average, min, & max on that line.

   pCurrentGraphLine = CurrentGraphLine (hWndGraph) ;

   for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
     { // for

     if (pLine->bFirstTime)
         {
         // skip until we have collect enough samples to plot the first data
         continue ;
         }

     if (pLine->lnValidValues < MaxValues)
         {
         (pLine->lnValidValues)++ ;
         }

     // Get the new value for this line.
     eValue = CounterEntry (pLine) ;

     if (pLine == pCurrentGraphLine)
        {  // if
        // get the statistical data for this line
        eUpdateMinMaxAve (eValue, pLine,
                                 iValidValues, pLine->lnValidValues,
                                 iDataPoint, MaxValues) ;
        }  // if

     // Now put the new value into the data array
     pLine->lnValues[iDataPoint] = eValue ;
     }

   GetLocalTime (&(pGraph->pDataTime[iDataPoint])) ;
   }  // UpdateLGData

BOOL HandleGraphTimer (void)
   {
   PGRAPHSTRUCT pGraph;

   //NOTE: get a strategy for these "no-go" states
   if (!(pGraph = pGraphs) || !pGraphs->pSystemFirst)
       return(FALSE);

   if (!UpdateLines(&(pGraphs->pSystemFirst), pGraphs->pLineFirst))
        return (TRUE) ;

   UpdateLGData(pGraph);

   return(TRUE);
   }

VOID ClearGraphTimer(PGRAPHSTRUCT pGraph)
   {
   KillTimer(pGraph->hWnd, GRAPH_TIMER_ID);
   }

VOID SetGraphTimer(PGRAPHSTRUCT pGraph)
   {
   SetTimer(pGraph->hWnd, GRAPH_TIMER_ID, pGraph->gInterval, NULL) ;
   }

VOID ResetGraphTimer(PGRAPHSTRUCT pGraph)
{
    KillTimer(pGraph->hWnd, GRAPH_TIMER_ID);
    SetGraphTimer(pGraph);
}

VOID GetGraphConfig(PGRAPHSTRUCT pGraph)
{

    LoadRefreshSettings(pGraph);
    LoadLineGraphSettings(pGraph);

    // Init the structure
    pGraph->pLineFirst = NULL;

    //NOTE: put the rest of this in Config

    pGraph->gOptions.bLegendChecked    = TRUE;
    pGraph->gOptions.bMenuChecked      = TRUE;
    pGraph->gOptions.bLabelsChecked    = TRUE;
    pGraph->gOptions.bVertGridChecked  = FALSE;
    pGraph->gOptions.bHorzGridChecked  = FALSE;
    pGraph->gOptions.bStatusBarChecked = TRUE;
    pGraph->gOptions.GraphVGrid        = TRUE;
    pGraph->gOptions.GraphHGrid        = TRUE;
    pGraph->gOptions.HistVGrid         = TRUE;
    pGraph->gOptions.HistHGrid         = TRUE;

    pGraph->gOptions.iGraphOrHistogram = LINE_GRAPH;       // vs. BAR_GRAPH
    pGraph->gOptions.iVertMax = DEF_GRAPH_VMAX;

    return;
}

BOOL InsertGraph (HWND hWnd)
   {
   PGRAPHSTRUCT    pGraph;

   pGraph = MemoryAllocate (sizeof (GRAPHSTRUCT)) ;
   if (!pGraph)
      return (FALSE) ;

   pGraphs = pGraph;

   GetGraphConfig(pGraph);
   pGraph->bManualRefresh = FALSE ;

   pGraph->gMaxValues = DEFAULT_MAX_VALUES;
   pGraph->pptDataPoints =
      (PPOINT) MemoryAllocate (sizeof (POINT) * pGraph->gMaxValues) ;

   pGraph->pDataTime =
      (SYSTEMTIME *) MemoryAllocate (sizeof(SYSTEMTIME) * pGraph->gMaxValues) ;

   pGraph->hWnd = hWnd ;
   pGraph->bModified = FALSE ;

   pGraph->Visual.iColorIndex = 0 ;
   pGraph->Visual.iWidthIndex = 0 ;
   pGraph->Visual.iStyleIndex = 0 ;

   return(TRUE) ;
   }

void PlaybackSetGraphLines (HWND hWndChart,
                            PLINE pLineFirst,
                            int iDisplayTic,
                            int iLogTic,
                            BOOL CalcData)
   {
   PLINE          pLine ;
   FLOAT          eValue ;

   for (pLine = pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for
      eValue = CounterEntry (pLine) ;
      pLine->lnValues[iDisplayTic] = eValue ;
      pLine->aiLogIndexes[iDisplayTic] = iLogTic ;

      // only need to do this on request.
      if (CalcData)
         {
         eUpdateMinMaxAve (eValue, pLine, iDisplayTic, iDisplayTic,
            iDisplayTic, iDisplayTic) ;
         }  // if
      }  // for
   }  // PlaybackSetGraphLines

BOOL ChartInsertLine (PGRAPHSTRUCT pGraph,
                      PLINE pLine)
/*
   Effect:        Insert the line pLine into the graph pGraph and
                  allocate space for the graph's number of values.

   Returns:       Whether the line could be added and space allocated.

   See Also:      LineAllocate (line.c), ChartDeleteLine.
*/
   {  // ChartInsertLine
   PLINE          pLineEquivalent ;
   INT            i ;
   FLOAT          *pTempPts;
   HPEN           tempPen ;

   pGraph->bModified = TRUE ;

   pLineEquivalent = FindEquivalentLine (pLine, pGraph->pLineFirst) ;
   if (pLineEquivalent)
      {
      pLineEquivalent->Visual = pLine->Visual ;
      pLineEquivalent->iScaleIndex = pLine->iScaleIndex ;
      pLineEquivalent->eScale = pLine->eScale ;

      tempPen = pLineEquivalent->hPen ;
      pLineEquivalent->hPen =  pLine->hPen ;
      pLine->hPen = tempPen ;
      return FALSE ;
      }
   else
      {
      if (!pGraph->pLineFirst && !PlayingBackLog())
         {
         SetGraphTimer (pGraph) ;
         }

      if (!SystemAdd (&pGraph->pSystemFirst, pLine->lnSystemName))
        return FALSE;

      LineAppend (&pGraph->pLineFirst, pLine) ;

      pLine->lnMinValue = FLT_MAX ;
      pLine->lnMaxValue = - FLT_MAX ;
      pLine->lnAveValue = 0.0F ;
      pLine->lnValidValues = 0 ;

      pLine->lnValues =
         (FLOAT *) MemoryAllocate (sizeof (FLOAT) * pGraph->gMaxValues) ;

      for (i = pGraph->gMaxValues, pTempPts = pLine->lnValues ;
           i > 0 ;
           i-- )
         {
         *pTempPts++ = (FLOAT) 0.0 ;
         }

      if (PlayingBackLog ())
         {
         pLine->aiLogIndexes =
            (int *) MemoryAllocate (sizeof (LONG) * pGraph->gMaxValues) ;
         }

      // Add the line to the legend, resize the legend window, and then
      // select the new line as the current legend item. Do it in this
      // sequence to avoid the legend scroll bar momentarily appearing and
      // then disappearing, since the resize will obviate the scroll bar.

      LegendAddItem (hWndGraphLegend, pLine) ;

      if (!bDelayAddAction)
         {
         SizeGraphComponents (hWndGraph) ;
         LegendSetSelection (hWndGraphLegend,
                          LegendNumItems (hWndGraphLegend) - 1) ;

         if (PlayingBackLog ())
            PlaybackChart (pGraph->hWnd) ;
         }
      }

   return (TRUE) ;
   }  // ChartInsertLine

VOID ChartDeleteLine (PGRAPHSTRUCT pGraph,
                      PLINESTRUCT pLine)
   {
   PLINESTRUCT npLine;

   pGraph->bModified = TRUE ;

   if (pGraph->pLineFirst == pLine)
       pGraph->pLineFirst = pLine->pLineNext;
   else
   {
       for (npLine = pGraph->pLineFirst; npLine; npLine = npLine->pLineNext)
       {
           if (npLine->pLineNext == pLine)
               npLine->pLineNext = pLine->pLineNext;
       }
   }

   if (!pGraph->pLineFirst)
      {
      ResetGraph (pGraph) ;
      }
   else
      {
      BuildValueListForSystems (
         pGraph->pSystemFirst,
         pGraph->pLineFirst) ;
      }

   // Delete the legend entry for this line.
   // If the line was highlighted then this will undo the highlight.

   LegendDeleteItem (hWndGraphLegend, pLine) ;

   LineFree (pLine) ;

   SizeGraphComponents (hWndGraph) ;

   }

FLOAT Counter_Queuelen(PLINESTRUCT pLine)
{

    return((FLOAT)0.0);
//    pLine;
}

void ClearGraphDisplay (PGRAPHSTRUCT pGraph)
   {
   PLINESTRUCT    pLine;

   // reset the timeline data
//   pGraph->gKnownValue = -1 ;
   pGraph->gKnownValue = 0 ;
   pGraph->gTimeLine.iValidValues = 0 ;
   pGraph->gTimeLine.xLastTime = 0 ;
   memset (pGraph->pDataTime, 0, sizeof(SYSTEMTIME) * pGraph->gMaxValues) ;

   // loop through lines,
   // If one of the lines is highlighted then do the calculations
   // for average, min, & max on that line.

   for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
      { // for

      pLine->bFirstTime = 2 ;
      pLine->lnMinValue = FLT_MAX ;
      pLine->lnMaxValue = - FLT_MAX ;
      pLine->lnAveValue = 0.0F ;
      pLine->lnValidValues = 0 ;
      memset (pLine->lnValues, 0, sizeof(FLOAT) * pGraph->gMaxValues) ;
      }

   StatusTimer (hWndGraphStatus, TRUE) ;
   WindowInvalidate (hWndGraphDisplay) ;
   }

void ResetGraphView (HWND hWndGraph)
   {
   PGRAPHSTRUCT      pGraph ;

   pGraph = GraphData (hWndGraph) ;

   if (!pGraph)
      {
      return ;
      }

   ChangeSaveFileName (NULL, IDM_VIEWCHART) ;

   if (pGraph->pSystemFirst)
      {
      ResetGraph (pGraph) ;
      }
   }  // ResetGraphView

void ResetGraph (PGRAPHSTRUCT pGraph)
   {
   ClearGraphTimer (pGraph) ;
   ClearLegend (hWndGraphLegend) ;
   if (pGraph->pLineFirst)
      {
      FreeLines (pGraph->pLineFirst) ;
      pGraph->pLineFirst = NULL ;
      }

   if (pGraph->pSystemFirst)
      {
      FreeSystems (pGraph->pSystemFirst) ;
      pGraph->pSystemFirst = NULL ;
      }
//   pGraph->gKnownValue = -1 ;
   pGraph->gKnownValue = 0 ;
   pGraph->gTimeLine.iValidValues = 0 ;
   pGraph->gTimeLine.xLastTime = 0 ;

   // reset visual data
   pGraph->Visual.iColorIndex = 0 ;
   pGraph->Visual.iWidthIndex = 0 ;
   pGraph->Visual.iStyleIndex = 0 ;

   memset (pGraph->pDataTime, 0, sizeof(SYSTEMTIME) * pGraph->gMaxValues) ;

   SizeGraphComponents (hWndGraph) ;
   InvalidateRect(hWndGraph, NULL, TRUE) ;
   }

void PlaybackChart (HWND hWndChart)
   {  // PlaybackChart
   int            iDisplayTics ;       // num visual points to display
   int            iDisplayTic ;
   int            iLogTic ;
   int            iLogTicsMove ;
   BOOL           bFirstTime = TRUE;
   int            iLogTicsRemaining ;

   if (!pGraphs->pLineFirst)
      {
      // no line to playback
      return ;
      }

   iLogTicsRemaining = PlaybackLog.iSelectedTics ;

   // we only have iDisplayTics-1 points since
   // we have to use the first two sample points to
   // get the first data points.
   if (iLogTicsRemaining <= pGraphs->gMaxValues)
      {
      iDisplayTics = iLogTicsRemaining ;
      pGraphs->gTimeLine.iValidValues = iDisplayTics - 1 ;
      }
   else
      {
      iDisplayTics = pGraphs->gMaxValues ;
      pGraphs->gTimeLine.iValidValues = iDisplayTics ;
      }

   iDisplayTic = -1 ;
   iLogTic = PlaybackLog.StartIndexPos.iPosition ;

   while (iDisplayTics)
      {

      PlaybackLines (pGraphs->pSystemFirst,
                     pGraphs->pLineFirst,
                     iLogTic) ;

      if (bFirstTime)
         {
         bFirstTime = FALSE ;

         // get the second sample data to form the first data point
         iLogTic++ ;
         iLogTicsRemaining-- ;
         PlaybackLines (pGraphs->pSystemFirst,
                        pGraphs->pLineFirst,
                        iLogTic) ;
         }
      iDisplayTic++ ;
      PlaybackSetGraphLines (hWndChart, pGraphs->pLineFirst,
         iDisplayTic, iLogTic, (iDisplayTics == 1)) ;

      // setup DDA to get the index of the next sample point
      iLogTicsMove = DDA_DISTRIBUTE (iLogTicsRemaining, iDisplayTics) ;
      iLogTicsRemaining -= iLogTicsMove ;
      iLogTic += iLogTicsMove ;

      iDisplayTics-- ;

      }  // while

   // point to the last value for valuebar display
   pGraphs->gKnownValue = iDisplayTic ;

   }  // PlaybackChart

#if 0
PLINESTRUCT CurrentGraphLine (HWND hWndGraph)
   {  // CurrentGraphLine
   UNREFERENCED_PARAMETER (hWndGraph) ;

   return (LegendCurrentLine (hWndGraphLegend)) ;
   }
#endif

BOOL AddChart (HWND hWndParent)
   {
   PLINE pCurrentLine = CurrentGraphLine (hWndGraph) ;

   return (AddLine (hWndParent,
                    &(pGraphs->pSystemFirst),
                    &(pGraphs->Visual),
                    pCurrentLine ? pCurrentLine->lnSystemName : NULL,
                    LineTypeChart)) ;
   }

BOOL EditChart (HWND hWndParent)
   {  // EditChart
   return (EditLine (hWndParent,
                     &(pGraphs->pSystemFirst),
                     CurrentGraphLine (hWndGraph),
                     LineTypeChart)) ;
   }

void GraphAddAction ()
   {
   PGRAPHSTRUCT      pGraph ;

   pGraph = GraphData (hWndGraph) ;

   SizeGraphComponents (hWndGraph) ;

   LegendSetSelection (hWndGraphLegend,
      LegendNumItems (hWndGraphLegend) - 1) ;

   if (PlayingBackLog ())
      PlaybackChart (pGraph->hWnd) ;
   }

BOOL OpenChartVer1 (HANDLE hFile,
                    DISKCHART *pDiskChart,
                    PGRAPHSTRUCT pGraph)
   {  // OpenChartVer1
   bDelayAddAction = TRUE ;
   pGraph->Visual = pDiskChart->Visual ;
   pGraph->gOptions = pDiskChart->gOptions ;
   pGraph->gMaxValues = pDiskChart->gMaxValues ;
   pGraph->bManualRefresh = pDiskChart->bManualRefresh ;
   pGraphs->gInterval = (INT) (pGraph->gOptions.eTimeInterval * (FLOAT) 1000.0) ;
   ReadLines (hFile, pDiskChart->dwNumLines,
               &(pGraph->pSystemFirst), &(pGraph->pLineFirst), IDM_VIEWCHART) ;

   bDelayAddAction = FALSE ;

   GraphAddAction () ;

   return (TRUE) ;
   }  // OpenChartVer1

BOOL OpenChart (HWND hWndGraph,
                HANDLE hFile,
                DWORD dwMajorVersion,
                DWORD dwMinorVersion,
                BOOL bChartFile)
   {  // OpenChart
   PGRAPHSTRUCT   pGraph ;
   DISKCHART      DiskChart ;
   BOOL           bSuccess = TRUE ;

   pGraph = pGraphs ;
   if (!pGraph)
      {
      bSuccess = FALSE ;
      goto Exit0 ;
      }

   if (!FileRead (hFile, &DiskChart, sizeof (DISKCHART)))
      {
      bSuccess = FALSE ;
      goto Exit0 ;
      }

   switch (dwMajorVersion)
      {
      case (1):

         SetHourglassCursor() ;

         ResetGraphView (hWndGraph) ;

         OpenChartVer1 (hFile, &DiskChart, pGraph) ;

         // change to chart view if we are opening a
         // chart file
         if (bChartFile && iPerfmonView != IDM_VIEWCHART)
            {
            SendMessage (hWndMain, WM_COMMAND, (LONG)IDM_VIEWCHART, 0L) ;
            }

         if (iPerfmonView == IDM_VIEWCHART)
            {
            SetPerfmonOptions (&DiskChart.perfmonOptions) ;
            }

         SetArrowCursor() ;

         break ;
      }  // switch

Exit0:

   if (bChartFile)
      {
      CloseHandle (hFile) ;
      }

   return (bSuccess) ;
   }  // OpenChart

BOOL SaveChart (HWND hWndGraph, HANDLE hInputFile, BOOL bGetFileName)
   {
   PGRAPHSTRUCT   pGraph ;
   PLINE          pLine ;
   HANDLE         hFile ;
   DISKCHART      DiskChart ;
   PERFFILEHEADER FileHeader ;
   TCHAR          szFileName [256] ;
   BOOL           newFileName = FALSE ;

   if (hInputFile)
      {
      // use the input file handle if it is available
      // this is the case for saving workspace data
      hFile = hInputFile ;
      }
   else
      {
      if (pChartFullFileName)
         {
         lstrcpy (szFileName, pChartFullFileName) ;
         }
      if (bGetFileName || pChartFullFileName == NULL)
         {
//         if (!pChartFullFileName)
//            {
//            StringLoad (IDS_GRAPH_FNAME, szFileName) ;
//            }

         if (!FileGetName (hWndGraph, IDS_CHARTFILE, szFileName))
            {
            return (FALSE) ;
            }
         newFileName = TRUE ;
         }

      hFile = FileHandleCreate (szFileName) ;

      if (hFile && newFileName)
         {
         ChangeSaveFileName (szFileName, IDM_VIEWCHART) ;
         }
      else if (!hFile)
         {
         DlgErrorBox (hWndGraph, ERR_CANT_OPEN, szFileName) ;
         }
      }

   if (!hFile)
      return (FALSE) ;

   pGraph = pGraphs ;
   if (!pGraph)
      {
      if (!hInputFile)
         {
         CloseHandle (hFile) ;
         }
      return (FALSE) ;
      }

   if (!hInputFile)
      {
      // only need to write file header if not workspace
      memset (&FileHeader, 0, sizeof (FileHeader)) ;
      lstrcpy (FileHeader.szSignature, szPerfChartSignature) ;
      FileHeader.dwMajorVersion = ChartMajorVersion ;
      FileHeader.dwMinorVersion = ChartMinorVersion ;

      if (!FileWrite (hFile, &FileHeader, sizeof (PERFFILEHEADER)))
         {
         goto Exit0 ;
         }
      }

   DiskChart.Visual = pGraph->Visual ;
   DiskChart.gOptions = pGraph->gOptions ;
   DiskChart.gMaxValues = pGraph->gMaxValues ;
   DiskChart.dwNumLines = NumLines (pGraph->pLineFirst) ;
   DiskChart.bManualRefresh = pGraph->bManualRefresh ;
   DiskChart.perfmonOptions = Options ;

   if (!FileWrite (hFile, &DiskChart, sizeof (DISKCHART)))
      {
      goto Exit0 ;
      }

   for (pLine = pGraph->pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for
      if (!WriteLine (pLine, hFile))
         {
         goto Exit0 ;
         }
      }  // for

   if (!hInputFile)
      {
      CloseHandle (hFile) ;
      }

   return (TRUE) ;

Exit0:
   if (!hInputFile)
      {
      CloseHandle (hFile) ;

      // only need to report error if not workspace
      DlgErrorBox (hWndGraph, ERR_SETTING_FILE, szFileName) ;
      }
   return (FALSE) ;

   }  // SaveChart

#define TIME_TO_WRITE 2
BOOL ExportChartLabels (HANDLE hFile, PGRAPHSTRUCT pGraph)
{
   TCHAR          UnicodeBuff [LongTextLen] ;
   CHAR           TempBuff [LongTextLen * 2] ;
   int            StringLen ;
   PLINESTRUCT    pLine;
   int            TimeToWriteFile ;
   int            iIndex ;
   LPTSTR         lpItem ;

   for (iIndex = 0 ; iIndex < 5 ; iIndex++)
      {
      // for iIndex == 0, get counter name
      // for iIndex == 1, get instance name
      // for iIndex == 2, get parent name
      // for iIndex == 3, get object name
      // for iIndex == 4, get computer name
      if (iIndex == 4)
         {
         // the last label field, write date/time labels
         strcpy (TempBuff, LineEndStr) ;
         StringLen = strlen (TempBuff) ;
         StringLoad (IDS_EXPORT_DATE, UnicodeBuff) ;
         ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
         strcat (TempBuff, pDelimiter) ;
         StringLen = strlen (TempBuff) ;

         StringLoad (IDS_EXPORT_TIME, UnicodeBuff) ;
         ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
         strcat (TempBuff, pDelimiter) ;
         StringLen = strlen (TempBuff) ;
         }
      else
         {
         strcpy (TempBuff, LineEndStr) ;
         strcat (TempBuff, pDelimiter) ;
         strcat (TempBuff, pDelimiter) ;
         StringLen = strlen (TempBuff) ;
         }

      TimeToWriteFile = 0 ;

      for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
         {
         switch (iIndex)
            {
            case 0:
               lpItem = (LPTSTR) pLine->lnCounterName ;
               break ;

            case 1:
               lpItem = (LPTSTR) pLine->lnInstanceName ;
               break ;

            case 2:
               lpItem = (LPTSTR) pLine->lnPINName ;
               break ;

            case 3:
               lpItem = (LPTSTR) pLine->lnObjectName ;
               break ;

            case 4:
               lpItem = (LPTSTR) pLine->lnSystemName ;
               break ;
            }

         if (lpItem)
            {
            ConvertUnicodeStr (&TempBuff[StringLen], lpItem) ;
            }
         else
            {
            TempBuff[StringLen] = '\0' ;
            }
         strcat (TempBuff, pDelimiter);
         StringLen = strlen (TempBuff) ;

         if (++TimeToWriteFile > TIME_TO_WRITE)
            {
            // better write the buffers before they overflow.
            // there are better ways to check for overflow
            // but this is good enough

            if (!FileWrite (hFile, TempBuff, StringLen))
               {
               goto Exit0 ;
               }
            StringLen = TimeToWriteFile = 0 ;
            }
         }     // for each line

      if (StringLen)
         {
         // write the last block of data
         if (!FileWrite (hFile, TempBuff, StringLen))
            {
            goto Exit0 ;
            }
         }
      }     // for iIndex

   return (TRUE) ;

Exit0:
   return (FALSE) ;

}  // ExportChartLabels

BOOL ExportLogChart (HANDLE hFile, PGRAPHSTRUCT pGraph)
{
   TCHAR          UnicodeBuff [LongTextLen] ;
   CHAR           TempBuff [LongTextLen * 2] ;
   int            StringLen ;
   PLINESTRUCT    pLine;
   int            TimeToWriteFile ;
   FLOAT          eValue ;
   int            iLogTic ;
   BOOL           bFirstTime = TRUE ;
   SYSTEMTIME     LogSystemTime ;
   LOGPOSITION    LogPosition ;

   iLogTic = PlaybackLog.StartIndexPos.iPosition ;

   // we have to export every point from the log file

   for ( ; iLogTic <= PlaybackLog.StopIndexPos.iPosition ; iLogTic++)
      {

      PlaybackLines (pGraphs->pSystemFirst,
                     pGraphs->pLineFirst,
                     iLogTic) ;

      if (!bFirstTime)
         {
         // export the values
         TimeToWriteFile = 0 ;

         if (!LogPositionN (iLogTic, &LogPosition))
            {
            goto Exit0 ;
            }

         LogPositionSystemTime (&LogPosition, &LogSystemTime) ;

         strcpy (TempBuff, LineEndStr) ;
         StringLen = strlen (TempBuff) ;

         SystemTimeDateString (&LogSystemTime, UnicodeBuff) ;
         ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
         strcat (TempBuff, pDelimiter) ;
         StringLen = strlen (TempBuff) ;

         SystemTimeTimeString (&LogSystemTime, UnicodeBuff, FALSE) ;
         ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
         strcat (TempBuff, pDelimiter) ;
         StringLen = strlen (TempBuff) ;

         for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
            {

            eValue = CounterEntry (pLine) ;

            TSPRINTF (UnicodeBuff,
                      eValue > (FLOAT)999999.0 ?
                           szLargeValueFormat : szSmallValueFormat,
                      eValue) ;
            ConvertDecimalPoint (UnicodeBuff) ;
            ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
            strcat (TempBuff, pDelimiter) ;
            StringLen = strlen (TempBuff) ;

            if (++TimeToWriteFile > TIME_TO_WRITE)
               {
               if (!FileWrite (hFile, TempBuff, StringLen))
                  {
                  goto Exit0 ;
                  }
               StringLen = TimeToWriteFile = 0 ;
               TempBuff[0] = '\0' ;
               }
            }

         if (StringLen)
            {
            if (!FileWrite (hFile, TempBuff, StringLen))
               {
               goto Exit0 ;
               }
            }
         }
      else
         {
         // skip the first data point since we
         // need 2 points to form the first value
         bFirstTime = FALSE ;
         }
      }

   return (TRUE) ;

Exit0:
   return (FALSE) ;

}  // ExportLogChart

BOOL ExportLineValue (HANDLE hFile, PGRAPHSTRUCT pGraph,
   int CurrentIndex, int iDataPoint)
{
   TCHAR          UnicodeBuff [MiscTextLen] ;
   CHAR           TempBuff [LongTextLen] ;
   int            StringLen ;
   PLINESTRUCT    pLine;
   int            MaxValues ;
   int            TimeToWriteFile ;
   SYSTEMTIME     *pSystemTime ;
   BOOL           ValidValue ;

   pSystemTime = pGraph->pDataTime ;
   pSystemTime += CurrentIndex ;

   if (pSystemTime->wYear == 0 && pSystemTime->wYear == 0)
      {
      // ignore value that has 0 system time
      return (TRUE) ;
      }

   MaxValues = pGraph->gMaxValues ;
   strcpy (TempBuff, LineEndStr) ;
   StringLen = strlen (TempBuff) ;

   SystemTimeDateString (pSystemTime, UnicodeBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   SystemTimeTimeString (pSystemTime, UnicodeBuff, FALSE) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;

   TimeToWriteFile = 0 ;
   for (pLine=pGraph->pLineFirst; pLine; pLine=pLine->pLineNext)
      {
      if (!pLine->bFirstTime)
         {
         ValidValue = FALSE ;
         // check if this is a valid value
         if (pLine->lnValidValues == MaxValues)
            {
            // this is the simple case where we have filled up
            // the whole buffer
            ValidValue = TRUE ;
            }
         else if (pLine->lnValidValues <= iDataPoint)
            {
            if (CurrentIndex <= iDataPoint &&
               CurrentIndex > iDataPoint - pLine->lnValidValues)
               {
               ValidValue = TRUE ;
               }
            }
         else
            {
            if (CurrentIndex <= iDataPoint ||
               CurrentIndex > (MaxValues - pLine->lnValidValues + iDataPoint))
               {
               // this is the case when we start the new line in the middle
               // and data buffer has been wrap-around.
               ValidValue = TRUE ;
               }
            }

         // only export the data when we determine it is valid
         if (ValidValue)
            {
            TSPRINTF (UnicodeBuff,
               pLine->lnValues[CurrentIndex] > (FLOAT)999999.0 ?
               szLargeValueFormat : szSmallValueFormat,
               pLine->lnValues[CurrentIndex]) ;
            ConvertDecimalPoint (UnicodeBuff) ;
            ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
            }
         }
      strcat (TempBuff, pDelimiter) ;
      StringLen = strlen (TempBuff) ;

      if (++TimeToWriteFile > TIME_TO_WRITE)
         {
         // better write the buffers before they overflow.
         // there are better ways to check for overflow
         // but this is good enough

         if (!FileWrite (hFile, TempBuff, StringLen))
            {
            goto Exit0 ;
            }
         StringLen = TimeToWriteFile = 0 ;
         TempBuff[0] = '\0' ;
         }
      }

   if (StringLen)
      {
      // write the last block of data
      if (!FileWrite (hFile, TempBuff, StringLen))
         {
         goto Exit0 ;
         }
      }

   return (TRUE) ;

Exit0:
   return (FALSE) ;

}     // ExportLineValue

BOOL ExportCurrentChart (HANDLE hFile, PGRAPHSTRUCT pGraph)
{
   int            KnownValue,
                  MaxValues,
                  iValidValues,
                  iDataPoint ;
   BOOL           SimpleCase = FALSE ;
   int            iIndex ;

   MaxValues = pGraph->gMaxValues ;
   KnownValue = pGraph->gKnownValue ;
   iValidValues = pGraph->gTimeLine.iValidValues ;

   if (iValidValues < MaxValues)
      {
      // data have not wrapped around, so the oldest time
      // is started at 0.
      SimpleCase = TRUE ;
      iValidValues = (KnownValue % MaxValues) + 1 ;
      }

   iDataPoint = KnownValue % MaxValues ;

   if (!SimpleCase)
      {
      for (iIndex = iDataPoint+1 ; iIndex < MaxValues ; iIndex++)
         {
         if (!ExportLineValue (hFile, pGraph, iIndex, iDataPoint))
            {
            goto Exit0 ;
            }
         }
      }

   for (iIndex = 0 ; iIndex <= iDataPoint ; iIndex++)
      {
      if (!ExportLineValue (hFile, pGraph, iIndex, iDataPoint))
         {
         goto Exit0 ;
         }
      }

   return (TRUE) ;

Exit0:
   return (FALSE) ;

}  // ExportCurrentChart

void ExportChart (void)
{

   PGRAPHSTRUCT   pGraph ;
   HANDLE         hFile = 0 ;
   LPTSTR         pFileName = NULL ;
   INT            ErrCode = 0 ;

   if (!(pGraph = pGraphs))
      {
      return ;
      }

   // see if there is anything to export..
   if (!(pGraph->pLineFirst))
      {
      return ;
      }

   SetHourglassCursor() ;

   if (ErrCode = ExportFileOpen (hWndGraph, &hFile, pGraph->gInterval, &pFileName))
      {
      goto Exit0 ;
      }

   if (!pFileName)
      {
      // this is the case when user cancel.
      goto Exit0 ;
      }

   // export the column labels
   if (!ExportChartLabels (hFile, pGraph))
      {

      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   // export the lines
   if (PlayingBackLog())
      {
      if (!ExportLogChart (hFile, pGraph))
         {
         ErrCode = ERR_EXPORT_FILE ;
         goto Exit0 ;
         }
      }
   else
      {
      if (!ExportCurrentChart (hFile, pGraph))
         {
         ErrCode = ERR_EXPORT_FILE ;
         goto Exit0 ;
         }
      }
Exit0:

   SetArrowCursor() ;

   if (hFile)
      {
      CloseHandle (hFile) ;
      }

   if (pFileName)
      {
      if (ErrCode)
         {
         DlgErrorBox (hWndGraph, ErrCode, pFileName) ;
         }

      MemoryFree (pFileName) ;
      }

}     // ExportChart

typedef struct CHARTDATAPOINTSTRUCT
   {
   int         iLogIndex ;
   int         xDispDataPoint ;
   } CHARTDATAPOINT, *PCHARTDATAPOINT ;

void PlaybackChartDataPoint (PCHARTDATAPOINT pChartDataPoint)
   {  // PlaybackChartDataPoint
   int            iDisplayTics ;       // num visual points to display
   int            iDisplayTic ;
   int            iLogTic ;
   int            iLogTicsMove ;
   BOOL           bFirstTime = TRUE;
   int            iLogTicsRemaining ;
   int            numOfData, xDispDataPoint, rectWidth, xPos ;
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWndGraph) ;

   iLogTicsRemaining = PlaybackLog.iSelectedTics ;

   // we only have iDisplayTics-1 points since
   // we have to use the first two sample points to
   // get the first data points.
   if (iLogTicsRemaining <= pGraphs->gMaxValues)
      {
      iDisplayTics = iLogTicsRemaining ;
      }
   else
      {
      iDisplayTics = pGraphs->gMaxValues ;
      }

   iDisplayTic = -1 ;
   iLogTic = PlaybackLog.StartIndexPos.iPosition ;

   numOfData      = pGraph->gMaxValues - 1 ;
   rectWidth      = pGraph->rectData.right - pGraph->rectData.left ;
   xDispDataPoint = pGraph->rectData.left ;

   while (iDisplayTics && numOfData)
      {

      if (!bFirstTime)
         {
         iDisplayTic++ ;
         }
      else
         {
         bFirstTime = FALSE ;

         // get the second sample data to form the first data point
         iLogTic++ ;
         iLogTicsRemaining-- ;

         iDisplayTic++ ;
         }

      pChartDataPoint[iDisplayTic].iLogIndex = iLogTic ;
      pChartDataPoint[iDisplayTic].xDispDataPoint = xDispDataPoint ;

      // setup DDA to get the index of the next sample point
      iLogTicsMove = DDA_DISTRIBUTE (iLogTicsRemaining, iDisplayTics) ;
      iLogTicsRemaining -= iLogTicsMove ;
      iLogTic += iLogTicsMove ;

      xPos = DDA_DISTRIBUTE (rectWidth, numOfData) ;
      xDispDataPoint += xPos ;
      numOfData-- ;
      rectWidth -= xPos ;

      iDisplayTics-- ;

      }  // while

   }  // PlaybackChartDataPoint


GRAFDATA.H

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//
#if 0
PGRAPHSTRUCT GraphData (HWND hWndGraphDisplay) ;
#endif
#define GraphData(hWndGraphDisplay)       \
      (pGraphs)

FLOAT eUpdateAve(FLOAT eValue, PLINESTRUCT pLineStruct, INT iValidValues,
                 INT iDataPoint, INT iTotalValidPoints) ;

FLOAT eUpdateMin(FLOAT eValue, PLINESTRUCT pLineStruct, INT iValidValues,
                 INT iTotalValidPoints, INT iDataPoint, INT gMaxPoints) ;

FLOAT eUpdateMax(FLOAT eValue, PLINESTRUCT pLineStruct, INT iValidValues,
                 INT iTotalValidPoints) ;

PPERFSYSTEM InsertSystem(LPTSTR SysName) ;

BOOL ChartInsertLine (PGRAPHSTRUCT pGraph,
                      PLINE pLineNew) ;

VOID ChartDeleteLine (PGRAPHSTRUCT lgraph,
                      PLINESTRUCT pline) ;

void ResetGraph (PGRAPHSTRUCT pGraph) ;
void ResetGraphView (HWND hWndGraph) ;

void ClearGraphDisplay (PGRAPHSTRUCT pGraph) ;

BOOL InsertGraph(HWND hwnd) ;

void PlaybackChart (HWND hWndChart) ;

#if 0
PLINESTRUCT CurrentGraphLine (HWND hWndGraph) ;
#endif
#define CurrentGraphLine(hWndGraph)       \
   (LegendCurrentLine (hWndGraphLegend))

BOOL AddChart (HWND hWndParent) ;

BOOL EditChart (HWND hWndParent) ;

BOOL HandleGraphTimer (void) ;

VOID ClearGraphTimer(PGRAPHSTRUCT pGraph) ;

BOOL SaveChart (HWND hWndGraph, HANDLE hInputFile, BOOL bGetFileName) ;
BOOL OpenChart (HWND hWndGraph, HANDLE hFile,
         DWORD dwMajorVersion, DWORD dwMinorVersion, BOOL bChartFile) ;

void ExportChart (void) ;

VOID UpdateValueBarData (PGRAPHSTRUCT pGraph) ;

void GraphAddAction (void) ;


GRAFDISP.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdio.h>
#include "perfmon.h"
#include "grafdisp.h"      // external declarations for this file

#include "grafdata.h"      // for InsertGraph, et al.
#include "graph.h"
#include "legend.h"
#include "line.h"          // for LineCreatePen
#include "perfmops.h"      // for DoWindowDrag
#include "playback.h"      // for PlayingBackLog
#include "valuebar.h"
#include "utils.h"
#include "timeline.h"      // for IsTLineWindowUp & TLineRedraw
#include "counters.h"      // for CounterEntry

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

// this macro is used in doing a simple DDA (Digital Differential Analyzer)
// * 10 + 5 is to make the result round up with .5
#define DDA_DISTRIBUTE(TotalTics, numOfData) \
   ((TotalTics * 10 / numOfData) + 5) / 10

HDC   hGraphDisplayDC ;
//=============================//
// GraphDisplay Class          //
//=============================//

TCHAR   szGraphDisplayWindowClass[] = TEXT("PerfChart") ;
#define dwGraphDisplayClassStyle    (CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC)
#define iGraphDisplayClassExtra     (0)
#define iGraphDisplayWindowExtra    (0)
#define dwGraphDisplayWindowStyle   (WS_CHILD | WS_VISIBLE)

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//
BOOL UpdateTimeLine (HDC hDC, PGRAPHSTRUCT pGraph, BOOL getLastTimeLocation) ;

#if 0
PGRAPHSTRUCT GraphData (HWND hWndGraphDisplay)
/*
   Effect:        Return the graph associated with graph display window
                  hWndGraphData. At the present time, we only have one
                  graph data window and one graph structure. In the
                  future, we may have several of each. The graph structure
                  is conceptually instance data of the graph display
                  window. Use of this function allows for easier additions
                  to the code.
*/
   {
   return (pGraphs) ;
   }
#endif

INT ScaleAndInvertY (FLOAT ey,
                     PLINESTRUCT pLineStruct,
                     PGRAPHSTRUCT pGraph)
/*
   Effect:        Given data value ey, scale and fit the value to fit
                  within the graph data area of the window, considering
                  the scale set for the line and the current size of the
                  data rectangle.
*/
   {  // ScaleAndInvertY
   INT     yGraphDataHeight,               // Height of graph area
           yInverted ;                     // Scaled & Inverted Y.
   FLOAT   eppd,
           eyScaled ;

   // Take care of any scaling now, at output time.
   ey *= pLineStruct->eScale ;

   // Calculate the Cy of the graph area.
   yGraphDataHeight = pGraph->rectData.bottom - pGraph->rectData.top ;

   // Calculate the pixels per data point.
   eppd = (FLOAT) ((FLOAT) yGraphDataHeight / (FLOAT) pGraph->gOptions.iVertMax) ;
   eyScaled = eppd * ey ;
   yInverted = (INT) (((FLOAT) yGraphDataHeight) - eyScaled) ;

   yInverted += pGraph->rectData.top ;

   // Clamp the range to fit with in the graph portion of the windows
   yInverted = PinInclusive (yInverted,
                             pGraph->rectData.top, pGraph->rectData.bottom) ;
   return (yInverted) ;
   }  // ScaleAndInvertY

BOOL DrawGrid (HDC hDC,
               PGRAPHSTRUCT pGraph,
               LPRECT lpRect,
               BOOL bForPaint)
/*
   Effect:        Draw the grid lines in the graph display window.
                  These grid lines are in the graph data area only,
                  which is indicated by pGraph->rectData.

   Called By:     OnPaint only.
*/
   {  // DrawGrid
   int            iGrid, iLines ;
   int            xGrid, yGrid ;
   POINT          aPoints [4 * iGraphMaxTics] ;
   DWORD          aCounts [2 * iGraphMaxTics] ;
   HPEN           hPenPrevious ;
   int            bottomAdjust ;

   if (!pGraph->gOptions.bHorzGridChecked &&
       !pGraph->gOptions.bVertGridChecked)
      return (FALSE) ;

   hPenPrevious = SelectPen (hDC, IsPrinterDC (hDC) ?
      GetStockObject (BLACK_PEN) : pGraph->hGridPen) ;

   iLines = 0 ;

   if (pGraph->gOptions.bHorzGridChecked)
      {
      for (iGrid = 1 ;
           iGrid < pGraph->yNumTics ;
           iGrid++)
         {  // for
         yGrid = pGraph->ayTics[iGrid] + pGraph->rectData.top ;
         if (yGrid >= lpRect->top &&
             yGrid <= lpRect->bottom)
            {  // if
            aPoints[2 * iLines].x = lpRect->left ;
            aPoints[2 * iLines].y = yGrid ;
            aPoints[2 * iLines + 1].x = lpRect->right ;
            aPoints[2 * iLines + 1].y = yGrid ;

            aCounts[iLines] = 2 ;
            iLines++ ;
            }  // if
         }  // for
      }  // if

   if (pGraph->gOptions.bVertGridChecked)
      {
      bottomAdjust = lpRect->bottom + (bForPaint ? 1 : 0) ;
      for (iGrid = 1 ;
           iGrid < pGraph->xNumTics ;
           iGrid++)
         {  // for
         xGrid = pGraph->axTics[iGrid] + pGraph->rectData.left ;
         if (xGrid >= lpRect->left &&
             xGrid <= lpRect->right)
            {  // if
            aPoints[2 * iLines].x = xGrid ;
            aPoints[2 * iLines].y = lpRect->top ;
            aPoints[2 * iLines + 1].x = xGrid ;
            aPoints[2 * iLines + 1].y = bottomAdjust ;

            aCounts[iLines] = 2 ;
            iLines++ ;
            }  // if
         }  // for
      }  // if

   if (iLines)
      PolyPolyline (hDC, aPoints, aCounts, iLines) ;

   SelectPen (hDC, hPenPrevious) ;

   return (TRUE) ;
   }  // DrawGrid

BOOL DrawBarChartData (HDC hDC,
                       PGRAPHSTRUCT pGraph)
   {  // DrawBarChartData
   PLINESTRUCT pLineStruct ;
   PFLOAT      pDataPoints ;
   INT         nLegendItems,
               cx,
               cxBar,
               xDataPoint,
               y ;
   RECT        rectBar ;
   RECT        rectBkgrnd ;
   HBRUSH      hOldBrush ;
   FLOAT       eValue ;
   PLINESTRUCT pCurrentLine ;

   // Determine how many items are in the legend.

   nLegendItems = 0 ;

   for (pLineStruct = pGraph->pLineFirst ;
        pLineStruct ;
        pLineStruct = pLineStruct->pLineNext)
      {  // for
      nLegendItems++ ;
      }  // for

   if (nLegendItems == 0)
      return(FALSE) ;

   // get current select line for highlighting
   if (pGraph->HighLightOnOff)
      {
      pCurrentLine = CurrentGraphLine (hWndGraph) ;
      }
   else
      {
      pCurrentLine = NULL ;
      }

   // Determine the width of each bar.
   cx = pGraph->rectData.right - pGraph->rectData.left ;

   if (PlayingBackLog())
      {
      // get the average using the start and stop data point
      // from the log file
      PlaybackLines (pGraph->pSystemFirst,
                     pGraph->pLineFirst,
                     PlaybackLog.StartIndexPos.iPosition) ;
      PlaybackLines (pGraph->pSystemFirst,
                     pGraph->pLineFirst,
                     PlaybackLog.StopIndexPos.iPosition) ;
      }
   else
      {
      // Loop through all the DataLines and draw a bar for
      // it's last value.
      xDataPoint = pGraph->gKnownValue % pGraph->gMaxValues ;
      }

   rectBar.bottom = pGraph->rectData.bottom + 1 ;

   rectBkgrnd = pGraph->rectData ;

   hOldBrush = SelectBrush (hDC, hBrushFace) ;

   PatBlt (hDC,
           rectBkgrnd.left, rectBkgrnd.top,
           rectBkgrnd.right - rectBkgrnd.left,
           rectBkgrnd.bottom - rectBkgrnd.top + 1,
           PATCOPY) ;
   DrawGrid(hDC, pGraph, &(rectBkgrnd), FALSE) ;

   rectBar.right = pGraph->rectData.left ;
   for (pLineStruct = pGraph->pLineFirst ;
        pLineStruct ;
        pLineStruct = pLineStruct->pLineNext)
      {  // for
      pDataPoints = pLineStruct->lnValues ;

      if (PlayingBackLog())
         {
         eValue = CounterEntry (pLineStruct) ;
         }
      else
         {
         eValue = pDataPoints[xDataPoint] ;
         }

      y = ScaleAndInvertY (eValue,
                           pLineStruct,
                           pGraph) ;

      rectBar.left   = rectBar.right ;
      rectBar.top    = y ;

      // nomore line to draw
      if (nLegendItems == 0 )
         {
         break ;
         }

      cxBar = DDA_DISTRIBUTE (cx, nLegendItems) ;
      rectBar.right  = rectBar.left + cxBar ;

      // setup for next DDA
      nLegendItems-- ;
      cx -= cxBar ;

      // NOTE: this handle creation should be moved to line
      //       create time.

      if (pCurrentLine == pLineStruct)
         {
         SetBkColor (hDC, crWhite) ;
         }
      else
         {
         SetBkColor (hDC, pLineStruct->Visual.crColor) ;
         }
       ExtTextOut (hDC, rectBar.right, rectBar.top, ETO_OPAQUE,
         &rectBar, NULL, 0, NULL) ;
      }  // for

   return (TRUE) ;
   }

/***************************************************************************
 * DrawTLGraphData - Draw Time Line Graph Data.
 *
 *  Some notes about drawing the DataPoint graphs.
 *
 *      1]  It's real expensive to make a GDI call. So, we do not
 *          make a MoveToEx and LineTo call for each point.  Instead
 *          we create a polyline and send it down to GDI.
 *
 *      2]  The X coordinates for each point in the polyline is generated
 *          from our favorite xDataPoint to xWindows DDA.
 *
 *      3]  The Y coordinate is generated from the pLineStruct->lnValues[x]
 *          data associated with each line.
 ***************************************************************************/
BOOL DrawTLGraphData (HDC hDC,
                      BOOL bForPaint,
                      PRECT prctPaint,
                      PGRAPHSTRUCT pGraph)
/*
   Called By:     UpdateGraphDisplay only.
*/
   {
   PLINESTRUCT pLineStruct ;
   HPEN        hPen = 0 ;
   HPEN        hOldPen ;
   PFLOAT      pDataPoints ;
   INT         i, j,
               iValidValues,
               xDispDataPoint,
               xLeftLimit,
               xRightLimit ;
   PPOINT      pptDataPoints ;
   INT         numOfData, rectWidth, xPos ;
   PLINESTRUCT pCurrentLine ;
   INT         DrawZeroPoint = 0 ;

//   SetBkColor (hDC, crLightGray) ;

   if (!IsPrinterDC (hDC))
      {
      if (bForPaint)
         {
         IntersectClipRect (hDC,
                            pGraph->rectData.left,
                            pGraph->rectData.top,
                            pGraph->rectData.right,
                            pGraph->rectData.bottom + 1) ;
         }
      else
         {
         IntersectClipRect (hDC,
                            pGraph->rectData.left,
                            pGraph->rectData.top,
                            PlayingBackLog () ?
                              pGraph->rectData.right :
                              min (pGraph->rectData.right,
                                   pGraph->gTimeLine.xLastTime + 2),
                            pGraph->rectData.bottom + 1) ;
         }
      }

   xLeftLimit  = prctPaint->left  - pGraph->gTimeLine.ppd - 1 ;

   if (bForPaint)
      xRightLimit = prctPaint->right + pGraph->gTimeLine.ppd ;
   else
      xRightLimit = prctPaint->right ;

   pptDataPoints  = pGraph->pptDataPoints ;

   iValidValues   = pGraph->gTimeLine.iValidValues ;

   if (!PlayingBackLog() &&
      pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      // drawing the 0th at the end of the chart.
      DrawZeroPoint = 1 ;
      if (iValidValues == pGraph->gMaxValues)
         {
         iValidValues++ ;
         }
      }

   // get current select line for highlighting
   if (pGraph->HighLightOnOff)
      {
      pCurrentLine = CurrentGraphLine (hWndGraph) ;
      }
   else
      {
      pCurrentLine = NULL ;
      }

   // loop through lines to plot
   for (pLineStruct = pGraph->pLineFirst ;
        pLineStruct || pCurrentLine;
        pLineStruct = pLineStruct->pLineNext)
      {  // for

      if (pLineStruct == NULL)
         {
         // now draw the current line
         pLineStruct = pCurrentLine ;
         }
      else if (pLineStruct == pCurrentLine)
         {
         // skip this line and draw it later
         continue ;
         }

      // "Localize" some variables from the line data structure.
      pDataPoints    = pLineStruct->lnValues ;

      rectWidth      = pGraph->rectData.right - pGraph->rectData.left ;
      numOfData      = pGraph->gMaxValues - 1 + DrawZeroPoint ;

      // Generate the polyline data.
      xDispDataPoint = pGraph->rectData.left ;

      // Only process points that lie within the update region.
      // Also only process points that have valid data.
      j = 0 ;

      for (i = 0 ; i < iValidValues ; i++)
         {  // for
         if (xDispDataPoint > xRightLimit)
            {
            // we are done!
            break ;
            }
         if (xDispDataPoint >= xLeftLimit)
            {
            // It is within the limits, plot the point
            pptDataPoints[j].x = xDispDataPoint ;
            pptDataPoints[j].y = ScaleAndInvertY (
               (i == pGraph->gMaxValues) ? pDataPoints[0] : pDataPoints[i],
               pLineStruct,
               pGraph) ;
            j++ ;
            }  // if

         // setup for the next point
         if (!numOfData)
            {
            // no more points to go
            break ;
            }

         xPos = DDA_DISTRIBUTE (rectWidth, numOfData) ;
         xDispDataPoint += xPos ;
         numOfData-- ;
         rectWidth -= xPos ;
         }  // for i

      // only need to draw the line if there is point to draw.
      if (j > 0)
         {
         // Set the pen color and draw the polyline.
         if (IsPrinterDC (hDC))
            {
            hPen = LineCreatePen (hDC, &(pLineStruct->Visual), TRUE) ;
            hOldPen = SelectObject (hDC, hPen) ;
            }
         else
            {
            if (pCurrentLine == pLineStruct)
               {
               // highlight this line by turning it into White color
               hOldPen = SelectObject (hDC, hWhitePen) ;
               }
            else
               {
               SelectObject (hDC, pLineStruct->hPen) ;
               }
            }

         Polyline(hDC, pptDataPoints, j) ;

         if (hPen)
            {
            SelectObject (hDC, hOldPen) ;

            if (hPen != hWhitePen)
               {
               DeletePen (hPen) ;
               }
            hPen = 0 ;
            }
         }

      if (pCurrentLine == pLineStruct)
         {
         // We are done...
         break ;
         }
      }  // for pLine

   if (IsTLineWindowUp())
      {
      // re-draw the timelines if need
      TLineRedraw (hDC, pGraph) ;
      }

   // reset the clipping region
   SelectClipRgn (hDC, pGraph->hGraphRgn) ;

   return (TRUE) ;
   }  // DrawTLGraphData

/***************************************************************************
 * bInitTimeLine - Initialize the fields of the time line structure.
 ***************************************************************************/
BOOL bInitTimeLine(PGRAPHSTRUCT pGraph)
{

        pGraph->gTimeLine.xLastTime      = 0 ;
        pGraph->gTimeLine.ppd            = 0 ;
        pGraph->gTimeLine.rppd           = 0 ;
        pGraph->gTimeLine.iValidValues   = 1 ;

        return (TRUE) ;

}

/***************************************************************************
 * Scale Time Line
 *
 *  This routine should be called from the WM_SIZE message.
 *  It does the scaling from the number of data points to the
 *  size of the window.
 ***************************************************************************/
void ScaleTimeLine (PGRAPHSTRUCT pGraph)
   {
   INT     nDataPoints,
           cxClient ;

   // Calculate the pels per data point.
   nDataPoints = pGraph->gMaxValues - 1 ;
   cxClient    = pGraph->rectData.right - pGraph->rectData.left ;

   // ppd  = Pixels per DataPoint.
   // rppd = Remaining Pixels per DataPoint.
   pGraph->gTimeLine.ppd  = cxClient / nDataPoints ;
   pGraph->gTimeLine.rppd = cxClient % nDataPoints ;
   }

void DisplayTimeLine(HDC hDC, PGRAPHSTRUCT pGraph)
/*
   Called By:     OnPaint only.

   Assert:        xDisplayPoint has been set by UpdateTimeLine on this
                  same timer tick.
*/
   {  // DisplayTimeLine
   INT     xDisplayPoint ;
   RECT    rect ;

   if (pGraph->gTimeLine.xLastTime == -1)
      {
      UpdateTimeLine (hGraphDisplayDC, pGraph, TRUE) ;
      }

   // xDisplayPoint is X coordinate to display the time line at.
   if ((xDisplayPoint = pGraph->gTimeLine.xLastTime) == 0)
      return ;

   SelectBrush (hDC, pGraph->hbRed) ;

   if (xDisplayPoint >= pGraph->rectData.right)
      {
      rect.left   = pGraph->rectData.left ;
      }
   else
      {
//      rect.left   = xDisplayPoint++ ;
      rect.left   = xDisplayPoint ;
      }
   rect.top    = pGraph->rectData.top ;
   rect.right  = rect.left + 2 ;
   rect.bottom = pGraph->rectData.bottom ;

//   IntersectRect (&rect, &rect, &pGraph->rectData) ;
   if (rect.right > pGraph->rectData.right)
      {
      rect.right = pGraph->rectData.right ;
      }
   PatBlt (hDC,
           rect.left, rect.top,
           rect.right - rect.left,
           rect.bottom - rect.top + 1 ,
           PATCOPY) ;

   }  // DisplayTimeLine

int SuggestedNumTics (int iRange)
/*
   Effect:        Return an appropriate number of tic marks to display
                  within iRange pixels.

                  These numbers are empirically chosen for pleasing
                  results.
*/
   {  // SuggestedNumTics
   if (iRange < 20)
      return (0) ;

   if (iRange < 50)
      return (2) ;

   if (iRange < 100)
      return (4) ;

   if (iRange < 150)
      return (5) ;

   if (iRange < 300)
      return (10) ;

   if (iRange < 500)
      return (20) ;

   return (25) ;
   }  // SuggestedNumTics

void SetGridPositions (PGRAPHSTRUCT pGraph)
   {  // SetGridPositions
   int            xDataWidth ;
   int            yDataHeight ;

   int            iCurrentTicPixels ;
   int            iNumTics ;

   int            i ;

   //=============================//
   // Set number of Tics          //
   //=============================//

   xDataWidth = pGraph->rectData.right - pGraph->rectData.left ;
   yDataHeight = pGraph->rectData.bottom - pGraph->rectData.top ;

   pGraph->xNumTics = PinInclusive (SuggestedNumTics (xDataWidth),
                                    0, iGraphMaxTics) ;
   pGraph->yNumTics = PinInclusive (SuggestedNumTics (yDataHeight),
                                    0, iGraphMaxTics) ;

   // if we have more tics than possible integral values, reduce the number
   // of tics.
   if (pGraph->gOptions.iVertMax < pGraph->yNumTics)
      pGraph->yNumTics = pGraph->gOptions.iVertMax ;

   //=============================//
   // Set X Tic Positions         //
   //=============================//

   if (pGraph->xNumTics)
      {
      iNumTics = pGraph->xNumTics ;

      pGraph->axTics[0] = 0 ;
      for (i = 1 ;
           i < pGraph->xNumTics ;
           i++)
         {  // for
         if (iNumTics == 0)
            {
            break ;
            }
         iCurrentTicPixels = DDA_DISTRIBUTE (xDataWidth, iNumTics) ;
         pGraph->axTics [i] = pGraph->axTics [i - 1] + iCurrentTicPixels ;

         xDataWidth -= iCurrentTicPixels ;
         iNumTics-- ;
         }  // for
      }  // if

   //=============================//
   // Set Y Tic Positions         //
   //=============================//

   if (pGraph->yNumTics)
      {
      iNumTics = pGraph->yNumTics ;

      pGraph->ayTics[0] = 0 ;
      for (i = 1 ;
           i < pGraph->yNumTics ;
           i++)
         {  // for
         if (iNumTics == 0)
            {
            break ;
            }
         iCurrentTicPixels = DDA_DISTRIBUTE (yDataHeight, iNumTics) ;
         pGraph->ayTics [i] = pGraph->ayTics [i- 1] + iCurrentTicPixels ;

         yDataHeight -= iCurrentTicPixels ;
         iNumTics-- ;
         }  // for
      }  // if
   }  // SetGridPositions

int GraphVerticalScaleWidth (HDC hDC,
                             PGRAPHSTRUCT pGraph)
   {
   TCHAR          szMaxValue [20] ;
   int            xWidth ;

   if (!pGraph->gOptions.bLabelsChecked)
      return (0) ;

//   SelectFont (hDC, IsPrinterDC (hDC) ? hFontPrinterScales : hFontScales) ;

   TSPRINTF (szMaxValue, TEXT(" %1d "),
            pGraph->gOptions.iVertMax * 10 ) ;

   xWidth = TextWidth (hDC, szMaxValue) ;

   return (xWidth) ;
   }

void DrawGraphScale (HDC hDC,
                     PGRAPHSTRUCT pGraph)
   {
   TCHAR   szScale [20] ;

   INT     len,
           i,
           nLines,
           iUnitsPerLine ;
   FLOAT   ePercentOfTotal  ;
   FLOAT   eDiff ;
   BOOL    bUseFloatingPt = FALSE ;

   //=============================//
   // Draw Vertical Scale?        //
   //=============================//

   if (!pGraph->gOptions.bLabelsChecked)
       return ;

   // Get the number of lines.
   nLines = pGraph->yNumTics ;

   // nLines may be zero if the screen size if getting too small
   if (nLines > 0)
      {
      // Calculate what percentage of the total each line represents.
      ePercentOfTotal = ((FLOAT) 1.0) / ((FLOAT) nLines)  ;

      // Calculate the amount (number of units) of the Vertical max each
      // each line in the graph represents.
      iUnitsPerLine = (INT) ((FLOAT) pGraph->gOptions.iVertMax * ePercentOfTotal) ;
      ePercentOfTotal *= (FLOAT) pGraph->gOptions.iVertMax ;
      eDiff = (FLOAT)iUnitsPerLine - ePercentOfTotal ;
      if (eDiff < (FLOAT) 0.0)
         eDiff = -eDiff ;

      if (eDiff > (FLOAT) 0.1)
         bUseFloatingPt = TRUE ;
      }

   //=============================//
   // Set Drawing Attributes      //
   //=============================//

//   SelectFont (hDC, IsPrinterDC (hDC) ? hFontPrinterScales : hFontScales) ;
   SetBkMode(hDC, TRANSPARENT) ;
   SetTextAlign (hDC, TA_TOP | TA_RIGHT) ;
   SelectObject(hDC, GetStockObject (BLACK_PEN)) ;

   // Set the background color to gray
   if (!IsPrinterDC (hDC))
      FillRect (hDC, &(pGraph->rectVertScale), hbLightGray) ;

// TESTING TESTING
#ifdef   PERFMON_DEBUG
GdiSetBatchLimit(1) ;   // disable Batching
#endif

   // Now Output each string.
   for (i = 0 ;
        i < nLines ;
        i++)
      {  // for
      if (bUseFloatingPt)
         {
         len = TSPRINTF (szScale, TEXT("%1.1f"),
            (FLOAT)pGraph->gOptions.iVertMax - ((FLOAT)i *
             ePercentOfTotal)) ;
         ConvertDecimalPoint (szScale) ;
         }
      else
         {
         len = TSPRINTF (szScale, TEXT("%d"),
                         pGraph->gOptions.iVertMax - (i * iUnitsPerLine)) ;
         }
      TextOut (hDC,
               pGraph->rectVertScale.right,
               pGraph->ayTics[i] +
               pGraph->rectData.top - HalfTextHeight,
               szScale,
               len) ;
      }  // for

   // Output the "min value" separately.
   TextOut (hDC,
            pGraph->rectVertScale.right,
            pGraph->rectData.bottom - HalfTextHeight,
            TEXT("0"),
            1) ;

#ifdef   PERFMON_DEBUG
// TESTING TESTING
GdiSetBatchLimit(0) ;   // enable default Batching
#endif

   }  // DrawGraphScale

void SizeGraphDisplayComponentsRect (HDC hDC,
                                     PGRAPHSTRUCT pGraph,
                                     RECT rectDisplay)
   {  // SizeGraphDisplayComponentsRect
   int            xScaleWidth ;

   if (!rectDisplay.right || !rectDisplay.bottom)
      return ;

   //=============================//
   // Size the Vertical Scale     //
   //=============================//

   xScaleWidth = GraphVerticalScaleWidth (hDC, pGraph) ;
   pGraph->rectVertScale.left = rectDisplay.left ;
   pGraph->rectVertScale.top = rectDisplay.top ;
   pGraph->rectVertScale.right = rectDisplay.left + xScaleWidth ;
   pGraph->rectVertScale.bottom = rectDisplay.bottom ;

   //=============================//
   // Size the Horizontal Scale   //
   //=============================//

   pGraph->rectHorzScale.left = 0 ;
   pGraph->rectHorzScale.top = 0 ;
   pGraph->rectHorzScale.right = 0 ;
   pGraph->rectHorzScale.bottom = 0 ;

   //=============================//
   // Size the Data Area          //
   //=============================//

   pGraph->rectData.left = pGraph->rectVertScale.right + 3 + ThreeDPad ;
   pGraph->rectData.right = rectDisplay.right - 5 - ThreeDPad ;
   pGraph->rectData.top = rectDisplay.top + 5 + ThreeDPad ;
   pGraph->rectData.bottom = rectDisplay.bottom - 5 - ThreeDPad ;

   SetGridPositions (pGraph) ;
   ScaleTimeLine (pGraph) ;

   //==========================================//
   // Invalidate the last time line poisition  //
   //==========================================//
   pGraph->gTimeLine.xLastTime = -1 ;

   if (pGraph->hGraphRgn)
      {
      DeleteObject (pGraph->hGraphRgn) ;
      }

   pGraph->hGraphRgn = CreateRectRgn (rectDisplay.left,
      rectDisplay.top,
      rectDisplay.right,
      rectDisplay.bottom) ;

   SelectClipRgn (hDC, pGraph->hGraphRgn) ;

   }  // SizeGraphDisplayComponentsRect

void SizeGraphDisplayComponents (HWND hWnd)
/*
   Effect:        Given the graph display window hWnd, of size
                  (xWidth x yHeight), determine the size and position
                  of the various graph display components: the vertical
                  scale, the horizontal scale, and the data area.

   Called By:     OnSize, any other routine that changes the visibility
                  of a vertical or horizontal scale.

   Note:          This function has multiple return points.
*/
   {  // SizeGraphDisplayComponents
   PGRAPHSTRUCT   pGraph ;
   RECT           rectClient ;

   pGraph = GraphData (hWnd) ;
   GetClientRect (hWnd, &rectClient) ;

   SizeGraphDisplayComponentsRect (hGraphDisplayDC, pGraph, rectClient) ;
   }

void UpdateGraphDisplay (HDC hDC,
                         BOOL bForPaint,
                         LPRECT lpRect,
                         PGRAPHSTRUCT pGraph)
/*
   Effect:        Draw the portions of the graph that change as the
                  graph's values change. This includes the background,
                  the grid, the lines, and the timeline.
*/
   {  // UpdateGraphDisplay
   RECT           rectUpdate ;

   if (!bForPaint && !IsPrinterDC (hDC) &&
       pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      HBRUSH         hOldBrush ;

      rectUpdate = pGraph->rectData ;
      rectUpdate.bottom += 1 ;

      IntersectRect (&rectUpdate, lpRect, &rectUpdate) ;
      hOldBrush = SelectBrush (hDC, hBrushFace) ;

      PatBlt (hDC,
              rectUpdate.left, rectUpdate.top,
              rectUpdate.right - rectUpdate.left,
              rectUpdate.bottom - rectUpdate.top,
              PATCOPY) ;
      }
   else
      {
      IntersectRect (&rectUpdate, lpRect, &pGraph->rectData) ;
      }

   if (pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      DrawGrid(hDC, pGraph, &rectUpdate, bForPaint) ;
      if (pGraph->pLineFirst != NULL)
          {
          DrawTLGraphData(hDC, bForPaint, &rectUpdate, pGraph) ;
          if (!PlayingBackLog ())
             DisplayTimeLine(hDC, pGraph) ;
          }
      }
   else
      {
      DrawBarChartData (hDC, pGraph) ;
      }
   }  // UpdateGraphDisplay

BOOL UpdateTimeLine (HDC hDC,
                     PGRAPHSTRUCT pGraph,

BOOL getLastTimeLocation)
/*
   Called By:     GraphTimer only.

   See Also:      UpdateGraphDisplay.
*/
   {
   INT     i,
           xDisplayPoint,
           xDataPoint ;
   RECT    rctUpdate ;
   INT     xLastTime ;
   INT     rectWidth,
           xPos,
           numOfPoints ;

   if ((xLastTime = pGraph->gTimeLine.xLastTime) != 0)
      {
      if ((pGraph->gKnownValue % pGraph->gMaxValues) == 1)
         {
         // Data wrap around case
         rctUpdate.left   = pGraph->rectData.left ;
         rctUpdate.right  = pGraph->rectData.left +
             pGraph->gTimeLine.ppd + 1 ;
         }
      else
         {
         rctUpdate.left   = xLastTime - pGraph->gTimeLine.ppd ;
         rctUpdate.right  = xLastTime +
             pGraph->gTimeLine.ppd + 1 ;
         }
      rctUpdate.top    = pGraph->rectData.top ;
      rctUpdate.bottom = pGraph->rectData.bottom + 1 ;
      }

   // Calculate where to draw the time line.
   // This is done by running a simple DDA (Digital Differential Analyzer)
   // We have to position the time depending upon the size of the
   // graph window.  In essence we need to calculate the x display
   // coordinate.

   // Note we should wrap Known Value in UpdateGLData.
   // We should also use a data buffer of 256 bytes so we can
   // wrap with and AND.

   // xDataPoint =  pGraph->gKnownValue ;
   xDataPoint =  pGraph->gKnownValue % pGraph->gMaxValues ;

   xDisplayPoint = pGraph->rectData.left ;

   numOfPoints = pGraph->gMaxValues - 1 ;

   if (!PlayingBackLog() &&
      pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      // drawing the 0th at the end of the chart.
      // So, we do have gMaxValues points
      numOfPoints++ ;
      if ((pGraph->gKnownValue % pGraph->gMaxValues) == 0)
         {
         xDataPoint = pGraph->gMaxValues ;
         }
      }

   rectWidth = pGraph->rectData.right - pGraph->rectData.left ;

   for (i = 0 ; i < xDataPoint ; i++)
      {
      if (numOfPoints == 0)
         {
         break ;
         }
      xPos = DDA_DISTRIBUTE (rectWidth, numOfPoints) ;
      xDisplayPoint += xPos ;
      rectWidth -= xPos ;
      numOfPoints-- ;
      }  // for

   pGraph->gTimeLine.xLastTime = xDisplayPoint ;

   if (!getLastTimeLocation && iPerfmonView == IDM_VIEWCHART && !bPerfmonIconic)
      {
      UpdateGraphDisplay (hDC, FALSE, &rctUpdate, pGraph) ;
      }

   return(TRUE) ;
   }

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void /*static*/ OnCreate (HWND hWnd)
/*
   Effect:        Perform all actions needed when a GraphDisplay window is
                  created.
                  In particular, initialize the graph instance data and
                  create the child windows.

   Called By:     GraphDisplayWndProc, in response to a WM_CREATE message.
*/
   {  // OnCreate
   LOGBRUSH       LogBrush ;
   TEXTMETRIC     tmScales ;

   hGraphDisplayDC = GetDC(hWnd) ;

   SelectFont(hGraphDisplayDC, hFontScales) ;
   GetTextMetrics(hGraphDisplayDC, &tmScales) ;
   HalfTextHeight = tmScales.tmHeight / 2 ;

   SetBkColor (hGraphDisplayDC, crLightGray) ;

   InsertGraph(hWnd) ;
   bInitTimeLine(pGraphs) ;

   pGraphs->hWnd = hWnd ;

   // Create the brush and pen used by the time line.
   // We don't want to create these on every timer tick.

   LogBrush.lbStyle = BS_SOLID ;
   LogBrush.lbColor = RGB(0xff, 0, 0) ;
   LogBrush.lbHatch = 0 ;

   // Now get the system resources we use "all the time"
   pGraphs->hbRed = CreateBrushIndirect(&LogBrush) ;
   pGraphs->hGridPen = CreatePen (PS_SOLID, 1, crGray) ;

   pGraphs->xNumTics = 0 ;
   pGraphs->yNumTics = 0 ;
   }  // OnCreate

void /*static*/ OnSize (HWND hWnd,
                    WORD xWidth,
                    WORD yHeight)
   {  // OnSize
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWnd) ;

   SizeGraphDisplayComponents (hWnd) ;
   }  // OnSize

void /*static*/ OnPaint (HWND hWnd)
   {
   HDC            hDC ;
   PAINTSTRUCT    ps ;
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWnd) ;
   hDC = BeginPaint(hWnd, &ps) ;

   DrawGraphDisplay (hDC, ps.rcPaint, pGraph) ;

   EndPaint(hWnd, &ps) ;
   }  // OnPaint

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

#ifdef KEEP_PRINT
void PrintGraphDisplay (HDC hDC,
                        PGRAPHSTRUCT pGraph)
   {
   DrawGraphScale (hDC, pGraph) ;

//!!   UpdateGraphDisplay (hDC, TRUE, &(pGraph->rectData), pGraph) ;

   IntersectClipRect (hDC, 0, 0, 10000, 10000) ;
   SelectBrush (hDC, GetStockObject (HOLLOW_BRUSH)) ;
   SelectPen (hDC, GetStockObject (BLACK_PEN)) ;
   Rectangle (hDC,
              pGraph->rectData.left,
              pGraph->rectData.top,
              pGraph->rectData.right,
              pGraph->rectData.bottom) ;
   }  // PrintGraphDisplay
#endif

void DrawGraphDisplay (HDC hDC,
                       RECT rectDraw,
                       PGRAPHSTRUCT pGraph)
   {
   BOOL        bPaintScale ;
   INT         LocalThreeDPad = ThreeDPad - 1 ;

   // Only draw the vertical labels if the paint rectangle
   // any portion of the window to the left of the graph area.

   bPaintScale = (rectDraw.left <= pGraph->rectVertScale.right) ;
   if (bPaintScale)
      DrawGraphScale (hDC, pGraph) ;
   if (IsPrinterDC (hDC))
      Rectangle (hDC,
                 pGraph->rectData.left,
                 pGraph->rectData.top,
                 pGraph->rectData.right,
                 pGraph->rectData.bottom) ;
   else
      ThreeDConcave1 (hDC,
                     pGraph->rectData.left - LocalThreeDPad,
                     pGraph->rectData.top - LocalThreeDPad,
                     pGraph->rectData.right + LocalThreeDPad,
                     pGraph->rectData.bottom + LocalThreeDPad + 1) ;

   UpdateGraphDisplay (hDC, TRUE, &(rectDraw), pGraph) ;
   }  // DrawGraphDisplay

LRESULT APIENTRY GraphDisplayWndProc (HWND hWnd,
                                      UINT uMsg,
                                      WPARAM wParam,
                                      LPARAM lParam)
   {  // GraphDisplayWndProc
   LONG        lret = 0L ;
   BOOL        bCallDefProc = FALSE ;

   switch (LOWORD (uMsg))
      {
      case WM_LBUTTONDOWN:
         DoWindowDrag (hWnd, lParam) ;
         break ;

      case WM_LBUTTONDBLCLK:
         SendMessage (hWndMain, uMsg, wParam, lParam) ;
         break ;

      case WM_CREATE:
         OnCreate (hWnd) ;
         break ;

      case WM_SIZE:
         OnSize (hWnd, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      case WM_DESTROY:
         KillTimer(hWndMain, GRAPH_TIMER_ID) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_TIMER:
         GraphTimer (hWnd, FALSE) ;
         break ;

      default:
         bCallDefProc = TRUE ;
         break ;
      }  // switch

   if (bCallDefProc)
      {
      lret = DefWindowProc(hWnd, uMsg, wParam, lParam) ;
      }
   return (lret) ;
   }

BOOL GraphDisplayInitializeApplication (void)
   {  // GraphDisplayInitializeApplication
   WNDCLASS       wc ;

   wc.style         = dwGraphDisplayClassStyle ;
   wc.lpfnWndProc   = (WNDPROC) GraphDisplayWndProc ;
   wc.hInstance     = hInstance ;
   wc.cbClsExtra    = iGraphDisplayWindowExtra ;
   wc.cbWndExtra    = iGraphDisplayClassExtra ;
   wc.hIcon         = NULL ;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
   wc.hbrBackground = hbLightGray ;
   wc.lpszMenuName  = NULL ;
   wc.lpszClassName = (LPTSTR) szGraphDisplayWindowClass ;

   return (RegisterClass (&wc)) ;
   }  // GraphDisplayInitializeApplication

HWND CreateGraphDisplayWindow (HWND hWndGraph)
   {
   return (CreateWindow (szGraphDisplayWindowClass,   // class
                         NULL,                        // caption
                         dwGraphDisplayWindowStyle,   // window style
                         0, 0,                        // position
                         0, 0,                        // size
                         hWndGraph,                   // parent window
                         NULL,                        // menu
                         hInstance,                  // program instance
                         NULL)) ;                     // user-supplied data
   }

BOOL ToggleGraphRefresh (HWND hWnd)
   {  // ToggleGraphRefresh
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWnd) ;

   if (pGraph->bManualRefresh)
      SetGraphTimer (pGraph) ;
   else
      ClearGraphTimer (pGraph) ;

   pGraph->bManualRefresh = !pGraph->bManualRefresh ;
   return (pGraph->bManualRefresh) ;
   }  // ToggleGraphRefresh

BOOL GraphRefresh (HWND hWnd)
   {  // GraphRefresh
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWnd) ;

   return (pGraph->bManualRefresh) ;
   }  // GraphRefresh

void GraphTimer (HWND hWnd,
                 BOOL bForce)
/*
   Effect:        Handle any actions necessary when the graph display
                  window hWnd receives a timer tick. In particular,
                  update the graph display and update the status bar
                  if it is showing.

   Called By:     GraphDisplayWndProc, in response to a WM_TIMER message.
*/
   {  // GraphTimer
   PGRAPHSTRUCT         pGraph ;

   pGraph = GraphData (hWnd) ;

   if (!pGraph->pLineFirst)
      return ;

   if (bForce || !pGraph->bManualRefresh)
      {

      HandleGraphTimer () ;

      // If we are displaying a time-line graph then do the
      // calculations for the minimal update region.
      // If were doing a bar graph, then draw the
      // whole graph area.

      if (pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
         UpdateTimeLine (hGraphDisplayDC, pGraph, FALSE) ;
      else
         DrawBarChartData (hGraphDisplayDC, pGraph) ;

      // Take care of the status bar window
      StatusTimer (hWndGraphStatus, FALSE) ;

      }  // if
   }  // GraphTimer

// this routine set/reset the line highlight mode
void ChartHighlight (void)
   {
   PGRAPHSTRUCT         pGraph ;

   if (pGraph = GraphData (hWndGraph))
      {
      if (pGraph->pLineFirst)
         {
         // toggle the HightlightOnOff mode
         pGraph->HighLightOnOff = !pGraph->HighLightOnOff ;
         WindowInvalidate (hWndGraphDisplay) ;
         }
      else
         {
         // no line availble, just turn it off
         pGraph->HighLightOnOff = FALSE ;
         }
      }
   }  // ChartHighlight


GRAFDISP.H

BOOL GraphDisplayInitializeApplication (void) ;

HWND CreateGraphDisplayWindow (HWND hWndGraph) ;

void SizeGraphDisplayComponentsRect (HDC hDC,
                                     PGRAPHSTRUCT pGraph,
                                     RECT rectDisplay) ;

void SizeGraphDisplayComponents (HWND hWnd) ;

BOOL GraphRefresh (HWND hWnd) ;
BOOL ToggleGraphRefresh (HWND hWnd) ;

void GraphTimer (HWND hWnd,
                 BOOL bForce) ;

void DrawGraphDisplay (HDC hDC,
                       RECT rectDraw,
                       PGRAPHSTRUCT pGraph) ;

void PrintGraphDisplay (HDC hDC,
                        PGRAPHSTRUCT pGraph) ;

void ChartHighlight (void) ;


GRAPH.C

/*****************************************************************************
 *
 *  Graph.c - This module handles the graphing window.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *
 ****************************************************************************/

/*
   File Contents:

      This file contains the code for creating and manipulating the graph
      window. This window is a child of hWndMain and represents one of the
      three "views" of the program. The other views are log and alert.

      The graph window is actually just a container window, whose surface
      is completely filled by its children: hWndGraphDisplay, ,
      hWndGraphLegend, and hWndGraphStatus. Therefore much of this file is
      merely calls to the appropriate functions for these children.

      The graph window is responsible for the graph structure itself,
      however. Conceptually this should be instance data for the graph
      window. Right now, however, there is only one window and only one
      graph structure. Nevertheless, we go through the GraphData(hWnd)
      function to get a pointer to the graph structure for the graph window.

*/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"
#include "graph.h"
#include "grafdisp.h"
#include "legend.h"
#include "valuebar.h"
#include "utils.h"   // for WindowShow

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

//=============================//
// Graph Class                 //
//=============================//

TCHAR   szGraphWindowClass[] = TEXT("PerfGraph") ;
#define dwGraphClassStyle           (CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS)
#define iGraphClassExtra            (0)
#define iGraphWindowExtra           (0)
#define dwGraphWindowStyle          (WS_CHILD)

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void static OnCreate (HWND hWnd)
   {  // OnCreate
   hWndGraphDisplay = CreateGraphDisplayWindow (hWnd) ;
   hWndGraphLegend = CreateGraphLegendWindow (hWnd) ;
   hWndGraphStatus = CreateGraphStatusWindow (hWnd) ;
   }  // OnCreate

void static OnPaint (HWND hWnd)
   {
   HDC            hDC ;
   PAINTSTRUCT    ps ;

   hDC = BeginPaint (hWnd, &ps) ;
   EndPaint (hWnd, &ps) ;
   }

void static OnSize (HWND hWnd, int xWidth, int yHeight)
   {  // OnSize
   SizeGraphComponents (hWnd) ;
   }  // OnSize

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

LRESULT APIENTRY GraphWndProc (HWND hWnd,
                               WORD wMsg,
                               DWORD wParam,
                               LONG lParam)
   {  // GraphWndProc
   BOOL           bCallDefProc ;
   LRESULT        lReturnValue ;

   bCallDefProc = FALSE ;
   lReturnValue = 0L ;

   switch (wMsg)
      {  // switch
      case WM_CREATE:
         OnCreate (hWnd) ;
         break ;

      case WM_LBUTTONDBLCLK:
         SendMessage (hWndMain, WM_LBUTTONDBLCLK, wParam, lParam) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_SIZE:
         OnSize (hWnd, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      default:
         bCallDefProc = TRUE ;
      }  // switch

   if (bCallDefProc)
      lReturnValue = DefWindowProc (hWnd, wMsg, wParam, lParam) ;

   return (lReturnValue);
   }  // GraphWndProc

BOOL GraphInitializeApplication (void)
/*
   Note:          There is no background brush set for the MainWindow
                  class so that the main window is never erased. The
                  client area of MainWindow is always covered by one
                  of the view windows. If we erase it, it would just
                  flicker needlessly.
*/
   {  // GraphInitializeApplication
   BOOL           bSuccess ;
   WNDCLASS       wc ;

   //=============================//
   // Register GraphWindow class  //
   //=============================//

   wc.style         = dwGraphClassStyle ;
   wc.lpfnWndProc   = (WNDPROC) GraphWndProc ;
   wc.hInstance     = hInstance ;
   wc.cbClsExtra    = iGraphWindowExtra ;
   wc.cbWndExtra    = iGraphClassExtra ;
   wc.hIcon         = NULL ;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
   wc.hbrBackground = NULL ;                          // see note above
   wc.lpszMenuName  = NULL ;
   wc.lpszClassName = szGraphWindowClass ;

   bSuccess = RegisterClass (&wc) ;

   //=============================//
   // Register Child classes      //
   //=============================//

   if (bSuccess)
      bSuccess = GraphDisplayInitializeApplication () ;

   if (bSuccess)
      bSuccess = GraphLegendInitializeApplication () ;

   if (bSuccess)
      bSuccess = GraphStatusInitializeApplication () ;

   return (bSuccess) ;
   }  // GraphInitializeApplication

HWND CreateGraphWindow (HWND hWndParent)
/*
   Effect:        Create the graph window. This window is a child of
                  hWndMain and is a container for the graph data,
                  graph label, graph legend, and graph status windows.

   Note:          We dont worry about the size here, as this window
                  will be resized whenever the main window is resized.

*/
   {
   return (CreateWindow (szGraphWindowClass,       // window class
                         NULL,                     // caption
                         dwGraphWindowStyle,       // style for window
                         0, 0,                     // initial position
                         0, 0,                     // initial size
                         hWndParent,               // parent
                         NULL,                     // menu
                         hInstance,               // program instance
                         NULL)) ;                  // user-supplied data
   }

void SizeGraphComponents (HWND hWnd)
/*
   Effect:        Move and show the various components of the graph to
                  fill the size (xWidth x yHeight). Take into account
                  whether the user wants to show the legend or status
                  bars. Also take into account if we have room for these
                  items.

   Internals:     If the user doesn't want the status or legend windows,
                  they aren't shown. Also, if the user wanted the status
                  window but not the legend window, the status window is
                  not shown.

                  We may disregard the user's desire for the legend or
                  status bar if there is not room. In particular, a legend
                  window has a minimum width (LegendMinWidth ()) and a
                  minimum height (LegendMinHeight ()). These values are
                  fixed for a given session of perfmon. It also has a
                  preferred height, which takes into consideration the
                  size of the graph window and the number of items in
                  the legend. This value is returned by LegendHeight().

                  We don't show the legend if its minimum height would
                  take up more than half the graph height.

                  If we feel we don't have room for the legend, we don't
                  show the status window either.

   See Also:      LegendMinWidth, LegendMinHeight, LegendHeight,
                  ValuebarHeight.

   Called By:     OnSize, any other function that may remove or add one
                  of the graph components.
*/
   {  // SizeGraphComponents
   RECT           rectClient ;
   BOOL           bShowLegend ;
   BOOL           bShowStatus ;
   int            yStatusHeight ;
   int            yLegendHeight ;
   int            xWidth ;
   int            yHeight ;

   GetClientRect (hWnd, &rectClient) ;
   xWidth = rectClient.right - rectClient.left ;
   yHeight = rectClient.bottom - rectClient.top ;

   // if the graph window has no size, neither will its children.
   if (!xWidth || !yHeight)
      return ;

   //=============================//
   // Show the Legend Window?     //
   //=============================//

   if (!pGraphs->gOptions.bLegendChecked)
      bShowLegend = FALSE ;
   else if (xWidth < LegendMinWidth (hWndGraphLegend))
      bShowLegend = FALSE ;
   else if (yHeight < 5 * LegendMinHeight (hWndGraphLegend))
      bShowLegend = FALSE ;
   else
      bShowLegend = TRUE ;

   //=============================//
   // Show the Status Window?     //
   //=============================//

   if (!pGraphs->gOptions.bStatusBarChecked)
      bShowStatus = FALSE ;
   else if (!bShowLegend)
      bShowStatus = FALSE ;
   else
      bShowStatus = TRUE ;

   yStatusHeight = bShowStatus ?
                     ValuebarHeight (hWndGraphStatus) : 0 ;
   yLegendHeight = bShowLegend ?
                     LegendHeight (hWndGraphLegend, yHeight) : 0 ;

   //=============================//
   // Update the status window    //
   //=============================//

   if (bShowStatus)
      MoveWindow (hWndGraphStatus,
                  0, yHeight - yStatusHeight - yLegendHeight,
                  xWidth, yStatusHeight,
                  TRUE) ;
   WindowShow (hWndGraphStatus, bShowStatus) ;

   //=============================//
   // Update the legend window    //
   //=============================//

   if (bShowLegend)
      MoveWindow (hWndGraphLegend,
                  0, yHeight - yLegendHeight,
                  xWidth, yLegendHeight,
                  TRUE) ;
   WindowShow (hWndGraphLegend, bShowLegend) ;

   //=============================//
   // Update the display window   //
   //=============================//

   MoveWindow (hWndGraphDisplay,
               0, 0,
               xWidth, yHeight - yStatusHeight - yLegendHeight,
               TRUE) ;
   SizeGraphDisplayComponents (hWndGraphDisplay) ;
   WindowInvalidate (hWndGraphDisplay) ;
   WindowShow (hWndGraphDisplay, TRUE) ;
   }  // SizeGraphComponents


GRAPH.H

/*****************************************************************************
 *
 *  Graph.h - Header for graphing window.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *
 ****************************************************************************/

#define GRAPH_BORDER 4

BOOL GraphInitializeApplication (void) ;

HWND CreateGraphWindow (HWND hWndParent) ;

void SizeGraphComponents (HWND hWnd) ;


INIT.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//
#include <string.h>     // strupr
#include <stdio.h>      // for sprintf.

#include "perfmon.h"
#include "init.h"       // external declarations for this file

#include "alert.h"      // for AlertIitializeApplication
#include "command.h"    // for ViewChart
#include "grafdata.h"   // for QuerySaveChart
#include "graph.h"      // for GraphInitializeApplication
#include "legend.h"     // for LegendInitializeApplication
#include "log.h"        // for LogInitializeApplication
#include "intrline.h"   // for ILineInitializeApplication
#include "perfdata.h"   // for PerfDataInitializeInstance
#include "perfmops.h"   // for OpenFileHandler, for now
#include "status.h"     // for StatusInitializeApplication
#include "timeline.h"   // for TLineInitializeApplication
#include "playback.h"   // for PlaybackInitializeInstance
#include "registry.h"   // for Load/SaveMainWindowPlacement
#include "report.h"     // for ReportInitializeApplication
#include "toolbar.h"    // for ToolbarInitializeApplication
#include "utils.h"
#include "fileopen.h"   // for FileOpen
#include "pmemory.h"    // for MemoryFree

extern   TCHAR          DefaultLangId[] ;
extern   TCHAR          EnglishLangId[] ;

static   LPSTR         lpszCommandLine ;

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define szPerfmonMainClass TEXT("PerfmonMainClass")

HHOOK   lpMsgFilterProc ;
DWORD FAR PASCAL MessageFilterProc (int nCode, WPARAM wParam,
      LPARAM lParam) ;

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void GetScalesFonts (void)
   {
   LOGFONT        lf ;

   memset (&lf, 0, sizeof (lf)) ;

   lstrcpy (lf.lfFaceName, szScalesFontFace) ;
   lf.lfHeight = iScalesFontHeight ;
   lf.lfWeight = FW_REGULAR ;

   if (PRIMARYLANGID(GetUserDefaultLangID ()) == LANG_JAPANESE)
     lf.lfCharSet = SHIFTJIS_CHARSET;

   hFontScales = CreateFontIndirect (&lf) ;

   if (PRIMARYLANGID(GetUserDefaultLangID ()) != LANG_JAPANESE)
     lf.lfWeight = FW_BOLD ;

   hFontScalesBold = CreateFontIndirect (&lf) ;
   }

BOOL InitializeSystemValues (void)
/*
   Effect:        Read and store in variables the various system values,
                  such as the width and height of the screen and icons,
                  the width of scroll bars, etc.

   Called By:     PerfmonInitialize only.

   Returns:       Whether this function was successful in getting all
                  needed system values.
*/
   {  // InitializeSystemValues
   xScreenWidth =  GetSystemMetrics (SM_CXSCREEN) ;
   yScreenHeight = GetSystemMetrics (SM_CYSCREEN) ;

   xBorderWidth = GetSystemMetrics (SM_CXBORDER) ;
   yBorderHeight = GetSystemMetrics (SM_CYBORDER) ;

   xScrollWidth = GetSystemMetrics (SM_CXVSCROLL) ;
   yScrollHeight = GetSystemMetrics (SM_CYHSCROLL) ;

   xScrollThumbWidth = GetSystemMetrics (SM_CXHTHUMB) ;
   yScrollThumbHeight = GetSystemMetrics (SM_CYVTHUMB) ;

   xDlgBorderWidth = GetSystemMetrics (SM_CXDLGFRAME) ;
   yDlgBorderHeight = GetSystemMetrics (SM_CYDLGFRAME) ;

   MinimumSize = yScrollHeight +
                 GetSystemMetrics (SM_CYMENU) +
                 GetSystemMetrics (SM_CYCAPTION) ;

   //================================================================//
   // create all the brushes and pens for performance improvement    //
   //================================================================//
   CreatePerfmonSystemObjects () ;
   hWhitePen = CreatePen (PS_SOLID, 3, crWhite) ;

   return (TRUE) ;
   }  // InitializeSystemValues

BOOL InitializeApplication (void)
/*
   Effect:        Perform all initializations required for the FIRST
                  instance of the Perfmon application. In particular,
                  register all of Perfmon's window classes.

   Note:          There is no background brush set for the MainWindow
                  class so that the main window is never erased. The
                  client area of MainWindow is always covered by one
                  of the view windows. If we erase it, it would just
                  flicker needlessly.

   Called By:     PerfmonInitialize only.

   Returns:       Whether this function was successful in initializing.
*/
   {  // InitializeApplication
   BOOL           bSuccess ;
   WNDCLASS       wc ;
   TCHAR          LocalHelpFileName [ShortTextLen] ;
   LPTSTR         pFileName ;

   hIcon = LoadIcon (hInstance, idIcon) ;

   //=============================//
   // Register Main window class  //
   //=============================//

   wc.style         = CS_DBLCLKS | CS_BYTEALIGNCLIENT;
   wc.lpfnWndProc   = (WNDPROC) MainWndProc;
   wc.hInstance     = hInstance;
   wc.cbClsExtra    = 0 ;
   wc.cbWndExtra    = 0;
   wc.hIcon         = hIcon ;
   wc.hCursor       = LoadCursor(NULL, IDI_APPLICATION);
   wc.hbrBackground = NULL ;                             // see note above
   wc.lpszMenuName  = idMenuChart ;
   wc.lpszClassName = szPerfmonMainClass ;

   bSuccess = RegisterClass (&wc) ;

   //=============================//
   // Register Abstract "Systems" //
   //=============================//
   hbLightGray = GetStockObject (LTGRAY_BRUSH) ;

   if (bSuccess)
      bSuccess = StatusInitializeApplication () ;

   if (bSuccess)
      bSuccess = GraphInitializeApplication () ;

#ifdef ADVANCED_PERFMON
   if (bSuccess)
      bSuccess = LogInitializeApplication () ;

   if (bSuccess)
      bSuccess = AlertInitializeApplication () ;

   if (bSuccess)
      bSuccess = ReportInitializeApplication () ;

   if (bSuccess)
      bSuccess = ILineInitializeApplication () ;

   if (bSuccess)
      bSuccess = TLineInitializeApplication () ;
#endif

   // setup messagehook to handle F1 as help
   lpMsgFilterProc = SetWindowsHookEx (WH_MSGFILTER,
          (HOOKPROC) MessageFilterProc,
          hInstance,
          GetCurrentThreadId()) ;

   // get the help file full path name
   LoadString (hInstance, IDS_HELPFILE_NAME,
      (LPTSTR)LocalHelpFileName, ShortTextLen-1);

   if (LocalHelpFileName[0])
      {
      pszHelpFile = (LPTSTR) MemoryAllocate (FilePathLen * sizeof (TCHAR)) ;
      SearchPath (NULL, LocalHelpFileName, NULL,
            FilePathLen - 1, pszHelpFile, &pFileName) ;
      }
   else
      {
      // no help file
      pszHelpFile = (LPTSTR) MemoryAllocate (sizeof (TCHAR)) ;
      *pszHelpFile = TEXT('\0') ;
      }

   return (bSuccess) ;
   }  // InitializeApplication

BOOL InitializeInstance (int nCmdShow, LPCSTR lpszCmdLine)
/*
   Effect:        Perform all initializations required for EACH instance
                  of the Perfmon application. In particular, create all
                  of Perfmon's initial windows, and perform any other
                  initializations except registering classes (done in
                  InitializeApplication).

   Called By:     PerfmonInitialize only.

   Note:          This function has multiple return points.

   Returns:       Whether this function was successful in initalizing.
*/
   {  // InitializeInstance
   DWORD          ComputerNameLength;
   TCHAR          szApplication [WindowCaptionLen] ;

   //=============================//
   // Set Priority high           //
   //=============================//

   SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS) ;
   SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ;

   //=============================//
   // Load Resources              //
   //=============================//

   GetScalesFonts () ;

   hMenuChart = LoadMenu (hInstance, idMenuChart) ;

#ifdef ADVANCED_PERFMON
   hMenuAlert = LoadMenu (hInstance, idMenuAlert) ;
   hMenuLog = LoadMenu (hInstance, idMenuLog) ;
   hMenuReport = LoadMenu (hInstance, idMenuReport) ;
#endif

   hAccelerators = LoadAccelerators (hInstance, idAccelerators) ;

   //=============================//
   // Initialize Systems          //
   //=============================//

   iLanguage = GetUserDefaultLangID() ;
   iEnglishLanguage = MAKELANGID (LANG_ENGLISH, LANG_NEUTRAL) ;
//   iEnglishLanguage = MAKELANGID (iLanguage & 0x0ff, LANG_NEUTRAL) ;
   TSPRINTF (DefaultLangId, TEXT("%03x"), iLanguage) ;
   TSPRINTF (EnglishLangId, TEXT("%03x"), iEnglishLanguage) ;

   // GetComputerName returns the name without the "\\" prefix. We add
   // the prefix before even calling the routine. This is so that all our
   // computer names have the prefix and are therefore compatible with
   // I_SetSystemFocus (see perfmops.c).

   ComputerNameLength = MAX_COMPUTERNAME_LENGTH + 1;
   lstrcpy (LocalComputerName, szComputerPrefix) ;
   GetComputerName (LocalComputerName + lstrlen (szComputerPrefix),
                    &ComputerNameLength);

   PlaybackInitializeInstance () ;
   PerfDataInitializeInstance () ;

//Copy command line into buffer for modification
lpszCommandLine = (LPSTR)MemoryAllocate (strlen (lpszCmdLine) + sizeof(CHAR)) ;
   if(lpszCommandLine)
   strcpy(lpszCommandLine, lpszCmdLine) ;
else
return (FALSE) ;

   StringLoad (IDS_APPNAME, szApplication) ;

//Insure CmdLineComputerName starts out NULL
CmdLineComputerName[0] = TEXT('\0') ;

//Check to see if there is a computer name specified on the command
//line (indicated by /c, or /C).  If there is, get it and
//null out the command line string so that it is not mistaken for
//startup file.  This assumes that specifying a computer and a startup
//file on the command line are mutually exclusive.  If the /c option
//is on the command line, anything else on the command line is discarded.
if (!strempty (lpszCommandLine)) {
LPSTRlpszNewCL ;

//Preserve initial command line pointer position
lpszNewCL = (LPSTR)lpszCommandLine ;
while (*lpszNewCL != '\0' && *lpszCommandLine) {
if (*lpszNewCL== '/' || *lpszNewCL== '-') {
lpszNewCL++ ;
if (*lpszNewCL == 'c' || *lpszNewCL == 'C') {
int i = 0;

lpszNewCL++ ;
//ignore leading backslashes * spaces
while (*lpszNewCL == '\\' || *lpszNewCL == ' ')
lpszNewCL++ ;

//Terminate at first blank space if there is one.
//We don't allow anything else on the command line
//if a computer name is specified.
while (lpszNewCL[i] != ' ' && lpszNewCL[i] != '\0')
i++ ;
lpszNewCL[i] = '\0' ;

if ((*lpszNewCL != '\0') && (strlen(lpszNewCL) <= MAX_COMPUTERNAME_LENGTH)) {
int nSizePrefix ;

nSizePrefix = lstrlen (szComputerPrefix) ;
lstrcpy( CmdLineComputerName, szComputerPrefix ) ;
MultiByteToWideChar (CP_ACP,
MB_PRECOMPOSED,
lpszNewCL,
-1,
CmdLineComputerName + nSizePrefix,
sizeof(CmdLineComputerName) - nSizePrefix) ;
//prevent lpszCommandLine from being used as input file & stop while loop
*lpszCommandLine = '\0' ;
} else {
LPTSTRlpszErrMsg ;
TCHARlpszFormat[80] ;

LoadString(hInstance, ERR_BADCOMPUTERNAME, lpszFormat, sizeof(lpszFormat)/sizeof(TCHAR)) ;
lpszErrMsg = (LPTSTR)MemoryAllocate ((strlen (lpszNewCL) + 1)*sizeof(TCHAR) + sizeof(lpszFormat)) ;
//If memory allocation failed, don't display error message
   if (lpszErrMsg) {
//lpszFormat uses %S specifier so lpszNewCL can be ansi string
wsprintf (lpszErrMsg, lpszFormat, lpszNewCL) ;
MessageBox (NULL, lpszErrMsg, szApplication, MB_OK | MB_ICONSTOP | MB_TASKMODAL) ;
}

//prevent lpszCommandLine from being used as input file & stop while loop
*lpszCommandLine = '\0' ;
}
}
}
lpszNewCL++ ;
}
}

   //=============================//
   // Create Window               //
   //=============================//

   hWndMain = CreateWindow (szPerfmonMainClass,
                            szApplication,
                          WS_OVERLAPPEDWINDOW | WS_BORDER,
                      CW_USEDEFAULT, CW_USEDEFAULT,
                      CW_USEDEFAULT, CW_USEDEFAULT,
                      NULL,
                            NULL,
                            NULL,
                            NULL);

   if (!hWndMain)
      return (FALSE) ;

   ViewChart (hWndMain) ;

   LoadMainWindowPlacement (hWndMain) ;

   //=============================//
   // Setup for event logging     //
   //=============================//
   hEventLog = RegisterEventSource (
      (LPTSTR)NULL,            // Use Local Machine
      TEXT("PerfMon"));        // event log app name to find in registry

   return (TRUE) ;
   }  // InitializeInstance

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL PerfmonInitialize (HINSTANCE hCurrentInstance,
                        HINSTANCE hPrevInstance,
                        LPCSTR lpszCmdLine,
                        int nCmdShow)
/*
   Effect:        Performa all initializations required when Perfmon is
                  started. In particular, initialize all "systems", register
                  all window classes, create needed windows, read in and
                  process font and Perfmon lists.

   Called By:     WinMain only, at the start of the application.

   Assert:        There are no other instances of Perfmon currently
                  executing.

   Returns:       Whether initialization was successful. If this function
                  returns FALSE, Perfmon should exit immediately.

   Internals:     The bSuccess variable is used to conditionalize each
                  successive initialization step.
*/
   {  // PerfmonInitialize
   BOOL           bSuccess ;
   TCHAR          szFilePath [FilePathLen + 1] ;
   LPTSTR         pFileNameStart ;
   HANDLE         hFindFile ;
   WIN32_FIND_DATA FindFileInfo ;
   CHAR           QuoteChar ;
   LPSTR          pCmdLine ;
   LPSTR          lpszCmdLineStart = NULL ;
   int            NameOffset ;
   int            StringLen ;

   hInstance = hCurrentInstance ;
   bSuccess = InitializeSystemValues () ;

   if (bSuccess && !hPrevInstance)
      bSuccess = InitializeApplication () ;

   if (bSuccess)
      bSuccess = InitializeInstance (nCmdShow, lpszCmdLine) ;

   GetDateTimeFormats() ;

   lpszCmdLineStart = lpszCommandLine ;

   if (bSuccess)
      {

      if (strempty (lpszCommandLine))
         StringLoad (IDS_DEFAULTPATH, szFilePath) ;
      else
         {
         // check for single or double quote
         QuoteChar = *lpszCommandLine ;
         if (QuoteChar == '\'' || QuoteChar == '\"')
            {
            lpszCommandLine++ ;

            // remove the matching QuoteChar if found
            pCmdLine = (LPSTR) lpszCommandLine ;
            while (*pCmdLine != '\0')
               {
               if (*pCmdLine == QuoteChar)
                  {
                  *pCmdLine = '\0' ;
                  break ;
                  }
               else
                  {
                  pCmdLine++ ;
                  }
               }
            }

         // convert the LPSTR to LPTSTR

         StringLen = strlen(lpszCommandLine) + 1 ;
         if (StringLen > sizeof(szFilePath))
            {
            StringLen = sizeof (szFilePath) - sizeof (TEXT('\0')) ;
            }
         szFilePath[FilePathLen] = TEXT('\0') ;

         mbstowcs (szFilePath, lpszCommandLine, StringLen) ;

         if (!(QuoteChar == '\'' || QuoteChar == '\"'))
            {
            // if there is no quote, then looking for trailing space
            LPTSTR   lpTempStr = szFilePath ;

            while (*lpTempStr != TEXT('\0'))
               {
               if (*lpTempStr == TEXT(' '))
                  {
                  *lpTempStr = TEXT('\0') ;
                  break ;
                  }
               lpTempStr++ ;
               }
            }

         pFileNameStart = ExtractFileName (szFilePath) ;
         NameOffset = pFileNameStart - szFilePath ;

         // convert short filename to long NTFS filename if necessary
         hFindFile = FindFirstFile (szFilePath, &FindFileInfo) ;
         if (hFindFile && hFindFile != INVALID_HANDLE_VALUE)
            {
            // append the file name back to the path name
            lstrcpy (&szFilePath[NameOffset], FindFileInfo.cFileName) ;
            FindClose (hFindFile) ;
            }
         }

//      OpenFileHandler (hWndMain, szFilePath) ;
      FileOpen (hWndMain, (int)0, (LPTSTR)szFilePath) ;
      PrepareMenu (GetMenu (hWndMain));
      }

   if (lpszCmdLineStart)
      {
      MemoryFree (lpszCmdLineStart) ;
      lpszCommandLine = NULL ;
      }

   return (bSuccess) ;
   }  // PerfmonInitialize

void PerfmonClose (HWND hWndMain)
   {
   // close the log file for now.
   // need to query the user later..!!
#if 0
   if (LogCollecting (hWndLog))
      {
      PLOG pLog = LogData (hWndLog) ;

      if (pLog)
         {
         CloseLog (hWndLog, pLog) ;
         }
      }
#endif

   // reset all views - will free all systems as well
   ResetGraphView (hWndGraph) ;
   ResetAlertView (hWndAlert) ;
   ResetLogView (hWndLog) ;
   ResetReportView (hWndReport) ;

   // close the local machine
   if (bCloseLocalMachine)
      {
      RegCloseKey (HKEY_PERFORMANCE_DATA) ;
      }

   // free all the filenames
   if (pChartFullFileName)
      {
      MemoryFree (pChartFullFileName) ;
      pChartFullFileName = NULL ;
      }
   if (pAlertFullFileName)
      {
      MemoryFree (pAlertFullFileName) ;
      pAlertFullFileName = NULL ;
      }
   if (pLogFullFileName)
      {
      MemoryFree (pLogFullFileName) ;
      pLogFullFileName = NULL ;
      }
   if (pReportFullFileName)
      {
      MemoryFree (pReportFullFileName) ;
      pReportFullFileName = NULL ;
      }

   // free all the GDI resources
   DeletePen (hWhitePen) ;
   DeletePerfmonSystemObjects () ;

   // close event log
   DeregisterEventSource (hEventLog);

   SaveMainWindowPlacement (hWndMain) ;
   DestroyWindow (hWndMain) ;
   }  // PerfmonClose


INIT.H

BOOL PerfmonInitialize (HINSTANCE hCurrentInstance,
                        HINSTANCE hPrevInstance,
                        LPCSTR lpszCmdLine,
                        int nCmdShow) ;

void PerfmonClose (HWND hWndMain) ;


INTRLINE.C

/*
==============================================================================

  Application:

            Microsoft Windows NT (TM) Performance Monitor

  File:
            intrline.c -- IntervalLine custom control.

  Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
==============================================================================
*/

/*

Basic Information
-----------------

An ILine (Interval Line) control is a horizontally-sliding device; the user
can slide the start position and end position independently by dragging
either "grab" bar, or move them simultaneously by dragging the center grab
bar. An ILine control is used in the Input Log dialog of Perfmon, but could
be used anywhere. The control allows the user to specify the start, end and
duration of playback within the range of previously-logged data.

ILine Parts
-----------

     iBeginValue                                            iEndValue
     |      iStartValue                        iStopValue           |
     |      |                                           |           |
     v      v                                           v           v
     +------+-+---------------------------------------+-+-----------+
     |      |X|                                       |X|           |
     |      |X|                                       |X|           |
     +------+-+---------------------------------------+-+-----------+
             ^                    ^                    ^
             Start grab bar       Center grab bar      Stop grab bar

ILine Terminology
-----------------

All external routines are designated by the prefix ILine-.
Internal routines are designated by the prefix IL-.

The suffix -Value represents an integer in the user-supplied domain.
The suffix -Pixel represents an integer location on the screen.

Note that the range of the IntervalLine is represented by "Begin" and
"End", while the the currently selected interval is represented by
"Start" and "Stop".

Implementation Notes
--------------------

ILine is a custom control, but does not have all the messages
normally associated with a full-fledged control type like "button".
In particular, it doesn't have a keyboard interface or ask it's parent
for color choices. It also doesn't have the functions needed for interfacing
with the dialog editor custom control menu.

An ILine control is designed to work with an integer range of values
specified by the user of the control. These values should have meaning to
the caller. The caller can specify the minimum value of the range
(iBeginValue), the maximum value of the range (iEndValue), and the current
starting and stopping values (iStartValue and iStopValue).

These values are set with a function-based interface. (ILSetXXX).

The code distinguishes between the *values* for the begin, end, start, and
stop, and the *pixels* which represent locations on the control for these
values.

Various bits of the control style allow for changing the control's
behavior.

To allow for multiple ILine controls, all data used by each ILine
instance is allocated from the heap and associated with the control.
The allocation is done in OnCreate and the freeing in OnDestroy. The
instance data is contained in the ILINE structure. Each message handler
retrieves this instance data with the ILData function.

*/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"
#include "utils.h"
#include "intrline.h"
#include "pmemory.h"        // for MemoryXXX (mallloc-type) routines

BOOL  IntrLineFocus ;

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define dwILineClassStyle     (CS_HREDRAW | CS_VREDRAW)
#define iILineClassExtra      (0)
#define iILineWindowExtra     (sizeof (PILINE))
#define dwILineWindowStyle    (WS_CHILD | WS_VISIBLE)

#define ILModeNone            0
#define ILModeLeft            1
#define ILModeCenter          2
#define ILModeRight           3

#define TO_THE_END            0x7FFFFFFFL

//==========================================================================//
//                                  Typedefs                                //
//==========================================================================//

// The instance data for each IL window.  One of these is allocated for each
// window in the window's OnCreate processing and free'd in OnDestroy.
typedef struct ILINESTRUCT
   {  // ILINE
   int            iBeginValue ;        // user-supplied lowest range
   int            iEndValue ;          // user-supplied highest range
   int            iStartValue ;        // current start of selected interval
   int            iStopValue ;         // current end of selected interval
   int            iGranularityValue ;
   int            iMinimumRangeValue ;

   RECT           rectBorder ;
   RECT           rectLeftBk ;
   RECT           rectLeftGrab ;
   RECT           rectCenterGrab ;
   RECT           rectRightGrab ;
   RECT           rectRightBk ;

   HBRUSH         hBrushBk ;

   POINTS         ptsMouse ;
   int            iMode ;              // who is being tracked?
   } ILINE ;

typedef ILINE *PILINE ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

// Width of the start and stob grab bars
#define ILGrabWidth()      \
   (xScrollThumbWidth)

// A rectangle is "drawable" if it has both nonzero width and height
#define RectDrawable(lpRect)           \
   ((lpRect->right - lpRect->left) &&  \
    (lpRect->bottom - lpRect->top))

//======================================//
// ILine Accessor Macros                //
//======================================//

// These macros reference fields in the ILine structure and should be
// used in place of direct reference to the fields. This makes the code
// easier to read and allows modification of the underlying structure.

// You can use these macros to both get and set the fields.

#define ILBegin(pILine)    \
   (pILine->iBeginValue)

#define ILEnd(pILine)      \
   (pILine->iEndValue)

#define ILStart(pILine)    \
   (pILine->iStartValue)

#define ILStop(pILine)     \
   (pILine->iStopValue)

#define ILMode(pILine)     \
   (pILine->iMode)

//======================================//
// ILine Pseudo-Accessor Macros         //
//======================================//

// These macros are used to get values which don't correspond to a single
// field. You can get the value with these macros but can't set it.

#define ILWidth(pILine)    \
   (pILine->rectBorder.right)

#define ILRange(pILine)    \
   (ILEnd (pILine) - ILBegin (pILine))

#define ILStartPixel(pILine) \
   (pILine->rectLeftGrab.left)

#define ILStopPixel(pILine)   \
   (pILine->rectRightGrab.right)

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void static ILNotifyChange (HWND hWnd,
                            PILINE pILine)
   {  // ILNotifyChange
   HWND           hWndParent ;

   hWndParent = WindowParent (hWnd) ;

   if (hWndParent)
      SendMessage (hWndParent, WM_COMMAND,
                   (WPARAM) WindowID (hWnd),
                   (LPARAM) hWnd) ;
   }  // ILNotifyChange

BOOL ILGrabRect (IN PILINE pILine,
                 OUT LPRECT lpRect)
/*
   Effect:        Return in lpRect the rectangle representing the position
                  of the grab bar currently being tracked.
*/
   {  // ILGrabRect
   switch (ILMode (pILine))
      {  // switch
      case ILModeLeft:
         *lpRect = pILine->rectLeftGrab ;
         return (TRUE) ;
         break ;

      case ILModeRight:
         *lpRect = pILine->rectRightGrab ;
         return (TRUE) ;
         break ;

      case ILModeCenter:
         *lpRect = pILine->rectCenterGrab ;
         return (TRUE) ;
         break ;

      case ILModeNone:
         lpRect->left = 0 ;
         lpRect->top = 0 ;
         lpRect->right = 0 ;
         lpRect->bottom = 0 ;
         return (FALSE) ;
         break ;
      }  // switch
   }  // ILGrabRect

PILINE ILData (HWND hWndIL)
   {
   return ((PILINE) GetWindowLong (hWndIL, 0)) ;
   }

PILINE AllocateILData (HWND hWndIL)
   {
   PILINE         pILine ;

   pILine = MemoryAllocate (sizeof (ILINE)) ;
   SetWindowLong (hWndIL, 0, (LONG) pILine) ;

   return (pILine) ;
   }

void static DrawGrab (HDC hDC,
                      LPRECT lpRectGrab,
                      BOOL bDown)
   {  // DrawGrab
   if (!RectDrawable (lpRectGrab))
      return ;

   if (bDown)
      ThreeDConcave1 (hDC,
                     lpRectGrab->left,
                     lpRectGrab->top,
                     lpRectGrab->right,
                     lpRectGrab->bottom) ;
   else
      ThreeDConvex1 (hDC,
                    lpRectGrab->left,
                    lpRectGrab->top,
                    lpRectGrab->right,
                    lpRectGrab->bottom) ;
   }  // DrawGrab

int ILValueToPixel (PILINE pILine,
                    int iValue)
   {
   int            xPixel ;

   if (ILRange (pILine))
      xPixel = MulDiv (iValue, ILWidth (pILine), ILRange (pILine)) ;
   else
      xPixel = 0 ;

   return (PinExclusive (xPixel, 0, pILine->rectBorder.right)) ;
   }

int ILPixelToValue (PILINE pILine,
                    int xPixel)
   {
   int            iValue ;

   if (ILWidth (pILine))
      iValue = MulDiv (xPixel, ILRange (pILine), ILWidth (pILine)) ;
   else
      iValue = 0 ;

   return (PinInclusive (iValue, ILBegin (pILine), ILEnd (pILine))) ;
   }

void static ILCalcPositions (HWND hWnd,
                             PILINE pILine)
/*
   Effect:        Determine and set all of the physical rectangles of ILine,
                  based on the current size of the ILine window and the
                  current logical Start, Stop, Begin, and End values.
*/
   {  // ILCalcPositions
   int            xStart, xStop ;
   int            yHeight ;

   GetClientRect (hWnd, &pILine->rectBorder) ;
   yHeight = pILine->rectBorder.bottom ;

   xStart = ILValueToPixel (pILine, ILStart (pILine)) ;
   xStop = ILValueToPixel (pILine, ILStop (pILine)) ;

   pILine->rectLeftBk.left = 1 ;
   pILine->rectLeftBk.top = 1 ;
   pILine->rectLeftBk.right = xStart ;
   pILine->rectLeftBk.bottom = yHeight - 1 ;

   pILine->rectLeftGrab.left = xStart ;
   pILine->rectLeftGrab.top = 1 ;
   pILine->rectLeftGrab.right = xStart + ILGrabWidth () ;
   pILine->rectLeftGrab.bottom = yHeight - 1 ;

   pILine->rectRightBk.left = xStop ;
   pILine->rectRightBk.top = 1 ;
   pILine->rectRightBk.right = pILine->rectBorder.right - 1 ;
   pILine->rectRightBk.bottom = yHeight - 1 ;

   pILine->rectRightGrab.left = xStop - ILGrabWidth () ;
   pILine->rectRightGrab.top = 1 ;
   pILine->rectRightGrab.right = xStop ;
   pILine->rectRightGrab.bottom = yHeight - 1 ;

   pILine->rectCenterGrab.left = pILine->rectLeftGrab.right ;
   pILine->rectCenterGrab.top = 1 ;
   pILine->rectCenterGrab.right = pILine->rectRightGrab.left ;
   pILine->rectCenterGrab.bottom = yHeight - 1 ;

   if (pILine->rectLeftGrab.right > pILine->rectRightGrab.left)
      {
      pILine->rectLeftGrab.right = pILine->rectLeftGrab.left +
                                   (xStop - xStart) / 2 ;
      pILine->rectRightGrab.left = pILine->rectLeftGrab.right ;
      pILine->rectCenterGrab.left = 0 ;
      pILine->rectCenterGrab.right = 0 ;
      }
   }  // ILCalcPositions

void static ILDraw (HDC hDC,
                    PILINE pILine,
                    LPRECT lpRectUpdate)
/*
   Effect:        Draw the image of pILine on hDC.  Draw at least the
                  portions within rectUpdate.

   Called By:     OnPaint, OnMouseMove.
*/
   {  // ILDraw

   //=============================//
   // Draw Border                 //
   //=============================//

   FrameRect (hDC, &pILine->rectBorder, GetStockObject (BLACK_BRUSH)) ;

   //=============================//
   // Draw Background             //
   //=============================//

   FillRect (hDC, &pILine->rectLeftBk, pILine->hBrushBk) ;
   FillRect (hDC, &pILine->rectRightBk, pILine->hBrushBk) ;

   //=============================//
   // Draw Range Grabs            //
   //=============================//

   DrawGrab (hDC, &pILine->rectLeftGrab,
             ILMode (pILine) == ILModeLeft) ;
   DrawGrab (hDC, &pILine->rectRightGrab,
             ILMode (pILine) == ILModeRight) ;

   //=============================//
   // Draw Center Grab            //
   //=============================//

   DrawGrab (hDC, &pILine->rectCenterGrab,
             ILMode (pILine) == ILModeCenter) ;
   }  // ILDraw

#if 0
void ILPageLeftRight (HWND hWnd,
                      PILINE pILine,
                      BOOL bLeft)
   {  // ILPageLeftRight
   int            iStart, iStop, iMove ;
   HDC            hDC ;

   iStart = ILStart (pILine) ;
   iStop = ILStop (pILine) ;

   if (ILRange (pILine) >= 10)
      iMove = ILRange (pILine) / 10 ;
   else
      iMove = 1 ;

   if (bLeft)
      iMove = -iMove ;

   iStart += iMove ;
   iStop += iMove ;

   ILineSetStart (hWnd, iStart) ;
   ILineSetStop (hWnd, iStop) ;

   hDC = GetDC (hWnd) ;
   ILDraw (hDC, pILine, &pILine->rectBorder) ;
   ReleaseDC (hWnd, hDC) ;

   ILNotifyChange (hWnd, pILine) ;
   }  // ILPageLeftRight
#endif

void ILMoveLeftRight (HWND hWnd,
                      BOOL bStart,
                      BOOL bLeft,
                      int  MoveAmt)
   {  // ILPageLeftRight
   int            iStart, iStop, iMove ;
   int            iBegin, iEnd ;
   HDC            hDC ;
   PILINE         pILine ;

   pILine = ILData (hWnd) ;
   iStart = ILStart (pILine) ;
   iStop = ILStop (pILine) ;
   iBegin = ILBegin (pILine) ;
   iEnd = ILEnd (pILine) ;

   iMove = MoveAmt ;
   if (bLeft)
      iMove = -iMove ;

   if (bStart)
      {
      if (MoveAmt == TO_THE_END)
         {
         iStart = iBegin ;
         }
      else
         {
         iStart += iMove ;
         if (iStart >= iStop)
            {
            return;
            }
         }

      ILineSetStart (hWnd, iStart) ;
      }
   else
      {
      if (MoveAmt == TO_THE_END)
         {
         iStop = iEnd ;
         }
      else
         {
         iStop += iMove ;
         if (iStart >= iStop)
            {
            return;
            }
         }

      ILineSetStop (hWnd, iStop) ;
      }

   hDC = GetDC (hWnd) ;
   ILDraw (hDC, pILine, &pILine->rectBorder) ;
   ReleaseDC (hWnd, hDC) ;

   ILNotifyChange (hWnd, pILine) ;
   }  // ILMoveLeftRight

static BOOL OnKeyDown (HWND hWnd,
                       WPARAM wParam)
   {
   BOOL bHandle = TRUE ;
   BOOL bStart ;
   BOOL bLeftDirection ;
   BOOL bShiftKeyDown ;

   if (wParam == VK_LEFT || wParam == VK_RIGHT)
      {
      bShiftKeyDown = (GetKeyState (VK_SHIFT) < 0) ;

      if (!bShiftKeyDown)
         {
         if (wParam == VK_LEFT)
            {
            // Left Arrow --> move Start Edge Left
            bStart = TRUE ;
            bLeftDirection = TRUE ;
            }
         else
            {
            // Right Arrow --> move Stop Edge Right
            bStart = FALSE ;
            bLeftDirection = FALSE ;
            }
         }
      else
         {
         if (wParam == VK_LEFT)
            {
            // Shift Left Arrow --> move Stop Edge Left
            bStart = FALSE ;
            bLeftDirection = TRUE ;
            }
         else
            {
            // Shift Right Arrow --> move Start Edge Right
            bStart = TRUE ;
            bLeftDirection = FALSE ;
            }
         }
      ILMoveLeftRight (hWnd, bStart, bLeftDirection, 1) ;
      }
   else if (wParam == VK_HOME)
      {
      // move iStart all the way the Left
      ILMoveLeftRight (hWnd, TRUE, TRUE, TO_THE_END) ;
      }
   else if (wParam == VK_END)
      {
      // move iStop all the way the right
      ILMoveLeftRight (hWnd, FALSE, FALSE, TO_THE_END) ;
      }
   else
      {
      bHandle = FALSE ;
      }
   return (bHandle) ;

   }  // OnKeyDown

void StartGrab (HWND hWnd,
                PILINE pILine)
   {  // StartGrab
   RECT           rectUpdate ;
   HDC            hDC ;

   SetCapture (hWnd) ;
   ILGrabRect (pILine, &rectUpdate) ;

   hDC = GetDC (hWnd) ;
   ILDraw (hDC, pILine, &rectUpdate) ;
   ReleaseDC (hWnd, hDC) ;
   }  // StartGrab

void EndGrab (HWND hWnd,
              PILINE pILine)
/*
   Internals:     Set the mode to null after getting the grab rectangle
                  so ILGrabRect knows which grab bar to get.
*/
   {
   RECT           rectUpdate ;
   HDC            hDC ;

   ReleaseCapture () ;

   ILGrabRect (pILine, &rectUpdate) ;
   ILMode (pILine) = ILModeNone ;

   hDC = GetDC (hWnd) ;
   ILDraw (hDC, pILine, &rectUpdate) ;
   ReleaseDC  (hWnd, hDC) ;
   }

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnPaint (HWND hWnd)
   {
   PAINTSTRUCT    ps ;
   HDC            hDC ;
   PILINE         pILine ;

   pILine = ILData (hWnd) ;
   hDC = BeginPaint (hWnd, &ps) ;

   ILDraw (hDC, pILine, &ps.rcPaint) ;

   EndPaint (hWnd, &ps) ;
   }

void static OnCreate (HWND hWnd)
   {
   PILINE         pILine ;

   pILine = AllocateILData (hWnd) ;
   ILBegin (pILine) =  0 ;
   ILEnd (pILine) =  100 ;
   ILStart (pILine) =  0 ;
   ILStop (pILine) = 100 ;
   ILMode (pILine) = ILModeNone ;

   pILine->hBrushBk = CreateSolidBrush (crBlue) ;
   ILCalcPositions (hWnd, pILine) ;
   }

void static OnDestroy (HWND hWnd)
   {
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   IntrLineFocus = FALSE ;
   DeleteBrush (pILine->hBrushBk) ;
   MemoryFree (pILine) ;
   }

void static OnLButtonUp (HWND hWnd)
   {  // OnLButtonUp
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   if (ILMode (pILine) == ILModeNone)
      return ;

   EndGrab (hWnd, pILine) ;
   }  // OnLButtonUp

void static OnMouseMove (HWND hWnd,
                         POINTS ptsMouse)
/*
   Effect:        Handle any actions needed when the mouse moves in the
                  ILine hWnd's client area or while the mouse is captured.
                  In particular, if we are tracking one of the grab bars,
                  determine if the mouse movement represents a logical value
                  change and move the grab bar accordingly.

   Called By:     ILineWndProc, in response to a WM_MOUSEMOVE message.

   See Also:      OnLButtonDown, OnLButtonUp.

   Note:          This function has multiple return points.

   Note:          Since we have captured the mouse, we receive mouse msgs
                  even when the mouse is outside our client area, but still
                  in client coordinates. Thus we can have negative mouse
                  coordinates. That is why we convert the lParam of the
                  mouse msg into a POINTS structure rather than 2 WORDS.

   Internals:     Remember that an IntervalLine can only take on integral
                  values in the user-supplied range. Therefore we do our
                  movement calculation in user values, not pixels. We
                  determine what the logical value would be for the previous
                  (last mouse move) and current mouse position. If these
                  LOGICAL values differ, we attempt an adjustment of the
                  grab bar by that logical amount.  This way the grab
                  values assume on integral positions and the calculations
                  are simplified.

                  If we calculated by pixel movement, and then shifted the
                  bar into the nearest integal position, we would encounter
                  rounding problems. In particular, when tracking the center
                  grab bar, if we moved both start and stop by the same
                  amount of PIXELS, then converted to LOGICAL values, we
                  might find our center bar shrinking and growing while
                  the bar moves.
*/
   {
   int            iMousePrevious, iMouseCurrent ;
   int            iMouseMove ;
   PILINE         pILine ;
   HDC            hDC ;

   //=============================//
   // Are we tracking?            //
   //=============================//

   pILine = ILData (hWnd) ;
   if (ILMode (pILine) == ILModeNone)
      return ;

   //=============================//
   // Calc LOGICAL mouse movement //
   //=============================//

   ptsMouse.x = PinInclusive ((INT)ptsMouse.x,
                              (INT)pILine->rectBorder.left,
                              (INT)pILine->rectBorder.right) ;

   iMousePrevious = ILPixelToValue (pILine, pILine->ptsMouse.x) ;
   iMouseCurrent = ILPixelToValue (pILine, ptsMouse.x) ;

   iMouseMove = iMouseCurrent - iMousePrevious ;
   if (!iMouseMove)
      return ;

   //=============================//
   // Move grab bar positions     //
   //=============================//

   switch (ILMode (pILine))
      {  // switch
      case ILModeLeft:
         ILStart (pILine) += iMouseMove ;
         ILStart (pILine) = min (ILStart (pILine), ILStop (pILine) - 1) ;
         break ;

      case ILModeCenter:
         // Before we slide the center grab bar we need to see if the
         // desired movement amount would send either end out of bounds,
         // and reduce the movement accordingly.

         if (ILStart (pILine) + iMouseMove < ILBegin (pILine))
            iMouseMove = ILBegin (pILine) - ILStart (pILine) ;
         if (ILStop (pILine) + iMouseMove > ILEnd (pILine))
            iMouseMove = ILEnd (pILine) - ILStop (pILine) ;

         ILStart (pILine) += iMouseMove ;
         ILStop (pILine) += iMouseMove ;
         break ;

      case ILModeRight:
         ILStop (pILine) += iMouseMove ;
         ILStop (pILine) = max (ILStart (pILine) + 1, ILStop (pILine)) ;
         break ;
      }  // switch

   //=============================//
   // Keep grab bars in range     //
   //=============================//

   ILStart (pILine) = PinInclusive (ILStart (pILine),
                                    ILBegin (pILine), ILEnd (pILine)) ;
   ILStop (pILine) = PinInclusive (ILStop (pILine),
                                   ILBegin (pILine), ILEnd (pILine)) ;

   //=============================//
   // Determine pixel pos, draw   //
   //=============================//

   ILCalcPositions (hWnd, pILine) ;

   hDC = GetDC (hWnd) ;
   ILDraw (hDC, pILine, &pILine->rectBorder) ;
   ReleaseDC (hWnd, hDC) ;

   pILine->ptsMouse = ptsMouse ;
   ILNotifyChange (hWnd, pILine) ;
   }  // OnMouseMove

void static OnLButtonDown (HWND hWnd,
                           POINTS ptsMouse)
   {
   PILINE         pILine ;
   POINT          ptMouse ;

   pILine = ILData (hWnd) ;

   pILine->ptsMouse = ptsMouse ;
   ptMouse.x = ptsMouse.x ;
   ptMouse.y = ptsMouse.y ;

#if 0
   // Russ said this is bad, so don't do it
   if (PtInRect (&pILine->rectLeftBk, ptMouse))
      ILPageLeftRight (hWnd, pILine, TRUE) ;

   else if (PtInRect (&pILine->rectRightBk, ptMouse))
      ILPageLeftRight (hWnd, pILine, FALSE) ;

   else if (PtInRect (&pILine->rectLeftGrab, ptMouse))
#endif
   if (PtInRect (&pILine->rectLeftGrab, ptMouse) ||
       PtInRect (&pILine->rectLeftBk, ptMouse))
      {
      ILMode (pILine) = ILModeLeft ;
      StartGrab (hWnd, pILine) ;
      }

   else if (PtInRect (&pILine->rectRightGrab, ptMouse) ||
            PtInRect (&pILine->rectRightBk, ptMouse))
      {
      ILMode (pILine) = ILModeRight ;
      StartGrab (hWnd, pILine) ;
      }

   else if (PtInRect (&pILine->rectCenterGrab, ptMouse))
      {
      ILMode (pILine) = ILModeCenter ;
      StartGrab (hWnd, pILine) ;
      }
   }

void static OnSize (HWND hWnd, WORD xWidth, WORD yHeight)
   {  // OnSize
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   ILCalcPositions (hWnd, pILine) ;
   }  // OnSize

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

LONG FAR PASCAL ILineWndProc (HWND hWnd,
                              unsigned msg,
                              WPARAM wParam,
                              LONG lParam)
/*
   Note:          This function must be declared in the application's
                  linker-definition file, perfmon.def file.
*/
   {  // ILineWndProc
   BOOL           bCallDefWindowProc ;
   POINTS         ptsMouse ;
   LONG           lReturnValue ;

   bCallDefWindowProc = FALSE ;
   lReturnValue = 0L ;

   switch (msg)
      {  // switch
      case WM_CREATE:
         OnCreate (hWnd) ;
         break ;

      case WM_DESTROY:
         OnDestroy (hWnd) ;
         break ;

      case WM_LBUTTONDOWN:
         // See the note in OnMouseMove for why we are using POINTS
         SetFocus (hWnd) ;
         ptsMouse = MAKEPOINTS (lParam) ;
         OnLButtonDown (hWnd, ptsMouse) ;
         break ;

      case WM_LBUTTONUP:
         OnLButtonUp (hWnd) ;
         break ;

      case WM_SETFOCUS:
      case WM_KILLFOCUS:
         {
         PILINE         pILine ;

         IntrLineFocus = (msg == WM_SETFOCUS) ;

         pILine = ILData (hWnd) ;
         ILNotifyChange (hWnd, pILine) ;
         }
         return 0 ;

      case WM_MOUSEMOVE:
         // See the note in OnMouseMove for why we are using POINTS
         ptsMouse = MAKEPOINTS (lParam) ;
         OnMouseMove (hWnd, ptsMouse) ;
         break ;

      case WM_KEYDOWN:
         if (!OnKeyDown (hWnd, wParam))
            {
            bCallDefWindowProc = TRUE ;
            }
         break ;

      case WM_GETDLGCODE:
         // We want to handle Arrow keys input.  If we don't specify this
         // the dialog will not pass arrow keys to us.
         return (DLGC_WANTARROWS) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_SIZE:
         OnSize (hWnd, LOWORD (lParam), HIWORD (lParam)) ;

      default:
         bCallDefWindowProc = TRUE ;
      }  // switch

   if (bCallDefWindowProc)
      lReturnValue = DefWindowProc (hWnd, msg, wParam, lParam) ;

   return (lReturnValue) ;
   }  // ILineWndProc

BOOL ILineInitializeApplication (void)
/*
   Effect:        Perform all initializations required before an application
                  can create an IntervalLine. In particular, register the
                  IntervalLine window class.

   Called By:     The application, in its InitializeApplication routine.

   Returns:       Whether the class could be registered.
*/
   {  // ILineInitializeApplication
   WNDCLASS       wc ;

   wc.style =           dwILineClassStyle ;
   wc.lpfnWndProc =     ILineWndProc ;
   wc.cbClsExtra =      iILineClassExtra ;
   wc.cbWndExtra =      iILineWindowExtra ;
   wc.hInstance =       hInstance ;
   wc.hIcon =           NULL ;
   wc.hCursor =         LoadCursor (NULL, IDC_ARROW) ;
   wc.hbrBackground =   NULL ;
   wc.lpszMenuName =    NULL ;
   wc.lpszClassName =   szILineClass ;

   return (RegisterClass (&wc)) ;
   }  // ILineInitializeApplication

void ILineSetRange (HWND hWnd, int iBegin, int iEnd)

{  // ILineSetRange
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   ILBegin (pILine) = iBegin ;
   ILEnd (pILine) = iEnd ;

   ILCalcPositions (hWnd, pILine) ;
   }  // ILineSetRange

void ILineSetStart (HWND hWnd, int iStart)
   {  // ILineSetStart
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   iStart = PinInclusive (iStart, ILBegin (pILine), ILEnd (pILine)) ;
   ILStart (pILine) = iStart ;

   ILCalcPositions (hWnd, pILine) ;
   }  // ILineSetStart

void ILineSetStop (HWND hWnd, int iStop)
   {  // ILineSetStop
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   iStop = PinInclusive (iStop, ILBegin (pILine), ILEnd (pILine)) ;
   ILStop (pILine) = iStop ;

   ILCalcPositions (hWnd, pILine) ;
   }  // ILineSetStop

int ILineXStart (HWND hWnd)
   {
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   return (pILine->rectLeftGrab.left) ;
   }

int ILineXStop (HWND hWnd)
   {
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   return (pILine->rectRightGrab.right) ;
   }

int ILineStart (HWND hWnd)
   {
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   return  (ILStart (pILine)) ;
   }

int ILineStop (HWND hWnd)
   {
   PILINE         pILine ;

   pILine = ILData (hWnd) ;

   return  (ILStop (pILine)) ;
   }


INTRLINE.H

/*
==============================================================================

  Application:

            Microsoft Windows NT (TM) Performance Monitor

  File:
            intrline.h -- IntervalLine custom control.

  Written by:

            Mike Moskowitz 24 Mar 92.

  Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
==============================================================================
*/

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define szILineClass          TEXT("PerfILine")

#define ILN_SELCHANGED        (WM_USER + 0x200)

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL ILineInitializeApplication (void) ;

void ILineSetRange (HWND hWnd, int iBegin, int iEnd) ;

void ILineSetStart (HWND hWnd, int iStart) ;

void ILineSetStop (HWND hWnd, int iStop) ;

int ILineStart (HWND hWnd) ;

int ILineStop (HWND hWnd) ;

int ILineXStart (HWND hWnd) ;

int ILineXStop (HWND hWnd) ;


LEGEND.C

/*
==============================================================================

  Application:

            Microsoft Windows NT (TM) Performance Monitor

  File:
            legend.c - legend window routines.

            This file contains code creating the legend window, which is
            a child of the graph windows. The legend window displays a
            legend line for each line in the associated graph. It also
            includes an area called the label, which are headers for those
            lines.

  Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
==============================================================================
*/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdio.h>      // for sprintf
#include "perfmon.h"
#include "legend.h"     // external declarations for this file

#include "owndraw.h"

#include "alert.h"      // for EditAlert
#include "grafdata.h"   // for EditChart
#include "pmemory.h"    // for MemoryXXX (mallloc-type) routines
#include "utils.h"
#include "valuebar.h"   // for StatusTimer
#include "playback.h"   // for PlayingBackLog()
#include "perfmops.h"   // for COnvertDecimalPoint

#define eScaleValueSpace       TEXT(">9999999999.0")

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define dwGraphLegendClassStyle     (CS_HREDRAW | CS_VREDRAW)
#define iGraphLegendClassExtra      (0)
#define iGraphLegendWindowExtra     (sizeof (PLEGEND))
#define dwGraphLegendWindowStyle    (WS_CHILD | WS_VISIBLE)

#define xLegendBorderWidth          (xDlgBorderWidth)
#define yLegendBorderHeight         (yDlgBorderHeight)

#define iLabelLen                   30
#define iLegendColMax               1000

#define LEFTORIENTATION             1
#define CENTERORIENTATION           2
#define RIGHTORIENTATION            3

#define LegendColorCol              0
#define LegendScaleCol              1
#define LegendCounterCol            2
#define LegendInstanceCol           3
#define LegendParentCol             4
#define LegendObjectCol             5
#define LegendSystemCol             6

#define iLegendNumCols              7

#define iLegendMaxVisibleItems      8

#define dwGraphLegendItemsWindowClass  TEXT("ListBox")
#define dwGraphLegendItemsWindowStyle           \
   (LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_OWNERDRAWFIXED | \
    WS_VISIBLE | WS_CHILD | WS_VSCROLL)

//==========================================================================//
//                                  Typedefs                                //
//==========================================================================//

typedef struct LEGENDCOLSTRUCT
   {
   TCHAR          szLabel [iLabelLen] ;
   int            xMinWidth ;
   int            xMaxWidth ;
   int            xWidth ;
   int            xPos ;
   int            iOrientation ;
   } LEGENDCOL ;

typedef LEGENDCOL *PLEGENDCOL ;

typedef struct LEGENDTRUCT
   {
   HWND           hWndItems ;
   HFONT          hFontItems ;
   HFONT          hFontLabels ;
   int            xMinWidth ;
   int            yLabelHeight ;
   int            yItemHeight ;
   int            iNumItemsVisible ;
   LEGENDCOL      aCols [iLegendNumCols] ;
   int            iLineType ;
   PLINE          pCurrentLine ;    // current selected line
   }  LEGEND ;

typedef LEGEND *PLEGEND ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define LabelTopMargin()      (2)
#define LegendLeftMargin()    (3 + ThreeDPad)

#define LegendItemTopMargin() (yBorderHeight)

#define IsLegendLabelVisible()  (TRUE)

#define ColCanGrow(pCol)                     \
   (pCol->xMaxWidth > pCol->xWidth)

#define LegendHorzMargin()    (3)

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

// LEGEND            Legend ;
PLEGEND     pGraphLegendData ;
PLEGEND     pAlertLegendData ;

#define  LegendData(hWnd)     \
   ((hWnd == hWndGraphLegend) ? pGraphLegendData : pAlertLegendData )

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//
void LegendSetCurrentLine (PLEGEND pLegend, int iIndex)
   {
   pLegend->pCurrentLine =
      (PLINESTRUCT) LBData (pLegend->hWndItems, iIndex) ;

   if (pLegend->pCurrentLine == (PLINESTRUCT) LB_ERR)
      {
      pLegend->pCurrentLine = NULL ;
      }
   }  // LegendSetCurrentLine

#if 0
PLEGEND LegendData (HWND hWnd)
   {
   return ((PLEGEND) GetWindowLong (hWnd, 0)) ;
   }
#endif

PLEGEND AllocateLegendData (HWND hWnd, BOOL bChartLegend)
   {
   PLEGEND        pLegend ;

   pLegend = MemoryAllocate (sizeof (LEGEND)) ;
//   SetWindowLong (hWnd, 0, (LONG) pLegend) ;

   if (bChartLegend)
      {
      hWndGraphLegend = hWnd ;
      pGraphLegendData = pLegend ;
      }
   else
      {
      hWndAlertLegend = hWnd ;
      pAlertLegendData = pLegend ;
      }

   return (pLegend) ;
   }

void DrawLegendLabel (PLEGEND pLegend,
                      HDC hDC,
                      HWND hWnd)
   {  // DrawLegendLabel
   int            i ;
   int            xPos ;
   RECT           rect ;
   RECT           rectLB ;
   INT            LocalThreeDPad = ThreeDPad - 1 ;

   SetBkColor (hDC, crLightGray) ;

   SelectFont (hDC, pLegend->hFontLabels) ;

   GetClientRect (hWnd, &rect) ;
//   Fill (hDC, crLightGray, &rect) ;
   ExtTextOut (hDC, rect.left, rect.top,
      ETO_OPAQUE, &(rect), NULL, 0, NULL ) ;

   GetWindowRect (pLegend->hWndItems, &rectLB) ;
   ScreenRectToClient (hWnd, &rectLB) ;
   ThreeDConcave1 (hDC,
                  rectLB.left - LocalThreeDPad,
                  rectLB.top - LocalThreeDPad,
                  rectLB.right + LocalThreeDPad,
                  rectLB.bottom + LocalThreeDPad) ;

   for (i = 0 ;
        i < iLegendNumCols ;
        i++)
      {  // for
      rect.left = pLegend->aCols[i].xPos ;
      rect.top = yBorderHeight ;
      rect.right = rect.left + pLegend->aCols[i].xWidth - LegendHorzMargin () ;
      rect.bottom = pLegend->yLabelHeight ;

      switch (pLegend->aCols[i].iOrientation)
         {  // switch
         case LEFTORIENTATION:
            SetTextAlign (hDC, TA_LEFT) ;
            xPos = rect.left ;
            break ;

         case CENTERORIENTATION:
            SetTextAlign (hDC, TA_CENTER) ;
            xPos = (rect.left + rect.right) / 2 ;
            break ;

         case RIGHTORIENTATION:
            SetTextAlign (hDC, TA_RIGHT) ;
            xPos = rect.right ;
            break ;

         default:
            xPos = rect.left ;
            break ;
         }  // switch

      ExtTextOut (hDC,
                  xPos, rect.top,
                  ETO_OPAQUE,
                  &rect,
                  pLegend->aCols[i].szLabel,
                  lstrlen (pLegend->aCols[i].szLabel),
                  NULL) ;
      }  // for
   }  // DrawLegendLabel

void DrawColorCol (PLEGEND pLegend,
                   PLINE pLine,
                   int iCol,
                   HDC hDC,
                   int yPos)
/*
   Effect:        Draw the "color" column of a legend entry. The color
                  column displays a small sample of the line drawing.

                  For charts, the sample is a line of the correct style,
                  color, and width.  Since we are using wide lines,
                  the round endcaps of the lines will breach the column.
                  Therefore we need to set the clip region. We could
                  remove this clipping if we find a way to change the
                  end cap design.

                  For alerts, the sample is a small circle which echos
                  the design in the alert log itself.
*/
   {  // DrawColorCol
   HBRUSH         hBrush, hBrushPrevious ;
   RECT           rect ;
   int            yMiddle ;
   int            iCircle ;

   rect.left = pLegend->aCols[iCol].xPos - LegendLeftMargin () + 2 ;
   rect.top = yPos + 1 ;
   rect.right = rect.left + pLegend->aCols[iCol].xWidth - LegendHorzMargin () ;
   rect.bottom = yPos + pLegend->yItemHeight - 1 ;

   yMiddle = rect.top + (rect.bottom - rect.top) / 2 ;
   iCircle = rect.bottom - rect.top - 2 ;

  switch (pLegend->iLineType)
      {  // switch
      case LineTypeChart:

         if (pLine->Visual.iWidth == 1)
            {
            // simple case with thin pen
            hBrush = SelectBrush (hDC, hbLightGray) ;
            Rectangle (hDC, rect.left, rect.top, rect.right, rect.bottom) ;

            HLine (hDC, pLine->hPen,
                   rect.left + 1, rect.right - 1, yMiddle) ;
            SelectBrush (hDC, hBrush) ;
            }
         else
            {
            // thicker pen width, have to set ClipRect so
            // it will not draw otherside the rect.
            SaveDC (hDC) ;
            hBrush = SelectBrush (hDC, hbLightGray) ;
            Rectangle (hDC, rect.left, rect.top, rect.right, rect.bottom) ;

            IntersectClipRect (hDC,
                               rect.left + 1,
                               rect.top + 1,
                               rect.right - 1,
                               rect.bottom - 1) ;
            HLine (hDC, pLine->hPen,
                   rect.left + 1, rect.right - 1, yMiddle) ;
            SelectBrush (hDC, hBrush) ;
            RestoreDC (hDC, -1) ;
            }
         break ;

      case LineTypeAlert:
         hBrushPrevious = SelectBrush (hDC, pLine->hBrush) ;

         Ellipse (hDC,
                  rect.left + 2,
                  rect.top + 2,
                  rect.left + 2 + iCircle,
                  rect.top + 2 + iCircle) ;

         SelectBrush (hDC, hBrushPrevious) ;
         break ;
      }  // switch

   }  // DrawColorCol

void DrawLegendCol (PLEGEND pLegend,
                    int iCol,
                    HDC hDC,
                    int yPos,
                    LPTSTR lpszValue)
/*
   Effect:        Draw the value lpszValue for the column iCol on hDC.

   Assert:        The foreground and background text colors of hDC are
                  properly set.
*/
   {  // DrawLegendCol
   RECT           rect ;
   int            xPos ;

   rect.left = pLegend->aCols[iCol].xPos - LegendLeftMargin () ;
   rect.top = yPos ;
   rect.right = rect.left + pLegend->aCols[iCol].xWidth - LegendHorzMargin () ;
   rect.bottom = yPos + pLegend->yItemHeight ;

//   SetTextAlign (hDC, TA_TOP) ;
   switch (pLegend->aCols[iCol].iOrientation)
      {  // switch
      case LEFTORIENTATION:
         SetTextAlign (hDC, TA_LEFT) ;
         xPos = rect.left ;
         break ;

      case CENTERORIENTATION:
         SetTextAlign (hDC, TA_CENTER) ;
         xPos = (rect.left + rect.right) / 2 ;
         break ;

      case RIGHTORIENTATION:
         SetTextAlign (hDC, TA_RIGHT) ;
         xPos = rect.right ;
         break ;

      default:
         xPos = rect.left ;
         break ;
      }  // switch

   ExtTextOut (hDC,
               xPos, rect.top + LegendItemTopMargin (),
               ETO_OPAQUE | ETO_CLIPPED,
               &rect,
               lpszValue,
               lstrlen (lpszValue),
               NULL) ;
   }  // DrawLegendCol

void DrawLegendItem (PLEGEND pLegend,
                     PLINESTRUCT pLine,
                     int yPos,
                     HDC hDC)
   {
   TCHAR          szValue [256] ;
   TCHAR          szInstance [256] ;
   TCHAR          szParent [256] ;
   TCHAR          szNoName[4] ;

   szNoName[0] = szNoName[1] = szNoName[2] = TEXT('-') ;
   szNoName[3] = TEXT('\0') ;

   //=============================//
   // Determine Instance, Parent  //
   //=============================//

   // It's possible that there will be no instance, therefore
   // the lnInstanceName would be NULL.

   if (pLine->lnObject.NumInstances > 0)
      {
      // Test for the parent object instance name title index.
      // If there is one, it implies that there will be a valid
      // Parent Object Name and a valid Parent Object Instance Name.

      // If the Parent Object title index is 0 then
      // just display the instance name.

      lstrcpy (szInstance, pLine->lnInstanceName) ;
      if (pLine->lnInstanceDef.ParentObjectTitleIndex &&
          pLine->lnPINName)
         {
         // Get the Parent Object Name.
         lstrcpy (szParent, pLine->lnPINName) ;
         }
      else
         {
         lstrcpy (szParent, szNoName) ;
         }
      }
   else
      {
      lstrcpy (szInstance, szNoName) ;
      lstrcpy (szParent, szNoName) ;
      }

   //=============================//
   // Draw Color                  //
   //=============================//

   DrawColorCol (pLegend, pLine, LegendColorCol, hDC, yPos) ;

   //=============================//
   // Draw Scale/Value            //
   //=============================//

   if (pLegend->iLineType == LineTypeChart)
      {
      if (pLine->eScale < (FLOAT) 1.0)
         {
         TSPRINTF (szValue, TEXT("%6.6f"), pLine->eScale) ;
         }
      else
         {
         TSPRINTF (szValue, TEXT("%10.3f"), pLine->eScale) ;
         }
      ConvertDecimalPoint (szValue) ;
      }
   else
      {
      FLOAT tempAlertValue ;

      tempAlertValue = pLine->eAlertValue ;
      if (tempAlertValue < (FLOAT) 0.0)
         {
         tempAlertValue = -tempAlertValue ;
         }

      if (tempAlertValue >= (FLOAT) 10000.0)
         {
         if (tempAlertValue < (FLOAT) 1.0E+8)
            {
            TSPRINTF (szValue, TEXT("%c%10.0f"),
               pLine->bAlertOver ? TEXT('>') : TEXT('<'),
               pLine->eAlertValue) ;
            }
         else
            {
            TSPRINTF (szValue, TEXT("%c%10.3e"),
               pLine->bAlertOver ? TEXT('>') : TEXT('<'),
               pLine->eAlertValue) ;
            }
         ConvertDecimalPoint (szValue) ;
         }
      else
         {
         TSPRINTF (szValue, TEXT("%c%10.4f"),
            pLine->bAlertOver ? TEXT('>') : TEXT('<'),
            pLine->eAlertValue) ;
         ConvertDecimalPoint (szValue) ;
         }
      }

   SetTextAlign (hDC, TA_TOP) ;

   DrawLegendCol (pLegend, LegendScaleCol,
                  hDC, yPos, szValue) ;

   //=============================//
   // Draw Counter                //
   //=============================//

   DrawLegendCol (pLegend, LegendCounterCol,
                  hDC, yPos, pLine->lnCounterName) ;

   //=============================//
   // Draw Instance               //
   //=============================//

   DrawLegendCol (pLegend, LegendInstanceCol,
                  hDC, yPos, szInstance) ;

   //=============================//
   // Draw Parent                 //
   //=============================//

   DrawLegendCol (pLegend, LegendParentCol,
                  hDC, yPos, szParent) ;

   //=============================//
   // Draw Object                 //
   //=============================//

   DrawLegendCol (pLegend, LegendObjectCol,
                  hDC, yPos, pLine->lnObjectName) ;

   //=============================//
   // Draw System                 //
   //=============================//

   DrawLegendCol (pLegend, LegendSystemCol,
                  hDC, yPos, pLine->lnSystemName) ;

   }

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

BOOL OnLegendCreate (HWND hWnd, LPCREATESTRUCT lpCS)
   {
   PLEGEND        pLegend ;
   HDC            hDC ;
   int            iCol ;
   BOOL           bChartLegend ;

   bChartLegend = (lpCS->lpCreateParams) == (LPVOID) TRUE ;

   pLegend = AllocateLegendData (hWnd, bChartLegend) ;

   if (!pLegend)
      return (FALSE) ;

   if (bChartLegend)
      {
      pLegend->iLineType = LineTypeChart ;
      }
   else
      {
      pLegend->iLineType = LineTypeAlert ;
      }

   pLegend->hFontItems = hFontScales ;
   pLegend->hFontLabels = hFontScalesBold ;
   hDC = GetDC (hWnd) ;

   //=============================//
   // Load Labels                 //
   //=============================//

   StringLoad (IDS_LABELCOLOR, pLegend->aCols[LegendColorCol].szLabel) ;
   if (pLegend->iLineType == LineTypeChart)
      StringLoad (IDS_LABELSCALE,
                  pLegend->aCols[LegendScaleCol].szLabel) ;
   else
      StringLoad (IDS_LABELVALUE,
                  pLegend->aCols[LegendScaleCol].szLabel) ;
   StringLoad (IDS_LABELCOUNTER, pLegend->aCols[LegendCounterCol].szLabel) ;
   StringLoad (IDS_LABELINSTANCE, pLegend->aCols[LegendInstanceCol].szLabel) ;
   StringLoad (IDS_LABELPARENT, pLegend->aCols[LegendParentCol].szLabel) ;
   StringLoad (IDS_LABELOBJECT, pLegend->aCols[LegendObjectCol].szLabel) ;
   StringLoad (IDS_LABELSYSTEM, pLegend->aCols[LegendSystemCol].szLabel) ;

   //=============================//
   // Label dimensions            //
   //=============================//

   SelectFont (hDC, pLegend->hFontLabels) ;
   pLegend->yLabelHeight = FontHeight (hDC, TRUE) + 2 * LabelTopMargin () ;

   //=============================//
   // Column dimensions           //
   //=============================//

   for (iCol = 0 ;
        iCol < iLegendNumCols ;
        iCol++)
      {  // for
      pLegend->aCols[iCol].iOrientation = LEFTORIENTATION ;
      pLegend->aCols[iCol].xMinWidth =
         TextWidth (hDC, pLegend->aCols[iCol].szLabel) + LegendHorzMargin () ;
      }  // for

   SelectFont (hDC, pLegend->hFontItems) ;
   pLegend->yItemHeight = FontHeight (hDC, TRUE) + 2 * LegendItemTopMargin () ;

   pLegend->aCols[LegendColorCol].xMaxWidth = max (3 * xScrollWidth,
      pLegend->aCols[LegendColorCol].xMinWidth) ;

   pLegend->aCols[LegendScaleCol].xMaxWidth = TextWidth (hDC, eScaleValueSpace) ;
   pLegend->aCols[LegendCounterCol].xMaxWidth = iLegendColMax ;
   pLegend->aCols[LegendInstanceCol].xMaxWidth = iLegendColMax ;
   pLegend->aCols[LegendParentCol].xMaxWidth = iLegendColMax ;
   pLegend->aCols[LegendObjectCol].xMaxWidth = iLegendColMax ;
   pLegend->aCols[LegendSystemCol].xMaxWidth = iLegendColMax ;

   pLegend->aCols[LegendColorCol].iOrientation = LEFTORIENTATION ;
   pLegend->aCols[LegendScaleCol].iOrientation = RIGHTORIENTATION ;

   ReleaseDC (hWnd, hDC) ;

   //=============================//
   // Create Legend Items Listbox //
   //=============================//

   pLegend->hWndItems =
      CreateWindow (TEXT("ListBox"),               // window class
                    NULL,                          // window caption
                    dwGraphLegendItemsWindowStyle, // window style
                    0, 0, 0, 0,                    // window size and pos
                    hWnd,                          // parent window
                    NULL,                          // menu
                    hInstance,                     // program instance
                    (LPVOID) TRUE) ;               // user-supplied data
   }  // OnLegendCreate

void static OnSize (HWND hWnd, int xWidth, int yHeight)
/*
   Effect:        Perform all actions necessary when the legend window is
                  being resized. In particular, set the width and starting
                  position for each column of the legend list and labels.

   Internals:     What we do is determine the amount of space in the
                  width that is greater than the minimum, and allocate
                  that space among each of the columns that is willing to
                  be larger than minimum. If there is not enough room for
                  the minimum, don't bother with calculations.
*/

   {  // OnSize
   PLEGEND        pLegend ;
   PLEGENDCOL     pCol ;
   int            xMin ;
   int            xSlack ;
   int            iColsToGrow ;
   int            iCol ;

   pLegend = LegendData (hWnd) ;

   //=============================//
   // Resize Legend Items         //
   //=============================//

   if (IsLegendLabelVisible ())
      MoveWindow (pLegend->hWndItems,
                  LegendLeftMargin (), pLegend->yLabelHeight + ThreeDPad,
                  xWidth - 2 * LegendLeftMargin (),
                  yHeight - pLegend->yLabelHeight - yLegendBorderHeight,
                  TRUE) ;
   else
      MoveWindow (pLegend->hWndItems,
                  0, 0,
                  xWidth, yHeight,
                  TRUE) ;

   //=============================//
   // Allocate width to Columns   //
   //=============================//

   xMin = LegendMinWidth (hWnd) ;
   xSlack = max (xWidth - xMin, 0) ;
   iColsToGrow = 0 ;

   for (iCol = 0 ;
        iCol < iLegendNumCols ;
        iCol++)
      {  // for
      pCol = &(pLegend->aCols[iCol]) ;

      pCol->xWidth = pCol->xMinWidth ;
      if (ColCanGrow (pCol))
         iColsToGrow++ ;
      }  // for

   for (iCol = 0 ;
        iCol < iLegendNumCols ;
        iCol++)
      {  // for
      pCol = &(pLegend->aCols[iCol]) ;

      if (ColCanGrow (pCol))
         {
         if ((pCol->xWidth + xSlack / iColsToGrow) >
             pCol->xMaxWidth)
            {  // if
            pCol->xWidth = pCol->xMaxWidth ;
            xSlack -= (pCol->xMaxWidth - pCol->xMinWidth) ;
            iColsToGrow-- ;
            }  // if
         }  // if
      }  // for

   for (iCol = 0 ;
        iCol < iLegendNumCols ;
        iCol++)
      {  // for
      pCol = &(pLegend->aCols[iCol]) ;

      if (ColCanGrow (pCol))
        pCol->xWidth += xSlack / iColsToGrow ;
      }  // for

   if (pLegend->aCols[LegendCounterCol].xWidth <
      pLegend->aCols[LegendCounterCol].xMaxWidth)
      {
      // cut some from the other columns and give them to CounterCol
      if (pLegend->aCols[LegendColorCol].xWidth - xScrollWidth >=
          pLegend->aCols[LegendColorCol].xMinWidth)
         {
         pLegend->aCols[LegendColorCol].xWidth -= xScrollWidth ;
         pLegend->aCols[LegendCounterCol].xWidth += xScrollWidth ;
         }
      if (pLegend->aCols[LegendInstanceCol].xWidth - xScrollWidth >=
          pLegend->aCols[LegendInstanceCol].xMinWidth)
         {
         pLegend->aCols[LegendInstanceCol].xWidth -= xScrollWidth ;
         pLegend->aCols[LegendCounterCol].xWidth += xScrollWidth ;
         }
      if (pLegend->aCols[LegendParentCol].xWidth - xScrollWidth >=
          pLegend->aCols[LegendParentCol].xMinWidth)
         {
         pLegend->aCols[LegendParentCol].xWidth -= xScrollWidth ;
         pLegend->aCols[LegendCounterCol].xWidth += xScrollWidth ;
         }
      }

   //=============================//
   // Set Column positions        //
   //=============================//

   pLegend->aCols[0].xPos = LegendLeftMargin () ;
   for (iCol = 1 ;
        iCol < iLegendNumCols ;
        iCol++)
      {  // for
      pLegend->aCols[iCol].xPos =
         pLegend->aCols[iCol - 1].xPos + pLegend->aCols[iCol - 1].xWidth ;

      if (iCol == LegendCounterCol)
         {
         // add some space between Scale/Value col & Counter col
         pLegend->aCols[LegendCounterCol].xPos += LegendLeftMargin () ;
         }
      }  // for

   }  // OnSize

void static OnPaint (HWND hWnd)
   {  // OnPaint
   PLEGEND        pLegend ;
   HDC            hDC ;
   PAINTSTRUCT    ps ;

   hDC = BeginPaint (hWnd, &ps) ;

   if (IsLegendLabelVisible ())
      {
      pLegend = LegendData (hWnd) ;
      DrawLegendLabel (pLegend, hDC, hWnd) ;
      }

   if (LBNumItems (pLegend->hWndItems) == 0)
      {
      WindowInvalidate(pLegend->hWndItems) ;
      }

   EndPaint (hWnd, &ps) ;
   }  // OnPaint

void OnDrawLegendItem (HWND hWnd,
                        LPDRAWITEMSTRUCT lpDI)
   {  // OnDrawItem
   HFONT          hFontPrevious ;
   HDC            hDC ;
   PLEGEND        pLegend ;
   PLINESTRUCT    pLine ;
   int            iLBIndex ;
   COLORREF       preBkColor ;
   COLORREF       preTextColor ;
   BOOL           ResetColor = FALSE ;

   hDC = lpDI->hDC ;
   iLBIndex = DIIndex (lpDI) ;

   pLegend = LegendData (hWnd) ;

   if (iLBIndex == -1)
      pLine = NULL ;
   else
      pLine = (PLINESTRUCT) LBData (pLegend->hWndItems, iLBIndex) ;

   //=============================//
   // Set DC attributes           //
   //=============================//

   if (DISelected (lpDI) || pLine == NULL)
      {  // if
      preTextColor = SetTextColor (hDC, GetSysColor (COLOR_HIGHLIGHTTEXT)) ;
      preBkColor = SetBkColor (hDC, GetSysColor (COLOR_HIGHLIGHT)) ;
      ResetColor = TRUE ;
      }  // if
   ExtTextOut (hDC, lpDI->rcItem.left, lpDI->rcItem.top,
      ETO_OPAQUE, &(lpDI->rcItem), NULL, 0, NULL ) ;

   //=============================//
   // Draw Legend Item            //
   //=============================//

   hFontPrevious = SelectFont (hDC, pLegend->hFontItems) ;
   if (pLine)
      DrawLegendItem (pLegend, pLine, lpDI->rcItem.top, hDC) ;
   SelectFont (hDC, hFontPrevious) ;

   //=============================//
   // Draw Focus                  //
   //=============================//

   if (DIFocus (lpDI))
      DrawFocusRect (hDC, &(lpDI->rcItem)) ;

   if (ResetColor == TRUE)
      {  // if
      preTextColor = SetTextColor (hDC, preTextColor) ;
      preBkColor = SetBkColor (hDC, preBkColor) ;
      }  // if
   }  // OnDrawItem

void static OnMeasureItem (HWND hWnd,
                           LPMEASUREITEMSTRUCT lpMI)
   {  // OnMeasureItem
   PLEGEND        pLegend ;

   pLegend = LegendData (hWnd) ;
   lpMI->itemHeight = pLegend->yItemHeight ;
   }  // OnMeasureItem

void static OnDestroy (HWND hWnd)
   {
   PLEGEND        pLegend ;

   pLegend = LegendData (hWnd) ;
   MemoryFree (pLegend) ;
   }  // OnDestroy

void static OnDoubleClick (HWND hWnd)
   {
   PLEGEND        pLegend ;

   pLegend = LegendData (hWnd) ;
   if (!pLegend)
      return ;

   switch (pLegend->iLineType)
      {  // switch
      case LineTypeChart:
         EditChart (hWndMain) ;
         break ;

      case LineTypeAlert:
         EditAlert (hWndAlert) ;
         break ;
      }  // switch
   }  // OnDoubleClick

void static OnSetFocus (HWND hWnd)
   {  // OnSetFocus
   PLEGEND        pLegend ;

   pLegend = LegendData (hWnd) ;

   SetFocus (pLegend->hWndItems) ;
   }

void static OnSelectionChanged (HWND hWnd)
   {  // OnSelectionChanged
   PLEGEND        pLegend ;
   PGRAPHSTRUCT         pGraph ;
   int            iIndex ;

   pLegend = LegendData (hWnd) ;

   // set the new selected line
   iIndex = LBSelection (pLegend->hWndItems) ;

   if (iIndex == LB_ERR)
      return ;

   LegendSetCurrentLine (pLegend, iIndex) ;

   if (pLegend->iLineType == LineTypeChart)
      {
      pGraph = GraphData (hWndGraph) ;

      if (!PlayingBackLog())
         {
         // update the min, max, & avg of the current line
         UpdateValueBarData (pGraph) ;
         }

      // update the valuebar display
      StatusTimer (hWndGraphStatus, TRUE) ;

      // change the Current highlighted line if necessary
      if (pGraph && pGraph->HighLightOnOff)
         {
         WindowInvalidate (hWndGraphDisplay) ;
         }
      }
   }  // OnSelectionChanged

//==========================================================================//
//                             Exported Functions                           //

//==========================================================================//

LRESULT APIENTRY GraphLegendWndProc (HWND hWnd,
                                     WORD wMsg,
                                     WPARAM wParam,
                                     LONG lParam)
   {  // GraphLegendWndProc
   BOOL           bCallDefProc ;
   LRESULT        lReturnValue ;

   bCallDefProc = FALSE ;
   lReturnValue = 0L ;

   switch (wMsg)
      {  // switch
      case WM_DELETEITEM:
         break ;

      case WM_COMMAND:
         switch (HIWORD (wParam))
            {  // switch
            case LBN_DBLCLK:
               OnDoubleClick (hWnd) ;
               break ;

            case LBN_SELCHANGE:
               OnSelectionChanged (hWnd) ;
               break ;

            default:
               break ;
            }  // switch
         break ;

      case WM_CREATE:
         OnLegendCreate (hWnd, (LPCREATESTRUCT) lParam) ;
         break ;

      case WM_DESTROY:
         OnDestroy (hWnd) ;
         break ;

      case WM_DRAWITEM:
         OnDrawLegendItem (hWnd, (LPDRAWITEMSTRUCT) lParam) ;
         break ;

      case WM_MEASUREITEM:
         OnMeasureItem (hWnd, (LPMEASUREITEMSTRUCT) lParam) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_SIZE:
         OnSize (hWnd, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      case WM_SETFOCUS:
         OnSetFocus (hWnd) ;
         break ;

      default:
         bCallDefProc = TRUE ;
      }  // switch

   if (bCallDefProc)
      lReturnValue = DefWindowProc (hWnd, wMsg, wParam, lParam) ;

   return (lReturnValue);
   }  // GraphLegendWndProc

int LegendMinWidth (HWND hWnd)
   {
   PLEGEND        pLegend ;
   int            iCol ;
   int            xMinWidth ;

   pLegend = LegendData (hWnd) ;
   xMinWidth = 0 ;

   for (iCol = 0 ;
        iCol < iLegendNumCols ;
        iCol++)
      {  // for
      xMinWidth += pLegend->aCols[iCol].xMinWidth ;
      }  // for

   return (xMinWidth) ;
   }

int LegendMinHeight (HWND hWnd)
   {
   PLEGEND        pLegend ;

   pLegend = LegendData (hWnd) ;

   if (IsLegendLabelVisible ())
      return (pLegend->yLabelHeight + pLegend->yItemHeight) ;
   else
      return (pLegend->yItemHeight) ;
   }

int LegendHeight (HWND hWnd, int yGraphHeight)
/*
   Effect:        Return the best height for the Legend window given the
                  number of items and label visibility of the legend, as
                  well as the overall height of the graph.
*/
   {  // LegendHeight
   PLEGEND        pLegend ;
   int            yPreferredHeight ;

   pLegend = LegendData (hWnd) ;

   yPreferredHeight = yLegendBorderHeight +
                      pLegend->yItemHeight *
                      PinInclusive (LBNumItems (pLegend->hWndItems),
                                    1, iLegendMaxVisibleItems) ;
   if (IsLegendLabelVisible ())
      yPreferredHeight += pLegend->yLabelHeight ;

   return (min (yPreferredHeight, yGraphHeight / 2)) ;
   }  // LegendHeight

int LegendFullHeight (HWND hWnd, HDC hDC)
/*
   Effect:        Return the best height for the Legend window given the
                  number of items and label visibility of the legend, as
                  well as the overall height of the graph.
*/
   {  // LegendHeight
   PLEGEND        pLegend ;
   int            yPreferredHeight ;

   pLegend = LegendData (hWnd) ;

   yPreferredHeight = yLegendBorderHeight +
                      pLegend->yItemHeight *
                      LBNumItems (pLegend->hWndItems) ;
   if (IsLegendLabelVisible ())
      yPreferredHeight += pLegend->yLabelHeight ;

   return (yPreferredHeight) ;
   }  // LegendFullHeight

HWND CreateGraphLegendWindow (HWND hWndGraph)
   {
   return (CreateWindow (szGraphLegendClass,       // class
                         NULL,                     // caption
                         dwGraphLegendWindowStyle, // window style
                         0, 0,                     // position
                         0, 0,                     // size
                         hWndGraph,                // parent window
                         NULL,                     // menu
                         hInstance,                // program instance
                         (LPVOID) TRUE)) ;         // user-supplied data
   }

BOOL GraphLegendInitializeApplication (void)
/*
   Called By:     GraphInitializeApplication only
*/
   {
   WNDCLASS       wc ;

   wc.style          = dwGraphLegendClassStyle ;
   wc.lpfnWndProc    = (WNDPROC) GraphLegendWndProc ;
   wc.hInstance      = hInstance ;
   wc.cbClsExtra     = iGraphLegendClassExtra ;
   wc.cbWndExtra     = iGraphLegendWindowExtra ;
   wc.hIcon          = NULL ;
   wc.hCursor        = LoadCursor (NULL, IDC_ARROW) ;
   wc.hbrBackground  = hbLightGray ;
   wc.lpszMenuName   = NULL ;
   wc.lpszClassName  = szGraphLegendClass ;

   return (RegisterClass (&wc)) ;
   }

BOOL LegendAddItem (HWND hWnd, PLINESTRUCT pLine)
/*
   Effect:        Add a legend entry for the line pLine. Don't select
                  the line as current entry here, because this will cause
                  the scroll bar to unneccesarily appear for a moment.
                  If you want this line to be the current line, resize
                  the legend first, then set as current line.

   See Also:      ChartInsertLine, AlertInsertLine.
*/
   {  // LegendAddItem
   PLEGEND        pLegend ;

   pLegend = LegendData (hWnd) ;
   LBAdd (pLegend->hWndItems, pLine) ;

   return (TRUE) ;
   }  // LegendAddItem

void LegendDeleteItem (HWND hWndLegend,
                       PLINE pLine)
   {  // LegendDeleteItem
   PLEGEND        pLegend ;
   int            iIndex ;
   int            iNextIndex ;
   int            iNumItems ;
   PGRAPHSTRUCT   pGraph ;

   pLegend = LegendData (hWndLegend) ;
   if (!pLegend)
      return ;

   iNumItems = LBNumItems (pLegend->hWndItems) ;

   iIndex = LBFind (pLegend->hWndItems, pLine) ;

   if (iIndex != LB_ERR)
      {
      LBDelete (pLegend->hWndItems, iIndex) ;
      }

   // no need to do anything if iNumItems is 1 to begin with
   if (iNumItems != LB_ERR && iNumItems > 1)
      {
      if (iIndex == iNumItems - 1)
         {
         // deleting the last line, then set selection
         // to the previous legend line
         iNextIndex = iIndex - 1 ;
         }
      else
         {
         // set the selection to the next legend line
         iNextIndex = iIndex ;
         }

      LBSetSelection (pLegend->hWndItems, iNextIndex) ;
      LegendSetCurrentLine (pLegend, iNextIndex) ;

      if (pLegend->iLineType == LineTypeChart)
         {
         // update the min, max, & avg of the current line
         pGraph = GraphData (hWndGraph) ;

         if (!PlayingBackLog())
            {
            // update the min, max, & avg of the current line
            UpdateValueBarData (pGraph) ;
            }

         // update the valuebar display
         StatusTimer (hWndGraphStatus, TRUE) ;
         }
      }

   }  // LegendDeleteItem

PLINE LegendCurrentLine (HWND hWndLegend)
   {  // LegendCurrentLine
   PLEGEND        pLegend ;

   pLegend = LegendData (hWndLegend) ;

   if (!pLegend)
      return (NULL) ;

   return (pLegend->pCurrentLine) ;

   }  // LegendCurrentLine

int LegendNumItems (HWND hWndLegend)
   {  // LegendNumItems
   PLEGEND        pLegend ;

   pLegend = LegendData (hWndLegend) ;

   return (LBNumItems (pLegend->hWndItems)) ;
   }  // LegendNumItems

void LegendSetSelection (HWND hWndLegend, int iIndex)
   {  // LegendSetSelection
   PLEGEND        pLegend ;

   pLegend = LegendData (hWndLegend) ;
   LBSetSelection (pLegend->hWndItems, iIndex) ;
   LegendSetCurrentLine (pLegend, iIndex) ;
   }  // LegendSetSelection

#ifdef KEEP_PRINT

void PrintLegend (HDC hDC, PGRAPHSTRUCT pGraph, HWND hWndLegend,
                  RECT rectLegend)
   {
   PLEGEND        pLegend ;
   int            yItemHeight ;
   HFONT          hFontItems ;
   PLINE          pLine ;
   int            iIndex ;
   int            iIndexNum ;

   pLegend = LegendData (hWndLegend) ;

   yItemHeight = pLegend->yItemHeight ;
   hFontItems = pLegend->hFontItems ;

   pLegend->hFontItems = hFontPrinterScales ;
   SelectFont (hDC, pLegend->hFontItems) ;

   pLegend->yItemHeight = FontHeight (hDC, TRUE) ;

   iIndexNum = LBNumItems (pLegend->hWndItems) ;
   for (iIndex = 0 ;
        iIndex < iIndexNum ;
        iIndex++)
      {  // for
      pLine = (PLINE) LBData (pLegend->hWndItems, iIndex) ;
      DrawLegendItem (pLegend, pLine, iIndex * pLegend->yItemHeight, hDC) ;
      }  // for

   pLegend->hFontItems = hFontItems ;
   pLegend->yItemHeight = yItemHeight ;

   SelectBrush (hDC, GetStockObject (HOLLOW_BRUSH)) ;
   SelectPen (hDC, GetStockObject (BLACK_PEN)) ;
   Rectangle (hDC, 0, 0,
              rectLegend.right - rectLegend.left,
              rectLegend.bottom - rectLegend.top) ;
   }
#endif

void ClearLegend (HWND hWndLegend)
   {
   PLEGEND        pLegend ;

   pLegend = LegendData (hWndLegend) ;
   if (!pLegend)
      return ;

   LBReset (pLegend->hWndItems) ;
   pLegend->pCurrentLine = NULL ;
   }

void LegendSetRedraw (HWND hWndLegend, BOOL bRedraw)
   {
   PLEGEND        pLegend ;

   pLegend = LegendData (hWndLegend) ;
   if (!pLegend)
      return ;

   LBSetRedraw (pLegend->hWndItems, bRedraw) ;
}


LEGEND.H

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define szGraphLegendClass          TEXT("PerfLegend")
#define szGraphLegendClassA         "PerfLegend"

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

void ClearLegend (HWND hWndLegend) ;

BOOL GraphLegendInitializeApplication (void) ;

HWND CreateGraphLegendWindow (HWND hWndGraph) ;

int LegendMinHeight (HWND hWnd) ;

int LegendMinWidth (HWND hWnd) ;

void LegendDeleteItem (HWND hWndLegend,
                       PLINE pLine) ;

int LegendHeight (HWND hWnd, int yGraphHeight) ;

BOOL LegendAddItem (HWND hWndGraph,
                    PLINESTRUCT pLine) ;

PLINE LegendCurrentLine (HWND hWndLegend) ;

int LegendNumItems (HWND hWndLegend) ;

void LegendSetSelection (HWND hWndLegend, int iIndex) ;

int LegendFullHeight (HWND hWnd, HDC hDC) ;

void PrintLegend (HDC hDC, PGRAPHSTRUCT pGraph, HWND hWndLegend,
                  RECT rectLegend) ;

void LegendSetRedraw (HWND hWndLegend, BOOL bRedraw) ;


LINE.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"    // included by all source files
#include "line.h"       // external declarations for this file
//#include <tchar.h>      // for _tcsncpy

#include "fileutil.h"   // for FileRead, FileWrite
#include "pmemory.h"     // for MemoryXXX (mallloc-type) routines
#include "perfdata.h"   // for UpdateSystemData, et al
#include "perfmops.h"   // for InsertLine
#include "system.h"     // for SystemAdd
#include "utils.h"
#include "playback.h"   // for PlayingBackLog
#include "counters.h"   // CounterEntry

#include <string.h>     // for strncpy
#ifdef UNICODE
#define _tcsncpywcsncpy
#else
#define _tcsncpystrncpy
#endif

TCHAR LOCAL_SYS_CODE_NAME[] = TEXT("....") ;
#define  sizeofCodeName sizeof(LOCAL_SYS_CODE_NAME) / sizeof(TCHAR) - 1

// Local Function prototype
PLINE ReadLine (PPERFSYSTEM *ppSystem,
                PPPERFSYSTEM ppSystemFirst,
                PPERFDATA *ppPerfData,
                HANDLE hFile,
                int LineType,
                PDISKLINE  *ppDiskLine,
                DWORD *pSizeofDiskLine) ;

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

PLINE LineAllocate (void)
/*
   Effect:        Allocate and initialize a Line data structure. Lines
                  are used as the primary elements of both charts and
                  alerts.

                  Establish any representation invariants for the Line
                  type.

                  Also alllocate another structure, an array of data
                  elements, iNumDataValues long.
*/
   {  // LineAllocate
   PLINE          pLine ;

   pLine = MemoryAllocate (sizeof (LINESTRUCT)) ;

   if (pLine)
      {
//  don't need to zero these again since MemoryAllocate is using
//  GMEM_ZEROPOINT
//      pLine->pLineNext = NULL ;
//      pLine->pLineCounterNext = NULL ;

      // do want to do this since (FLOAT)0.0 is not 0
      pLine->lnMinValue =
      pLine->lnMaxValue =
      pLine->lnAveValue = (FLOAT) 0.0 ;

      if (PlayingBackLog())
         {
         pLine->bFirstTime = FALSE ;
         }
      else
         {
//         pLine->bFirstTime = TRUE ;
         // we want to take 2 samples before plotting the first data
         pLine->bFirstTime = 2 ;
         }
      }  // if

   return (pLine) ;
   }  // LineAllocate

void LineFree (PLINE pLine)
   {  // LineFree
   // free any memory allocated by this line
   if (pLine->lnSystemName)
      MemoryFree (pLine->lnSystemName) ;

   if (pLine->lnObjectName)
      MemoryFree (pLine->lnObjectName) ;

   if (pLine->lnCounterName)
      MemoryFree (pLine->lnCounterName) ;

   if (pLine->lnInstanceName)
      MemoryFree (pLine->lnInstanceName) ;

   if (pLine->lnParentObjName)
      MemoryFree (pLine->lnParentObjName) ;

   if (pLine->lnPINName)
      MemoryFree (pLine->lnPINName) ;

   if (pLine->lpszAlertProgram)
      MemoryFree (pLine->lpszAlertProgram) ;

   if (pLine->hPen)
      DeletePen(pLine->hPen);

   if (pLine->hBrush)
      DeletePen(pLine->hBrush);

   if (pLine->lnValues)
      MemoryFree (pLine->lnValues) ;

   if (pLine->aiLogIndexes)
      MemoryFree (pLine->aiLogIndexes) ;

   MemoryFree (pLine) ;
   }  // LineFree

void LineAppend (PPLINE ppLineFirst,
                 PLINE pLineNew)
   {
   PLINE          pLine ;

   if (!*ppLineFirst)
      *ppLineFirst = pLineNew ;
   else
      {  // else
      for (pLine = *ppLineFirst ;
           pLine->pLineNext ;
           pLine = pLine->pLineNext)
         /* nothing */ ;
      pLine->pLineNext = pLineNew ;
      }  // else
   }

BOOL LineRemove (PPLINE ppLineFirst,
                 PLINE pLineRemove)
   {
   PLINE          pLine ;

   if (*ppLineFirst == pLineRemove)
      {
      *ppLineFirst = (*ppLineFirst)->pLineNext ;
      return (TRUE) ;
      }

   for (pLine = *ppLineFirst ;
        pLine->pLineNext ;
        pLine = pLine->pLineNext)
      {   // for
      if (pLine->pLineNext == pLineRemove)
         {
         pLine->pLineNext = pLineRemove->pLineNext ;
         return (TRUE) ;
         }  // if
      }  // for

   return (FALSE) ;
   }  // LineRemove

int NumLines (PLINE pLineFirst)
   {  // NumLines
   PLINE          pLine ;
   int            iNumLines ;

   if (!pLineFirst)
      return (0) ;

   iNumLines = 0 ;
   for (pLine = pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for
      iNumLines++ ;
      }  // for

   return (iNumLines) ;
   }  // NumLines

LPTSTR LineInstanceName (PLINE pLine)
   {
   if (pLine->lnObject.NumInstances <= 0)
      return (NULL) ;
   else
      return (pLine->lnInstanceName) ;
   }

LPTSTR LineParentName (PLINE pLine)
   {
   if (pLine->lnObject.NumInstances <= 0)
      return (NULL) ;
   else if (pLine->lnInstanceDef.ParentObjectTitleIndex)
      return (pLine->lnPINName) ;
   else
      return (NULL) ;
   }

void LineCounterAppend (PPLINE ppLineFirst,
                        PLINE pLineNew)
   {
   PLINE          pLine ;

   if (!*ppLineFirst)
      *ppLineFirst = pLineNew ;
   else
      {  // else
      for (pLine = *ppLineFirst ;
           pLine->pLineCounterNext ;
           pLine = pLine->pLineCounterNext)
         /* nothing */ ;
      pLine->pLineCounterNext = pLineNew ;
      }  // else
   }

BOOL EquivalentLine (PLINE pLine1,
                     PLINE pLine2)
   {  // LineEquivalent
   return (pstrsame (pLine1->lnCounterName, pLine2->lnCounterName) &&
           pstrsame (pLine1->lnInstanceName, pLine2->lnInstanceName) &&
           pstrsame (pLine1->lnPINName, pLine2->lnPINName) &&
           pstrsame (pLine1->lnObjectName, pLine2->lnObjectName) &&
           pstrsamei (pLine1->lnSystemName, pLine2->lnSystemName)) ;
   }  // LineEquivalent

PLINE FindEquivalentLine (PLINE pLineToFind,
                          PLINE pLineFirst)
   {
   PLINE          pLine ;

   for (pLine = pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for
      if (EquivalentLine (pLine, pLineToFind))
         return (pLine) ;
      }  // for

   return (NULL) ;
   }  // FindEquivalentLine

// This routine is used only to read the system name from a disk string
// It is mainly for performance improvement.
LPTSTR DiskStringReadSys (PDISKSTRING pDS)
   {  // DiskStringReadSys
   LPTSTR         lpszText ;
   LPTSTR         pDiskSysName ;
   int            iIndex ;
   BOOL           bLocalSysName = FALSE ;

   if (pDS->dwLength == 0)
      {
      return (NULL) ;
      }

   if (pDS->dwLength == sizeofCodeName)
      {
      // check for LOCAL_SYS_CODE_NAME
      bLocalSysName = TRUE ;
      pDiskSysName = (LPTSTR)((PBYTE) pDS + pDS->dwOffset) ;
      for (iIndex = 0 ; iIndex < sizeofCodeName; iIndex++, pDiskSysName++)
         {
         if (*pDiskSysName != LOCAL_SYS_CODE_NAME[iIndex])
            {
            bLocalSysName = FALSE ;
            break ;
            }
         }
      }

   if (bLocalSysName)
      {
      lpszText =
         MemoryAllocate ((lstrlen(LocalComputerName)+1) * sizeof(TCHAR)) ;
      if (lpszText)
         {
         lstrcpy (lpszText, LocalComputerName) ;
         }
      }
   else
      {
      lpszText = MemoryAllocate (sizeof (TCHAR) * (pDS->dwLength + 1)) ;
      if (lpszText)
         {
         _tcsncpy ((WCHAR *)lpszText, (WCHAR *)((PBYTE) pDS + pDS->dwOffset),
                  pDS->dwLength) ;
         }
      }

   return (lpszText) ;
   }  // DiskStringReadSys

LPTSTR DiskStringRead (PDISKSTRING pDS)
   {  // DiskStringRead
   LPTSTR         lpszText ;

   if (pDS->dwLength == 0)
      {
      return (NULL) ;
      }

   lpszText = MemoryAllocate (sizeof (TCHAR) * (pDS->dwLength + 1)) ;
   if (!lpszText)
      {
      return (NULL) ;
      }

   _tcsncpy ((WCHAR *)lpszText, (WCHAR *)((PBYTE) pDS + pDS->dwOffset),
            pDS->dwLength) ;

   return (lpszText) ;
   }  // DiskStringRead

int DiskStringLength (LPTSTR lpszText)
   {
   if (!lpszText)
      return (0) ;
   else
      return (lstrlen (lpszText)) ;
   }

PBYTE DiskStringCopy (PDISKSTRING pDS, LPTSTR lpszText, PBYTE pNextFree)
   {
   if (!lpszText)
      {
      pDS->dwOffset = 0 ;
      pDS->dwLength = 0 ;
      return (pNextFree) ;
      }
   else
      {
      pDS->dwOffset = pNextFree - (PBYTE) pDS ;
      pDS->dwLength = DiskStringLength (lpszText) ;
      _tcsncpy ((WCHAR *)pNextFree, (WCHAR *)lpszText, pDS->dwLength) ;
      return (pNextFree + pDS->dwLength * sizeof(TCHAR)) ;
      }
   }  // DiskStringCopy

void CounterName (PPERFSYSTEM pSystem,
                  PPERFCOUNTERDEF pCounter,
                  LPTSTR lpszCounter)
   {  // CounterName
//!!   strclr (lpszCounter) ;
   lpszCounter [0] = TEXT('\0') ;
   QueryPerformanceName (pSystem,
                         pCounter->CounterNameTitleIndex,
                         0, 256,
                         lpszCounter,
                         FALSE) ;
   }  // CounterName

PPERFOBJECT LineFindObject (PPERFSYSTEM pSystem,
                            PPERFDATA pPerfData,
                            PLINE pLine)
/*
   Effect:        Set the lnObject field of pLine to the object with the
                  name of lnObjectName, and return TRUE. Return FALSE if
                  there is no such object.
*/
   {  // LineFindObject
   PPERFOBJECT    pObject ;

   pObject = GetObjectDefByName (pSystem, pPerfData, pLine->lnObjectName) ;

   if (pObject)
      {
      pLine->lnObject = *pObject ;
      return (pObject) ;
      }
   else
      return (NULL) ;
   }  // LineFindObject

PPERFCOUNTERDEF LineFindCounter (PPERFSYSTEM pSystem,
                                 PPERFOBJECT pObject,
                                 PPERFDATA pPerfData,
                                 PLINE pLine)
   {  // LineFindCounter
   UINT               i ;
   PPERFCOUNTERDEF   pCounter ;
   TCHAR             szCounter [256] ;

   for (i = 0, pCounter = FirstCounter (pObject) ;
        i < pObject->NumCounters ;
        i++, pCounter = NextCounter (pCounter))
      {  // for
      CounterName (pSystem, pCounter, szCounter) ;
      if (strsame (szCounter, pLine->lnCounterName))
         {
         pLine->lnCounterDef = *pCounter ;
         return (pCounter) ;
         }  // if
      }  // for

   return (NULL) ;
   }  // LineFindCounter

PPERFINSTANCEDEF LineFindInstance (PPERFDATA pPerfData,
                                   PPERFOBJECT pObject,
                                   PLINE pLine)
   {  // LineFindInstance

   PPERFINSTANCEDEF  pInstance = NULL ;

   if ((pObject->NumInstances > 0) && pLine->lnInstanceName)
      {
      // instances exist, find the right one

      if (pLine->lnUniqueID != PERF_NO_UNIQUE_ID)
         {
         pInstance = GetInstanceByUniqueID(pObject, pLine->lnUniqueID) ;
         }
      else
         {
         pInstance = GetInstanceByName(pPerfData, pObject,
                        pLine->lnInstanceName, pLine->lnPINName) ;
         }
      }

   if (pInstance)
      {
      pLine->lnInstanceDef = *pInstance ;
      }

   return (pInstance) ;
   }  // LineFindInstance

void ReadLines (HANDLE hFile,
                DWORD dwNumLines,
                PPPERFSYSTEM ppSystemFirst,
                PPLINE ppLineFirst,
                int LineType)
   {
   DWORD          i ;
   PPERFDATA      pPerfData ;
   PLINE          pLine ;
   PPERFSYSTEM    pSystem ;
   PDISKLINE      pDiskLine = NULL ;
   DWORD          SizeofDiskLine = 0 ;  // bytes in pDiskLine

   pPerfData = AllocatePerfData () ;
   pSystem = *ppSystemFirst ;

#if 0
   if (!pSystem)
      {
      pSystem = SystemAdd (ppSystemFirst, LocalComputerName) ;
      pSystem = *ppSystemFirst ; //!!
      }

   UpdateSystemData (pSystem, &pPerfData) ;
#endif
   pDiskLine = MemoryAllocate (FilePathLen) ;
   if (!pDiskLine)
      {
      // no memory to begin with, forget it
      DlgErrorBox (hWndMain, ERR_NO_MEMORY) ;
      return ;
      }
   SizeofDiskLine = FilePathLen ;

   for (i = 0 ;
        i < dwNumLines ;
        i++)
      {
      pLine = ReadLine (&pSystem, ppSystemFirst, &pPerfData, hFile,
            LineType, &pDiskLine, &SizeofDiskLine) ;
      if (pLine)
         {
         if (InsertLine (pLine) == FALSE)
            {
            // no inert occurred due to either line already existed
            // or error detected.
            LineFree (pLine) ;
            }
         }
      }

   if (pDiskLine)
      {
      MemoryFree (pDiskLine);
      }

   BuildValueListForSystems (*ppSystemFirst, *ppLineFirst) ;

   MemoryFree (pPerfData) ;
   }  // ReadLines

PLINE ReadLine (PPERFSYSTEM *ppSystem,
                PPPERFSYSTEM ppSystemFirst,
                PPERFDATA *ppPerfData,
                HANDLE hFile,
                int LineType,
                PDISKLINE  *ppDiskLine,
                DWORD *pSizeofDiskLine)

/*
   Effect:        Read in a line from the file hFile, at the current file
                  position.

   Internals:     The very first characters are a line signature, then a
                  length integer. If the signature is correct, then allocate
                  the length amount, and work with that.
*/
   {  // ReadLine
   PLINE             pLine ;

   struct
      {
      DWORD             dwSignature ;
      DWORD             dwLength ;
      } LineHeader ;

   PPERFOBJECT       pObject ;
   PPERFCOUNTERDEF   pCounter ;
   PDISKLINE         pDiskLine ;    // Local copy of the pointer
   PPERFINSTANCEDEF  pInstance ;

   pLine = NULL ;

   //=============================//
   // read and compare signature  //
   //=============================//

   if (!FileRead (hFile, &LineHeader, sizeof (LineHeader)))
      return (NULL) ;

   if (LineHeader.dwSignature != dwLineSignature ||
       LineHeader.dwLength == 0)
      {
      SetLastError (ERROR_BAD_FORMAT) ;
      return (NULL) ;
      }

   //=============================//
   // read and allocate length    //
   //=============================//

//   if (!FileRead (hFile, &dwLength, sizeof (dwLength)) || dwLength == 0)
//      return (NULL) ;

   // check if we need a bigger buffer,
   // normally, it should be the same except the first time...
   if (LineHeader.dwLength > *pSizeofDiskLine)
      {
      if (*ppDiskLine)
         {
         // free the previous buffer
         MemoryFree (*ppDiskLine);
         *pSizeofDiskLine = 0 ;
         }

      // re-allocate a new buffer
      *ppDiskLine = (PDISKLINE) MemoryAllocate (LineHeader.dwLength) ;
      if (!(*ppDiskLine))
         {
         // no memory, should flag an error...
         return (NULL) ;
         }
      *pSizeofDiskLine = LineHeader.dwLength ;
      }

   pDiskLine = *ppDiskLine ;

   //=============================//
   // copy diskline, alloc line   //
   //=============================//

   if (!FileRead (hFile, pDiskLine, LineHeader.dwLength))
      return (NULL) ;

   pLine = LineAllocate () ;
   if (!pLine)
      {
      return (NULL) ;
      }

   pLine->iLineType = pDiskLine->iLineType ;

   //=============================//
   // convert system information  //
   //=============================//

   pLine->lnSystemName = DiskStringReadSys (&(pDiskLine->dsSystemName)) ;
   if (!pLine->lnSystemName)
      goto ErrorBadLine ;

   if (!*ppSystem || !strsamei (pLine->lnSystemName, (*ppSystem)->sysName))
      {
      *ppSystem = SystemAdd (ppSystemFirst, pLine->lnSystemName) ;
      if (!*ppSystem)
         {
         SetLastError (ERROR_BAD_FORMAT) ;
         goto ErrorBadLine ;
         }
      UpdateSystemData (*ppSystem, ppPerfData) ;
      }  // if

   //=============================//
   // convert object information  //
   //=============================//

   pLine->lnObjectName = DiskStringRead (&(pDiskLine->dsObjectName)) ;
   if (!pLine->lnObjectName)
      goto ErrorBadLine ;

   pObject = LineFindObject (*ppSystem, *ppPerfData, pLine) ;

   //=============================//
   // convert counter information //
   //=============================//

   pLine->lnCounterName = DiskStringRead (&(pDiskLine->dsCounterName)) ;
   if (!pLine->lnCounterName)
      goto ErrorBadLine ;

   if (pObject)
      {
      pCounter = LineFindCounter (*ppSystem, pObject, *ppPerfData, pLine) ;
      if (!pCounter)
         {
         SetLastError (ERROR_BAD_FORMAT) ;
         goto ErrorBadLine ;
         }
      }

   //=============================//
   // convert instance info       //
   //=============================//

   pLine->lnUniqueID = pDiskLine->dwUniqueID ;
   pLine->lnInstanceName = DiskStringRead (&(pDiskLine->dsInstanceName)) ;
   pLine->lnPINName = DiskStringRead (&(pDiskLine->dsPINName)) ;

   if (pObject &&
       pLine->lnObject.NumInstances > 0 &&
       pLine->lnInstanceName == NULL)
      {
      goto ErrorBadLine ;
      }

   if (pObject) {
      pInstance = LineFindInstance (*ppPerfData, pObject, pLine) ;

      if (pInstance)
         {
         pLine->lnParentObjName = DiskStringRead (&(pDiskLine->dsParentObjName)) ;
         }
      }
   else {
      pLine->lnParentObjName = DiskStringRead (&(pDiskLine->dsParentObjName)) ;
   }

   //=============================//
   // convert chart information   //
   //=============================//

   if (LineType == IDM_VIEWCHART)
      {
      pLine->Visual = pDiskLine->Visual ;
      pLine->hPen = CreatePen (pLine->Visual.iStyle,
                               pLine->Visual.iWidth,
                               pLine->Visual.crColor) ;
      pLine->iScaleIndex = pDiskLine->iScaleIndex ;
      pLine->eScale = pDiskLine->eScale ;
      }

   //=============================//
   // convert alert information   //
   //=============================//

   if (LineType == IDM_VIEWALERT)
      {
      pLine->Visual = pDiskLine->Visual ;
      pLine->hBrush = CreateSolidBrush (pLine->Visual.crColor) ;
      pLine->bAlertOver = pDiskLine->bAlertOver ;
      pLine->eAlertValue = pDiskLine->eAlertValue ;
      pLine->lpszAlertProgram = DiskStringRead (&(pDiskLine->dsAlertProgram)) ;
      pLine->bEveryTime = pDiskLine->bEveryTime ;
      pLine->bAlerted = FALSE ;
      }

   //=============================//
   // Convert the nasty stuff     //
   //=============================//

   if (pObject) {
      pLine->lnCounterType = pCounter->CounterType;
      pLine->lnCounterLength = pCounter->CounterSize;
      }

   return (pLine) ;

ErrorBadLine:
   if (!pLine)
      {
      LineFree (pLine) ;
      }
   return (NULL) ;
   }  // ReadLine

BOOL WriteLine (PLINE pLine,
                HANDLE hFile)
   {  // WriteLine
   PDISKLINE      pDiskLine ;
   DWORD          dwSignature ;
   DWORD          dwLength ;
   PBYTE          pNextFree ;
   BOOL           bConvertName ;

   //=============================//
   // write signature             //
   //=============================//

   dwSignature = dwLineSignature ;
   if (!FileWrite (hFile, &dwSignature, sizeof (dwSignature)))
      return (FALSE) ;

   if (IsLocalComputer(pLine->lnSystemName))
      {
      bConvertName = TRUE ;
      }
   else
      {
      bConvertName = FALSE ;
      }

   //=============================//
   // compute and allocate length //
   //=============================//

   dwLength = sizeof (DISKLINE) ;
   if (bConvertName)
      {
      dwLength += DiskStringLength (LOCAL_SYS_CODE_NAME) * sizeof (TCHAR) ;
      }
   else
      {
      dwLength += DiskStringLength (pLine->lnSystemName) * sizeof (TCHAR) ;
      }
   dwLength += DiskStringLength (pLine->lnObjectName) * sizeof (TCHAR) ;
   dwLength += DiskStringLength (pLine->lnCounterName) * sizeof (TCHAR) ;
   dwLength += DiskStringLength (pLine->lnInstanceName) * sizeof (TCHAR) ;
   dwLength += DiskStringLength (pLine->lnPINName) * sizeof (TCHAR) ;
   dwLength += DiskStringLength (pLine->lnParentObjName) * sizeof (TCHAR) ;
   dwLength += DiskStringLength (pLine->lpszAlertProgram) * sizeof (TCHAR) ;

   if (!FileWrite (hFile, &dwLength, sizeof (dwLength)))
      return (FALSE) ;

   pDiskLine = (PDISKLINE) MemoryAllocate (dwLength) ;
   if (!pDiskLine)
      return (FALSE) ;

   pNextFree = (PBYTE) pDiskLine + sizeof (DISKLINE) ;

   //=============================//
   // convert fixed size fields   //
   //=============================//

   pDiskLine->iLineType = pLine->iLineType ;
   pDiskLine->dwUniqueID = pLine->lnUniqueID ;
   pDiskLine->Visual = pLine->Visual ;
   pDiskLine->iScaleIndex = pLine->iScaleIndex ;
   pDiskLine->eScale = pLine->eScale ;
   pDiskLine->bAlertOver = pLine->bAlertOver ;
   pDiskLine->eAlertValue = pLine->eAlertValue ;
   pDiskLine->bEveryTime = pLine->bEveryTime ;

   //=============================//
   // copy disk string fields     //
   //=============================//

   if (bConvertName)
      {
      pNextFree = DiskStringCopy (&pDiskLine->dsSystemName,
                                  LOCAL_SYS_CODE_NAME,
                                  pNextFree) ;
      }
   else
      {
      pNextFree = DiskStringCopy (&pDiskLine->dsSystemName,
                                  pLine->lnSystemName,
                                  pNextFree) ;
      }

   pNextFree = DiskStringCopy (&pDiskLine->dsObjectName,
                               pLine->lnObjectName,
                               pNextFree) ;

   pNextFree = DiskStringCopy (&pDiskLine->dsCounterName,
                               pLine->lnCounterName,
                               pNextFree) ;

   pNextFree = DiskStringCopy (&pDiskLine->dsParentObjName,
                               pLine->lnParentObjName,
                               pNextFree) ;

   pNextFree = DiskStringCopy (&pDiskLine->dsInstanceName,
                               pLine->lnInstanceName,
                               pNextFree) ;

   pNextFree = DiskStringCopy (&pDiskLine->dsPINName,
                               pLine->lnPINName,
                               pNextFree) ;

   pNextFree = DiskStringCopy (&pDiskLine->dsAlertProgram,
                               pLine->lpszAlertProgram,
                               pNextFree) ;

   FileWrite (hFile, pDiskLine, dwLength) ;
   MemoryFree (pDiskLine) ;
   return (TRUE) ;

//ErrorBadLine:
   MemoryFree (pDiskLine) ;
   return (FALSE) ;
   }  // WriteLine

// we are not doing printing.  In case we need this
// later, then define DO_PRINTING
#ifdef DO_PRINTING
int aiPrinterLineStyles [] =
   {
   PS_SOLID,
   PS_DASH,
   PS_DOT,
   PS_DASHDOT,
   PS_DASHDOTDOT
   } ;
#define NumPrinterLineStyles()   \
   (sizeof (aiPrinterLineStyles) / sizeof (aiPrinterLineStyles [0]))

COLORREF acrPrinterLineColors [] =
   {
   RGB (192, 192, 192),
   RGB (128, 128, 128),
   RGB (64, 64, 64),
   RGB (0, 0, 0)
   }  ;

#define NumPrinterLineColors()   \
   (sizeof (acrPrinterLineColors) / sizeof (acrPrinterLineColors [0]))
#endif      // DO_PRINTING

HPEN LineCreatePen (HDC hDC,
                    PLINEVISUAL pVisual,
                    BOOL bForPrint)
   {  // LineCreatePen
   HPEN        hPen ;
#ifdef DO_PRINTING
   LOGBRUSH    logBrush ;

   if (bForPrint)
      {
      logBrush.lbStyle = PS_SOLID ;
//!!         aiPrinterLineStyles [pVisual->iColorIndex % NumPrinterLineStyles ()] ;
      logBrush.lbColor =
         acrPrinterLineColors [pVisual->iColorIndex % NumPrinterLineColors ()] ;
      logBrush.lbHatch = 0 ;

      hPen = ExtCreatePen (logBrush.lbStyle |
                           PS_GEOMETRIC |
                           PS_ENDCAP_SQUARE |
                           PS_JOIN_BEVEL,
                           VertInchPixels (hDC, pVisual->iWidth, 20),
                           &logBrush,
                           0, NULL) ;
      }
   else
#endif
      hPen = CreatePen (pVisual->iStyle,
                        pVisual->iWidth,
                        pVisual->crColor) ;

   return (hPen) ;
   }  // LineCreatePen

VOID FreeLines (PLINESTRUCT pLineFirst)
   {  // FreeLines
   PLINESTRUCT    pLine,next_line;

   for (pLine = pLineFirst; pLine; pLine = next_line)
      {
      next_line = pLine->pLineNext;
      LineFree (pLine) ;
      }
   }  // FreeLines


LINE.H

VOID FreeLines (PLINESTRUCT pLineFirst) ;

PLINE LineAllocate (void) ;

void LineFree (PLINE pLine) ;

void LineAppend (PPLINE ppLineFirst,
                 PLINE pLineNew) ;

BOOL LineRemove (PPLINE ppLineFirst,
                 PLINE pLineRemove) ;

int NumLines (PLINE pLineFirst) ;

LPTSTR LineInstanceName (PLINE pLine) ;

LPTSTR LineParentName (PLINE pLine) ;

void LineCounterAppend (PPLINE ppLineFirst,
                        PLINE pLineNew) ;

BOOL EquivalentLine (PLINE pLine1,
                     PLINE pLine2) ;

PLINE FindEquivalentLine (PLINE pLineToFind,
                          PLINE pLineFirst) ;

BOOL WriteLine (PLINE pLine,
                HANDLE hFile) ;

void ReadLines (HANDLE hFile,
                DWORD dwNumLines,
                PPPERFSYSTEM ppSystemFirst,
                PPLINE ppLineFirst,
                int LineType) ;

HPEN LineCreatePen (HDC hDC,
                    PLINEVISUAL pVisual,
                    BOOL bForPrint) ;

PPERFOBJECT LineFindObject (PPERFSYSTEM pSystem,
                            PPERFDATA pPerfData,
                            PLINE pLine) ;

PPERFCOUNTERDEF LineFindCounter (PPERFSYSTEM pSystem,
                                 PPERFOBJECT pObject,
                                 PPERFDATA pPerfData,
                                 PLINE pLine) ;

PPERFINSTANCEDEF LineFindInstance (PPERFDATA pPerfData,
                                   PPERFOBJECT pObject,
                                   PLINE pLine) ;


LOG.C

/*****************************************************************************
 *
 *  Log.c - This module handles the Logging.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *
 ****************************************************************************/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdio.h>

#include "perfmon.h"
#include "log.h"

#include "fileutil.h"
#include "owndraw.h"
#include "pmemory.h"       // for MemoryXXX (mallloc-type) routines
#include "perfmops.h"      // for SystemAdd
#include "perfdata.h"
#include "playback.h"      // for PlayingBackLog
#include "status.h"        // for StatusUpdateIcons
#include "system.h"        // for SystemAdd
#include "utils.h"
#include "fileopen.h"      // for FileGetName
#include "command.h"

extern TCHAR LOCAL_SYS_CODE_NAME[] ;

//==========================================================================//
//                              Funtion Prototypes                          //
//==========================================================================//

BOOL LogWriteStartBookmark (HWND hWnd, SYSTEMTIME *pSystemTime) ;
BOOL LogWriteBookmarkData (HWND hWnd, PBOOKMARK pBookMark) ;
BOOL LogWriteSystemBookmark (HWND hWnd, LPTSTR SysName, BOOL DisConnect, SYSTEMTIME *pSystemTime) ;

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define LogNameMinLen             15
#define LogObjectMinLen           20

// This is set to 1 min
#define LARGE_INTERVAL            60

//=============================//
// Log Class                   //
//=============================//

#define dwLogClassStyle           (CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS)
#define iLogClassExtra            (0)
#define iLogWindowExtra           (0)
#define dwLogWindowStyle          (WS_CHILD)

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

int            xStatusWidth ;
int            xNameMinWidth ;

TCHAR          szClosed [ShortTextLen] ;
TCHAR          szCollecting [ShortTextLen] ;
// TCHAR          szPaused [ControlStringLen + 1] ;

LOGINDEXBLOCK  *pLogIndexBlock ;

#define WM_START_LOGGING  WM_USER+2

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define LogEntryN(hWndLogEntries, iIndex)        \
   ((PLOGENTRY) LBData (hWndLogEntries, iIndex))

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void LogAddEntryToList (PLOGENTRY *ppLogEntryFirst, PLOGENTRY pLogNewEntry)
{
   // insert the new entry at the beginning
   pLogNewEntry->pNextLogEntry = *ppLogEntryFirst ;
   *ppLogEntryFirst = pLogNewEntry ;
}

void LogDeleteEntryFromList (PLOGENTRY *ppLogEntryFirst, PLOGENTRY pLogEntryDel)
{
   PLOGENTRY   pLogEntry ;

   if (*ppLogEntryFirst == pLogEntryDel)
      {
      *ppLogEntryFirst = pLogEntryDel->pNextLogEntry ;
      }
   else
      {
      for (pLogEntry = *ppLogEntryFirst ;
         pLogEntry ;
         pLogEntry = pLogEntry->pNextLogEntry)
         {
         if (pLogEntry->pNextLogEntry == pLogEntryDel)
            {
            // found, remove this entry from the list
            pLogEntry->pNextLogEntry =
               pLogEntryDel->pNextLogEntry ;
            break ;
            }
         }
      }
}

// LogDeleteIndex - delete the log entry specified by iIndex
// and do memory clean-up
void static LogDeleteIndex (HWND hWndLogEntries, int iIndex)
{
   PLOGENTRY   pLogEntry ;
   PLOG        pLog ;

   pLogEntry = (PLOGENTRY) LBData(hWndLogEntries, iIndex) ;

   if (pLogEntry && pLogEntry != (PLOGENTRY)LB_ERR)
      {
      pLog = LogData (hWndLog) ;

      if (pLog->pLogEntryFirst)
         {
         LogDeleteEntryFromList (&(pLog->pLogEntryFirst), pLogEntry) ;
         }

      MemoryFree (pLogEntry) ;
      }

   LBDelete (hWndLogEntries, iIndex) ;
}

void LogEntriesChanged (HWND hWndLogEntries)
/*
   Effect:        Perform any actions needed when an entry has been added or
                  removed from the log. In particular, determine if a new
                  "Object" column width is appropriate. If it is, then
                  change the width and redraw the log entries list.
*/
   {  // LogEntriesChanged
   int         iIndex ;
   int         iIndexNum ;
   int         xCol1Width ;
   HDC         hDC ;
   PLOG        pLog ;
   PLOGENTRY   pLogEntry ;
   PPERFSYSTEM pSystem;

   pLog = LogData (hWndLog) ;
   xCol1Width = 0 ;

   hDC = GetDC (hWndLog) ;
   iIndexNum = LBNumItems (hWndLogEntries) ;

    // clear value Strings for all systems

    for (pSystem = pLog->pSystemFirst;
         pSystem;
         pSystem = pSystem->pSystemNext) {
        if (pSystem && pSystem->FailureTime == 0) {
            RemoveObjectsFromSystem (pSystem);
        }
    }

   for (iIndex = 0 ;
        iIndex < iIndexNum ;
        iIndex++)
      {  // for all items in the list
      pLogEntry = LogEntryN (hWndLogEntries, iIndex) ;
      xCol1Width = max (TextWidth (hDC, pLogEntry->szObject),
                        xCol1Width) ;

      pSystem = SystemGet (pLog->pSystemFirst, pLogEntry->szComputer);
      if (pSystem && pSystem->FailureTime == 0) {
         AppendObjectToValueList (
                pLogEntry->ObjectTitleIndex,
                pSystem->lpszValue);
         }
      }  // for

   xCol1Width += 2 * xScrollWidth ;
   xCol1Width = max (xCol1Width,
                     TextAvgWidth (hDC, LogObjectMinLen)) ;
   if (xCol1Width != pLog->xCol1Width)
      {
      pLog->xCol1Width = xCol1Width ;
      WindowInvalidate (hWndLogEntries) ;
      }
   ReleaseDC (hWndLog, hDC) ;
   }  // LogEntriesChanged

LPTSTR StatusText (int iPMStatus)
/*
   Effect:        Return a string representation of the log status
                  iPMStatus.

   Note:          Since these are globals, we can just return a pointer
                  to them. The user of this routine should not free
                  these pointers or modify the string.
*/
   {
   switch (iPMStatus)
      {  // switch
      case iPMStatusClosed:
         return (szClosed) ;

      case iPMStatusCollecting:
         return (szCollecting) ;

//      case iPMStatusPaused:
//         return (szPaused) ;

      default:
         return (szClosed) ;
      }  // switch
   }  // StatusText

PLOG AllocateLogData (HWND hWndLog)
   {
   PLOG           pLog ;

   pLog = LogData (hWndLog) ;

   pLog->iStatus = iPMStatusClosed ;
   pLog->bManualRefresh = FALSE ;

   // let's not give it a filename

/*!!
   FileCombine (pLog->szFilePath,
                szDefaultLogDirectory, szDefaultLogFileName) ;
!!*/
   strclr (pLog->szFilePath) ;

   pLog->pSystemFirst = NULL;
   pLog->lFileSize = 0L ;
   pLog->iIntervalMSecs = iDefaultLogIntervalSecs * 1000 ;

   pLog->pLogData = (PPERFDATA) MemoryAllocate (STARTING_SYSINFO_SIZE) ;
   pLog->dwDetailLevel = PERF_DETAIL_WIZARD ;
   LogEntriesChanged (hWndLogEntries) ;

   return (pLog) ;
   }  // AllocateLogData

void FreeLogData (PLOG pLog)
   {  // FreeLogData
   MemoryFree (pLog->pLogData) ;
   }  // FreeLogData

void UpdateLogSize (HWND hWnd)
/*
   Effect:        Set the size value to the current size.  Also change the
                  size entry in the status line.
*/
   {  // UpdateLogSize
   PLOG           pLog ;
   TCHAR          szSize [ShortTextLen + 1] ;

   pLog = LogData (hWnd) ;

   LongToCommaString (pLog->lFileSize, szSize) ;
   SetDlgItemText (hWnd, IDD_LOGSIZE, szSize) ;

   if (!PlayingBackLog())
      {
      StatusUpdateIcons (hWndStatus) ;
      }
   }  // UpdateLogSize

HANDLE LogAppendSetup(PLOG pLog, PLOGHEADER pLogFileHeader)
   {  // LogAppendSetup
   PLOGHEADER     pHeader ;
   LOGPOSITION    LP ;
   DWORD          lPreviousIndexBlock ;
   DWORD          lNextIndexBlock ;
   PLOGHEADER     pPlaybackLogHeader ;
   HANDLE         hMapHandle ;

   pHeader = (PLOGHEADER) FileMap(pLog->hFile, &hMapHandle) ;
   if (!pHeader ||
       !strsame(pHeader->szSignature, LogFileSignature) ||
       pHeader->wVersion != LogFileVersion ||
       pHeader->wRevision != LogFileRevision)
      {
      if (pHeader)
         {
         FileUnMap((LPVOID)pHeader, hMapHandle) ;
         }
      return 0 ;
      }

   *pLogFileHeader = *pHeader ;

   LP.pIndexBlock = FirstIndexBlock(pHeader) ;
   LP.iIndex = 0 ;
   LP.iPosition = 0 ;
   lPreviousIndexBlock = pHeader->iLength ;
   lNextIndexBlock = LP.pIndexBlock->lNextBlockOffset ;

   // since inside NextReLogIndexPosition would eventually call
   // PlaybackSeek for large log file, we have to temporarily
   // setup PlaybackLog.pHeader.   Not a good fix but it works...
   pPlaybackLogHeader = PlaybackLog.pHeader ;
   PlaybackLog.pHeader = pHeader ;
   while (NextReLogIndexPosition(&LP))
      {
      if (LP.pIndexBlock->lNextBlockOffset != lNextIndexBlock)
         {
         lPreviousIndexBlock = lNextIndexBlock ;
         lNextIndexBlock = LP.pIndexBlock->lNextBlockOffset ;
         }
      }

   // get the last system time from this log file
   if (LP.iIndex > 0)
      {
      SYSTEMTIME localSystemTime ;

      if (LogPositionSystemTime (&LP, &localSystemTime))
         {
         pLog->LastLogTime = localSystemTime ;
         }
      }

   PlaybackLog.pHeader = pPlaybackLogHeader ;
   if (!pLogIndexBlock)
      {
      pLogIndexBlock = (LOGINDEXBLOCK *) MemoryAllocate (sizeof(LOGINDEXBLOCK)) ;
      }
   *pLogIndexBlock = *LP.pIndexBlock ;
   pLog->lIndexBlockOffset = lPreviousIndexBlock ;
   pLog->iIndex = ++LP.iIndex ;
   pLog->lFileSize = FileSeekEnd(pLog->hFile, 0) ;

   FileUnMap((LPVOID)pHeader, hMapHandle) ;
   return pLog->hFile ;
   }  // LogAppendSetup

void LogRemoveCounterName (PLOG pLog)
   {
   PPERFSYSTEM    pSystem ;

   if (pLog->pBaseCounterName)
      {
      MemoryFree (pLog->pBaseCounterName) ;
      }
   pLog->pBaseCounterName = 0 ;
   pLog->lBaseCounterNameSize = 0 ;
   pLog->lBaseCounterNameOffset = 0 ;

   // clear all the system marker to indicate they have not been
   // saved
   for (pSystem = pLog->pSystemFirst ;
      pSystem ;
      pSystem = pSystem->pSystemNext)
      {
      pSystem->bSystemCounterNameSaved = FALSE ;
      }
   }

int CreateLogFile (PLOG pLog, BOOL bCreateFile, BOOL bSameFile)
   {  // CreateLogFile
   HANDLE               returnHandle ;
   LOGHEADER            LogFileHeader ;
   long                 lCurPosition ;
   LOGFILECOUNTERNAME   CounterNameRecord ;

   pLog->lFileSize = 0 ;
   memset (&(pLog->LastLogTime), 0, sizeof(SYSTEMTIME)) ;

   if (!pLogIndexBlock)
      {
      pLogIndexBlock = (LOGINDEXBLOCK *) MemoryAllocate (sizeof(LOGINDEXBLOCK)) ;
      }

   lstrcpy (pLogIndexBlock->szSignature, LogIndexSignature) ;
   pLog->hFile = FileHandleOpen (pLog->szFilePath) ;
   if (pLog->hFile != INVALID_HANDLE_VALUE)
      {
      // if this is a pre-existing log file, set up to append to it
      returnHandle = LogAppendSetup(pLog, &LogFileHeader) ;
      if (!returnHandle)
         {
         // this is not a log file...
         CloseHandle (pLog->hFile) ;
         return (ERR_BAD_LOG_FILE) ;
         }

      pLog->hFile = returnHandle ;
      }

   if (bCreateFile && (!pLog->hFile || pLog->hFile == INVALID_HANDLE_VALUE))
      {
      // Create a new log file if needed.
      pLog->hFile = FileHandleCreate (pLog->szFilePath) ;
      if (!pLog->hFile || pLog->hFile == INVALID_HANDLE_VALUE)
         return (ERR_LOG_FILE) ;
      lstrcpy (LogFileHeader.szSignature, LogFileSignature) ;
      LogFileHeader.wVersion = LogFileVersion ;
      LogFileHeader.wRevision = LogFileRevision ;
      LogFileHeader.iLength = sizeof (LOGHEADER) ;
      LogFileHeader.lBaseCounterNameOffset = 0 ;
      if (!FileWrite (pLog->hFile, &LogFileHeader, sizeof (LogFileHeader)))
         {
         CloseHandle (pLog->hFile) ;
         return (ERR_LOG_FILE) ;
         }

      pLog->iIndex = 0 ;
      pLog->lIndexBlockOffset = FileTell (pLog->hFile) ;
      FileSeekCurrent (pLog->hFile, sizeof (LOGINDEXBLOCK)) ;
      pLog->lFileSize = sizeof(LOGHEADER) + sizeof (LOGINDEXBLOCK) ;
      pLogIndexBlock->iNumIndexes = 0 ;
      pLogIndexBlock->lNextBlockOffset = 0 ;

      // get rid of any previous counter names and get ready for start
//      if (!bSameFile)
//         {
//         LogRemoveCounterName (pLog) ;
//         }
      LogRemoveCounterName (pLog) ;
      }
   else if (bCreateFile)
      {
      // this is an existing log file, setup the counter names
      // LogFileHeader already has the header info filled in
      // by LogAppendSetup
      if (!bSameFile || !pLog->pBaseCounterName)
         {
         // get rid of any previous counter names
         LogRemoveCounterName (pLog) ;

         // read the new names and get ready for start
         lCurPosition = FileTell (pLog->hFile) ;

         FileSeekBegin (pLog->hFile,
            LogFileHeader.lBaseCounterNameOffset) ;

         if (!(FileRead (pLog->hFile,
            &CounterNameRecord,
            sizeof (CounterNameRecord))))
            {
            FileSeekBegin (pLog->hFile,
               lCurPosition) ;
            goto EXIT ;
            }

         FileSeekBegin (pLog->hFile,
            CounterNameRecord.lCurrentCounterNameOffset) ;

         if (!(pLog->pBaseCounterName = MemoryAllocate (
            CounterNameRecord.lUnmatchCounterNames)))
            {
            FileSeekBegin (pLog->hFile,
               lCurPosition) ;
            goto EXIT ;
            }
         if (!(FileRead (pLog->hFile,
            pLog->pBaseCounterName,
            CounterNameRecord.lUnmatchCounterNames)))
            {
            MemoryFree (pLog->pBaseCounterName) ;
            pLog->pBaseCounterName = NULL ;
            FileSeekBegin (pLog->hFile,
               lCurPosition) ;
            goto EXIT ;
            }
         // we got the data, fill in other info
         pLog->lBaseCounterNameSize =
            CounterNameRecord.lUnmatchCounterNames ;
         pLog->lBaseCounterNameOffset =
            LogFileHeader.lBaseCounterNameOffset ;

         FileSeekBegin (pLog->hFile,
            lCurPosition) ;
         }
      }

EXIT:

   return (0) ;

   }  // CreateLogFile

void LogWriteIndexBlock (PLOG pLog)
   {
   FileSeekBegin (pLog->hFile,
                  pLog->lIndexBlockOffset) ;
   pLogIndexBlock->lNextBlockOffset = 0 ;
   FileWrite (pLog->hFile,
              (LPSTR) pLogIndexBlock,
              sizeof (LOGINDEXBLOCK)) ;
   }  // LogWriteIndexBlock

BOOL LogWriteIndex (PLOG pLog,
                    UINT uFlags,
                    SYSTEMTIME *pSystemTime,
                    LONG lDataOffset,
                    int iSystemsLogged)
   {  // LogWriteIndex
   LOGINDEX       Index ;
   long           lNextBlockOffset ;
   BOOL           WriteOK ;
   //=============================//
   // Add Index Block Entry       //
   //=============================//
   //=============================//
   // Index Block Full?           //
   //=============================//

   WriteOK = TRUE ;

   if (pLog->iIndex == LogFileBlockMaxIndexes - 1)
      {
      lNextBlockOffset = FileTell (pLog->hFile) ;
      pLogIndexBlock->lNextBlockOffset = lNextBlockOffset ;
      FileSeekBegin (pLog->hFile,
                     pLog->lIndexBlockOffset) ;
      WriteOK = FileWrite (pLog->hFile,
                           (LPSTR) pLogIndexBlock,
                           sizeof (LOGINDEXBLOCK)) ;
      if (WriteOK)
         {
         FileSeekBegin (pLog->hFile,
                        lNextBlockOffset) ;
         // Fake file end until we really write the block
         pLogIndexBlock->iNumIndexes = 0 ;
         pLogIndexBlock->lNextBlockOffset = 0 ;
         WriteOK = FileWrite (pLog->hFile,
                              (LPSTR) pLogIndexBlock,
                              sizeof (LOGINDEXBLOCK)) ;
         if (WriteOK)
            {
            pLog->lIndexBlockOffset = lNextBlockOffset ;
            pLog->iIndex = 0 ;
            pLog->lFileSize += sizeof (LOGINDEXBLOCK) ;
            }
         }
      }  // if

   //=============================//
   // Add Index Block Entry       //
   //=============================//

   Index.uFlags = uFlags ;
   Index.SystemTime = *pSystemTime ;
   Index.lDataOffset = lDataOffset ;
   Index.iSystemsLogged = iSystemsLogged ;

   pLogIndexBlock->aIndexes [pLog->iIndex] = Index ;
   pLog->iIndex++ ;
   pLogIndexBlock->iNumIndexes++ ;

   // write out the index block if the log interval if too large
   if (pLog->iIntervalMSecs >= LARGE_INTERVAL * 1000 )
      {
      LONG           lCurPosition ;

      // save the current file position
      lCurPosition = FileTell (pLog->hFile) ;

      // flush the index block to the file
      LogWriteIndexBlock (pLog) ;

      // restore previous file position since
      // LogWriteIndexBlock has messed it up
      FileSeekBegin (pLog->hFile, lCurPosition) ;
      }
   return (WriteOK) ;
   }  // LogWriteIndex

BOOL LogWritePerfData (HWND hWnd,
                       PLOG pLog,
                       PPERFDATA pPerfData,
                       SYSTEMTIME *pSystemTime,
                       DWORD iNumSystems,
                       BOOL bWriteIndex)
   {  // LogWritePerfData
   LONG           lSize ;
   BOOL           WriteOK ;
   LONG           lCurPosition ;

   lSize = pPerfData->TotalByteLength ;

   lCurPosition = FileTell (pLog->hFile) ;

   //=============================//
   // Write Perf Data             //
   //=============================//

   WriteOK = FileWrite (pLog->hFile, (LPSTR) pPerfData, lSize) ;
   if (WriteOK)
      {
      pLog->lFileSize += lSize ;

      if (bWriteIndex)
         {
         WriteOK = LogWriteIndex (pLog,
                                  LogFileIndexData,
                                  pSystemTime,
                                  lCurPosition,
                                  iNumSystems) ;
         }
      }
   if ( !WriteOK )
      {
      CloseLog (hWnd, pLog) ;
      PrepareMenu (GetMenu (hWndMain)) ;
      UpdateLogDisplay (hWnd) ;
      DlgErrorBox (hWnd, ERR_LOG_FILE, pLog->szFilePath) ;
      }
   return (WriteOK) ;
   }  // LogWritePerfData

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnSize (HWND hDlg,
                    int xWidth,
                    int yHeight)
/*
   Effect:        Perform any actions necessary when the log window (dialog)
                  is resized. In particular, move and resize some of the
                  dialogs controls.

   Internals:     The rightmost control, the log status, contains one of
                  only several values. These values are all within
                  xStatusWidth, computed at init time. Put this control
                  one scroll width away from the right edge at fixed
                  width. Move its associated text prompt with it. Then
                  use the remaining space for the filename control, which
                  can probably use it.

   To Do:         Need to consider minimum first.
*/
   {  // OnSize
   int            xStatusPos ;
   int            xStatusTextPos ;
   int            xNameWidth ;
   int            xMinWidth ;

   //=============================//
   // Enough space for minimums?  //
   //=============================//

   xMinWidth =
      xScrollWidth +                         // margin before prompt
      DialogWidth (hDlg, IDD_LOGFILETEXT) +  // width of prompt
      xNameMinWidth +                        // width of name
      xScrollWidth +
      DialogWidth (hDlg, IDD_LOGSTATUSTEXT) +
      DialogWidth (hDlg, IDD_LOGSTATUS) +
      xScrollWidth ;

   xStatusPos = xWidth - xStatusWidth - xScrollWidth ;
   DialogMove (hDlg, IDD_LOGSTATUS,
               xStatusPos, NOCHANGE,
               xStatusWidth, NOCHANGE) ;

   xStatusTextPos = xStatusPos -
                    DialogWidth (hDlg, IDD_LOGSTATUSTEXT) -
                    xScrollWidth ;
   DialogMove (hDlg, IDD_LOGSTATUSTEXT,
               xStatusTextPos, NOCHANGE,
               NOCHANGE, NOCHANGE) ;

   xNameWidth = xStatusTextPos -
                DialogWidth (hDlg, IDD_LOGFILETEXT) -
                2 * xScrollWidth ;
   DialogMove (hDlg, IDD_LOGFILE,
               NOCHANGE, NOCHANGE,
               xNameWidth, NOCHANGE) ;

   DialogMove (hDlg, IDD_LOGSIZE,
               DialogXPos (hDlg, IDD_LOGFILE), NOCHANGE,
               DialogWidth (hDlg, IDD_LOGFILE), NOCHANGE) ;

   DialogMove (hDlg, IDD_LOGINTERVALTEXT,
               DialogXPos (hDlg, IDD_LOGSTATUSTEXT), NOCHANGE,
               DialogWidth (hDlg, IDD_LOGSTATUSTEXT), NOCHANGE) ;

   DialogMove (hDlg, IDD_LOGINTERVAL,
               DialogXPos (hDlg, IDD_LOGSTATUS), NOCHANGE,
               DialogWidth (hDlg, IDD_LOGSTATUS), NOCHANGE) ;

   DialogMove (hDlg, IDD_LOGENTRIESTEXT,
               xScrollWidth, NOCHANGE, NOCHANGE, NOCHANGE) ;

   DialogMove (hDlg, IDD_LOGENTRIES,
               xScrollWidth, NOCHANGE,
               xWidth - 2 * xScrollWidth,
               yHeight - DialogYPos (hDlg, IDD_LOGENTRIES) - yScrollHeight) ;
   WindowInvalidate (hDlg) ;
   }  // OnSize

int OnCtlColor (HWND hDlg,
                       HDC hDC)
   {
   SetTextColor (hDC, crBlack) ;
   SetBkColor (hDC, crLightGray) ;
   return ((int) hbLightGray) ;
   }

void static OnInitDialog (HWND hDlg)
   {
   HDC            hDC ;
   PLOG           pLog ;

   hWndLogEntries = DialogControl (hDlg, IDD_LOGENTRIES) ;

   pLog = AllocateLogData (hDlg) ;
   if (!pLog)
      return ;

   StringLoad (IDS_CLOSED, szClosed) ;
//   StringLoad (IDS_PAUSED, szPaused) ;
   StringLoad (IDS_COLLECTING, szCollecting) ;
   UpdateLogDisplay (hDlg) ;

   hDC = GetDC (hDlg) ;
   xStatusWidth = max (TextWidth (hDC, szClosed),
                       TextWidth (hDC, szCollecting)) ;
//                       max (TextWidth (hDC, szPaused),
//                            TextWidth (hDC, szCollecting))) ;
   xStatusWidth += xScrollWidth ;

   xNameMinWidth = TextAvgWidth (hDC, LogNameMinLen) ;
   ReleaseDC (hDlg, hDC) ;
   }

void static OnDestroy (HWND hWnd)
/*
   Effect:        Perform any actions necessary when a LogDisplay window
                  is being destroyed. In particular, free the instance
                  data for the log.

                  Since we really only have one log window and one global
                  log data structure, we don't free the structure. We do,
                  however, delete the objects allocated within the structure.
*/
   {  // OnDestroy
   PLOG           pLog ;

   pLog = LogData (hWnd) ;
   FreeLogData (pLog) ;
   }  // OnDestroy

void static OnDrawItem (HWND hWnd, LPDRAWITEMSTRUCT lpDI)
   {  // OnDrawItem
   HDC            hDC ;
   RECT           rectComputer, rectObject ;
   PLOGENTRY      pLogEntry ;
   PLOG           pLog ;
   COLORREF       preBkColor ;
   COLORREF       preTextColor ;

   pLog = LogData (hWnd) ;

   pLogEntry = LogEntryN (hWndLogEntries, DIIndex (lpDI)) ;

   // LogEntryN (SendMessage) will return LB_ERR for error, have to
   // check for that case
   if (!pLogEntry || pLogEntry == (PLOGENTRY)LB_ERR)
      {
      return ;
      }

   hDC = lpDI->hDC ;

   SelectFont (hDC, hFontScales) ;

   if (DISelected (lpDI))
      {  // if
      preTextColor = SetTextColor (hDC, GetSysColor (COLOR_HIGHLIGHTTEXT)) ;
      preBkColor = SetBkColor (hDC, GetSysColor (COLOR_HIGHLIGHT)) ;
      }  // if

   rectObject.left = lpDI->rcItem.left ;
   rectObject.top = lpDI->rcItem.top ;
   rectObject.right = rectObject.left + pLog->xCol1Width ;
   rectObject.bottom = lpDI->rcItem.bottom ;

   ExtTextOut (hDC,
               rectObject.left + xScrollWidth, rectObject.top,
               ETO_OPAQUE,
               &rectObject,
               pLogEntry->szObject,
               lstrlen (pLogEntry->szObject),
               NULL) ;

   rectComputer.left = rectObject.right ;
   rectComputer.top = lpDI->rcItem.top ;
   rectComputer.right = lpDI->rcItem.right ;
   rectComputer.bottom = lpDI->rcItem.bottom ;

   ExtTextOut (hDC,
               rectComputer.left, rectComputer.top,
               ETO_OPAQUE,
               &rectComputer,
               pLogEntry->szComputer,
               lstrlen (pLogEntry->szComputer),
               NULL) ;

   if (DIFocus (lpDI))
      DrawFocusRect (hDC, &(lpDI->rcItem)) ;

   if (DISelected (lpDI))
      {  // if
      preTextColor = SetTextColor (hDC, preTextColor) ;
      preBkColor = SetBkColor (hDC, preBkColor) ;
      }  // if
//   RestoreDC (hDC, -1) ;
   }  // OnDrawItem

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

int APIENTRY LogDisplayDlgProc (HWND hDlg,
                                unsigned iMessage,
                                WPARAM wParam,
                                LONG lParam)
/*
   Note:          This function must be exported in the application's
                  linker-definition file, perfmon.def.
*/
   {  // LogDisplayDlgProc
//   HDC            hDC ;

   switch (iMessage)
      {
      case WM_INITDIALOG:
         OnInitDialog (hDlg) ;
         break ;

      case WM_CTLCOLORDLG:
      case WM_CTLCOLOREDIT:
      case WM_CTLCOLORBTN:
      case WM_CTLCOLORSTATIC:
         return (OnCtlColor (hDlg, (HDC) wParam)) ;
         break ;

      case WM_DRAWITEM:
         OnDrawItem (hDlg, (LPDRAWITEMSTRUCT) lParam) ;
         break ;

      case WM_LBUTTONDBLCLK:
         SendMessage (hWndMain, WM_LBUTTONDBLCLK, wParam, lParam) ;
         break ;

      case WM_LBUTTONDOWN:
         DoWindowDrag (hDlg, lParam) ;
         break ;

      case WM_SIZE:
         OnSize (hDlg, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      case WM_TIMER:
         LogTimer (hDlg, FALSE) ;
         break ;

      case WM_SETFOCUS:
         SetFocus (hWndLogEntries) ;
         break ;

      case WM_START_LOGGING:
         {
         PLOG        pLog ;

         pLog = LogData (hDlg) ;
         if (StartLog (hDlg, pLog, FALSE))
            {
            UpdateLogDisplay (hDlg) ;
            PrepareMenu (GetMenu (hWndMain)) ;
            }
         }
         break ;

      case WM_DESTROY:
         OnDestroy (hDlg) ;
         return (FALSE) ;
         break ;

      default:
         return (FALSE) ;
      } // switch

   return (TRUE) ;
   }  // LogDisplayDlgProc

#if 0
PLOG LogData (HWND hWndLog)
   {
   return (&Log) ;
   }
#endif

HWND CreateLogWindow (HWND hWndParent)
/*
   Effect:        Create the Log window. This window is a child of
                  hWndMain.

   Note:          We dont worry about the size here, as this window
                  will be resized whenever the main window is resized.

*/
   {  // CreateLogWindow
   HWND           hWnd ;
   hWnd = CreateDialog (hInstance,
                        MAKEINTRESOURCE (idDlgLogDisplay),
                        hWndParent,
                        (DLGPROC) LogDisplayDlgProc) ;

   return (hWnd) ;
   }  // CreateLogWindow

void UpdateLogDisplay (HWND hWnd)
/*
   Effect:        Set the values for the various controls in the log
                  display.

   Called By:     OnInitDialog, any other routines that change these
                  values.
*/
   {  // UpdateLogDisplay
   PLOG           pLog ;
   WCHAR          szSize [MiscTextLen + 1] ;

   pLog = LogData (hWnd) ;

   DialogSetString (hWnd, IDD_LOGFILE, pLog->szFilePath) ;

   // position the cursor at the end of the text
   EditSetTextEndPos (hWnd, IDD_LOGFILE) ;

   DialogSetString (hWnd, IDD_LOGSTATUS, StatusText (pLog->iStatus)) ;

   LongToCommaString (pLog->lFileSize, szSize) ;
   DialogSetString (hWnd, IDD_LOGSIZE, szSize) ;

   DialogSetInterval (hWnd, IDD_LOGINTERVAL, pLog->iIntervalMSecs) ;
   }  // UpdateLogDisplay

BOOL LogInitializeApplication (void)
   {
   return (TRUE) ;
   }  // LogInitializeApplication

void SetLogTimer (HWND hWnd,
                  int iIntervalMSecs)
   {
   PLOG           pLog ;

   pLog = LogData (hWnd) ;
   pLog->iIntervalMSecs = iIntervalMSecs ;

   KillTimer (hWnd, LogTimerID) ;
   SetTimer (hWnd, LogTimerID, pLog->iIntervalMSecs, NULL) ;
   }

void ClearLogTimer (HWND hWnd)
   {
   KillTimer (hWnd, LogTimerID) ;
   }

BOOL CloseLogStopTimer (HWND hWnd, PLOG pLog)
   {
   CloseHandle (pLog->hFile) ;

   pLog->hFile = 0 ;
   pLog->iStatus = iPMStatusClosed ;

   ClearLogTimer (hWnd) ;

   return (TRUE) ;
   }

BOOL CloseLog (HWND hWnd, PLOG pLog)
   {  // CloseLog
   LogWriteIndexBlock (pLog) ;
   CloseLogStopTimer (hWnd, pLog) ;
   WindowInvalidate (hWndStatus) ;
   return (TRUE) ;
   } // CloseLog

BOOL StartLog (HWND hWnd, PLOG pLog, BOOL bSameFile)
   {
   int            RetCode ;
   SYSTEMTIME     SystemTime ;

   if ((RetCode = CreateLogFile (pLog, TRUE, bSameFile)) == 0)
      {
      pLog->iStatus = iPMStatusCollecting ;

      GetLocalTime (&SystemTime) ;

      // write a dummy record.
      // this is needed because when playingback log
      // it will skip the first index from the first
      // index block.
      LogWriteIndex (pLog, 0, &SystemTime, 0, 0) ;

      if (!PlayingBackLog())
         {
         // write out a bookmark to indicate start of new data
         if (!LogWriteStartBookmark (hWnd, &SystemTime))
            {
            RetCode = 0 ;
            goto ErrorExit ;
            }

         if (!(pLog->bManualRefresh))
            {
            SetLogTimer (hWnd, pLog->iIntervalMSecs) ;
            WindowInvalidate (hWndStatus) ;
            }
         }
      else
         {
         // check if time if OK
         LOGPOSITION    LP ;
         SYSTEMTIME     FirstSystemTime ;
         int            TimeDiff ;

         LP = PlaybackLog.StartIndexPos ;

         if (LogPositionSystemTime (&LP, &FirstSystemTime))
            {
            // we don't want to append data to the log file if
            // the time is not in order.  So, forget it if the
            // last log time in the Log file is greater than the First
            // log time of the playback log file.
            TimeDiff = SystemTimeDifference (&(FirstSystemTime),
                                             &(pLog->LastLogTime)) ;
            if (TimeDiff > 0)
               {
               // error , time not in order
               CloseHandle (pLog->hFile) ;
               RetCode = ERR_CANT_RELOG_DATA ;
               }
            }
         }

      if (RetCode == 0)
         {
         // write counter names if needed
         LogWriteSystemCounterNames (hWnd, pLog) ;
         return (TRUE) ;
         }
      }

ErrorExit:
   pLog->hFile = 0 ;
   CloseLogStopTimer(hWnd, pLog);
   PrepareMenu (GetMenu (hWndMain)) ;
   UpdateLogDisplay (hWnd) ;
   if (RetCode)
      {
      DlgErrorBox (hWnd, RetCode, pLog->szFilePath);
      }
   return (FALSE) ;
   }

DWORD LogFindEntry(LPTSTR lpszComputer, DWORD ObjectTitleIndex)
/*
   Effect:         Returns the index of the specified Computer/Object
                   if it already exists in the Entries List Box,
                   otherwise returns LOG_ENTRY_NOT_FOUND
*/
   {
   DWORD          iLogEntry ;
   DWORD          iLogNum ;
   PLOGENTRY      pLogEntry ;

   iLogNum = (DWORD) LBNumItems(hWndLogEntries) ;
   for (iLogEntry = 0;
        iLogEntry < iLogNum ;
        iLogEntry++)
      {
      pLogEntry = (PLOGENTRY) LBData(hWndLogEntries, iLogEntry) ;
      if (pLogEntry->ObjectTitleIndex == ObjectTitleIndex &&
          strsamei(pLogEntry->szComputer, lpszComputer))
         {
         return iLogEntry ;
         }
      }
   return (DWORD) LOG_ENTRY_NOT_FOUND;
   }

BOOL LogAddEntry (HWND hWndLog,
                  LPTSTR lpszComputer,
                  LPTSTR lpszObject,
                  DWORD ObjectTitleIndex,
                  BOOL  bGetObjectTitleIndex)
/*
   Effect:        Add an entry in the log structure for the computer and
                  object to be logged.

   Returns:       Whether the operation could be performed.
*/
   {  // LogAddEntry
   PLOG           pLog ;
   PLOGENTRY      pLogEntry ;
   UINT           iIndex ;
   PPERFSYSTEM    pCurrentSystem = NULL ;
   DWORD          CurrentObjectTitleIndex ;

   pLog = LogData (hWndLog) ;

   pCurrentSystem = SystemAdd (&(pLog->pSystemFirst), lpszComputer) ;

   pLogEntry = MemoryAllocate (sizeof (LOGENTRY)) ;
   if (!pLogEntry)
      return (FALSE) ;

   lstrcpy (pLogEntry->szComputer, lpszComputer) ;
   lstrcpy (pLogEntry->szObject, lpszObject) ;

   pLogEntry->ObjectTitleIndex = ObjectTitleIndex ;

   // if reading from a log setting file, get the
   // latest Object index by the perfdata itself.
   // There may be case that the id has been changed
   if (bGetObjectTitleIndex &&
      pCurrentSystem &&
      pCurrentSystem->pSystemPerfData)
      {
      if (pCurrentSystem->pSystemPerfData->Signature[0] == TEXT('\0'))
         {
         UpdateSystemData (
            pCurrentSystem,
            &(pCurrentSystem->pSystemPerfData)) ;
         }

      if (CurrentObjectTitleIndex = GetObjectIdByName(
         pCurrentSystem,
         pCurrentSystem->pSystemPerfData,
         lpszObject))
         {
         pLogEntry->ObjectTitleIndex = CurrentObjectTitleIndex ;
         }
      }

   iIndex = LBAdd (hWndLogEntries, pLogEntry) ;

   if (!bDelayAddAction)
      {
      if (iIndex == LB_ERR)
         {
         iIndex = 0 ;
         }

      LBSetSelection (hWndLogEntries, iIndex) ;
      LBSetVisible (hWndLogEntries, iIndex) ;

      LogEntriesChanged (hWndLogEntries) ;
      }

   LogAddEntryToList (&(pLog->pLogEntryFirst), pLogEntry) ;

   }  // LogAddEntry

BOOL ToggleLogRefresh (HWND hWnd)
   {  // ToggleLogRefresh
   PLOG        pLog ;

   pLog = LogData (hWnd) ;

   if (pLog->bManualRefresh)
      SetLogTimer (hWnd, pLog->iIntervalMSecs) ;
   else
      ClearLogTimer (hWnd) ;

   pLog->bManualRefresh = !pLog->bManualRefresh ;
   return (pLog->bManualRefresh) ;
   }  // ToggleLogRefresh

BOOL LogRefresh (HWND hWnd)
   {  // LogRefresh
   PLOG        pLog ;

   pLog = LogData (hWnd) ;

   return (pLog->bManualRefresh) ;
   }  // LogRefresh

BOOL  CheckUnusedSystem (LPTSTR      lpszComputer)
{
   BOOL  bStillUse = FALSE ;
   PLOGENTRY      pLogEntry ;
   PLOG           pLog ;

   pLog = LogData (hWndLog) ;

   for (pLogEntry = pLog->pLogEntryFirst ;
        pLogEntry ;
        pLogEntry = pLogEntry->pNextLogEntry)
      { // for
      if (strsamei(pLogEntry->szComputer, lpszComputer))
         {
         bStillUse = TRUE ;
         break ;
         }
      }
   return (bStillUse) ;
}

int SelectLogObjects(LPTSTR      lpszComputer,
                     PPERFDATA   pPerfData,
                     PPERFDATA   *ppLogData)
/*
   Effect:        This routine copies the header from pPerfData
                  to pLogData and initializes the byte length and the
                  number of objects.  It then copies the previously
                  selected objects from pPerfData to pLogData.  If
                  pLogData must be enlarged to accomodate the new data,
                  this routine will enlarge it.

   Returns:       An updated pLogData, and TRUE if at least one object
                  was copied.

*/

   {
   PLOGENTRY      pLogEntry ;
   PPERF_OBJECT_TYPE
                  pObject ;
   DWORD          TotalBytes ;
   DWORD          NumObjects ;
   PBYTE          pNextObject ;
   DWORD          MaxLogDataSize ;
   PLOG           pLog ;

   if (!*ppLogData || !pPerfData)
      return -1 ;

   memcpy (*ppLogData, pPerfData, pPerfData->HeaderLength) ;
   TotalBytes = pPerfData->HeaderLength ;
   MaxLogDataSize = MemorySize(*ppLogData) ;
   NumObjects = 0;

   pLog = LogData (hWndLog) ;

   for (pLogEntry = pLog->pLogEntryFirst ;
        pLogEntry ;
        pLogEntry = pLogEntry->pNextLogEntry)
      { // for
      if (strsamei(pLogEntry->szComputer, lpszComputer))
         {
         pObject = GetObjectDefByTitleIndex(pPerfData,
            pLogEntry->ObjectTitleIndex) ;

         if (pObject)
            {
            if (MaxLogDataSize < TotalBytes + pObject->TotalByteLength)
               {
               *ppLogData = MemoryResize(*ppLogData,
                                         TotalBytes + pObject->TotalByteLength) ;
               if (!*ppLogData)
                  return -1 ;

               }

            pNextObject = (PBYTE) *ppLogData + TotalBytes ;
            memcpy (pNextObject, pObject, pObject->TotalByteLength);
            TotalBytes += pObject->TotalByteLength ;
            NumObjects++;
            }
         else
            {
            }
         }
      } // for

   if (!NumObjects)
      return 1 ;

   (*ppLogData)->TotalByteLength = TotalBytes ;
   (*ppLogData)->NumObjectTypes = NumObjects ;

   return 0 ;
   }

void LogTimer (HWND hWnd, BOOL bForce)
/*
   Effect:        Perform all actions necessary when the log window
                  receives a timer tic. In particular, if we are
                  collecting data, get a new perf_data_block and add a
                  header entry. If the header block is full, write the
                  data to disk.

   Called By:     LogDisplayDlgProc, in response to a WM_TIMER message.
*/
   {  // OnTimer
   PLOG           pLog ;
   PPERFSYSTEM       pSystem ;
   BOOL           bWriteIndex ;
   DWORD          iNumSystems ;
   SYSTEMTIME     SystemTime ;
   int            iNoUseSystemDetected = 0 ;

   int               NumberOfSystems = 0 ;
   DWORD             WaitStatus ;
   BOOL              bNeedToStoreName = FALSE ;
   HANDLE            *lpPacketHandles ;

   pLog = LogData (hWnd) ;

   if (pLog->iStatus != iPMStatusCollecting)
      return ;

   if (bForce || !pLog->bManualRefresh)
      {
      if (pLog->NumberOfHandles == 0)
         {
         pLog->NumberOfHandles = MAXIMUM_WAIT_OBJECTS ;
         pLog->lpHandles = (HANDLE *) MemoryAllocate (pLog->NumberOfHandles * sizeof (HANDLE)) ;
         if (!pLog->lpHandles)
            {
            // out of memory, can't go on
            pLog->NumberOfHandles = 0 ;
            return ;
            }
         }

      iNumSystems = SystemCount(pLog->pSystemFirst) ;
      bWriteIndex = TRUE ;

      for (pSystem = pLog->pSystemFirst ;
           pSystem ;
           pSystem = pSystem->pSystemNext)
         {  // for

         if (pSystem->hStateDataMutex == 0)
            continue ;

         // lock the state data mutex
         WaitStatus = WaitForSingleObject(pSystem->hStateDataMutex, 100L);
         if (WaitStatus == WAIT_OBJECT_0)
            {
            ResetEvent (pSystem->hPerfDataEvent) ;
            pSystem->StateData = WAIT_FOR_PERF_DATA ;

            if (NumberOfSystems >= pLog->NumberOfHandles)
               {
               pLog->NumberOfHandles += MAXIMUM_WAIT_OBJECTS ;
               pLog->lpHandles = (HANDLE *) MemoryResize (
                  pLog->lpHandles,
                  pLog->NumberOfHandles * sizeof (HANDLE)) ;
               if (!pLog->lpHandles)
                  {
                  // out of memory, can't go on
                  pLog->NumberOfHandles = 0 ;
                  return ;
                  }
               }

            // add this to the wait
            pLog->lpHandles [NumberOfSystems] = pSystem->hPerfDataEvent ;
            NumberOfSystems++ ;
            }

         // Send Message to thread to take a data sample
         PostThreadMessage (
            pSystem->dwThreadID,
            WM_GET_PERF_DATA,
            (WPARAM)0,
            (LPARAM)0) ;

         ReleaseMutex(pSystem->hStateDataMutex);
         }  // for each system

      // wait for all the data
      if (NumberOfSystems)
         {
         // increase timeout if we are monitoring lots of systems
         // For every additional 5 systems, add five more seconds
         lpPacketHandles = pLog->lpHandles ;
         do
            {
            WaitStatus = WaitForMultipleObjects (
               min (NumberOfSystems, MAXIMUM_WAIT_OBJECTS),
               lpPacketHandles,
               TRUE,       // wait for all objects
               DataTimeOut + (NumberOfSystems / 5) * DEFAULT_DATA_TIMEOUT);

            if (WaitStatus == WAIT_TIMEOUT ||
               NumberOfSystems <= MAXIMUM_WAIT_OBJECTS)
               {
//if (WaitStatus == WAIT_TIMEOUT)
//mike2(TEXT("WaitTimeOut for %ld systems\n"), NumberOfSystems) ;
               break ;
               }

            // more systems --> more to wait
            NumberOfSystems -= MAXIMUM_WAIT_OBJECTS ;
            lpPacketHandles += MAXIMUM_WAIT_OBJECTS ;
            } while (TRUE) ;

         for (pSystem = pLog->pSystemFirst ;
              pSystem ;
              pSystem = pSystem->pSystemNext)
            {  // for

            if (pSystem->hStateDataMutex == 0)
               continue ;

            // lock the state data mutex
            WaitStatus = WaitForSingleObject(pSystem->hStateDataMutex, 100L);
            if (WaitStatus == WAIT_OBJECT_0)
               {
               // check for system disconnect/reconnect
               if (pSystem->dwSystemState == SYSTEM_DOWN ||
                   pSystem->dwSystemState == SYSTEM_RECONNECT)
                  {
                  BOOL  bDisconnected ;

                  GetLocalTime (&SystemTime) ;
                  if (bDisconnected = (pSystem->dwSystemState == SYSTEM_DOWN))
                     {
                     pSystem->dwSystemState = SYSTEM_DOWN_RPT ;
                     }
                  else
                     {
                     pSystem->dwSystemState = SYSTEM_RECONNECT_RPT ;
                     }
                  if (!LogWriteSystemBookmark (
                     hWnd,
                     pSystem->sysName,
                     bDisconnected,
                     &SystemTime))
                     {
                     pSystem->StateData = IDLE_STATE ;
                     ReleaseMutex(pSystem->hStateDataMutex);
                     return ;
                     }
                  }

               if (pSystem->StateData == PERF_DATA_READY)
                  {
                  if (pSystem->bSystemCounterNameSaved == FALSE)
                     {
                     // we have not written the system name to log file.  This
                     // is the case when this system is down when we first
                     // start logging data...
                     bNeedToStoreName = TRUE ;
                     }

                  if (bWriteIndex)
                     {
                     GetLocalTime (&SystemTime) ;
                     }

                  if (SelectLogObjects(pSystem->sysName,
                     pSystem->pSystemPerfData,
                     &pLog->pLogData) == 0)
                     {
                     if ( !LogWritePerfData (hWnd, pLog, pLog->pLogData, &SystemTime,
                        iNumSystems, bWriteIndex) )
                        {
                        CloseLogStopTimer(hWnd, pLog) ;

                        pSystem->StateData = IDLE_STATE ;
                        ReleaseMutex(pSystem->hStateDataMutex);

                        return ;
                        }
                     // write an index for only the first system
                     bWriteIndex = FALSE ;
                     }
                  else
                     {
                     if (!bAddLineInProgress)
                        {
                        pSystem->bSystemNoLongerNeeded = TRUE ;
                        iNoUseSystemDetected ++ ;
                        }
                     }
                  }  // if PERF_DATA_READY
               else if (!bAddLineInProgress &&
                  CheckUnusedSystem (pSystem->sysName) == FALSE)
                  {
                  // we don't need this system any more
                  pSystem->bSystemNoLongerNeeded = TRUE ;
                  iNoUseSystemDetected ++ ;
                  }

               pSystem->StateData = IDLE_STATE ;
               ReleaseMutex(pSystem->hStateDataMutex);
               }  // wait for StateDataMutex
            }  // For each system
         }  // if NumberOfSystems

      if (!bWriteIndex)
         {
         UpdateLogSize (hWnd) ;
         }
      }  // if

   if (iNoUseSystemDetected)
      {
      DeleteUnusedSystems (&(pLog->pSystemFirst), iNoUseSystemDetected) ;
      }

   if (bNeedToStoreName == TRUE)
      {
      LogWriteSystemCounterNames (hWnd, pLog) ;
      }
   }  // LogTimer

BOOL NextIntervalIndexPosition (PLOG pLog, PLOGPOSITION pLP, int *pNumTics)
   {
   SYSTEMTIME     SystemTime1 ;
   SYSTEMTIME     SystemTime2 ;
   LOGPOSITION    LP ;
   PLOGINDEX      pIndex ;
   DWORD          TimeDiff ;

   LogPositionSystemTime (pLP, &SystemTime1) ;

   LP = *pLP ;

   while (NextReLogIndexPosition (&LP))
      {  // while

      *pNumTics = *pNumTics - 1 ;

      pIndex = IndexFromPosition (&LP) ;
      if (pIndex && IsBookmarkIndex (pIndex))
         {
         *pLP = LP ;
         return TRUE ;
         }
      LogPositionSystemTime (&LP, &SystemTime2) ;
      TimeDiff = (DWORD) SystemTimeDifference (&SystemTime1, &SystemTime2) ;
      if (TimeDiff * 1000 >= pLog->iIntervalMSecs)
         {  // if
         *pLP = LP ;
         return (TRUE) ;
         }  // if
      }  // while

   return (FALSE) ;
   }  // NextIntervalIndexPosition

BOOL ReLogTimer (HWND hWnd,
                 PLOG pLog,
                 LOGPOSITION lp,
                 BOOL *pWriteBookmark)
   {  // ReLogTimer
   PPERFSYSTEM    pSystem ;
   BOOL           bWriteIndex ;
   DWORD          iNumSystems ;
   SYSTEMTIME     SystemTime ;
   PPERFDATA      pPerfData ;

   bWriteIndex = TRUE ;

   // First count number of systems to be logged

   iNumSystems = 0;

   for (pSystem = pLog->pSystemFirst ;
        pSystem ;
        pSystem = pSystem->pSystemNext)
      {  // for
      pPerfData = LogDataFromPosition (pSystem, &lp) ;
      if (pPerfData)
         {
         if (SelectLogObjects(pSystem->sysName,
                              pPerfData,
                              &pLog->pLogData) == 0)
            {
            iNumSystems++;
            }
         }
      }  // for

   // Now we can log the data

   for (pSystem = pLog->pSystemFirst ;
        pSystem ;
        pSystem = pSystem->pSystemNext)
      {  // for
      pPerfData = LogDataFromPosition (pSystem, &lp) ;
      if (pPerfData)
         {
         // write an index for only the first system
         LogPositionSystemTime (&lp, &SystemTime) ;
         if (SelectLogObjects(pSystem->sysName,
                              pPerfData,
                              &pLog->pLogData) == 0)
            {
            if (*pWriteBookmark)
               {
               // only need to write the start bookmark once.
               *pWriteBookmark = FALSE ;
               LogWriteStartBookmark (hWnd, &SystemTime) ;
               }
            if ( !LogWritePerfData (hWnd, pLog, pLog->pLogData, &SystemTime,
                                    iNumSystems, bWriteIndex) )
               {
               CloseLogStopTimer(hWnd, pLog) ;
               return FALSE ;
               }
            else
               {
               // write the index for only the first system logged
               bWriteIndex = FALSE ;
               }
            }
         }
      }  // for

   return TRUE ;
   }  // ReLogTimer

void ReLog (HWND hWndLog, BOOL bSameFile)
   {  // PlaybackLog
   PLOG           pLog ;
   LOGPOSITION    lp ;
//   SYSTEMTIME     SystemTime ;
   PLOGINDEX      pIndex ;
   PBOOKMARK      pBookmark;
   int            iDisplayTics ;

   // bWriteBookmark tell relogtimer to write start bookmark
   BOOL           bWriteBookmark = TRUE ;

   pLog = LogData (hWndLog) ;
   if (StartLog (hWndLog, pLog, bSameFile) == FALSE)
      {
      return ;
      }

   lp = PlaybackLog.StartIndexPos ;
   iDisplayTics = PlaybackLog.iSelectedTics;

   while (iDisplayTics > 0)
      {
      pIndex = IndexFromPosition (&lp) ;
      if (pIndex)
         {
         if (IsBookmarkIndex (pIndex))
            {
            pBookmark = (PBOOKMARK) PlaybackSeek (pIndex->lDataOffset) ;
            if (!LogWriteBookmarkData (hWndLog, pBookmark))
               break;
            }
         else if (!ReLogTimer (hWndLog, pLog, lp, &bWriteBookmark))
            break ;
         }

      if (!NextIntervalIndexPosition (pLog, &lp, &iDisplayTics))
         break ;

      }  // while
   UpdateLogSize (hWndLog) ;
   CloseLog (hWndLog, pLog) ;
   }  // ReLog

// SaveLog is diff than other because we are not saving a "Line"
// We are actually saving an entry in the hWndLogEntries listbox.
// It only contains the system & object name.
BOOL SaveLog (HWND hWndLog, HANDLE hInputFile, BOOL bGetFileName)
   {
   int         iIndex, iIndexNum ;
   PLOG        pLog ;
   PLOGENTRY   pLogEntry ;
   LOGENTRY       tempLogEntry ;
   HANDLE         hFile ;
   DISKLOG        DiskLog ;
   PERFFILEHEADER FileHeader ;
   TCHAR          szFileName [256] ;
   BOOL           newFileName = FALSE ;

   pLog = LogData (hWndLog) ;
   if (!pLog)
      {
      return (FALSE) ;
      }

   if (hInputFile)
      {
      // use the input file handle if it is available
      // this is the case for saving workspace data
      hFile = hInputFile ;
      }
   else
      {
      if (pLogFullFileName)
         {
         lstrcpy (szFileName, pLogFullFileName) ;
         }
      if (bGetFileName || pLogFullFileName == NULL)
         {
//         if (pLogFullFileName == NULL)
//            {
//            StringLoad (IDS_LOG_FNAME, szFileName) ;
//            }

         if (!FileGetName (hWndLog, IDS_LOGFILE, szFileName))
            {
            return (FALSE) ;
            }
         newFileName = TRUE ;
         }

      hFile = FileHandleCreate (szFileName) ;

      if (hFile && newFileName)
         {
         ChangeSaveFileName (szFileName, IDM_VIEWLOG) ;
         }
      else if (!hFile)
         {
         DlgErrorBox (hWndLog, ERR_CANT_OPEN, szFileName) ;
         }
      }

   if (!hFile)
      return (FALSE) ;

   iIndexNum = LBNumItems (hWndLogEntries) ;

   if (!hInputFile)
      {
      memset (&FileHeader, 0, sizeof (FileHeader)) ;
      lstrcpy (FileHeader.szSignature, szPerfLogSignature) ;
      FileHeader.dwMajorVersion = LogMajorVersion ;
      FileHeader.dwMinorVersion = LogMinorVersion ;

      if (!FileWrite (hFile, &FileHeader, sizeof (PERFFILEHEADER)))
         {
         goto Exit0 ;
         }
      }

   DiskLog.dwIntervalSecs = pLog->iIntervalMSecs ;
   DiskLog.dwNumLines = iIndexNum ;
   DiskLog.bManualRefresh = pLog->bManualRefresh ;
   DiskLog.perfmonOptions = Options ;
   lstrcpy(DiskLog.LogFileName, pLog->szFilePath) ;

   if (!FileWrite (hFile, &DiskLog, sizeof (DISKLOG)))
      {
      goto Exit0 ;
      }

   for (iIndex = 0 ;
        iIndex < iIndexNum ;
        iIndex++)
      {  // for
      pLogEntry = LogEntryN (hWndLogEntries, iIndex) ;
      if (pstrsamei (pLogEntry->szComputer, LocalComputerName))
         {
         tempLogEntry = *pLogEntry ;
         lstrcpy (tempLogEntry.szComputer, LOCAL_SYS_CODE_NAME) ;
         if (!FileWrite (hFile,
               &tempLogEntry,
               sizeof(LOGENTRY)-sizeof(pLogEntry->pNextLogEntry)))
            {
            goto Exit0 ;
            }
         }
      else
         {
         if (!FileWrite (hFile,
               pLogEntry,
               sizeof(LOGENTRY)-sizeof(pLogEntry->pNextLogEntry)))
            {
            goto Exit0 ;
            }
         }
      }  // for

   if (!hInputFile)
      {
      CloseHandle (hFile) ;
      }

   return (TRUE) ;

Exit0:
   if (!hInputFile)
      {
      CloseHandle (hFile) ;

      // only need to report error if not workspace
      DlgErrorBox (hWndLog, ERR_SETTING_FILE, szFileName) ;
      }
   return (FALSE) ;
   }

BOOL OpenLogVer1 (HWND hWndLog, HANDLE hFile, DISKLOG *pDiskLog, PLOG
   pLog, DWORD dwMinorVersion)
   {
   int            iIndex, iIndexNum ;
   PLOGENTRY      pLogEntry ;
   LOGENTRY       LogEntry ;
   PPERFSYSTEM    pSystem;

   pLog->iIntervalMSecs = pDiskLog->dwIntervalSecs ;
   if (dwMinorVersion < 3)
      {
      pLog->iIntervalMSecs *= 1000 ;
      }

   pLog->lFileSize = 0L ;
   if (dwMinorVersion >= 5)
      {
      lstrcpy (pLog->szFilePath, pDiskLog->LogFileName) ;
      }
   else
      {
      strclr (pLog->szFilePath) ;

      // fixed the file pointer for backward compatible with older version.
//      FileSeekCurrent (hFile, -((int) (sizeof (pDiskLog->LogFileName)))) ;
      }

   pLog->bManualRefresh = pDiskLog->bManualRefresh ;
   iIndexNum = pDiskLog->dwNumLines ;

   LBSetRedraw (hWndLogEntries, FALSE) ;

   bDelayAddAction = TRUE ;

   for (iIndex = 0 ; iIndex < iIndexNum ; iIndex++)
      {
      if (!FileRead (hFile,
            &LogEntry,
            sizeof(LOGENTRY)-sizeof(LogEntry.pNextLogEntry)))
         {
         break ;
         }

      if (pstrsame (LogEntry.szComputer, LOCAL_SYS_CODE_NAME))
         {
         // convert it back to the local name
         lstrcpy (LogEntry.szComputer, LocalComputerName) ;
         }

      LogAddEntry (hWndLog,
                  LogEntry.szComputer,
                  LogEntry.szObject,
                  LogEntry.ObjectTitleIndex,
                  TRUE) ;
      }

   bDelayAddAction = FALSE ;

   LBSetSelection (hWndLogEntries, 0) ;
   LBSetVisible (hWndLogEntries, 0) ;

   LogEntriesChanged (hWndLogEntries) ;

   LBSetRedraw (hWndLogEntries, TRUE) ;

   for (pSystem = pLog->pSystemFirst ;
      pSystem ;
      pSystem = pSystem->pSystemNext)
      {
      if (pSystem)
         {
         RemoveObjectsFromSystem (pSystem);
         }
      }

   for (iIndex = 0 ;
        iIndex < iIndexNum ;
        iIndex++)
      {  // for all items in the list
      pLogEntry = LogEntryN (hWndLogEntries, iIndex) ;

      pSystem = SystemGet (pLog->pSystemFirst, pLogEntry->szComputer);
      if (pSystem)
         {
         AppendObjectToValueList (
                pLogEntry->ObjectTitleIndex,
                pSystem->lpszValue);
         }
      }  // for

   if (!strempty(pLog->szFilePath))
      {
      if (pLog->pLogEntryFirst &&
          pLog->pSystemFirst)
         {
         // PostMessage so it will start logging
         PostMessage (
            hWndLog,
            WM_START_LOGGING,
            0,
            0) ;
         }
      else
         {
         HANDLE   hLogFile ;

         // get the file size.
         hLogFile = FileHandleOpen (pLog->szFilePath) ;

         if (hLogFile && hLogFile != INVALID_HANDLE_VALUE)
            {
            pLog->lFileSize = GetFileSize (hLogFile, NULL);

CloseHandle (hLogFile) ;
            }
         }
      }

   return (TRUE) ;
   }

BOOL OpenLog (HWND hWndLog,
              HANDLE hFile,
              DWORD dwMajorVersion,
              DWORD dwMinorVersion,
              BOOL bLogFile)
   {
   PLOG        pLog ;
   DISKLOG     DiskLog ;
   BOOL        bSuccess = TRUE ;

   pLog = LogData (hWndLog) ;
   if (!pLog)
      {
      bSuccess = FALSE ;
      goto Exit0 ;
      }

   if (!FileRead (hFile, &DiskLog, sizeof (DISKLOG) - sizeof(DiskLog.LogFileName)))
      {
      bSuccess = FALSE ;
      goto Exit0 ;
      }

   if (dwMajorVersion == 1 && dwMinorVersion >= 5 ||
      dwMajorVersion > 1)
      {
      // read LogFileName
      if (!FileRead (hFile, DiskLog.LogFileName, sizeof (DiskLog.LogFileName)))
         {
         bSuccess = FALSE ;
         goto Exit0 ;
         }
      }

   switch (dwMajorVersion)
      {
      case (1):

         SetHourglassCursor() ;

         ResetLogView (hWndLog) ;

         OpenLogVer1 (hWndLog, hFile, &DiskLog, pLog, dwMinorVersion) ;

         // change to log view if we are opening a
         // log file
         if (bLogFile && iPerfmonView != IDM_VIEWLOG)
            {
            SendMessage (hWndMain, WM_COMMAND, (LONG)IDM_VIEWLOG, 0L) ;
            }

         if (iPerfmonView == IDM_VIEWLOG)
            {
            SetPerfmonOptions (&DiskLog.perfmonOptions) ;
            }

         UpdateLogDisplay (hWndLog) ;

         SetArrowCursor() ;

         break ;
      }

Exit0:

   if (bLogFile)
      {
      CloseHandle (hFile) ;
      }

   return (bSuccess) ;
   }  // OpenLog

BOOL LogCollecting (HWND hWndLog)
/*
   Effect:        Return whether the log associated with hWndLog is currently
                  collecting data (writing performance values to disk).
*/
   {  // LogCollecting
   PLOG           pLog ;

   pLog = LogData (hWndLog) ;

   return (pLog->iStatus == iPMStatusCollecting) ;
   }  // LogCollecting

int LogFileSize (HWND hWndLog)
   {
   PLOG           pLog ;

   pLog = LogData (hWndLog) ;

   return (pLog->lFileSize) ;
   }

BOOL LogWriteSystemBookmark (
   HWND     hWnd,
   LPTSTR   lpSystemName,
   BOOL     bDisconnected,
   SYSTEMTIME *pSystemTime)
   {
   BOOKMARK       Bookmark ;
   TCHAR    NewSystemBookmark [MiscTextLen * 2] ;

   memset (&Bookmark, 0, sizeof (BOOKMARK)) ;

   NewSystemBookmark [0] = TEXT('\0') ;
   StringLoad (
      bDisconnected ?
         IDS_SYSTEM_DOWN :
         IDS_SYSTEM_UP,
      NewSystemBookmark) ;

   lstrcat (NewSystemBookmark, TEXT(" - ")) ;
   lstrcat (NewSystemBookmark, lpSystemName) ;
   Bookmark.SystemTime = *pSystemTime ;
   lstrcpy (Bookmark.szComment, NewSystemBookmark) ;

   return (LogWriteBookmarkData (hWndLog, &Bookmark)) ;
   }

BOOL LogWriteStartBookmark (HWND hWnd, SYSTEMTIME *pSystemTime)
   {
   BOOKMARK       Bookmark ;
   TCHAR    NewDataBookmark [MiscTextLen] ;

   memset (&Bookmark, 0, sizeof (BOOKMARK)) ;

   NewDataBookmark [0] = TEXT('\0') ;
   StringLoad (IDS_NEWDATA_BOOKMARK, NewDataBookmark) ;
   Bookmark.SystemTime = *pSystemTime ;
   lstrcpy (Bookmark.szComment, NewDataBookmark) ;

   return (LogWriteBookmarkData (hWndLog, &Bookmark)) ;
   }

BOOL LogWriteBookmarkData (HWND hWnd, PBOOKMARK pBookmark)
   {
   PLOG           pLog ;
   LONG           lDataOffset ;
   BOOL           WriteOK ;

   pLog = LogData (hWndLog) ;
   if (!pLog)
      return (FALSE) ;

   lDataOffset = FileTell (pLog->hFile) ;
   WriteOK = FileWrite (pLog->hFile, pBookmark, sizeof (BOOKMARK)) ;
   if ( WriteOK )
      {
      pLog->lFileSize += sizeof (BOOKMARK) ;
      UpdateLogSize (hWndLog) ;

      WriteOK = LogWriteIndex (pLog, LogFileIndexBookmark,
                               &(pBookmark->SystemTime),
                               lDataOffset,
                               0) ;
      }
   if ( !WriteOK )
      {
      CloseLog (hWndLog, pLog) ;
      PrepareMenu (GetMenu (hWndMain)) ;
      UpdateLogDisplay (hWndLog) ;
      DlgErrorBox (hWndLog, ERR_LOG_FILE, pLog->szFilePath);
      }
   return WriteOK ;
   }

BOOL LogWriteBookmark (HWND hWndLog,
                       LPCTSTR lpszComment)
   {  // LogWriteBookmark
   BOOKMARK       Bookmark ;

   memset (&Bookmark, 0, sizeof (BOOKMARK)) ;

   GetLocalTime (&Bookmark.SystemTime) ;
   lstrcpy (Bookmark.szComment, lpszComment) ;

   return (LogWriteBookmarkData (hWndLog, &Bookmark)) ;
   }  // LogWriteBookmark

BOOL AnyLogLine (void)
{  // CurrentLogLine
   int iIndex ;

   iIndex = LBSelection (hWndLogEntries) ;
   if (iIndex == LB_ERR)
      {
      return (FALSE) ;
      }
   else
      {
      return (TRUE) ;
      }
}

void ResetLogView (HWND hWndLog)
{
   PLOG        pLog ;

   pLog = LogData (hWndLog) ;

   ChangeSaveFileName (NULL, IDM_VIEWLOG) ;

   if (pLog && pLog->pSystemFirst)
      {
      ResetLog (hWndLog) ;
      }
}  // ResetLogView

BOOL ResetLog (HWND hWndLog)
{
   int         iIndex ;
   PLOG        pLog ;
   int         iEntriesNum ;

   pLog = LogData (hWndLog) ;

   if (LogCollecting (hWndLog))
      {
      CloseLog (hWndLog, pLog) ;
      }

   LBSetRedraw (hWndLogEntries, FALSE) ;
   iEntriesNum = LBNumItems (hWndLogEntries) ;

   // only need to zero out the list head
   // each item will be deleted by LogDeleteIndex via the listbox
   pLog->pLogEntryFirst = NULL ;

   // delete each line
   for (iIndex = iEntriesNum - 1 ;
        iIndex >= 0 ;
        iIndex-- )
      {
      LogDeleteIndex (hWndLogEntries, iIndex) ;
      }

   LBSetRedraw (hWndLogEntries, TRUE) ;

   if (pLog->pSystemFirst)
      {
      FreeSystems (pLog->pSystemFirst) ;
      pLog->pSystemFirst = NULL ;
      }

   MemoryFree (pLog->pLogData) ;

   pLog->pLogData = (PPERFDATA) MemoryAllocate (STARTING_SYSINFO_SIZE) ;

   LogEntriesChanged (hWndLogEntries) ;

   pLog->iStatus = iPMStatusClosed ;
   UpdateLogDisplay (hWndLog) ;

   return (TRUE) ;
}

BOOL LogDeleteEntry (HWND hWndLog)
{
   int         iIndex ;
   PLOG        pLog ;
   BOOL        retCode ;
   int         iEntriesNum ;

   pLog = LogData (hWndLog) ;

   iIndex = LBSelection (hWndLogEntries) ;

   if (iIndex == LB_ERR)
      {
      retCode = FALSE ;
      }
   else
      {
      // remove the current selection
      LogDeleteIndex (hWndLogEntries, iIndex) ;

      iEntriesNum = LBNumItems (hWndLogEntries) ;

      if (iEntriesNum == 0 || iEntriesNum == LB_ERR)
         {
         // delete the last line or something bad happened,
         // then reset the window.
         ResetLog (hWndLog) ;
         }
      else
         {
         // set selection on the item above the deleted item.
         iIndex-- ;
         if (iIndex < 0)
            {
            iIndex = 0 ;
            }
         LBSetSelection (hWndLogEntries, iIndex) ;
         LBSetVisible (hWndLogEntries, iIndex) ;
         }

      LogEntriesChanged (hWndLogEntries) ;
      retCode = TRUE ;
      }
   return (retCode) ;
}

void ExportLog (void)
{
   HANDLE      hFile ;
   PLOG        pLog ;
   PLOGENTRY   pLogEntry ;
   int         iIndex ;
   int         iIndexNum ;
   CHAR        TempBuff [LongTextLen * 2] ;
   TCHAR       UnicodeBuff [LongTextLen] ;
   TCHAR       UnicodeBuff1 [MiscTextLen] ;
   int         StringLen ;
   LPTSTR      pFileName = NULL ;
   INT         ErrCode = 0 ;

   if (!(pLog = LogData (hWndLog)))
      {
      return ;
      }

   // see if there is anything to export..
   iIndexNum = LBNumItems (hWndLogEntries) ;
   if (iIndexNum == 0 || iIndexNum == LB_ERR)
      {
      return ;
      }

   if (!FileGetName (hWndLog, IDS_EXPORTFILE, UnicodeBuff))
      {
      // user cancel
      return ;
      }

   pFileName = StringAllocate (UnicodeBuff) ;

   // open the file..
   if (!(hFile = FileHandleCreate (UnicodeBuff)))
      {
      // can't open the file
      ErrCode = ERR_CANT_OPEN ;
      return ;
      }

   SetHourglassCursor() ;

   // get header
   StringLoad (IDS_REPORT_HEADER, UnicodeBuff) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   StringLen = strlen (TempBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], LocalComputerName) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   if (!(strempty(pLog->szFilePath)))
      {
      // export filename is there is one
      StringLoad (IDS_REPORT_LOGFILE, UnicodeBuff) ;
      ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
      StringLen = strlen (TempBuff) ;
      ConvertUnicodeStr (&TempBuff[StringLen], pLog->szFilePath) ;
      strcat (TempBuff, LineEndStr) ;

      if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
         {
         ErrCode = ERR_EXPORT_FILE ;
         goto Exit0 ;
         }
      }

   // export interval
   StringLoad (IDS_CHARTINT_FORMAT, UnicodeBuff1) ;
   TSPRINTF (UnicodeBuff, UnicodeBuff1,
       (FLOAT) pLog->iIntervalMSecs / (FLOAT) 1000.0) ;
   ConvertDecimalPoint (UnicodeBuff) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   strcat (TempBuff, LineEndStr) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   // export Labels
   StringLoad (IDS_LABELOBJECT, UnicodeBuff) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   strcat (TempBuff, pDelimiter) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   StringLoad (IDS_LABELSYSTEM, UnicodeBuff) ;
   ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      ErrCode = ERR_EXPORT_FILE ;
      goto Exit0 ;
      }

   // export each counter
   for (iIndex = 0 ; iIndex < iIndexNum ; iIndex++)
      {  // for

      pLogEntry = LogEntryN (hWndLogEntries, iIndex) ;

      if (!pLogEntry || pLogEntry == (PLOGENTRY)LB_ERR)
         {
         continue ;
         }

      ConvertUnicodeStr (TempBuff, pLogEntry->szObject) ;
      strcat (TempBuff, pDelimiter) ;

      if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
         {
         ErrCode = ERR_EXPORT_FILE ;
         break ;
         }

      ConvertUnicodeStr (TempBuff, pLogEntry->szComputer) ;
      strcat (TempBuff, LineEndStr) ;

      if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
         {
         ErrCode = ERR_EXPORT_FILE ;
         break ;
         }
      }

Exit0:

   SetArrowCursor() ;
   CloseHandle (hFile) ;

   if (pFileName)
      {
      if (ErrCode)
         {
         DlgErrorBox (hWndGraph, ErrCode, pFileName) ;
         }
      MemoryFree (pFileName) ;
      }

}  // ExportLog

LPTSTR   MatchSystemCounters (LPTSTR pBaseSysCounter,
   long   lBaseSysSize,
   LPTSTR pSysCounter,
   long   lSysSize,
   long   *pMatchPortion)
{
   LPTSTR   pNotMatch = NULL ;
   long     i, lSizeToCompare ;

   *pMatchPortion = 0 ;
   lSizeToCompare = min (lBaseSysSize, lSysSize) / sizeof (TCHAR) ;

   for (i = 0 ; i < lSizeToCompare ; i++, pBaseSysCounter++, pSysCounter++)
      {
      if (*pBaseSysCounter != *pSysCounter)
         {
         pNotMatch = pSysCounter ;
         break ;
         }
      }

   if (pNotMatch == NULL)
      {
      if (lBaseSysSize < lSysSize)
         {
         // the new system has longer counter names than base system
         // setup the extra portion.
         pNotMatch = pSysCounter ;
         }
      else
         {
         // new system counter name is shorter than or equal to
         // the base system counter names
         *pMatchPortion = lSysSize ;
         }
      }

   return (pNotMatch) ;

}

void LogWriteSystemCounterNames (HWND hWnd, PLOG pLog)
   {
   long           dwArraySize ;
   PPERFSYSTEM    pSystem = pLog->pSystemFirst ;
   LPTSTR         pMatchLen ;
   LPTSTR         pCounterName ;
   long           lMatchLen, lMatchPortion ;

   for (pSystem = pLog->pSystemFirst ;
        pSystem ;
        pSystem = pSystem->pSystemNext)
      {
      if (pSystem->bSystemCounterNameSaved == TRUE ||
          pSystem->CounterInfo.dwLastId == 0)
         {
         // we have either wrote out the counter name for
         // this system, or this system is not connect when
         // reading in the setting file,  skip it then.
         continue ;
         }

      dwArraySize = (pSystem->CounterInfo.dwLastId + 1 ) ;

      if (!pLog->lBaseCounterNameOffset)
         {
         LogWriteCounterName (hWnd, pSystem, pLog,
            (LPTSTR)(pSystem->CounterInfo.TextString + dwArraySize),
            0,
            pSystem->CounterInfo.dwCounterSize,
            0 ) ;
         }
      else
         {
         // check for matched characters between this system and the
         // base system
         pCounterName = (LPTSTR)(pSystem->CounterInfo.TextString + dwArraySize) ;
         pMatchLen = MatchSystemCounters (pLog->pBaseCounterName,
                        pLog->lBaseCounterNameSize,
                        pCounterName,
                        pSystem->CounterInfo.dwCounterSize,
                        &lMatchPortion) ;

         if (pMatchLen)
            {
            // This system matches part of the base system
            // (all if it has more names)
            lMatchLen = (long) (pMatchLen - pCounterName) * sizeof (TCHAR) ;
            LogWriteCounterName (hWnd, pSystem, pLog,
               pMatchLen,
               lMatchLen,
               pSystem->CounterInfo.dwCounterSize - lMatchLen,
               0 ) ;
            }
         else
            {
            // This system matches the based system
            LogWriteCounterName (hWnd, pSystem, pLog,
               NULL,
               lMatchPortion,
               0,
               0 ) ;
            }
         }
      }
   } // LogWriteSystemCounterNames

BOOL LogWriteCounterName (HWND hWnd,
                          PPERFSYSTEM pSystem,
                          PLOG   pLog,
                          LPTSTR pCounterName,
                          long sizeMatched,
                          long sizeOfData,
                          BOOL bBaseCounterName)
   {
   BOOL                 ReadOK ;
   BOOL                 WriteOK ;
   SYSTEMTIME           SystemTime ;
   LOGFILECOUNTERNAME   CounterNameRecord ;
   LOGHEADER            LogFileHeader ;
   long                 lDataOffset, lCurPosition ;
   TCHAR                Dummy [sizeof(DWORD)] ;
   int                  PatchBytes ;

   if (pSystem->bSystemCounterNameSaved == TRUE)
      return FALSE ;

   GetLocalTime (&SystemTime) ;

   lCurPosition = FileTell (pLog->hFile) ;

   lstrcpy (CounterNameRecord.szComputer, pSystem->sysName) ;
   CounterNameRecord.lBaseCounterNameOffset = pLog->lBaseCounterNameOffset ;
   CounterNameRecord.lCurrentCounterNameOffset =
      lCurPosition + sizeof (LOGFILECOUNTERNAME) ;
   CounterNameRecord.lMatchLength = sizeMatched ;
   CounterNameRecord.lUnmatchCounterNames = sizeOfData ;
   CounterNameRecord.dwLastCounterId = pSystem->CounterInfo.dwLastId ;
   CounterNameRecord.dwLangId = pSystem->CounterInfo.dwLangId ;
   WriteOK = FileWrite (pLog->hFile, &CounterNameRecord,
      sizeof (CounterNameRecord)) ;

   if (WriteOK)
      {
      pLog->lFileSize += sizeof (LOGFILECOUNTERNAME) ;

      if (sizeOfData)
         {

         WriteOK = FileWrite (pLog->hFile, pCounterName, sizeOfData) ;

         if (WriteOK && (PatchBytes = sizeOfData % sizeof(DWORD)) > 0)
            {
            // ensure the file is in DWORD boundary.
            WriteOK = FileWrite (pLog->hFile, Dummy, PatchBytes) ;
            }

         if (WriteOK)
            {
            pLog->lFileSize += sizeOfData + PatchBytes ;

            if (!pLog->lBaseCounterNameOffset)
               {
               // this is the first counter name data block
               // then update the log file header
               lDataOffset = FileTell (pLog->hFile) ;

               FileSeekBegin (pLog->hFile, 0L) ;

               ReadOK = FileRead (pLog->hFile,
                  &LogFileHeader,
                  sizeof (LogFileHeader)) ;

               if (ReadOK)
                  {
                  LogFileHeader.lBaseCounterNameOffset = lCurPosition ;
                  FileSeekBegin (pLog->hFile, 0L) ;
                  WriteOK = FileWrite (pLog->hFile,
                     &LogFileHeader,
                     sizeof (LogFileHeader)) ;
                  }
               else
                  {
                  // flag an error
                  WriteOK = FALSE ;
                  }

               // retore back to current file position
               FileSeekBegin (pLog->hFile, lDataOffset) ;

               if (ReadOK && WriteOK)
                  {
                  // allocate memory to save the base system counter names
                  if (pLog->pBaseCounterName)
                     {
                     MemoryFree (pLog->pBaseCounterName) ;
                     }
                  if (pLog->pBaseCounterName = MemoryAllocate (sizeOfData))
                     {
                     memcpy (pLog->pBaseCounterName,
                        pCounterName,
                        sizeOfData) ;
                     pLog->lBaseCounterNameOffset = lCurPosition ;
                     pLog->lBaseCounterNameSize = sizeOfData ;
                     }
                  }
               }  // if (!pLog->lBaseCounterNameOffset)
            }
         }  // if (sizeOfData)
      }

   if ( WriteOK )
      {
      WriteOK = LogWriteIndex (pLog, LogFileIndexCounterName,
                               &SystemTime,
                               lCurPosition,
                               0) ;
      }

   if ( !WriteOK )
      {
      CloseLog (hWndLog, pLog) ;
      PrepareMenu (GetMenu (hWndMain)) ;
      UpdateLogDisplay (hWndLog) ;
      DlgErrorBox (hWndLog, ERR_LOG_FILE, pLog->szFilePath);
      }
   else
      {
      UpdateLogSize (hWnd) ;
      pSystem->bSystemCounterNameSaved = TRUE ;
      }

   return (TRUE) ;
   }


LOG.H

#define LOG_ENTRY_NOT_FOUND (-1)

#define szDefaultLogDirectory    TEXT("")
#define szDefaultLogFileName     TEXT("perfmon.log")

#define iDefaultLogIntervalSecs  15

#define IsDataIndex(pIndex)      \
   (pIndex->uFlags & LogFileIndexData)

#define IsBookmarkIndex(pIndex)  \
   (pIndex->uFlags & LogFileIndexBookmark)

#define IsCounterNameIndex(pIndex)  \
   (pIndex->uFlags & LogFileIndexCounterName)

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

#if 0
PLOG LogData (HWND hWndLog) ;
#endif
#define LogData(hWndLog)      \
   (&Log)

HWND CreateLogWindow (HWND hWndParent) ;

LRESULT APIENTRY LogWndProc (HWND hWnd,
                               WORD wMsg,
                               DWORD wParam,
                               LONG lParam) ;

BOOL LogInitializeApplication  (void) ;

void UpdateLogDisplay (HWND hWnd) ;

BOOL StartLog (HWND hWnd, PLOG pLog, BOOL bSameFile) ;

BOOL CloseLog (HWND hWnd, PLOG pLog) ;

BOOL LogAddEntry (HWND hWndLog,
                  LPTSTR lpszComputer,
                  LPTSTR lpszObject,
                  DWORD ObjectTitleIndex,
                  BOOL  bGetObjectTitleIndex) ;

void SetLogTimer (HWND hWnd,
                  int iIntervalSecs) ;

BOOL LogRefresh (HWND hWnd) ;
BOOL ToggleLogRefresh (HWND hWnd) ;

void LogTimer (HWND hWnd, BOOL bForce) ;

void ReLog (HWND hWndLog, BOOL bSameFile) ;

BOOL OpenLog (HWND hWndLog,
              HANDLE hFile,
              DWORD dwMajorVersion,
              DWORD dwMinorVersion,
              BOOL bLogFile) ;

BOOL LogCollecting (HWND hWndLog) ;

int LogFileSize (HWND hWndLog) ;

BOOL LogWriteBookmark (HWND hWndLog,
                       LPCTSTR lpszComment) ;

DWORD LogFindEntry (LPTSTR lpszComputer, DWORD ObjectTitleIndex) ;

BOOL ResetLog  (HWND hWndLog) ;
void ResetLogView  (HWND hWndLog) ;
BOOL LogDeleteEntry  (HWND hWndLog) ;
BOOL AnyLogLine (void) ;

BOOL SaveLog (HWND hWndLog, HANDLE hInputFile, BOOL bGetFileName) ;

void ExportLog (void) ;

int CreateLogFile (PLOG pLog, BOOL bCreateFile, BOOL bSameFile) ;

BOOL LogWriteCounterName (HWND hWnd,
                          PPERFSYSTEM pSystem,
                          PLOG   pLog,
                          LPTSTR pCounterName,
                          long sizeMatched,
                          long sizeOfData,
                          BOOL bBaseCounterName) ;

void LogWriteSystemCounterNames (HWND hWnd, PLOG pLog) ;


LOGOPTNS.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"       // basic defns, windows.h
#include "logoptns.h"      // external declarations for this file

#include "dlgs.h"          // common dialog control IDs
#include "log.h"           // for StartLog, SetLogTimer, CloseLog
#include "fileutil.h"      // file utilities
#include "utils.h"         // various utilities
#include "playback.h"      // for PlayingBackLog
#include "pmhelpid.h"      // Help IDs

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

DWORD          iIntervalMSecs ;
BOOL           bNeedToRelogData ;
BOOL           bNeedToStartLog ;
BOOL           bNeedToSetTimer ;

extern BOOL    LocalManualRefresh ;
static BOOL    bStopButtonClicked ;
extern HWND    hWndLogEntries ;

// This is a counter that is init. to 0.  It is incremened by 1
// when the user click the cancel button.
// It is set to -1 when we sent the cancell msg internally.
int            bCancelled ;
TCHAR          szmsgFILEOK[] = FILEOKSTRING ;
DWORD          msgFILEOK ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define LogOptionsOFNStyle                      \
   (OFN_ENABLETEMPLATE | OFN_HIDEREADONLY |     \
    OFN_SHOWHELP | OFN_ENABLEHOOK)

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void EnableFileControls (HDLG hDlg, BOOL bEnable)
   {
   DialogEnable (hDlg, stc3, bEnable) ;
   DialogEnable (hDlg, lst1, bEnable) ;
   DialogEnable (hDlg, stc1, bEnable) ;
   DialogEnable (hDlg, lst2, bEnable) ;
   DialogEnable (hDlg, stc2, bEnable) ;
   DialogEnable (hDlg, cmb1, bEnable) ;
   DialogEnable (hDlg, stc4, bEnable) ;
   DialogEnable (hDlg, cmb2, bEnable) ;
   DialogEnable (hDlg, edt1, bEnable) ;
   }

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnInitDialog (HDLG hDlg)
   {
   PLOG           pLog ;
   int            i ;
   BOOL           DisplayManualRefresh ;
   pLog = LogData (hWndLog) ;

   if (msgFILEOK == 0)
      {
      msgFILEOK = RegisterWindowMessage(szmsgFILEOK);
      }

   bCancelled = 0 ;

   switch (pLog->iStatus)
      {  // switch
      case iPMStatusClosed:
//         DialogEnable (hDlg, IDD_LOGOPTPAUSE, FALSE) ;
         break ;

      case iPMStatusPaused:
         EnableFileControls (hDlg, FALSE) ;
         DialogSetText (hDlg, IDD_LOGOPTSTART, IDS_STOP) ;
//         DialogSetText (hDlg, IDD_LOGOPTPAUSE, IDS_RESUME) ;
         break ;

      case iPMStatusCollecting:
         EnableFileControls (hDlg, FALSE) ;
         DialogSetText (hDlg, IDD_LOGOPTSTART, IDS_STOP) ;
         break ;
      }  // switch

   for (i = 0 ;
        i < NumIntervals ;
        i++)
      CBAddInt (DialogControl (hDlg, IDD_LOGOPTINTERVAL), aiIntervals [i]) ;
   DialogSetInterval (hDlg, IDD_LOGOPTINTERVAL, pLog->iIntervalMSecs) ;
   iIntervalMSecs = pLog->iIntervalMSecs ;

   LocalManualRefresh = pLog->bManualRefresh ;
   DisplayManualRefresh = TRUE ;

   if (PlayingBackLog ())
      {
      DialogSetText (hDlg, IDD_LOGOPTSTART, IDS_CREATELOGFILE) ;
      DisplayManualRefresh = FALSE ;
      }

   if (LBNumItems (hWndLogEntries) == 0)
      {
      DialogEnable (hDlg, IDD_LOGOPTSTART, FALSE) ;
//      DialogEnable (hDlg, IDD_LOGOPTPAUSE, FALSE) ;
      }

   if (DisplayManualRefresh)
      {
      if (LocalManualRefresh)
         {
         DialogEnable (hDlg, IDD_LOGOPTINTERVAL, FALSE) ;
         DialogEnable (hDlg, IDD_LOGOPTINTERVALTEXT, FALSE) ;
         }

      CheckRadioButton (hDlg,
         IDD_LOGOPTIONSMANUALREFRESH,
         IDD_LOGOPTIONSPERIODIC,
         LocalManualRefresh ? IDD_LOGOPTIONSMANUALREFRESH :
         IDD_LOGOPTIONSPERIODIC) ;
      }
   else
      {
      DialogEnable (hDlg, IDD_LOGOPTIONSMANUALREFRESH, FALSE) ;
      CheckRadioButton (hDlg,
         IDD_LOGOPTIONSMANUALREFRESH,
         IDD_LOGOPTIONSPERIODIC,
         IDD_LOGOPTIONSPERIODIC) ;
      }

   dwCurrentDlgID = HC_PM_idDlgOptionLog ;

   WindowCenter (hDlg) ;
   }  // OnInitDialog

void OnStart (HDLG hDlg)
/*
   Effect:        Handle any actions necessary when the user clicks on
                  the "Start/Stop" button.

   Note:          This button could be displaying start or stop, depending
                  on the situation.
*/
   {
   PLOG           pLog ;

   pLog = LogData (hDlg) ;

   bStopButtonClicked = FALSE ;
   switch (pLog->iStatus)
      {  // switch
      case iPMStatusClosed:
         if (PlayingBackLog ())
            {
            bNeedToRelogData = TRUE ;
            }
         else
            {
            FLOAT eTimeInterval ;

            eTimeInterval = DialogFloat (hDlg, IDD_LOGOPTINTERVAL, NULL) ;
            if (eTimeInterval > MAX_INTERVALSEC ||
                eTimeInterval < MIN_INTERVALSEC)
               {
               DlgErrorBox (hDlg, ERR_BADTIMEINTERVAL) ;
               SetFocus (DialogControl (hDlg, IDD_LOGOPTINTERVAL)) ;
               EditSetTextEndPos (hDlg, IDD_LOGOPTINTERVAL) ;
               return ;
               break ;
               }

            eTimeInterval = eTimeInterval * (FLOAT) 1000.0 + (FLOAT) 0.5 ;
            pLog->iIntervalMSecs = (DWORD) eTimeInterval ;

            iIntervalMSecs = pLog->iIntervalMSecs ;
            bNeedToStartLog = TRUE ;
            }
         break ;

      case iPMStatusCollecting:
      case iPMStatusPaused:
         CloseLog (hWndLog, pLog) ;
         bStopButtonClicked = TRUE ;
         break ;
      }  // switch

   SimulateButtonPush (hDlg, IDD_OK) ;
   }

BOOL static OnOK (HDLG hDlg)
/*
   Effect:        Perform any hooked actions needed when the user selects
                  OK in the log options dialog. In particular, if we are
                  currently logging, record the need to relog and CANCEL
                  the dialog, never letting the real dialog proc get the
                  OK. Remember, this is actually a file open dialog that
                  we have perverted. If we let the OK through, the common
                  dialog manager will try to open it and it will inform
                  the user that the file is locked. This way, we let the
                  user click OK, but the dialog thinks we cancelled.

   Called By:     LogOptionsHookProc only.

   Returns:       Whether the message was handled by this function or not.
*/
   {  // OnOK
   PLOG           pLog ;

   pLog = LogData (hWndLog) ;
   if (pLog->iStatus == iPMStatusCollecting ||
       pLog->iStatus == iPMStatusPaused)
      {
      if (LocalManualRefresh != pLog->bManualRefresh)
         {
         if (!LocalManualRefresh)
            {
            FLOAT eTimeInterval ;

            eTimeInterval = DialogFloat (hDlg, IDD_LOGOPTINTERVAL, NULL) ;

            if (eTimeInterval > MAX_INTERVALSEC ||
                eTimeInterval < MIN_INTERVALSEC)
               {
               DlgErrorBox (hDlg, ERR_BADTIMEINTERVAL) ;
               SetFocus (DialogControl (hDlg, IDD_LOGOPTINTERVAL)) ;
               EditSetTextEndPos (hDlg, IDD_LOGOPTINTERVAL) ;
               return (FALSE) ;
               }

            eTimeInterval = eTimeInterval * (FLOAT) 1000.0 + (FLOAT) 0.5 ;
            pLog->iIntervalMSecs = (DWORD) eTimeInterval ;
            iIntervalMSecs = pLog->iIntervalMSecs ;
            UpdateLogDisplay (hWndLog) ;
            }
         ToggleLogRefresh (hWndLog) ;
         }
      else
         {
         bNeedToSetTimer = TRUE ;
         bCancelled = -1 ;
         }
      SimulateButtonPush (hDlg, IDD_CANCEL) ;
      return TRUE ;
      }
   else
      {
      if (!LocalManualRefresh)
         {
         FLOAT eTimeInterval ;

         eTimeInterval = DialogFloat (hDlg, IDD_LOGOPTINTERVAL, NULL) ;

         if (eTimeInterval > MAX_INTERVALSEC ||
             eTimeInterval < MIN_INTERVALSEC)
            {
            DlgErrorBox (hDlg, ERR_BADTIMEINTERVAL) ;
            SetFocus (DialogControl (hDlg, IDD_LOGOPTINTERVAL)) ;
            EditSetTextEndPos (hDlg, IDD_LOGOPTINTERVAL) ;
            return (TRUE) ;
            }
         }
      }
      return FALSE ;
   }  // OnOK

void OnPause (HDLG hDlg)
   {
   }

BOOL APIENTRY LogOptionsHookProc (HWND hDlg,
                                  UINT iMessage,
                                  WPARAM wParam,
                                  LPARAM lParam)
   {
   BOOL           bHandled ;

   bHandled = TRUE ;

   if (iMessage == msgFILEOK)
      {
      bHandled = OnOK (hDlg) ;
      return (bHandled) ;
      }

   switch (iMessage)
      {
      case WM_INITDIALOG:
         OnInitDialog (hDlg) ;
         break ;

      case WM_DESTROY:

         {
         FLOAT eTimeInterval ;

         if (!bCancelled)
            {
            eTimeInterval = DialogFloat (hDlg, IDD_LOGOPTINTERVAL, NULL) ;
            if (eTimeInterval > MAX_INTERVALSEC ||
                eTimeInterval < MIN_INTERVALSEC)
               {
               DlgErrorBox (hDlg, ERR_BADTIMEINTERVAL) ;
               }
            else
               {
               eTimeInterval = eTimeInterval * (FLOAT) 1000.0 + (FLOAT) 0.5 ;
               iIntervalMSecs = (DWORD) eTimeInterval ;
               }
            }

         dwCurrentDlgID = 0 ;
         bHandled = FALSE ;
         }
         break ;

      case WM_COMMAND:
         switch (wParam)
            {  // switch
            case IDD_LOGOPTSTART:
               OnStart (hDlg) ;
               break ;

            case IDD_LOGOPTPAUSE:
               OnPause (hDlg) ;
               break ;

            case IDD_OK:
               bHandled = OnOK (hDlg) ;
               break ;

            case IDD_CANCEL:
               bCancelled += 1 ;
               bHandled = FALSE ;
               break ;

            case IDD_LOGOPTIONSPERIODIC:
               if (PlayingBackLog())
                  {
                  break ;
                  }
               // else fall thru to the following case...
            case IDD_LOGOPTIONSMANUALREFRESH:
               // check if the Manual refresh is currently checked.
               // Then toggle the ManualRefresh button
               LocalManualRefresh =
                  (wParam == IDD_LOGOPTIONSMANUALREFRESH) ;

               CheckRadioButton (hDlg,
                  IDD_LOGOPTIONSMANUALREFRESH,
                  IDD_LOGOPTIONSPERIODIC,
                  LocalManualRefresh ? IDD_LOGOPTIONSMANUALREFRESH :
                  IDD_LOGOPTIONSPERIODIC) ;

               DialogEnable (hDlg, IDD_LOGOPTINTERVAL, !LocalManualRefresh) ;
               DialogEnable (hDlg, IDD_LOGOPTINTERVALTEXT, !LocalManualRefresh) ;
               break ;

            case ID_HELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            default:
               bHandled = FALSE ;
               break ;
            }  // switch
         break;

      default:
         bHandled = FALSE ;
         break;
      }  // switch

   return (bHandled) ;
   }  // LogOptionsHookProc

BOOL DisplayLogOptions (HWND hWndParent,
                        HWND hWndLog)

   {  // DisplayLogOptions
   TCHAR          szFilePath [FilePathLen + 1] ;
   TCHAR          szFileTitle [FilePathLen + 1] ;
   TCHAR          szFileDirectory [FilePathLen + 1] ;
   TCHAR          szCaption [WindowCaptionLen + 1] ;
   OPENFILENAME   ofn ;
   PLOG           pLog ;
   int            RetCode ;
   int            PrevLogSize ;
   BOOL           bSameFile ;

   TCHAR szFilter[LongTextLen] ;
   int   StringLength ;

   StringLoad (IDS_SAVELOGFILE, szFilter) ;
   StringLength = lstrlen (szFilter) + 1 ;
   LoadString (hInstance, IDS_SAVELOGFILEEXT,
      &szFilter[StringLength], sizeof(szFilter)-StringLength) ;
   StringLength += lstrlen (&szFilter[StringLength]) ;
   szFilter[StringLength+1] = szFilter[StringLength+2] = TEXT('\0') ;

   // This dialog is used to change attributes for an existing log file,
   // and to select the name of a log file to open. Therefore we have
   // different options in these cases.

   pLog = LogData (hWndLog) ;
   if (!strempty (pLog->szFilePath))
      {
      FileNameExtension (pLog->szFilePath, szFileTitle) ;
      FileDriveDirectory (pLog->szFilePath, szFileDirectory) ;
      lstrcpy (szFilePath, szFileTitle) ;
      }
   else
      {
      szFileTitle[0] = szFileDirectory[0] = TEXT('\0') ;
      StringLoad (IDS_SAVELOGFILEEXT, szFilePath) ;
      }

   StringLoad (IDS_LOGOPTIONS, szCaption) ;

   ofn.lStructSize = sizeof(OPENFILENAME) ;
   ofn.hwndOwner = hWndParent ;
   ofn.hInstance = hInstance ;
   ofn.lpstrFilter = szFilter;
   ofn.lpstrCustomFilter = (LPTSTR) NULL;
   ofn.nMaxCustFilter = 0L;
   ofn.nFilterIndex = 1L;
   ofn.lpstrFile = szFilePath ;
   ofn.nMaxFile = FilePathLen ;
   ofn.lpstrFileTitle = szFileTitle ;
   ofn.nMaxFileTitle = FilePathLen ;
   ofn.lpstrInitialDir = szFileDirectory ;
   ofn.lpstrTitle = (LPTSTR) szCaption ;
   ofn.Flags = LogOptionsOFNStyle  ;
   ofn.nFileOffset = 0;
   ofn.nFileExtension = 0;
   ofn.lpstrDefExt = (LPTSTR) NULL;
   ofn.lpfnHook = (LPOFNHOOKPROC) LogOptionsHookProc ;
   ofn.lpTemplateName = idDlgLogOptions ;

   bNeedToRelogData = FALSE;
   bNeedToStartLog = FALSE ;
   bNeedToSetTimer = FALSE ;
   bStopButtonClicked = FALSE ;

   if (GetSaveFileName(&ofn) && !bStopButtonClicked)
      {
      pLog = LogData (hWndLog) ;

      // save previous log file name & size
      // so we can reset if error
      PrevLogSize = pLog->lFileSize ;

      lstrcpy (szFileTitle, pLog->szFilePath) ;

      bSameFile = pstrsamei (pLog->szFilePath, ofn.lpstrFile) ;

      if (!bSameFile)
         {
         lstrcpy (pLog->szFilePath, ofn.lpstrFile) ;
         }

      pLog->iIntervalMSecs = iIntervalMSecs ;

      if (bNeedToStartLog)
         {
         pLog->bManualRefresh = LocalManualRefresh ;
         StartLog (hWndLog, pLog, bSameFile) ;
         }
      else if (bNeedToRelogData)
         {
         bNeedToRelogData = FALSE ;
         SetHourglassCursor() ;
         ReLog (hWndLog, bSameFile) ;
         SetArrowCursor() ;
         }
      else if (LocalManualRefresh != pLog->bManualRefresh)
         {
         ToggleLogRefresh (hWndLog) ;
         }

      if (!pLog->hFile)
         {
         if (bNeedToStartLog)
            {
            // if we get here, that means StartLog has detected
            // problem, just restore the old stuff..
            pLog->lFileSize = PrevLogSize ;
            lstrcpy (pLog->szFilePath, szFileTitle) ;
            }
         // verify if this is a good log file and setup the file size
         else if ((RetCode = CreateLogFile (pLog, FALSE, bSameFile)) != 0)
            {
            DlgErrorBox (hWndLog, RetCode, pLog->szFilePath);
            pLog->lFileSize = PrevLogSize ;
            lstrcpy (pLog->szFilePath, szFileTitle) ;
            }
         else
            {
            // unfortunately, we have to close this file.
            // Otherwise, when we do StartLog, the common dialog
            // will complain that this file is already in-used.
            CloseHandle (pLog->hFile) ;
            }
         pLog->hFile = 0 ;
         }

//      UpdateLogDisplay (hWndLog) ;
      }

   // Remember, we faked out GetSaveFileName to return FALSE. See OnOK doc.
   if (bNeedToSetTimer)
      {
      SetLogTimer (hWndLog, iIntervalMSecs) ;
//      UpdateLogDisplay (hWndLog) ;
      }

   UpdateLogDisplay (hWndLog) ;

   return (TRUE) ;
   }  // DisplayLogOptions


LOGOPTNS.H

BOOL DisplayLogOptions (HWND hWndParent,
                        HWND hWndLog) ;

BOOL APIENTRY LogOptionsHookProc (HWND hDlg,
                                  UINT iMessage,
                                  WPARAM wParam,
                                  LPARAM lParam) ;


MEMORY.C

#include "perfmon.h"    // included by all perfmon sources
#include "pmemory.h"     // external declarations for this file

LPMEMORY MemoryAllocate (DWORD dwSize)
   {  // MemoryAllocate
   HMEMORY        hMemory ;
   LPMEMORY       lpMemory ;

   hMemory = GlobalAlloc (GHND, dwSize) ;
   if (!hMemory)
      return (NULL) ;
   lpMemory = GlobalLock (hMemory) ;
   if (!lpMemory)
      GlobalFree (hMemory) ;
   return (lpMemory) ;
   }  // MemoryAllocate

VOID MemoryFree (LPMEMORY lpMemory)
   {  // MemoryFree
   HMEMORY        hMemory ;

   if (!lpMemory)
      return ;

   hMemory = GlobalHandle (lpMemory) ;

   if (hMemory)
      {  // if
      GlobalUnlock (hMemory) ;
      GlobalFree (hMemory) ;
      }  // if
   }  // MemoryFree

DWORD MemorySize (LPMEMORY lpMemory)
   {
   HMEMORY        hMemory ;

   hMemory = GlobalHandle (lpMemory) ;
   if (!hMemory)
      return (0L) ;

   return (GlobalSize (hMemory)) ;
   }

LPMEMORY MemoryResize (LPMEMORY lpMemory,
                       DWORD dwNewSize)
   {
   HMEMORY        hMemory ;
   LPMEMORY       lpNewMemory ;

   hMemory = GlobalHandle (lpMemory) ;
   if (!hMemory)
      return (NULL) ;

   GlobalUnlock (hMemory) ;

   hMemory = GlobalReAlloc (hMemory, dwNewSize, GHND) ;

   if (!hMemory)
      return (NULL) ;

   lpNewMemory = GlobalLock (hMemory) ;

   return (lpNewMemory) ;
   }  // MemoryResize


MENUIDS.H

#define MENUIDTOHELPID        4000

#define FILEMENUPOSITION      0
#define EDITMENUPOSITION      1
#define VIEWMENUPOSITION      2
#define OPTIONSMENUPOSITION   3

// Special "view-independent" menu IDs for the toolbar
#define IDM_TOOLBARADD                 4291
#define IDM_TOOLBARMODIFY              4292
#define IDM_TOOLBARDELETE              4294
#define IDM_TOOLBAROPTIONS             4295
#define IDM_TOOLBARREFRESH             4296
#define IDM_TOOLBARBOOKMARK            4297

//=============================//
// "File" Menu IDs             //
//=============================//

#define IDM_FILENEWCHART            4101
#define IDM_FILEOPENCHART           4102
#define IDM_FILESAVECHART           4103
#define IDM_FILESAVEASCHART         4104
#define IDM_FILEEXPORTCHART         4105

#define IDM_FILENEWALERT            4106
#define IDM_FILEOPENALERT           4107
#define IDM_FILESAVEALERT           4108
#define IDM_FILESAVEASALERT         4109
#define IDM_FILEEXPORTALERT         4110

#define IDM_FILENEWLOG              4111
#define IDM_FILEOPENLOG             4112
#define IDM_FILESAVELOG             4113
#define IDM_FILESAVEASLOG           4114
#define IDM_FILEEXPORTLOG           4115

#define IDM_FILENEWREPORT           4116
#define IDM_FILEOPENREPORT          4117
#define IDM_FILESAVEREPORT          4118
#define IDM_FILESAVEASREPORT        4119
#define IDM_FILEEXPORTREPORT        4120

#define IDM_FILESAVEWORKSPACE       4121
#define IDM_FILEPRINTERSETUP        4125
#define IDM_FILEEXIT                4127

//=============================//
// "Edit" Menu IDs             //
//=============================//

#define IDM_EDITADDCHART            4201
#define IDM_EDITMODIFYCHART         4202
#define IDM_EDITCLEARCHART          4203
#define IDM_EDITDELETECHART         4204

#define IDM_EDITADDALERT            4205
#define IDM_EDITMODIFYALERT         4206
#define IDM_EDITCLEARALERT          4207
#define IDM_EDITDELETEALERT         4208

#define IDM_EDITADDLOG              4209
#define IDM_EDITMODIFYLOG           4210
#define IDM_EDITCLEARLOG            4211
#define IDM_EDITDELETELOG           4212

#define IDM_EDITADDREPORT           4213
#define IDM_EDITMODIFYREPORT        4214
#define IDM_EDITCLEARREPORT         4215
#define IDM_EDITDELETEREPORT        4216

#define IDM_EDITTIMEWINDOW          4217

//=============================//
// "View" Menu IDs             //
//=============================//

#define IDM_VIEWCHART               4301
#define IDM_VIEWALERT               4302
#define IDM_VIEWLOG                 4303
#define IDM_VIEWREPORT              4304

// IDM_WORKSPACE is not a view nor menu, just a define
#define IDM_WORKSPACE               4305

//=============================//
// "Options" Menu IDs          //
//=============================//

#define IDM_OPTIONSCHART            4401

#define IDM_OPTIONSALERT            4403

#define IDM_OPTIONSLOG              4405

#define IDM_OPTIONSREPORT           4407

#define IDM_OPTIONSMANUALREFRESH    4409

#define IDM_OPTIONSDISPLAYMENU      4408
#define IDM_OPTIONSDISPLAYTOOL      4409
#define IDM_OPTIONSDISPLAYSTATUS    4410
#define IDM_OPTIONSDISPLAYONTOP     4411

#define IDM_OPTIONSBOOKMARK         4413
#define IDM_OPTIONSDATASOURCE       4415

#define IDM_OPTIONSREFRESHNOWCHART  4416
#define IDM_OPTIONSREFRESHNOWALERT  4417
#define IDM_OPTIONSREFRESHNOWLOG    4418
#define IDM_OPTIONSREFRESHNOWREPORT 4419

// no really a menu item.  This is sent from the ^G for now
// to turn the legend on/off.
#define IDM_OPTIONSLEGENDONOFF      4420

//=============================//
// "Help" Menu IDs             //
//=============================//

#define IDM_HELPCONTENTS            4501
#define IDM_HELPSEARCH              4502
#define IDM_HELPHELP                4503
#define IDM_HELPABOUT               4504

//=============================//
// "System" Menu IDs           //
//=============================//

#define IDM_SYSTEMRESTORE           4910
#define IDM_SYSTEMMOVE              4911
#define IDM_SYSTEMSIZE              4912
#define IDM_SYSTEMMINIMIZE          4913
#define IDM_SYSTEMMAXIMIZE          4914
#define IDM_SYSTEMCLOSE             4915
#define IDM_SYSTEMSWITCHTO          4916

#define IDM_FIRSTMENUID             IDM_FILENEWCHART
#define IDM_LASTMENUID              IDM_SYSTEMSWITCHTO


OWNDRAW.H

#define DIWindow(lpDI)              \
   (lpDI->hwndItem)

#define DIIndex(lpDI)               \
   (lpDI->itemID)

#define DIEntire(lpDI)              \
   (lpDI->itemAction & ODA_DRAWENTIRE)

#define DIFocusChanged(lpDI)        \
   (lpDI->itemAction & ODA_FOCUS)

#define DISelectionChanged(lpDI)    \
   (lpDI->itemAction & ODA_SELECT)

#define DISelected(lpDI)            \
   (lpDI->itemState & ODS_SELECTED)

#define DIDisabled(lpDI)            \
   (lpDI->itemState & ODS_DISABLED)

#define DIFocus(lpDI)               \
   (lpDI->itemState & ODS_FOCUS)


PERFDATA.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <string.h>     // strupr
#include <stdio.h>   // for sprintf.
#include "perfmon.h"
#include "utils.h"

#include "pmemory.h"        // for MemoryXXX (mallloc-type) routines
#include "playback.h"   // for PlayingBackLog
#include "perfdata.h"   // external declarations for this file
#include "system.h"     // for DeleteUnusedSystems
#include "line.h"       // for LineFind???()
#include "perfmops.h"   // for AppendObjectToValueList()

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

TCHAR NamesKey[] = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib";
TCHAR Counters[] = L"Counters";
TCHAR Help[] = L"Help";
TCHAR LastHelp[] = L"Last Help";
TCHAR LastCounter[] = L"Last Counter";
TCHAR SysVersion[] = L"Version";
TCHAR CounterNameStr[] = L"Counter ";
TCHAR ExplainNameStr[] = L"Explain ";

#define szPerfSubkey      (NULL)
TCHAR   NULL_NAME[] = L" ";
#define RESERVED    0L

TCHAR          DefaultLangId[4] ;
TCHAR          EnglishLangId[4] ;

static   HANDLE            *lpHandles ;
static   int               NumberOfHandles = 0 ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

// When the conversion of this code is complete, this will be the *only*
// allocated copy of the performance data.  It will monotonically grow
// to hold the largest of the system's performance data.

// PPERFDATA      pGlobalPerfData ;

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

NTSTATUS  AddNamesToArray (LPTSTR pNames,
   DWORD    dwLastID,
   LPWSTR   *lpCounterId) ;

//======================================//
// Object Accessors                     //
//======================================//

void ObjectName (PPERFSYSTEM pSystem,
                 PPERFOBJECT pObject,
                 LPTSTR lpszName,
                 int iLen)
   {  // ObjectName
   strclr (lpszName) ;
   QueryPerformanceName (pSystem,
                         pObject->ObjectNameTitleIndex,
                         0, iLen, lpszName, FALSE) ;
   }  // ObjectName

//======================================//
// Counter Accessors                    //
//======================================//

PERF_INSTANCE_DEFINITION *
FirstInstance(
    PERF_OBJECT_TYPE *pObjectDef)
{
    return (PERF_INSTANCE_DEFINITION *)
               ((PCHAR) pObjectDef + pObjectDef->DefinitionLength);
}

PERF_INSTANCE_DEFINITION *
NextInstance(
    PERF_INSTANCE_DEFINITION *pInstDef)
{
    PERF_COUNTER_BLOCK *pCounterBlock;

    pCounterBlock = (PERF_COUNTER_BLOCK *)
                        ((PCHAR) pInstDef + pInstDef->ByteLength);

    return (PERF_INSTANCE_DEFINITION *)
               ((PCHAR) pCounterBlock + pCounterBlock->ByteLength);
}

LPTSTR
GetInstanceName(
PPERFINSTANCEDEF  pInstDef)
{
    return (LPTSTR) ((PCHAR) pInstDef + pInstDef->NameOffset);
}

void
GetInstanceNameStr (PPERFINSTANCEDEF pInstance,
                    LPTSTR lpszInstance)
   {
   LPSTR pSource;

   pSource = (LPSTR) GetInstanceName(pInstance) ;

   if (pSource[1] == '\0' && pSource[2] != '\0')
      {
      // Must be Unicode
      wcsncpy (lpszInstance,
               (LPTSTR)pSource,
               pInstance->NameLength);
      }
   else
      {
      // Must be ANSI (or a single Unicode character)
      mbstowcs (lpszInstance,
                pSource,
                pInstance->NameLength);
      }

   }

void
GetPerfComputerName(PPERFDATA pPerfData,
                    LPTSTR lpszComputerName)
   {
   lstrcpy(lpszComputerName, szComputerPrefix) ;
   if (pPerfData)
      {
      wcsncpy (&lpszComputerName[2],
               (LPWSTR)((PBYTE) pPerfData + pPerfData->SystemNameOffset),
               pPerfData->SystemNameLength/sizeof(WCHAR)) ;
      }
   else
      {
      lpszComputerName[0] = TEXT('\0') ;
      }
   }

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

int CounterIndex (PPERFCOUNTERDEF pCounterToFind,
                  PPERFOBJECT pObject)
/*
   Effect:        Return the index ("counter number") of pCounterToFind
                  within pObject. If the counter doesnt belong to pObject,
                  return -1.
*/
   {  // CounterIndex
   PPERFCOUNTERDEF   pCounter ;
   UINT              iCounter ;

   for (iCounter = 0, pCounter = FirstCounter (pObject) ;
        iCounter < pObject->NumCounters ;
        iCounter++, pCounter = NextCounter (pCounter))
      {  // for
      if (pCounter->CounterNameTitleIndex ==
          pCounterToFind->CounterNameTitleIndex)
         return (iCounter) ;
      }  // for

   return (-1) ;
   }  // CounterIndex

HKEY
OpenSystemPerfData (
    IN LPCTSTR lpszSystem
)
{  // OpenSystemPerfData

    HKEY    hKey = NULL;
    LONG    lStatus;

    lStatus = ERROR_CANTOPEN;   // default error if none is returned

    if (IsLocalComputer(lpszSystem) || PlayingBackLog()) {
        bCloseLocalMachine = TRUE ;
        SetLastError (ERROR_SUCCESS);
        return HKEY_PERFORMANCE_DATA;
    } else if (lstrlen (lpszSystem) < MAX_COMPUTERNAME_LENGTH+3) {
        // Must be a remote system
        try {
            lStatus = RegConnectRegistry (
                (LPTSTR)lpszSystem,
                HKEY_PERFORMANCE_DATA,
                &hKey);
        } finally {
            if (lStatus != ERROR_SUCCESS) {
                SetLastError (lStatus);
                hKey = NULL;
            }
        }
    }
    return (hKey);

}  // OpenSystemPerfData

LPWSTR
*AddNewName(
    HKEY    hKeyNames,
    PCOUNTERTEXT  pCounterInfo,
    LPWSTR  CounterBuffer,
    LPWSTR  HelpBuffer,
    DWORD   dwLastId,
    DWORD   dwCounterSize,
    DWORD   dwHelpSize,
    LANGID  LangIdUsed
   )
{
    LPWSTR  *lpReturnValue;
    LPWSTR  *lpCounterId;
    LPWSTR  lpCounterNames;
    LPWSTR  lpHelpText;
    DWORD   dwArraySize;
    DWORD   dwBufferSize;
    DWORD   dwValueType;
    LONG    lWin32Status;
    NTSTATUS    Status;
    DWORD   dwLastError;

    dwArraySize = (dwLastId + 1 ) * sizeof(LPWSTR);
    lpReturnValue = MemoryAllocate (dwArraySize + dwCounterSize + dwHelpSize);

    if (!lpReturnValue) goto ERROR_EXIT;

    // initialize pointers into buffer

    lpCounterId = lpReturnValue;
    lpCounterNames = (LPWSTR)((LPBYTE)lpCounterId + dwArraySize);
    lpHelpText = (LPWSTR)((LPBYTE)lpCounterNames + dwCounterSize);

    // read counters into memory

    dwBufferSize = dwCounterSize;
    lWin32Status = RegQueryValueEx (
        hKeyNames,
        CounterBuffer,
        RESERVED,
        &dwValueType,
        (LPVOID)lpCounterNames,
        &dwBufferSize);

    if (lWin32Status != ERROR_SUCCESS) goto ERROR_EXIT;

    if (bExplainTextButtonHit) {
        dwBufferSize = dwHelpSize;
        lWin32Status = RegQueryValueEx (
            hKeyNames,
            HelpBuffer,
            RESERVED,
            &dwValueType,
            (LPVOID)lpHelpText,
            &dwBufferSize);

    if (lWin32Status != ERROR_SUCCESS) goto ERROR_EXIT;
    }

    // load counter array items
    Status = AddNamesToArray (lpCounterNames, dwLastId, lpCounterId);
    if (Status != ERROR_SUCCESS) goto ERROR_EXIT;

    if (bExplainTextButtonHit) {
        Status = AddNamesToArray (lpHelpText, dwLastId, lpCounterId);
    }

    if (Status != ERROR_SUCCESS) goto ERROR_EXIT;

    if (pCounterInfo) {
        pCounterInfo->dwLastId = dwLastId;
        pCounterInfo->dwLangId = LangIdUsed;
        pCounterInfo->dwHelpSize = dwHelpSize;
        pCounterInfo->dwCounterSize = dwCounterSize;
    }

    return lpReturnValue;

ERROR_EXIT:
    if (lWin32Status != ERROR_SUCCESS) {
        dwLastError = GetLastError();
    }

    if (lpReturnValue) {
        MemoryFree ((LPVOID)lpReturnValue);
    }

    return NULL;
}

LPWSTR
*BuildNewNameTable(
    PPERFSYSTEM   pSystem,
    LPWSTR        lpszLangId,     // unicode value of Language subkey
    PCOUNTERTEXT  pCounterInfo,
    LANGID        iLangId,         // lang ID of the lpszLangId
    DWORD         dwLastId
)
/*++

BuildNewNameTable

Arguments:

    lpszLangId
            The unicode id of the language to look up. (English is 0x409)

Return Value:

    pointer to an allocated table. (the caller must free it when finished!)
    the table is an array of pointers to zero terminated strings. NULL is
    returned if an error occured.

--*/
{
    LONG    lWin32Status;
    DWORD   dwValueType;
    DWORD   dwLastError;
    DWORD   dwBufferSize;
    DWORD   dwCounterSize;
    DWORD   dwHelpSize;
    HKEY    hKeyNames;
    TCHAR   CounterBuffer [MiscTextLen] ;
    TCHAR   ExplainBuffer [MiscTextLen] ;
    TCHAR   subLangId [ShortTextLen] ;
    LANGID  LangIdUsed = iLangId;

    //initialize local variables
    hKeyNames = pSystem->sysDataKey;

    // check for null arguments and insert defaults if necessary
    if (!lpszLangId) {
        lpszLangId = DefaultLangId;
        LangIdUsed = iLanguage ;
    }

    // get size of counter names and add that to the arrays
    lstrcpy (CounterBuffer, CounterNameStr);
    lstrcat (CounterBuffer, lpszLangId);

    lstrcpy (ExplainBuffer, ExplainNameStr);
    lstrcat (ExplainBuffer, lpszLangId);

    dwBufferSize = 0;
    lWin32Status = RegQueryValueEx (
        hKeyNames,
        CounterBuffer,
        RESERVED,
        &dwValueType,
        NULL,
        &dwBufferSize);

    if (lWin32Status != ERROR_SUCCESS) {
        // try take out the country ID
        LangIdUsed = MAKELANGID (LangIdUsed & 0x0ff, LANG_NEUTRAL);
        TSPRINTF (subLangId, TEXT("%03x"), LangIdUsed);
        lstrcpy (CounterBuffer, CounterNameStr);
        lstrcat (CounterBuffer, subLangId);

        lstrcpy (ExplainBuffer, ExplainNameStr);
        lstrcpy (ExplainBuffer, subLangId);

        dwBufferSize = 0;
        lWin32Status = RegQueryValueEx (
            hKeyNames,
            CounterBuffer,
            RESERVED,
            &dwValueType,
            NULL,
            &dwBufferSize);
    }

    if (lWin32Status != ERROR_SUCCESS) {
        // try the EnglishLangId
        if (!strsame(EnglishLangId, subLangId)) {

            lstrcpy (CounterBuffer, CounterNameStr);
            lstrcat (CounterBuffer, EnglishLangId);

            lstrcpy (ExplainBuffer, ExplainNameStr);
            lstrcpy (ExplainBuffer, EnglishLangId);

            LangIdUsed = iEnglishLanguage ;

            dwBufferSize = 0;
            lWin32Status = RegQueryValueEx (
                hKeyNames,
                CounterBuffer,
                RESERVED,
                &dwValueType,
                NULL,
                &dwBufferSize);
        }
    }

    // Fail, too bad...
    if (lWin32Status != ERROR_SUCCESS) {
        goto BNT_BAILOUT;
    }

    dwCounterSize = dwBufferSize;

    // If ExplainText is needed, then
    // get size of help text and add that to the arrays

    if (bExplainTextButtonHit) {
        dwBufferSize = 0;
        lWin32Status = RegQueryValueEx (
              hKeyNames,
              ExplainBuffer,
              RESERVED,
              &dwValueType,
              NULL,
              &dwBufferSize);

        if (lWin32Status != ERROR_SUCCESS) goto BNT_BAILOUT;

        dwHelpSize = dwBufferSize;
     } else {
        dwHelpSize = 0;
     }

    return (AddNewName(
        hKeyNames,
        pCounterInfo,
        CounterBuffer,
        ExplainBuffer,
        dwLastId,
        dwCounterSize,
        dwHelpSize,
        LangIdUsed));

BNT_BAILOUT:
    if (lWin32Status != ERROR_SUCCESS) {
        dwLastError = GetLastError();
    }

    return NULL;
}

LPWSTR
*BuildOldNameTable(
    HKEY          hKeyRegistry,   // handle to registry db with counter names
    LPWSTR        lpszLangId,     // unicode value of Language subkey
    PCOUNTERTEXT  pCounterInfo,
    LANGID        iLangId,         // lang ID of the lpszLangId
    DWORD         dwLastId
)
/*++

BuildOldNameTable

Arguments:

    hKeyRegistry
            Handle to an open registry (this can be local or remote.) and
            is the value returned by RegConnectRegistry or a default key.

    lpszLangId
            The unicode id of the language to look up. (English is 0x409)

Return Value:

    pointer to an allocated table. (the caller must free it when finished!)
    the table is an array of pointers to zero terminated strings. NULL is
    returned if an error occured.

--*/
{
    LPWSTR  *lpReturnValue = NULL;

    LONG    lWin32Status;
    DWORD   dwValueType;
    DWORD   dwLastError;
    DWORD   dwBufferSize;
    DWORD   dwCounterSize;
    DWORD   dwHelpSize;
    HKEY    hKeyNames;
    TCHAR   tempBuffer [MiscTextLen] ;
    TCHAR   subLangId [ShortTextLen] ;
    LPWSTR  lpValueNameString;
    LANGID  LangIdUsed = iLangId;
    TCHAR   Slash[2];

    //initialize local variables
    hKeyNames = NULL;
    Slash[0] = L'\\';
    Slash[1] = L'\0';

    // check for null arguments and insert defaults if necessary
    if (!lpszLangId) {
        lpszLangId = DefaultLangId;
        LangIdUsed = iLanguage ;
    }

    // get size of string buffer
    lpValueNameString = tempBuffer ;

    lstrcpy (lpValueNameString, NamesKey);
    lstrcat (lpValueNameString, Slash);
    lstrcat (lpValueNameString, lpszLangId);

    lWin32Status = RegOpenKeyEx (
        hKeyRegistry,
        lpValueNameString,
        RESERVED,
        KEY_READ,
        &hKeyNames);

    if (lWin32Status != ERROR_SUCCESS) {
        // try take out the country ID
        LangIdUsed = MAKELANGID (LangIdUsed & 0x0ff, LANG_NEUTRAL);
        TSPRINTF (subLangId, TEXT("%03x"), LangIdUsed);
        lstrcpy (lpValueNameString, NamesKey);
        lstrcat (lpValueNameString, Slash);
        lstrcat (lpValueNameString, subLangId);

        lWin32Status = RegOpenKeyEx (
                hKeyRegistry,
                lpValueNameString,
                RESERVED,
                KEY_READ,
                &hKeyNames);
    }

    if (lWin32Status != ERROR_SUCCESS) {
        // try the EnglishLangId
        if (!strsame(EnglishLangId, subLangId)) {

            lstrcpy (lpValueNameString, NamesKey);
            lstrcat (lpValueNameString, Slash);
            lstrcat (lpValueNameString, EnglishLangId);

            LangIdUsed = iEnglishLanguage ;

            lWin32Status = RegOpenKeyEx (
                hKeyRegistry,
                lpValueNameString,
                RESERVED,
                KEY_READ,
                &hKeyNames);
        }
    }

    // Fail, too bad...
    if (lWin32Status != ERROR_SUCCESS) {
        goto BNT_BAILOUT;
    }

    // get size of counter names and add that to the arrays

    dwBufferSize = 0;
    lWin32Status = RegQueryValueEx (
        hKeyNames,
        Counters,
        RESERVED,
        &dwValueType,
        NULL,
        &dwBufferSize);

    if (lWin32Status != ERROR_SUCCESS) goto BNT_BAILOUT;

    dwCounterSize = dwBufferSize;

    // If ExplainText is needed, then
    // get size of help text and add that to the arrays

    if (bExplainTextButtonHit) {
        dwBufferSize = 0;
        lWin32Status = RegQueryValueEx (
              hKeyNames,
              Help,
              RESERVED,
              &dwValueType,
              NULL,
              &dwBufferSize);

        if (lWin32Status != ERROR_SUCCESS) goto BNT_BAILOUT;

        dwHelpSize = dwBufferSize;
     } else {
        dwHelpSize = 0;
     }

    lpReturnValue = AddNewName(
        hKeyNames,
        pCounterInfo,
        Counters,
        Help,
        dwLastId,
        dwCounterSize,
        dwHelpSize,
        LangIdUsed);

    RegCloseKey (hKeyNames);

    return lpReturnValue;

BNT_BAILOUT:
    if (lWin32Status != ERROR_SUCCESS) {
        dwLastError = GetLastError();
    }

    if (lpReturnValue) {
        MemoryFree ((LPVOID)lpReturnValue);
    }

    if (hKeyNames) RegCloseKey (hKeyNames);

    return NULL;
}

LPWSTR
*BuildNameTable(
    PPERFSYSTEM   pSysInfo,
    HKEY          hKeyRegistry,   // handle to registry db with counter names
    LPWSTR        lpszLangId,     // unicode value of Language subkey
    PCOUNTERTEXT  pCounterInfo,
    LANGID        iLangId         // lang ID of the lpszLangId
)
/*++

BuildNameTable

Arguments:

    hKeyRegistry
            Handle to an open registry (this can be local or remote.) and
            is the value returned by RegConnectRegistry or a default key.

    lpszLangId
            The unicode id of the language to look up. (English is 0x409)

Return Value:

    pointer to an allocated table. (the caller must free it when finished!)
    the table is an array of pointers to zero terminated strings. NULL is
    returned if an error occured.

--*/
{

    LPWSTR  *lpReturnValue;
    LONG    lWin32Status;
    DWORD   dwLastError;
    DWORD   dwValueType;
    DWORD   dwLastHelp;
    DWORD   dwLastCounter;
    DWORD   dwLastId;
    DWORD   dwBufferSize;
    HKEY    hKeyValue;
    DWORD   dwSystemVersion;

    //initialize local variables
    lpReturnValue = NULL;
    hKeyValue = NULL;

    // open registry to get number of items for computing array size

    lWin32Status = RegOpenKeyEx (
        hKeyRegistry,
        NamesKey,
        RESERVED,
        KEY_READ,
        &hKeyValue);

    if (lWin32Status != ERROR_SUCCESS) {
        goto BNT_BAILOUT;
    }

    // get number of items

    dwBufferSize = sizeof (dwLastHelp);
    lWin32Status = RegQueryValueEx (
        hKeyValue,
        LastHelp,
        RESERVED,
        &dwValueType,
        (LPBYTE)&dwLastHelp,
        &dwBufferSize);

    if ((lWin32Status != ERROR_SUCCESS) || (dwValueType != REG_DWORD)) {
        goto BNT_BAILOUT;
    }

    dwBufferSize = sizeof (dwLastCounter);
    lWin32Status = RegQueryValueEx (
        hKeyValue,
        LastCounter,
        RESERVED,
        &dwValueType,
        (LPBYTE)&dwLastCounter,
        &dwBufferSize);

    if ((lWin32Status != ERROR_SUCCESS) || (dwValueType != REG_DWORD)) {
        goto BNT_BAILOUT;
    }

    if (dwLastCounter >= dwLastHelp) {
        dwLastId = dwLastCounter;
    } else {
        dwLastId = dwLastHelp;
    }

    // get system version
    dwBufferSize = sizeof (dwSystemVersion);
    lWin32Status = RegQueryValueEx (
        hKeyValue,
        SysVersion,
        RESERVED,
        &dwValueType,
        (LPBYTE)&dwSystemVersion,
        &dwBufferSize);

    if ((lWin32Status != ERROR_SUCCESS) || (dwValueType != REG_DWORD)) {
        pSysInfo->SysVersion = 0x10000;
    } else {
        pSysInfo->SysVersion = dwSystemVersion;
    }

    if (pSysInfo->SysVersion <= 0x10000) {
        lpReturnValue = BuildOldNameTable (
            hKeyRegistry,
            lpszLangId,
            pCounterInfo,
            iLangId,
            dwLastId) ;
    } else {
        lpReturnValue = BuildNewNameTable (
            pSysInfo,
            lpszLangId,
            pCounterInfo,
            iLangId,
            dwLastId) ;
    }

    RegCloseKey (hKeyValue);
    return lpReturnValue;

BNT_BAILOUT:
    if (lWin32Status != ERROR_SUCCESS) {
        dwLastError = GetLastError();
    }
    if (hKeyValue) RegCloseKey (hKeyValue);
    return NULL;
}

DWORD GetSystemKey (PPERFSYSTEM pSysInfo, HKEY *phKeyMachine)
{
    DWORD   dwStatus;

   // connect to system registry

    if (IsLocalComputer(pSysInfo->sysName) ||
       (PlayingBackLog() && PlaybackLog.pBaseCounterNames)) {
        *phKeyMachine = HKEY_LOCAL_MACHINE;
    } else if (lstrlen(pSysInfo->sysName) < MAX_COMPUTERNAME_LENGTH+3) {
        try {
            dwStatus = RegConnectRegistry (
                pSysInfo->sysName,
                HKEY_LOCAL_MACHINE,
                phKeyMachine);

            if (dwStatus != ERROR_SUCCESS) {
                if (PlayingBackLog()) {
                    // If remote machine is not on and we are
                    // playing back log, then, use the counters from
                    // local machine.
                    *phKeyMachine = HKEY_LOCAL_MACHINE;
                } else {
                    return dwStatus;
                }
            }
        } finally {
            ; // nothing
        }
    }
    return 0;
}

DWORD GetSystemNames(PPERFSYSTEM pSysInfo)
{
    HKEY    hKeyMachine = 0;
    DWORD   dwStatus;

    if (dwStatus = GetSystemKey (pSysInfo, &hKeyMachine)) {
         return dwStatus;
    }

    // if here, then hKeyMachine is an open key to the system's
    //  HKEY_LOCAL_MACHINE registry database

    // only one language is supported by this approach.
    // multiple language support would:
    //  1.  enumerate language keys
    //       and for each key:
    //  2.  allocate memory for structures
    //  3.  call BuildNameTable for each lang key.

    pSysInfo->CounterInfo.pNextTable = NULL;
    pSysInfo->CounterInfo.dwLangId = iLanguage ;   // default Lang ID

    if (PlayingBackLog() && PlaybackLog.pBaseCounterNames) {
        pSysInfo->CounterInfo.TextString = LogBuildNameTable (pSysInfo) ;
    } else {
        pSysInfo->CounterInfo.TextString = BuildNameTable (
              pSysInfo,
              hKeyMachine,
              NULL,                               // use default
              &pSysInfo->CounterInfo,
              0);
    }

    if (hKeyMachine && hKeyMachine != HKEY_LOCAL_MACHINE) {
        RegCloseKey (hKeyMachine) ;
    }

    if (pSysInfo->CounterInfo.TextString == NULL) {
        return GetLastError();
    } else {
        return ERROR_SUCCESS;
    }
}

BOOL  GetHelpText(
    PPERFSYSTEM pSysInfo
    )
{
    LPWSTR  *lpCounterId;
    LPWSTR  lpHelpText;
    LONG    lWin32Status;
    DWORD   dwValueType;
    DWORD   dwArraySize;
    DWORD   dwBufferSize;
    DWORD   dwCounterSize;
    DWORD   dwHelpSize;
    NTSTATUS    Status;
    DWORD   dwLastId;
    TCHAR   Slash[2];

    HKEY    hKeyNames;

    TCHAR   SysLangId [ShortTextLen] ;
    TCHAR   ValueNameString [MiscTextLen] ;
    HKEY    hKeyMachine = 0;
    DWORD   dwStatus;

    SetHourglassCursor() ;

    //initialize local variables
    lpHelpText = NULL;
    hKeyNames = hKeyMachine = NULL;
    Slash[0] = L'\\';
    Slash[1] = L'\0';

    dwBufferSize = 0;

    TSPRINTF (SysLangId, TEXT("%03x"), pSysInfo->CounterInfo.dwLangId) ;

    if (pSysInfo->SysVersion <= 0x10000) {
        // old version, get help from registry
        if (dwStatus = GetSystemKey (pSysInfo, &hKeyMachine)) {
            goto ERROR_EXIT;
        }

        lstrcpy (ValueNameString, NamesKey);
        lstrcat (ValueNameString, Slash);
        lstrcat (ValueNameString, SysLangId);

        lWin32Status = RegOpenKeyEx (
                  hKeyMachine,
                  ValueNameString,
                  RESERVED,
                  KEY_READ,
                  &hKeyNames);

       if (lWin32Status != ERROR_SUCCESS) goto ERROR_EXIT;
    } else {
        // new system version, get it from the HKEY_PERFORMANCE
        hKeyNames = pSysInfo->sysDataKey;
        lstrcpy (ValueNameString, ExplainNameStr);
        lstrcat (ValueNameString, SysLangId);
    }

    dwHelpSize = 0;
    lWin32Status = RegQueryValueEx (
              hKeyNames,
              pSysInfo->SysVersion <= 0x010000 ? Help : ValueNameString,
              RESERVED,
              &dwValueType,
              NULL,
              &dwHelpSize);

    if (lWin32Status != ERROR_SUCCESS || dwHelpSize == 0) goto ERROR_EXIT;

    dwLastId = pSysInfo->CounterInfo.dwLastId;
    dwArraySize = (dwLastId + 1) * sizeof (LPWSTR);
    dwCounterSize = pSysInfo->CounterInfo.dwCounterSize;

    // allocate another memory to get the help text
    lpHelpText = MemoryAllocate (dwHelpSize);
    if (!lpHelpText) goto ERROR_EXIT;

    dwBufferSize = dwHelpSize;
    lWin32Status = RegQueryValueEx (
        hKeyNames,
        pSysInfo->SysVersion <= 0x010000 ? Help : ValueNameString,
        RESERVED,
        &dwValueType,
        (LPVOID)lpHelpText,
        &dwBufferSize);

    if (lWin32Status != ERROR_SUCCESS) goto ERROR_EXIT;

    // setup the help text pointers
    lpCounterId = pSysInfo->CounterInfo.TextString;
    Status = AddNamesToArray (lpHelpText, dwLastId, lpCounterId) ;
    if (Status != ERROR_SUCCESS) goto ERROR_EXIT;

    pSysInfo->CounterInfo.dwHelpSize = dwHelpSize;

    if (pSysInfo->SysVersion <= 0x010000)
        RegCloseKey (hKeyNames);

    if (hKeyMachine && hKeyMachine != HKEY_LOCAL_MACHINE) {
        RegCloseKey (hKeyMachine) ;
    }

    pSysInfo->CounterInfo.HelpTextString = lpHelpText;

    SetArrowCursor() ;

    return TRUE;

ERROR_EXIT:

    SetArrowCursor() ;

    if (lpHelpText) {
        MemoryFree ((LPVOID)lpHelpText);
    }

    if (hKeyNames) {
        RegCloseKey (hKeyNames);
    }
    if (hKeyMachine && hKeyMachine != HKEY_LOCAL_MACHINE) {
        RegCloseKey (hKeyMachine) ;
    }

    return FALSE;
}

//
//  QueryPerformanceName -Get a title, given an index
//
//Inputs:
//
//          pSysInfo        -   Pointer to sysinfo struct for the
//                              system in question
//
//    dwTitleIndex    -Index of Title entry
//
//          LangID          -   language in which title should be displayed
//
//    cbTitle    -# of char in the lpTitle buffer
//
//    lpTitle    -pointer to a buffer to receive the
//                              Title
//
//          Help            -   TRUE is help is desired, else counter or
//                              object is assumed
DWORD
QueryPerformanceName(
    PPERFSYSTEM pSysInfo,
    DWORD dwTitleIndex,
    LANGID LangID,
    DWORD cbTitle,
    LPTSTR lpTitle,
    BOOL Help
    )
{
    LPWSTR  lpTitleFound;
    NTSTATUS    Status;
    BOOL    bGetTextSuccess = TRUE ;

    DBG_UNREFERENCED_PARAMETER(LangID);

    if (Help && pSysInfo->CounterInfo.dwHelpSize == 0) {
        // we have not get the help text yet, go get it
        bGetTextSuccess = GetHelpText (pSysInfo);
    }

    if (!bGetTextSuccess) {
        Status = ERROR_INVALID_NAME;
        goto ErrorExit;
    }

    if ((dwTitleIndex > 0) && (dwTitleIndex <= pSysInfo->CounterInfo.dwLastId)) {
        // then title should be found in the array
        lpTitleFound = pSysInfo->CounterInfo.TextString[dwTitleIndex];
        if (!lpTitleFound) {
            // no entry for this index
            Status = ERROR_INVALID_NAME;
        }
        else if ((DWORD)lstrlen(lpTitleFound) < cbTitle) {
            lstrcpy (lpTitle, lpTitleFound);
            return (ERROR_SUCCESS);
        } else {
            Status = ERROR_MORE_DATA;
        }
    } else {

        Status = ERROR_INVALID_NAME;
    }

ErrorExit:
    // if here, then an error occured, so return a blank

if ((DWORD)lstrlen (NULL_NAME) < cbTitle) {
        lstrcpy (lpTitle, NULL_NAME);
    }

    return Status;   // title not returned

}

LONG
GetSystemPerfData (
    IN HKEY hKeySystem,
    IN LPTSTR lpszValue,
    OUT PPERFDATA pPerfData,
    OUT PDWORD pdwPerfDataLen
)
   {  // GetSystemPerfData
   LONG     lError ;
   DWORD    Type ;

   // have to pass in a Type to RegQueryValueEx(W) or else it
   // will crash
   lError = RegQueryValueEx (hKeySystem, lpszValue, NULL, &Type,
                            (LPSTR) pPerfData, pdwPerfDataLen) ;
   return (lError) ;
   }  // GetSystemPerfData

BOOL CloseSystemPerfData (HKEY hKeySystem)
   {  // CloseSystemPerfData
   return (TRUE) ;
   }  // CloseSystemPerfData

int CBLoadObjects (HWND hWndCB,
                   PPERFDATA pPerfData,
                   PPERFSYSTEM pSysInfo,
                   DWORD dwDetailLevel,
                   LPTSTR lpszDefaultObject,
                   BOOL bIncludeAll)
/*
   Effect:        Load into the combo box CB one item for each Object in
                  pPerfData. For each item, look up the object's name in
                  the registry strings associated with pSysInfo, and
                  attach the object to the data field of the CB item.

                  Dont add those objects that are more detailed than
                  dwDetailLevel.

                  Set the current selected CB item to the object named
                  lpszDefaultObject, or to the default object specified in
                  pPerfData if lpszDefaultObject is NULL.
*/
   {  // CBLoadObjects
   UINT           i ;
   int            iIndex ;
   PPERFOBJECT    pObject ;
   TCHAR          szObject [PerfObjectLen + 1] ;
   TCHAR          szDefaultObject [PerfObjectLen + 1] ;

   CBReset (hWndCB) ;
   strclr (szDefaultObject) ;

   pObject = FirstObject (pPerfData) ;

   for (i = 0, pObject = FirstObject (pPerfData) ;
        i < pPerfData->NumObjectTypes ;
        i++, pObject = NextObject (pObject))
      {  // for
      if (pObject->DetailLevel <= dwDetailLevel)
         {  // if
         strclr (szObject) ;
         QueryPerformanceName (pSysInfo, pObject->ObjectNameTitleIndex,
                               0, PerfObjectLen, szObject, FALSE) ;

         // if szObject not empty, add it to the Combo-box
         if (!strsame(szObject, NULL_NAME))
            {
            iIndex = CBAdd (hWndCB, szObject) ;
            CBSetData (hWndCB, iIndex, (DWORD) pObject) ;

            if ((LONG)pObject->ObjectNameTitleIndex == pPerfData->DefaultObject)
               lstrcpy (szDefaultObject, szObject) ;
            } // if szObject not empty
         }  // if
      }  // for

   if (bIncludeAll)
      {
      StringLoad (IDS_ALLOBJECTS, szObject) ;
      CBInsert (hWndCB, 0, szObject) ;
      // assume "ALL" is default unless overridden
      lstrcpy (szDefaultObject, szObject) ;
      }

   if (lpszDefaultObject)
      lstrcpy (szDefaultObject, lpszDefaultObject) ;

   iIndex = CBFind (hWndCB, szDefaultObject) ;
   CBSetSelection (hWndCB, (iIndex != CB_ERR) ? iIndex : 0) ;

   return (i) ;
   }  // CBLoadObjects

int LBLoadObjects (HWND hWndLB,
                   PPERFDATA pPerfData,
                   PPERFSYSTEM pSysInfo,
                   DWORD dwDetailLevel,
                   LPTSTR lpszDefaultObject,
                   BOOL bIncludeAll)
/*
   Effect:        Load into the list box LB one item for each Object in
                  pPerfData. For each item, look up the object's name in
                  the registry strings associated with pSysInfo, and
                  attach the object to the data field of the LB item.

                  Dont add those objects that are more detailed than
                  dwDetailLevel.

                  Set the current selected LB item to the object named
                  lpszDefaultObject, or to the default object specified in
                  pPerfData if lpszDefaultObject is NULL.
*/
   {  // LBLoadObjects
   UINT           i ;
   int            iIndex ;
   PPERFOBJECT    pObject ;
   TCHAR          szObject [PerfObjectLen + 1] ;
   TCHAR          szDefaultObject [PerfObjectLen + 1] ;

   LBReset (hWndLB) ;
   strclr (szDefaultObject) ;

   pObject = FirstObject (pPerfData) ;

   for (i = 0, pObject = FirstObject (pPerfData) ;
        i < pPerfData->NumObjectTypes ;
        i++, pObject = NextObject (pObject))
      {  // for
      if (pObject->DetailLevel <= dwDetailLevel)
         {  // if
         strclr (szObject) ;
         QueryPerformanceName (pSysInfo, pObject->ObjectNameTitleIndex,
                               0, PerfObjectLen, szObject, FALSE) ;

         // if szObject is not empty, add it to the listbox
         if (!strsame(szObject, NULL_NAME))
            {
            iIndex = LBAdd (hWndLB, szObject) ;
            LBSetData (hWndLB, iIndex, (DWORD) pObject) ;

            if ((LONG)pObject->ObjectNameTitleIndex == pPerfData->DefaultObject)
               lstrcpy (szDefaultObject, szObject) ;
            } // if szObject is not empty
         }
      }  // for

   if (bIncludeAll)
      {
      StringLoad (IDS_ALLOBJECTS, szObject) ;
      LBInsert (hWndLB, 0, szObject) ;
         LBSetData (hWndLB, iIndex, (DWORD) NULL) ;
      // assume "ALL" is default unless overridden
      lstrcpy (szDefaultObject, szObject) ;
      }

   if (lpszDefaultObject)
      lstrcpy (szDefaultObject, lpszDefaultObject) ;

   iIndex = LBFind (hWndLB, szDefaultObject) ;
   LBSetSelection (hWndLB, (iIndex != LB_ERR) ? iIndex : 0) ;

   return (i) ;
   }  // LBLoadObjects

/***************************************************************************\
* GetObjectDef()
*
* Entry: pointer to data block and the number of the object type
* Exit:  returns a pointer to the specified object type definition
*
\***************************************************************************/

PERF_OBJECT_TYPE *GetObjectDef(
    PERF_DATA_BLOCK *pDataBlock,
    DWORD NumObjectType)
{
    DWORD NumTypeDef;

    PERF_OBJECT_TYPE *pObjectDef;

    pObjectDef = FirstObject(pDataBlock);

    for ( NumTypeDef = 0;
  NumTypeDef < pDataBlock->NumObjectTypes;
  NumTypeDef++ ) {

if ( NumTypeDef == NumObjectType ) {

    return pObjectDef;
}
        pObjectDef = NextObject(pObjectDef);
    }
    return 0;
}

/***************************************************************************\
* GetObjectDefByTitleIndex()
*
* Entry: pointer to data block and the title index of the object type
* Exit:  returns a pointer to the specified object type definition
*
\***************************************************************************/

PERF_OBJECT_TYPE *GetObjectDefByTitleIndex(
    PERF_DATA_BLOCK *pDataBlock,
    DWORD ObjectTypeTitleIndex)
{
    DWORD NumTypeDef;

    PERF_OBJECT_TYPE *pObjectDef;

    pObjectDef = FirstObject(pDataBlock);

    for ( NumTypeDef = 0;
  NumTypeDef < pDataBlock->NumObjectTypes;
  NumTypeDef++ ) {

        if ( pObjectDef->ObjectNameTitleIndex == ObjectTypeTitleIndex ) {

    return pObjectDef;
}
        pObjectDef = NextObject(pObjectDef);
    }
    return 0;
}

/***************************************************************************\
* GetObjectDefByName()
*
* Entry: pointer to data block and the name of the object type
* Exit:  returns a pointer to the specified object type definition
*
\***************************************************************************/

PERF_OBJECT_TYPE *GetObjectDefByName(
    PPERFSYSTEM pSystem,
    PERF_DATA_BLOCK *pDataBlock,
    LPTSTR pObjectName)
{
    DWORD NumTypeDef;
    TCHAR szObjectName [PerfObjectLen + 1] ;

    PERF_OBJECT_TYPE *pObjectDef;

    pObjectDef = FirstObject(pDataBlock);
    for ( NumTypeDef = 0;
  NumTypeDef < pDataBlock->NumObjectTypes;
  NumTypeDef++ ) {

        ObjectName (pSystem, pObjectDef, szObjectName, PerfObjectLen) ;
        if (strsame (szObjectName, pObjectName) ) {

    return pObjectDef;
}
        pObjectDef = NextObject(pObjectDef);
    }
    return 0;
}

/***************************************************************************\
* GetObjectIdByName()
*
* Entry: pointer to data block and the name of the object type
* Exit:  returns the Object title index for the specified Object Name
*
\***************************************************************************/

DWORD GetObjectIdByName(
    PPERFSYSTEM pSystem,
    PERF_DATA_BLOCK *pDataBlock,
    LPTSTR pObjectName)
{
    DWORD NumTypeDef;
    TCHAR szObjectName [PerfObjectLen + 1] ;

    PERF_OBJECT_TYPE *pObjectDef;

    pObjectDef = FirstObject(pDataBlock);
    for ( NumTypeDef = 0;
  NumTypeDef < pDataBlock->NumObjectTypes;
  NumTypeDef++ ) {

        ObjectName (pSystem, pObjectDef, szObjectName, PerfObjectLen) ;
        if (strsame (szObjectName, pObjectName) ) {

    return pObjectDef->ObjectNameTitleIndex;
}
        pObjectDef = NextObject(pObjectDef);
    }
    return 0;
}

/***************************************************************************\
* GetCounterDef()
*
* Entry: pointer to object type definition the number of the Counter
* definition
* Exit:  returns a pointer to the specified Counter definition
*
\***************************************************************************/

PERF_COUNTER_DEFINITION *GetCounterDef(
    PERF_OBJECT_TYPE *pObjectDef,
    DWORD NumCounter)
{
    DWORD NumCtrDef;

    PERF_COUNTER_DEFINITION *pCounterDef;

    pCounterDef = FirstCounter(pObjectDef);

    for ( NumCtrDef = 0;
  NumCtrDef < pObjectDef->NumCounters;
  NumCtrDef++ ) {

if ( NumCtrDef == NumCounter ) {

    return pCounterDef;
}
        pCounterDef = NextCounter(pCounterDef);
    }
    return 0;
}

/***************************************************************************\
* GetCounterNumByTitleIndex()
*
* Entry: pointer to object type definition and the title index of
*        the name of the Counter definition
* Exit:  returns the number of the specified Counter definition
*
\***************************************************************************/

LONG GetCounterNumByTitleIndex(
    PERF_OBJECT_TYPE *pObjectDef,
    DWORD CounterTitleIndex)
{
    DWORD NumCtrDef;

    PERF_COUNTER_DEFINITION *pCounterDef;

    pCounterDef = FirstCounter(pObjectDef);

    for ( NumCtrDef = 0;
  NumCtrDef < pObjectDef->NumCounters;
  NumCtrDef++ ) {

        if ( pCounterDef->CounterNameTitleIndex == CounterTitleIndex ) {

    return NumCtrDef;
}
        pCounterDef = NextCounter(pCounterDef);
    }
    return 0;
}

/***************************************************************************\
* GetCounterData()
*
* Entry: pointer to object definition and number of counter, must be
* an object with no instances
* Exit:  returns a pointer to the data
*
\***************************************************************************/

PVOID GetCounterData(
    PERF_OBJECT_TYPE *pObjectDef,
    PERF_COUNTER_DEFINITION *pCounterDef)
{

    PERF_COUNTER_BLOCK *pCtrBlock;

    pCtrBlock = (PERF_COUNTER_BLOCK *)((PCHAR)pObjectDef +
      pObjectDef->DefinitionLength);

    return (PVOID)((PCHAR)pCtrBlock + pCounterDef->CounterOffset);
}

/***************************************************************************\
* GetInstanceCounterData()
*
* Entry: pointer to object definition and number of counter, and a pointer
*        to the instance for which the data is to be retrieved
* Exit:  returns a pointer to the data
*
\***************************************************************************/

PVOID GetInstanceCounterData(
    PERF_OBJECT_TYPE *pObjectDef,
    PERF_INSTANCE_DEFINITION *pInstanceDef,
    PERF_COUNTER_DEFINITION *pCounterDef)
{

    PERF_COUNTER_BLOCK *pCtrBlock;

    pCtrBlock = (PERF_COUNTER_BLOCK *)((PCHAR)pInstanceDef +
      pInstanceDef->ByteLength);

    return (PVOID)((PCHAR)pCtrBlock + pCounterDef->CounterOffset);
}

/***************************************************************************\
* GetNextInstance()
*
* Entry: pointer to instance definition
* Exit:  returns a pointer to the next instance definition.  If none,
*        points to byte past this instance
*
\***************************************************************************/

PERF_INSTANCE_DEFINITION *GetNextInstance(
    PERF_INSTANCE_DEFINITION *pInstDef)
{
    PERF_COUNTER_BLOCK *pCtrBlock;

    pCtrBlock = (PERF_COUNTER_BLOCK *)
                ((PCHAR) pInstDef + pInstDef->ByteLength);

    return (PERF_INSTANCE_DEFINITION *)
           ((PCHAR) pCtrBlock + pCtrBlock->ByteLength);
}

/***************************************************************************\
* GetInstance()
*
* Entry: pointer to object type definition, the name of the instance,
* the name of the parent object type, and the parent instance index.
* The name of the parent object type is NULL if no parent.
* Exit:  returns a pointer to the specified instance definition
*
\***************************************************************************/

PERF_INSTANCE_DEFINITION *GetInstance(
    PERF_OBJECT_TYPE *pObjectDef,
    LONG InstanceNumber)
{

   PERF_INSTANCE_DEFINITION *pInstanceDef;
   LONG NumInstance;

   if (!pObjectDef)
      {
      return 0;
      }

   pInstanceDef = FirstInstance(pObjectDef);

   for ( NumInstance = 0;
      NumInstance < pObjectDef->NumInstances;
      NumInstance++ )
      {
   if ( InstanceNumber == NumInstance )
         {
         return pInstanceDef;
         }
      pInstanceDef = GetNextInstance(pInstanceDef);
      }

   return 0;
}

/***************************************************************************\
* GetInstanceByUniqueID()
*
* Entry: pointer to object type definition, and
*        the unique ID of the instance.
* Exit:  returns a pointer to the specified instance definition
*
\***************************************************************************/

PERF_INSTANCE_DEFINITION *GetInstanceByUniqueID(
    PERF_OBJECT_TYPE *pObjectDef,
    LONG UniqueID)
{

    PERF_INSTANCE_DEFINITION *pInstanceDef;

    LONG NumInstance;

    pInstanceDef = FirstInstance(pObjectDef);

    for ( NumInstance = 0;
  NumInstance < pObjectDef->NumInstances;
  NumInstance++ ) {

        if ( pInstanceDef->UniqueID == UniqueID ) {

    return pInstanceDef;
}
        pInstanceDef = GetNextInstance(pInstanceDef);
    }
    return 0;
}

/***************************************************************************\
* GetInstanceByNameUsingParentTitleIndex()
*
* Entry: pointer to object type definition, the name of the instance,
* and the name of the parent instance.
* The name of the parent instance is NULL if no parent.
* Exit:  returns a pointer to the specified instance definition
*
\***************************************************************************/

PERF_INSTANCE_DEFINITION *GetInstanceByNameUsingParentTitleIndex(
    PERF_DATA_BLOCK *pDataBlock,
    PERF_OBJECT_TYPE *pObjectDef,
    LPTSTR pInstanceName,
    LPTSTR pParentName)
{
   BOOL fHaveParent;
   PERF_OBJECT_TYPE *pParentObj;

    PERF_INSTANCE_DEFINITION  *pParentInst,
     *pInstanceDef;

   LONG   NumInstance;
   TCHAR  InstanceName[256];

   fHaveParent = FALSE;
   pInstanceDef = FirstInstance(pObjectDef);

   for ( NumInstance = 0;
      NumInstance < pObjectDef->NumInstances;
      NumInstance++ )
      {

      GetInstanceNameStr(pInstanceDef,InstanceName);
      if ( lstrcmp(InstanceName, pInstanceName) == 0 )
         {

         // Instance name matches

         if ( pParentName == NULL )
            {

            // No parent, we're done

            return pInstanceDef;

            }
         else
            {

            // Must match parent as well

            pParentObj = GetObjectDefByTitleIndex(
               pDataBlock,
               pInstanceDef->ParentObjectTitleIndex);

            if (!pParentObj)
               {
               // can't locate the parent, forget it
               break ;
               }

            // Object type of parent found; now find parent
            // instance

            pParentInst = GetInstance(pParentObj,
               pInstanceDef->ParentObjectInstance);

            if (!pParentInst)
               {
               // can't locate the parent instance, forget it
               break ;
               }

            GetInstanceNameStr(pParentInst,InstanceName);
            if ( lstrcmp(InstanceName, pParentName) == 0 )
               {

               // Parent Instance Name matches that passed in

               return pInstanceDef;
               }
            }
         }
      pInstanceDef = GetNextInstance(pInstanceDef);
      }
   return 0;
}

/***************************************************************************\
* GetInstanceByName()
*
* Entry: pointer to object type definition, the name of the instance,
* and the name of the parent instance.
* The name of the parent instance is NULL if no parent.
* Exit:  returns a pointer to the specified instance definition
*
\***************************************************************************/

PERF_INSTANCE_DEFINITION *GetInstanceByName(
    PERF_DATA_BLOCK *pDataBlock,
    PERF_OBJECT_TYPE *pObjectDef,
    LPTSTR pInstanceName,
    LPTSTR pParentName)
{
    BOOL fHaveParent;

    PERF_OBJECT_TYPE *pParentObj;

    PERF_INSTANCE_DEFINITION *pParentInst,
     *pInstanceDef;

    LONG  NumInstance;
    TCHAR  InstanceName[256];

    fHaveParent = FALSE;
    pInstanceDef = FirstInstance(pObjectDef);

    for ( NumInstance = 0;
  NumInstance < pObjectDef->NumInstances;
  NumInstance++ ) {

        GetInstanceNameStr(pInstanceDef,InstanceName);
        if ( lstrcmp(InstanceName, pInstanceName) == 0 ) {

    // Instance name matches

    if ( !pInstanceDef->ParentObjectTitleIndex ) {

// No parent, we're done

return pInstanceDef;

    } else {

// Must match parent as well

                pParentObj = GetObjectDefByTitleIndex(
 pDataBlock,
                                 pInstanceDef->ParentObjectTitleIndex);

// Object type of parent found; now find parent
// instance

pParentInst = GetInstance(pParentObj,
  pInstanceDef->ParentObjectInstance);

            GetInstanceNameStr(pParentInst,InstanceName);
                if ( lstrcmp(InstanceName, pParentName) == 0 ) {

    // Parent Instance Name matches that passed in

    return pInstanceDef;
}
    }
}
        pInstanceDef = GetNextInstance(pInstanceDef);
    }
    return 0;
}  // GetInstanceByName

BOOL FailedLineData (PPERFDATA pPerfData,
                     PLINE pLine)
/*
        This routine handles the case where there is no data for a
        system.
*/

{  // FailedLineData
   LONGLONG     liDummy ;

   // System no longer exists.
   liDummy = (LONGLONG) 0;
   if (pLine->lnCounterType == PERF_COUNTER_TIMER_INV)
   {
      // Timer inverse with Performance Counter as timer
      pLine->lnaOldCounterValue[0] = pLine->lnOldTime ;
      pLine->lnaCounterValue[0] = pLine->lnNewTime ;
   } else if (pLine->lnCounterType == PERF_100NSEC_TIMER_INV ||
              pLine->lnCounterType == PERF_100NSEC_MULTI_TIMER_INV)
   {
      // Timer inverse with System Time as timer
      pLine->lnaOldCounterValue[0] = pLine->lnOldTime100Ns ;
      pLine->lnaCounterValue[0] = pLine->lnNewTime100Ns ;
   } else
   {
      // Normal timer
      pLine->lnaOldCounterValue[0] =
      pLine->lnaCounterValue[0] =
      pLine->lnaOldCounterValue[1] =
      pLine->lnaCounterValue[1] = liDummy ;
   }
   return TRUE ;

}  // FailedLineData

BOOL UpdateLineData (PPERFDATA pPerfData,
                     PLINE pLine,
                     PPERFSYSTEM pSystem)
/*
   Assert:        pPerfData holds the performance data for the same
                  system as pLine.
*/
{  // UpdateLineData
   PPERFOBJECT       pObject ;
   PPERFINSTANCEDEF  pInstanceDef ;
   PPERFCOUNTERDEF   pCounterDef ;
   PPERFCOUNTERDEF   pCounterDef2 ;
   PDWORD            pCounterValue ;
   PDWORD            pCounterValue2 ;
   UINT              iCounterIndex ;
   LONGLONG          liDummy[2] ;

   // Use Object time units if available, otherwise use system
   // performance timer

   pLine->lnOldTime = pLine->lnNewTime;

   pLine->lnOldTime100Ns = pLine->lnNewTime100Ns;
   pLine->lnNewTime100Ns = *(LONGLONG UNALIGNED *)(&pPerfData->PerfTime100nSec);

   pLine->lnPerfFreq = *(LONGLONG UNALIGNED *)(&pPerfData->PerfFreq) ;

   if ((pLine->lnObject.TotalByteLength == 0) && !(PlayingBackLog()))
   {
      // this is the case when openning a setting file and the remote
      // system is not up at that time.  We have all the names but no
      // pObject, pCounter, etc.  So, we have to re-built the linedata.
      PPERFOBJECT       pObject ;
      PPERFCOUNTERDEF   pCounter ;
      PPERFINSTANCEDEF  pInstance ;

      pObject = LineFindObject (pSystem, pPerfData, pLine) ;
      if (!pObject)
      {
         //Something wrong, this object is still not there...
         return FALSE ;
      }
      pCounter = LineFindCounter (pSystem, pObject, pPerfData, pLine) ;
      if (!pCounter)
      {
         return FALSE ;
      }
      if (pObject &&
          pLine->lnObject.NumInstances > 0 &&
          pLine->lnInstanceName == NULL)
      {
         return FALSE ;
      }
      pInstance = LineFindInstance (pPerfData, pObject, pLine) ;
      if (!pInstance)
      {
         if (pLine->lnParentObjName)
         {
            MemoryFree (pLine->lnParentObjName) ;
            pLine->lnParentObjName = NULL ;
         }
      }

      pLine->lnCounterType = pCounter->CounterType;
      pLine->lnCounterLength = pCounter->CounterSize;

      if (pSystem->lpszValue && strsame (pSystem->lpszValue, L"Global"))
      {
         DWORD dwBufferSize = MemorySize (pSystem->lpszValue) ;
         memset (pSystem->lpszValue, 0, dwBufferSize) ;
      }

      AppendObjectToValueList (
                    pLine->lnObject.ObjectNameTitleIndex,
                    pSystem->lpszValue);

   }

   pObject = GetObjectDefByTitleIndex(
                pPerfData,
                pLine->lnObject.ObjectNameTitleIndex);

   if (!pObject)
   {
      // Object Type no longer exists.  This is possible if we are
      // looking at a log file which has not always collected all
      // the same data, such as appending measurements of different
      // object types.

      pCounterValue =
      pCounterValue2 = (PDWORD) liDummy;
      liDummy[0] = (LONGLONG)0;

      pLine->lnNewTime = *(LONGLONG UNALIGNED *)(&pPerfData->PerfTime);

      if (pLine->lnCounterType == PERF_COUNTER_TIMER_INV)
      {
         // Timer inverse with Performance Counter as timer
         pLine->lnaOldCounterValue[0] = pLine->lnOldTime ;
         pLine->lnaCounterValue[0] = pLine->lnNewTime ;
      } else if (pLine->lnCounterType == PERF_100NSEC_TIMER_INV ||
                 pLine->lnCounterType == PERF_100NSEC_MULTI_TIMER_INV)
      {
         // Timer inverse with System Time as timer
         pLine->lnaOldCounterValue[0] = pLine->lnOldTime100Ns ;
         pLine->lnaCounterValue[0] = pLine->lnNewTime100Ns ;
      } else
      {
         // Normal timer or counter
         pLine->lnaOldCounterValue[0] =
         pLine->lnaCounterValue[0] =
         pLine->lnaOldCounterValue[1] =
         pLine->lnaCounterValue[1] = liDummy[0] ;
      }
      return TRUE ;
   }
   else
   {
      pCounterDef = &pLine->lnCounterDef ;

      if (pCounterDef->CounterType & PERF_OBJECT_TIMER) {
         pLine->lnNewTime = *(LONGLONG UNALIGNED *)(&pObject->PerfTime);
      } else {
         pLine->lnNewTime = *(LONGLONG UNALIGNED *)(&pPerfData->PerfTime);
      }

      iCounterIndex = CounterIndex (pCounterDef, pObject) ;

      // Get second counter, only if we are not at
      // the end of the counters; some computations
      // require a second counter

      if (iCounterIndex < pObject->NumCounters-1 && iCounterIndex != -1) {
          pCounterDef2 = GetCounterDef(pObject, iCounterIndex+1);
      } else {
          pCounterDef2 = NULL;
      }

      if (pObject->NumInstances > 0)
      {

          if ( pLine->lnUniqueID != PERF_NO_UNIQUE_ID ) {
              pInstanceDef = GetInstanceByUniqueID(pObject,
                                               pLine->lnUniqueID);
          } else {

              pInstanceDef =
                  GetInstanceByNameUsingParentTitleIndex(
                      pPerfData,
                      pObject,
                      pLine->lnInstanceName,
                      pLine->lnPINName);
          }

          if (pInstanceDef) {
              pLine->lnInstanceDef = *pInstanceDef;
              pCounterValue = GetInstanceCounterData(pObject,
                                               pInstanceDef,
                                               pCounterDef);
              if ( pCounterDef2 ) {
                  pCounterValue2 = GetInstanceCounterData(pObject,
                                                    pInstanceDef,
                                                    pCounterDef2);
              }
          } else {
              pCounterValue =
              pCounterValue2 = (PDWORD) liDummy;
              liDummy[0] = (LONGLONG) 0;
              liDummy[1] = (LONGLONG) 0;
          }

          // Got everything...

      } // instances exist, look at them for counter blocks

      else
      {
          pCounterValue = GetCounterData(pObject, pCounterDef);
          if (pCounterDef2)
          {
              pCounterValue2 = GetCounterData(pObject, pCounterDef2);
          }

      } // counter def search when no instances
   }

   pLine->lnaOldCounterValue[0] = pLine->lnaCounterValue[0] ;

   if (pLine->lnCounterLength <= 4)
   {
       pLine->lnaCounterValue[0] = *pCounterValue;
       pLine->lnaCounterValue[0] &= (LONGLONG) (0x0ffffffff) ;
   }
   else
   {
       pLine->lnaCounterValue[0] = *(LONGLONG UNALIGNED *) pCounterValue;
   }

   // Get second counter, only if we are not at
   // the end of the counters; some computations
   // require a second counter

   if ( pCounterDef2 ) {
       pLine->lnaOldCounterValue[1] =
           pLine->lnaCounterValue[1] ;
       if (pCounterDef2->CounterSize <= 4)
       {
           pLine->lnaCounterValue[1] = *pCounterValue2;
           pLine->lnaCounterValue[1] &= (LONGLONG) (0x0ffffffff) ;
       }
       else
           pLine->lnaCounterValue[1] =
               *((LONGLONG UNALIGNED *) pCounterValue2);
      }
   return (TRUE) ;
}  // UpdateLineData

BOOL UpdateSystemData (PPERFSYSTEM pSystem,
                       PPERFDATA *ppPerfData)
   {  // UpdateSystemData
   #define        PERF_SYSTEM_TIMEOUT (60L * 1000L)
   long           lError ;
   DWORD          Status ;
   DWORD          Size;

   if (!ppPerfData)
      return (FALSE) ;

   while (TRUE)
      {
      if (pSystem->FailureTime)
         {
         BOOL     TimeToCheck = FALSE ;
         DWORD    CurrentTickCount = GetTickCount() ;

         if (CurrentTickCount < pSystem->FailureTime) {
            // wrap-around case
            if (CurrentTickCount >= PERF_SYSTEM_TIMEOUT)
               TimeToCheck = TRUE ;
            else if ( ~pSystem->FailureTime >= PERF_SYSTEM_TIMEOUT)
               TimeToCheck = TRUE ;
            else if (CurrentTickCount + (~pSystem->FailureTime) >= PERF_SYSTEM_TIMEOUT)
               TimeToCheck = TRUE ;
            }
         else {
            if (CurrentTickCount - pSystem->FailureTime >= PERF_SYSTEM_TIMEOUT)
               TimeToCheck = TRUE ;
            }

         if (TimeToCheck)
            {
            // free any memory hanging off this system
            SystemFree (pSystem, FALSE) ;

            // get the registry info
            pSystem->sysDataKey = OpenSystemPerfData(pSystem->sysName) ;

            Status = !ERROR_SUCCESS ;
            if (pSystem->sysDataKey)
               {
               Status = GetSystemNames(pSystem);
               }

            if (Status != ERROR_SUCCESS)
               {
               // something wrong in getting the registry info,
               // remote system must be still down (??)
               pSystem->FailureTime = GetTickCount();

               // Free any memory that may have created
               SystemFree (pSystem, FALSE) ;

               return (FALSE) ;
               }

            // time to check again
            pSystem->FailureTime = 0 ;
            }
         else
            {
            // not time to check again
            return (FALSE) ;
            }
         }

      if (pSystem->FailureTime == 0 )
         {
         Size = MemorySize (*ppPerfData);

         lError = GetSystemPerfData (pSystem->sysDataKey,
                                     pSystem->lpszValue,
                                     *ppPerfData,
                                     &Size) ;
         if ((!lError) &&
            (Size > 0) &&
            (*ppPerfData)->Signature[0] == (WCHAR)'P' &&
            (*ppPerfData)->Signature[1] == (WCHAR)'E' &&
            (*ppPerfData)->Signature[2] == (WCHAR)'R' &&
            (*ppPerfData)->Signature[3] == (WCHAR)'F' )
            {
            if (pSystem->dwSystemState == SYSTEM_OK)
               {
               return (TRUE) ;
               }
            else if (pSystem->dwSystemState == SYSTEM_DOWN ||
               pSystem->dwSystemState == SYSTEM_DOWN_RPT )
               {
               pSystem->dwSystemState = SYSTEM_RECONNECT ;
               }
            else if (pSystem->dwSystemState == SYSTEM_RECONNECT_RPT)
               {
               pSystem->dwSystemState = SYSTEM_OK ;
               }
            // for SYSTEM_RECONNECT case, we want to wait for Alert
            // view to report the re-connection first
            return (TRUE) ;
            }

         if (lError == ERROR_MORE_DATA)
            {
            *ppPerfData = MemoryResize (*ppPerfData,
                                        MemorySize (*ppPerfData) +
                                        dwPerfDataIncrease) ;
            if (!*ppPerfData)
               {
               if (pSystem->dwSystemState != SYSTEM_DOWN_RPT)
                  {
                  pSystem->dwSystemState = SYSTEM_DOWN ;
                  }
               pSystem->FailureTime = GetTickCount();
               return (FALSE) ;
               }
            }
         else
            {
            if (pSystem->dwSystemState != SYSTEM_DOWN_RPT)
               {
               pSystem->dwSystemState = SYSTEM_DOWN ;
               }
            pSystem->FailureTime = GetTickCount();
            return (FALSE) ;
            }  // else
         } // if
      }  // while
   }  // UpdateSystemData

BOOL FailedLinesForSystem (LPTSTR lpszSystem,
                           PPERFDATA pPerfData,
                           PLINE pLineFirst)
   {  // FailedLinesForSystem
   PLINE          pLine ;
   BOOL           bMatchFound = FALSE ;   // no line from this system

   for (pLine = pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for pLine
      if (strsamei (lpszSystem, pLine->lnSystemName))
         {
         FailedLineData (pPerfData, pLine) ;
         if (pLine->bFirstTime)
            {
            pLine->bFirstTime-- ;
            }
         bMatchFound = TRUE ; // one or more lines from this system
         }
      }  // for pLine

   return (bMatchFound) ;
   }

BOOL UpdateLinesForSystem (LPTSTR lpszSystem,
                           PPERFDATA pPerfData,
                           PLINE pLineFirst,
                           PPERFSYSTEM pSystem)
   {  // UpdateLinesForSystem
   PLINE          pLine ;
   BOOL           bMatchFound = FALSE ;   // no line from this system

   for (pLine = pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for pLine
      if (strsamei (lpszSystem, pLine->lnSystemName))
         {
         UpdateLineData (pPerfData, pLine, pSystem) ;
         if (pLine->bFirstTime)
            {
            pLine->bFirstTime-- ;
            }
         bMatchFound = TRUE ; // one or more lines from this system
         }
      }  // for pLine

   return (bMatchFound) ;
   }

BOOL PerfDataInitializeInstance (void)
   {
//   pGlobalPerfData = MemoryAllocate (STARTING_SYSINFO_SIZE) ;
//   return (pGlobalPerfData != NULL) ;
   return (TRUE) ;
   }

NTSTATUS  AddNamesToArray (LPTSTR lpNames,
   DWORD    dwLastId,
   LPWSTR   *lpCounterId)
   {
   LPWSTR      lpThisName;
   LPWSTR      lpStopString;
   DWORD       dwThisCounter;
   NTSTATUS    Status = ERROR_SUCCESS;

   for (lpThisName = lpNames;
        *lpThisName;
       )
      {

      // first string should be an integer (in decimal unicode digits)
      dwThisCounter = wcstoul(lpThisName, &lpStopString, 10);

      if ((dwThisCounter == 0) || (dwThisCounter == ULONG_MAX))
      {
         Status += 1;
         goto ADD_BAILOUT;  // bad entry
      }

      // point to corresponding counter name

      while (*lpThisName++);

      if (dwThisCounter <= dwLastId)
         {

         // and load array element;

         lpCounterId[dwThisCounter] = lpThisName;

         }

      while (*lpThisName++);

      }

ADD_BAILOUT:
   return (Status) ;
   }

// try the new way of getting data...

BOOL UpdateLines (PPPERFSYSTEM ppSystemFirst,
                  PLINE pLineFirst)
   {
   PPERFSYSTEM       pSystem ;
   int               iNoUseSystemDetected = 0 ;
   int               NumberOfSystems = 0 ;
   DWORD             WaitStatus ;
   HANDLE            *lpPacketHandles ;

   // allocate the handle array for multiple wait
   if (NumberOfHandles == 0)
      {
      NumberOfHandles = MAXIMUM_WAIT_OBJECTS ;
      lpHandles = (HANDLE *) MemoryAllocate (NumberOfHandles * sizeof (HANDLE)) ;
      if (!lpHandles)
         {
         // out of memory, can't go on
         NumberOfHandles = 0 ;
         return FALSE ;
         }
      }

   for (pSystem = *ppSystemFirst ;
        pSystem ;
        pSystem = pSystem->pSystemNext)
      {  // for

      // lock the state data mutex, should be quick unless this thread
      // is still getting data from last time
      if (pSystem->hStateDataMutex == 0)
         continue ;

      WaitStatus = WaitForSingleObject(pSystem->hStateDataMutex, 100L);
      if (WaitStatus == WAIT_OBJECT_0)
         {
         ResetEvent (pSystem->hPerfDataEvent) ;
         pSystem->StateData = WAIT_FOR_PERF_DATA ;

         // add this to the wait
         if (NumberOfSystems >= NumberOfHandles)
            {
            NumberOfHandles += MAXIMUM_WAIT_OBJECTS ;
            lpHandles = (HANDLE *) MemoryResize (
               lpHandles,
               NumberOfHandles * sizeof (HANDLE)) ;
            if (!lpHandles)
               {
               // out of memory, can't go on
               NumberOfHandles = 0 ;
               return FALSE ;
               }
            }

         lpHandles [NumberOfSystems] = pSystem->hPerfDataEvent ;
         NumberOfSystems++ ;

         // Send Message to thread to take a data sample
         PostThreadMessage (
            pSystem->dwThreadID,
            WM_GET_PERF_DATA,
            (WPARAM)0,
            (LPARAM)0) ;

         ReleaseMutex(pSystem->hStateDataMutex);
         }
      }

   // wait for all the data
   if (NumberOfSystems)
      {
      // increase timeout if we are monitoring lots of systems
      // For every additional 5 systems, add 5 more seconds
      lpPacketHandles = lpHandles ;
      do
         {
         WaitStatus = WaitForMultipleObjects (
            min (NumberOfSystems, MAXIMUM_WAIT_OBJECTS),
            lpPacketHandles,
            TRUE,       // wait for all objects
            DataTimeOut + (NumberOfSystems / 5) * DEFAULT_DATA_TIMEOUT);

         if (WaitStatus == WAIT_TIMEOUT ||
            NumberOfSystems <= MAXIMUM_WAIT_OBJECTS)
            {
//if (WaitStatus == WAIT_TIMEOUT)
//   mike2(TEXT("WaitTimeOut for %ld systems\n"), NumberOfSystems) ;

            break ;
            }

         // more systems --> more to wait
         NumberOfSystems -= MAXIMUM_WAIT_OBJECTS ;
         lpPacketHandles += MAXIMUM_WAIT_OBJECTS ;
         } while (TRUE) ;

      for (pSystem = *ppSystemFirst ;
           pSystem ;
           pSystem = pSystem->pSystemNext)
         {  // for

         if (pSystem->hStateDataMutex == 0)
            continue ;

         // lock the state data mutex
         WaitStatus = WaitForSingleObject(pSystem->hStateDataMutex, 100L);
         if (WaitStatus == WAIT_OBJECT_0)
            {
            if (pSystem->StateData != PERF_DATA_READY)
               {
               if (!FailedLinesForSystem (pSystem->sysName,
                  pSystem->pSystemPerfData,
                  pLineFirst))
                  {
                  if (!bAddLineInProgress)
                     {
                     // mark this system as no-longer-needed
                     iNoUseSystemDetected++ ;
                     pSystem->bSystemNoLongerNeeded = TRUE ;
                     }
                  }
               }
            else
               {
               if (!UpdateLinesForSystem (pSystem->sysName,
                    pSystem->pSystemPerfData,
                    pLineFirst,
                    pSystem))
                  {
                  if (!bAddLineInProgress)
                     {
                     // mark this system as no-longer-needed
                     iNoUseSystemDetected++ ;
                     pSystem->bSystemNoLongerNeeded = TRUE ;
                     }
                  }
               }
            pSystem->StateData = IDLE_STATE ;
            ReleaseMutex(pSystem->hStateDataMutex);
            }
         else
            {
            if (!FailedLinesForSystem (pSystem->sysName,
               pSystem->pSystemPerfData,
               pLineFirst))
               {
               if (!bAddLineInProgress)
                  {
                  // mark this system as no-longer-needed
                  iNoUseSystemDetected++ ;
                  pSystem->bSystemNoLongerNeeded = TRUE ;
                  }
               }
            }
         }

      // check for un-used systems
      if (iNoUseSystemDetected)
          {
          // some unused system(s) detected.
          DeleteUnusedSystems (ppSystemFirst, iNoUseSystemDetected) ;
          }
      }

   return (TRUE) ;
   }  // UpdateLines

void PerfDataThread (PPERFSYSTEM pSystem)
   {
   MSG      msg ;
   BOOL     bGetPerfData ;
   DWORD    WaitStatus ;

   while (GetMessage (&msg, NULL, 0, 0))
      {
      if (LOWORD(msg.message) == WM_GET_PERF_DATA)
         {

         // this system has been marked as no long used,
         // forget about getting data and continue until
         // we get to the WM_FREE_SYSTEM msg
         if (pSystem->bSystemNoLongerNeeded)
            continue ;

         bGetPerfData = FALSE ;

         if (!bAddLineInProgress ||
            (pSystem->lpszValue &&
            !strsame (pSystem->lpszValue, L"Global")))
            {
            bGetPerfData = UpdateSystemData (pSystem, &(pSystem->pSystemPerfData)) ;
            }

         WaitStatus = WaitForSingleObject(pSystem->hStateDataMutex, 1000L);
         if (WaitStatus == WAIT_OBJECT_0)
            {
            if (pSystem->StateData == WAIT_FOR_PERF_DATA)
               {
               pSystem->StateData = bGetPerfData ?
                  PERF_DATA_READY : PERF_DATA_FAIL ;
               }
            else
               {
//mike2(TEXT("Thread - System = %s, WaitStatus = %d\n"),
//pSystem->sysName, WaitStatus) ;
               }
            ReleaseMutex(pSystem->hStateDataMutex);
            SetEvent (pSystem->hPerfDataEvent) ;
            }
         }  // WM_GET_PERF_DATA MSG

      else if (LOWORD(msg.message) == WM_FREE_SYSTEM)
         {
//mike2(TEXT("Thread - System = %s closing\n"),
//pSystem->sysName) ;
         // do the memory cleanup during SystemFree stage
         // cleanup all the data collection variables
         if (pSystem->hPerfDataEvent)
             CloseHandle (pSystem->hPerfDataEvent) ;

         if (pSystem->hStateDataMutex)
             CloseHandle (pSystem->hStateDataMutex) ;

         if (pSystem->pSystemPerfData)
             MemoryFree (pSystem->pSystemPerfData);

         if (pSystem->lpszValue) {
             MemoryFree (pSystem->lpszValue);
             pSystem->lpszValue = NULL ;
         }

         CloseHandle (pSystem->hThread);

         MemoryFree (pSystem) ;
         break ;  // get out of message loop
         }  // WM_FREE_SYSTEM MSG
      }  // GetMessage Loop

   ExitThread (TRUE) ;
   }  // PerfDataThread


PERFDATA.H

#define dwPerfDataIncrease       0x1000

#define AllocatePerfData()      (MemoryAllocate (STARTING_SYSINFO_SIZE))

// Messages for the perf data collection thread
#define WM_GET_PERF_DATA (WM_USER + 102)
#define WM_FREE_SYSTEM   (WM_USER + 103)

// State for perf data collection
#define WAIT_FOR_PERF_DATA    0x0010
#define PERF_DATA_READY       0x0011
#define PERF_DATA_FAIL        0x0012
#define IDLE_STATE            0x0013

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define IsLocalComputer(a) (!lstrcmp(a,LocalComputerName))
#define IsRemoteComputer(a) (!IsLocalComputer(a))

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

#if 0
PPERFOBJECT FirstObject (PPERFDATA pPerfData) ;

PPERFOBJECT NextObject (PPERFOBJECT pObject) ;

PERF_COUNTER_DEFINITION *
FirstCounter(
    PERF_OBJECT_TYPE *pObjectDef) ;

PERF_COUNTER_DEFINITION *
NextCounter(
    PERF_COUNTER_DEFINITION *pCounterDef) ;
#endif

#define FirstObject(pPerfData)         \
   ((PPERFOBJECT) ((PBYTE) pPerfData + pPerfData->HeaderLength))

#define NextObject(pObject)            \
   ((PPERFOBJECT) ((PBYTE) pObject + pObject->TotalByteLength))

#define FirstCounter(pObjectDef)       \
   ((PERF_COUNTER_DEFINITION *) ((PCHAR)pObjectDef + pObjectDef->HeaderLength))

#define NextCounter(pCounterDef)       \
   ((PERF_COUNTER_DEFINITION *) ((PCHAR)pCounterDef + pCounterDef->ByteLength))

void
GetInstanceNameStr (PPERFINSTANCEDEF pInstance,
                    LPTSTR lpszInstance) ;

LPTSTR
GetInstanceName (PPERFINSTANCEDEF pInstance) ;

void
GetPerfComputerName(PPERFDATA pPerfData,
                    LPTSTR szComputerName) ;

PERF_INSTANCE_DEFINITION *GetInstanceByName(
    PERF_DATA_BLOCK *pDataBlock,
    PERF_OBJECT_TYPE *pObjectDef,
    LPTSTR pInstanceName,
    LPTSTR pParentName) ;

PERF_INSTANCE_DEFINITION *GetInstanceByUniqueID(
    PERF_OBJECT_TYPE *pObjectDef,
    LONG UniqueID) ;

HKEY OpenSystemPerfData (IN LPCTSTR lpszSystem) ;

LONG GetSystemPerfData (
    IN HKEY hKeySystem,
    IN LPTSTR lpszValue,
    OUT PPERFDATA pPerfData,
    OUT PDWORD pdwPerfDataLen
);

BOOL CloseSystemPerfData (HKEY hKeySystem) ;

int CBLoadObjects (HWND hWndCB,
                   PPERFDATA pPerfData,
                   PPERFSYSTEM pSysInfo,
                   DWORD dwDetailLevel,
                   LPTSTR lpszDefaultObject,
                   BOOL bIncludeAll) ;

int LBLoadObjects (HWND hWndCB,
                   PPERFDATA pPerfData,
                   PPERFSYSTEM pSysInfo,
                   DWORD dwDetailLevel,
                   LPTSTR lpszDefaultObject,
                   BOOL bIncludeAll) ;

BOOL UpdateSystemData (PPERFSYSTEM pSystem,
                       PPERFDATA *ppPerfData) ;

BOOL UpdateLinesForSystem (LPTSTR lpszSystem,
                           PPERFDATA pPerfData,
                           PLINE pLineFirst,
                           PPERFSYSTEM pSystem) ;

BOOL FailedLinesForSystem (LPTSTR lpszSystem,
                           PPERFDATA pPerfData,
                           PLINE pLineFirst) ;

BOOL UpdateLines (PPPERFSYSTEM ppSystemFirst,
                  PLINE pLineFirst) ;

BOOL PerfDataInitializeInstance (void) ;

DWORD
QueryPerformanceName(
    PPERFSYSTEM pSysInfo,
    DWORD dwTitleIndex,
    LANGID LangID,
    DWORD cbTitle,
    LPTSTR lpTitle,
    BOOL Help
    );

PERF_INSTANCE_DEFINITION *
FirstInstance(
    PERF_OBJECT_TYPE *pObjectDef) ;

PERF_INSTANCE_DEFINITION *
NextInstance(
    PERF_INSTANCE_DEFINITION *pInstDef) ;

int CounterIndex (PPERFCOUNTERDEF pCounterToFind,
                  PPERFOBJECT pObject) ;

DWORD GetSystemNames(PPERFSYSTEM pSysInfo) ;

PERF_OBJECT_TYPE *GetObjectDefByTitleIndex(
    PERF_DATA_BLOCK *pDataBlock,
    DWORD ObjectTypeTitleIndex) ;

PERF_OBJECT_TYPE *GetObjectDefByName(
    PPERFSYSTEM pSystem,
    PERF_DATA_BLOCK *pDataBlock,
    LPTSTR pObjectName) ;

DWORD GetObjectIdByName(
    PPERFSYSTEM pSystem,
    PERF_DATA_BLOCK *pDataBlock,
    LPTSTR pObjectName) ;

LPTSTR
InstanceName(
PERF_INSTANCE_DEFINITION *pInstDef) ;

void PerfDataThread (PPERFSYSTEM pSystem) ;


PERFERR.H

#define PostError()


PERFMON.C

/*****************************************************************************
 *
 *  Perfmon.c - This is the WinMain module. It creates the main window and
 *              the threads, and contains the main MainWndProc.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *  Authors -
 *
 *       Russ Blake
 *       Mike Moskowitz
 *       Hon-Wah Chan
 *       Bob Watson
 *
 ****************************************************************************/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//
#undef NOSYSCOMMANDS

// DEFINE_GLOBALS will define all the globals listed in globals.h
#define DEFINE_GLOBALS

#include "perfmon.h"

#include "command.h"

#include "graph.h"
#include "log.h"
#include "alert.h"
#include "report.h"     // for CreateReportWindow
#include "legend.h"
#include "init.h"
#include "perfmops.h"
#include "toolbar.h"    // for CreateToolbar
#include "status.h"     // for CreatePMStatusWindow
#include "utils.h"

#include "fileopen.h"   // for FileOpen

#define dwToolbarStyle     (WS_CHILD | WS_VISIBLE | TBS_NOCAPTION)

extern TCHAR szInternational[] ;

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnSize (HWND hWnd,
                    WORD xWidth,
                    WORD yHeight)
/*
   Effect:        Perform any actions needed when the main window is
                  resized. In particular, size the four data windows,
                  only one of which is visible right now.
*/
   {  // OnSize
   SizePerfmonComponents () ;
   }

void static OnCreate (HWND hWnd)
/*
   Effect:        Perform all actions needed when the main window is
                  created. In particular, create the three data windows,
                  and show one of them.

   To Do:         Check for proper creation. If not possible, we will
                  need to abort creation of the program.

   Called By:     MainWndProc only, in response to a WM_CREATE message.
*/
   {  // OnCreate
   hWndGraph = CreateGraphWindow (hWnd) ;

#ifdef ADVANCED_PERFMON
   hWndLog = CreateLogWindow (hWnd) ;
   hWndAlert = CreateAlertWindow (hWnd) ;
   hWndReport = CreateReportWindow (hWnd) ;
#endif

   hWndStatus = CreatePMStatusWindow (hWnd) ;

   CreateToolbarWnd (hWnd) ;
   MinimumSize += WindowHeight (hWndToolbar) ;

   Options.bMenubar = TRUE ;
   Options.bToolbar = TRUE ;
   Options.bStatusbar = TRUE;
   Options.bAlwaysOnTop = FALSE ;

   // initialize to chart view - HWC
   iPerfmonView = IDM_VIEWCHART;

   ShowWindow (PerfmonViewWindow (), SW_SHOWNORMAL) ;
   }  // OnCreate

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

void MenuBarHit (DWORD wParam)
{
   if (wParam == MENUCLOSING)
      {
      StatusLineReady (hWndStatus) ;
      dwCurrentMenuID = 0 ;
      }
   else if (HIWORD(wParam) & MF_SYSMENU)
      {
      WORD   SystemMenuItem = 0 ;
      switch (LOWORD (wParam))
         {
         case SC_RESTORE:
            SystemMenuItem = IDM_SYSTEMRESTORE ;
            break ;

         case SC_SIZE:
            SystemMenuItem = IDM_SYSTEMSIZE ;
            break ;

         case SC_MOVE:
            SystemMenuItem = IDM_SYSTEMMOVE ;
            break ;

         case SC_MINIMIZE:
            SystemMenuItem = IDM_SYSTEMMINIMIZE ;
            break ;

         case SC_MAXIMIZE:
            SystemMenuItem = IDM_SYSTEMMAXIMIZE ;
            break ;

         case SC_CLOSE:
            SystemMenuItem = IDM_SYSTEMCLOSE ;
            break ;

         case SC_TASKLIST:
            SystemMenuItem = IDM_SYSTEMSWITCHTO ;
            break ;
         }

      if (SystemMenuItem)
         {
         StatusLine (hWndStatus, SystemMenuItem) ;
         dwCurrentMenuID = MenuIDToHelpID (SystemMenuItem) ;
         }
      }
   else
      {
      StatusLine (hWndStatus, LOWORD (wParam)) ;
      }
}

void OnDropFile (DWORD wParam)
   {
   TCHAR FileName [FilePathLen + 1] ;
   LPTSTR         pFileNameStart ;
   HANDLE         hFindFile ;
   WIN32_FIND_DATA FindFileInfo ;
   int            NameOffset ;
   int            NumOfFiles = 0 ;

   NumOfFiles = DragQueryFile ((HDROP) wParam, 0xffffffff, NULL, 0) ;
   if (NumOfFiles > 0)
      {
      // we only open the first file for now
      DragQueryFile((HDROP) wParam, 0, FileName, FilePathLen) ;

      pFileNameStart = ExtractFileName (FileName) ;
      NameOffset = pFileNameStart - FileName ;

      // convert short filename to long NTFS filename if necessary
      hFindFile = FindFirstFile (FileName, &FindFileInfo) ;
      if (hFindFile && hFindFile != INVALID_HANDLE_VALUE)
         {
         // append the file name back to the path name
         lstrcpy (&FileName[NameOffset], FindFileInfo.cFileName) ;
         FindClose (hFindFile) ;
         }

      FileOpen (hWndMain, (int)0, (LPTSTR)FileName) ;
      PrepareMenu (GetMenu (hWndMain));
      }

   DragFinish ((HDROP) wParam) ;
   }

LRESULT APIENTRY MainWndProc (HWND hWnd,
                              UINT message,
                              DWORD wParam,
                              LONG lParam)
   {
   LONG     lRetCode = 0L ;
   BOOL     bCallDefWinProc = FALSE ;

   switch (LOWORD (message))
      {  // switch
      case WM_LBUTTONDBLCLK:
         ShowPerfmonMenu (!Options.bMenubar) ;
         if (Options.bMenubar)
            {
            PrepareMenu (GetMenu (hWnd)) ;
            }
         break ;

   case WM_COMMAND:
      if (PerfmonCommand (hWnd,wParam,lParam))
         return(0);
         else
            bCallDefWinProc = TRUE ;
         break;

      case WM_MENUSELECT:
         MenuBarHit (wParam) ;
         break ;

      case WM_NCHITTEST:
        /* if we have no title/menu bar, clicking and dragging the client
         * area moves the window. To do this, return HTCAPTION.
         * Note dragging not allowed if window maximized, or if caption
         * bar is present.
         */
        wParam = DefWindowProc(hWnd, message, wParam, lParam);
        if (!Options.bMenubar &&
            (wParam == HTCLIENT) &&
            !IsZoomed (hWndMain))
           return HTCAPTION ;
        else
           return wParam ;
        break ;

      case WM_SHOWWINDOW:
         PrepareMenu (GetMenu (hWnd)) ;
         break ;

      case WM_SIZE:
         OnSize (hWnd, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      case WM_GETMINMAXINFO:
         {
         MINMAXINFO   *pMinMax ;

         pMinMax = (MINMAXINFO *) lParam ;
         pMinMax->ptMinTrackSize.x = MinimumSize ;
         pMinMax->ptMinTrackSize.y = MinimumSize ;
         }
         break ;

      case WM_F1DOWN:
         if (dwCurrentDlgID)
            {
            CallWinHelp (dwCurrentDlgID) ;
            }
         else if (dwCurrentMenuID)
            {
            CallWinHelp (dwCurrentMenuID) ;
            dwCurrentMenuID = 0 ;
            }
         break ;

      case WM_CREATE:
         OnCreate (hWnd) ;
         ViewChart (hWnd) ;
         PrepareMenu (GetMenu (hWnd)) ;
         break ;

      case WM_DESTROY:
         WinHelp (hWndMain, pszHelpFile, HELP_QUIT, 0) ;
         PostQuitMessage (0);
         break ;

      case WM_QUERYENDSESSION:
         // please shut it down
         return (1) ;
         break ;

      case WM_ENDSESSION:
         if (wParam == TRUE)
            {
            // close any log file before closing down
            PerfmonClose (hWnd) ;
            return (1) ;
            }
         else
            bCallDefWinProc = TRUE ;
         break ;

      case WM_CLOSE:
         PerfmonClose (hWnd) ;
         break ;

      case WM_ACTIVATE:
         {
         int   fActivate = LOWORD (wParam) ;

         bPerfmonIconic = (BOOL) HIWORD (wParam) ;
         if (bPerfmonIconic == 0 && fActivate != WA_INACTIVE)
            {
            // set focus on the Legend window
            if (iPerfmonView == IDM_VIEWCHART)
               {
               SetFocus (hWndGraphLegend) ;
               }
            else if (iPerfmonView == IDM_VIEWALERT)
               {
               SetFocus (hWndAlertLegend) ;
               }
            else if (iPerfmonView == IDM_VIEWLOG)
               {
               SetFocus (hWndLogEntries) ;
               }
            else if (iPerfmonView == IDM_VIEWREPORT)
               {
               SetFocus (hWndReport) ;
               }
            }
         }
         break ;

      case WM_SYSCOLORCHANGE:
         DeletePerfmonSystemObjects () ;
         CreatePerfmonSystemObjects () ;
         WindowInvalidate (PerfmonViewWindow()) ;
         break ;

      case WM_WININICHANGE:
         if (!lParam || strsamei((LPTSTR)lParam, szInternational))
            {
            GetDateTimeFormats () ;
            }
         break ;

      case WM_DROPFILES:
         OnDropFile (wParam) ;
         return (0) ;
         break ;

      default:
         bCallDefWinProc = TRUE ;
      break;

      }  // switch

   if (bCallDefWinProc)
      {
      lRetCode = DefWindowProc (hWnd, message, wParam, lParam) ;
      }
   return (lRetCode);
   }  // MainWndProc

int PASCAL WinMain (HINSTANCE hCurrentInstance,
            HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine,
                    int nCmdShow)
   {  // WinMain
   MSG      msg;

   if (!PerfmonInitialize (hCurrentInstance, hPrevInstance,
                           lpszCmdLine, nCmdShow))
      return (FALSE) ;

   DragAcceptFiles (hWndMain, TRUE) ;

   while (GetMessage (&msg, NULL, 0, 0))
      {
      if (!TranslateAccelerator(hWndMain, hAccelerators, &msg))
         {
         TranslateMessage (&msg) ;
         DispatchMessage (&msg) ;
         }
      }  // while

   return(msg.wParam);
   }

DWORD FAR PASCAL MessageFilterProc (int nCode,
                                    WPARAM wParam,
                                    LPARAM lParam)
   {
   LPMSG lpMsg = (LPMSG)lParam ;
   extern HHOOK lpMsgFilterProc ;

   if (nCode < 0)
      {
      return FALSE ;
      }

   if (nCode == MSGF_DIALOGBOX || nCode == MSGF_MENU)
      {
      if (lpMsg->message == WM_KEYDOWN && lpMsg->wParam == VK_F1)
          {
          PostMessage (hWndMain, WM_F1DOWN, nCode, 0L) ;
          return TRUE ;
          }
      }

   return (DefHookProc (nCode, wParam,
              (DWORD)lpMsg,
              &lpMsgFilterProc)) ;
   }

#if 0
/***************************************/
VOID ErrorExit(LPTSTR pszError,HWND hwnd)
/***************************************/
{
    // NOTE: make sure all lgraph calls set StopQuerying.

    if (hwnd)
    {
        MessageBox(NULL, pszError, nm_buf, MB_ICONHAND | MB_OK | MB_SYSTEMMODAL);
        DestroyWindow(hwnd);
    }
    return;
}
#endif

void SizePerfmonComponents (void)
   {
   RECT           rectClient ;
   int            xWidth, yHeight ;
   int            yToolbarHeight ;
   int            yStatusHeight ;
   int            yViewHeight ;

   GetClientRect (hWndMain, &rectClient) ;
   xWidth = rectClient.right - rectClient.left ;
   yHeight = rectClient.bottom - rectClient.top ;

   if (Options.bToolbar)
      {
      SendMessage (hWndToolbar, WM_SIZE, 0, 0L) ;
      }

   yToolbarHeight = Options.bToolbar ? (WindowHeight (hWndToolbar) - 1) : 0 ;
   yStatusHeight = Options.bStatusbar ? StatusHeight (hWndStatus) : 0 ;

   if (Options.bStatusbar)
      {
      if (yToolbarHeight + yStatusHeight > yHeight)
         {
         // too small to display both toolbar and status bar
         // just display part of the status bar
         yStatusHeight = yHeight - yToolbarHeight ;
         }

      MoveWindow (hWndStatus,
                  0, yHeight - yStatusHeight, xWidth, yStatusHeight, TRUE) ;
      //WindowInvalidate (hWndStatus) ;
      }
   //WindowInvalidate (hWndMain) ;
   WindowShow (hWndStatus, Options.bStatusbar) ;
   WindowShow (hWndToolbar, Options.bToolbar) ;

   yViewHeight = yHeight - yStatusHeight - yToolbarHeight ;

   MoveWindow (hWndGraph,
               0, yToolbarHeight,
               xWidth, yViewHeight,
               TRUE) ;
   MoveWindow (hWndAlert,
               0, yToolbarHeight,
               xWidth, yViewHeight,
               TRUE) ;
   MoveWindow (hWndLog,
               0, yToolbarHeight,
               xWidth, yViewHeight,
               TRUE) ;
   MoveWindow (hWndReport,
               0, yToolbarHeight,
               xWidth, yViewHeight,
               TRUE) ;

   }  // SizePerfmonComponents


PERFMON.H

/*****************************************************************************
 *
 *  Perfmon.h - This is the main include file. It contains all major data
 *              structures and general defines.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *  Author: Mike Moskowicz
 *
 *          [1-May-1992]
 *
 ****************************************************************************/

#include <limits.h>
#include <windows.h>
#include <commdlg.h>
#include <shellapi.h>
#include <stdlib.h>
#include <string.h>
#include <winperf.h>
#include <tchar.h>

#include "perfmrc.h"
#include "strids.h"
#include "menuids.h"
#include "dlgids.h"

#include "sizes.h"
#include "typedefs.h"
#include "winhelpr.h"

extern VOID LoadLineGraphSettings(PGRAPHSTRUCT);
extern VOID LoadRefreshSettings(PGRAPHSTRUCT);
extern VOID SetGraphTimer(PGRAPHSTRUCT lgraph) ;
extern BOOL QueryTitleEntry(HKEY,DWORD,DWORD,DWORD,LPTSTR);
extern VOID ResetGraphTimer(PGRAPHSTRUCT lgraph);

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

typedef LONG NTSTATUS;

#define VERSION_NUMBER      1
#define SAVED_LOG           4
#define SAVED_ALERT         2
#define SAVED_GRAPH         1
#define SAVED_WORKSPACE     (SAVED_GRAPH | SAVED_ALERT | SAVED_LOG)
#define FNAMLEN             128

#ifdef JAPAN
#define szScalesFontFace         TEXT("System")
#define iScalesFontHeight        10
#define szScalesPrinterFontFace  TEXT("System")
#else
#define szScalesFontFace         TEXT("Helv")
#define iScalesFontHeight        8
#define szScalesPrinterFontFace  TEXT("Helvetica")
#endif

#define iScalesPrinterFontHeight 12
#define iTitlePrinterFontHeight  18

#define crLightGray              RGB (0xC0, 0xC0, 0xC0)
#define crDarkGray               RGB (0x40, 0x40, 0x40)
#define crGray                   RGB (0x80, 0x80, 0x80)
#define crRed                    RGB (0xFF, 0x00, 0x00)
#define crGreen                  RGB (0x00, 0xFF, 0x00)
#define crBlue                   RGB (0x00, 0x00, 0xFF)
#define crBlack                  RGB (0x00, 0x00, 0x00)
#define crWhite                  RGB (0xFF, 0xFF, 0xFF)

#define LogTimerID               100
#define AlertTimerID             200
#define ChartTimerID             300
#define ReportTimerID            400

#define ChartTool                IDM_VIEWCHART
#define AlertTool                IDM_VIEWALERT
#define LogTool                  IDM_VIEWLOG
#define ReportTool               IDM_VIEWREPORT
#define AddTool                  IDM_TOOLBARADD
#define EditTool                 IDM_TOOLBARMODIFY
#define DeleteTool               IDM_TOOLBARDELETE
#define RefreshTool              IDM_TOOLBARREFRESH
#define BookmarkTool             IDM_OPTIONSBOOKMARK
#define OptionsTool              IDM_TOOLBAROPTIONS

#define szComputerPrefix         TEXT("\\\\")      // two backslashes

#define WIDESTYLES               FALSE       // Don't allow wide styled lines

//
// Determine if an argument is present by testing the value of the pointer
// to the argument value.
//

#define ARGUMENT_PRESENT(ArgumentPointer)    (\
    (CHAR *)(ArgumentPointer) != (CHAR *)(NULL) )

//==========================================================================//
//                                Global Data                               //
//==========================================================================//

#include "globals.h"

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

LRESULT APIENTRY MainWndProc (HWND hwnd,
                              UINT message,
                              DWORD wParam,
                              LONG lParam) ;

void SizePerfmonComponents (void) ;

#define TSPRINTF _stprintf

// #ifdef UNICODE
// #define TSPRINTF swprintf
// #else
// #define TSPRINTF sprintf
// #endif


PERFMOPS.C

#include "perfmon.h"
#include <lmcons.h>
#include <lmerr.h>
#include <lmapibuf.h>
#include <lmwksta.h>
#include "uiexprt.h"
#include <stdio.h>         // for sprintf
#include <locale.h>        // for setlocale
#include "utils.h"

#include "perfdata.h"      // for OpenSystemPerfData
#include "alert.h"         // for AlertInsertLine
#include "report.h"        // for ReportInsertLine
#include "grafdata.h"      // for GraphInsertLine
#include "log.h"           // for OpenLog
#include "fileopen.h"      // for FileGetName
#include "fileutil.h"      // for FileRead etc
#include "command.h"       // for PrepareMenu
#include "playback.h"      // for PlayingBackLog & LogPositionSystemTime
#include "system.h"
#include "globals.h"
#include "pmemory.h"       // for MemoryFree
#include "status.h"        // for StatusLineReady
#include "pmhelpid.h"

// test for delimiter, end of line and non-digit characters
// used by IsNumberInUnicodeList routine
//
#define DIGIT       1
#define DELIMITER   2
#define INVALID     3

// globals used for International Date and Time formats
enum DATE_STYLE
   {
   YEAR_FIRST,       // YYMMDD
   DAY_FIRST,        // DDMMYY
   MONTH_FIRST       // MMDDYY
   } DateStyle ;

TCHAR szInternational[] = TEXT("Intl") ;
TCHAR sz1159[6] ;       // AM String
TCHAR sz2359[6] ;       // PM String
int   iTime ;           // = 0 for 12-hour format,  <> 0 for 24-hour format
int   YearCharCount ;   // = 4 for 1990, = 2 for 90

TCHAR szDateFormat[ResourceStringLen] ;
TCHAR szTimeFormat[ResourceStringLen] ;   // time format including msec
TCHAR szTimeFormat1[ResourceStringLen] ;  // time format without msec

TCHAR LeadingZeroStr [] = TEXT("%02d") ;
TCHAR NoLeadingZeroStr [] = TEXT("%d") ;

TCHAR szDecimal [2] ;
TCHAR szCurrentDecimal [2] ;

#define EvalThisChar(c,d) ( \
     (c == d) ? DELIMITER : \
     (c == 0) ? DELIMITER : \
     (c < (WCHAR)'0') ? INVALID : \
     (c > (WCHAR)'9') ? INVALID : \
     DIGIT)

#define SIZE_OF_BIGGEST_INTEGER  16
// #define SIZE_OF_BIGGEST_INTEGER (16*sizeof(WCHAR))

//==========================================================================//
//                                  Typedefs                                //
//==========================================================================//

BOOL AddObjectToSystem ( PLINE , PPERFSYSTEM );
BOOL GetLogFileComputer (HWND hWndParent, LPTSTR lpComputerName, DWORD BufferSize) ;

HWND PerfmonViewWindow (void)
/*
   Effect:        Return the current data window, i.e. the window currently
                  visible as the client area of Perfmon.  This is either a
                  chart, log, alert, or report window.
*/
   {  // PerfmonDataWindow
   switch (iPerfmonView)
      {  // switch
      case IDM_VIEWLOG:
         return (hWndLog) ;

      case IDM_VIEWALERT:
         return (hWndAlert) ;

      case IDM_VIEWREPORT:
         return (hWndReport) ;

//      case IDM_VIEWCHART:
      default:
         return (hWndGraph) ;
      }  // switch
   }  // PerfmonViewWindow

#define szChooseComputerLibrary     TEXT("ntlanman.dll")
#define szChooseComputerFunction    "I_SystemFocusDialog"

BOOL ChooseComputer (HWND hWndParent, LPTSTR lpszComputer)
/*
   Effect:        Display the choose Domain/Computer dialog provided by
                  network services.  If the user selects a computer,
                  copy the computer name to lpszComputer and return
                  nonnull. If the user cancels, return FALSE.

   Internals:     This dialog and code is currently not an exported
                  routine regularly found on any user's system. Right
                  now, we dynamically load and call the routine.

                  This is definitely temporary code that will be
                  rewritten when NT stabilizes. The callers of this
                  routine, however, will not need to be modified.

                  Also, the Domain/Computer dialog currently allows
                  a domain to be selected, which we cannot use. We
                  therefore loop until the user cancels or selects
                  a computer, putting up a message if the user selects
                  a domain.

   Assert:        lpszComputer is at least MAX_SYSTEM_NAME_LENGTH + 1
                  characters.
*/
   {  // ChooseComputer
   BOOL                     bSuccess ;
   WCHAR                    wszWideComputer[MAX_COMPUTERNAME_LENGTH + 3] ;
   HLIBRARY                 hLibrary ;
   LPFNI_SYSTEMFOCUSDIALOG  lpfnChooseComputer ;
   LONG                     lError ;

   if (!PlayingBackLog())
      {

      // bring up the select network computer dialog
      hLibrary = LoadLibrary (szChooseComputerLibrary) ;
      if (!hLibrary || hLibrary == INVALID_HANDLE_VALUE)
         {
         return (FALSE) ;
         }

      lpfnChooseComputer = (LPFNI_SYSTEMFOCUSDIALOG)
         GetProcAddress (hLibrary, szChooseComputerFunction) ;

      if (!lpfnChooseComputer)
         {
         FreeLibrary (hLibrary) ;
         return (FALSE) ;
         }

      lError = (*lpfnChooseComputer) (hWndParent,
         FOCUSDLG_SERVERS_ONLY | FOCUSDLG_BROWSE_ALL_DOMAINS,
         wszWideComputer,
         sizeof(wszWideComputer) / sizeof(WCHAR),
         &bSuccess,
         pszHelpFile,
         HC_PM_idDlgSelectNetworkComputer) ;

      FreeLibrary (hLibrary) ;
      }
   else
      {
      // bring up the select Log Computer dialog
      bSuccess = GetLogFileComputer (hWndParent,
         wszWideComputer,
         sizeof(wszWideComputer) / sizeof(WCHAR)) ;
      }

   if (bSuccess)
      {
      lstrcpy (lpszComputer, wszWideComputer) ;
//      wcstombs(lpszComputer, wszWideComputer, MAX_COMPUTERNAME_LENGTH + 3) ;
      }

   return (bSuccess) ;
   }  // ChooseComputer

void SystemTimeDateString (SYSTEMTIME *pSystemTime,
                           LPTSTR lpszDate)
   {
   int      wYear ;

   wYear = pSystemTime->wYear ;
   if (YearCharCount == 2)
      {
      wYear %= 100 ;
      }

   switch (DateStyle)
      {
      case YEAR_FIRST:
         TSPRINTF (lpszDate, szDateFormat,
             wYear, pSystemTime->wMonth, pSystemTime->wDay) ;
         break ;

      case DAY_FIRST:
         TSPRINTF (lpszDate, szDateFormat,
             pSystemTime->wDay, pSystemTime->wMonth, wYear) ;
         break ;

      case MONTH_FIRST:
      default:
         TSPRINTF (lpszDate, szDateFormat,
             pSystemTime->wMonth, pSystemTime->wDay, wYear) ;
         break ;
      }
   }

void SystemTimeTimeString (SYSTEMTIME *pSystemTime,
                           LPTSTR lpszTime,
                           BOOL   bOutputMsec)
   {
   int            iHour ;
   BOOL           bPM ;

   if (iTime)
      {
      // 24 hor format
      if (bOutputMsec)
         {
         TSPRINTF (lpszTime, szTimeFormat,
                pSystemTime->wHour,
                pSystemTime->wMinute,
                (FLOAT)pSystemTime->wSecond +
                (FLOAT)pSystemTime->wMilliseconds / (FLOAT) 1000.0) ;
         }
      else
         {
         TSPRINTF (lpszTime, szTimeFormat1,
                pSystemTime->wHour,
                pSystemTime->wMinute,
                pSystemTime->wSecond) ;

         }
      }
   else
      {
      // 12 hour format
      iHour = pSystemTime->wHour ;
      bPM = (iHour >= 12) ;

      if (iHour > 12)
         iHour -= 12 ;
      else if (!iHour)
         iHour = 12 ;

      if (bOutputMsec)
         {
         TSPRINTF (lpszTime, szTimeFormat,
                iHour, pSystemTime->wMinute,
                (FLOAT)pSystemTime->wSecond +
                (FLOAT)pSystemTime->wMilliseconds / (FLOAT) 1000.0 ,
                bPM ? sz2359 : sz1159) ;
         }
      else
         {
         TSPRINTF (lpszTime, szTimeFormat1,
                iHour, pSystemTime->wMinute,
                pSystemTime->wSecond,
                bPM ? sz2359 : sz1159) ;
         }
      }
   }

void ShowPerfmonWindowText ()
   {
   LPTSTR   *ppFileName ;
   TCHAR    szApplication [MessageLen] ;

   switch (iPerfmonView)
      {
      case IDM_VIEWCHART:
         ppFileName = &pChartFileName ;
         break ;

      case IDM_VIEWALERT:
         ppFileName = &pAlertFileName ;
         break ;

      case IDM_VIEWREPORT:
         ppFileName = &pReportFileName ;
         break ;

      case IDM_VIEWLOG:
         ppFileName = &pLogFileName ;
         break ;

      default:
         ppFileName = NULL ;
         break ;
      }

   if (ppFileName == NULL || *ppFileName == NULL)
      {
      ppFileName = &pWorkSpaceFileName ;
      }

   // display the name file name on the Title bar.
   StringLoad (IDS_APPNAME, szApplication) ;

   if (*ppFileName)
      {
      lstrcat (szApplication, TEXT(" - ")) ;
      lstrcat (szApplication, *ppFileName) ;
      }
   SetWindowText (hWndMain, szApplication) ;
   }

void ShowPerfmonMenu (BOOL bMenu)
   {  // ShowPerfmonMenu
   if (!bMenu)
      {
      WindowEnableTitle (hWndMain, FALSE) ;
//      SetMenu(hWndMain, NULL) ;
      }
   else
      {
      WindowEnableTitle (hWndMain, TRUE) ;
      switch (iPerfmonView)
         {  // switch
         case IDM_VIEWCHART:
            SetMenu (hWndMain, hMenuChart) ;
            break ;

         case IDM_VIEWALERT:
            SetMenu (hWndMain, hMenuAlert) ;
            break ;

         case IDM_VIEWLOG:
            SetMenu (hWndMain, hMenuLog) ;
            break ;

         case IDM_VIEWREPORT:
            SetMenu (hWndMain, hMenuReport) ;
            break ;
         }  // switch
      }  // else

   if (bMenu != Options.bMenubar)
      {
      PrepareMenu (GetMenu (hWndMain)) ;
      }

   Options.bMenubar = bMenu ;

   // Show Window Text
   if (bMenu)
      {
      ShowPerfmonWindowText () ;
      }
   }  // ShowPerfmonMenu

void SmallFileSizeString (int iFileSize,
                          LPTSTR lpszFileText)
   {  // SmallFileSizeString
   if (iFileSize < 1000000)
      TSPRINTF (lpszFileText, TEXT(" %1.1fK "), ((FLOAT) iFileSize) / 1000.0f) ;
   else
      TSPRINTF (lpszFileText, TEXT(" %1.1fM "), ((FLOAT) iFileSize) / 1000000.0f) ;
   }  // SmallFileSizeString

BOOL DoWindowDrag (HWND hWnd, LPARAM lParam)
   {
   POINT    lPoint ;

   if (!Options.bMenubar && !IsZoomed (hWndMain))
      {
      // convert lParam from client to screen
      lPoint.x = LOWORD (lParam) ;
      lPoint.y = HIWORD (lParam) ;
      ClientToScreen (hWnd, &lPoint) ;
      lParam = MAKELONG (lPoint.x, lPoint.y) ;
      SendMessage (hWndMain, WM_NCLBUTTONDOWN, HTCAPTION, lParam) ;
      return (TRUE) ;
      }
   else
      return (FALSE) ;
   }

// Filetimes are in 100NS units
#define FILETIMES_PER_SECOND     10000000

int SystemTimeDifference (SYSTEMTIME *pst1, SYSTEMTIME *pst2)
   {
   LONGLONG       li1, li2 ;
   LONGLONG       liDifference, liDifferenceSeconds ;
   int            RetInteger;
   BOOL           bNegative;

   li1 = (LONGLONG) 0 ;
   li2 = (LONGLONG) 0 ;

   SystemTimeToFileTime (pst1, (FILETIME *) &li1) ;
   SystemTimeToFileTime (pst2, (FILETIME *) &li2) ;

   // check for special cases when the time can be 0
   if (li2 == (LONGLONG)0)
      {
      if (li1 == (LONGLONG)0)
         {
         return 0 ;
         }
      else
         {
         return -INT_MAX ;
         }
      }
   else if (li1 == (LONGLONG)0)
      {
      return INT_MAX ;
      }

   liDifference = li2 - li1 ;
   bNegative = liDifference <= (LONGLONG) 0 ;

   // add the round-off factor before doing the division
   if (bNegative)
      {
      liDifferenceSeconds = (LONGLONG) (- FILETIMES_PER_SECOND / 2) ;
      }
   else
      {
      liDifferenceSeconds = (LONGLONG) (FILETIMES_PER_SECOND / 2) ;
      }

   liDifferenceSeconds = liDifferenceSeconds + liDifference ;

   liDifferenceSeconds =
         liDifferenceSeconds / (LONGLONG) (FILETIMES_PER_SECOND) ;

   RetInteger = (int) (liDifferenceSeconds & ((LONGLONG) 0x0ffffffff));

   if (bNegative)
      {
      return (-RetInteger) ;
      }
   else
      {
      return (RetInteger) ;
      }
   }

BOOL InsertLine (PLINE pLine)
{  // InsertLine

    BOOL bReturn;

    switch (pLine->iLineType) {  // switch
        case LineTypeChart:
            bReturn = ChartInsertLine (pGraphs, pLine) ;
            break ;

        case LineTypeAlert:
            bReturn = AlertInsertLine (hWndAlert, pLine) ;
            break ;

        case LineTypeReport:
            bReturn = ReportInsertLine (hWndReport, pLine) ;
            break ;

    }  // switch

    return bReturn;

}  // InsertLine

BOOL OpenWorkspace (HANDLE hFile, DWORD dwMajorVersion, DWORD dwMinorVersion)
   {
   DISKWORKSPACE  DiskWorkspace ;

   if (!FileRead (hFile, &DiskWorkspace, sizeof(DiskWorkspace)))
      {
      goto Exit0 ;
      }

   if (DiskWorkspace.ChartOffset == 0 &&
       DiskWorkspace.AlertOffset == 0 &&
       DiskWorkspace.LogOffset == 0 &&
       DiskWorkspace.ReportOffset == 0)
      {
      goto Exit0 ;
      }

   switch (dwMajorVersion)
      {  // switch
      case (1):

         if (dwMinorVersion >= 1)
            {
            // setup the window position and size
            DiskWorkspace.WindowPlacement.length = sizeof(WINDOWPLACEMENT);
            DiskWorkspace.WindowPlacement.flags = WPF_SETMINPOSITION;
            if (!SetWindowPlacement (hWndMain, &(DiskWorkspace.WindowPlacement)))
                {
                goto Exit0 ;
                }
            }

         // change to the view as stored in the workspace file
         SendMessage (hWndMain, WM_COMMAND,
            (LONG)DiskWorkspace.iPerfmonView, 0L) ;
         iPerfmonView = DiskWorkspace.iPerfmonView ;

         if (DiskWorkspace.ChartOffset)
            {
            if (FileSeekBegin(hFile, DiskWorkspace.ChartOffset) == 0xFFFFFFFF)
               {
               goto Exit0 ;
               }

            if (!OpenChart (hWndGraph,
                           hFile,
                           dwMajorVersion,
                           dwMinorVersion,
                           FALSE))
               {
               goto Exit0 ;
               }
            }
         if (DiskWorkspace.AlertOffset)
            {
            if (FileSeekBegin(hFile, DiskWorkspace.AlertOffset) == 0xffffffff)
               {
               goto Exit0 ;
               }
            if (!OpenAlert (hWndAlert,
                       hFile,
                       dwMajorVersion,
                       dwMinorVersion,
                       FALSE))
               {
               goto Exit0 ;
               }
            }
         if (DiskWorkspace.LogOffset)
            {
            if (FileSeekBegin(hFile, DiskWorkspace.LogOffset) == 0xffffffff)
               {
               goto Exit0 ;
               }
            if (!OpenLog (hWndLog,
                          hFile,
                          dwMajorVersion,
                          dwMinorVersion,
                          FALSE))
               {
               goto Exit0 ;
               }
            }
         if (DiskWorkspace.ReportOffset)
            {
            if (FileSeekBegin(hFile, DiskWorkspace.ReportOffset) == 0xffffffff)
               {
               goto Exit0 ;
               }
            if (!OpenReport (hWndReport,
                        hFile,
                        dwMajorVersion,
                        dwMinorVersion,
                        FALSE))
               {
               goto Exit0 ;
               }
            }
         break ;

      default:
         goto Exit0 ;
         break ;
      }

   CloseHandle (hFile) ;
   return (TRUE) ;

Exit0:
   CloseHandle (hFile) ;
   return (FALSE) ;

   }  // OpenWorkspace

BOOL SaveWorkspace (void)
   {
   DISKWORKSPACE  DiskWorkspace ;
   PERFFILEHEADER FileHeader ;
   HANDLE         hFile ;
   long           DiskWorkspacePosition ;
   TCHAR          szFileName[FilePathLen] ;
   BOOL           bWriteErr = TRUE ;

   if (!FileGetName (PerfmonViewWindow(), IDS_WORKSPACEFILE, szFileName))
      {
      return (FALSE) ;
      }

   hFile = FileHandleCreate (szFileName) ;
   if (!hFile)
      {
      DlgErrorBox (PerfmonViewWindow (), ERR_CANT_OPEN, szFileName) ;
      return (FALSE) ;
      }

   memset (&FileHeader, 0, sizeof (FileHeader)) ;
   lstrcpy (FileHeader.szSignature, szPerfWorkspaceSignature) ;
   FileHeader.dwMajorVersion = WorkspaceMajorVersion ;
   FileHeader.dwMinorVersion = WorkspaceMinorVersion ;

   if (!FileWrite (hFile, &FileHeader, sizeof (PERFFILEHEADER)))
      {
      goto Exit0 ;
      }

   // reserve space in the file.  We will fill up info
   // and write into this guy later.
   memset (&DiskWorkspace, 0, sizeof(DiskWorkspace)) ;
   DiskWorkspacePosition = FileTell (hFile) ;
   DiskWorkspace.WindowPlacement.length = sizeof(WINDOWPLACEMENT);
   if (!GetWindowPlacement (hWndMain, &(DiskWorkspace.WindowPlacement)))
      {
      goto Exit0 ;
      }

   if (!FileWrite (hFile, &DiskWorkspace, sizeof (DISKWORKSPACE)))
      {
      goto Exit0 ;
      }

   // put in chart data
   DiskWorkspace.ChartOffset = FileTell (hFile) ;
   if (!SaveChart (hWndGraph, hFile, 0))
      {
      goto Exit0 ;
      }

   // put in alert data
   DiskWorkspace.AlertOffset = FileTell (hFile) ;
   if (!SaveAlert (hWndAlert, hFile, 0))
      {
      goto Exit0 ;
      }

   // put in log data
   DiskWorkspace.LogOffset = FileTell (hFile) ;
   if (!SaveLog (hWndLog, hFile, 0))
      {
      goto Exit0 ;
      }

   // put in report data
   DiskWorkspace.ReportOffset = FileTell (hFile) ;
   if (!SaveReport (hWndReport, hFile, 0))
      {
      goto Exit0 ;
      }

   // put in the disk header info
   DiskWorkspace.iPerfmonView = iPerfmonView ;
   FileSeekBegin (hFile, DiskWorkspacePosition) ;
   if (!FileWrite (hFile, &DiskWorkspace, sizeof (DISKWORKSPACE)))
      {
      goto Exit0 ;
      }
   bWriteErr = FALSE ;

Exit0:
   if (bWriteErr)
      {
      DlgErrorBox (PerfmonViewWindow (), ERR_SETTING_FILE, szFileName) ;
      }

   CloseHandle (hFile) ;

   }  // SaveWorkspace

void SetPerfmonOptions (OPTIONS *pOptions)
   {
   Options = *pOptions ;
   ShowPerfmonMenu (Options.bMenubar) ;
   SizePerfmonComponents () ;
   WindowSetTopmost (hWndMain, Options.bAlwaysOnTop) ;
   }  // SetPerfmonOptions

void ChangeSaveFileName (LPTSTR szFileName, int iPMView)
   {
   LPTSTR   *ppFullName ;
   LPTSTR   *ppFileName ;
   BOOL     errorInput = FALSE ;
   TCHAR    szApplication [MessageLen] ;

   switch (iPMView)
      {
      case IDM_VIEWCHART:
         ppFileName = &pChartFileName ;
         ppFullName = &pChartFullFileName ;
         break ;

      case IDM_VIEWALERT:
         ppFileName = &pAlertFileName ;
         ppFullName = &pAlertFullFileName ;
         break ;

      case IDM_VIEWREPORT:
         ppFileName = &pReportFileName ;
         ppFullName = &pReportFullFileName ;
         break ;

      case IDM_VIEWLOG:
         ppFileName = &pLogFileName ;
         ppFullName = &pLogFullFileName ;
         break ;

      case IDM_WORKSPACE:
         // not a view but a define
         ppFileName = &pWorkSpaceFileName ;
         ppFullName = &pWorkSpaceFullFileName ;
         break ;

      default:
         errorInput = TRUE ;
         break ;
      }

   if (errorInput)
      {
      return ;
      }

   // release last filename
   if (*ppFullName)
      {
      MemoryFree (*ppFullName) ;
      *ppFileName = NULL ;
      *ppFullName = NULL ;
      }

   // allocate new file name and display it
   if (szFileName && (*ppFullName = StringAllocate (szFileName)))
      {
      *ppFileName = ExtractFileName (*ppFullName) ;
      }

   if (iPerfmonView == iPMView || iPMView == IDM_WORKSPACE)
      {
      StatusLineReady (hWndStatus) ;

      if (Options.bMenubar)
         {
         // display the name file name on the Title bar.
         StringLoad (IDS_APPNAME, szApplication) ;

         if (*ppFileName == NULL)
            {
            ppFileName = &pWorkSpaceFileName ;
            }

         if (*ppFileName)
            {
            lstrcat (szApplication, TEXT(" - ")) ;
            lstrcat (szApplication, *ppFileName) ;
            }
         SetWindowText (hWndMain, szApplication) ;
         }
      }
   }     // ChangeSaveFileName

BOOL
IsNumberInUnicodeList (
    IN DWORD   dwNumber,
    IN LPTSTR  lpwszUnicodeList
)
/*++

IsNumberInUnicodeList

Arguments:

    IN dwNumber
        DWORD number to find in list

    IN lpwszUnicodeList
        Null terminated, Space delimited list of decimal numbers

Return Value:

    TRUE:
            dwNumber was found in the list of unicode number strings

    FALSE:
            dwNumber was not found in the list.

--*/
{
    DWORD   dwThisNumber;
    WCHAR   *pwcThisChar;
    BOOL    bValidNumber;
    BOOL    bNewItem;
    WCHAR   wcDelimiter;    // could be an argument to be more flexible

    if (lpwszUnicodeList == 0) return FALSE;    // null pointer, # not founde

    pwcThisChar = lpwszUnicodeList;
    dwThisNumber = 0;
    wcDelimiter = (WCHAR)' ';
    bValidNumber = FALSE;
    bNewItem = TRUE;

    while (TRUE) {
        switch (EvalThisChar (*pwcThisChar, wcDelimiter)) {
            case DIGIT:
                // if this is the first digit after a delimiter, then
                // set flags to start computing the new number
                if (bNewItem) {
                    bNewItem = FALSE;
                    bValidNumber = TRUE;
                }
                if (bValidNumber) {
                    dwThisNumber *= 10;
                    dwThisNumber += (*pwcThisChar - (WCHAR)'0');
                }
                break;

            case DELIMITER:
                // a delimter is either the delimiter character or the
                // end of the string ('\0') if when the delimiter has been
                // reached a valid number was found, then compare it to the
                // number from the argument list. if this is the end of the
                // string and no match was found, then return.
                //
                if (bValidNumber) {
                    if (dwThisNumber == dwNumber) return TRUE;
                    bValidNumber = FALSE;
                }
                if (*pwcThisChar == 0) {
                    return FALSE;
                } else {
                    bNewItem = TRUE;
                    dwThisNumber = 0;
                }
                break;

            case INVALID:
                // if an invalid character was encountered, ignore all
                // characters up to the next delimiter and then start fresh.
                // the invalid number is not compared.
                bValidNumber = FALSE;
                break;

            default:
                break;

        }
        pwcThisChar++;
    }

}   // IsNumberInUnicodeList

BOOL
AppendObjectToValueList (
    DWORD   dwObjectId,
    PWSTR   pwszValueList
)
/*++

AppendObjectToValueList

Arguments:

    IN dwNumber
        DWORD number to insert in list

    IN PUNICODE_STRING
        pointer to unicode string structure that contains buffer that is
        Null terminated, Space delimited list of decimal numbers that
        may have this number appended to.

Return Value:

    TRUE:
            dwNumber was added to list

    FALSE:
            dwNumber was not added. (because it's already there or
                an error occured)

--*/
{
    WCHAR           tempString [SIZE_OF_BIGGEST_INTEGER] ;
    DWORD           dwStrLen, dwNewStrLen;

    if (!pwszValueList) {
        return FALSE;
    }

    if (IsNumberInUnicodeList(dwObjectId, pwszValueList)) {
        return FALSE;   // object already in list
    } else {
        // append the new object id the  value list
        TSPRINTF (tempString, TEXT("%d "), dwObjectId) ;

        // see if string will fit (compare in bytes)

        dwStrLen = MemorySize (pwszValueList) - sizeof (UNICODE_NULL);

        dwNewStrLen = (lstrlen (pwszValueList) + lstrlen (tempString)) *
            sizeof (WCHAR);

        if (dwNewStrLen <= dwStrLen) {
            lstrcat (pwszValueList, tempString);
            return TRUE;
        } else {
            SetLastError (ERROR_OUTOFMEMORY);
            return FALSE;
        }
    }
}

BOOL
AddObjectToSystem (
    PLINE pLine,
    PPERFSYSTEM pFirstSystem
)
{
    PPERFSYSTEM     pSystem;

    if ((ARGUMENT_PRESENT (pLine)) && (ARGUMENT_PRESENT(pFirstSystem))) {
        pSystem = SystemGet (pFirstSystem, pLine->lnSystemName);

        if (pSystem) {
            return AppendObjectToValueList (
                pLine->lnObject.ObjectNameTitleIndex,
                pSystem->lpszValue);
        } else {
            return FALSE;
        }
    } else {
        return FALSE;
    }
}

BOOL
RemoveObjectsFromSystem (
    PPERFSYSTEM pSystem
)
{
    DWORD   dwBufferSize = 0;

    if (ARGUMENT_PRESENT (pSystem)) {
        // don't do foreign computers
        if (pSystem->lpszValue && !strsame (pSystem->lpszValue, L"Foreign")){
            dwBufferSize = MemorySize (pSystem->lpszValue);

            memset (pSystem->lpszValue, 0, dwBufferSize);
            return TRUE;
        } else {
            return FALSE;
        }
    } else {
        return FALSE;
    }

}

BOOL
BuildValueListForSystems (
    PPERFSYSTEM pSystemListHead,
    PLINE       pLineListHead
)
/*++

BuildValueListForSystem

Abstract:

    Walks down line list and builds the list of objects to query from
    that system containing that line.

Arguments:

    pSystemListHead

        head of system linked list
        each system will have it's "Value Name" list appended

    pLineListHead

        head of line list that will be searched for creating the new
        valuelist.

Return Value:

--*/
{

    PPERFSYSTEM     pSystem;    // system that contains current line
    PLINE           pThisLine;  // current line

    if ((ARGUMENT_PRESENT (pLineListHead)) && (ARGUMENT_PRESENT(pSystemListHead))) {
        // clear system entries:
        for (pSystem = pSystemListHead; pSystem; pSystem = pSystem->pSystemNext) {
            if (pSystem && pSystem->FailureTime == 0) {
                RemoveObjectsFromSystem (pSystem);
            }
        }

        // add new enties

        for (pThisLine = pLineListHead; pThisLine; pThisLine = pThisLine->pLineNext) {

            pSystem = SystemGet (pSystemListHead, pThisLine->lnSystemName);
            if (pSystem && pSystem->FailureTime == 0) {
                AppendObjectToValueList (
                    pThisLine->lnObject.ObjectNameTitleIndex,
                    pSystem->lpszValue);

            }
        }
        return TRUE;
    } else {    // argument(s) missing
        return FALSE;
    }
}

// define in Addline.c
extern   PLINESTRUCT       pLineEdit ;
#define  bEditLine (pLineEdit != NULL)

BOOL
SetSystemValueNameToGlobal (
    PPERFSYSTEM pSystem
)
{

    if (!bEditLine && ARGUMENT_PRESENT(pSystem)) {
        if (pSystem->lpszValue && RemoveObjectsFromSystem(pSystem)) {
            lstrcpy (
                pSystem->lpszValue,
                TEXT("Global")) ;
            return TRUE;
        } else {
            return FALSE;
        }
    } else {
        return FALSE;
    }
}

BOOL
RemoveUnusedSystems (
    PPERFSYSTEM pSystemHead,
    PLINE       pLineHead
)
/*++

    walks system list and removes systems with no lines from list

--*/
{
    PPERFSYSTEM pSystem;
    PPERFSYSTEM pLastSystem;
    PLINE       pLine;
    BOOL        bSystemFound;

    pLastSystem = NULL;

    if ((ARGUMENT_PRESENT (pLineHead)) && (ARGUMENT_PRESENT(pSystemHead))) {
        for (pSystem = pSystemHead;
             pSystem;
             pLastSystem = pSystem, pSystem = pSystem->pSystemNext) {

            if (pSystem) {
                bSystemFound = FALSE;
                // walk lines to see if this system has a line
                for (pLine = pLineHead; pLine; pLine = pLine->pLineNext) {
                    // if system in line is this system, then bailout
                    if (strsame (pLine->lnSystemName, pSystem->sysName)) {
                        bSystemFound = TRUE;
                        break;
                    }
                }

                if (!bSystemFound) {    // delete this unused system

                    // fix pointers
                    pLastSystem->pSystemNext = pSystem->pSystemNext;

                    SystemFree (pSystem, TRUE);

                    // set pointer back to a valid structure
                    pSystem = pLastSystem;
                }
            }
        }
    }
    return TRUE;
}

void CreatePerfmonSystemObjects ()
{
   ColorBtnFace = GetSysColor (COLOR_BTNFACE) ;
   hBrushFace = CreateSolidBrush (ColorBtnFace) ;
   hPenHighlight = CreatePen (PS_SOLID, 1, GetSysColor (COLOR_BTNHIGHLIGHT)) ;
   hPenShadow = CreatePen (PS_SOLID, 1, GetSysColor (COLOR_BTNSHADOW)) ;
}

void DeletePerfmonSystemObjects ()
{
   if (hBrushFace)
      {
      DeleteBrush (hBrushFace) ;
      hBrushFace = 0 ;
      }
   if (hPenHighlight)
      {
      DeletePen (hPenHighlight) ;
      hPenHighlight = 0 ;
      }
   if (hPenShadow)
      {
      DeletePen (hPenShadow) ;
      hPenShadow = 0 ;
      }
}

// This routine count the number of the same charatcer in the input string
int  SameCharCount (LPTSTR pInputString)
{
   int      Count = 0 ;
   TCHAR    InputChar = *pInputString ;

   if (InputChar)
      {
      while (InputChar == *pInputString)
         {
         Count ++ ;
         pInputString ++ ;
         }
      }
   return (Count) ;
}

// create the format to be used in SystemTimeDateString()
BOOL CreateDateFormat (LPTSTR pShortDate)
{
   int   iIndex ;
   int   iDayCount ;
   int   iMonthCount ;
   int   DateSeparatorCount ;
   TCHAR szDateSeparator [10] ;
   BOOL  bFirstLeading, bSecondLeading, bThirdLeading ;

   // get the date format based on the first char
   if (*pShortDate == TEXT('M') || *pShortDate == TEXT('m'))
      {
      DateStyle = MONTH_FIRST ;
      }
   else if (*pShortDate == TEXT('D') || *pShortDate == TEXT('d'))
      {
      DateStyle = DAY_FIRST ;
      }
   else if (*pShortDate == TEXT('Y') || *pShortDate == TEXT('y'))
      {
      DateStyle = YEAR_FIRST ;
      }
   else
      {
      // bad format
      return FALSE ;
      }

   bFirstLeading = bSecondLeading = bThirdLeading = FALSE ;

   switch (DateStyle)
      {
      case YEAR_FIRST:
         // YYYY-MM-DD
         YearCharCount = SameCharCount (pShortDate) ;
         pShortDate += YearCharCount ;
         DateSeparatorCount = SameCharCount (pShortDate) ;

         // get the separator string
         for (iIndex = 0; iIndex < DateSeparatorCount; iIndex ++)
            {
            szDateSeparator [iIndex] = *pShortDate++ ;
            }
         szDateSeparator [iIndex] = TEXT('\0') ;

         iMonthCount = SameCharCount (pShortDate) ;
         pShortDate += iMonthCount + DateSeparatorCount ;
         iDayCount = SameCharCount (pShortDate) ;

         if (YearCharCount == 2)
            {
            bFirstLeading = TRUE ;
            }

         if (iMonthCount == 2)
            {
            bSecondLeading = TRUE ;
            }

         if (iDayCount == 2)
            {
            bThirdLeading = TRUE ;
            }

         break ;

      case MONTH_FIRST:
         // MM-DD-YYYY
         iMonthCount = SameCharCount (pShortDate) ;
         pShortDate += iMonthCount ;
         DateSeparatorCount = SameCharCount (pShortDate) ;

         // get the separator string
         for (iIndex = 0; iIndex < DateSeparatorCount; iIndex ++)
            {
            szDateSeparator [iIndex] = *pShortDate++ ;
            }
         szDateSeparator [iIndex] = TEXT('\0') ;

         iDayCount = SameCharCount (pShortDate) ;
         pShortDate += iMonthCount + DateSeparatorCount ;
         YearCharCount = SameCharCount (pShortDate) ;

         if (iMonthCount == 2)
            {
            bFirstLeading = TRUE ;
            }

         if (iDayCount == 2)
            {
            bSecondLeading = TRUE ;
            }

         if (YearCharCount == 2)
            {
            bThirdLeading = TRUE ;
            }

         break ;

      case DAY_FIRST:
         // DD-MM-YYYY
         iDayCount = SameCharCount (pShortDate) ;
         pShortDate += iDayCount ;
         DateSeparatorCount = SameCharCount (pShortDate) ;

         // get the separator string
         for (iIndex = 0; iIndex < DateSeparatorCount; iIndex ++)
            {
            szDateSeparator [iIndex] = *pShortDate++ ;
            }
         szDateSeparator [iIndex] = TEXT('\0') ;

         iMonthCount = SameCharCount (pShortDate) ;
         pShortDate += iMonthCount + DateSeparatorCount ;
         YearCharCount = SameCharCount (pShortDate) ;

         if (iDayCount == 2)
            {
            bFirstLeading = TRUE ;
            }

         if (iMonthCount == 2)
            {
            bSecondLeading = TRUE ;
            }

         if (YearCharCount == 2)
            {
            bThirdLeading = TRUE ;
            }

         break ;
      }

   // now generate the date format
   lstrcpy (szDateFormat, bFirstLeading ? LeadingZeroStr : NoLeadingZeroStr) ;
   lstrcat (szDateFormat, szDateSeparator) ;
   lstrcat (szDateFormat, bSecondLeading ? LeadingZeroStr : NoLeadingZeroStr) ;
   lstrcat (szDateFormat, szDateSeparator) ;
   lstrcat (szDateFormat, bThirdLeading ? LeadingZeroStr : NoLeadingZeroStr) ;

   return TRUE ;
}

BOOL CreateTimeFormat (LPTSTR pTimeSeparator, int iLeadingZero)
{
   // create the format to be used in SystemTimeTimeString
   if (iLeadingZero)
      {
      lstrcpy (szTimeFormat, LeadingZeroStr) ;
      }
   else
      {
      lstrcpy (szTimeFormat, NoLeadingZeroStr) ;
      }

   lstrcat (szTimeFormat, pTimeSeparator) ;
   lstrcat (szTimeFormat, LeadingZeroStr) ;
   lstrcat (szTimeFormat, pTimeSeparator) ;
//   lstrcat (szTimeFormat, LeadingZeroStr) ;

   // Duplicate the format without the msec field (for export use)
   lstrcpy (szTimeFormat1, szTimeFormat) ;

   // for the msec
   lstrcat (szTimeFormat, TEXT("%02.1f")) ;

   // for sec without msec
   lstrcat (szTimeFormat1, TEXT("%02d")) ;

   if (iTime == 0)
      {
      lstrcat (szTimeFormat, TEXT(" %s ")) ;
      lstrcat (szTimeFormat1, TEXT(" %s ")) ;
      }

   return TRUE ;
}  // CreateTimeFormats

BOOL GetInternational()
{
   TCHAR szShortDate[40] ;
   TCHAR szTime[40] ;   // time separator
   DWORD RetCode ;
   int   iTLZero = 0 ;      // = 0 for no leading zero, <> 0 for leading zero
   CHAR  aLanguageStr [2] ;
   LPSTR pRetStr ;
   LPTSTR lpStr ;

   // read the data from the win.ini (which i smapped to registry)
   RetCode = GetProfileString(szInternational,
      TEXT("sShortDate"), szShortDate, szShortDate, sizeof(szShortDate)/sizeof(TCHAR));

   if (RetCode)
      {
      RetCode = GetProfileString(szInternational,
         TEXT("sTime"), szTime, szTime, sizeof(szTime)/sizeof(TCHAR));
      }

   if (RetCode)
      {
      iTime   = GetProfileInt(szInternational, TEXT("iTime"), iTime);
      iTLZero = GetProfileInt(szInternational, TEXT("iTLZero"), iTLZero);

      GetProfileString(szInternational, TEXT("sDecimal"), szDecimal, szDecimal, sizeof(szDecimal)/sizeof(TCHAR));

      if (iTime == 0)
         {
         // get the AM PM strings for 12-hour format.
         // These two strings could be NULL.
         sz1159[0] = sz2359[0] = TEXT('\0') ;
         GetProfileString(szInternational,
            TEXT("s1159"), sz1159, sz1159, sizeof(sz1159)/sizeof(TCHAR));

         GetProfileString(szInternational,
            TEXT("s2359"), sz2359, sz2359, sizeof(sz2359)/sizeof(TCHAR));
         }
      }

   // create the two formats
   if (RetCode)
      {
      RetCode = (DWORD) CreateDateFormat (szShortDate) ;
      }

   if (RetCode)
      {
      RetCode = (DWORD) CreateTimeFormat (szTime, iTLZero) ;
      }

   // use the system default language numeric
   aLanguageStr[0] = '\0' ;
   pRetStr = setlocale(LC_NUMERIC, aLanguageStr);

   // get current decimal point used by C-runtime
   TSPRINTF (szShortDate, TEXT("%f"), (FLOAT)1.0) ;
   lpStr = szShortDate ;

   szCurrentDecimal [0] = TEXT('\0') ;

   while (*lpStr != TEXT('\0'))
      {
      if (*lpStr == TEXT('1'))
         {
         lpStr++ ;
         szCurrentDecimal [0] = *lpStr ;
         break ;
         }
      lpStr++ ;
      }

   if (szCurrentDecimal[0] == TEXT('\0'))
      {
      szCurrentDecimal [0] = TEXT('.') ;
      }

   return (RetCode != 0) ;
}  // GetInternational

// this routine is called to get the date/time formats either
// for the resource or from the registry.
void GetDateTimeFormats ()
{
   PALERT        pAlert ;
   PLOG          pLog ;
   if (!GetInternational())
      {
      // GetInternational failed, then get default formats from resource
      iTime = 0 ;
      DateStyle = MONTH_FIRST ;
      YearCharCount = 4 ;
      StringLoad (IDS_S1159, sz1159) ;
      StringLoad (IDS_S2359, sz2359) ;
      StringLoad (IDS_TIME_FORMAT, szTimeFormat) ;
      StringLoad (IDS_SHORT_DATE_FORMAT, szDateFormat) ;
      }
   WindowInvalidate (PerfmonViewWindow()) ;

   // reset all the field taht may be affected by the
   // language numberic changes

   pAlert = AlertData (hWndMain) ;
   pLog = LogData (hWndMain) ;

   if (pAlert)
      {
      DialogSetInterval (hWndAlert, IDD_ALERTINTERVAL, pAlert->iIntervalMSecs) ;
      }

   if (pLog)
      {
      DialogSetInterval (hWndLog, IDD_LOGINTERVAL, pLog->iIntervalMSecs) ;
      }
}  // GetDateTimeFormats

void ConvertDecimalPoint (LPTSTR lpFloatPointStr)
{
   if (szCurrentDecimal[0] == szDecimal[0])
      {
      // no need to convert anything
      return ;
      }

   while (*lpFloatPointStr)
      {
      if (*lpFloatPointStr == szCurrentDecimal[0])
         {
         *lpFloatPointStr = szDecimal[0] ;
         break ;
         }
      ++lpFloatPointStr ;
      }
}  // ConvertDecimalPoint

void ReconvertDecimalPoint (LPTSTR lpFloatPointStr)
{
   if (szCurrentDecimal[0] == szDecimal[0])
      {
      // no need to convert anything
      return ;
      }

   while (*lpFloatPointStr)
      {
      if (*lpFloatPointStr == szDecimal[0])
         {
         *lpFloatPointStr = szCurrentDecimal[0] ;
         break ;
         }
      ++lpFloatPointStr ;
      }
}  // ReconvertDecimalPoint


PERFMOPS.H

HWND PerfmonViewWindow (void) ;

BOOL ChooseComputer (HWND hWndParent, LPTSTR lpszComputer) ;

BOOL CurrentComputerName (LPTSTR lpszComputer) ;

void GetDateTimeFormats (void) ;

void SystemTimeDateString (SYSTEMTIME *pSystemTime,
                           LPTSTR lpszDate) ;

void SystemTimeTimeString (SYSTEMTIME *pSystemTime,
                           LPTSTR lpszTime,
                           BOOL bOutputMsec) ;

VOID dlg_error_box (HANDLE hDlg, UINT id) ;

void ShowPerfmonMenu (BOOL bMenu) ;

void SmallFileSizeString (int iFileSize,
                          LPTSTR lpszFileText) ;

BOOL DoWindowDrag (HWND hWnd, LPARAM lParam) ;

int SystemTimeDifference (SYSTEMTIME *pst1, SYSTEMTIME *pst2) ;

BOOL OpenFileHandler(HWND hWnd, LPTSTR lpszFilePath) ;

BOOL InsertLine (PLINE pLine)  ;

BOOL OpenWorkspace (HANDLE hFile, DWORD dwMajorVersion, DWORD dwMinorVersion) ;

BOOL SaveWorkspace (void) ;

INT ExportFileOpen (HWND hWnd, HANDLE *phFile, int IntervalSecs, LPTSTR *ppFileName) ;

void SetPerfmonOptions (OPTIONS *pOptions) ;

void ChangeSaveFileName (LPTSTR szFileName, int iPMView) ;

BOOL AppendObjectToValueList ( DWORD   dwObjectId, LPTSTR   pwszValueList );

BOOL RemoveObjectsFromSystem (PPERFSYSTEM pSystem);

BOOL BuildValueListForSystems (PPERFSYSTEM, PLINE);

BOOL SetSystemValueNameToGlobal (PPERFSYSTEM);

void CreatePerfmonSystemObjects () ;

void DeletePerfmonSystemObjects () ;

void ConvertDecimalPoint (LPTSTR lpFloatPointStr) ;

void ReconvertDecimalPoint (LPTSTR lpFloatPointStr) ;

void ShowPerfmonWindowText (void) ;


PERFMRC.H

//=============================//
// ID Macro Definition         //
//=============================//

#ifdef RC_INVOKED
   #define ID(id) id
#else
   #define ID(id) MAKEINTRESOURCE (id)
#endif

//=============================//
// Resource IDs                //
//=============================//

#define idMenuChart                 ID (2001)
#define idMenuAlert                 ID (2002)
#define idMenuLog                   ID (2003)
#define idMenuReport                ID (2004)

#define idAccelerators              ID (4001)

#define idDlgLogDisplay             ID (4002)
#define idDlgLogOptions             ID (4003)
#define idDlgAddLog                 ID (4004)
#define idDlgAbort                  ID (4005)
#define idDlgTimeframe              ID (4006)
#define idDlgAlertDisplay           ID (4007)
#define idDlgAddLine                ID (4008)
#define idDlgChartOptions           ID (4009)
#define idDlgAlertOptions           ID (4010)
#define idDlgAddBookmark            ID (4011)
#define idDlgReportOptions          ID (4012)
#define idDlgDisplayOptions         ID (4013)
#define idDlgDataSource             ID (4014)
#define idDlgAbout                  ID (4015)
#define idDlgExportOptions          ID (4016)
#define idDlgChooseComputer         ID (4017)

#define idIcon                      ID (5001)

#define idBitmapToolbar             101
#define idBitmapAlertStatus         ID (6002)
#define idBitmapLogStatus           ID (6003)


PLAYBACK.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"
#include "playback.h"   // external declarations for this module

#include "bookmark.h"   // for BookmarkAppend
#include "grafdata.h"   // for ResetGraph
#include "perfdata.h"   // for UpdateLinesForSystem
#include "perfmops.h"   // for SystemTimeDateString
#include "log.h"
#include "pmemory.h"    // for MemoryAllocate
#include "fileutil.h"
#include "utils.h"
#include "alert.h"      // for ResetAlert
#include "report.h"     // for ResetReport

NTSTATUS  AddNamesToArray (LPTSTR pNames,
   DWORD    dwLastID,
   LPWSTR   *lpCounterId) ;

void  PlaybackAddCounterName (PLOGINDEX pIndex) ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define PointerSeek(pBase, lFileOffset)         \
   ((PVOID) ((PBYTE) pBase + lFileOffset))

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

PVOID PlaybackSeek (long lFileOffset)
   {  // PlaybackSeek
   return (PointerSeek (PlaybackLog.pHeader, lFileOffset)) ;
   }  // PlaybackSeek

PLOGINDEXBLOCK FirstIndexBlock (PLOGHEADER pLogHeader)
   {
   return ((PLOGINDEXBLOCK) PointerSeek (pLogHeader, pLogHeader->iLength)) ;
   }

PLOGINDEX IndexFromPosition (PLOGPOSITION pLogPosition)
   {
   return (&pLogPosition->pIndexBlock->aIndexes [pLogPosition->iIndex]) ;
   }

PPERFDATA DataFromIndex (PLOGINDEX pLogIndex,
                         LPTSTR lpszSystemName)
   {
   PPERFDATA pPerfData;
   TCHAR     szLoggedComputerName[MAX_COMPUTERNAME_LENGTH + 3] ;
   int       iNumSystem ;

   // Note: NULL lpszSystemName means return first logged system name
   //       at the specified index.

   pPerfData = PlaybackSeek (pLogIndex->lDataOffset) ;

   for (iNumSystem = 0;
        iNumSystem < pLogIndex->iSystemsLogged;
        iNumSystem++)
      {
      if ( pPerfData &&
           pPerfData->Signature[0] == (WCHAR)'P' &&
           pPerfData->Signature[1] == (WCHAR)'E' &&
           pPerfData->Signature[2] == (WCHAR)'R' &&
           pPerfData->Signature[3] == (WCHAR)'F' )
          {
          GetPerfComputerName(pPerfData, szLoggedComputerName) ;
          if (!lpszSystemName || strsamei(lpszSystemName, szLoggedComputerName))
              {
              return pPerfData ;
              }
          pPerfData = (PPERFDATA)((PBYTE) pPerfData +
                                  pPerfData->TotalByteLength) ;
          }
      else
          {
          break ;
          }
       }
   return NULL ;
   }

PPERFDATA DataFromIndexPosition (PLOGPOSITION pLogPosition,
                                 LPTSTR lpszSystemName)
   {
   PLOGINDEX      pLogIndex ;
//   long           lDataFileOffset ;

   pLogIndex = IndexFromPosition (pLogPosition) ;
   return (DataFromIndex (pLogIndex, lpszSystemName)) ;
   }

BOOL NextLogPosition (IN OUT PLOGPOSITION pLogPosition)
   {
   PLOGINDEXBLOCK pIndexBlock ;

   if (pLogPosition->pIndexBlock->iNumIndexes == 0)
      {
      // no data in this index block.  This is most likely
      // a corrupted log file caused by system failure...
      return (FALSE) ;
      }

   if (pLogPosition->iIndex == pLogPosition->pIndexBlock->iNumIndexes - 1)
      {
      if (pLogPosition->pIndexBlock->lNextBlockOffset)
         {
         pIndexBlock =
            PlaybackSeek (pLogPosition->pIndexBlock->lNextBlockOffset) ;

         if (pIndexBlock->iNumIndexes == 0)
            {
            // no data in the next index block.  This is most likely
            // a corrupted log file caused by system failure...
            return (FALSE) ;
            }
         else
            {
            pLogPosition->pIndexBlock = pIndexBlock ;
            pLogPosition->iIndex = 0 ;
            return (TRUE) ;
            }
         }
      else
         return (FALSE) ;
      }
   else
      {
      pLogPosition->iIndex++ ;
      return (TRUE) ;
      }
   }

BOOL NextIndexPosition (IN OUT PLOGPOSITION pLogPosition,
                        BOOL bCheckForNonDataIndexes)
/*
   Effect:        Set pLogPosition to the next log position from
                  the current position of pLogPosition if there is one.

   Returns:       Whether there was a next log position.
*/
   {  // NextIndexPosition
   LOGPOSITION    LP ;
   PLOGINDEX      pIndex ;
   PBOOKMARK      pBookmarkDisk, pBookmark ;
//   LONG           lFilePosition ;

   pIndex = IndexFromPosition (pLogPosition) ;

   LP = *pLogPosition ;
   pBookmark = NULL ;

   while (TRUE)
      {  // while
      if (!NextLogPosition (&LP))
         return (FALSE) ;
      pIndex = IndexFromPosition (&LP) ;

      if (pIndex && bCheckForNonDataIndexes && IsCounterNameIndex (pIndex))
         {
         PlaybackAddCounterName (pIndex) ;
         }

      if (pIndex && bCheckForNonDataIndexes && IsBookmarkIndex (pIndex))
         {
         if (pBookmark)
            {
            // this is the case when several bookmarks are
            // found before any data index...
            pBookmark->iTic = PlaybackLog.iTotalTics ;
            BookmarkAppend (&PlaybackLog.pBookmarkFirst, pBookmark) ;
            }

         pBookmarkDisk = PlaybackSeek (pIndex->lDataOffset) ;
         pBookmark = MemoryAllocate (sizeof (BOOKMARK)) ;
         *pBookmark = *pBookmarkDisk;
         pBookmark->pBookmarkNext = NULL ;
         }

      if (pIndex && IsDataIndex (pIndex))
         {
         LP.iPosition++ ;
         *pLogPosition = LP ;
         if (pBookmark)
            {
            pBookmark->iTic = PlaybackLog.iTotalTics ;
            BookmarkAppend (&PlaybackLog.pBookmarkFirst, pBookmark) ;
            }
         return (TRUE) ;
         }
      }  // while
   }  // NextIndexPosition

BOOL NextReLogIndexPosition (IN OUT PLOGPOSITION pLogPosition)
/*
   Effect:        Set pLogPosition to the next log position from
                  the current position of pLogPosition if there is one.
                  Will return bookmarks, counternames, or data.

   Returns:       Whether there was a next relog position.
*/
   {  // NextReLogIndexPosition
   LOGPOSITION    LP ;
   PLOGINDEX      pIndex ;
//   LONG           lFilePosition ;

   pIndex = IndexFromPosition (pLogPosition) ;

   LP = *pLogPosition ;

   if (!NextLogPosition (&LP))
      return (FALSE) ;
   pIndex = IndexFromPosition (&LP) ;

   if (pIndex && IsDataIndex (pIndex))
      {
      LP.iPosition++ ;
      }
   *pLogPosition = LP ;
   return (TRUE) ;
   }  // NextReLogIndexPosition

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

void PlaybackInitializeInstance (void)
   {  // PlaybackInitializeInstance
   PlaybackLog.iStatus = iPMStatusClosed ;
   PlaybackLog.hFile = NULL ;

   PlaybackLog.szFilePath  = MemoryAllocate (FilePathLen * sizeof (TCHAR)) ;
   PlaybackLog.szFileTitle = MemoryAllocate (FilePathLen * sizeof (TCHAR)) ;
   lstrcpy (PlaybackLog.szFilePath,  szDefaultLogFileName) ;
   lstrcpy (PlaybackLog.szFileTitle, szDefaultLogFileName) ;
   }  // PlaybackInitializeInstance

INT OpenPlayback (LPTSTR lpszFilePath, LPTSTR lpszFileTitle)
   {  // OpenPlayback
   BOOL     bFirstTime = TRUE ;

   lstrcpy  (PlaybackLog.szFilePath, lpszFilePath) ;
   lstrcpy  (PlaybackLog.szFileTitle, lpszFileTitle) ;
   PlaybackLog.hFile = FileHandleReadOnly (lpszFilePath) ;
   if (!PlaybackLog.hFile || PlaybackLog.hFile == INVALID_HANDLE_VALUE)
      {
      return (ERR_CANT_OPEN) ;
      }

   PlaybackLog.pHeader = (PLOGHEADER) FileMap (PlaybackLog.hFile,
      &PlaybackLog.hMapHandle) ;

   if (!PlaybackLog.pHeader)
      {
      if (PlaybackLog.hMapHandle)
         {
         CloseHandle (PlaybackLog.hMapHandle) ;
         }

      CloseHandle (PlaybackLog.hFile) ;
      return (ERR_CANT_OPEN) ;
      }

   if (!strsame (PlaybackLog.pHeader->szSignature, LogFileSignature))
      {
      FileUnMap((LPVOID)PlaybackLog.pHeader, PlaybackLog.hMapHandle) ;
      CloseHandle (PlaybackLog.hFile) ;
      return (ERR_BAD_LOG_FILE) ;
      }

   PlaybackLog.BeginIndexPos.pIndexBlock = FirstIndexBlock (PlaybackLog.pHeader) ;
   PlaybackLog.BeginIndexPos.iIndex = 0 ;
   PlaybackLog.BeginIndexPos.iPosition = 0 ;
   PlaybackLog.pBookmarkFirst = NULL ;

   PlaybackLog.iTotalTics = 1 ;
   PlaybackLog.EndIndexPos = PlaybackLog.BeginIndexPos ;
   while (NextIndexPosition (&PlaybackLog.EndIndexPos, TRUE))
      {
      if (bFirstTime)
         {
         // set the begin index to the first data index
         bFirstTime = FALSE ;
         PlaybackLog.BeginIndexPos.iIndex =
            PlaybackLog.EndIndexPos.iIndex ;
         }
      else
         {
         PlaybackLog.iTotalTics++ ;
         }
      }

   if (PlaybackLog.iTotalTics == 1 )
      {
      // no data inside the log file.  It must be a corrupted
      // log file
      FileUnMap((LPVOID)PlaybackLog.pHeader, PlaybackLog.hMapHandle) ;
      CloseHandle (PlaybackLog.hFile) ;
      return (ERR_CORRUPT_LOG) ;
      }

//   PlaybackLog.StartIndexPos = PlaybackLog.BeginIndexPos ;

   // getthe first data index
   if (!LogPositionN (1, &(PlaybackLog.StartIndexPos)))
      {
      PlaybackLog.StartIndexPos = PlaybackLog.BeginIndexPos ;
      }

   PlaybackLog.StopIndexPos = PlaybackLog.EndIndexPos ;
   PlaybackLog.StopIndexPos.iPosition =
      min (PlaybackLog.StopIndexPos.iPosition,
           PlaybackLog.iTotalTics - 1 ) ;

   PlaybackLog.iSelectedTics = PlaybackLog.iTotalTics ;

   PlaybackLog.iStatus = iPMStatusPlaying ;
   return (0) ;
   }  // OpenPlayback

void CloseInputLog (HWND hWndParent)
   {  // CloseInputLog
   PBOOKMARK      pBookmark, pNextBookmark ;
   BOOL           retCode, retCode1 ;
   PLOGCOUNTERNAME pLogCounterName, pNextCounterName ;

   UNREFERENCED_PARAMETER (hWndParent) ;

   // free the bookmark list
   for (pBookmark = PlaybackLog.pBookmarkFirst ;
      pBookmark ;
      pBookmark = pNextBookmark )
      {
      // save next bookmark and free current bookmark
      pNextBookmark = pBookmark->pBookmarkNext ;
      MemoryFree (pBookmark) ;
      }
   PlaybackLog.pBookmarkFirst = NULL ;

   // free all counter names stuff
   if (PlaybackLog.pBaseCounterNames)
      {
      MemoryFree (PlaybackLog.pBaseCounterNames) ;
      }
   PlaybackLog.pBaseCounterNames = NULL ;
   PlaybackLog.lBaseCounterNameSize = 0 ;
   PlaybackLog.lBaseCounterNameOffset = 0 ;

   for (pLogCounterName = PlaybackLog.pLogCounterNameFirst ;
      pLogCounterName ;
      pLogCounterName = pNextCounterName)
      {
      pNextCounterName = pLogCounterName->pCounterNameNext ;
      MemoryFree (pLogCounterName->pRemainNames) ;
      MemoryFree (pLogCounterName) ;
      }

   PlaybackLog.pLogCounterNameFirst = NULL ;

   retCode1 = FileUnMap((LPVOID)PlaybackLog.pHeader, PlaybackLog.hMapHandle) ;
   retCode = CloseHandle (PlaybackLog.hFile) ;
   PlaybackLog.iStatus = iPMStatusClosed ;

   ResetGraphView (hWndGraph) ;
   ResetAlertView (hWndAlert) ;
   ResetLogView (hWndLog) ;
   ResetReportView (hWndReport) ;
   }

BOOL LogPositionN (int iIndex, PLOGPOSITION pLP)
   {  // LogPositionN
   LOGPOSITION    LP ;
   int            i ;

   LP = PlaybackLog.BeginIndexPos ;
   for (i = 0 ;
        i < iIndex ;
        i++)
      {
      if (!NextIndexPosition (&LP, FALSE))
         return (FALSE) ;
      }

   *pLP = LP ;
   return (TRUE) ;
   }  // LogPositionN

PLOGINDEX PlaybackIndexN (int iIndex)
   {
   LOGPOSITION    LP ;
   int            i ;

   LP = PlaybackLog.BeginIndexPos ;
   for (i = 0 ;
        i < iIndex ;
        i++)
      {
      if (!NextIndexPosition (&LP, FALSE))
         return (NULL) ;
      }

   return (IndexFromPosition (&LP)) ;
   }

BOOL PlaybackLines (PPERFSYSTEM pSystemFirst,
                    PLINE pLineFirst,
                    int iLogTic)
   {  // PlaybackLines

   PLOGINDEX      pLogIndex ;
   PPERFDATA      pPerfData ;
   PPERFSYSTEM       pSystem ;
   BOOL           bAnyFound ;

   pLogIndex = PlaybackIndexN (iLogTic) ;
   if (!pLogIndex)
      return (FALSE) ;

   bAnyFound = FALSE ;
   for (pSystem = pSystemFirst ;
        pSystem ;
        pSystem = pSystem->pSystemNext)
      {  // for
      pPerfData = DataFromIndex (pLogIndex, pSystem->sysName) ;
      if (pPerfData)
         {
         UpdateLinesForSystem (pSystem->sysName,
                               pPerfData,
                               pLineFirst,
                               NULL) ;
         bAnyFound = TRUE ;
         }
      else
         {
         FailedLinesForSystem (pSystem->sysName,
                               pPerfData,
                               pLineFirst) ;
         }
      }
   return (bAnyFound) ;
   }  // PlaybackLines

PPERFDATA LogDataFromPosition (PPERFSYSTEM pSystem,
                               PLOGPOSITION pLogPosition)
   {  // LogDataFromPosition
   PLOGINDEX      pLogIndex ;

   if (!pLogPosition)
      return (NULL) ;

   pLogIndex = IndexFromPosition (pLogPosition) ;
   if (!pLogIndex)
      return (NULL) ;

   return (DataFromIndex (pLogIndex, pSystem->sysName)) ;
   }  // LogDataFromPosition

BOOL LogPositionSystemTime (PLOGPOSITION pLP, SYSTEMTIME *pSystemTime)
/*
   Effect:        Given a logposition, get the index entry for that position
                  and return the system time stored therein.
*/
   {  // LogPositionSystemTime
   PLOGINDEX      pLogIndex ;

   pLogIndex = IndexFromPosition (pLP) ;
   if (!pLogIndex)
      return (FALSE) ;

   *pSystemTime = pLogIndex->SystemTime ;
   }  // LogPositionSystemTime

int LogPositionIntervalSeconds (PLOGPOSITION pLPStart,
                                PLOGPOSITION pLPStop)
/*
   Effect:        Return the time difference (in seconds) between the
                  system times of the two specified log positions.
*/
   {  // LogPositionIntervalSeconds
   SYSTEMTIME     SystemTimeStart ;
   SYSTEMTIME     SystemTimeStop ;

   if (LogPositionSystemTime (pLPStart, &SystemTimeStart) &&
       LogPositionSystemTime (pLPStop, &SystemTimeStop))
      return (SystemTimeDifference (&SystemTimeStart, &SystemTimeStop)) ;
   else
      return (0) ;
   }  // LogPositionIntervalSeconds

int PlaybackSelectedSeconds (void)
   {  // PlaybackSelectedSeconds
   return (LogPositionIntervalSeconds (&PlaybackLog.StartIndexPos,
                                       &PlaybackLog.StopIndexPos)) ;
   }  // PlaybackSelectedSeconds

void BuildLogComputerList (HWND hDlg, int DlgID)
   {
   PPERFDATA pPerfData;
   int       iNumSystem ;
   HWND      hListBox = GetDlgItem (hDlg, DlgID) ;
   PLOGINDEX pLogIndex ;
   TCHAR     szLoggedComputerName[MAX_COMPUTERNAME_LENGTH + 3] ;
   int       Index ;

   pLogIndex = IndexFromPosition (&(PlaybackLog.StartIndexPos)) ;
   pPerfData = PlaybackSeek (pLogIndex->lDataOffset) ;

   for (iNumSystem = 0;
        iNumSystem < pLogIndex->iSystemsLogged;
        iNumSystem++)
      {
      if ( pPerfData &&
           pPerfData->Signature[0] == (WCHAR)'P' &&
           pPerfData->Signature[1] == (WCHAR)'E' &&
           pPerfData->Signature[2] == (WCHAR)'R' &&
           pPerfData->Signature[3] == (WCHAR)'F' )
          {
          GetPerfComputerName(pPerfData, szLoggedComputerName) ;
          Index = LBFind (hListBox, szLoggedComputerName) ;
          if (Index != LB_ERR)
             {
             // computer name already exist, we must have reach the next
             // block of perfdata
             break ;
             }
          LBAdd (hListBox, szLoggedComputerName) ;
          pPerfData = (PPERFDATA)((PBYTE) pPerfData +
                                  pPerfData->TotalByteLength) ;
          }
       else
          {
          break;
          }
       }
   }  // BuildLogComputerList

void  PlaybackAddCounterName (PLOGINDEX pIndex)
{
   PLOGCOUNTERNAME      pLogCounterName, pListCounterName ;
   PLOGFILECOUNTERNAME  pDiskCounterName ;
   PVOID                pCounterData ;
   BOOL                 bExist = FALSE ;

   pDiskCounterName = PlaybackSeek (pIndex->lDataOffset) ;

   // check we have a record for this system
   for (pListCounterName = PlaybackLog.pLogCounterNameFirst ;
        pListCounterName ;
        pListCounterName = pListCounterName->pCounterNameNext)
      {
      if (strsamei(pDiskCounterName->szComputer,
         pListCounterName->CounterName.szComputer))
         {
         // found!
         pLogCounterName = pListCounterName ;
         bExist = TRUE ;
         break ;
         }
      }

   if (!bExist)
      {
      // new counter name record
      if (!(pLogCounterName = MemoryAllocate (sizeof(LOGCOUNTERNAME))))
         {
         return ;
         }
      }
   else
      {
      // free old memory in previous counter name record.
      if (pLogCounterName->pRemainNames)
         {
         MemoryFree (pLogCounterName->pRemainNames) ;
         }
      pLogCounterName->pRemainNames = NULL ;
      }

   pLogCounterName->CounterName = *pDiskCounterName ;

   if (pDiskCounterName->lBaseCounterNameOffset == 0)
      {
      // this is the base counter names,
      // get the master copy of the counter names

      if (!(pCounterData =
            MemoryAllocate (pDiskCounterName->lUnmatchCounterNames)))
         {
         MemoryFree (pLogCounterName) ;
         return ;
         }

      // free the old one if it exists.
      if (PlaybackLog.pBaseCounterNames)
         {
         MemoryFree (PlaybackLog.pBaseCounterNames) ;
         }

      PlaybackLog.pBaseCounterNames = pCounterData ;

      pCounterData =
         PlaybackSeek (pDiskCounterName->lCurrentCounterNameOffset) ;

      memcpy (PlaybackLog.pBaseCounterNames,
         pCounterData,
         pDiskCounterName->lUnmatchCounterNames) ;

      PlaybackLog.lBaseCounterNameSize =
         pDiskCounterName->lUnmatchCounterNames ;

      PlaybackLog.lBaseCounterNameOffset =
         pDiskCounterName->lBaseCounterNameOffset ;
      }
   else if (pDiskCounterName->lUnmatchCounterNames)
      {
      // this is not a based system and it has extra counter names
      // allocate a buffer to hold them
      pLogCounterName->pRemainNames =
         MemoryAllocate (pDiskCounterName->lUnmatchCounterNames) ;

      if (pLogCounterName->pRemainNames)
         {
         pCounterData =
            PlaybackSeek (pDiskCounterName->lCurrentCounterNameOffset) ;

         memcpy(pLogCounterName->pRemainNames,
            pCounterData,
            pDiskCounterName->lUnmatchCounterNames) ;
         }
      }

   if (!bExist)
      {
      // now add the new counter name record to the linked list
      if (!PlaybackLog.pLogCounterNameFirst)
         {
         PlaybackLog.pLogCounterNameFirst = pLogCounterName ;
         }
      else
         {
         for (pListCounterName = PlaybackLog.pLogCounterNameFirst ;
            pListCounterName->pCounterNameNext ;
            pListCounterName = pListCounterName->pCounterNameNext)
            {
            // do nothing until we get to the end of the list
            ;
            }
         pListCounterName->pCounterNameNext = pLogCounterName ;
         }
      }

}  // PlaybackAddCounterName

LPWSTR *LogBuildNameTable (PPERFSYSTEM pSysInfo)
{

   DWORD             dwArraySize ;
   PLOGCOUNTERNAME   pCounterName ;
   LPWSTR            *lpCounterId = NULL ;
   LPWSTR            lpCounterNames ;
   NTSTATUS          Status ;

   for (pCounterName = PlaybackLog.pLogCounterNameFirst ;
        pCounterName ;
        pCounterName = pCounterName->pCounterNameNext)
      {
      if (strsamei (pSysInfo->sysName, pCounterName->CounterName.szComputer))
         {
         // found the right system
         break ;
         }
      }
   if (!pCounterName)
      {
      goto ERROR_EXIT ;
      }

   dwArraySize = (pCounterName->CounterName.dwLastCounterId + 1)
      * sizeof (LPWSTR) ;

   lpCounterId = MemoryAllocate (dwArraySize +
      pCounterName->CounterName.lMatchLength +
      pCounterName->CounterName.lUnmatchCounterNames ) ;

   if (!lpCounterId)
      {
      goto ERROR_EXIT ;
      }

   // initialize pointers into buffer

   lpCounterNames = (LPWSTR)((LPBYTE)lpCounterId + dwArraySize);
   if (pCounterName->CounterName.lBaseCounterNameOffset == 0)
      {
      // this is the base system
      memcpy(lpCounterNames,
         PlaybackLog.pBaseCounterNames,
         PlaybackLog.lBaseCounterNameSize) ;
      }
   else
      {
      // copy the matched portion from the base system
      memcpy(lpCounterNames,
         PlaybackLog.pBaseCounterNames,
         pCounterName->CounterName.lMatchLength) ;

      // copy the unmatched portion
      if (pCounterName->CounterName.lUnmatchCounterNames)
         {
         memcpy(((PBYTE)lpCounterNames +
            pCounterName->CounterName.lMatchLength),
            pCounterName->pRemainNames,
            pCounterName->CounterName.lUnmatchCounterNames) ;
         }
      }

   Status = AddNamesToArray (lpCounterNames,
      pCounterName->CounterName.dwLastCounterId,
      lpCounterId) ;

   if (Status != ERROR_SUCCESS)
      {
      goto ERROR_EXIT ;
      }

   pSysInfo->CounterInfo.dwLastId =
      pCounterName->CounterName.dwLastCounterId ;
   pSysInfo->CounterInfo.dwLangId =
      pCounterName->CounterName.dwLangId ;
   pSysInfo->CounterInfo.dwHelpSize = 0 ;
   pSysInfo->CounterInfo.dwCounterSize =
      pCounterName->CounterName.lMatchLength +
      pCounterName->CounterName.lUnmatchCounterNames ;

   return (lpCounterId) ;

ERROR_EXIT:
   if (lpCounterId)
      {
      MemoryFree (lpCounterId) ;
      }
   return (NULL) ;
} // LogBuildNameTable


PLAYBACK.H

#define PlayingBackLog()         \
   (PlaybackLog.iStatus == iPMStatusPlaying)

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

void PlaybackInitializeInstance (void) ;

PLOGINDEXBLOCK FirstIndexBlock (PLOGHEADER pLogHeader) ;

PVOID PlaybackSeek (long lFileOffset) ;

PPERFDATA DataFromIndexPosition (PLOGPOSITION pLogPosition,
                                 LPTSTR lpszSystemName) ;

INT OpenPlayback (LPTSTR lpszFilePath, LPTSTR lpszFileTitle) ;

void CloseInputLog (HWND hWndParent) ;

BOOL OpenInputLog (HWND hWndParent) ;

PLOGINDEX PlaybackIndexN (int iIndex) ;

PLOGINDEX IndexFromPosition (PLOGPOSITION pLogPosition) ;

BOOL LogPositionN (int iIndex, PLOGPOSITION pLP) ;

BOOL NextIndexPosition (IN OUT PLOGPOSITION pLogPosition,
                        BOOL bCheckForNonDataIndexes) ;

BOOL PlaybackLines (PPERFSYSTEM pSystemFirst,
                    PLINE pLineFirst,
                    int iLogTic) ;

PPERFDATA LogDataFromPosition (PPERFSYSTEM pSystem,
                               PLOGPOSITION pLogPosition) ;

int PlaybackSelectedSeconds (void) ;

BOOL LogPositionSystemTime (PLOGPOSITION pLP, SYSTEMTIME *pSystemTime) ;

int LogPositionIntervalSeconds (PLOGPOSITION pLPStart,
                                PLOGPOSITION pLPStop) ;

BOOL NextReLogIndexPosition (IN OUT PLOGPOSITION pLogPosition) ;

void BuildLogComputerList (HWND hDlg, int DlgID) ;

LPWSTR *LogBuildNameTable(PPERFSYSTEM pSysInfo) ;


PMEMORY.H

LPMEMORY MemoryAllocate (DWORD dwSize) ;

VOID MemoryFree (LPMEMORY lpMemory) ;

DWORD MemorySize (LPMEMORY lpMemory) ;

LPMEMORY MemoryResize (LPMEMORY lpMemory,
                       DWORD dwNewSize) ;


PMHELPID.H

/*****************************************************************************
 *
 *  PMHelpID.h -  This file contains meun and dialogs ids for context-sensitive
 *                help for Performance Monitor.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *  Author: Hon-Wah Chan
 *
 *          [5-Oct-1992]
 *
 *
 ****************************************************************************/

//=============================//
// "File" Menu IDs             //
//=============================//

#define HC_PM_MENU_FILENEWCHART            101
#define HC_PM_MENU_FILEOPENCHART           102
#define HC_PM_MENU_FILESAVECHART           103
#define HC_PM_MENU_FILESAVEASCHART         104
#define HC_PM_MENU_FILEEXPORTCHART         105

#define HC_PM_MENU_FILENEWALERT            106
#define HC_PM_MENU_FILEOPENALERT           107
#define HC_PM_MENU_FILESAVEALERT           108
#define HC_PM_MENU_FILESAVEASALERT         109
#define HC_PM_MENU_FILEEXPORTALERT         110

#define HC_PM_MENU_FILENEWLOG              111
#define HC_PM_MENU_FILEOPENLOG             112
#define HC_PM_MENU_FILESAVELOG             113
#define HC_PM_MENU_FILESAVEASLOG           114
#define HC_PM_MENU_FILEEXPORTLOG           115

#define HC_PM_MENU_FILENEWREPORT           116
#define HC_PM_MENU_FILEOPENREPORT          117
#define HC_PM_MENU_FILESAVEREPORT          118
#define HC_PM_MENU_FILESAVEASREPORT        119
#define HC_PM_MENU_FILEEXPORTREPORT        120

#define HC_PM_MENU_FILESAVEWORKSPACE       121
#define HC_PM_MENU_FILEEXIT                127
#define HC_NTPM_MENU_FILEEXIT              128

//=============================//
// "Edit" Menu IDs             //
//=============================//

#define HC_PM_MENU_EDITADDCHART            201
#define HC_PM_MENU_EDITMODIFYCHART         202
#define HC_PM_MENU_EDITCLEARCHART          203
#define HC_PM_MENU_EDITDELETECHART         204

#define HC_PM_MENU_EDITADDALERT            205
#define HC_PM_MENU_EDITMODIFYALERT         206
#define HC_PM_MENU_EDITCLEARALERT          207
#define HC_PM_MENU_EDITDELETEALERT         208

#define HC_PM_MENU_EDITADDLOG              209
#define HC_PM_MENU_EDITCLEARLOG            211
#define HC_PM_MENU_EDITDELETELOG           212

#define HC_PM_MENU_EDITADDREPORT           213
#define HC_PM_MENU_EDITCLEARREPORT         215
#define HC_PM_MENU_EDITDELETEREPORT        216

#define HC_PM_MENU_EDITTIMEWINDOW          217

//=============================//
// "View" Menu IDs             //
//=============================//

#define HC_PM_MENU_VIEWCHART               301
#define HC_PM_MENU_VIEWALERT               302
#define HC_PM_MENU_VIEWLOG                 303
#define HC_PM_MENU_VIEWREPORT              304

//=============================//
// "Options" Menu IDs          //
//=============================//

#define HC_PM_MENU_OPTIONSCHART            401

#define HC_PM_MENU_OPTIONSALERT            403

#define HC_PM_MENU_OPTIONSLOG              405

#define HC_PM_MENU_OPTIONSREPORT           407

#define HC_PM_MENU_OPTIONSDISPLAYMENU      408
#define HC_PM_MENU_OPTIONSDISPLAYTOOLBAR   409
#define HC_PM_MENU_OPTIONSDISPLAYSTATUS    410
#define HC_PM_MENU_OPTIONSDISPLAYONTOP     411

#define HC_PM_MENU_OPTIONSREFRESHNOW       412

#define HC_PM_MENU_OPTIONSBOOKMARK         413

#define HC_PM_MENU_OPTIONSDATASOURCE       415

//=============================//
// "Help" Menu IDs             //
//=============================//

#define HC_PM_MENU_HELPCONTENTS            501
#define HC_PM_MENU_HELPSEARCH              502
#define HC_PM_MENU_HELPHELP                503
#define HC_PM_MENU_HELPABOUT               504
#define HC_NTPM_MENU_HELPABOUT             505

//=============================//
// "System" Menu IDs           //
//=============================//
#define HC_PM_MENU_SYSTEMMENU_RESTORE      910
#define HC_PM_MENU_SYSTEMMENU_MOVE         911
#define HC_PM_MENU_SYSTEMMENU_SIZE         912
#define HC_PM_MENU_SYSTEMMENU_MINIMIZE     913
#define HC_PM_MENU_SYSTEMMENU_MAXMIZE      914
#define HC_PM_MENU_SYSTEMMENU_CLOSE        915
#define HC_PM_MENU_SYSTEMMENU_SWITCHTO     916

//=============================//
// "File" Menu Dialog IDs      //
//=============================//
#define HC_PM_idDlgFileOpen                1000
#define HC_PM_idDlgFileSaveAs              1001
#define HC_PM_idDlgFileSaveWorkSpace       1002
#define HC_PM_idDlgFileExport              1003

//=============================//
// "EditFile" Menu Dialog IDs  //
//=============================//
#define HC_PM_idDlgEditAddToChart          1010
#define HC_PM_idDlgEditAddToAlert          1011
#define HC_PM_idDlgEditAddToLog            1012
#define HC_PM_idDlgEditAddToReport         1013
#define HC_PM_idDlgEditChartLine           1014
#define HC_PM_idDlgEditAlertEntry          1015
#define HC_PM_idDlgEditTimeFrame           1016

//=============================//
// "Option" Menu Dialog IDs    //
//=============================//
#define HC_PM_idDlgOptionChart             1020
#define HC_PM_idDlgOptionAlert             1021
#define HC_PM_idDlgOptionLog               1022
#define HC_PM_idDlgOptionReport            1023
#define HC_PM_idDlgOptionDataFrom          1024
#define HC_PM_idDlgOptionBookMark          1025
#define HC_PM_idDlgOptionOpenLogFile       1026

//==============================//
// "Computer list" Dailog ID    //
// replace NetworkComputerList  //
// when playing back log file   //
//==============================//
#define HC_PM_idDlgLogComputerList         1030
#define HC_PM_idDlgSelectNetworkComputer   1031


ADDLINE.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdio.h>
#include <math.h>

#include "perfmon.h"
#include "addline.h"

#include "alert.h"      // for AlertInsertLine, AlertDeleteLine
#include "grafdata.h"   // for ChartInsertLine, ChartDeleteLine
#include "graph.h"      // for SizeGraphComponents
#include "legend.h"     // for LegendAddItem
#include "line.h"       // for LineAllocate, LineFree.
#include "pmemory.h"    // for MemoryXXX (mallloc-type) routines
#include "perfdata.h"   // for QueryPerformanceData
#include "perfmops.h"   // for dlg_error_box
#include "playback.h"   // for PlaybackLines
#include "report.h"     // for ReportInsertLine, ReportDeleteLine
#include "system.h"     // for SystemGet
#include "utils.h"
#include "playback.h"   // for PlayingBackLog
#include "counters.h"
#include "pmhelpid.h"   // Help IDs

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#ifdef ADVANCED_PERFMON
#define ADDLINEDETAILLEVEL    PERF_DETAIL_WIZARD
#else
#define ADDLINEDETAILLEVEL    PERF_DETAIL_NOVICE
#endif

#define iInitialExplainLen    256

// defines used in owner-drawn items
#define OWNER_DRAWN_ITEM      2
#define OWNER_DRAW_FOCUS      1

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

// defined in PerfData.c
extern WCHAR   NULL_NAME[] ;

COLORREF argbColors[] =
   {
   RGB (0xff, 0x00, 0x00),
   RGB (0x00, 0x80, 0x00),
   RGB (0x00, 0x00, 0xff),
   RGB (0xff, 0xff, 0x00),
   RGB (0xff, 0x00, 0xff),
   RGB (0x00, 0xff, 0xff),
   RGB (0x80, 0x00, 0x00),
   RGB (0x40, 0x40, 0x40),
   RGB (0x00, 0x00, 0x80),
   RGB (0x80, 0x80, 0x00),
   RGB (0x80, 0x00, 0x80),
   RGB (0x00, 0x80, 0x80),
   RGB (0x40, 0x00, 0x00),
   RGB (0x00, 0x40, 0x00),
   RGB (0x00, 0x00, 0x40),
   RGB (0x00, 0x00, 0x00)
   } ;

TCHAR *apszScaleFmt[] =
   {
   TEXT("%6.6f"),
   TEXT("%5.5f"),
   TEXT("%4.4f"),
   TEXT("%3.3f"),
   TEXT("%2.2f"),
   TEXT("%1.1f"),
   TEXT("%2.1f"),
   TEXT("%3.1f"),
   TEXT("%4.1f"),
   TEXT("%5.1f"),
   TEXT("%6.1f"),
   TEXT("%7.1f")
   } ;
#define DEFAULT_SCALE 0
#define NUMBER_OF_SCALE sizeof(apszScaleFmt)/sizeof(apszScaleFmt[0])

int               iLineType ;
static PPERFDATA  pPerfData ;
PPERFSYSTEM       pSystem ;
PLINESTRUCT       pLineEdit ;
PPERFSYSTEM       *ppSystemFirst ;
PLINEVISUAL       pVisual ;

#define bEditLine (pLineEdit != NULL)

BOOL              ComputerChange ;
LPTSTR            pCurrentSystem ;

//=============================//
// Alert-related locals        //
//=============================//

BOOL           bAlertOver ;         // over or under?
FLOAT          eAlertValue ;        // value to compare
BOOL           bEveryTime ;         // run every time or once?

// program to run;
LPTSTR          pszAlertProgram ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define InChartAdd()             \
   (iLineType == LineTypeChart)

#define InAlertAdd()             \
   (iLineType == LineTypeAlert)

#define InReportAdd()            \
   (iLineType == LineTypeReport)

#define NumColorIndexes()     \
   (sizeof (argbColors) / sizeof (argbColors[0]))

#define NumWidthIndexes()  5

#define NumStyleIndexes()  4

//==========================================================================//
//                              Forward Declarations                        //
//==========================================================================//

BOOL /*static*/ OnObjectChanged (HDLG hDlg) ;

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

PPERFINSTANCEDEF ParentInstance (PPERFINSTANCEDEF pInstance)
   {  // ParentInstance
   PPERFOBJECT          parent_obj ;
   PPERFINSTANCEDEF     parent_instance ;
   PERF_COUNTER_BLOCK   *counter_blk;
   LONG                 i ;

   parent_obj =
      GetObjectDefByTitleIndex (pPerfData,
                                pInstance->ParentObjectTitleIndex) ;
   if (!parent_obj)
      return (NULL) ;

   // Then get the parent instance.
   // NOTE: can use unique ID field to match here instead
   // of name compare.
   for (i = 0,
        parent_instance = (PERF_INSTANCE_DEFINITION *) ( (PBYTE)parent_obj
        + parent_obj->DefinitionLength);
        i < parent_obj->NumInstances;
        i++, parent_instance = (PERF_INSTANCE_DEFINITION *) ( (PBYTE)counter_blk
        + counter_blk->ByteLength))
      {  // for
      counter_blk = (PERF_COUNTER_BLOCK *) ( (PBYTE)parent_instance
                   + parent_instance->ByteLength);
     if ((DWORD)i == pInstance->ParentObjectInstance)
        return (parent_instance) ;
     }

   return (NULL) ;
   }  // ParentInstance

PPERFOBJECT SelectedObject (HWND hWndObjects,
                            LPTSTR lpszObjectName)
/*
   Effect:        Return the pObject associated with the currently selected
                  combo-box item of hWndObjects. Set lpszObjectName to
                  the object's name.

                  If no item is selected in the combobox, return NULL.

   Assert:        The pObject for each CB item was added when the string
                  was added to the CB, by CBLoadObjects.

   See Also:      LoadObjects.
*/
   {  // SelectedObject
   int            iIndex ;

   iIndex = CBSelection (hWndObjects) ;
   if (iIndex == CB_ERR)
      return (NULL) ;

   if (lpszObjectName)
      CBString (hWndObjects, iIndex, lpszObjectName) ;

   return ((PPERFOBJECT) CBData (hWndObjects, iIndex)) ;
   }  // SelectedObject

PPERFCOUNTERDEF SelectedCounter (HWND hWndCounters,
                                 LPTSTR lpszCounterName)
/*
   Effect:        Return the pCounter associated with the currently selected
                  LB item of hWndCounters. Set lpszCounterName to
                  the Counter's name.

                  If no item is selected in the listbox, return NULL.

   Assert:        The pCounter for each LB item was added when the string
                  was added to the LB, by LoadCounters.

   See Also:      LoadCounters.
*/
   {  // SelectedCounter
   int            iIndex ;

   iIndex = LBSelection (hWndCounters) ;
   if (iIndex == LB_ERR)
      return (NULL) ;

   if (lpszCounterName)
      LBString (hWndCounters, iIndex, lpszCounterName) ;
   return ((PPERFCOUNTERDEF) LBData (hWndCounters, iIndex)) ;
   }  // SelectedCounter

void VisualIncrement (PLINEVISUAL pVisual)
/*
   Effect:        Cycle through the combinations of color, width, and
                  style to distinguish between lines.  The color attributes
                  are like a number:
                        <style> <width> <color>

                  Since color is the LSB, it is always incremented. The
                  others are incremented whenever the color rolls over.

                  If a current index is -1, that means don't increment
                  that visual attribute.
*/
   {  // VisualIncrement
   pVisual->iColorIndex =
      (pVisual->iColorIndex + 1) % NumColorIndexes () ;

   if (pVisual->iColorIndex)
      return ;

   if (pVisual->iWidthIndex == -1)
      return ;

   pVisual->iWidthIndex =
      (pVisual->iWidthIndex + 1) % NumWidthIndexes () ;

   if (pVisual->iWidthIndex)
       return ;

   if (pVisual->iStyleIndex == -1)
      return ;

   pVisual->iStyleIndex =
      (pVisual->iStyleIndex + 1) % NumStyleIndexes () ;
   }  // VisualIncrement

COLORREF LineColor (int iColorIndex)
   {
   return (argbColors [iColorIndex]) ;
   }

int LineWidth (int iWidthIndex)
   {
   switch (iWidthIndex)
      {  // switch
      case 0:
          return (1) ;
          break ;

      case 1:
          return (3) ;
          break ;

      case 2:
          return (5) ;
          break ;

      case 3:
          return (7) ;
          break ;

      case 4:
          return (9) ;
          break ;
      }  // switch
   }  // LineWidth

int LineStyle (int iStyleIndex)
   {
   return (iStyleIndex) ;
   }

BOOL /*static*/ LoadInstances (HDLG hDlg)
   {
   PPERFOBJECT       pObject ;
   PPERFINSTANCEDEF  pInstance, pInstanceParent ;
   TCHAR             szInstance [256], szInstanceParent [256] ;
   TCHAR             szCompositeName [256 * 2] ;
   TCHAR             szInstCompositeName [256 * 2] ;

   LONG              iInstance ;
   UINT              iIndex ;

   int               xTextExtent = 0 ;
   int               currentTextExtent ;
   HFONT             hFont ;
   HDC               hDC = 0 ;
   HWND              hWndObjects = DialogControl (hDlg, IDD_ADDLINEOBJECT);
   HWND              hWndInstances = DialogControl (hDlg, IDD_ADDLINEINSTANCE);

   // turn off horiz. scrollbar
   LBSetHorzExtent (hWndInstances, 0) ;
   LBReset (hWndInstances) ;

   pObject = SelectedObject (hWndObjects, NULL) ;
   if (!pObject)
      return (FALSE) ;

   if (pObject->NumInstances <= 0)
      {
      MLBSetSelection (hWndInstances, 0, TRUE) ;
      return (FALSE) ;
      }

   // turn off Listbox redraw
   LBSetRedraw (hWndInstances, FALSE) ;

   if (bEditLine)
      {
      if (pLineEdit->lnObject.NumInstances > 0)
         {
         if (pLineEdit->lnInstanceDef.ParentObjectTitleIndex)
            {
            // Get the Parent Object Instance Name.
            // and prefix it to the Instance Name, to make
            // the string we want to display.
            TSPRINTF (szInstCompositeName,
                     TEXT("%s ==> %s"),
                     pLineEdit->lnPINName,
                     pLineEdit->lnInstanceName) ;
            }
         else
            {
            lstrcpy (szInstCompositeName, pLineEdit->lnInstanceName) ;
            }
         }
      else
         {
         szInstCompositeName[0] = TEXT('\0');
         }
      }

   if (!bEditLine && (hDC = GetDC (hWndInstances)))
      {
      hFont = (HFONT)SendMessage(hWndInstances, WM_GETFONT, 0, 0L);
      if (hFont)
         SelectObject(hDC, hFont);
      }

   for (iInstance = 0, pInstance = FirstInstance (pObject) ;
        iInstance < pObject->NumInstances;
        iInstance++, pInstance = NextInstance (pInstance))
      {  // for
      GetInstanceNameStr (pInstance, szInstance) ;
      pInstanceParent = ParentInstance (pInstance) ;

      if (pInstanceParent)
         {
         GetInstanceNameStr (pInstanceParent, szInstanceParent) ;
         TSPRINTF (szCompositeName, TEXT("%s ==> %s"),
                   szInstanceParent, szInstance) ;
         }
     else
        lstrcpy (szCompositeName, szInstance) ;

  iIndex = LBAdd (hWndInstances, szCompositeName) ;
   if (iIndex != LB_ERR)
      {
      LBSetData (hWndInstances, iIndex, (LPARAM) pInstance) ;
      }

   // get the biggest text width
   if (hDC)
      {
      currentTextExtent = TextWidth (hDC, szCompositeName) + xScrollWidth / 2  ;
      if (currentTextExtent > xTextExtent)
         {
         xTextExtent = currentTextExtent ;
         }
      }
   }  // for

   if (hDC)
      {
      // turn on horiz. scrollbar if necessary...
      LBSetHorzExtent (hWndInstances, xTextExtent) ;
      ReleaseDC (hWndInstances, hDC) ;
      }

   if (!bEditLine || szInstCompositeName[0] == TEXT('\0'))
      {
      MLBSetSelection (hWndInstances, 0, TRUE) ;
      }
   else
      {
      BOOL bSetSelection = TRUE ;

      iIndex = LBFind (hWndInstances, szInstCompositeName) ;
      if (iIndex == LB_ERR)
         {
         if (bEditLine)
            {
            bSetSelection = FALSE ;
            }
         iIndex = 0 ;
         }

      if (bSetSelection)
         {
         MLBSetSelection (hWndInstances, iIndex, TRUE) ;
         }

      LBSetVisible (hWndInstances, iIndex) ;
      }

   // turn on Listbox redraw
   LBSetRedraw (hWndInstances, TRUE) ;

   }  // LoadInstances

BOOL OnCounterChanged (HDLG hDlg)
/*
   Effect:        Perform any actions necessary when the counter has changed.
                  In particular, display the explanation for the counter
                  that has the focus rectangle.
*/
   {
   LPTSTR         lpszText ;
   PPERFCOUNTERDEF pCounter ;
   int            iStatus ;
   int            iFocusIndex ;
   HWND           hWndCounters = DialogControl (hDlg, IDD_ADDLINECOUNTER);
   HWND           hWndScales = DialogControl (hDlg, IDD_ADDLINESCALE) ;

   iFocusIndex = LBFocus (hWndCounters) ;
   if  (iFocusIndex == LB_ERR)
      return (FALSE) ;

   if (PlayingBackLog())
      {
      DialogEnable (hDlg, IDD_ADDLINEEXPANDEXPLAIN, FALSE) ;
      return (TRUE) ;
      }

   pCounter = (PPERFCOUNTERDEF) LBData (hWndCounters, iFocusIndex) ;
   if ((!pCounter) || (pCounter == (PPERFCOUNTERDEF)LB_ERR))
      return (FALSE) ;

   // no need to get help text before the button is clicked
   if (!bExplainTextButtonHit)
      return (FALSE) ;

   // Create initial string
   lpszText = MemoryAllocate (iInitialExplainLen * sizeof (TCHAR)) ;

   if (!lpszText)
      return (FALSE) ;

   while (TRUE)
      {
      lpszText[0] = TEXT('\0') ;

      #ifdef UNICODE
      iStatus = QueryPerformanceName  (pSystem,
                                       pCounter->CounterHelpTitleIndex,
                                       iLanguage,
                                       MemorySize (lpszText) / sizeof(TCHAR),
                                       lpszText,
                                       TRUE) ;
      #else
      iStatus = QueryPerformanceNameW (pSystem,
                                       pCounter->CounterHelpTitleIndex,
                                       iLanguage,
                                       MemorySize (lpszText),
                                       lpszText,
                                       TRUE) ;
      #endif

      if (iStatus == ERROR_SUCCESS)
         break ;

      if (iStatus == ERROR_MORE_DATA)
         lpszText =
            MemoryResize (lpszText,
                          MemorySize (lpszText) + iInitialExplainLen) ;
      else
         break ;
      }  // while

   // Don't use my DialogSetString, it won't handle such large strings.
   SetDlgItemText (hDlg, IDD_ADDLINEEXPLAIN, lpszText) ;
   MemoryFree (lpszText) ;

   return (TRUE) ;
   }  // OnCounterChanged

BOOL LoadCounters (HDLG hDlg,
                   UINT iSelectCounterDefn)
   {  // LoadCounters
   PPERFOBJECT       pObject ;

   TCHAR             szCounterName [256] ;
   TCHAR             szDefaultCounterName [256] ;
   PPERFCOUNTERDEF   pCounter ;
   UINT              i ;
   int               iIndex ;
   int               xTextExtent = 0 ;
   int               currentTextExtent ;
   HFONT             hFont ;
   HDC               hDC = 0 ;
   BOOL              bSetSelection = TRUE ;
   HWND              hWndObjects = DialogControl (hDlg, IDD_ADDLINEOBJECT);
   HWND              hWndCounters = DialogControl (hDlg, IDD_ADDLINECOUNTER);

   strclr (szDefaultCounterName) ;

   // turn off horiz. scrollbar
   LBSetHorzExtent (hWndCounters, 0) ;
   LBReset (hWndCounters) ;

   pObject = SelectedObject (hWndObjects, NULL) ;
   if (!pObject)
      return (FALSE) ;

   if (!bEditLine && (hDC = GetDC (hWndCounters)))
      {
      hFont = (HFONT)SendMessage(hWndCounters, WM_GETFONT, 0, 0L);
      if (hFont)
         SelectObject(hDC, hFont);
      }

   // turn off Listbox redraw
   LBSetRedraw (hWndCounters, FALSE) ;

   for (i = 0, pCounter = FirstCounter (pObject) ;
        i < pObject->NumCounters ;
        i++, pCounter = NextCounter (pCounter))
      {  // for
      if (pCounter->CounterType != PERF_SAMPLE_BASE &&
          pCounter->CounterType != PERF_COUNTER_NODATA &&
          pCounter->CounterType != PERF_AVERAGE_BASE &&
          pCounter->CounterType != PERF_COUNTER_QUEUELEN_TYPE &&
          pCounter->CounterType != PERF_COUNTER_MULTI_BASE &&
          pCounter->CounterType != PERF_RAW_BASE &&
          pCounter->DetailLevel <= ADDLINEDETAILLEVEL)
         {   // if
         szCounterName[0] = TEXT('\0') ;
         QueryPerformanceName (pSystem,
                                 pCounter->CounterNameTitleIndex,
                                 0, sizeof (szCounterName) / sizeof(TCHAR),
                                 szCounterName,
                                 FALSE) ;

         // if szCounterName is not empty, add it to the listbox
         if (!strsame(szCounterName, NULL_NAME))
            {
            iIndex = LBAdd (hWndCounters, szCounterName) ;
            LBSetData (hWndCounters, iIndex, (DWORD) pCounter) ;

            // get the biggest text width
            if (hDC)
               {
               currentTextExtent = TextWidth (hDC, szCounterName) + xScrollWidth / 2 ;
               if (currentTextExtent > xTextExtent)
                  {
                  xTextExtent = currentTextExtent ;
                  }
               }

            if (iSelectCounterDefn == i)
               lstrcpy (szDefaultCounterName, szCounterName) ;
            } // if szCounterName is not empty
         }   // if
      }  // for

   if (bEditLine)
      lstrcpy (szDefaultCounterName, pLineEdit->lnCounterName) ;

   iIndex = LBFind (hWndCounters, szDefaultCounterName) ;
   if (iIndex == LB_ERR)
      {
      if (bEditLine)
         {
         bSetSelection = FALSE ;
         }
      iIndex = 0 ;
      }

   if (bSetSelection)
      {
      MLBSetSelection (hWndCounters, iIndex, TRUE) ;
      }
   LBSetVisible (hWndCounters, iIndex) ;

   if (hDC)
      {
      // turn on horiz. scrollbar if necessary...
      LBSetHorzExtent (hWndCounters, xTextExtent) ;
      ReleaseDC (hWndCounters, hDC) ;
      }

   // turn on Listbox redraw
   LBSetRedraw (hWndCounters, TRUE) ;

   OnCounterChanged (hDlg) ;
   }  // LoadCounters

void LoadObjects (HDLG hDlg,
                  PPERFDATA pPerfData)
/*
   Effect:        Load into the object CB the objects for the current
                  pPerfData.
*/
   {
   LPTSTR         lpszObject ;
   HWND           hWndObjects = DialogControl (hDlg, IDD_ADDLINEOBJECT);

   lpszObject = bEditLine ? pLineEdit->lnObjectName : NULL ;

   CBLoadObjects (hWndObjects,
                  pPerfData,
                  pSystem,
                  ADDLINEDETAILLEVEL,
                  lpszObject,
                  FALSE) ;
   OnObjectChanged (hDlg) ;
//   UpdateWindow (hDlg) ;
   }  // LoadObjects

void OnComputerChanged (HDLG hDlg)
   {

   PPERFSYSTEM pLocalSystem;
   PPERFDATA   pLocalPerfData;

   pLocalPerfData = pPerfData;
   pLocalSystem = GetComputer (hDlg,
                          IDD_ADDLINECOMPUTER,
                          TRUE,
                          &pLocalPerfData,
                          ppSystemFirst) ;
   if (pLocalSystem && pLocalPerfData)
      {
      pSystem = pLocalSystem;
      pPerfData = pLocalPerfData;
      LoadObjects (hDlg, pPerfData) ;
      ComputerChange = FALSE ;
      }

   }  // OnComputerChanged

BOOL AddOneChartLine (HWND hDlg,
                      PPERFCOUNTERDEF pCounter,
                      LPTSTR lpszCounter,
                      PPERFINSTANCEDEF pInstance)
   {
   TCHAR             szComputer [MAX_SYSTEM_NAME_LENGTH] ;

//   PPERFOBJECT       pObject ;
   PERF_OBJECT_TYPE       UNALIGNED *pObject ;
   TCHAR             szObject [PerfObjectLen] ;

   TCHAR             szInstance [256] ;

   PLINE             pLine ;
//   int               i ;
   int               iCounterIndex ;
   int               j ;

   PPERFINSTANCEDEF  pInstanceParent ;
   PERF_COUNTER_BLOCK *pCounterBlock ;
   TCHAR          szInstanceParent [256] ;
   TCHAR          szObjectParent [256] ;
   HWND           hWndColors = DialogControl (hDlg, IDD_ADDLINECOLOR) ;
   HWND           hWndWidths = DialogControl (hDlg, IDD_ADDLINEWIDTH) ;
   HWND           hWndStyles = DialogControl (hDlg, IDD_ADDLINESTYLE) ;
   HWND           hWndScales = DialogControl (hDlg, IDD_ADDLINESCALE) ;
   HWND           hWndObjects = DialogControl (hDlg, IDD_ADDLINEOBJECT);

   //=============================//
   // Get selected data values    //
   //=============================//

   DialogText (hDlg, IDD_ADDLINECOMPUTER, szComputer) ;

   pObject = (PERF_OBJECT_TYPE UNALIGNED *)SelectedObject (hWndObjects, szObject) ;
   if (!pObject)
      return (FALSE) ;

   if (pInstance)
      GetInstanceNameStr (pInstance, szInstance) ;

   //=============================//
   // Allocate the line           //
   //=============================//

   pLine = LineAllocate () ;
   if (!pLine)
      {
      DlgErrorBox (hDlg, ERR_NO_MEMORY);
      return (FALSE) ;
      }

   //=============================//
   // Set line's data values      //
   //=============================//

   pLine->iLineType = iLineType ;
   pLine->lnSystemName = StringAllocate (szComputer) ;

   pLine->lnObject = *pObject ;
   pLine->lnObjectName = StringAllocate (szObject) ;

   pLine->lnCounterDef = *pCounter ;
   pLine->lnCounterName = StringAllocate (lpszCounter) ;

   if (pObject->NumInstances > 0 && pInstance)
      {
      pLine->lnInstanceDef = *pInstance ;
      pLine->lnInstanceName = StringAllocate (szInstance) ;

      pLine->lnUniqueID = pInstance->UniqueID ;

      if (pInstance->ParentObjectTitleIndex)
         {
         szObjectParent[0] = (TCHAR)'\0';
         QueryPerformanceName (pSystem,
                               pInstance->ParentObjectTitleIndex,
                               0,  PerfObjectLen, szObjectParent, FALSE) ;
         pLine->lnParentObjName = StringAllocate (szObjectParent) ;
         }

      pInstanceParent = ParentInstance (pInstance) ;
      if (pInstanceParent)
         {
         GetInstanceNameStr (pInstanceParent, szInstanceParent) ;
         if (pInstance->ParentObjectTitleIndex)
            {
            pLine->lnPINName = StringAllocate (szInstanceParent) ;
            }
         }
      }  // if

   pLine->lnCounterType = pCounter->CounterType;
   pLine->lnCounterLength = pCounter->CounterSize;

   pLine->lnOldTime = pPerfData->PerfTime.HighPart << 32 +
      pPerfData->PerfTime.LowPart ;
   pLine->lnNewTime = pLine->lnOldTime ;

   for (j = 0 ; j < 2 ; j++)
      {
      pLine->lnaCounterValue[j] = (LONGLONG) 0 ;
      }

   //=============================//
   // Chart-related Values        //
   //=============================//

   pLine->iScaleIndex = CBSelection (hWndScales) ;
   if (pLine->iScaleIndex == 0)
      {
      // use the default scale
      pLine->eScale = (FLOAT) pow ((double)10.0,
          (double)pCounter->DefaultScale) ;
      }
   else
      {
      pLine->eScale = DialogFloat (hDlg, IDD_ADDLINESCALE, NULL) ;
      }

   if (pObject->NumInstances > 0 && pInstance)
      {
      pCounterBlock = (PERF_COUNTER_BLOCK *) ( (PBYTE) pInstance +
                       pInstance->ByteLength);
      }
   else
      {
      pCounterBlock = (PERF_COUNTER_BLOCK *) ( (PBYTE) pObject +
                       pObject->DefinitionLength);
      }

   if (pLine->lnCounterLength <= 4)
      {
      pLine->lnaOldCounterValue[0] =
               * ( (DWORD FAR *) ( (PBYTE)pCounterBlock +
               pCounter[0].CounterOffset));
      pLine->lnaOldCounterValue[0] &= (LONGLONG) (0x0ffffffff);
      }
   else
      {
      pLine->lnaOldCounterValue[0] =
              * ( (LONGLONG UNALIGNED *) ( (PBYTE)pCounterBlock +
              pCounter[0].CounterOffset));
      }

   // Get second counter, only if we are not at
   // the end of the counters; some computations
   // require a second counter

   iCounterIndex = CounterIndex (pCounter, (PPERFOBJECT) pObject) ;
   if ((UINT) iCounterIndex < pObject->NumCounters - 1 &&
       iCounterIndex != -1)
      {
      if (pLine->lnCounterLength <= 4)
         {
         pLine->lnaOldCounterValue[1] =
                  * ( (DWORD FAR *) ( (PBYTE)pCounterBlock +
                  pCounter[1].CounterOffset));
         pLine->lnaOldCounterValue[1] &= (LONGLONG) (0x0ffffffff);
         }
      else
         pLine->lnaOldCounterValue[1] =
                 * ( (LONGLONG UNALIGNED *) ( (PBYTE)pCounterBlock +
                 pCounter[1].CounterOffset));
      }

   pLine->lnaOldCounterValue[0] = pLine->lnaCounterValue[0];
   pLine->lnaOldCounterValue[1] = pLine->lnaCounterValue[1];

   //=============================//
   // Visual Values               //
   //=============================//

   pLine->Visual.iColorIndex = CBSelection (hWndColors) ;
   pLine->Visual.crColor = LineColor (pLine->Visual.iColorIndex) ;

   pLine->Visual.iWidthIndex = CBSelection (hWndWidths) ;
   pLine->Visual.iWidth = LineWidth (pLine->Visual.iWidthIndex) ;

   pLine->Visual.iStyleIndex = CBSelection (hWndStyles) ;
   pLine->Visual.iStyle = LineStyle (pLine->Visual.iStyleIndex) ;

   *pVisual = pLine->Visual ;
   if (!bEditLine)
      VisualIncrement (pVisual) ;

   CBSetSelection (hWndColors, pVisual->iColorIndex) ;
   CBSetSelection (hWndWidths, pVisual->iWidthIndex) ;
   CBSetSelection (hWndStyles, pVisual->iStyleIndex) ;

   if (iLineType == LineTypeChart)
      {
      pLine->hPen = LineCreatePen (NULL, &(pLine->Visual), FALSE) ;
      }

   //=============================//
   // Alert Related Values        //
   //=============================//

   if (iLineType == LineTypeAlert)
      {
      pLine->bAlertOver = bAlertOver ;
      pLine->eAlertValue = eAlertValue ;
      pLine->bEveryTime =
         IsDlgButtonChecked (hDlg, IDD_ADDLINEPROGRAMEVERYTIME) ;
      pLine->bAlerted = FALSE ;
      pLine->hBrush = CreateSolidBrush (pLine->Visual.crColor) ;
      if (!PlayingBackLog ())
         {
         pLine->lpszAlertProgram = StringAllocate (pszAlertProgram) ;
         }
      else
         {
         pLine->lpszAlertProgram = NULL ;
         }
      }
   //=============================//
   // Insert the line!            //
   //=============================//

   if (InsertLine (pLine) == FALSE)
      {
      // no inert occurred due to either line already existed
      // or error detected.
      LineFree (pLine) ;
      }
   else
      {
      if (pSystem->lpszValue && pSystem->FailureTime == 0)
         {
         if (strsame(pSystem->lpszValue, TEXT("Global")))
            {
            // take out the "Global" string
            *(pSystem->lpszValue) = 0 ;
            }
         AppendObjectToValueList (
            pLine->lnObject.ObjectNameTitleIndex,
            pSystem->lpszValue) ;
         }
      }
   }  // AddOneChartLine

BOOL AddCounter (HWND hDlg,
                      PPERFCOUNTERDEF pCounter,
                      LPTSTR lpszCounter)
   {  // AddCounter
   int               iInstanceIndex ;
   int               iInstanceNum ;
   PPERFINSTANCEDEF  pInstance ;
   HWND              hWndInstances = DialogControl (hDlg, IDD_ADDLINEINSTANCE);

   // NOTE: for now, we don't check for duplicate lines
   if (!IsCounterSupported (pCounter->CounterType))
      {
      DlgErrorBox (hDlg, ERR_COUNTER_NOT_IMP);
      return (FALSE) ;
      }

   if ((iInstanceNum = LBNumItems (hWndInstances)) && iInstanceNum != LB_ERR)
      {

      if (iInstanceNum > 1)
         {
         // delay some of the insert actions for performacne improvement
         bDelayAddAction = TRUE ;

         if (InChartAdd())
            {
            LegendSetRedraw (hWndGraphLegend, FALSE) ;
            }
         else if (InAlertAdd())
            {
            LegendSetRedraw (hWndAlertLegend, FALSE) ;
            }
         }

      for (iInstanceIndex = 0 ;
           iInstanceIndex < iInstanceNum ;
           iInstanceIndex++)
         {  // for
         if (LBSelected (hWndInstances, iInstanceIndex))
            {  // if
            pInstance = (PPERFINSTANCEDEF) LBData (hWndInstances, iInstanceIndex) ;
            if (pInstance == (PPERFINSTANCEDEF) LB_ERR)
               {
               pInstance = NULL;
               }
            AddOneChartLine (hDlg, pCounter, lpszCounter, pInstance) ;
            }  // if
         }  // for

      if (bDelayAddAction)
         {
         // now do the post add-line actions
         bDelayAddAction = FALSE ;
         if (InReportAdd())
            {
            PREPORT pReport ;

            pReport = ReportData (hWndReport) ;
            ReportAddAction (pReport) ;
            }
         else if (InChartAdd())
            {
            GraphAddAction () ;
            LegendSetRedraw (hWndGraphLegend, TRUE) ;
            }
         else if (InAlertAdd())
            {
            AlertAddAction () ;
            LegendSetRedraw (hWndAlertLegend, TRUE) ;
            }
         }
      }
   else
      {
      pInstance = NULL;
      AddOneChartLine (hDlg, pCounter, lpszCounter, pInstance) ;
      }

   return (TRUE) ;
   }  // AddCounter

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

BOOL /*static*/ OnInitDialog (HWND hDlg)
   {
   int            i ;
   FLOAT          ScaleFactor ;
   TCHAR          tempBuff[ShortTextLen] ;
   TCHAR          szCaption [WindowCaptionLen] ;
   TCHAR          szRemoteComputerName[MAX_COMPUTERNAME_LENGTH + 3] ;
   HWND           hWndComputer = DialogControl (hDlg, IDD_ADDLINECOMPUTER);
   HWND           hWndObjects = DialogControl (hDlg, IDD_ADDLINEOBJECT);
   HWND           hWndInstances = DialogControl (hDlg, IDD_ADDLINEINSTANCE);
   HWND           hWndCounters = DialogControl (hDlg, IDD_ADDLINECOUNTER);
   HWND           hWndColors = DialogControl (hDlg, IDD_ADDLINECOLOR) ;
   HWND           hWndWidths = DialogControl (hDlg, IDD_ADDLINEWIDTH) ;
   HWND           hWndStyles = DialogControl (hDlg, IDD_ADDLINESTYLE) ;
   HWND           hWndScales = DialogControl (hDlg, IDD_ADDLINESCALE) ;

   // this is used to tell UPdateLines not to mark any
   // system as not used
   bAddLineInProgress = TRUE ;

   // turn this off until the Explain text button is clicked
   bExplainTextButtonHit = FALSE ;

   if (InAlertAdd())
      {
      pszAlertProgram = (LPTSTR) MemoryAllocate (FilePathLen * sizeof (TCHAR)) ;
      }

   if (!PlayingBackLog ())
      pPerfData = MemoryAllocate (STARTING_SYSINFO_SIZE) ;

   pSystem = NULL ;

   if (!bEditLine && PlayingBackLog())
      {
      pPerfData = DataFromIndexPosition (&(PlaybackLog.StartIndexPos), NULL) ;
      GetPerfComputerName(pPerfData, szRemoteComputerName);
      DialogSetString (hDlg, IDD_ADDLINECOMPUTER, szRemoteComputerName);
      }
   else
      {
      if (bEditLine)
         {
         DialogSetString (hDlg, IDD_ADDLINECOMPUTER,
            pLineEdit->lnSystemName) ;
         }
      else
         {
//Try to use current system (if any), otherwise computer specified on commandline (if any),
//otherwise local computer
         DialogSetString (hDlg, IDD_ADDLINECOMPUTER,
            pCurrentSystem ?
                pCurrentSystem :
                (( CmdLineComputerName[0] ) ?
                   CmdLineComputerName :
                   LocalComputerName )) ;
         }
      }

   OnComputerChanged (hDlg) ;

   //=============================//
   // Set default line values     //
   //=============================//

   bAlertOver = bEditLine ? pLineEdit->bAlertOver : TRUE ;
   bEveryTime = bEditLine ? pLineEdit->bEveryTime : TRUE ;

   //=============================//
   // Fill line attribute CBs     //
   //=============================//

   // Load the colors combobox, select the default color.
   for (i = 0 ; i < NumColorIndexes () ; i++)
       CBAdd (hWndColors, i) ;
   CBSetSelection (hWndColors, pVisual->iColorIndex) ;

   // Load the widths combobox, select the default width.
   for (i = 0 ; i < NumWidthIndexes () ; i++)
      CBAdd (hWndWidths, i) ;
   CBSetSelection (hWndWidths, pVisual->iWidthIndex) ;

   // Load the styles combobox, select the default style.
   for (i = 0 ; i < NumStyleIndexes () ; i++)
      CBAdd (hWndStyles, i) ;
   CBSetSelection (hWndStyles, pVisual->iStyleIndex) ;

#if (!WIDESTYLES)
   DialogEnable (hDlg, IDD_ADDLINESTYLE, pVisual->iWidthIndex == 0) ;
   DialogEnable (hDlg, IDD_ADDLINESTYLETEXT, pVisual->iWidthIndex == 0) ;

   if (pVisual->iWidthIndex == 0 && pVisual->iStyleIndex > 0)
      {
      DialogEnable (hDlg, IDD_ADDLINEWIDTHTEXT, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINEWIDTH, FALSE) ;
      }
#endif

   // Init the scale combo box.

   StringLoad (IDS_DEFAULT, tempBuff) ;
   CBAdd (hWndScales, tempBuff) ;

   // we are formatting the scale factors during run-time so
   // the c-runtime library will pick up the default locale
   // decimal "charatcer".
   ScaleFactor = (FLOAT)0.000001 ;
   for (i = 0 ; i < NUMBER_OF_SCALE ; i++)
      {
      TSPRINTF(tempBuff, apszScaleFmt[i], ScaleFactor) ;
      ConvertDecimalPoint (tempBuff) ;
      ScaleFactor *= (FLOAT) 10.0 ;
      CBAdd (hWndScales, tempBuff) ;
      }

   CBSetSelection (hWndScales, bEditLine ? pLineEdit->iScaleIndex : DEFAULT_SCALE) ;

   CheckRadioButton (hDlg, IDD_ADDLINEIFOVER, IDD_ADDLINEIFUNDER,
                     bAlertOver ? IDD_ADDLINEIFOVER: IDD_ADDLINEIFUNDER) ;
   CheckRadioButton (hDlg, IDD_ADDLINEPROGRAMFIRSTTIME, IDD_ADDLINEPROGRAMEVERYTIME,
                     bEveryTime ? IDD_ADDLINEPROGRAMEVERYTIME: IDD_ADDLINEPROGRAMFIRSTTIME) ;

   if (bEditLine)
      {
      DialogSetText (hDlg, IDD_ADDLINEADD, IDS_OK) ;
      DialogSetFloat (hDlg, IDD_ADDLINEIFVALUE, pLineEdit->eAlertValue) ;
      if (pLineEdit->lpszAlertProgram)
         DialogSetString (hDlg, IDD_ADDLINEPROGRAM,
                          pLineEdit->lpszAlertProgram) ;

      DialogEnable (hDlg, IDD_ADDLINECOMPUTERTEXT, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINECOMPUTER, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINEELLIPSES, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINEOBJECTTEXT, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINEOBJECT, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINECOUNTERTEXT, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINECOUNTER, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINEINSTANCE, FALSE) ;
      DialogEnable (hDlg, IDD_ADDLINEINSTANCETEXT, FALSE) ;
      }
   else
      {
      // set the scroll limit on the edit box
      EditSetLimit (GetDlgItem(hDlg, IDD_CHOOSECOMPUTERNAME),
         MAX_SYSTEM_NAME_LENGTH-1) ;

      }

   if (PlayingBackLog())
      {
      DialogEnable (hDlg, IDD_ADDLINEEXPANDEXPLAIN, FALSE) ;
      }

   //=============================//
   // LineType specific init      //
   //=============================//

   switch (iLineType)
      {
      case LineTypeChart:
         dwCurrentDlgID = bEditLine ?
                  HC_PM_idDlgEditChartLine : HC_PM_idDlgEditAddToChart ;

         StringLoad (bEditLine ?
                     IDS_EDITCHART : IDS_ADDTOCHART, szCaption) ;

         DialogShow (hDlg, IDD_ADDLINEIFGROUP, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEIFVALUE, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEIFUNDER, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEIFOVER, FALSE) ;

         DialogShow (hDlg, IDD_ADDLINEPROGRAMGROUP, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEPROGRAM, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEPROGRAMFIRSTTIME, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEPROGRAMEVERYTIME, FALSE) ;
         break ;

      case LineTypeAlert:
         dwCurrentDlgID = bEditLine ?
                  HC_PM_idDlgEditAlertEntry : HC_PM_idDlgEditAddToAlert ;

         StringLoad (bEditLine ?
                     IDS_EDITALERT : IDS_ADDTOALERT, szCaption) ;

         DialogShow (hDlg, IDD_ADDLINESCALE, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINESCALETEXT, FALSE) ;

         DialogShow (hDlg, IDD_ADDLINEWIDTH, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEWIDTHTEXT, FALSE) ;

         DialogShow (hDlg, IDD_ADDLINESTYLE, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINESTYLETEXT, FALSE) ;

         if (PlayingBackLog ())
            {
            DialogEnable (hDlg, IDD_ADDLINEPROGRAM, FALSE) ;
            DialogEnable (hDlg, IDD_ADDLINEPROGRAMGROUP, FALSE) ;
            DialogEnable (hDlg, IDD_ADDLINEPROGRAMEVERYTIME, FALSE) ;
            DialogEnable (hDlg, IDD_ADDLINEPROGRAMFIRSTTIME, FALSE) ;
            }

         // set the scroll limit on the edit boxes

         EditSetLimit (GetDlgItem(hDlg, IDD_ADDLINEIFVALUE), ShortTextLen) ;
         EditSetLimit (GetDlgItem(hDlg, IDD_ADDLINEPROGRAM), FilePathLen-1) ;

         break ;

      case LineTypeReport:

         dwCurrentDlgID = HC_PM_idDlgEditAddToReport ;

         StringLoad (bEditLine ?
                     IDS_EDITREPORT : IDS_ADDTOREPORT, szCaption) ;

         DialogShow (hDlg, IDD_ADDLINEIFGROUP, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEIFVALUE, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEIFUNDER, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEIFOVER, FALSE) ;

         DialogShow (hDlg, IDD_ADDLINEPROGRAMGROUP, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEPROGRAM, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEPROGRAMFIRSTTIME, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEPROGRAMEVERYTIME, FALSE) ;

         DialogShow (hDlg, IDD_ADDLINECOLOR, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINECOLORTEXT, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINESCALE, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINESCALETEXT, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEWIDTH, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINEWIDTHTEXT, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINESTYLE, FALSE) ;
         DialogShow (hDlg, IDD_ADDLINESTYLETEXT, FALSE) ;
         break ;
      }  // switch

   SetWindowText (hDlg, szCaption) ;
   SendDlgItemMessage (hDlg,
                       IDD_ADDLINEEXPLAIN, WM_SETFONT,
                       (WPARAM) hFontScales, (LPARAM) FALSE) ;
   WindowCenter (hDlg) ;
   return (TRUE) ;
   }  // OnInitDialog

BOOL /*static*/ OnObjectChanged (HDLG hDlg)
/*
   Effect:        Perform any actions necessary when the user has selected
                  a new object category from the object CB, or when a default
                  object is first selected into the dialog. In particular,
                  find and load the counters, instances, etc., for this
                  object.

   Called by:     OnInitDialog, AddLineDlgProc (in response to an
                  IDM_ADDLINEOBJECT notification).
*/
   {  // OnObjectChanged
   PPERFOBJECT    pObject ;
   HWND           hWndInstances = DialogControl (hDlg, IDD_ADDLINEINSTANCE);
   HWND           hWndCounters = DialogControl (hDlg, IDD_ADDLINECOUNTER);
   HWND           hWndObjects = DialogControl (hDlg, IDD_ADDLINEOBJECT);

   LBReset (hWndInstances) ;
   LBReset (hWndCounters) ;

   pObject = SelectedObject (hWndObjects, NULL) ;
   if (!pObject)
      return (FALSE) ;

   LoadCounters (hDlg, (UINT)pObject->DefaultCounter) ;
   LoadInstances (hDlg) ;

   }  // OnObjectChanged

void /*static*/ OnEllipses (HWND hDlg)
   {
   TCHAR          szComputer [256] ;

   DialogText (hDlg, IDD_ADDLINECOMPUTER, szComputer) ;
   if (ChooseComputer (hDlg, szComputer))
      {  // if
      SetHourglassCursor() ;
      DialogSetString (hDlg, IDD_ADDLINECOMPUTER, szComputer) ;
      OnComputerChanged (hDlg) ;
      }  // if
   }

BOOL LineModifyAttributes (HWND hDlg, PLINE pLineToModify)
{
   LINEVISUAL  LineVisual ;
   HPEN        hLinePen ;
   int         iScaleIndex ;        // chart attribute
   FLOAT      eScale ;             // chart attribute

   BOOL        bLocalAlertOver ;         // alert attribute - over or under?
   FLOAT       eLocalAlertValue ;        // alert attribute - value to compare
   LPTSTR      lpLocalszAlertProgram ;   // alert attribute - program to run
   BOOL        bLocalEveryTime ;         // alert attribute - run every time or once?
   BOOL        bLocalAlerted ;           // alert attribute - alert happened on line?

   HPEN        hTempPen ;
   LPTSTR      lpTempAlertProgram ;
   HWND        hWndColors = DialogControl (hDlg, IDD_ADDLINECOLOR) ;
   HWND        hWndWidths = DialogControl (hDlg, IDD_ADDLINEWIDTH) ;
   HWND        hWndStyles = DialogControl (hDlg, IDD_ADDLINESTYLE) ;
   HWND        hWndScales = DialogControl (hDlg, IDD_ADDLINESCALE) ;

   //=============================//
   // Visual Values               //
   //=============================//

   LineVisual.iColorIndex = CBSelection (hWndColors) ;
   LineVisual.crColor = LineColor (LineVisual.iColorIndex) ;

   LineVisual.iWidthIndex = CBSelection (hWndWidths) ;
   LineVisual.iWidth = LineWidth (LineVisual.iWidthIndex) ;

   LineVisual.iStyleIndex = CBSelection (hWndStyles) ;
   LineVisual.iStyle = LineStyle (LineVisual.iStyleIndex) ;

   hLinePen = LineCreatePen (NULL, &(LineVisual), FALSE) ;

   //=============================//
   // Chart-related Values        //
   //=============================//

   if (InChartAdd())
      {
      iScaleIndex = CBSelection (hWndScales) ;
      if (iScaleIndex == 0)
          {
          // use the default scale
          eScale = (FLOAT) pow ((double)10.0,
              (double)pLineToModify->lnCounterDef.DefaultScale) ;
          }
      else
          {
          eScale = DialogFloat (hDlg, IDD_ADDLINESCALE, NULL) ;
          }
      }

   //=============================//
   // Alert Related Values        //
   //=============================//

   if (InAlertAdd())
      {
      bLocalAlertOver = bAlertOver ;
      eLocalAlertValue = eAlertValue ;
      bLocalEveryTime =
         IsDlgButtonChecked (hDlg, IDD_ADDLINEPROGRAMEVERYTIME) ;
      bLocalAlerted = FALSE ;
      lpLocalszAlertProgram = StringAllocate (pszAlertProgram) ;
      }

   // Just do it..
   pLineToModify->Visual = LineVisual ;
   if (pLineToModify->hPen)
      {
      hTempPen = pLineToModify->hPen ;
      pLineToModify->hPen = hLinePen ;
      DeletePen (hTempPen) ;
      }

   if (InChartAdd())
      {
      pLineToModify->iScaleIndex = iScaleIndex ;
      pLineToModify->eScale = eScale ;

      }

   if (InAlertAdd())
      {
      HBRUSH   hOldBrush;
      pLineToModify->bAlertOver = bLocalAlertOver ;
      pLineToModify->eAlertValue = eLocalAlertValue ;
      pLineToModify->bEveryTime = bLocalEveryTime ;
      pLineToModify->bAlerted = FALSE ;

      hOldBrush = pLineToModify->hBrush ;
      pLineToModify->hBrush = CreateSolidBrush (pLineToModify->Visual.crColor) ;
      DeleteBrush (hOldBrush);

      lpTempAlertProgram = pLineToModify->lpszAlertProgram ;
      pLineToModify->lpszAlertProgram = lpLocalszAlertProgram ;

      if (lpTempAlertProgram)
         {
         MemoryFree (lpTempAlertProgram) ;
         }
      }

   return (TRUE) ;

} // LineModifyAttributes

BOOL OnAddLines (HWND hDlg)
   {
   PPERFCOUNTERDEF   pCounter ;
   TCHAR             szCounter [256] ;
   BOOL              bOK ;
   int               iCounter ;
   int               iCounterNum ;
   HWND              hWndCounters = DialogControl (hDlg, IDD_ADDLINECOUNTER);

   if (ComputerChange)
      {
      // if computer has changed, don't want to continue
      // because the perfdata may have changed
      OnComputerChanged (hDlg) ;
      return (TRUE) ;
      }

   //=============================//
   // Dialog Values Acceptable?   //
   //=============================//

   if (InAlertAdd ())
      {
      eAlertValue = DialogFloat (hDlg, IDD_ADDLINEIFVALUE, &bOK) ;
      if (!bOK)
         {
         DlgErrorBox (hDlg, ERR_NEEDALERTVALUE);
         SetFocus (DialogControl (hDlg, IDD_ADDLINEIFVALUE)) ;
         return (FALSE) ;
         }  // if
      }  // if

   if (bEditLine)
      {
      LineModifyAttributes (hDlg, pLineEdit) ;
      EndDialog (hDlg, TRUE) ;
      }

      // If the user changed the textbox for computer name and pressed enter,
      // the OnAddLines function would be called without a check of the
      // computer name. This solves that problem.
   else
      {

      iCounterNum = LBNumItems (hWndCounters) ;
      for (iCounter = 0 ;
           iCounter < iCounterNum ;
           iCounter++)
         {  // for
         // NOTE: for now, we don't check for duplicate lines
         if (LBSelected (hWndCounters, iCounter))
            {  // if
            pCounter = (PPERFCOUNTERDEF) LBData (hWndCounters, iCounter) ;
            LBString (hWndCounters, iCounter, szCounter) ;

            if (!IsCounterSupported (pCounter->CounterType))
               {
               DlgErrorBox (hDlg, ERR_COUNTER_NOT_IMP);
               }
            else
               {
               AddCounter (hDlg, pCounter, szCounter) ;
               }
            }  // if
         }  // for
      DialogSetText (hDlg, IDCANCEL, IDS_DONE) ;
      } // else (not bEditLine)

   if (InChartAdd ())
      SizeGraphComponents (hWndGraph) ;
   if (InAlertAdd ())
      SizeAlertComponents (hWndAlert) ;

   WindowInvalidate (PerfmonViewWindow ()) ;

   }  // OnAddLines

void OnExpandExplain (HWND hDlg)
/*
   Effect:        Perform actions needed when user clicks on the Explain...
                  button. In particular, expand the dialog size to
                  uncover the explain edit box, and gray out the button.
*/
   {  // OnExpandExplain
   RECT           rectWindow ;

   // Disable button first
   DialogEnable (hDlg, IDD_ADDLINEEXPANDEXPLAIN, FALSE) ;

   // go get the help text
   bExplainTextButtonHit = TRUE ;
   OnCounterChanged (hDlg) ;

   GetWindowRect (hDlg, &rectWindow) ;
   MoveWindow (hDlg,
               rectWindow.left,
               rectWindow.top,
               rectWindow.right - rectWindow.left,
               rectWindow.bottom - rectWindow.top +
               DialogHeight (hDlg, IDD_ADDLINEEXPLAINGROUP) +
               yScrollHeight,
               TRUE) ;

   WindowCenter (hDlg) ;

   }  // OnExpandExplain

BOOL /*static*/ OnCommand (HWND hDlg,
                       WPARAM wParam,
                       LPARAM lParam)
   {
   int            iWidthIndex ;
   int            iStyleIndex ;
   HWND           hWndWidths = DialogControl (hDlg, IDD_ADDLINEWIDTH) ;
   HWND           hWndStyles = DialogControl (hDlg, IDD_ADDLINESTYLE) ;

   switch (LOWORD (wParam))
      {
      case IDD_ADDLINEIFVALUE:
         // We don't get the value when the user types it. We get it when
         // the user attempts to add the line.
         break ;

      case IDD_ADDLINEPROGRAM:
         GetDlgItemText (hDlg,
            IDD_ADDLINEPROGRAM,
            pszAlertProgram,
            FilePathLen - 1) ;
         break ;

      case IDD_ADDLINEIFOVER:
      case IDD_ADDLINEIFUNDER:
         bAlertOver = (LOWORD (wParam) == IDD_ADDLINEIFOVER) ;
         CheckRadioButton (hDlg, IDD_ADDLINEIFOVER, IDD_ADDLINEIFUNDER,
                           bAlertOver ? IDD_ADDLINEIFOVER: IDD_ADDLINEIFUNDER) ;
         break ;

      case IDD_ADDLINEPROGRAMFIRSTTIME:
      case IDD_ADDLINEPROGRAMEVERYTIME:
         bEveryTime = (LOWORD (wParam) == IDD_ADDLINEPROGRAMEVERYTIME) ;
         CheckRadioButton (hDlg, IDD_ADDLINEPROGRAMFIRSTTIME, IDD_ADDLINEPROGRAMEVERYTIME,
                           bEveryTime ? IDD_ADDLINEPROGRAMEVERYTIME: IDD_ADDLINEPROGRAMFIRSTTIME) ;
         break ;

      case IDD_ADDLINEWIDTH:
         iWidthIndex = CBSelection (hWndWidths) ;
#if (!WIDESTYLES)
         DialogEnable (hDlg, IDD_ADDLINESTYLETEXT,
                       iWidthIndex == 0  || iWidthIndex == CB_ERR) ;
         DialogEnable (hDlg, IDD_ADDLINESTYLE,
                       iWidthIndex == 0  || iWidthIndex == CB_ERR) ;
#endif
         break ;

      case IDD_ADDLINESTYLE:
         iStyleIndex = CBSelection (hWndStyles) ;
#if (!WIDESTYLES)
         DialogEnable (hDlg, IDD_ADDLINEWIDTHTEXT,
                       iStyleIndex == 0  || iStyleIndex == CB_ERR) ;
         DialogEnable (hDlg, IDD_ADDLINEWIDTH,
                       iStyleIndex == 0  || iStyleIndex == CB_ERR) ;
#endif
         break ;

      case IDCANCEL:
         EndDialog (hDlg, 0);
         return (TRUE);
         break ;

      case IDD_ADDLINEADD :

         if (ComputerChange)
            {
            SetHourglassCursor() ;
            OnComputerChanged (hDlg) ;
            }
         else
            {
            SetHourglassCursor() ;
            OnAddLines (hDlg) ;
            SetArrowCursor() ;
            }
         break;

      case IDD_ADDLINEEXPANDEXPLAIN :
         if (ComputerChange)
            {
            SetHourglassCursor() ;
            OnComputerChanged (hDlg) ;
            }
         else
            {
            OnExpandExplain (hDlg) ;
            }
         break;

      case IDD_ADDLINEELLIPSES:
         SetHourglassCursor() ;
         OnEllipses (hDlg) ;
         SetArrowCursor() ;
         break ;

      case IDD_ADDLINECOUNTER:
         if (ComputerChange)
            {
            SetHourglassCursor() ;
            OnComputerChanged (hDlg) ;
            }
         else if (HIWORD (wParam) == LBN_SELCHANGE)
            OnCounterChanged (hDlg) ;
         break ;

      case IDD_ADDLINEOBJECT:
         if (ComputerChange)
            {
            SetHourglassCursor() ;
            OnComputerChanged (hDlg) ;
            }
         else if (HIWORD (wParam) == CBN_SELCHANGE)
            OnObjectChanged (hDlg) ;
         break ;

      case IDD_ADDLINEINSTANCE:
         if (ComputerChange)
            {
            SetHourglassCursor() ;
            OnComputerChanged (hDlg) ;
            }
         break ;

      case IDD_ADDLINECOMPUTER:
         if (HIWORD (wParam) == EN_UPDATE)
            {
            ComputerChange = TRUE ;
            }
         break ;

      case IDD_ADDLINEHELP:
         CallWinHelp (dwCurrentDlgID) ;
         break ;

default:
    break;
    } // switch

   return (FALSE) ;
   } // OnCommand

void /*static*/ OnMeasureItem (HWND hDlg,
                           PMEASUREITEMSTRUCT pMI)
   {
   pMI->CtlType    = ODT_COMBOBOX ;
   pMI->CtlID      = IDD_ADDLINECOLOR ;
   pMI->itemData   = 0 ;
   pMI->itemWidth  = 0 ;

   // need 14 in order to draw the thickest line width
   pMI->itemHeight = 14 ;
//   pMI->itemHeight = 12 ;
   }

//***************************************************************************
//                                                                          *
//  FUNCTION   : HandleSelectionState(LPDRAWITEMSTRUCT)                     *
//                                                                          *
//  PURPOSE    : Handles a change in an item selection state. If an item is *
//               selected, a black rectangular frame is drawn around that   *
//               item; if an item is de-selected, the frame is removed.     *
//                                                                          *
//  COMMENT    : The black selection frame is slightly larger than the gray *
//               focus frame so they won't paint over each other.           *
//                                                                          *
//***************************************************************************
void static HandleSelectionState (LPDRAWITEMSTRUCTlpdis)
{
HBRUSHhbr ;

if (lpdis->itemState & ODS_SELECTED)
{
// selecting item -- paint a black frame
hbr = GetStockObject(BLACK_BRUSH) ;
}
else
{
// de-selecting item -- remove frame
hbr = CreateSolidBrush(GetSysColor(COLOR_WINDOW)) ;
}
FrameRect(lpdis->hDC, (LPRECT)&lpdis->rcItem, hbr) ;
DeleteObject (hbr) ;
   }  // HandleSelectionState

//***************************************************************************
//                                                                          *
//  FUNCTION   : HandleFocusState(LPDRAWITEMSTRUCT)                         *
//                                                                          *
//  PURPOSE    : Handle a change in item focus state. If an item gains the  *
//               input focus, a gray rectangular frame is drawn around that *
//               item; if an item loses the input focus, the gray frame is  *
//               removed.                                                   *
//                                                                          *
//  COMMENT    : The gray focus frame is slightly smaller than the black    *
//               selection frame so they won't paint over each other.       *
//                                                                          *
//***************************************************************************
void static HandleFocusState (LPDRAWITEMSTRUCTlpdis)
   {
RECT   rc ;
HBRUSHhbr ;

// Resize rectangle to place focus frame between the selection
// frame and the item.
CopyRect ((LPRECT)&rc, (LPRECT)&lpdis->rcItem) ;
InflateRect ((LPRECT)&rc, -OWNER_DRAW_FOCUS, -OWNER_DRAW_FOCUS) ;

if (lpdis->itemState & ODS_FOCUS)
{
// gaining input focus -- paint a gray frame
hbr = GetStockObject(GRAY_BRUSH) ;
}
else
{
// losing input focus -- remove (paint over) frame
hbr = CreateSolidBrush(GetSysColor(COLOR_WINDOW)) ;
}
FrameRect(lpdis->hDC, (LPRECT)&rc, hbr) ;
DeleteObject (hbr) ;
   }  // HandleFocusState

void /*static*/ OnDrawItem (HWND hDlg, PDRAWITEMSTRUCT pDI)
   {  // OnDrawItem
   HDC            hDC ;
   PRECT          prect ;
   INT            itemData,
                  itemID,
                  CtlID,
                  itemAction ;
   LOGBRUSH       logBrush ;
   HANDLE         hBrush,
                  hOldBrush,
                  hPen,
                  hOldPen ;
   INT            x1, y1, x2, y2, cy ;
   POINT          point ;
   INT            iPenWidth ;
   COLORREF       BackgroundColor ;

   hDC        = pDI-> hDC ;
   CtlID      = pDI->CtlID ;
   prect      = &pDI->rcItem ;
   itemData   = pDI->itemData ;
   itemID     = pDI->itemID ;
   itemAction = pDI->itemAction ;

   if (itemID == -1)
      {
      // invalid ID, can't go on
      HandleFocusState (pDI) ;
      }
   else if (itemAction == ODA_SELECT)
      {
      HandleSelectionState(pDI);
      }
   else if (itemAction == ODA_FOCUS)
      {
      HandleFocusState (pDI) ;
      }
   else
      {

      // draw the entire item

      InflateRect (prect, -OWNER_DRAWN_ITEM, -OWNER_DRAWN_ITEM) ;

      switch (CtlID)
         {  // switch
         case IDD_ADDLINECOLOR:

            // Draw a color rectangle into the control area

            logBrush.lbStyle = BS_SOLID ;
            logBrush.lbColor = (COLORREF) argbColors[itemID] ;
            logBrush.lbHatch = 0 ;

            hBrush = CreateBrushIndirect (&logBrush) ;
            hOldBrush = SelectObject (hDC, hBrush) ;

            hPen = GetStockObject (NULL_PEN) ;
            hOldPen = SelectObject (hDC, hPen) ;

            x1 = prect->left ;
            y1 = prect->top ;
            x2 = prect->right ;
            y2 = prect->bottom ;

            Rectangle (hDC, x1, y1, x2, y2) ;

            SelectObject (hDC, hOldBrush) ;
            DeleteObject (hBrush) ;

            InflateRect (prect, OWNER_DRAWN_ITEM, OWNER_DRAWN_ITEM) ;

            HandleSelectionState (pDI) ;
            HandleFocusState (pDI) ;

            break ;

         case IDD_ADDLINEWIDTH:
         case IDD_ADDLINESTYLE:

            // First draw a rectangle, white interior, null border
            hBrush = GetStockObject (WHITE_BRUSH) ;
            hOldBrush = SelectObject (hDC, hBrush) ;

            // we need to set the bk color in order to draw
            // the dash lines coorectly during focus.  Otherwise,
            // the COLOR_WINDOW background will make all dash lines
            // look like solid line...

BackgroundColor = SetBkColor (hDC, crWhite) ;

            hPen = GetStockObject (NULL_PEN) ;
            hOldPen = SelectObject (hDC, hPen) ;

            x1 = prect->left ;
            y1 = prect->top ;
            x2 = prect->right ;
            y2 = prect->bottom ;

            Rectangle (hDC, x1, y1, x2, y2) ;

            SelectObject (hDC, hOldPen) ;

            // Draw a line of the itemID width in the middle
            // of the control area.

            if (CtlID == IDD_ADDLINEWIDTH)
               {
               iPenWidth = LineWidth (itemID) ;
               hPen = CreatePen (PS_SOLID, iPenWidth, RGB (0, 0, 0)) ;
               }
            else
               {
               hPen = CreatePen (itemID, 1, RGB (0, 0, 0)) ;
               }

            hOldPen = SelectObject (hDC, hPen) ;

            x1 = prect->left + 8 ;
            cy = prect->bottom - prect->top ;
            y1 = prect->top + (cy / 2) - 1 ;
            x2 = prect->right - 8 ;
            MoveToEx (hDC, x1, y1, &point) ;
            LineTo (hDC, x2, y1) ;

            SelectObject (hDC, hOldPen) ;
            DeleteObject (hPen) ;
            SelectObject (hDC, hOldBrush) ;
            BackgroundColor = SetBkColor (hDC, BackgroundColor) ;

            InflateRect (prect, OWNER_DRAWN_ITEM, OWNER_DRAWN_ITEM) ;

            HandleSelectionState (pDI) ;
            HandleFocusState (pDI) ;

            break ;
         }   // switch
      }  // draw entire item
   }  // OnDrawItem

void /*static*/ OnDestroy (HDLG hDlg)
   {  // OnDestroy
   if (!PlayingBackLog ())
      MemoryFree (pPerfData) ;

   if (pszAlertProgram)
      {
      MemoryFree (pszAlertProgram) ;
      pszAlertProgram = NULL ;
      }

   pLineEdit = NULL ;
   bAddLineInProgress = FALSE ;
   dwCurrentDlgID = 0 ;
   bExplainTextButtonHit = FALSE ;
   }  // OnDestroy

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL FAR PASCAL AddLineDlgProc (HWND hDlg,
                                WORD msg,
                                DWORD wParam,
                                LONG lParam)
   {  // AddLineDlgProc

   BOOL Status;

   switch (msg)
      {  // switch
      case WM_COMMAND:
         OnCommand (hDlg, wParam, lParam) ;
         return (FALSE) ;
         break ;

   case WM_INITDIALOG:
         SetHourglassCursor() ;
         Status = OnInitDialog (hDlg) ;
         SetArrowCursor() ;

         // set focus on the "Add" button instead of the "Computer"
         SetFocus (DialogControl (hDlg, IDD_ADDLINEADD)) ;
         return FALSE ;
         break ;

      case WM_MEASUREITEM:
         OnMeasureItem (hDlg, (PMEASUREITEMSTRUCT) lParam) ;
         return (TRUE) ;
         break ;

      case WM_DRAWITEM:
         OnDrawItem (hDlg, (PDRAWITEMSTRUCT) lParam) ;
         return (TRUE) ;
         break ;

      case WM_DESTROY:
         OnDestroy (hDlg) ;
         break ;

   default:
      break;
      }  // switch

   return (FALSE) ;
   }  // AddLineDlgProc

BOOL AddLine (HWND hWndParent,
              PPERFSYSTEM *ppSystemFirstView,
              PLINEVISUAL pLineVisual,
              LPTSTR pInCurrentSystem,
              int iLineTypeToAdd)
/*
   Effect:        Display the add line dialog box, allowing the user
                  to specify the computer, object, counter, instance,
                  and scale for a line.  The user can also select the
                  visual aspects of color, width and line style.

*/
   {  // AddLine
   pLineEdit = NULL ;

   ppSystemFirst = ppSystemFirstView ;
   iLineType = iLineTypeToAdd ;
   pVisual = pLineVisual ;
   pCurrentSystem = pInCurrentSystem ;

   return (DialogBox (hInstance, idDlgAddLine,
                      hWndParent, (DLGPROC) AddLineDlgProc)) ;
   }  // AddLine

BOOL EditLine (HWND hWndParent,
               PPERFSYSTEM *ppSystemFirstView,
               PLINE pLineToEdit,
               int iLineTypeToEdit)
   {  // EditLine
   if (!pLineToEdit)
      {
      return (FALSE) ;
      }

   pLineEdit = pLineToEdit ;

   ppSystemFirst = ppSystemFirstView ;
   iLineType = iLineTypeToEdit ;
   pVisual = &(pLineToEdit->Visual) ;

   return (DialogBox (hInstance, idDlgAddLine,
                      hWndParent, (DLGPROC) AddLineDlgProc)) ;
   }  // EditLine


REGISTRY.C

/*****************************************************************************
 *
 *  Registry.c - This module handles requests for registry data, and
 *               reading/writing of window placement data
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *
 ****************************************************************************/

#include <stdio.h>

#include "perfmon.h"
#include "registry.h"
#include "utils.h"
#include "sizes.h"

static TCHAR PerfmonNamesKey[] = TEXT("SOFTWARE\\Microsoft\\PerfMon") ;
static TCHAR WindowKeyName[] = TEXT("WindowPos") ;
static TCHAR TimeOutKeyName[] = TEXT("DataTimeOut") ;

VOID LoadLineGraphSettings(PGRAPHSTRUCT lgraph)
{
   lgraph->gMaxValues = DEFAULT_MAX_VALUES;
   lgraph->gOptions.bLegendChecked = DEFAULT_F_DISPLAY_LEGEND;
   lgraph->gOptions.bLabelsChecked = DEFAULT_F_DISPLAY_CALIBRATION;

   return;
}

VOID LoadRefreshSettings(PGRAPHSTRUCT lgraph)
{
   lgraph->gInterval = DEF_GRAPH_INTERVAL;
   lgraph->gOptions.eTimeInterval = (FLOAT) lgraph->gInterval / (FLOAT) 1000.0 ;
   return;
}

BOOL LoadMainWindowPlacement (HWND hWnd)
   {
   WINDOWPLACEMENT   WindowPlacement ;
   TCHAR             szWindowPlacement [TEMP_BUF_LEN] ;
   HKEY              hKeyNames ;
   DWORD             Size;
   DWORD             Type;
   DWORD             Status;
   DWORD             localDataTimeOut;
   STARTUPINFO       StartupInfo ;

   GetStartupInfo (&StartupInfo) ;

   DataTimeOut = DEFAULT_DATA_TIMEOUT ;

   Status = RegOpenKeyEx(HKEY_CURRENT_USER, PerfmonNamesKey,
      0L, KEY_READ, &hKeyNames) ;

   if (Status == ERROR_SUCCESS)
      {
      // get the data timeout  value
      Size = sizeof(localDataTimeOut) ;

      Status = RegQueryValueEx(hKeyNames, TimeOutKeyName, NULL,
         &Type, (LPBYTE)&localDataTimeOut, &Size) ;
      if (Status == ERROR_SUCCESS && Type == REG_DWORD)
         {
         DataTimeOut = localDataTimeOut ;
         }

      // get the window placement data
      Size = sizeof(szWindowPlacement) ;

      Status = RegQueryValueEx(hKeyNames, WindowKeyName, NULL,
         &Type, (LPBYTE)szWindowPlacement, &Size) ;
      RegCloseKey (hKeyNames) ;

      if (Status == ERROR_SUCCESS)
         {

         int            iNumScanned ;

         iNumScanned = swscanf (szWindowPlacement,
            TEXT("%d %d %d %d %d %d %d %d %d"),
            &WindowPlacement.showCmd,
            &WindowPlacement.ptMinPosition.x,
            &WindowPlacement.ptMinPosition.y,
            &WindowPlacement.ptMaxPosition.x,
            &WindowPlacement.ptMaxPosition.y,
            &WindowPlacement.rcNormalPosition.left,
            &WindowPlacement.rcNormalPosition.top,
            &WindowPlacement.rcNormalPosition.right,
            &WindowPlacement.rcNormalPosition.bottom) ;

         if (StartupInfo.dwFlags == STARTF_USESHOWWINDOW)
            {
            WindowPlacement.showCmd = StartupInfo.wShowWindow ;
            }
         WindowPlacement.length = sizeof(WINDOWPLACEMENT);
         WindowPlacement.flags = WPF_SETMINPOSITION;
         if (!SetWindowPlacement (hWnd, &WindowPlacement)) {
            return (FALSE);
         }
         bPerfmonIconic = IsIconic(hWnd) ;
         return (TRUE) ;
         }
      }

   if (Status != ERROR_SUCCESS)
      {
      // open registry failed, use input from startup info or Max as default

      if (StartupInfo.dwFlags == STARTF_USESHOWWINDOW)
         {
         ShowWindow (hWnd, StartupInfo.wShowWindow) ;
         }
      else
         {
         ShowWindow (hWnd, SW_SHOWMAXIMIZED) ;
         }
      return (FALSE) ;
      }
   }

BOOL SaveMainWindowPlacement (HWND hWnd)
   {
   WINDOWPLACEMENT   WindowPlacement ;
   TCHAR             ObjectType [2] ;
   TCHAR             szWindowPlacement [TEMP_BUF_LEN] ;
   HKEY              hKeyNames = 0 ;
   DWORD             Size ;
   DWORD             Status ;
   DWORD             dwDisposition ;

   ObjectType [0] = TEXT(' ') ;
   ObjectType [1] = TEXT('\0') ;

   WindowPlacement.length = sizeof(WINDOWPLACEMENT);
   if (!GetWindowPlacement (hWnd, &WindowPlacement)) {
      return FALSE;
   }
   TSPRINTF (szWindowPlacement, TEXT("%d %d %d %d %d %d %d %d %d"),
            WindowPlacement.showCmd,
            WindowPlacement.ptMinPosition.x,
            WindowPlacement.ptMinPosition.y,
            WindowPlacement.ptMaxPosition.x,
            WindowPlacement.ptMaxPosition.y,
            WindowPlacement.rcNormalPosition.left,
            WindowPlacement.rcNormalPosition.top,
            WindowPlacement.rcNormalPosition.right,
            WindowPlacement.rcNormalPosition.bottom) ;

   // try to create it first
   Status = RegCreateKeyEx(HKEY_CURRENT_USER, PerfmonNamesKey, 0L,
      ObjectType, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS | KEY_WRITE,
      NULL, &hKeyNames, &dwDisposition) ;

   // if it has been created before, then open it
   if (dwDisposition == REG_OPENED_EXISTING_KEY)
      {
      Status = RegOpenKeyEx(HKEY_CURRENT_USER, PerfmonNamesKey, 0L,
         KEY_WRITE, &hKeyNames) ;
      }

   // we got the handle, now store the window placement data
   if (Status == ERROR_SUCCESS)
      {
      Size = (lstrlen (szWindowPlacement) + 1) * sizeof (TCHAR) ;

      Status = RegSetValueEx(hKeyNames, WindowKeyName, 0,
         REG_SZ, (LPBYTE)szWindowPlacement, Size) ;

      if (dwDisposition != REG_OPENED_EXISTING_KEY && Status == ERROR_SUCCESS)
         {
         // now add the DataTimeOut key for the first time
         Status = RegSetValueEx(hKeyNames, TimeOutKeyName, 0,
            REG_DWORD, (LPBYTE)&DataTimeOut, sizeof(DataTimeOut)) ;
         }

      RegCloseKey (hKeyNames) ;

      }

   return (Status == ERROR_SUCCESS) ;
   }


REGISTRY.H

/*****************************************************************************
 *
 *  Registry.h - This is the include file for the user config code.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *
 ****************************************************************************/

// default values for .INI files

#define DEFAULT_TIMER_INTERVAL  2000    // sampling interval

// LINEGRAPH
// first three are general default values. The others are specific
#define DEFAULT_VAL_BOTTOM        0
#define DEFAULT_DVAL_AXISHEIGHT   100
#define DEFAULT_MAX_VALUES        100

// LINEGRAPH DISP
#define DEFAULT_F_DISPLAY_LEGEND  TRUE
#define DEFAULT_F_DISPLAY_CALIBRATION TRUE

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL LoadMainWindowPlacement (HWND hWnd) ;

BOOL SaveMainWindowPlacement (HWND hWnd) ;


REPORT.C

/*****************************************************************************
 *
 *  Report.c - This file contains the report window handler.  Some of the
 *       support routines are in RptFct.c
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *  Author -
 *
 *       Hon-Wah Chan
 *
 ****************************************************************************/

#include "perfmon.h"
#include <stdio.h>      // for sprintf
#include <string.h>     // for strncpy
#include "report.h"     // Exported declarations for this file

#include "addline.h"    // for AddLine, EditLine
#include "perferr.h"    // for PostError
#include "fileutil.h"   // for FileHandleCreate
#include "line.h"       // for LineAppend
#include "pmemory.h"    // for MemoryXXX (mallloc-type) routines
#include "perfdata.h"   // for UpdateLines
#include "perfmops.h"   // for DoWindowDrag
#include "playback.h"   // for PlaybackLines, PlayingBackLog
#include "system.h"     // for SystemGet
#include "utils.h"
#include "menuids.h"    // for IDM_VIEWREPORT
#include "fileopen.h"   // for FileGetName
#include "counters.h"   // for CounterEntry

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

TCHAR          szSystemFormat [ResourceStringLen] ;
TCHAR          szObjectFormat [ResourceStringLen] ;

//=============================//
// Report Class                //
//=============================//

TCHAR   szReportWindowClass[] = TEXT("PerfRpt") ;
#define dwReportClassStyle           (CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS)
#define iReportClassExtra            (0)
#define iReportWindowExtra           (0)
#define dwReportWindowStyle          (WS_CHILD | WS_VSCROLL | WS_HSCROLL)

#define szValuePlaceholder          TEXT("-999999999.999")
#define szValueLargeHexPlaceholder  TEXT(" xBBBBBBBBDDDDDDDD")

#define szHexFormat                 TEXT("x%08lX")
#define szLargeHexFormat            TEXT("x%08lX%08lX")
#define szLargeValueFormat          TEXT("%12.0f")
#define eStatusLargeValueMax        ((FLOAT) 999999999.0)
#define szValueFormat               TEXT("%12.3f")

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

PREPORT AllocateReportData (HWND hWndReport)
   {
   PREPORT        pReport ;

   pReport = ReportData (hWndReport) ;

   pReport->hWnd = hWndReport ;
   pReport->iStatus = iPMStatusClosed ;
   pReport->bManualRefresh = FALSE ;
   pReport->bModified = FALSE ;

   pReport->Visual.iColorIndex = 0 ;
   pReport->Visual.iWidthIndex = -1 ;
   pReport->Visual.iStyleIndex = -1 ;

   pReport->iIntervalMSecs = iDefaultReportIntervalSecs * 1000 ;
   pReport->pSystemFirst = NULL ;
   pReport->pLineFirst = NULL ;

   pReport->CurrentItemType = REPORT_TYPE_NOTHING ;
   pReport->CurrentItem.pLine = NULL ;

   return (pReport) ;
   }  // AllocateReportData

void FreeReportData (PREPORT pReport)
   {  // FreeReportData
   }  // FreeReportData

BOOL LineCounterRemove (PPLINE ppLineFirst,
                        PLINE pLineRemove)
   {
   PLINE          pLine ;

   if (*ppLineFirst == pLineRemove)
      {
      *ppLineFirst = (*ppLineFirst)->pLineCounterNext ;
      return (TRUE) ;
      }

   for (pLine = *ppLineFirst ;
        pLine->pLineCounterNext ;
        pLine = pLine->pLineCounterNext)
      {   // for
      if (pLine->pLineCounterNext == pLineRemove)
         {
         pLine->pLineCounterNext = pLineRemove->pLineCounterNext ;
         return (TRUE) ;
         }  // if
      }  // for

   return (FALSE) ;
   }  // LineCounterRemove

void DrawCounter (HDC hDC, PREPORT pReport,
                  PCOUNTERGROUP pCounterGroup)
   {  // DrawCounter

   RECT  Rect ;

   if (!pCounterGroup->pLineFirst)
      return ;

   SelectFont (hDC, pReport->hFont) ;
   TextOut (hDC, xCounterMargin, pCounterGroup->yLine,
            pCounterGroup->pLineFirst->lnCounterName,
            lstrlen (pCounterGroup->pLineFirst->lnCounterName)) ;

   if (pCounterGroup == pReport->CurrentItem.pCounter)
      {
      ReportCounterRect (pReport, pCounterGroup, &Rect) ;
      DrawFocusRect (hDC, &Rect) ;
      }

   }  // DrawCounter

void DrawObject (HDC hDC, PREPORT pReport,
                 POBJECTGROUP pObjectGroup)
   {  // DrawObject
   TCHAR          szLine [LongTextLen] ;
   PCOUNTERGROUP  pCounterGroup ;
   PCOLUMNGROUP   pColumnGroup ;

   if (!pObjectGroup->pCounterGroupFirst)
      {
      return ;
      }

   SelectFont (hDC, pReport->hFontHeaders) ;

   SetTextAlign (hDC, TA_RIGHT) ;

   for (pColumnGroup = pObjectGroup->pColumnGroupFirst ;
        pColumnGroup ;
        pColumnGroup = pColumnGroup->pColumnGroupNext)
      {  // for
      // Draw Parent
      if (pColumnGroup->lpszParentName)
         TextOut (hDC,
                  ValueMargin (pReport) +
                  pColumnGroup->xPos + pColumnGroup->xWidth,
                  pObjectGroup->yFirstLine - pReport->yLineHeight,
                  pColumnGroup->lpszParentName,
                  lstrlen (pColumnGroup->lpszParentName)) ;

      // Draw Instance
      if (pColumnGroup->lpszInstanceName)
         {
         TextOut (hDC,
                  ValueMargin (pReport) +
                  pColumnGroup->xPos + pColumnGroup->xWidth,
                  pObjectGroup->yFirstLine,
                  pColumnGroup->lpszInstanceName,
                  lstrlen (pColumnGroup->lpszInstanceName)) ;
         }

      if (pColumnGroup == pReport->CurrentItem.pColumn)
         {
         RECT  Rect ;

         ReportColumnRect (pReport, pColumnGroup, &Rect) ;
         DrawFocusRect (hDC, &Rect) ;
         }

      }  // for
   SetTextAlign (hDC, TA_LEFT) ;

   TSPRINTF (szLine, szObjectFormat, pObjectGroup->lpszObjectName) ;
   TextOut (hDC,
            xObjectMargin, pObjectGroup->yFirstLine,
            szLine, lstrlen (szLine)) ;

   if (pObjectGroup == pReport->CurrentItem.pObject)
      {
      RECT  Rect ;

      ReportObjectRect (pReport, pObjectGroup, &Rect) ;
      DrawFocusRect (hDC, &Rect) ;
      }

   SelectFont (hDC, pReport->hFont) ;

   for (pCounterGroup = pObjectGroup->pCounterGroupFirst ;
        pCounterGroup ;
        pCounterGroup = pCounterGroup->pCounterGroupNext)
      {  // for
      DrawCounter (hDC, pReport, pCounterGroup) ;
      }  // for

   }  // DrawObject

void DrawSystem (HDC hDC, PREPORT pReport, PSYSTEMGROUP pSystemGroup)
   {  // DrawSystem
   TCHAR          szLine [LongTextLen] ;
   POBJECTGROUP   pObjectGroup ;

   SelectFont (hDC, pReport->hFontHeaders) ;

   if (!pSystemGroup->pObjectGroupFirst)
      return ;

   SetTextAlign (hDC, TA_LEFT) ;
   TSPRINTF (szLine, szSystemFormat, pSystemGroup->lpszSystemName) ;
   TextOut (hDC,
            xSystemMargin, pSystemGroup->yFirstLine,
            szLine, lstrlen (szLine)) ;

   if (pSystemGroup == pReport->CurrentItem.pSystem)
      {
      RECT  Rect ;

      ReportSystemRect (pReport, pSystemGroup, &Rect) ;
      DrawFocusRect (hDC, &Rect) ;
      }

   for (pObjectGroup = pSystemGroup->pObjectGroupFirst ;
        pObjectGroup ;
        pObjectGroup = pObjectGroup->pObjectGroupNext)
      {  // for
      DrawObject (hDC, pReport, pObjectGroup) ;
      }  // for
   }  // DrawSystem

void DrawReportValue (HDC hDC, PREPORT pReport, PLINE pLine)
   {  // DrawReportValue
   TCHAR          szValue [20] ;
   FLOAT          eValue ;
   RECT           rectValue ;

   // skip until we have collect enough samples for the first data
   if (pLine->bFirstTime == 0)
      {
      eValue = CounterEntry (pLine) ;
      if (pLine->lnCounterType == PERF_COUNTER_RAWCOUNT_HEX ||
          pLine->lnCounterType == PERF_COUNTER_LARGE_RAWCOUNT_HEX)
         {
         DWORD LowValue  ;
         DWORD HighValue ;

         LowValue  = (DWORD) (pLine->lnaCounterValue[0]) ;
         HighValue = (DWORD) (pLine->lnaCounterValue[0] >> 32);

         if (pLine->lnCounterType == PERF_COUNTER_RAWCOUNT_HEX ||
             HighValue == 0)
            {
            TSPRINTF (szValue,
                      szHexFormat,
                      LowValue) ;
            }
         else
            {

            TSPRINTF (szValue,
                      szLargeHexFormat,
                      HighValue,
                      LowValue) ;
            }
         }
      else
         {
         TSPRINTF (szValue,
                   (eValue > eStatusLargeValueMax) ?
                       szLargeValueFormat : szValueFormat,
                   eValue) ;
         ConvertDecimalPoint (szValue) ;
         }
      }
   else
      {
      // draw "- - - -"
      lstrcpy(szValue, DashLine);
      }

   ReportLineValueRect (pReport, pLine, &rectValue) ;

   ExtTextOut (hDC,
               rectValue.right - 2, rectValue.top,
               ETO_CLIPPED | ETO_OPAQUE,
               &rectValue,
               szValue, lstrlen (szValue), NULL) ;

   if (pReport->CurrentItemType == REPORT_TYPE_LINE &&
       pLine == pReport->CurrentItem.pLine)
      {
      DrawFocusRect (hDC, &rectValue) ;
      }
   }  // DrawReportValue

void DrawReportValues (HDC hDC, PREPORT pReport)
   {
   PSYSTEMGROUP   pSystemGroup ;
   POBJECTGROUP   pObjectGroup ;
   PCOUNTERGROUP  pCounterGroup ;
   PLINE          pLine ;

   SelectFont (hDC, pReport->hFont) ;
   SetTextAlign (hDC, TA_RIGHT) ;

   for (pSystemGroup = pReport->pSystemGroupFirst ;
        pSystemGroup ;
        pSystemGroup = pSystemGroup->pSystemGroupNext)
      {  // for System...
      for (pObjectGroup = pSystemGroup->pObjectGroupFirst ;
           pObjectGroup ;
           pObjectGroup = pObjectGroup->pObjectGroupNext)
         {  // for Object...
         for (pCounterGroup = pObjectGroup->pCounterGroupFirst ;
              pCounterGroup ;
              pCounterGroup = pCounterGroup->pCounterGroupNext)
            {  // for Counter...
            for (pLine = pCounterGroup->pLineFirst ;
                 pLine ;
                 pLine = pLine->pLineCounterNext)
               {  // for Line...
               DrawReportValue (hDC, pReport, pLine) ;
               }  // for Line...
            }  // for Counter...
         }  // for Object
      }  // for System...

   }

void DrawReportHeaders (HDC hDC, PREPORT pReport)
   {  // DrawReportHeaders
   PSYSTEMGROUP   pSystemGroup ;

   for (pSystemGroup = pReport->pSystemGroupFirst ;
        pSystemGroup ;
        pSystemGroup = pSystemGroup->pSystemGroupNext)
      {  // for
      DrawSystem (hDC, pReport, pSystemGroup) ;
      }  // for
   }  // DrawReportHeaders

void DrawReport (HDC hDC, PREPORT pReport)
   {
   SetBkColor (hDC, GetSysColor(COLOR_WINDOW)) ;
   DrawReportHeaders (hDC, pReport) ;
   //UpdateLines (&(pReport->pSystemFirst), pReport->pLineFirst) ;
   DrawReportValues (hDC, pReport) ;
   }

void SetLinePosition (HDC hDC,
                      PREPORT pReport,
                      POBJECTGROUP pObjectGroup,
                      PLINE pLine)
   {  // SetLinePositions
   PCOLUMNGROUP   pColumnGroup ;

   pColumnGroup = GetColumnGroup (pReport, pObjectGroup, pLine) ;
   if (!pColumnGroup)
      {
      pLine->xReportPos = 0 ;
      pLine->iReportColumn = -1 ;
      }
   else
      {
      pLine->xReportPos = pColumnGroup->xPos ;
      pLine->iReportColumn = pColumnGroup->ColumnNumber ;
      }
   }  // SetLinePosition

void SetCounterPositions (HDC hDC,
                          PREPORT pReport,
                          POBJECTGROUP pObjectGroup,
                          PCOUNTERGROUP pCounterGroup,
                          int yLine)
   {  // SetCounterPositions
   PLINE          pLine ;
   int            yPos ;

   if (!pCounterGroup->pLineFirst)
      return ;

   yPos = pCounterGroup->yLine ;

   SelectFont (hDC, pReport->hFontHeaders) ;

   for (pLine = pCounterGroup->pLineFirst ;
        pLine ;
        pLine = pLine->pLineCounterNext)
      {  // for
      SetLinePosition (hDC, pReport, pObjectGroup, pLine) ;
      pLine->yReportPos = yPos ;
      }  // for
   }  // SetCounterPositions

void SetColumnPositions (HDC hDC,
                         PREPORT pReport,
                         POBJECTGROUP pObjectGroup)
   {  // SetColumnPositions
   int            xPos ;
   PCOLUMNGROUP   pColumnGroup ;

   xPos = 0 ;
   for (pColumnGroup = pObjectGroup->pColumnGroupFirst ;
        pColumnGroup ;
        pColumnGroup = pColumnGroup->pColumnGroupNext)
      {  // for
      pColumnGroup->xWidth = max (max (pColumnGroup->ParentNameTextWidth,
                                       pColumnGroup->InstanceNameTextWidth),
                                  pReport->xValueWidth) ;
      pColumnGroup->xPos = xPos ;
      pColumnGroup->yFirstLine = pObjectGroup->yFirstLine ;
      xPos += (pColumnGroup->xWidth + xColumnMargin) ;
      }  // for
   }  // SetColumnPositions

void SetObjectPositions (HDC hDC,
                         PREPORT pReport,
                         POBJECTGROUP pObjectGroup,
                         int yLine)
/*
   Effect:        Determine and set the logical coordinates for the
                  object pObject within the report pReport.

                  For each instance x counter, determine the appropriate
                  column, adding a column description to the object if
                  needed.

   Called By:     SetSystemPositions only.

   See Also:      SetSystemPositions, SetCounterPositions, ColumnGroup.
*/
   {  // SetObjectPositions
   PCOUNTERGROUP  pCounterGroup ;
   int            yPos ;
   PLINE          pLine ;

   // check if there is parnet name for this object type
   // if so, need to add extra space for the parent name
   if (pObjectGroup->pCounterGroupFirst)
      {
      pCounterGroup = pObjectGroup->pCounterGroupFirst ;
      pLine = pCounterGroup->pLineFirst ;
      if (pLine && LineParentName(pLine))
         {
         pObjectGroup->yFirstLine += yLine ;
         }
      }

   SetColumnPositions (hDC, pReport, pObjectGroup) ;

   yPos = pObjectGroup->yFirstLine + yLine ;

   for (pCounterGroup = pObjectGroup->pCounterGroupFirst ;
        pCounterGroup ;
        pCounterGroup = pCounterGroup->pCounterGroupNext)
      {  // for
      pCounterGroup->yLine = yPos + yLine ;

      SetCounterPositions (hDC, pReport, pObjectGroup, pCounterGroup, yLine) ;

      yPos = pCounterGroup->yLine ;
      }  // for

   pObjectGroup->yLastLine = yPos + yLine ;
   }  // SetObjectPositions

void SetSystemPositions (HDC hDC,
                         PREPORT pReport,
                         PSYSTEMGROUP pSystemGroup,
                         int yLine)
   {  // SetSystemPositions
   POBJECTGROUP   pObjectGroup ;
   int            yPos ;

   yPos = pSystemGroup->yFirstLine ;

   for (pObjectGroup = pSystemGroup->pObjectGroupFirst ;
        pObjectGroup ;
        pObjectGroup = pObjectGroup->pObjectGroupNext)
      {  // for
      pObjectGroup->yFirstLine = yPos + yLine ;

      SetObjectPositions (hDC, pReport, pObjectGroup, yLine) ;

      yPos = pObjectGroup->yLastLine ;
      }  // for

   pSystemGroup->yLastLine = yPos + yLine ;
   }  // SetSystemPositions

void static SetScrollRanges (HWND hWnd)
   {  // SetScrollRanges
   PREPORT        pReport ;
   RECT           rectClient ;
   int            xWidth, yHeight ;

   GetClientRect (hWnd, &rectClient) ;
   xWidth = rectClient.right - rectClient.left ;
   yHeight = rectClient.bottom - rectClient.top ;

   pReport = ReportData (hWnd) ;

   SetScrollRange (hWnd, SB_VERT,
                   0, max (0, pReport->yHeight - yHeight),
                   TRUE) ;
   SetScrollRange (hWnd, SB_HORZ,
                   0, max (0, pReport->xWidth - xWidth),
                   TRUE) ;
   }  // SetScrollRanges

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnCreate (HWND hWnd)
   {  // OnCreate
   HDC            hDC ;
   PREPORT        pReport ;

   pReport = AllocateReportData (hWnd) ;
   if (!pReport)
      return ;

   pReport->hFont = hFontScales ;
   pReport->hFontHeaders = hFontScalesBold ;

   pReport->pLineFirst = NULL ;
   pReport->pSystemFirst = NULL ;

   pReport->pSystemGroupFirst = NULL ;

   hDC = GetDC (hWnd) ;

   SelectFont (hDC, pReport->hFont) ;

   pReport->yLineHeight = FontHeight (hDC, TRUE) ;
   pReport->xValueWidth = TextWidth (hDC, szValuePlaceholder) ;
   ReleaseDC (hWnd, hDC) ;

   pReport->xWidth = 0 ;
   pReport->yHeight = 0 ;

   StringLoad (IDS_SYSTEMFORMAT, szSystemFormat) ;
   StringLoad (IDS_OBJECTFORMAT, szObjectFormat) ;
   }  // OnCreate

void static OnPaint (HWND hWnd)
   {
   HDC            hDC ;
   PAINTSTRUCT    ps ;
   PREPORT        pReport ;

   pReport = ReportData (hWnd) ;

   hDC = BeginPaint (hWnd, &ps) ;
   //hDC = hReportDC ;
   SetWindowOrgEx (hDC,
                   GetScrollPos (hWnd, SB_HORZ),
                   GetScrollPos (hWnd, SB_VERT),
                   NULL) ;

   DrawReport (hDC, pReport) ;

   EndPaint (hWnd, &ps) ;
   }

void static UpdateReportValues (PREPORT pReport)
/*
   Effect:        Redraw all the visible report values of pReport.
                  Since drawing the values completely covers any
                  previous values, there is no need to erase (or flicker)
                  between updating values.

   Called By:     ReportTimer, OnVScroll, OnHScroll.
*/
   {  // UpdateReportValues
   HDC            hDC ;

   hDC = GetDC (pReport->hWnd) ;

   SetBkColor (hDC, GetSysColor(COLOR_WINDOW)) ;
   SetWindowOrgEx (hDC,
                   GetScrollPos (pReport->hWnd, SB_HORZ),
                   GetScrollPos (pReport->hWnd, SB_VERT),
                   NULL) ;

   DrawReportValues (hDC, pReport) ;
   ReleaseDC (pReport->hWnd, hDC) ;

   }  // UpdateReportValues

void static OnHScroll (HWND hWnd,
                       int iScrollCode,
                       int iScrollNewPos)
   {  // OnHScroll
   PREPORT        pReport ;
   int            iScrollAmt, iScrollPos, iScrollRange ;
   int            iScrollLo ;
   RECT           rectClient ;
   int            xWidth ;

   pReport = ReportData (hWnd) ;

   GetClientRect (hWnd, &rectClient) ;
   xWidth = rectClient.right - rectClient.left ;

   if (pReport->xWidth <= xWidth)
      {
      // no horz scroll bar, forget it
      return ;
      }

   iScrollPos = GetScrollPos (hWnd, SB_HORZ) ;

   GetScrollRange (hWnd, SB_HORZ, &iScrollLo, &iScrollRange) ;

   switch (iScrollCode)
      {
      case SB_LINEUP:
           iScrollAmt = - Report.yLineHeight ;
           break ;

      case SB_LINEDOWN:
           iScrollAmt = Report.yLineHeight ;
           break ;

      case SB_PAGEUP:
           iScrollAmt = - (rectClient.right - rectClient.left) / 2 ;
           break ;

      case SB_PAGEDOWN:
           iScrollAmt = (rectClient.right - rectClient.left) / 2 ;
           break ;

      case SB_THUMBPOSITION:
           iScrollAmt = iScrollNewPos - iScrollPos ;
           break ;

      default:
           iScrollAmt = 0 ;
      }  // switch

     iScrollAmt = PinInclusive (iScrollAmt,
                                -iScrollPos,
                                iScrollRange - iScrollPos) ;
     if (iScrollAmt)
        {
        iScrollPos += iScrollAmt ;
        ScrollWindow (hWnd, -iScrollAmt, 0, NULL, NULL) ;
        SetScrollPos (hWnd, SB_HORZ, iScrollPos, TRUE) ;
        UpdateWindow (hWnd) ;
#if 0
        UpdateReportValues (pReport) ;
#endif
        }
   }  // OnHScroll

void static OnVScroll (HWND hWnd,
                       int iScrollCode,
                       int iScrollNewPos)
   {  // OnVScroll
   PREPORT        pReport ;
   int            iScrollAmt, iScrollPos, iScrollRange ;
   int            iScrollLo ;
   RECT           rectClient ;

   pReport = ReportData (hWnd) ;

   iScrollPos = GetScrollPos (hWnd, SB_VERT) ;
   GetScrollRange (hWnd, SB_VERT, &iScrollLo, &iScrollRange) ;

   GetClientRect (hWnd, &rectClient) ;

   switch (iScrollCode)
      {
      case SB_LINEUP:
           iScrollAmt = - Report.yLineHeight ;
           break ;

      case SB_LINEDOWN:
           iScrollAmt = Report.yLineHeight ;
           break ;

      case SB_PAGEUP:
           iScrollAmt = - (rectClient.bottom - rectClient.top) / 2 ;
           break ;

      case SB_PAGEDOWN:
           iScrollAmt = (rectClient.bottom - rectClient.top) / 2 ;
           break ;

      case SB_THUMBPOSITION:
           iScrollAmt = iScrollNewPos - iScrollPos ;
           break ;

      default:
           iScrollAmt = 0 ;
      }  // switch

     iScrollAmt = PinInclusive (iScrollAmt,
                                -iScrollPos,
                                iScrollRange - iScrollPos) ;
     if (iScrollAmt)
        {
        iScrollPos += iScrollAmt ;
        ScrollWindow (hWnd, 0, -iScrollAmt, NULL, NULL) ;
        SetScrollPos (hWnd, SB_VERT, iScrollPos, TRUE) ;

//        WindowInvalidate (hWnd) ;
        UpdateWindow (hWnd) ;
#if 0
        UpdateReportValues (pReport) ;
#endif
        }
   }  // OnVScroll

void static OnKeyDown (HWND hWnd, DWORD wParam)
   {
   switch (wParam)
      {
      case VK_UP:
         OnVScroll (hWnd, SB_LINEUP, 0) ;
         break ;

      case VK_DOWN:
         OnVScroll (hWnd, SB_LINEDOWN, 0) ;
         break ;

      case VK_LEFT:
         OnHScroll (hWnd, SB_LINEUP, 0) ;
         break ;

      case VK_RIGHT:
         OnHScroll (hWnd, SB_LINEDOWN, 0) ;
         break ;

      case VK_PRIOR:
         OnVScroll (hWnd, SB_PAGEUP, 0) ;
         break ;

      case VK_NEXT:
         OnVScroll (hWnd, SB_PAGEDOWN, 0) ;
         break ;
      }
   }  // OnKeyDown

LRESULT APIENTRY ReportWndProc (HWND hWnd,
                               WORD wMsg,
                               DWORD wParam,
                               LONG lParam)
   {  // ReportWndProc
   BOOL           bCallDefProc ;
   LRESULT        lReturnValue ;

   bCallDefProc = FALSE ;
   lReturnValue = 0L ;

   switch (wMsg)
      {  // switch
      case WM_CREATE:
         OnCreate (hWnd) ;
         break ;

      case WM_LBUTTONDOWN:

         if (!OnReportLButtonDown (hWnd, LOWORD (lParam), HIWORD (lParam)))
            {
            // mouse click do not hit on any entries, see if we
            // need to drag Perfmon
            if (!(Options.bMenubar))
               {
               DoWindowDrag (hWnd, lParam) ;
               }
            }
         break ;

      case WM_LBUTTONDBLCLK:
         SendMessage (hWndMain, WM_LBUTTONDBLCLK, wParam, lParam) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_SIZE:
         SetScrollRanges (hWnd) ;
         break ;

      case WM_HSCROLL:
         OnHScroll (hWnd, LOWORD (wParam), HIWORD (wParam)) ;
         break ;

      case WM_VSCROLL:
         OnVScroll (hWnd, LOWORD (wParam), HIWORD (wParam)) ;
         break ;

      case WM_TIMER:
         ReportTimer (hWnd, FALSE) ;
         break ;

      case WM_KEYDOWN:
         OnKeyDown (hWnd, wParam) ;
         break ;

      case WM_DESTROY:
         KillTimer (hWnd, ReportTimerID) ;
         break ;

      default:
         bCallDefProc = TRUE ;
      }  // switch

   if (bCallDefProc)
      lReturnValue = DefWindowProc (hWnd, wMsg, wParam, lParam) ;

   return (lReturnValue);
   }  // ReportWndProc

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

#if 0
PREPORT ReportData (HWND hWndReport)
   {
   return (&Report) ;
   }
#endif

void SetReportTimer (PREPORT pReport)
   {
   if (pReport->iStatus == iPMStatusCollecting)
      KillTimer (pReport->hWnd, ReportTimerID) ;

   SetTimer (pReport->hWnd, ReportTimerID,
             pReport->iIntervalMSecs , NULL) ;
   pReport->iStatus = iPMStatusCollecting ;
   }

void ClearReportTimer (PREPORT pReport)
   {
   pReport->iStatus = iPMStatusClosed ;
   KillTimer (pReport->hWnd, ReportTimerID) ;
   }

BOOL ReportInitializeApplication (void)
   {  // ReportInitializeApplication
   BOOL           bSuccess ;
   WNDCLASS       wc ;

   //=============================//
   // Register ReportWindow class  //
   //=============================//

   wc.style         = dwReportClassStyle ;
   wc.lpfnWndProc   = (WNDPROC) ReportWndProc ;
   wc.hInstance     = hInstance ;
   wc.cbClsExtra    = iReportWindowExtra ;
   wc.cbWndExtra    = iReportClassExtra ;
   wc.hIcon         = NULL ;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW) ;
//   wc.hbrBackground = GetStockObject (WHITE_BRUSH) ;
   wc.hbrBackground =   (HBRUSH) (COLOR_WINDOW + 1) ;
   wc.lpszMenuName  = NULL ;
   wc.lpszClassName = szReportWindowClass ;

   bSuccess = RegisterClass (&wc) ;

   //=============================//
   // Register Child classes      //
   //=============================//

   return (bSuccess) ;
   }  // ReportInitializeApplication

HWND CreateReportWindow (HWND hWndParent)
/*
   Effect:        Create the graph window. This window is a child of
                  hWndMain and is a container for the graph data,
                  graph label, graph legend, and graph status windows.

   Note:          We dont worry about the size here, as this window
                  will be resized whenever the main window is resized.

*/
   {
   return (CreateWindow (szReportWindowClass,       // window class
                         NULL,                     // caption
                         dwReportWindowStyle,       // style for window
                         0, 0,                     // initial position
                         0, 0,                     // initial size
                         hWndParent,               // parent
                         NULL,                     // menu
                         hInstance,               // program instance
                         NULL)) ;                  // user-supplied data
   }  // CreateReportWindow

void SetReportPositions (HDC hDC,
                         PREPORT pReport)
   {
   PSYSTEMGROUP   pSystemGroup ;
   int            yLine ;
   int            yPos ;

//   pReport->xMaxCounterWidth = 0 ;

   yLine = pReport->yLineHeight ;
   yPos = 2 * yLine ;

   for (pSystemGroup = pReport->pSystemGroupFirst ;
        pSystemGroup ;
        pSystemGroup = pSystemGroup->pSystemGroupNext)
      {  // for
      pSystemGroup->yFirstLine = yPos + yLine ;

      SetSystemPositions (hDC, pReport, pSystemGroup, yLine) ;

      yPos = pSystemGroup->yLastLine ;
      }  // for

   pReport->yHeight = yPos ;

   SetScrollRanges (pReport->hWnd) ;
   }  // SetReportPositions

void PlaybackReport (HWND hWndReport)
   {  // PlaybackReport
   PREPORT        pReport ;

   pReport = ReportData (hWndReport) ;

   PlaybackLines (pReport->pSystemFirst,
                  pReport->pLineFirst,
                  PlaybackLog.StartIndexPos.iPosition) ;
   PlaybackLines (pReport->pSystemFirst,
                  pReport->pLineFirst,
                  PlaybackLog.StopIndexPos.iPosition) ;
   }  // PlaybackReport

BOOL CurrentReportItem (HWND hWndReport)
   {  // CurrentReportItem
   PREPORT        pReport ;

   pReport = ReportData (hWndReport) ;
   if (!pReport)
      return (FALSE) ;

   return (pReport->CurrentItemType != REPORT_TYPE_NOTHING) ;
   }  // CurrentReportItem

BOOL AddReport (HWND hWndParent)
   {
   PREPORT        pReport ;
   LPTSTR         pCurrentSystem ;
   POBJECTGROUP  pParentObject ;

   pReport = ReportData (hWndReport) ;

   if (pReport->CurrentItemType == REPORT_TYPE_LINE)
      {
      pCurrentSystem = pReport->CurrentItem.pLine->lnSystemName ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_SYSTEM)
      {
      pCurrentSystem = pReport->CurrentItem.pSystem->lpszSystemName ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_OBJECT)
      {
      pCurrentSystem = pReport->CurrentItem.pObject->pParentSystem->lpszSystemName ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_COLUMN)
      {
      pParentObject =  pReport->CurrentItem.pColumn->pParentObject ;
      pCurrentSystem =  pParentObject->pParentSystem->lpszSystemName ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_COUNTER)
      {
      pParentObject =  pReport->CurrentItem.pCounter->pParentObject ;
      pCurrentSystem =  pParentObject->pParentSystem->lpszSystemName ;
      }
   else
      {
      pCurrentSystem = NULL ;
      }

   return (AddLine (hWndParent,
                    &(pReport->pSystemFirst),
                    &(pReport->Visual),
                    pCurrentSystem,
                    LineTypeReport)) ;
   }

BOOL ToggleReportRefresh (HWND hWnd)
   {  // ToggleReportRefresh
   PREPORT        pReport ;

   pReport = ReportData (hWnd) ;

   if (pReport->bManualRefresh)
      SetReportTimer (pReport) ;
   else
      ClearReportTimer (pReport) ;

   pReport->bManualRefresh = !pReport->bManualRefresh ;
   return (pReport->bManualRefresh) ;
   }  // ToggleReportRefresh

BOOL ReportRefresh (HWND hWnd)
   {  // ReportRefresh
   PREPORT        pReport ;

   pReport = ReportData (hWnd) ;

   return (pReport->bManualRefresh) ;
   }  // ReportRefresh

void ReportTimer (HWND hWnd, BOOL bForce)
   {
   PREPORT        pReport ;

   pReport = ReportData (hWnd) ;

   if (PlayingBackLog () || !pReport)
      {
      return;
      }

   if (bForce || !pReport->bManualRefresh)
      {  // if
      UpdateLines (&(pReport->pSystemFirst), pReport->pLineFirst) ;
      if (iPerfmonView == IDM_VIEWREPORT && !bPerfmonIconic)
         {
         // only need to draw the data when we are viewing it...
         UpdateReportValues (pReport) ;
         }
      }  // if
   }  // ReportTimer

BOOL SaveReport (HWND hWndReport, HANDLE hInputFile, BOOL bGetFileName)
   {
   PREPORT        pReport ;
   PLINE          pLine ;
   HANDLE         hFile ;
   DISKREPORT     DiskReport ;
   PERFFILEHEADER FileHeader ;
   TCHAR          szFileName [256] ;
   BOOL           newFileName = FALSE ;

   pReport = ReportData (hWndReport) ;
   if (!pReport)
      {
      return (FALSE) ;
      }

   if (hInputFile)
      {
      // use the input file handle if it is available
      // this is the case for saving workspace data
      hFile = hInputFile ;
      }
   else
      {
      if (pReportFullFileName)
         {
         lstrcpy (szFileName, pReportFullFileName) ;
         }
      if (bGetFileName || pReportFullFileName == NULL)
         {
         if (!FileGetName (hWndReport, IDS_REPORTFILE, szFileName))
            {
            return (FALSE) ;
            }
         newFileName = TRUE ;
         }

      hFile = FileHandleCreate (szFileName) ;

      if (hFile && newFileName)
         {
         ChangeSaveFileName (szFileName, IDM_VIEWREPORT) ;
         }
      else if (!hFile)
         {
         DlgErrorBox (hWndReport, ERR_CANT_OPEN, szFileName) ;
         }
      }

   if (!hFile)
      return (FALSE) ;

   if (!hInputFile)
      {
      memset (&FileHeader, 0, sizeof (FileHeader)) ;
      lstrcpy (FileHeader.szSignature, szPerfReportSignature) ;
      FileHeader.dwMajorVersion = ReportMajorVersion ;
      FileHeader.dwMinorVersion = ReportMinorVersion ;

      if (!FileWrite (hFile, &FileHeader, sizeof (PERFFILEHEADER)))
         {
         goto Exit0 ;
         }
      }

   DiskReport.Visual = pReport->Visual ;
   DiskReport.bManualRefresh = pReport->bManualRefresh ;
   DiskReport.dwIntervalSecs = pReport->iIntervalMSecs ;
   DiskReport.dwNumLines = NumLines (pReport->pLineFirst) ;
   DiskReport.perfmonOptions = Options ;

   if (!FileWrite (hFile, &DiskReport, sizeof (DISKREPORT)))
      {
      goto Exit0 ;
      }

   for (pLine = pReport->pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for
      if (!WriteLine (pLine, hFile))
         {
         goto Exit0 ;
         }
      }  // for

   if (!hInputFile)
      {
      CloseHandle (hFile) ;
      }

   return (TRUE) ;

Exit0:
   if (!hInputFile)
      {
      CloseHandle (hFile) ;

      // only need to report error if not workspace
      DlgErrorBox (hWndReport, ERR_SETTING_FILE, szFileName) ;
      }
   return (FALSE) ;
   }  // SaveReport

void ReportAddAction (PREPORT pReport)
   {
   HDC      hDC ;

   //=============================//
   // Calculate report positions  //
   //=============================//

   hDC = GetDC (hWndReport) ;
   SetReportPositions (hDC, pReport) ;
   ReleaseDC (hWndReport, hDC) ;

   if (PlayingBackLog ())
      {
      PlaybackReport (hWndReport) ;
      }
   else if (pReport->iStatus == iPMStatusClosed)
      {
      SetReportTimer (pReport) ;
      }

   WindowInvalidate (hWndReport) ;
   }

BOOL OpenReportVer1 (HANDLE hFile,
                     DISKREPORT *pDiskReport,
                     PREPORT pReport,
                     DWORD dwMinorVersion)
   {
   HDC   hDC ;

   pReport->Visual = pDiskReport->Visual ;
   pReport->iIntervalMSecs = pDiskReport->dwIntervalSecs ;
   if (dwMinorVersion < 3)
      {
      // convert this to msec
      pReport->iIntervalMSecs *= 1000 ;
      }
   pReport->bManualRefresh = pDiskReport->bManualRefresh ;

   bDelayAddAction = TRUE ;
   ReadLines (hFile, pDiskReport->dwNumLines,
             &(pReport->pSystemFirst), &(pReport->pLineFirst), IDM_VIEWREPORT) ;

   if (pReport->pLineFirst)
      {
      // set focus on the first line
      pReport->CurrentItem.pLine = pReport->pLineFirst ;
      pReport->CurrentItemType = REPORT_TYPE_LINE ;
      }
   bDelayAddAction = FALSE ;

   //=============================//
   // Calculate report positions  //
   //=============================//

   hDC = GetDC (hWndReport) ;
   SetReportPositions (hDC, pReport) ;
   ReleaseDC (hWndReport, hDC) ;

   if (PlayingBackLog ())

      {
      PlaybackReport (hWndReport) ;
      }
   else if (pReport->iStatus == iPMStatusClosed)
      {
      SetReportTimer (pReport) ;
      }

   WindowInvalidate (hWndReport) ;

   return (TRUE) ;
   }  // OpenReportVer1

BOOL OpenReport (HWND hWndReport,
                 HANDLE hFile,
                 DWORD dwMajorVersion,
                 DWORD dwMinorVersion,
                 BOOL bReportFile)
   {
   PREPORT        pReport ;
   DISKREPORT     DiskReport ;
   BOOL           bSuccess = TRUE ;

   pReport = ReportData (hWndReport) ;
   if (!pReport)
      {
      bSuccess = FALSE ;
      goto Exit0 ;
      }

   if (!FileRead (hFile, &DiskReport, sizeof (DISKREPORT)))
      {
      bSuccess = FALSE ;
      goto Exit0 ;
      }

   switch (dwMajorVersion)
      {
      case (1):

         SetHourglassCursor() ;

         ResetReportView (hWndReport) ;

         OpenReportVer1 (hFile, &DiskReport, pReport, dwMinorVersion) ;

         // change to report view if we are opening a
         // report file
         if (bReportFile && iPerfmonView != IDM_VIEWREPORT)
            {
            SendMessage (hWndMain, WM_COMMAND, (LONG)IDM_VIEWREPORT, 0L) ;
            }

         if (iPerfmonView == IDM_VIEWREPORT)
            {
            SetPerfmonOptions (&DiskReport.perfmonOptions) ;
            }

         SetArrowCursor() ;

         break ;
      }  // switch

Exit0:

   if (bReportFile)
      {
      CloseHandle (hFile) ;
      }

   return (bSuccess) ;
   }  // OpenReport

void ResetReportView (HWND hWndReport)
   {  // ResetReportView
   PREPORT        pReport ;

   pReport = ReportData (hWndReport) ;

   if (!pReport)
      {
      return ;
      }

   ChangeSaveFileName (NULL, IDM_VIEWREPORT) ;

   if (pReport->pSystemGroupFirst)
      {
      ResetReport (hWndReport) ;
      }
   }  // ResetReportView

void ResetReport (HWND hWndReport)
   {  // ResetReport
   PREPORT        pReport ;
   PSYSTEMGROUP   pSystemGroup, pSystemGroupDelete ;
   POBJECTGROUP   pObjectGroup, pObjectGroupDelete ;
   PCOUNTERGROUP  pCounterGroup, pCounterGroupDelete ;
   HDC            hDC ;

   pReport = ReportData (hWndReport) ;
   if (!pReport)
      return ;

   ClearReportTimer (pReport) ;

   pSystemGroup = pReport->pSystemGroupFirst ;
   while (pSystemGroup)
      {
      pObjectGroup = pSystemGroup->pObjectGroupFirst ;
      while (pObjectGroup)
         {
         pCounterGroup = pObjectGroup->pCounterGroupFirst ;
         while (pCounterGroup)
            {
            pCounterGroupDelete = pCounterGroup ;
            pCounterGroup = pCounterGroup->pCounterGroupNext ;
            MemoryFree (pCounterGroupDelete) ;
            }  // while pCounter...

         pObjectGroupDelete = pObjectGroup ;
         pObjectGroup = pObjectGroup->pObjectGroupNext ;
         ColumnGroupRemove (pObjectGroupDelete->pColumnGroupFirst) ;
         MemoryFree (pObjectGroupDelete->lpszObjectName) ;
         MemoryFree (pObjectGroupDelete) ;
         }  // while pObject

      pSystemGroupDelete = pSystemGroup ;
      pSystemGroup = pSystemGroup->pSystemGroupNext ;
      MemoryFree (pSystemGroupDelete->lpszSystemName) ;
      MemoryFree (pSystemGroupDelete) ;
      }  // while pSystem...

   FreeLines (pReport->pLineFirst) ;
   pReport->pLineFirst = NULL ;

   FreeSystems (pReport->pSystemFirst) ;
   pReport->pSystemFirst = NULL ;

   pReport->pSystemGroupFirst = NULL ;
   pReport->CurrentItemType   = REPORT_TYPE_NOTHING ;
   pReport->CurrentItem.pLine = NULL ;

   // reset scrolling ranges
   pReport->xWidth = 0 ;
   pReport->yHeight = 0 ;
   pReport->xMaxCounterWidth = 0 ;
   hDC = GetDC (hWndReport) ;
   SetReportPositions (hDC, pReport) ;

   SelectFont (hDC, pReport->hFont) ;
   pReport->xValueWidth = TextWidth (hDC, szValuePlaceholder) ;

   ReleaseDC (hWndReport, hDC) ;

   WindowInvalidate (hWndReport) ;
   }  // ResetReport

void ClearReportDisplay (HWND hWndReport)
   {  // ResetReport
   PREPORT        pReport ;
   PLINE          pLine;

   if (PlayingBackLog())
      {
      return ;
      }

   pReport = ReportData (hWndReport) ;
   if (!pReport || !pReport->pLineFirst)
      return ;

   for (pLine = pReport->pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {
      // reset the new data counts
      pLine->bFirstTime = 2 ;
      }

   // re-draw the values
   UpdateReportValues (pReport) ;

   }  // ClearReportDisplay

//=========================================
// we don't print. we just export
//
// if need printing, define KEEP_PRINT
//=========================================
#ifdef KEEP_PRINT
BOOL PrintReportDisplay (HDC hDC,
                         PREPORT pReport)
   {
   SetReportPositions (hDC, pReport) ;
   DrawReport (hDC, pReport) ;
   return TRUE ;
   }  // PrintReportDisplay

BOOL PrintReport (HWND hWndParent,
                  HWND hWndReport)
   {
   PREPORT        pReport ;
   HDC            hDC ;
   int            xPageWidth ;
   int            yPageHeight ;
   int            xValueWidth ;

   HFONT          hFont, hFontHeaders ;
   int            yLineHeight ;

   pReport = ReportData (hWndReport) ;
   if (!pReport)
      return (FALSE) ;

   hDC = PrintDC () ;
   if (!hDC)
      {
      PostError () ;
      return (FALSE) ;
      }

   xPageWidth = GetDeviceCaps (hDC, HORZRES) ;
   yPageHeight = GetDeviceCaps (hDC, VERTRES) ;

   StartJob (hDC, TEXT("Performance Monitor Report")) ;
   StartPage (hDC) ;

   hFont = pReport->hFont ;
   hFontHeaders = pReport->hFontHeaders ;
   yLineHeight = pReport->yLineHeight ;
   xValueWidth = pReport->xValueWidth ;

   pReport->hFont = hFontPrinterScales ;
   pReport->hFontHeaders = hFontPrinterScalesBold ;

   SelectFont (hDC, pReport->hFont) ;
   pReport->yLineHeight = FontHeight (hDC, TRUE) ;

   pReport->xValueWidth = TextWidth (hDC, szValuePlaceholder) ;

   PrintReportDisplay (hDC, pReport) ;

   EndPage (hDC) ;
   EndJob (hDC) ;

   DeleteDC (hDC) ;

   pReport->hFont = hFont ;
   pReport->hFontHeaders = hFontHeaders ;
   pReport->yLineHeight = yLineHeight ;

   pReport->xValueWidth = xValueWidth ;

   hDC = GetDC (hWndReport) ;
   SetReportPositions (hDC, pReport) ;
   ReleaseDC (hWndReport, hDC) ;

   return (FALSE) ;
   }  // PrintReport

// we don't print. we just export
#endif

BOOL ReportInsertLine (HWND hWnd, PLINE pLine)
/*
   Effect:        Insert the line pLine into the data structures for the
                  Report of window hWnd. The line is added to the list of
                  lines, and also added to the report structure in the
                  appropriate System, Object, and Counter.

   Returns:       Whether the function was successful. If this function
                  returns FALSE, the line was not added.
*/
   {  // ReportInsertLine
   HDC            hDC ;
   PREPORT        pReport ;
   PSYSTEMGROUP   pSystemGroup ;
   POBJECTGROUP   pObjectGroup ;
   PCOUNTERGROUP  pCounterGroup ;
   PLINE          pLineEquivalent ;
   int            OldCounterWidth ;
   BOOL           bNewCounterGroup ;

   pReport = ReportData (hWnd) ;
   pReport->bModified = TRUE ;

   pLineEquivalent = FindEquivalentLine (pLine, pReport->pLineFirst) ;
   if (pLineEquivalent)
      {
      return (FALSE) ;
      }
   else
      {
      //=============================//
      // Add line, line's system     //
      //=============================//

      LineAppend (&pReport->pLineFirst, pLine) ;
      SystemAdd (&pReport->pSystemFirst, pLine->lnSystemName) ;

      //=============================//
      // Find correct spot; add line //
      //=============================//

      pSystemGroup = GetSystemGroup (pReport, pLine->lnSystemName) ;
      pObjectGroup = GetObjectGroup (pSystemGroup, pLine->lnObjectName) ;
      pCounterGroup = GetCounterGroup (pObjectGroup,
                                 pLine->lnCounterDef.CounterNameTitleIndex,
                                 &bNewCounterGroup,
                                 pLine->lnCounterName) ;

      if (!pCounterGroup)
         return (FALSE) ;

      LineCounterAppend (&pCounterGroup->pLineFirst, pLine) ;

      //=============================//
      // Calculate report positions  //
      //=============================//
      hDC = GetDC (hWnd) ;
      SelectFont (hDC, pReport->hFontHeaders) ;

      if (bNewCounterGroup)
         {
         // re-calc. the max. counter group width
         OldCounterWidth = pReport->xMaxCounterWidth ;
         pReport->xMaxCounterWidth =
            max (pReport->xMaxCounterWidth,
                 TextWidth (hDC, pLine->lnCounterName)) ;
         if (OldCounterWidth < pReport->xMaxCounterWidth)
            {
            // adjust the report width with the new counter width
            pReport->xWidth +=
               (pReport->xMaxCounterWidth - OldCounterWidth);
            }
         }

      if (pLine->lnCounterType == PERF_COUNTER_LARGE_RAWCOUNT_HEX)
         {
         SelectFont (hDC, pReport->hFont) ;
         pReport->xValueWidth = TextWidth (hDC, szValueLargeHexPlaceholder) ;
         }

      if (!bDelayAddAction)
         {
         SetReportPositions (hDC, pReport) ;
         }
      ReleaseDC (hWnd, hDC) ;

      pReport->CurrentItem.pLine = pLine ;
      pReport->CurrentItemType = REPORT_TYPE_LINE ;

      if (!bDelayAddAction)
         {
         if (PlayingBackLog ())
            {
            PlaybackReport (hWndReport) ;
            }
         else if (pReport->iStatus == iPMStatusClosed)
            {
            SetReportTimer (pReport) ;
            }

         WindowInvalidate (hWnd) ;
         }
      }  // else

      return (TRUE) ;
   }  // ReportInsertLine

BOOL ExportComputerName (HANDLE hFile, PSYSTEMGROUP pSystemGroup)
{
   int            StringLen ;
   BOOL           bWriteSuccess = TRUE ;

   CHAR           TempBuff [LongTextLen * 2] ;

   TCHAR          UnicodeBuff [LongTextLen] ;

   // export computer name
   strcpy (TempBuff, LineEndStr) ;
   strcat (TempBuff, LineEndStr) ;
   StringLen = strlen (TempBuff) ;
   TSPRINTF (UnicodeBuff, szSystemFormat, pSystemGroup->lpszSystemName) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      bWriteSuccess = FALSE ;
      }

   return (bWriteSuccess) ;
}  // ExportComputerName

#define WRITE_FILE_TIME 2
BOOL ExportObjectName (HANDLE hFile, POBJECTGROUP pObjectGroup, int *pColNum)
{
   int            StringLen ;
   BOOL           bNeedToExport ;
   BOOL           bWriteSuccess = TRUE ;
   PCOLUMNGROUP   pColumnGroup ;
   int            ParentNum, InstanceNum ;
   int            TimeToWrite ;

   CHAR           TempBuff [512 * 2] ;

   TCHAR          UnicodeBuff [512] ;

   ParentNum = InstanceNum = 0 ;

   if (pColNum)
      {
      *pColNum = 0 ;
      }

   // export object name
   strcpy (TempBuff, LineEndStr) ;
   StringLen = strlen (TempBuff) ;
   TSPRINTF (UnicodeBuff, szObjectFormat, pObjectGroup->lpszObjectName) ;
   ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
   strcat (TempBuff, LineEndStr) ;

   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   TimeToWrite = 0 ;

   // export column group
   if (pObjectGroup->pColumnGroupFirst)
      {
      strcpy (TempBuff, pDelimiter) ;
      strcat (TempBuff, pDelimiter) ;
      StringLen = strlen (TempBuff) ;

      bNeedToExport = FALSE ;

      // export Parent Names
      for (pColumnGroup = pObjectGroup->pColumnGroupFirst ;
           pColumnGroup ;
           pColumnGroup = pColumnGroup->pColumnGroupNext)
         {  // for

         if (pColumnGroup->lpszParentName)
            {
            ParentNum++ ;
            bNeedToExport = TRUE ;
            ConvertUnicodeStr (&TempBuff[StringLen],
                               pColumnGroup->lpszParentName) ;
            StringLen = strlen (TempBuff) ;
            }

         strcat (&TempBuff[StringLen], pDelimiter) ;
         StringLen = strlen (TempBuff) ;

         // check if we need to export this line before it is filled up
         TimeToWrite++ ;
         if (TimeToWrite > WRITE_FILE_TIME)
            {
            if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
               {
               goto Exit0 ;
               }
            TimeToWrite = 0 ;
            StringLen = 0 ;
            TempBuff[0] = TEXT('\0') ;
            }
         }

      // write the line delimiter
      strcpy (&TempBuff[StringLen], LineEndStr) ;
      if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
         {
         goto Exit0 ;
         }

      if (!bNeedToExport)
         {
         ParentNum = 0 ;
         }

      // setup to export Instances
      strcpy (TempBuff, pDelimiter) ;
      strcat (TempBuff, pDelimiter) ;
      StringLen = strlen (TempBuff) ;
      bNeedToExport = FALSE ;
      TimeToWrite = 0 ;

      // export Instance Names
      for (pColumnGroup = pObjectGroup->pColumnGroupFirst ;
           pColumnGroup ;
           pColumnGroup = pColumnGroup->pColumnGroupNext)
         {  // for

         if (pColumnGroup->lpszInstanceName)
            {
            InstanceNum++ ;
            bNeedToExport = TRUE ;
            ConvertUnicodeStr (&TempBuff[StringLen],
                               pColumnGroup->lpszInstanceName) ;
            StringLen = strlen (TempBuff) ;
            }

         strcat (&TempBuff[StringLen], pDelimiter) ;
         StringLen = strlen (TempBuff) ;

         // check if we need to export this line before it is filled up
         TimeToWrite++ ;
         if (TimeToWrite > WRITE_FILE_TIME)
            {
            if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
               {
               goto Exit0 ;
               }
            TimeToWrite = 0 ;
            StringLen = 0 ;
            TempBuff[0] = TEXT('\0') ;
            }
         }

      // write the line delimiter
      strcpy (&TempBuff[StringLen], LineEndStr) ;
      if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
         {
         goto Exit0 ;
         }

      if (!bNeedToExport)
         {
         InstanceNum = 0 ;
         }
      }

   if (pColNum)
      {
      *pColNum = max (ParentNum, InstanceNum) ;
      }

   return (TRUE) ;

Exit0:
   return (FALSE) ;

}  // ExportObjectName

BOOL ExportLineName (HANDLE hFile, PLINE pLine, int *pExportCounterName)
{
   FLOAT          eValue ;
   int            StringLen ;
   BOOL           bWriteSuccess = TRUE ;

   CHAR           TempBuff [LongTextLen * 2] ;

   TCHAR          UnicodeBuff [LongTextLen] ;

   strcpy (TempBuff, pDelimiter) ;

   if (*pExportCounterName)
      {
      StringLen = strlen (TempBuff) ;
      ConvertUnicodeStr (&TempBuff[StringLen], pLine->lnCounterName) ;
      strcat (TempBuff, pDelimiter) ;
      *pExportCounterName = FALSE ;
      }
   StringLen = strlen (TempBuff) ;

   if (pLine->bFirstTime == 0)
      {
      eValue = CounterEntry (pLine) ;

      if (pLine->lnCounterType == PERF_COUNTER_RAWCOUNT_HEX ||
          pLine->lnCounterType == PERF_COUNTER_LARGE_RAWCOUNT_HEX)
         {
         DWORD LowValue  ;
         DWORD HighValue ;

         LowValue  = (DWORD) (pLine->lnaCounterValue[0]) ;
         HighValue = (DWORD) (pLine->lnaCounterValue[0] >> 32);

         if (pLine->lnCounterType == PERF_COUNTER_RAWCOUNT_HEX ||
             HighValue == 0)
            {
            TSPRINTF (UnicodeBuff,
                      szHexFormat,
                      LowValue) ;
            }
         else
            {
            TSPRINTF (UnicodeBuff,
                      szLargeHexFormat,
                      HighValue,
                      LowValue) ;
            }
         }
      else
         {
         TSPRINTF (UnicodeBuff,
                   (eValue > eStatusLargeValueMax) ?
                       szLargeValueFormat : szValueFormat,
                   eValue) ;
         ConvertDecimalPoint (UnicodeBuff) ;
         }
      ConvertUnicodeStr (&TempBuff[StringLen], UnicodeBuff) ;
      }
   else
      {
      // export "----" for unstable values
      strcat (&TempBuff[StringLen], "----");
      }

   // write the line value
   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   return (TRUE) ;

Exit0:
   return (FALSE) ;
}  // ExportLineName

// This routine is need to insert the line values into its
// column location.  It is needed because not all the instances (columns)
// are available for the same line.
void SaveColumnLineData (PLINE pLine, LPSTR pColumnLineData)
{
   FLOAT          eValue ;
   LPSTR          pColumnLine ;

   CHAR           TempBuff [LongTextLen * 2] ;

   TCHAR          UnicodeBuff [LongTextLen] ;

   if (!pColumnLineData || pLine->iReportColumn < 0)
      {
      return ;
      }

   // find the offset into the pColumnLineData buffer for current line
   pColumnLine = pColumnLineData + pLine->iReportColumn * ShortTextLen ;

   if (pLine->bFirstTime == 0)
      {

      eValue = CounterEntry (pLine) ;

      if (pLine->lnCounterType == PERF_COUNTER_RAWCOUNT_HEX ||
          pLine->lnCounterType == PERF_COUNTER_LARGE_RAWCOUNT_HEX)
         {
         DWORD LowValue  ;
         DWORD HighValue ;

         LowValue  = (DWORD) (pLine->lnaCounterValue[0]) ;
         HighValue = (DWORD) (pLine->lnaCounterValue[0] >> 32);

         if (pLine->lnCounterType == PERF_COUNTER_RAWCOUNT_HEX ||
             HighValue == 0)
            {
            TSPRINTF (UnicodeBuff,
                      szHexFormat,
                      LowValue) ;
            }
         else
            {
            TSPRINTF (UnicodeBuff,
                      szLargeHexFormat,
                      HighValue,
                      LowValue) ;
            }
         }
      else
         {
         TSPRINTF (UnicodeBuff,
                   (eValue > eStatusLargeValueMax) ?
                       szLargeValueFormat : szValueFormat,
                   eValue) ;
         ConvertDecimalPoint (UnicodeBuff) ;
         }
      ConvertUnicodeStr (TempBuff, UnicodeBuff) ;
      strncpy (pColumnLine, TempBuff, ShortTextLen) ;
      *(pColumnLine + ShortTextLen - 1) = '\0' ;
      }
   else
      {
      // export "----" for unstable values
      strcpy (pColumnLine, "----");
      }
}  // SaveColumnLineData

BOOL ExportColumnLineData (HANDLE hFile,
                           int ColumnTotal,
                           PCOUNTERGROUP pCounterGroup,
                           LPSTR pColumnLineData)
{
   int            iIndex ;
   int            StringLen ;
   CHAR           TempBuff [LongTextLen] ;
   LPSTR          pCurrentLineData ;

   // export the counter name
   strcpy (TempBuff, pDelimiter) ;
   StringLen = strlen (TempBuff) ;
   ConvertUnicodeStr (&TempBuff[StringLen], pCounterGroup->pLineFirst->lnCounterName) ;
   strcat (TempBuff, pDelimiter) ;
   if (!FileWrite (hFile, TempBuff, strlen(TempBuff)))
      {
      goto Exit0 ;
      }

   // go thru each column and export the line value if it has been stored
   for (iIndex = 0, pCurrentLineData = pColumnLineData ;
      iIndex < ColumnTotal ;
      iIndex++, pCurrentLineData += ShortTextLen )
      {
      if (*pCurrentLineData != 0)
         {
         // data available for this column
         if (!FileWrite (hFile, pCurrentLineData, strlen(pCurrentLineData)))
            {
            goto Exit0 ;
            }
         }

      if (!FileWrite (hFile, pDelimiter, strlen(pDelimiter)))
         {
         goto Exit0 ;
         }
      }

   if (!FileWrite (hFile, LineEndStr, strlen(LineEndStr)))
      {
      goto Exit0 ;
      }

   return (TRUE) ;

Exit0:
   return (FALSE) ;

}  // ExportColumnLineData

void ExportReport (void)
{
   HANDLE         hFile = 0 ;
   PREPORT        pReport ;
   PSYSTEMGROUP   pSystemGroup ;
   POBJECTGROUP   pObjectGroup ;
   PCOUNTERGROUP  pCounterGroup ;
   PLINE          pLine ;
   BOOL           bExportComputer ;
   BOOL           bExportObject ;
   BOOL           bExportCounterName ;
   int            ColumnTotal = 0 ;
   LPSTR          pColumnLineData = NULL ;
   LPTSTR         pFileName = NULL ;
   INT            ErrCode = 0 ;

   if (!(pReport = ReportData (hWndReport)))
      {
      return ;
      }

   // see if there is anything to export..
   if (!(pReport->pSystemGroupFirst))
      {
      return ;
      }

   SetHourglassCursor() ;

   if (ErrCode = ExportFileOpen (hWndReport, &hFile,
       pReport->iIntervalMSecs, &pFileName))
      {
      goto Exit0 ;
      }

   if (!pFileName)
      {
      // the case when user cancel
      goto Exit0 ;
      }

   // export each system group

   for (pSystemGroup = pReport->pSystemGroupFirst ;
        pSystemGroup ;
        pSystemGroup = pSystemGroup->pSystemGroupNext)
      {  // for System...

      bExportComputer = TRUE ;

      for (pObjectGroup = pSystemGroup->pObjectGroupFirst ;

pObjectGroup ;
           pObjectGroup = pObjectGroup->pObjectGroupNext)
         {  // for Object...

         bExportObject = TRUE ;

         for (pCounterGroup = pObjectGroup->pCounterGroupFirst ;
              pCounterGroup ;
              pCounterGroup = pCounterGroup->pCounterGroupNext)
            {  // for Counter...

            bExportCounterName = TRUE ;

            // Column data buffer has been allocated for this object type,
            // zero out the buffer and prepare for next round.

            if (pColumnLineData)
               {
               memset (pColumnLineData, 0, ColumnTotal * ShortTextLen) ;
               }

            for (pLine = pCounterGroup->pLineFirst ;
                 pLine ;
                 pLine = pLine->pLineCounterNext)
               {  // for Line...

               if (bExportComputer)
                  {
                  // only need to do this for the first object
                  bExportComputer = FALSE ;
                  if (!ExportComputerName (hFile, pSystemGroup))
                     {
                     ErrCode = ERR_EXPORT_FILE ;
                     goto Exit0 ;
                     }
                  }

               if (bExportObject)
                  {
                  // only need to do this for the first counter group
                  bExportObject = FALSE ;
                  if (!ExportObjectName (hFile, pObjectGroup, &ColumnTotal))
                     {
                     ErrCode = ERR_EXPORT_FILE ;
                     goto Exit0 ;
                     }

                  if (ColumnTotal > 1)
                     {
                     // special case to setup a column array and export
                     // the line values later
                     pColumnLineData = MemoryAllocate (ColumnTotal * ShortTextLen) ;
                     if (!pColumnLineData)
                        {
                        ErrCode = ERR_EXPORT_FILE ;
                        goto Exit0 ;
                        }
                     }
                  }

               if (ColumnTotal > 1)
                  {
                  // save the line value into its column & export later
                  SaveColumnLineData (pLine, pColumnLineData) ;
                  }
               else
                  {
                  // simple case, export the line now
                  if (!ExportLineName (hFile, pLine, &bExportCounterName))
                     {
                     ErrCode = ERR_EXPORT_FILE ;
                     goto Exit0 ;
                     }
                  }
               }  // for Line...

            if (!bExportCounterName)
               {
               // export the line end
               if (!FileWrite (hFile, LineEndStr, strlen(LineEndStr)))
                  {
                  ErrCode = ERR_EXPORT_FILE ;
                  goto Exit0 ;
                  }
               }

            if (pColumnLineData)
               {
               // now, do the actual export
               if (!ExportColumnLineData (hFile,
                  ColumnTotal,
                  pCounterGroup,
                  pColumnLineData))
                  {
                  ErrCode = ERR_EXPORT_FILE ;
                  goto Exit0 ;
                  }
               }
            }  // for Counter...

         // done with the object, done with the buffer
         if (pColumnLineData)
            {
            MemoryFree (pColumnLineData) ;
            ColumnTotal = 0 ;
            pColumnLineData = NULL ;
            }
         }  // for Object

      }  // for System...

Exit0:

   SetArrowCursor() ;

   if (pColumnLineData)
      {
      MemoryFree (pColumnLineData) ;
      }

   if (hFile)
      {
      CloseHandle (hFile) ;
      }

   if (pFileName)
      {
      if (ErrCode)
         {
         DlgErrorBox (hWndGraph, ErrCode, pFileName) ;
         }

      MemoryFree (pFileName) ;
      }

}  // ExportReport


REPORT.H

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define xSystemMargin               (xScrollWidth)

#ifdef JAPAN
#define xObjectMargin               xSystemMargin
#else
#define xObjectMargin               (2 * xScrollWidth)
#endif

#define xCounterMargin              (3 * xScrollWidth)
#define xColumnMargin               (xScrollWidth)

#define RightHandMargin             xObjectMargin
#define ValueMargin(pReport)       \
   (4 * xScrollWidth + pReport->xMaxCounterWidth)

#define iDefaultReportIntervalSecs  5

BOOL ReportInitializeApplication (void) ;

HWND CreateReportWindow (HWND hWndParent) ;

BOOL ReportInsertLine (HWND hWnd, PLINE pLine) ;

void SetReportPositions (HDC hDC, PREPORT pReport) ;

#if 0
PREPORT ReportData (HWND hWndReport) ;
#endif
#define ReportData(hWndReport)      \
   (&Report)

void SetReportTimer (PREPORT pReport) ;

void PlaybackReport (HWND hWndReport) ;

BOOL CurrentReportItem (HWND hWndReport) ;

BOOL AddReport (HWND hWndParent) ;

void ReportTimer (HWND hWnd, BOOL bForce) ;

BOOL ReportRefresh (HWND hWnd) ;
BOOL ToggleReportRefresh (HWND hWnd) ;

BOOL SaveReport (HWND hWndReport, HANDLE hInputFile, BOOL bGetFileName) ;

BOOL OpenReport (HWND hWndReport,
                 HANDLE hFile,
                 DWORD dwMajorVersion,
                 DWORD dwMinorVersion,
                 BOOL bReportFile) ;

BOOL PrintReportDisplay (HDC hDC,
                         PREPORT pReport) ;

void ResetReport (HWND hWndReport) ;
void ResetReportView (HWND hWndReport) ;

void ClearReportDisplay (HWND hWndReport) ;

BOOL ReportDeleteItem (HWND hWnd) ;

BOOL PrintReport (HWND hWndParent,
                  HWND hWndReport) ;

void ExportReport (void) ;

void ReportAddAction (PREPORT pReport) ;

void ReportSystemRect (PREPORT        pReport,
                       PSYSTEMGROUP   pSystemGroup,
                       LPRECT         lpRect) ;

void ReportObjectRect (PREPORT        pReport,
                       POBJECTGROUP   pObjectGroup,
                       LPRECT         lpRect) ;

void ReportCounterRect (PREPORT        pReport,
                        PCOUNTERGROUP  pCounterGroup,
                        LPRECT         lpRect) ;

void ReportColumnRect (PREPORT pReport,
                       PCOLUMNGROUP pColumnGroup,
                       LPRECT  lpRect) ;

void ReportLineValueRect (PREPORT pReport,
                          PLINE pLine,
                          LPRECT lpRect) ;

BOOL  OnReportLButtonDown (HWND hWnd,
                           WORD xPos,
                           WORD yPos) ;

PCOLUMNGROUP GetColumnGroup (PREPORT pReport,
                          POBJECTGROUP pObjectGroup,
                          PLINE pLine) ;

void DrawReportValue (HDC hDC, PREPORT pReport, PLINE pLine) ;

void ColumnGroupRemove (PCOLUMNGROUP pColumnGroupFirst) ;

BOOL LineCounterRemove (PPLINE ppLineFirst,
                        PLINE pLineRemove) ;

void ClearReportTimer (PREPORT pReport) ;

PSYSTEMGROUP GetSystemGroup (PREPORT pReport,
                          LPTSTR lpszSystemName) ;

POBJECTGROUP GetObjectGroup (PSYSTEMGROUP pSystemGroup,
                          LPTSTR lpszObjectName) ;

PCOUNTERGROUP GetCounterGroup (POBJECTGROUP pObjectGroup,
                            DWORD dwCounterIndex,
                            BOOL *pbCounterGroupCreated,
                            LPTSTR pCounterName) ;


RPTFCT.C

/*****************************************************************************
 *
 *  RptFct.c - This file contains support routines for the Report view.
 *       They are moved here because Report.c is getting too big.
 *
 *  Microsoft Confidential
 *  Copyright 1992 - 1998 Microsoft Corporation
 *
 *  Author -
 *
 *       Hon-Wah Chan
 *
 ****************************************************************************/

#include "perfmon.h"
#include <stdio.h>      // for sprintf
#include "report.h"     // Exported declarations for this file

#include "line.h"       // for LineAppend
#include "pmemory.h"    // for MemoryXXX (mallloc-type) routines
#include "system.h"     // for SystemGet
#include "utils.h"
#include "perfmops.h"   // for BuildValueListForSystems

// extern defined in Report.c

extern TCHAR          szSystemFormat [] ;
extern TCHAR          szObjectFormat [] ;

#define szValuePlaceholder          TEXT("-999999999.999")
#define szLargeValueFormat          TEXT("%12.0f")
#define eStatusLargeValueMax        ((FLOAT) 999999999.0)
#define szValueFormat               TEXT("%12.3f")

//========================
// Local routines prototypes
//========================

void ColumnRemoveOne (PREPORT pReport,
                      POBJECTGROUP pObjectGroup,
                      int iColumnNumber) ;

BOOL CounterGroupRemove (PCOUNTERGROUP *ppCounterGroupFirst,
                        PCOUNTERGROUP pCounterGroupRemove) ;

BOOL ObjectGroupRemove (POBJECTGROUP *ppObjectGroupFirst,
                        POBJECTGROUP pObjectGroupRemove) ;

BOOL SystemGroupRemove (PSYSTEMGROUP *ppSystemGroupFirst,
                        PSYSTEMGROUP pSystemGroupRemove) ;

PCOUNTERGROUP GetNextCounter (PSYSTEMGROUP   pSystemGroup,
                              POBJECTGROUP   pObjectGroup,
                              PCOUNTERGROUP  pCounterGroup) ;

// CheckColumnGroupRemove is used to check if the given
// column is empty.  If it is empty, it is removed from
// the column link list
void CheckColumnGroupRemove (PREPORT      pReport,
                             POBJECTGROUP pObjectGroup,
                             int          iReportColumn)
   {
   // check if we need to remove the this column
   PLINE          pCounterLine ;
   PCOUNTERGROUP  pCounterGrp ;
   BOOL           bColumnFound = FALSE ;

   if (iReportColumn < 0 || pObjectGroup->pCounterGroupFirst == NULL)
      {
      // no column for this Counter group, forget it
      return ;
      }

   // go thru each Counter group and check if any line in the
   // group matches the given column number
   for (pCounterGrp = pObjectGroup->pCounterGroupFirst ;
        pCounterGrp ;
        pCounterGrp = pCounterGrp->pCounterGroupNext )
      {
      for (pCounterLine = pCounterGrp->pLineFirst ;
           pCounterLine ;
           pCounterLine = pCounterLine->pLineCounterNext)
         {
         if (pCounterLine->iReportColumn == iReportColumn)
            {
            // found one, this column is not empty
            bColumnFound = TRUE ;
            break ;
            }
         }  // for pCounterLine
      if (bColumnFound)
         {
         break ;
         }
      }  // for pCounterGrp

   if (bColumnFound == FALSE)
      {
      // we have deleted the last column item, remove this column
      ColumnRemoveOne (pReport,
         pObjectGroup,
         iReportColumn) ;
      }
   }  // CheckColumnGroupRemove

//================================
// Line routine
//================================

void ReportLineValueRect (PREPORT pReport,
                          PLINE pLine,
                          LPRECT lpRect)
   {  // ReportLineValueRect
   lpRect->left = ValueMargin (pReport) + pLine->xReportPos ;
   lpRect->top = pLine->yReportPos ;
   lpRect->right = lpRect->left + pReport->xValueWidth ;
   lpRect->bottom = lpRect->top + pReport->yLineHeight ;
   }  // ReportLineValueRect

PLINE LineRemoveItem (PREPORT pReport,
                      enum REPORT_ITEM_TYPE  *pNewItemType)
   {
   PLINE          pLine ;
   PLINE          pNextLine = NULL ;
   PLINE          pReturnLine = NULL ;
   PLINE          pLeftLine = NULL ;
   PSYSTEMGROUP   pSystemGroup ;
   POBJECTGROUP   pObjectGroup ;
   PCOUNTERGROUP  pCounterGroup ;
   PCOUNTERGROUP  pNextCounterGroup ;
   BOOL           bCreatNewCounterGroup ;

   //=============================//
   // Remove line, line's system  //
   //=============================//

   pLine = pReport->CurrentItem.pLine ;
   LineRemove (&pReport->pLineFirst, pLine) ;

   // no more line, no more timer...
   if (!pReport->pLineFirst)
      {
      pReport->xWidth = 0 ;
      pReport->yHeight = 0 ;
      pReport->xMaxCounterWidth = 0 ;
      ClearReportTimer (pReport) ;
      }

   //=============================//
   // Get correct spot; remove line //
   //=============================//

   pSystemGroup = GetSystemGroup (pReport, pLine->lnSystemName) ;
   pObjectGroup = GetObjectGroup (pSystemGroup, pLine->lnObjectName) ;
   pCounterGroup = GetCounterGroup (pObjectGroup,
      pLine->lnCounterDef.CounterNameTitleIndex,
      &bCreatNewCounterGroup,
      pLine->lnCounterName) ;

   if (!pCounterGroup)
      return (NULL) ;

   LineCounterRemove (&pCounterGroup->pLineFirst, pLine) ;

   // check which line to get the focus
   if (pCounterGroup->pLineFirst)
      {
      // simple case, we still have line in the same counter group
      // get the one right after this delete line.
      for (pNextLine = pCounterGroup->pLineFirst ;
           pNextLine ;
           pNextLine = pNextLine->pLineCounterNext)
         {
         if (pNextLine->xReportPos > pLine->xReportPos)
            {
            if (pReturnLine == NULL ||
                pReturnLine->xReportPos > pNextLine->xReportPos)
               {
               pReturnLine = pNextLine ;
               }
            }
         else
            {
            if (pLeftLine == NULL ||
                pLeftLine->xReportPos < pNextLine->xReportPos)
               {
               pLeftLine = pNextLine ;
               }
            }
         }

      if (!pReturnLine && pLeftLine)
         {
         // the delete line is the last column, then use the line
         // to its left
         pReturnLine = pLeftLine ;
         }
      }
   else
      {
      pNextCounterGroup = GetNextCounter (
            pSystemGroup,
            pObjectGroup,
            pCounterGroup) ;

      if (pNextCounterGroup)
         {
         pLeftLine = NULL ;
         for (pNextLine = pNextCounterGroup->pLineFirst ;
              pNextLine ;
              pNextLine = pNextLine->pLineCounterNext)
            {
            // get the line in the first column
            if (pLeftLine == NULL ||
                pNextLine->xReportPos < pLeftLine->xReportPos)
               {
               pLeftLine = pNextLine ;
               }
            }
         pReturnLine = pLeftLine ;
         }

      // remove this counter group if there is no line
      CounterGroupRemove (&pObjectGroup->pCounterGroupFirst, pCounterGroup) ;
      }

   // check if we need to remove any empty column
   CheckColumnGroupRemove (pReport, pObjectGroup, pLine->iReportColumn) ;

   if (!(pObjectGroup->pCounterGroupFirst))
      ObjectGroupRemove (&pSystemGroup->pObjectGroupFirst, pObjectGroup) ;

   if (!(pSystemGroup->pObjectGroupFirst))
      SystemGroupRemove (&pReport->pSystemGroupFirst, pSystemGroup) ;

   LineFree (pLine) ;

   if (pReturnLine && pNewItemType)
      {
      *pNewItemType = REPORT_TYPE_LINE ;
      }

   return (pReturnLine) ;
   }  // LineRemoveItem

//======================================//
// Column Group routines                //
//======================================//

void ReportColumnRect (PREPORT pReport,
                       PCOLUMNGROUP pColumnGroup,
                       LPRECT  lpRect)
   {  // ReportColumnRect
   lpRect->left = ValueMargin (pReport) + pColumnGroup->xPos ;
   lpRect->top = pColumnGroup->yFirstLine ;
   lpRect->right = lpRect->left + pColumnGroup->xWidth ;
   lpRect->bottom = lpRect->top + pReport->yLineHeight ;
   if (pColumnGroup->lpszParentName)
      {
      lpRect->top -= pReport->yLineHeight ;
      }
   }  // ReportColumnRect

BOOL ColumnSame (PCOLUMNGROUP pColumnGroup,
                 LPTSTR lpszParentName,
                 LPTSTR lpszInstanceName)
   {  // ColumnSame
   BOOL           bParentSame ;
   BOOL           bInstanceSame ;

   bParentSame = (!lpszParentName && !pColumnGroup->lpszParentName) ||
                 strsame (lpszParentName, pColumnGroup->lpszParentName) ;
   bInstanceSame = (!lpszInstanceName && !pColumnGroup->lpszInstanceName) ||
                 strsame (lpszInstanceName, pColumnGroup->lpszInstanceName) ;

   return (bParentSame && bInstanceSame) ;
   }  // ColumnSame

PCOLUMNGROUP ColumnGroupCreate (PREPORT pReport,
                                int xPos,
                                LPTSTR lpszParentName,
                                LPTSTR lpszInstanceName,
                                int PreviousColumnNumber,
                                int yFirstLine)
   {  // ColumnGroupCreate
   PCOLUMNGROUP   pColumnGroup ;
   HDC            hDC ;

   hDC = GetDC (pReport->hWnd) ;
   pColumnGroup = MemoryAllocate (sizeof (COLUMNGROUP)) ;

   if (pColumnGroup)
      {
      pColumnGroup->pColumnGroupNext = NULL ;
      pColumnGroup->lpszParentName = StringAllocate (lpszParentName) ;
      pColumnGroup->lpszInstanceName = StringAllocate (lpszInstanceName) ;
      pColumnGroup->ParentNameTextWidth = TextWidth (hDC, lpszParentName) ;
      pColumnGroup->InstanceNameTextWidth = TextWidth (hDC, lpszInstanceName) ;
      pColumnGroup->xPos = xPos ;
      pColumnGroup->yFirstLine = yFirstLine ;
      pColumnGroup->ColumnNumber = PreviousColumnNumber + 1 ;
      pColumnGroup->xWidth = max (max (pColumnGroup->ParentNameTextWidth,
                                       pColumnGroup->InstanceNameTextWidth),
                                  pReport->xValueWidth) ;

      pReport->xWidth = max (pReport->xWidth,
                             RightHandMargin +
                             ValueMargin (pReport) +
                             pColumnGroup->xPos + pColumnGroup->xWidth +
                             xColumnMargin) ;
      }  // if

   ReleaseDC (pReport->hWnd, hDC) ;
   return (pColumnGroup) ;
   }  // ColumnGroupCreate

PCOLUMNGROUP GetColumnGroup (PREPORT pReport,
                          POBJECTGROUP pObjectGroup,
                          PLINE pLine)
/*
   Effect:        Return a pointer to the appropriate column group from
                  within the groups of pObject. If the line is a counter
                  without instances, return NULL. Otherwise, determine
                  if the counter's parent/instance pair is already found
                  in an existing column and return a pointer to that column.

                  If a column with the appropriate parent/instance isn't
                  found, add a new column *at the end*, and return that
                  column.

   Note:          This function has multiple return points.
*/
   {  // GetColumnGroup
   PCOLUMNGROUP   pColumnGroup ;
   LPTSTR         lpszParentName ;
   LPTSTR         lpszInstanceName ;

   if (!LineInstanceName (pLine))
      return (NULL) ;

   lpszParentName = LineParentName (pLine) ;
   lpszInstanceName = LineInstanceName (pLine) ;

   if (!pObjectGroup->pColumnGroupFirst)
      {
      pObjectGroup->pColumnGroupFirst =
         ColumnGroupCreate (pReport,
            0,
            lpszParentName,
            lpszInstanceName,
            -1,
            pObjectGroup->yFirstLine) ;

      if (pObjectGroup->pColumnGroupFirst)
         {
         pObjectGroup->pColumnGroupFirst->pParentObject =
            pObjectGroup ;
         }

      return (pObjectGroup->pColumnGroupFirst) ;
      }

   for (pColumnGroup = pObjectGroup->pColumnGroupFirst ;
        pColumnGroup ;
        pColumnGroup = pColumnGroup->pColumnGroupNext)
      {  // for
      if (ColumnSame (pColumnGroup, lpszParentName, lpszInstanceName))
         return (pColumnGroup) ;

      else if (!pColumnGroup->pColumnGroupNext)
         {  // if
         pColumnGroup->pColumnGroupNext =
            ColumnGroupCreate (pReport,
                               pColumnGroup->xPos + pColumnGroup->xWidth +
                               xColumnMargin,
                               lpszParentName,
                               lpszInstanceName,
                               pColumnGroup->ColumnNumber,
                               pObjectGroup->yFirstLine) ;

         if (pColumnGroup->pColumnGroupNext)
            {
            (pColumnGroup->pColumnGroupNext)->pParentObject =
               pObjectGroup ;

            // build the double link-list
            (pColumnGroup->pColumnGroupNext)->pColumnGroupPrevious =
               pColumnGroup ;
            }

         return (pColumnGroup->pColumnGroupNext) ;
         }  // if
      }  // for

   return (NULL) ;
   }  // GetColumnGroup

// ColumnRemoveOne removes the column with the specified column number
void ColumnRemoveOne (PREPORT pReport,
                      POBJECTGROUP pObjectGroup,
                      int iColumnNumber)
   {
   PCOLUMNGROUP   pColumnGroup ;
   PCOLUMNGROUP   pNextColumnGroup ;

   if (pObjectGroup->pColumnGroupFirst == NULL)
      {
      // no column group, forget it
      return ;
      }

   // Find the head list
   if (pObjectGroup->pColumnGroupFirst->ColumnNumber == iColumnNumber)
      {
      pColumnGroup = pObjectGroup->pColumnGroupFirst ;
      pObjectGroup->pColumnGroupFirst = pColumnGroup->pColumnGroupNext ;
      if (pColumnGroup->pColumnGroupNext)
         {
         // set up head of backward link list
         (pColumnGroup->pColumnGroupNext)->pColumnGroupPrevious = NULL ;
         }

      // free memory for this column group
      MemoryFree (pColumnGroup->lpszParentName) ;
      MemoryFree (pColumnGroup->lpszInstanceName) ;
      MemoryFree (pColumnGroup) ;

      return ;
      }

   // go thru the double link list to look for the right column
   for (pColumnGroup = pObjectGroup->pColumnGroupFirst ;
        pColumnGroup ;
        pColumnGroup = pNextColumnGroup)
      {
      pNextColumnGroup = pColumnGroup->pColumnGroupNext ;

      if (pNextColumnGroup == NULL)
         {
         // forget it if we can't find this column for some reson.
         break ;
         }

      if (pNextColumnGroup->ColumnNumber == iColumnNumber)
         {
         pColumnGroup->pColumnGroupNext = pNextColumnGroup->pColumnGroupNext ;

         // build backward link iff it is not the end of list
         if (pColumnGroup->pColumnGroupNext)
            {
            (pColumnGroup->pColumnGroupNext)->pColumnGroupPrevious =
               pColumnGroup ;
            }

         // free memory for this column group
         MemoryFree (pNextColumnGroup->lpszParentName) ;
         MemoryFree (pNextColumnGroup->lpszInstanceName) ;
         MemoryFree (pNextColumnGroup) ;

         // Done
         break ;
         }
      }
   }  // ColumnRemoveOne

// ColumnGroupRemove removes all the columns for a given column list
void ColumnGroupRemove (PCOLUMNGROUP pColumnGroupFirst)
   {
   PCOLUMNGROUP   pColumnGroup ;
   PCOLUMNGROUP   pNextColumnGroup ;

   for (pColumnGroup = pColumnGroupFirst ;
        pColumnGroup ;
        pColumnGroup = pNextColumnGroup)
      {
      pNextColumnGroup = pColumnGroup->pColumnGroupNext ;

      // free memory for this column group
      MemoryFree (pColumnGroup->lpszParentName) ;
      MemoryFree (pColumnGroup->lpszInstanceName) ;
      MemoryFree (pColumnGroup) ;
      }
   }  // ColumnGroupRemove

// ColumnRemoveItem  is called when user wants to delete a
// selected column.
PCOLUMNGROUP ColumnRemoveItem (PREPORT      pReport,
                               PCOLUMNGROUP pColumnGroup,
                               BOOL         bCleanUpLink,
                               enum REPORT_ITEM_TYPE  *pNewItemType)
   {
   PLINE          pLine, pNextLine ;
   PSYSTEMGROUP   pSystemGroup ;
   POBJECTGROUP   pObjectGroup ;
   PCOUNTERGROUP  pCounterGroup, pNextCounterGroup ;
   BOOL           bColumnFound ;
   PCOLUMNGROUP   pRetColumnGroup = NULL ;

   pObjectGroup = pColumnGroup->pParentObject ;
   pSystemGroup = pObjectGroup->pParentSystem ;

   // first, get rid of all the counter lines with this column number
   // Note - each Counter group has only 1 line (or 0) with this
   // column number
   for (pCounterGroup = pObjectGroup->pCounterGroupFirst ;
        pCounterGroup ;
        pCounterGroup = pNextCounterGroup )
      {
      pNextCounterGroup = pCounterGroup->pCounterGroupNext ;
      bColumnFound = FALSE ;

      for (pLine = pCounterGroup->pLineFirst ;
           pLine ;
           pLine = pNextLine)
         {
         pNextLine = pLine->pLineCounterNext ;
         if (pLine->iReportColumn == pColumnGroup->ColumnNumber)
            {
            bColumnFound = TRUE ;
            // delete this line
            LineRemove (&pReport->pLineFirst, pLine) ;
            LineCounterRemove (&pCounterGroup->pLineFirst, pLine) ;
            LineFree (pLine) ;
            break ;
            }
         }

      if (bColumnFound)
         {
         // check if we need delete this counter group
         if (!(pCounterGroup->pLineFirst))
            {
            CounterGroupRemove (&pObjectGroup->pCounterGroupFirst, pCounterGroup) ;
            }
         }
      }

   // determine which column group to go after deleting this
   if (pColumnGroup->pColumnGroupNext)
      {
      // get the Column group after this delete one
      pRetColumnGroup = pColumnGroup->pColumnGroupNext ;
      }
   else
      {
      // get the Counter group before this delete one
      pRetColumnGroup = pColumnGroup->pColumnGroupPrevious ;
      }

   if (pNewItemType)
      {
      if (pRetColumnGroup)
         {
         *pNewItemType = REPORT_TYPE_COLUMN ;
         }
      else
         {
         // get next counter group
         pNextCounterGroup = GetNextCounter (
            pSystemGroup,
            pObjectGroup,
            NULL) ;

         if (pNextCounterGroup)
            {
            // we have to return Counter group, so we have to do the
            // dirty casting..
            *pNewItemType = REPORT_TYPE_COUNTER ;
            pRetColumnGroup = (PCOLUMNGROUP) pNextCounterGroup ;
            }
         }
      }

   // remove this column group
   ColumnRemoveOne (pReport, pObjectGroup, pColumnGroup->ColumnNumber) ;

   // check for further cleanup
   if (bCleanUpLink)
      {
      if (!(pObjectGroup->pCounterGroupFirst))
         ObjectGroupRemove (&pSystemGroup->pObjectGroupFirst, pObjectGroup) ;

      if (!(pSystemGroup->pObjectGroupFirst))
         SystemGroupRemove (&pReport->pSystemGroupFirst, pSystemGroup) ;
      }
   return (pRetColumnGroup) ;
   }  // ColumnRemoveItem

//======================================//
// Counter Group routines               //
//======================================//

void ReportCounterRect (PREPORT        pReport,
                        PCOUNTERGROUP  pCounterGroup,
                        LPRECT         lpRect)
   {  // ReportCounterRect
   lpRect->left = xCounterMargin ;
   lpRect->top = pCounterGroup->yLine ;
   lpRect->right = lpRect->left + pCounterGroup->xWidth + yScrollHeight / 2 ;
   lpRect->bottom = lpRect->top + pReport->yLineHeight ;
   }  // ReportCounterRect

PCOUNTERGROUP CounterGroupCreate (DWORD  dwCounterIndex,
                                  LPTSTR pCounterName)
   {  // CounterGroupCreate
   PCOUNTERGROUP  pCounterGroup ;
   HDC            hDC ;
   PREPORT        pReport ;

   pCounterGroup = MemoryAllocate (sizeof (COUNTERGROUP)) ;

   if (pCounterGroup)
      {
      pCounterGroup->pCounterGroupNext = NULL ;
      pCounterGroup->pLineFirst = NULL ;
      pCounterGroup->dwCounterIndex = dwCounterIndex ;

      if (pCounterName)
         {
         hDC = GetDC (hWndReport) ;
         pReport = ReportData (hWndReport) ;
         SelectFont (hDC, pReport->hFont) ;
         pCounterGroup->xWidth = TextWidth (hDC, pCounterName) ;
         ReleaseDC (hWndReport, hDC) ;
         }
      }  // if

   return (pCounterGroup) ;
   }  // CounterGroupCreate

PCOUNTERGROUP GetCounterGroup (POBJECTGROUP pObjectGroup,
                            DWORD dwCounterIndex,
                            BOOL *pbCounterGroupCreated,
                            LPTSTR pCounterName)
   {  // GetCounterGroup
   PCOUNTERGROUP   pCounterGroup ;

   *pbCounterGroupCreated = FALSE ;
   if (!pObjectGroup)
      return (FALSE) ;

   if (!pObjectGroup->pCounterGroupFirst)
      {
      pObjectGroup->pCounterGroupFirst =
         CounterGroupCreate (dwCounterIndex, pCounterName) ;

      if (pObjectGroup->pCounterGroupFirst)
         {
         *pbCounterGroupCreated = TRUE ;
         pObjectGroup->pCounterGroupFirst->pParentObject =
            pObjectGroup ;
         }

      return (pObjectGroup->pCounterGroupFirst) ;
      }

   for (pCounterGroup = pObjectGroup->pCounterGroupFirst ;
        pCounterGroup ;
        pCounterGroup = pCounterGroup->pCounterGroupNext)
      {  // for
      if (dwCounterIndex && pCounterGroup->dwCounterIndex == dwCounterIndex)
         {
         return (pCounterGroup) ;
         }
      else if (!dwCounterIndex &&
         pCounterGroup->pLineFirst &&
         pstrsame (pCounterGroup->pLineFirst->lnCounterName, pCounterName))
         {
         return (pCounterGroup) ;
         }
      else if (!pCounterGroup->pCounterGroupNext)
         {  // if
         pCounterGroup->pCounterGroupNext =
            CounterGroupCreate (dwCounterIndex, pCounterName) ;
         if (pCounterGroup->pCounterGroupNext)
            {
            *pbCounterGroupCreated = TRUE ;
            (pCounterGroup->pCounterGroupNext)->pParentObject =
               pObjectGroup ;

            // build backward link
            (pCounterGroup->pCounterGroupNext)->pCounterGroupPrevious =
               pCounterGroup ;
            }
         return (pCounterGroup->pCounterGroupNext) ;
         }  // if
      }  // for

   return (NULL) ;
   }  // GetCounterGroup

BOOL CounterGroupRemove (PCOUNTERGROUP *ppCounterGroupFirst,
                        PCOUNTERGROUP pCounterGroupRemove)
   {
   PCOUNTERGROUP  pCounterGroup ;

   if (*ppCounterGroupFirst == pCounterGroupRemove)
      {
      *ppCounterGroupFirst = (*ppCounterGroupFirst)->pCounterGroupNext ;

      if (*ppCounterGroupFirst)
         {
         // set up head of backward link list
         (*ppCounterGroupFirst)->pCounterGroupPrevious = NULL ;
         }

      MemoryFree (pCounterGroupRemove) ;
      return (TRUE) ;
      }

   for (pCounterGroup = *ppCounterGroupFirst ;
        pCounterGroup->pCounterGroupNext ;
        pCounterGroup = pCounterGroup->pCounterGroupNext)
      {   // for
      if (pCounterGroup->pCounterGroupNext == pCounterGroupRemove)
         {
         pCounterGroup->pCounterGroupNext = pCounterGroupRemove->pCounterGroupNext ;
         if (pCounterGroup->pCounterGroupNext)
            {
            (pCounterGroup->pCounterGroupNext)->pCounterGroupPrevious
               = pCounterGroup ;
            }
         MemoryFree (pCounterGroupRemove) ;
         return (TRUE) ;
         }  // if
      }  // for

   return (FALSE) ;
   }  // CounterGroupRemove

// CounterRemoveItem is called when user wants to delete a
// selected counter (row)
PCOUNTERGROUP CounterRemoveItem (PREPORT        pReport,
                                 PCOUNTERGROUP  pCounterGroup,
                                 BOOL           bCleanUpLink,
                                 enum REPORT_ITEM_TYPE  *pNewItemType)
   {
   PLINE          pLine, pNextLine ;
   POBJECTGROUP   pObjectGroup ;
   PSYSTEMGROUP   pSystemGroup ;
   PCOLUMNGROUP   pColumnGroup ;
   PCOLUMNGROUP   pNextColumnGroup ;
   PCOUNTERGROUP  pRetCounterGroup = NULL ;

   pObjectGroup = pCounterGroup->pParentObject ;
   pSystemGroup = pObjectGroup->pParentSystem ;

   // first, remove all the counter lines from this counter group
   // and from the Report line link-list
   for (pLine = pCounterGroup->pLineFirst ;
        pLine ;
        pLine = pNextLine)
      {
      pNextLine = pLine->pLineCounterNext ;
      LineRemove (&pReport->pLineFirst, pLine) ;
      LineFree (pLine) ;
      }

   // we only need to delete the counter group iff we are deleting
   // this selected Counter.
   if (bCleanUpLink)
      {
      // determine which counter group to go after deleting this
      pRetCounterGroup = GetNextCounter (
         pSystemGroup ,
         pObjectGroup,
         pCounterGroup) ;

      // remove this counter group from its parent object group
      CounterGroupRemove (&pObjectGroup->pCounterGroupFirst, pCounterGroup) ;

      if (!(pObjectGroup->pCounterGroupFirst))
         ObjectGroupRemove (&pSystemGroup->pObjectGroupFirst, pObjectGroup) ;
      else
         {
         // Object group not empty, check for any empty column
         for (pColumnGroup = pObjectGroup->pColumnGroupFirst ;
            pColumnGroup ;
            pColumnGroup = pNextColumnGroup)
            {
            pNextColumnGroup = pColumnGroup->pColumnGroupNext ;
            CheckColumnGroupRemove (pReport, pObjectGroup, pColumnGroup->ColumnNumber) ;
            }
         }

      if (!(pSystemGroup->pObjectGroupFirst))
         {
         SystemGroupRemove (&pReport->pSystemGroupFirst, pSystemGroup) ;
         }
      }
   else
      {
      // get rid of this counter's memory
      MemoryFree (pCounterGroup) ;
      }

   if (pRetCounterGroup && pNewItemType)
      {
      *pNewItemType = REPORT_TYPE_COUNTER ;
      }
   return (pRetCounterGroup) ;
   }  // CounterRemoveItem

// GetNextCounter is used to get:
// If the current system is not empty, then get the
//    (next object first counter) or
//    (previous object last counter. )
// If the current system is empty, then get the
//    (next system first object first counter) or
//    (previous system last object last counter)
// Note - Any of the input pointers could be NULL pointer.
PCOUNTERGROUP GetNextCounter (PSYSTEMGROUP   pSystemGroup,
                              POBJECTGROUP   pObjectGroup,
                              PCOUNTERGROUP  pCounterGroup)
   {
   PCOUNTERGROUP  pRetCounter = NULL ;
   PCOUNTERGROUP  pCounterGrp ;
   POBJECTGROUP   pObjectGrp ;

   if (pCounterGroup && pCounterGroup->pCounterGroupNext)
      {
      pRetCounter = pCounterGroup->pCounterGroupNext ;
      }
   else if (pCounterGroup && pCounterGroup->pCounterGroupPrevious)
      {
      pRetCounter = pCounterGroup->pCounterGroupPrevious ;
      }
   else if (pObjectGroup && pObjectGroup->pObjectGroupNext)
      {
      // get the next Object first Counter
      pRetCounter = pObjectGroup->pObjectGroupNext->pCounterGroupFirst ;
      }
   else if (pObjectGroup && pObjectGroup->pObjectGroupPrevious)
      {
      // get the previous object last counter
      pCounterGrp = (pObjectGroup->pObjectGroupPrevious)->pCounterGroupFirst ;
      if (pCounterGrp)
         {
         // get the last counter group of this object
         for (;
              pCounterGrp->pCounterGroupNext ;
              pCounterGrp = pCounterGrp->pCounterGroupNext )
            {
            ;
            }
         }
      pRetCounter = pCounterGrp ;

      }
   else if (pSystemGroup && pSystemGroup->pSystemGroupNext)
      {
      // get next system first object first counter
      pObjectGrp = pSystemGroup->pSystemGroupNext->pObjectGroupFirst ;
      pRetCounter = pObjectGrp->pCounterGroupFirst ;
      }
   else if (pSystemGroup && pSystemGroup->pSystemGroupPrevious)
      {
      // get previous system last object last counter
      pObjectGrp = pSystemGroup->pSystemGroupPrevious->pObjectGroupFirst ;
      if (pObjectGrp)
         {
         // get the last object group of this system
         for (;
              pObjectGrp->pObjectGroupNext ;
              pObjectGrp = pObjectGrp->pObjectGroupNext )
            {
            ;
            }
         }

      if (pObjectGrp)
         {
         pCounterGrp = pObjectGrp->pCounterGroupFirst ;

if (pCounterGrp)
            {
            // get the last counter group of this object
            for (;
                 pCounterGrp->pCounterGroupNext ;
                 pCounterGrp = pCounterGrp->pCounterGroupNext )
               {
               ;
               }
            }
         pRetCounter = pCounterGrp ;
         }
      }

   return (pRetCounter) ;

   }  // GetNextCounter

//======================================//
// Object Group routines                //
//======================================//

void ReportObjectRect (PREPORT        pReport,
                       POBJECTGROUP   pObjectGroup,
                       LPRECT         lpRect)
   {  // ReportObjectRect
   lpRect->left = xObjectMargin ;
   lpRect->top = pObjectGroup->yFirstLine ;
   lpRect->right = lpRect->left + pObjectGroup->xWidth ;
   lpRect->bottom = lpRect->top + pReport->yLineHeight ;
   }  // ReportObjectRect

POBJECTGROUP ObjectGroupCreate (LPTSTR lpszObjectName)
   {  // ObjectGroupCreate
   POBJECTGROUP   pObjectGroup ;
   HDC            hDC ;
   PREPORT        pReport ;
   int            OldCounterWidth ;
   TCHAR          szLine [LongTextLen] ;

   pObjectGroup = MemoryAllocate (sizeof (OBJECTGROUP)) ;

   if (pObjectGroup)
      {
      pObjectGroup->pObjectGroupNext = NULL ;
      pObjectGroup->pCounterGroupFirst = NULL ;
      pObjectGroup->pColumnGroupFirst = NULL ;
      pObjectGroup->lpszObjectName = StringAllocate (lpszObjectName) ;

      hDC = GetDC (hWndReport) ;
      pReport = ReportData (hWndReport) ;
      SelectFont (hDC, pReport->hFontHeaders) ;

      TSPRINTF (szLine, szObjectFormat, lpszObjectName) ;
      pObjectGroup->xWidth = TextWidth (hDC, szLine) ;

      // re-calc. the max. counter group width
      OldCounterWidth = pReport->xMaxCounterWidth ;
      pReport->xMaxCounterWidth =
            max (pReport->xMaxCounterWidth,
                 pObjectGroup->xWidth + xObjectMargin) ;

      if (OldCounterWidth < pReport->xMaxCounterWidth)
          {
          // adjust the report width with the new counter width
          pReport->xWidth +=
               (pReport->xMaxCounterWidth - OldCounterWidth);
          }

      ReleaseDC (hWndReport, hDC) ;
      }  // if
   return (pObjectGroup) ;
   }  // ObjectGroupCreate

POBJECTGROUP GetObjectGroup (PSYSTEMGROUP pSystemGroup,
                          LPTSTR lpszObjectName)
   {
   POBJECTGROUP   pObjectGroup ;

   if (!pSystemGroup)
      return (FALSE) ;

   if (!pSystemGroup->pObjectGroupFirst)
      {
      pSystemGroup->pObjectGroupFirst = ObjectGroupCreate (lpszObjectName) ;
      if (pSystemGroup->pObjectGroupFirst)
         {
         pSystemGroup->pObjectGroupFirst->pParentSystem =
            pSystemGroup ;
         }
      return (pSystemGroup->pObjectGroupFirst) ;
      }

   for (pObjectGroup = pSystemGroup->pObjectGroupFirst ;
        pObjectGroup ;
        pObjectGroup = pObjectGroup->pObjectGroupNext)
      {  // for
      if (strsame (pObjectGroup->lpszObjectName, lpszObjectName))
         {
         return (pObjectGroup) ;
         }
      else if (!pObjectGroup->pObjectGroupNext)
         {  // if
         pObjectGroup->pObjectGroupNext =
            ObjectGroupCreate (lpszObjectName) ;

         if (pObjectGroup->pObjectGroupNext)
            {
            (pObjectGroup->pObjectGroupNext)->pParentSystem =
               pSystemGroup ;
            (pObjectGroup->pObjectGroupNext)->pObjectGroupPrevious =
               pObjectGroup ;
            }

         return (pObjectGroup->pObjectGroupNext) ;
         }  // if
      }  // for
   }  // GetObjectGroup

// ObjectGroupRemove removes the specified Object group
// from the Object double link list
BOOL ObjectGroupRemove (POBJECTGROUP *ppObjectGroupFirst,
                        POBJECTGROUP pObjectGroupRemove)
   {
   POBJECTGROUP  pObjectGroup ;

   if (*ppObjectGroupFirst == pObjectGroupRemove)
      {
      *ppObjectGroupFirst = (*ppObjectGroupFirst)->pObjectGroupNext ;
      if (*ppObjectGroupFirst)
         {
         // set up head of backward link list
         (*ppObjectGroupFirst)->pObjectGroupPrevious = NULL ;
         }

      // clean up the allocated memory
      ColumnGroupRemove (pObjectGroupRemove->pColumnGroupFirst) ;
      MemoryFree (pObjectGroupRemove->lpszObjectName) ;
      MemoryFree (pObjectGroupRemove) ;
      return (TRUE) ;
      }

   for (pObjectGroup = *ppObjectGroupFirst ;
        pObjectGroup->pObjectGroupNext ;
        pObjectGroup = pObjectGroup->pObjectGroupNext)
      {   // for
      if (pObjectGroup->pObjectGroupNext == pObjectGroupRemove)
         {
         pObjectGroup->pObjectGroupNext = pObjectGroupRemove->pObjectGroupNext ;
         if (pObjectGroup->pObjectGroupNext)
            {
            (pObjectGroup->pObjectGroupNext)->pObjectGroupPrevious =
               pObjectGroup ;
            }

         // clean up this object allocated memory and its column groups
         ColumnGroupRemove (pObjectGroupRemove->pColumnGroupFirst) ;
         MemoryFree (pObjectGroupRemove->lpszObjectName) ;
         MemoryFree (pObjectGroupRemove) ;
         return (TRUE) ;
         }  // if
      }  // for

   return (FALSE) ;
   }  // ObjectGroupRemove

// ObjectRemoveItem is called when user delete the selected object
PCOUNTERGROUP ObjectRemoveItem (PREPORT      pReport,
                                POBJECTGROUP pObjectGroup,
                                BOOL         bCleanUpLink,
                                enum REPORT_ITEM_TYPE  *pNewItemType)
   {
   PCOUNTERGROUP  pCounterGroup, pNextCounterGroup ;
   PSYSTEMGROUP   pSystemGroup ;
   PCOUNTERGROUP  pRetCounterGroup = NULL ;

   pSystemGroup = pObjectGroup->pParentSystem ;

   // remove all counter groups from this object
   for (pCounterGroup = pObjectGroup->pCounterGroupFirst ;
        pCounterGroup ;
        pCounterGroup = pNextCounterGroup )
      {
      pNextCounterGroup = pCounterGroup->pCounterGroupNext ;
      CounterRemoveItem (pReport, pCounterGroup, FALSE, NULL) ;
      }

   // remove all column groups from this group
   ColumnGroupRemove (pObjectGroup->pColumnGroupFirst) ;

   if (bCleanUpLink)
      {

      // get next counter group to get the focus
      if (pNewItemType)
         {
         pRetCounterGroup = GetNextCounter (
            pSystemGroup,
            pObjectGroup,
            NULL) ;

         if (pRetCounterGroup)
            {
            *pNewItemType = REPORT_TYPE_COUNTER ;
            }
         }

      // remove this object from its parent system group
      ObjectGroupRemove (&pSystemGroup->pObjectGroupFirst, pObjectGroup) ;

      if (!(pSystemGroup->pObjectGroupFirst))
         {
         SystemGroupRemove (&pReport->pSystemGroupFirst, pSystemGroup) ;
         }
      }
   else
      {
      // get rid of this object
      MemoryFree (pObjectGroup->lpszObjectName) ;
      MemoryFree (pObjectGroup) ;
      }

   return (pRetCounterGroup) ;

   }  // ObjectRemoveItem

//======================================//
// System Group routines                //
//======================================//
void ReportSystemRect (PREPORT        pReport,
                       PSYSTEMGROUP   pSystemGroup,
                       LPRECT         lpRect)
   {  // ReportSystemRect
   lpRect->left = xSystemMargin ;
   lpRect->top = pSystemGroup->yFirstLine ;
   lpRect->right = lpRect->left + pSystemGroup->xWidth ;
   lpRect->bottom = lpRect->top + pReport->yLineHeight ;
   }  // ReportSystemRect

PSYSTEMGROUP SystemGroupCreate (LPTSTR lpszSystemName)
   {  // SystemGroupCreate
   PSYSTEMGROUP   pSystemGroup ;
   HDC            hDC ;
   PREPORT        pReport ;
   TCHAR          szLine [LongTextLen] ;

   pSystemGroup = MemoryAllocate (sizeof (SYSTEMGROUP)) ;

   if (pSystemGroup)
      {
      pSystemGroup->pSystemGroupNext = NULL ;
      pSystemGroup->pObjectGroupFirst = NULL ;
      pSystemGroup->lpszSystemName = StringAllocate (lpszSystemName) ;

      // get width of system name
      hDC = GetDC (hWndReport) ;
      pReport = ReportData (hWndReport) ;
      SelectFont (hDC, pReport->hFontHeaders) ;

      TSPRINTF (szLine, szSystemFormat, lpszSystemName) ;
      pSystemGroup->xWidth = TextWidth (hDC, szLine) ;
      ReleaseDC (hWndReport, hDC) ;
      }  // if

   return (pSystemGroup) ;
   }  // SystemGroupCreate

PSYSTEMGROUP GetSystemGroup (PREPORT pReport,
                          LPTSTR lpszSystemName)
/*
   Effect;        Return a pointer to the system group of pReport with
                  a system name of lpszSystemName. If no system group
                  has that name, add a new system group.
*/
   {  // GetSystemGroup
   PSYSTEMGROUP   pSystemGroup ;

   if (!pReport->pSystemGroupFirst)
      {
      pReport->pSystemGroupFirst = SystemGroupCreate (lpszSystemName) ;
      return (pReport->pSystemGroupFirst) ;
      }

   for (pSystemGroup = pReport->pSystemGroupFirst ;
        pSystemGroup ;
        pSystemGroup = pSystemGroup->pSystemGroupNext)
      {  // for
      if (strsamei (pSystemGroup->lpszSystemName, lpszSystemName))
         return (pSystemGroup) ;
      else if (!pSystemGroup->pSystemGroupNext)
         {  // if
         pSystemGroup->pSystemGroupNext =
            SystemGroupCreate (lpszSystemName) ;
         if (pSystemGroup->pSystemGroupNext)
            {
            (pSystemGroup->pSystemGroupNext)->pSystemGroupPrevious =
               pSystemGroup ;
            }
         return (pSystemGroup->pSystemGroupNext) ;
         }  // if
      }  // for
   }  // GetSystemGroup

BOOL SystemGroupRemove (PSYSTEMGROUP *ppSystemGroupFirst,
                        PSYSTEMGROUP pSystemGroupRemove)
   {
   PSYSTEMGROUP  pSystemGroup ;

   if (*ppSystemGroupFirst == pSystemGroupRemove)
      {
      *ppSystemGroupFirst = (*ppSystemGroupFirst)->pSystemGroupNext ;
      if (*ppSystemGroupFirst)
         {
         (*ppSystemGroupFirst)->pSystemGroupPrevious = NULL ;
         }
      MemoryFree (pSystemGroupRemove->lpszSystemName) ;
      MemoryFree (pSystemGroupRemove) ;
      return (TRUE) ;
      }

   for (pSystemGroup = *ppSystemGroupFirst ;
        pSystemGroup->pSystemGroupNext ;
        pSystemGroup = pSystemGroup->pSystemGroupNext)
      {   // for
      if (pSystemGroup->pSystemGroupNext == pSystemGroupRemove)
         {
         pSystemGroup->pSystemGroupNext = pSystemGroupRemove->pSystemGroupNext ;
         if (pSystemGroup->pSystemGroupNext)
            {
            (pSystemGroup->pSystemGroupNext)->pSystemGroupPrevious =
               pSystemGroup ;
            }
         MemoryFree (pSystemGroupRemove->lpszSystemName) ;
         MemoryFree (pSystemGroupRemove) ;
         return (TRUE) ;
         }  // if
      }  // for

   return (FALSE) ;
   }  // SystemGroupRemove

// SystemRemoveItem is called when user deletes the selected System
PCOUNTERGROUP SystemRemoveItem (PREPORT      pReport,
                                PSYSTEMGROUP pSystemGroup,
                                BOOL         bCleanUpLink,
                                enum REPORT_ITEM_TYPE  *pNewItemType)
   {
   POBJECTGROUP   pObjectGroup, pNextObjectGroup ;
   PCOUNTERGROUP  pRetCounterGroup = NULL ;

   // remove all object groups from this system
   for (pObjectGroup = pSystemGroup->pObjectGroupFirst ;
        pObjectGroup ;
        pObjectGroup = pNextObjectGroup )
      {
      pNextObjectGroup = pObjectGroup->pObjectGroupNext ;
      ObjectRemoveItem (pReport, pObjectGroup, FALSE, NULL) ;
      }

   if (bCleanUpLink)
      {
      if (pNewItemType)
         {
         pRetCounterGroup = GetNextCounter (
            pSystemGroup,
            NULL,
            NULL) ;

         if (pRetCounterGroup)
            {
            *pNewItemType = REPORT_TYPE_COUNTER ;
            }
         }

      SystemGroupRemove (&pReport->pSystemGroupFirst, pSystemGroup) ;
      }
   else
      {
      // delete data from this system
      MemoryFree (pSystemGroup->lpszSystemName) ;
      MemoryFree (pSystemGroup) ;
      }

   return (pRetCounterGroup) ;

   }  // SystemRemoveItem

BOOL  ReportChangeFocus (HWND                   hWnd,
                         PREPORT                pReport,
                         REPORT_ITEM            SelectedItem,
                         enum REPORT_ITEM_TYPE  SelectedItemType,
                         int                    xOffset,
                         int                    yOffset,
                         RECT                   *pRect)
   {
   HDC         hDC ;
   BOOL        RetCode = FALSE ; // FALSE ==> same item being hit
   RECT        Rect ;
   REPORT_ITEM            PreviousItem ;
   enum REPORT_ITEM_TYPE  PreviousItemType ;

   if (pReport->CurrentItem.pLine != SelectedItem.pLine)
      {
      // not the same item
      RetCode = TRUE ;

      PreviousItemType = pReport->CurrentItemType ;
      PreviousItem.pLine = pReport->CurrentItem.pLine ;

      pReport->CurrentItemType = SelectedItemType ;
      pReport->CurrentItem.pLine = SelectedItem.pLine ;

      hDC = GetDC (hWnd) ;

      if (SelectedItemType == REPORT_TYPE_LINE)
         {
         SetWindowOrgEx (hDC, xOffset, yOffset, NULL) ;
         SelectFont (hDC, pReport->hFont) ;
         SetTextAlign (hDC, TA_RIGHT) ;
         SetBkColor (hDC, GetSysColor(COLOR_WINDOW)) ;
         DrawReportValue (hDC, pReport, SelectedItem.pLine) ;
         SetWindowOrgEx (hDC, -xOffset, -yOffset, NULL) ;
         }
      else
         {
         Rect = *pRect ;
         Rect.top -= yOffset ;
         Rect.bottom -= yOffset ;
         Rect.right -= xOffset ;
         Rect.left -= xOffset ;
         InvalidateRect (hWnd, &Rect, TRUE) ;
         }

      if (PreviousItemType == REPORT_TYPE_LINE)
         {
         SetWindowOrgEx (hDC, xOffset, yOffset, NULL) ;
         SelectFont (hDC, pReport->hFont) ;
         SetTextAlign (hDC, TA_RIGHT) ;
         SetBkColor (hDC, GetSysColor(COLOR_WINDOW)) ;
         DrawReportValue (hDC, pReport, PreviousItem.pLine) ;
         }
      else if (PreviousItemType != REPORT_TYPE_NOTHING)
         {
         if (PreviousItemType == REPORT_TYPE_SYSTEM)
            {
            ReportSystemRect (pReport, PreviousItem.pSystem, &Rect) ;
            }
         else if (PreviousItemType == REPORT_TYPE_OBJECT)
            {
            ReportObjectRect (pReport, PreviousItem.pObject, &Rect) ;
            }
         else if (PreviousItemType == REPORT_TYPE_COUNTER)
            {
            ReportCounterRect (pReport, PreviousItem.pCounter, &Rect) ;
            }
         else if (PreviousItemType == REPORT_TYPE_COLUMN)
            {
            ReportColumnRect (pReport, PreviousItem.pColumn, &Rect) ;
            }
         Rect.top -= yOffset ;
         Rect.bottom -= yOffset ;
         Rect.right -= xOffset ;
         Rect.left -= xOffset ;
         InvalidateRect (hWnd, &Rect, TRUE) ;
         }
      ReleaseDC (hWnd, hDC) ;
      }

   return (RetCode) ;
   }  // ReportChangeFocus

BOOL  OnReportLButtonDown (HWND hWnd,
                           WORD xPos,
                           WORD yPos)
   {
   PREPORT     pReport ;
   PLINE       pLine ;
   REPORT_ITEM PreviousItem ;
   REPORT_ITEM CurrentSelectedItem ;
   enum REPORT_ITEM_TYPE PreviousItemType ;
   RECT        rect ;
   POINT       pt ;
   int         xOffset, yOffset ;
   PSYSTEMGROUP   pSystemGroup ;
   POBJECTGROUP   pObjectGroup ;
   PCOUNTERGROUP  pCounterGroup ;
   PCOLUMNGROUP   pColumnGroup ;

   pReport = ReportData (hWnd) ;
   if (!pReport)
      return (FALSE) ;

   xOffset = GetScrollPos (hWnd, SB_HORZ) ;
   yOffset = GetScrollPos (hWnd, SB_VERT) ;
   pt.x = xPos + xOffset ;
   pt.y = yPos + yOffset ;
   PreviousItem = pReport->CurrentItem ;
   PreviousItemType = pReport->CurrentItemType ;

   for (pLine = pReport->pLineFirst ;
        pLine ;
        pLine = pLine->pLineNext)
      {  // for
      ReportLineValueRect (pReport, pLine, &rect) ;
      if (PtInRect (&rect, pt))
         {
         CurrentSelectedItem.pLine = pLine ;
         return (ReportChangeFocus (
            hWnd,
            pReport,
            CurrentSelectedItem,
            REPORT_TYPE_LINE,
            xOffset,
            yOffset,
            &rect)) ;
         }
      }  // for

   // check on hit on system, object, counter, column (parent+isntance names)
   for (pSystemGroup = pReport->pSystemGroupFirst ;
        pSystemGroup ;
        pSystemGroup = pSystemGroup->pSystemGroupNext)
      {  // for System...

      ReportSystemRect (pReport, pSystemGroup, &rect) ;
      if (PtInRect (&rect, pt))
         {
         CurrentSelectedItem.pSystem = pSystemGroup ;
         return (ReportChangeFocus (
            hWnd,
            pReport,
            CurrentSelectedItem,
            REPORT_TYPE_SYSTEM,
            xOffset,
            yOffset,
            &rect)) ;
         }

      for (pObjectGroup = pSystemGroup->pObjectGroupFirst ;
           pObjectGroup ;
           pObjectGroup = pObjectGroup->pObjectGroupNext)
         {  // for Object...

         ReportObjectRect (pReport, pObjectGroup, &rect) ;
         if (PtInRect (&rect, pt))
            {
            CurrentSelectedItem.pObject = pObjectGroup ;
            return (ReportChangeFocus (
               hWnd,
               pReport,
               CurrentSelectedItem,
               REPORT_TYPE_OBJECT,
               xOffset,
               yOffset,
               &rect)) ;
            }

         for (pColumnGroup = pObjectGroup->pColumnGroupFirst ;
              pColumnGroup ;
              pColumnGroup = pColumnGroup->pColumnGroupNext)
            {  // for Column...
            ReportColumnRect (pReport, pColumnGroup, &rect) ;
            if (PtInRect (&rect, pt))
               {
               CurrentSelectedItem.pColumn = pColumnGroup ;
               return (ReportChangeFocus (
                  hWnd,
                  pReport,
                  CurrentSelectedItem,
                  REPORT_TYPE_COLUMN,
                  xOffset,
                  yOffset,
                  &rect)) ;
               }
            }  // for Column

         for (pCounterGroup = pObjectGroup->pCounterGroupFirst ;
              pCounterGroup ;
              pCounterGroup = pCounterGroup->pCounterGroupNext)
            {  // for Counter...
            ReportCounterRect (pReport, pCounterGroup, &rect) ;
            if (PtInRect (&rect, pt))
               {
               CurrentSelectedItem.pCounter = pCounterGroup ;
               return (ReportChangeFocus (
                  hWnd,
                  pReport,
                  CurrentSelectedItem,
                  REPORT_TYPE_COUNTER,
                  xOffset,
                  yOffset,
                  &rect)) ;

               }
            }  // for Counter...
         }  // for Object...
      }  // for System...

   // nothing hit
   return (FALSE) ;
   }  // OnReportLButtonDown

BOOL ReportDeleteItem (HWND hWnd)
/*
   Effect:        Delete the current selected item.

*/
   {  // ReportDeleteItem

   HDC                     hDC ;
   PREPORT                 pReport ;
   REPORT_ITEM             NextItem ;
   enum  REPORT_ITEM_TYPE  NextItemType ;

   NextItemType = REPORT_TYPE_NOTHING ;
   NextItem.pLine = NULL ;

   pReport = ReportData (hWnd) ;
   if (pReport->CurrentItemType == REPORT_TYPE_NOTHING)
      {
      // nothing to delete...
      return (TRUE) ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_LINE)
      {
      NextItem.pLine = LineRemoveItem (pReport, &NextItemType) ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_SYSTEM)
      {
      NextItem.pCounter = SystemRemoveItem (
            pReport,
            pReport->CurrentItem.pSystem,
            TRUE,
            &NextItemType) ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_OBJECT)
      {
      NextItem.pCounter = ObjectRemoveItem (
            pReport,
            pReport->CurrentItem.pObject,
            TRUE,
            &NextItemType) ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_COUNTER)
      {
      NextItem.pCounter = CounterRemoveItem (
            pReport,
            pReport->CurrentItem.pCounter,
            TRUE,
            &NextItemType) ;
      }
   else if (pReport->CurrentItemType == REPORT_TYPE_COLUMN)
      {
      NextItem.pColumn = ColumnRemoveItem (
            pReport,
            pReport->CurrentItem.pColumn,
            TRUE,
            &NextItemType) ;
      }

   if (NextItemType != REPORT_TYPE_NOTHING)
      {
      pReport->CurrentItem.pLine = NextItem.pLine ;
      pReport->CurrentItemType = NextItemType ;
      }
   else
      {
      pReport->CurrentItem.pLine = pReport->pLineFirst ;
      pReport->CurrentItemType = REPORT_TYPE_LINE ;
      }

   if (pReport->pLineFirst)
      {
      BuildValueListForSystems (
         pReport->pSystemFirst,
         pReport->pLineFirst) ;
      }
   else
      {
      // no more line, no more timer...
      pReport->xWidth = 0 ;
      pReport->yHeight = 0 ;
      pReport->xMaxCounterWidth = 0 ;
      ClearReportTimer (pReport) ;

      FreeSystems (pReport->pSystemFirst) ;
      pReport->pSystemFirst = NULL ;
      pReport->pSystemGroupFirst = NULL ;
      pReport->CurrentItemType = REPORT_TYPE_NOTHING ;
      pReport->CurrentItem.pLine = NULL ;

      }

   //=============================//
   // Calculate report positions  //
   //=============================//

   hDC = GetDC (hWnd) ;
   SetReportPositions (hDC, pReport) ;

   if (!pReport->pLineFirst)
      {
      SelectFont (hDC, pReport->hFont) ;
      pReport->xValueWidth = TextWidth (hDC, szValuePlaceholder) ;
      }

   ReleaseDC (hWnd, hDC) ;
   WindowInvalidate (hWnd) ;

   return (TRUE) ;
   }  // ReportDeleteItem


RPTOPTNS.C

#include "perfmon.h"

#include "report.h"     // for ReportData
#include "utils.h"
#include "playback.h"   // for PlayingBackLog
#include "pmhelpid.h"   // Help IDs

extern BOOL LocalManualRefresh ;
static DWORD iIntervalMSecs ;

void static OnInitDialog (HWND hDlg, PREPORT pReport)
   {
   int            i ;

   for (i = 0 ;
        i < NumIntervals ;
        i++)
      CBAddInt (DialogControl (hDlg, IDD_REPORTOPTIONSINTERVAL),
                aiIntervals [i]) ;

   DialogSetInterval (hDlg, IDD_REPORTOPTIONSINTERVAL,
      pReport->iIntervalMSecs) ;

   LocalManualRefresh = pReport->bManualRefresh ;

   if (LocalManualRefresh && !PlayingBackLog())
      {
      DialogEnable (hDlg, IDD_REPORTOPTIONSINTERVAL, FALSE) ;
      DialogEnable (hDlg, IDD_REPORTOPTIONSINTERVALTEXT, FALSE) ;
      }

   CheckRadioButton(hDlg,
      IDD_REPORTOPTIONSMANUALREFRESH,
      IDD_REPORTOPTIONSPERIODIC,
      LocalManualRefresh ? IDD_REPORTOPTIONSMANUALREFRESH :
      IDD_REPORTOPTIONSPERIODIC) ;

   WindowCenter (hDlg) ;

   }

int FAR WINAPI ReportOptionsDlgProc (HWND hDlg,
                                     unsigned iMessage,
                                     WPARAM wParam,
                                     LONG lParam)
   {
   BOOL           bHandled ;

   bHandled = TRUE ;
   switch (iMessage)
      {
      case WM_INITDIALOG:
         dwCurrentDlgID = HC_PM_idDlgOptionReport ;
         OnInitDialog (hDlg, (PREPORT) lParam) ;
         return  (TRUE) ;

      case WM_CLOSE:
         dwCurrentDlgID = 0 ;
         EndDialog (hDlg, 0) ;
         break ;

      case WM_COMMAND:
         switch(wParam)
            {
            case IDD_OK:
               {
               FLOAT    eIntervalMSec ;

               eIntervalMSec = DialogFloat (hDlg, IDD_REPORTOPTIONSINTERVAL, NULL) ;

               if (eIntervalMSec > MAX_INTERVALSEC ||
                   eIntervalMSec < MIN_INTERVALSEC)
                  {
                  DlgErrorBox (hDlg, ERR_BADTIMEINTERVAL) ;
                  SetFocus (DialogControl (hDlg, IDD_REPORTOPTIONSINTERVAL)) ;
                  EditSetTextEndPos (hDlg, IDD_REPORTOPTIONSINTERVAL) ;
                  return (FALSE) ;
                  break ;
                  }
               eIntervalMSec = eIntervalMSec * (FLOAT) 1000.0 +
                  (FLOAT) 0.5 ;

               iIntervalMSecs = (DWORD) (eIntervalMSec);
               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, 1) ;
               }
               break ;

            case IDD_CANCEL:
               dwCurrentDlgID = 0 ;
               EndDialog (hDlg, 0) ;
               break ;

            case IDD_REPORTOPTIONSPERIODIC :
            case IDD_REPORTOPTIONSMANUALREFRESH :

               // check if the Manual refresh is currently checked.
               // Then toggle the ManualRefresh button
               LocalManualRefresh =
                  (wParam == IDD_REPORTOPTIONSMANUALREFRESH) ;

               CheckRadioButton(hDlg,
                  IDD_REPORTOPTIONSMANUALREFRESH,
                  IDD_REPORTOPTIONSPERIODIC,
                  LocalManualRefresh ? IDD_REPORTOPTIONSMANUALREFRESH :
                  IDD_REPORTOPTIONSPERIODIC ) ;

               DialogEnable (hDlg, IDD_REPORTOPTIONSINTERVAL, !LocalManualRefresh) ;
               DialogEnable (hDlg, IDD_REPORTOPTIONSINTERVALTEXT, !LocalManualRefresh) ;
               break ;

            case IDD_DISPLAYHELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            default:
               bHandled = FALSE ;
               break;
            }
         break;

      default:
            bHandled = FALSE ;
         break ;
      }  // switch

   return (bHandled) ;
   }  // ReportOptionsDlgProc

BOOL DisplayReportOptions (HWND hWndParent,
                           HWND hWndReport)
   {  // DisplayReportOptions
   PREPORT        pReport ;

   pReport = ReportData (hWndParent) ;

   if (DialogBoxParam (hInstance, idDlgReportOptions,
                       hWndParent, (DLGPROC) ReportOptionsDlgProc,
                       (LPARAM) pReport))
      {  // if
      pReport->iIntervalMSecs = iIntervalMSecs ;
      if (LocalManualRefresh != pReport->bManualRefresh)
         {
         ToggleReportRefresh (hWndReport) ;
         }
      else
         {
         SetReportTimer (pReport) ;
         }
      }  // if

   return (TRUE) ;
   }  // DisplayReportOptions


RPTOPTNS.H

BOOL DisplayReportOptions (HWND hWndParent,
                           HWND hWndReport) ;


SIZES.H

#define FilePathLen                256

//==========================================================================//
//                                 Other                                    //
//==========================================================================//

#define ControlStringLen           160
#define MenuStringLen               80
#define MessageLen                 160
#define ProfileValueLen            120
#define ProfileKeyLen               80
#define WindowCaptionLen            40
#define ResourceStringLen           40

#define MiscTextLen                 80
#define ShortTextLen                25
#define LongTextLen                256
#define TEMP_BUF_LEN               256

//==========================================================================//
//                         Perfmon-Related Sizes                            //
//==========================================================================//

#define PerfObjectLen               80

#define MAX_SYSTEM_NAME_LENGTH      128
#define DEFAULT_COUNTER_NAME_SIZE   (2 * 4096)
#define DEFAULT_COUNTER_HELP_SIZE   (24 * 4096)
#define SMALL_BUF_LEN   16  // For numeric strings done in ASCII or whatever
#define STARTING_SYSINFO_SIZE   (48L * 1024L)
#define NM_BUF_SIZE         64

// the min and max of time interval in sec
#define MAX_INTERVALSEC    (FLOAT)2000000.0
#define MIN_INTERVALSEC    (FLOAT)0.0

// the min and max for the Graph Vertical max.
#define MAX_VERTICAL        2000000000
#define MIN_VERTICAL        1


STATUS.C

#include "perfmon.h"
#include "status.h"  // External declarations for this file
#include <stdio.h>   // for sprintf.
#include <stdarg.h>  // For ANSI variable args. Dont use UNIX <varargs.h>

#include "log.h"        // for LogCollecting
#include "perfmops.h"   // for SmallFileSizeString
#include "playback.h"   // for PlayingBackLog
#include "utils.h"

//================================//
// Options for PaintStatusBar     //
//================================//
#define     PaintText         1
#define     PaintIcons        2
#define     PaintBoundary     4
#define     PaintAll          (PaintText + PaintIcons + PaintBoundary)

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define szStatusClass          TEXT("PerfmonStatusClass")
#define dwStatusClassStyle     (CS_HREDRAW | CS_VREDRAW | CS_OWNDC)
#define iStatusClassExtra      (0)
#define iStatusWindowExtra     (0)
#define dwStatusWindowStyle    (WS_CHILD | WS_VISIBLE)

#define szAlertMax            TEXT(" 99 ")
#define szAlertFormat         TEXT(" %2d ")
#define szAlertOverflow       TEXT(" ++")

#define szLogMax              TEXT(" 9,999.9M ")

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

HDC            hStatusDC ;                   // for OWN_DC

HDC            hLogBitmapDC ;
int            xLogBitmapWidth ;
int            yLogBitmapHeight ;

HDC            hAlertBitmapDC ;
int            xAlertBitmapWidth ;
int            yAlertBitmapHeight ;

int            yStatusHeight ;
int            xStatusAlertWidth ;           // of alert bm and num alerts
int            xStatusLogWidth ;             // of log bitmap and file size

int            szStatusLineLen ;             // no. of char. in szStatusLine
TCHAR          szStatusLine [MessageLen+ResourceStringLen] ;
TCHAR          szCurrentActivity [ResourceStringLen] ;
TCHAR          szStatusFormat  [ResourceStringLen] ;
TCHAR          szStatusFormat2 [ResourceStringLen] ;

HBITMAP        hBitmapAlertStatus ;
HBITMAP        hBitmapLogStatus ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define StatusTopMargin()     (2)
#define StatusLeftMargin()    (2)

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void DrawAlerts (HDC hDC,
                 LPRECT lpRect)
   {
   TCHAR          szText [10] ;
   RECT           rectText ;
   int            yBitmapTop ;

   if (!iUnviewedAlerts)
      return ;

   yBitmapTop = lpRect->top +
                (lpRect->bottom -
                 lpRect->top -
                 yAlertBitmapHeight) / 2 ;

   SetTextColor (hDC, crLastUnviewedAlert) ;

   BitBlt (hDC,                           // DC for Destination surface
      lpRect->right - xStatusAlertWidth,  // x pos for Destination surface
      yBitmapTop,                         // y for Destination surface
      xAlertBitmapWidth,                  // width of bitmap
      yAlertBitmapHeight,                 // height of bitmap
      hAlertBitmapDC,                     // DC for source surface
      0, 0,                               // location in source surface
      SRCCOPY) ;                          // ROP code

   SetTextColor (hDC, crBlack) ;

   if (iUnviewedAlerts > 99)
      lstrcpy (szText, szAlertOverflow) ;
   else
      TSPRINTF (szText, szAlertFormat, iUnviewedAlerts) ;

   rectText.left = lpRect->right - xStatusAlertWidth + xAlertBitmapWidth ;
   rectText.top = lpRect->top + 1 ;
   rectText.right = lpRect->right - 1 ;
   rectText.bottom = lpRect->bottom - 1 ;

   ExtTextOut (hDC, rectText.left, rectText.top, ETO_CLIPPED | ETO_OPAQUE,
      &rectText, szText, lstrlen (szText), NULL) ;

   lpRect->right -= (xStatusAlertWidth + (xAlertBitmapWidth >> 2)) ;
   }  // DrawAlerts

void DrawLog (HDC hDC,
              LPRECT lpRect)
   {
   TCHAR          szText [10] ;
   RECT           rectText ;
   int            yBitmapTop ;

   if (!LogCollecting (hWndLog))
      return ;

   yBitmapTop = lpRect->top +
                (lpRect->bottom -
                 lpRect->top -
                 yLogBitmapHeight) / 2 ;
   BitBlt (hDC,                           // DC for Destination surface
      lpRect->right - xStatusLogWidth,    // x pos for Destination surface
      yBitmapTop,                         // y for Destination surface
      xLogBitmapWidth,                    // width of bitmap
      yLogBitmapHeight,                   // height of bitmap
      hLogBitmapDC,                       // DC for source surface
      0, 0,                               // location in source surface
      SRCCOPY) ;                          // ROP code

   SmallFileSizeString (LogFileSize (hWndLog), szText) ;

   rectText.left = lpRect->right - xStatusLogWidth +
      xLogBitmapWidth + 1 ;
   rectText.top = lpRect->top + 1 ;
   rectText.right = lpRect->right - 1 ;
   rectText.bottom = lpRect->bottom - 1 ;

   ExtTextOut (hDC, rectText.left, rectText.top, ETO_CLIPPED | ETO_OPAQUE,
      &rectText, szText, lstrlen (szText), NULL) ;

   lpRect->right -= xStatusLogWidth ;
   }  // DrawLog

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnCreate (HWND hWnd)
/*
   Effect:        Perform any actions needed when a status window is created.
                  In particular, set the instance data to initial values,
                  determine the size and placement of the various elements
                  of the status display.

   Called By:     StatusWndProc only, in response to a WM_CREATE message.
*/
   {  // OnCreate
   HDC            hDC ;

   hBitmapAlertStatus = LoadBitmap (hInstance, idBitmapAlertStatus) ;
   hBitmapLogStatus = LoadBitmap (hInstance, idBitmapLogStatus) ;

   hDC = hStatusDC = GetDC (hWnd) ;
   SelectFont (hDC, hFontScales) ;
   SetBkColor (hDC, ColorBtnFace) ;
   SetTextAlign (hDC, TA_LEFT) ;
   SetBkMode (hDC, OPAQUE) ;

   yStatusHeight = 2 * StatusTopMargin () +
                   FontHeight (hDC, TRUE) +
                   2 * ThreeDPad ;

   BitmapDimemsion (hBitmapLogStatus, &yLogBitmapHeight, &xLogBitmapWidth) ;
   BitmapDimemsion (hBitmapAlertStatus, &yAlertBitmapHeight, &xAlertBitmapWidth) ;

   // pre-load the log and alert bitmaps for perfmormance

   hLogBitmapDC = CreateCompatibleDC (hDC) ;
   SelectObject (hLogBitmapDC, hBitmapLogStatus) ;

   hAlertBitmapDC = CreateCompatibleDC (hDC) ;
   SelectObject (hAlertBitmapDC, hBitmapAlertStatus) ;

   xStatusAlertWidth = xAlertBitmapWidth + 1 +
                       TextWidth (hDC, szAlertMax) ;
   xStatusLogWidth = xLogBitmapWidth +
                     TextWidth (hDC, szLogMax) ;

   StringLoad (IDS_CURRENTACTIVITY, szCurrentActivity) ;
   StringLoad (IDS_STATUSFORMAT, szStatusFormat) ;
   StringLoad (IDS_STATUSFORMAT2, szStatusFormat2) ;

   StatusLineReady (hWnd) ;
   }  // OnCreate

void static OnDestroy (HWND hWnd)
   {
   if (hBitmapAlertStatus)
      {
      DeleteObject (hBitmapAlertStatus) ;
      hBitmapAlertStatus = 0 ;
      }

   if (hBitmapLogStatus)
      {
      DeleteObject (hBitmapLogStatus) ;
      hBitmapLogStatus = 0 ;
      }
   ReleaseDC (hWnd, hStatusDC) ;

   }  // OnDestroy

void static PaintStatusBar (HWND hWnd, HDC hDC, int PaintOptions)
/*
   Effect:        Paint the invalid surface of hWnd. Draw each label, each
                  recessed value box, and each value.

   Called By:     StatusWndProc only, in response to a WM_PAINT message.
*/
   {
   RECT           rectClient ;

   if (bPerfmonIconic)
      {
      // no need to draw anything if iconic
      return ;
      }

   GetClientRect (hWnd, &rectClient) ;

   RectContract (&rectClient, StatusTopMargin (), StatusLeftMargin ()) ;

   if (PaintOptions == PaintAll)
      {
      ThreeDConcave1 (hDC,
                     rectClient.left, rectClient.top,
                     rectClient.right, rectClient.bottom) ;
      }

   rectClient.left += StatusLeftMargin () ;

   // Always draw the icons and need to draw log before Alerts!
   DrawLog (hDC, &rectClient) ;
   DrawAlerts (hDC, &rectClient) ;

   if (PaintOptions & PaintText)
      {
      rectClient.left += 1 ;
      rectClient.top += 1 ;
      rectClient.right -= 1 ;
      rectClient.bottom -= 1 ;
      ExtTextOut (hDC, rectClient.left, rectClient.top, ETO_CLIPPED | ETO_OPAQUE,
         &rectClient, szStatusLine, szStatusLineLen, NULL) ;
      }

   }  // PaintStatusBar

LRESULT APIENTRY StatusWndProc (HWND hWnd,
                                WORD wMsg,
                                WPARAM wParam,
                                LPARAM lParam)
   {  // StatusWndProc
   BOOL           bCallDefProc ;
   LRESULT        lReturnValue ;
   HDC            hDC ;
   PAINTSTRUCT    ps ;

   bCallDefProc = FALSE ;
   lReturnValue = 0L ;

   switch (wMsg)
      {  // switch
      case WM_PAINT:
         hDC = BeginPaint (hWnd, &ps) ;
         PaintStatusBar (hWnd, hDC, PaintAll) ;
         EndPaint (hWnd, &ps) ;
         break ;

      case WM_CREATE:
         OnCreate (hWnd) ;
         break ;

      case WM_DESTROY:
         OnDestroy (hWnd) ;
         break ;

      default:
         bCallDefProc = TRUE ;
      }  // switch

   if (bCallDefProc)
      lReturnValue = DefWindowProc (hWnd, wMsg, wParam, lParam) ;

   return (lReturnValue);
   }  // StatusWndProc

int StatusHeight (HWND hWnd)
/*
   Effect:        A status window has a preferred height, based on the font
                  used in its display. Return the preferred height, determined
                  when the window was created.

   Assert:        OnCreate has already been called, and it set
                  StatusData.yHeight.
*/
   {
   return (yStatusHeight) ;
   }

HWND CreatePMStatusWindow (HWND hWnd)
   {
   return (CreateWindow (szStatusClass,       // class
                         NULL,                // caption
                         dwStatusWindowStyle, // window style
                         0, 0,                // position
                         0, 0,                // size
                         hWnd,                // parent window
                         NULL,                // menu
                         hInstance,           // program instance
                         NULL)) ;             // user-supplied data
   }  // CreateStatusWindow

BOOL StatusInitializeApplication (void)
/*
   Called By:     InitializeApplication only
*/
   {
   WNDCLASS       wc ;

   wc.style          = dwStatusClassStyle ;
   wc.lpfnWndProc    = (WNDPROC) StatusWndProc ;
   wc.hInstance      = hInstance ;
   wc.cbClsExtra     = iStatusClassExtra ;
   wc.cbWndExtra     = iStatusWindowExtra ;
   wc.hIcon          = NULL ;
   wc.hCursor        = LoadCursor (NULL, IDC_ARROW) ;
   wc.hbrBackground  = hbLightGray ;
   wc.lpszMenuName   = NULL ;
   wc.lpszClassName  = szStatusClass ;

   return (RegisterClass (&wc)) ;
   }

BOOL _cdecl StatusLine (HWND hWnd,
                        WORD wStringID, ...)
   {
   TCHAR          szFormat [MessageLen] ;
   va_list        vaList ;

   if (wStringID == 0)
      {
      return (TRUE) ;
      }

   strclr (szStatusLine) ;

   if (LoadString (hInstance, wStringID, szFormat, MessageLen))
      {
      va_start (vaList, wStringID) ;
      TSPRINTF (szStatusLine, szFormat, vaList) ;
//      wvsprintf (szStatusLine, szFormat, vaList) ;
      va_end (vaList) ;
      dwCurrentMenuID = MenuIDToHelpID (wStringID) ;
      szStatusLineLen = lstrlen (szStatusLine) ;
      }
   else
      {
      dwCurrentMenuID = 0 ;
      szStatusLineLen = 0 ;
      }
   PaintStatusBar (hWndStatus, hStatusDC, PaintText + PaintIcons) ;

   return (TRUE) ;
   }  // StatusLine

void StatusLineReady (HWND hWnd)
   {
   int         stringLen ;
   LPTSTR      pFileName = NULL ;

   TSPRINTF (szStatusLine, szStatusFormat,
             PlayingBackLog () ?
               PlaybackLog.szFileTitle : szCurrentActivity) ;

   switch (iPerfmonView)
      {
      case IDM_VIEWCHART:
         pFileName = pChartFileName ;
         break ;

      case IDM_VIEWALERT:
         pFileName = pAlertFileName ;
         break ;

      case IDM_VIEWLOG:
         pFileName = pLogFileName ;
         break ;

      case IDM_VIEWREPORT:
         pFileName = pReportFileName ;
         break ;
      }

   if (pFileName)
      {
      stringLen = lstrlen (szStatusLine) ;
      TSPRINTF (&szStatusLine[stringLen], szStatusFormat2, pFileName) ;
      }

   szStatusLineLen = lstrlen (szStatusLine) ;

   PaintStatusBar (hWndStatus, hStatusDC, PaintText + PaintIcons) ;
   }

void StatusUpdateIcons (HWND hWndStatus)
   {
   PaintStatusBar (hWndStatus, hStatusDC, PaintIcons) ;
   }


STATUS.H

HWND CreatePMStatusWindow (HWND hWnd) ;

BOOL StatusInitializeApplication (void) ;

int StatusHeight (HWND hWnd) ;

BOOL _cdecl StatusLine (HWND hWnd,
                        WORD wStringID, ...) ;

void StatusLineReady (HWND hWnd) ;

void StatusUpdateIcons (HWND hWndStatus) ;


STRIDS.H

// File open related strings
#define IDS_CHARTFILE        700
#define IDS_CHARTFILEEXT     701
#define IDS_ALERTFILE        702
#define IDS_ALERTFILEEXT     703
#define IDS_LOGFILE          704
#define IDS_LOGFILEEXT       705
#define IDS_REPORTFILE       706
#define IDS_REPORTFILEEXT    707
#define IDS_ALLFILES         708
#define IDS_ALLFILESEXT      709
#define IDS_SAVELOGFILE      710
#define IDS_SAVELOGFILEEXT   711
#define IDS_FILEOPEN_TITLE   712
#define IDS_WORKSPACEFILE    713
#define IDS_WORKSPACEFILEEXT 714
#define IDS_EXPORTFILE       715
#define IDS_EXPORTFILECSVEXT 716
#define IDS_EXPORTFILETSV    717
#define IDS_EXPORTFILETSVEXT 718

/*STRING ID'S                */

#define IDS_APPNAME       800
#define IDS_NONAME          801
#define IDS_LEGENDNAME      803
#define IDS_SYSNAME         804
#define IDS_OBJNAME         805
#define IDS_COUNTERNAME     806
#define IDS_INSTNAME        807
#define IDS_SCALENAME       808
#define IDS_PARENT          809
#define IDS_CURRENT         810
#define IDS_SYSTEM_PROBLEM  811

// ERROR MESSAGES
#define IDS_NONNUMERIC     817     // number input is nonnumeric
#define IDS_MANYCLOCKS     818     // too many clocks
#define IDS_CANTDOTIMER      819     // can't allocate timer
#define IDS_BADTIMERMSG      820     // window receives bad mesage
#define IDS_BADERROR         821     // bad error message
#define IDS_NO_MEMORY        822     // out of memory
#define IDS_BADHMOD          823     // received bad module handle
#define IDS_BADBRUSH         824     // NULL brush created
#define IDS_CANT_REALLOC     825     // can't realloc graph
#define IDS_COUNTER_NOT_IMP  826     // counter not implemented yet
#define IDS_SAVEAS_TITLE     827
#define IDS_SAVEASW_TITLE    828
#define IDS_GRAPH_FNAME      829
#define IDS_LOG_FNAME        830
#define IDS_ALERT_FNAME      831
#define IDS_REPORT_FNAME     832
#define IDS_WORK_FNAME       833
#define IDS_EXPORTAS_TITLE   834

#define IDS_FILE_ERROR       850     // file error

#define IDS_STATUSTIME       901
#define IDS_STATUSLAST       902
#define IDS_STATUSAVG        903
#define IDS_STATUSMIN        904
#define IDS_STATUSMAX        905

#define IDS_LABELCOLOR       906
#define IDS_LABELSCALE       907
#define IDS_LABELCOUNTER     908
#define IDS_LABELINSTANCE    909
#define IDS_LABELPARENT      910
#define IDS_LABELOBJECT      911
#define IDS_LABELSYSTEM      912

#define IDS_CLOSED           913
#define IDS_PAUSED           914
#define IDS_COLLECTING       915

#define IDS_LOGOPTIONS       916

#define IDS_STOP             917
#define IDS_RESUME           918

#define IDS_EDIT             919
#define IDS_OPTIONS          920

#define IDS_START            921
#define IDS_PAUSE            922
#define IDS_DONE             923

#define IDS_OPENLOG          925
#define IDS_NEEDALERTVALUE   926

#define IDS_LABELVALUE       927

#define IDS_ADDTOCHART       928
#define IDS_ADDTOALERT       929
#define IDS_ADDTOREPORT      930

#define IDS_SYSTEMFORMAT     931
#define IDS_OBJECTFORMAT     932

#define IDS_STATUSFORMAT     934
#define IDS_CURRENTACTIVITY  935

#define IDS_EDITCHART        936
#define IDS_EDITALERT        937
#define IDS_EDITREPORT       938

#define IDS_OK               939
#define IDS_SAVECHART        940
#define IDS_MODIFIEDCHART    941

#define IDS_COMPUTERNOTFOUND 942
#define IDS_ALLOBJECTS       943
#define IDS_DEFAULTPATH      944
#define IDS_CREATELOGFILE    945

#define IDS_SAVEREPORT       946
#define IDS_MODIFIEDREPORT   947

#define IDS_STOPBEFORESTART  948
#define IDS_TIMEFRAME        949
#define IDS_STATUSFORMAT2    950

// the folowing strings are used by Export***
#define IDS_REPORT_HEADER    970
#define IDS_REPORT_LOGFILE   971
#define IDS_INTERVAL_FORMAT  972
#define IDS_CHARTINT_FORMAT  973

#define IDS_START_TEXT       974
#define IDS_STOP_TEXT        975
#define IDS_ALERT_TRIGGER    976
#define IDS_EXPORT_DATE      977
#define IDS_EXPORT_TIME      978

#define IDS_HELPFILE_NAME    979
#define IDS_NEWDATA_BOOKMARK 980

// default strings used in Date/Time
#define IDS_SHORT_DATE_FORMAT 1101
#define IDS_TIME_FORMAT       1102
#define IDS_S1159             1103
#define IDS_S2359             1104

// string id for default scale factor
#define IDS_DEFAULT           1120

// string id for Alert msg when system down
#define IDS_SYSTEM_DOWN       1121
#define IDS_SYSTEM_UP         1122

// the following are error strings
// error relating to files
#define ERR_LOG_FILE          2000
#define ERR_EXPORT_FILE       2001
#define ERR_SETTING_FILE      2002
#define ERR_BAD_LOG_FILE      2003
#define ERR_BAD_SETTING_FILE  2004
#define ERR_CORRUPT_LOG       2005
#define ERR_CANT_OPEN         2006
#define ERR_CANT_RELOG_DATA   2007

// system errors
#define ERR_NO_MEMORY         2100

// error relating to dialogs
#define ERR_COUNTER_NOT_IMP   2200
#define ERR_NEEDALERTVALUE    2201
#define ERR_STOPBEFORESTART   2202
#define ERR_COMPUTERNOTFOUND  2203
#define ERR_BADVERTMAX        2204
#define ERR_BADTIMEINTERVAL   2205

// error in computer name specified on command line
#define ERR_BADCOMPUTERNAME2300


SYSTEM.C

//
//  Foreign computer support needs more work (a-robw)
//
#ifdef FOREIGN_COMPUTER_SUPPORT
#undef FOREIGN_COMPUTER_SUPPORT
#endif

#include "perfmon.h"
#include "system.h"     // external declarations for this file

#include "perfdata.h"
#include "perfmops.h"
#include "playback.h"   // for PlayingBackLog
#include "pmemory.h"
#include "utils.h"      // for strsame, et al
#include "sizes.h"

DWORD
SystemCount(
    PPERFSYSTEM pSystemFirst
)
{
    PPERFSYSTEM       pSystem ;
    DWORD           iNumSystems ;

    iNumSystems = 0 ;

    for (pSystem = pSystemFirst ;
         pSystem ;
         pSystem = pSystem->pSystemNext) {
        iNumSystems++ ;
    }

    return iNumSystems ;
}

BOOL
SystemSetupThread (PPERFSYSTEM pSystem)
{
    DWORD           dwThreadID ;
    HANDLE          hThread ;
    HANDLE          hStateDataMutex ;
    HANDLE          hPerfDataEvent ;
    SECURITY_ATTRIBUTES  SecAttr ;
    PPERFDATA       pSystemPerfData ;

    SecAttr.nLength = sizeof (SecAttr) ;
    SecAttr.bInheritHandle = TRUE ;
    SecAttr.lpSecurityDescriptor = NULL ;

    hThread = CreateThread (&SecAttr, 1024L,
        (LPTHREAD_START_ROUTINE)PerfDataThread, (LPVOID)(pSystem), 0L, &dwThreadID);

    if (!hThread) {
        SystemFree (pSystem, TRUE);
        return (FALSE) ;
    }

    // create a State Data Lock mutex
    hStateDataMutex = CreateMutex (&SecAttr, FALSE, NULL);
    if (!hStateDataMutex) {
        CloseHandle (hThread) ;
        SystemFree (pSystem, TRUE);
        return (FALSE);
    }
    hPerfDataEvent = CreateEvent (&SecAttr, TRUE, 0L, NULL) ;
    if (!hPerfDataEvent) {
        CloseHandle (hStateDataMutex) ;
        CloseHandle (hThread) ;
        SystemFree (pSystem, TRUE);
        return (FALSE);
    }

    // allocate Perfdata
    pSystemPerfData = (PPERFDATA) MemoryAllocate (4096L) ;
    if (!pSystemPerfData) {
        CloseHandle (hPerfDataEvent) ;
        CloseHandle (hStateDataMutex) ;
        CloseHandle (hThread) ;
        SystemFree (pSystem, TRUE);
        return (FALSE);
    }
    // now setup the pSystem..
    pSystem->dwThreadID = dwThreadID ;
    pSystem->hThread = hThread ;
    pSystem->hPerfDataEvent = hPerfDataEvent ;
    pSystem->pSystemPerfData = pSystemPerfData ;
    pSystem->hStateDataMutex = hStateDataMutex ;

    return (TRUE) ;
}

PPERFSYSTEM
SystemCreate (
    LPCTSTR lpszSystemName
)
{
    PPERFSYSTEM     pSystem ;
    PPERFDATA       pLocalPerfData;
    DWORD           Status ;
    DWORD           dwMemSize;
    TCHAR           GlobalValueBuffer[] = L"Global" ;
    TCHAR           ForeignValueBuffer[8+MAX_SYSTEM_NAME_LENGTH+1] =
                    L"Foreign " ;

    // attempt to allocate system data structure

    pSystem = MemoryAllocate (sizeof (PERFSYSTEM)) ;
    if (!pSystem) {
        SetLastError (ERROR_OUTOFMEMORY) ;
        return (NULL) ;
    }

    // initialize name and help table pointers

    pSystem->CounterInfo.pNextTable = NULL;
    pSystem->CounterInfo.dwLangId = 0;
    pSystem->CounterInfo.dwLastId = 0;
    pSystem->CounterInfo.TextString = NULL;

    lstrcpy (pSystem->sysName, lpszSystemName) ;

    // try to open key to registry, error code is in GetLastError()

    pSystem->sysDataKey = OpenSystemPerfData(lpszSystemName);

    // if a Null Key was returned then:
    //  a) there's no such computer
    //  b) the system is a foreign computer
    //
    //  before giving up, then see if it's a foreign computer

    if (!pSystem->sysDataKey) {

        // build foreign computer string

        lstrcat(ForeignValueBuffer, lpszSystemName) ;

        // assign System value name pointer to the local variable for trial

        pSystem->lpszValue = ForeignValueBuffer;

        // try to get data from the computer to see if it's for real
        // otherwise, give up and return NULL

        pLocalPerfData = MemoryAllocate (STARTING_SYSINFO_SIZE);
        if (pLocalPerfData == NULL) { // no mem so give up
            SetLastError (ERROR_OUTOFMEMORY);
            pSystem->lpszValue = NULL;
            SystemFree (pSystem, TRUE);
            return (NULL);
        } else {
            pSystem->sysDataKey = HKEY_PERFORMANCE_DATA; // local machine
            bCloseLocalMachine = TRUE ;

            dwMemSize = STARTING_SYSINFO_SIZE;
            Status = GetSystemPerfData (
                    pSystem->sysDataKey,
                    pSystem->lpszValue,
                    pLocalPerfData,
                    &dwMemSize);

            // success means a valid buffer came back
            // more data means someone tried (so it's probably good (?)

            if (!((Status == ERROR_MORE_DATA) || (Status == ERROR_SUCCESS)) ||
                !((pLocalPerfData->Signature[0] == (WCHAR)'P') &&
                  (pLocalPerfData->Signature[1] == (WCHAR)'E') &&
                  (pLocalPerfData->Signature[2] == (WCHAR)'R') &&
                  (pLocalPerfData->Signature[3] == (WCHAR)'F'))) {
                MemoryFree (pLocalPerfData) ;

                // if we are reading from a setting file, let this pass thru'
                if (bDelayAddAction == TRUE) {
                   pSystem->sysDataKey = NULL ;
                   pSystem->FailureTime = GetTickCount();
                   pSystem->dwSystemState = SYSTEM_DOWN;

                   // Free any memory that may have created
                   SystemFree (pSystem, FALSE) ;

                   pSystem->lpszValue = MemoryAllocate (TEMP_BUF_LEN*sizeof(WCHAR));

                   if (!pSystem->lpszValue) {
                      // unable to allocate memory
                      SetLastError (ERROR_OUTOFMEMORY);
                      SystemFree (pSystem, TRUE);
                      return (NULL) ;
                   } else {
                      lstrcpy (pSystem->lpszValue, GlobalValueBuffer);
                   }

                   // Setup the thread's stuff
                   if (SystemSetupThread (pSystem))
                      return (pSystem) ;
                   else
                      return NULL;
                }
                SetLastError (ERROR_BAD_NET_NAME); // unable to find name
                SystemFree (pSystem, TRUE);
                return NULL;
            }

            MemoryFree (pLocalPerfData);    // don't really need anything from it

            // ok, so we've established that a foreign data provider
            // exists, now to finish the initialization.

            // change system name in structure to get counter names

            lstrcpy (pSystem->sysName, LocalComputerName);

            Status = GetSystemNames(pSystem);   // get counter names & explain text
            if (Status != ERROR_SUCCESS) {
                // unable to get names so bail out
                SetLastError (Status);
                SystemFree (pSystem, TRUE);
                return (NULL) ;
            }

            // restore computer name for displays, etc.

            lstrcpy (pSystem->sysName, lpszSystemName);

            // allocate value string buffer
            pSystem->lpszValue = MemoryAllocate (TEMP_BUF_LEN*sizeof(WCHAR));
            if (!pSystem->lpszValue) {
                // unable to allocate memory
                SetLastError (ERROR_OUTOFMEMORY);
                SystemFree (pSystem, TRUE);
                return (NULL) ;
            } else {
                lstrcpy (pSystem->lpszValue, ForeignValueBuffer);
            }
        }
    } else {
        // if here, then a connection to the system's registry was established
        // so continue with the system data structure initialization

        // get counter names & explain text from local computer

        Status = GetSystemNames(pSystem);
        if (Status != ERROR_SUCCESS) {
            // unable to get names so bail out
            SetLastError (Status);
            SystemFree (pSystem, TRUE);
            return (NULL) ;
        }

        // allocate value string buffer
        pSystem->lpszValue = MemoryAllocate(TEMP_BUF_LEN*sizeof(WCHAR));

        if (!pSystem->lpszValue) {
            // unable to allocate memory
            SetLastError (ERROR_OUTOFMEMORY);
            SystemFree (pSystem, TRUE);
            return (NULL) ;
        } else {
            SetSystemValueNameToGlobal (pSystem);
        }
    }

    // initialize remaining system pointers

    pSystem->pSystemNext = NULL ;
    pSystem->FailureTime = 0;

    // setup data for thread data collection
    if (!PlayingBackLog()) {
        // create a thread for data collection
        if (!SystemSetupThread (pSystem))
           return (NULL) ;
    }

    SetLastError (ERROR_SUCCESS);

    return (pSystem) ;
}  // SystemCreate

PPERFSYSTEM
SystemGet (
    PPERFSYSTEM pSystemFirst,
    LPCTSTR lpszSystemName
)
{
    PPERFSYSTEM       pSystem ;

    if (!pSystemFirst) {
        return (NULL) ;
    }

    for (pSystem = pSystemFirst ;
         pSystem ;
         pSystem = pSystem->pSystemNext) {
        if (strsamei (pSystem->sysName, lpszSystemName)) {
            return (pSystem) ;
        }
    }  // for

    return (NULL) ;
}

PPERFSYSTEM
SystemAdd (
    PPPERFSYSTEM ppSystemFirst,
    LPCTSTR lpszSystemName
)
{
    PPERFSYSTEM       pSystem ;
    PPERFSYSTEM       pSystemPrev ;

    if (!*ppSystemFirst) {
        *ppSystemFirst = SystemCreate (lpszSystemName) ;
        return (*ppSystemFirst) ;
    }

    for (pSystem = *ppSystemFirst ;
         pSystem ;
         pSystem = pSystem->pSystemNext) {
        pSystemPrev = pSystem ;
        if (strsamei (pSystem->sysName, lpszSystemName)) {
            return (pSystem) ;
        }
    }  // for

    pSystemPrev->pSystemNext = SystemCreate (lpszSystemName) ;
    return (pSystemPrev->pSystemNext) ;
}

void
SystemFree (
    PPERFSYSTEM pSystem,
    BOOL        bDeleteTheSystem
)
{  // SystemFree

    PCOUNTERTEXT pCounter, pNextCounter;

    if (!pSystem) {
        // can't proceed
        return ;
    }

    if (pSystem->sysDataKey && pSystem->sysDataKey != HKEY_PERFORMANCE_DATA) {
        // close the remote computer key
        RegCloseKey (pSystem->sysDataKey);
        pSystem->sysDataKey = 0 ;
    }

    for (pCounter = pSystem->CounterInfo.pNextTable, pNextCounter = NULL;
         pCounter;
         pCounter = pNextCounter) {
        pNextCounter = pCounter->pNextTable;
        MemoryFree (pCounter);
    }
    pSystem->CounterInfo.pNextTable = NULL ;

    if (pSystem->CounterInfo.TextString) {
        MemoryFree (pSystem->CounterInfo.TextString);
        pSystem->CounterInfo.TextString = NULL ;
    }

    if (pSystem->CounterInfo.HelpTextString) {
        MemoryFree (pSystem->CounterInfo.HelpTextString);
        pSystem->CounterInfo.HelpTextString = NULL ;
    }
    pSystem->CounterInfo.dwLastId = 0 ;
    pSystem->CounterInfo.dwHelpSize = 0 ;
    pSystem->CounterInfo.dwCounterSize = 0 ;

    if (bDeleteTheSystem) {
#if 0
        // cleanup all the data collection variables
        if (pSystem->hPerfDataEvent)
            CloseHandle (pSystem->hPerfDataEvent) ;

        if (pSystem->hStateDataMutex)
            CloseHandle (pSystem->hStateDataMutex) ;

        if (pSystem->hThread)
            CloseHandle (pSystem->hThread) ;

        if (pSystem->pSystemPerfData)
            MemoryFree (pSystem->pSystemPerfData);

        if (pSystem->lpszValue) {
            MemoryFree (pSystem->lpszValue);
            pSystem->lpszValue = NULL ;
        }
        MemoryFree (pSystem) ;
#endif
        if (pSystem->hThread) {
            // let the thread clean up memory
            PostThreadMessage (
               pSystem->dwThreadID,
               WM_FREE_SYSTEM,
               (WPARAM)0,
               (LPARAM)0) ;
        } else {
            // if no thread, clean up memory here.
            // Should not happen.
            if (pSystem->pSystemPerfData)
                MemoryFree (pSystem->pSystemPerfData);

            if (pSystem->lpszValue) {
                MemoryFree (pSystem->lpszValue);
                pSystem->lpszValue = NULL ;
            }
            MemoryFree (pSystem) ;
        }
    }
}

void
DeleteUnusedSystems (
    PPPERFSYSTEM  ppSystemFirst ,
    int           iNoUseSystems
)
{
    PPERFSYSTEM   pPrevSys, pCurrentSys, pNextSys ;

    // delete all the marked system from the list header until
    // we hit one that is not marked
    while ((*ppSystemFirst)->bSystemNoLongerNeeded) {
       // delect from the list header
       pCurrentSys = *ppSystemFirst ;
       *ppSystemFirst = pCurrentSys->pSystemNext ;
       SystemFree (pCurrentSys, TRUE) ;
       iNoUseSystems-- ;
       if (iNoUseSystems <= 0 || !(*ppSystemFirst)) {
          // done
          break ;
       }
    }

    if (iNoUseSystems <= 0 || !(*ppSystemFirst)) {
       return ;
    }

    // now walk the list and delete each marked system
    for (pPrevSys = *ppSystemFirst, pCurrentSys = pPrevSys->pSystemNext ;
         pCurrentSys && iNoUseSystems > 0 ;
         pCurrentSys = pNextSys) {

       if (pCurrentSys->bSystemNoLongerNeeded) {
          // the current system is marked, updated the list and free
          // this system.  No need to change pPrevSys here
          pNextSys = pPrevSys->pSystemNext = pCurrentSys->pSystemNext ;
          SystemFree (pCurrentSys, TRUE) ;
          iNoUseSystems-- ;
       } else {
          // pCurrentSys is OK, update the 2 list pointers and
          // carry on looping
          pPrevSys = pCurrentSys ;
          pNextSys = pCurrentSys->pSystemNext ;
       }
    }
}

void
FreeSystems (
    PPERFSYSTEM pSystemFirst
)
{
    PPERFSYSTEM    pSystem, pSystemNext ;

    for (pSystem = pSystemFirst;
         pSystem;
         pSystem = pSystemNext) {
        pSystemNext = pSystem->pSystemNext ;
        SystemFree (pSystem, TRUE) ;
    }
}  // FreeSystems

PPERFSYSTEM
GetComputer (
    HDLG hDlg,
    WORD wControlID,
    BOOL bWarn,
    PPERFDATA *ppPerfData,
    PPERFSYSTEM *ppSystemFirst
)
/*
   Effect:        Attempt to set the current computer to the one in the
                  hWndComputers dialog edit box. If this computer system
                  can be found, load the objects, etc. for the computer
                  and set pSystem and ppPerfdata to the values for this
                  system.
*/
{  // GetComputer
    TCHAR          szComputer [MAX_SYSTEM_NAME_LENGTH + 1] ;
    PPERFSYSTEM    pSystem;
    TCHAR          tempBuffer [LongTextLen] ;
    DWORD          dwBufferSize ;
    LPTSTR         pBuffer = NULL ;

    DialogText (hDlg, wControlID, szComputer) ;

    // If necessary, add the system to the lists for this view.
    pSystem = SystemGet (*ppSystemFirst, szComputer) ;
    if (!pSystem) {
        pSystem = SystemAdd (ppSystemFirst, szComputer) ;
    }

    if (!pSystem && bWarn) {
        DialogSetString (hDlg, wControlID, LocalComputerName) ;

        // Note: this will succeed since the local computer is always
        // available
        EditSetModified (GetDlgItem(hDlg, wControlID), FALSE) ;

        pSystem = SystemGet (*ppSystemFirst, LocalComputerName) ;
        if (!pSystem) {
            pSystem = SystemAdd (ppSystemFirst, LocalComputerName) ;
        }

//        MessageBoxResource (hDlg, IDS_COMPUTERNOTFOUND, IDS_APPNAME, MB_OK) ;
        DlgErrorBox (hDlg, ERR_COMPUTERNOTFOUND) ;

        SetFocus (DialogControl(hDlg, wControlID)) ;
    }

    if (pSystem) {
        if (PlayingBackLog ()) {
            *ppPerfData =
            LogDataFromPosition (pSystem, &(PlaybackLog.StartIndexPos)) ;
        } else {

            if (pSystem->lpszValue) {
               // save the previous lpszValue string before
               // SetSystemValueNameToGlobal screw it up
               dwBufferSize = MemorySize (pSystem->lpszValue) ;
               if (dwBufferSize <= sizeof(tempBuffer)) {
                  pBuffer = tempBuffer ;
               } else {
                  pBuffer = MemoryAllocate (dwBufferSize) ;
               }
               memcpy (pBuffer, pSystem->lpszValue, dwBufferSize) ;
            }

            SetSystemValueNameToGlobal (pSystem);
            UpdateSystemData (pSystem, ppPerfData) ;

            if (pSystem->lpszValue) {
               // retore the previous lpszValue string
               memcpy (pSystem->lpszValue, pBuffer, dwBufferSize) ;
               if (pBuffer != tempBuffer) {
                  MemoryFree (pBuffer) ;
               }
            }
        }
    }
    return (pSystem) ;

}  // GetComputer


SYSTEM.H

void SystemFree (PPERFSYSTEM pSystem, BOOL bDeleteTheSystem);

void FreeSystems (PPERFSYSTEM pSystemFirst) ;

void DeleteUnusedSystems (PPPERFSYSTEM ppSystemFirst, int iNoUsedSystem) ;

PPERFSYSTEM SystemGet (PPERFSYSTEM pSystemFirst,
                       LPCTSTR lpszSystemName) ;

PPERFSYSTEM  SystemAdd (PPPERFSYSTEM ppSystemFirst,
                        LPCTSTR lpszSystemName) ;

DWORD SystemCount (PPERFSYSTEM pSystemFirst) ;

PPERFSYSTEM GetComputer (HDLG hDlg,
                         WORD wControlID,
                         BOOL bWarn,
                         PPERFDATA *pPerfData,
                         PPERFSYSTEM *ppSystemFirst) ;


TIMEFRM.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdio.h>

#include "perfmon.h"          // included by all source
#include "timefrm.h"          // external declarations for this file

#include "alert.h"            // for PlaybackAlert
#include "grafdata.h"         // for PlaybackChart
#include "perfmops.h"         // for PerfmonViewWindow
#include "playback.h"         // for PlaybackIndexN
#include "report.h"           // for PlaybackReport
#include "timeline.h"         // for TL_INTERVAL
#include "utils.h"
#include "pmhelpid.h"         // for Help IDs

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

static RECT    TFrameRectWindow ;

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void ClearSystemTime (SYSTEMTIME *pSystemTime)
   {  // ClearSystemTime
   pSystemTime->wYear = 0 ;
   pSystemTime->wMonth = 0 ;
   pSystemTime->wDayOfWeek = 0 ;
   pSystemTime->wDay = 0 ;
   pSystemTime->wHour = 0 ;
   pSystemTime->wMinute = 0 ;
   pSystemTime->wSecond = 0 ;
   pSystemTime->wMilliseconds = 0 ;
   }  // ClearSystemTime

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnInitDialog (HDLG hDlg)
   {  // OnInitDialog
   PBOOKMARK      pBookmark ;
   int            iIndex ;
   TCHAR          szText [20+20+BookmarkCommentLen] ;
   TCHAR          szDate [20] ;
   TCHAR          szTime [20] ;
   int            iBookmarksNum ;
   HWND           hWndTLine ;
   HWND           hWndBookmarks ;
   int            currentTextExtent = 0 ;
   int            maxTextExtent = 0 ;
   HDC            hDC = 0 ;
   HFONT          hFont ;

   hWndTLine = DialogControl (hDlg, IDD_TIMEFRAMETIMELINE) ;
   hWndBookmarks = DialogControl (hDlg, IDD_TIMEFRAMEBOOKMARKS) ;
   SetFont (hWndBookmarks, hFontScales) ;
   LBSetHorzExtent (hWndBookmarks, 0) ;

   TLineSetRange (hWndTLine, 0, PlaybackLog.iTotalTics - 1) ;
   TLineSetStart (hWndTLine, PlaybackLog.StartIndexPos.iPosition) ;
   TLineSetStop (hWndTLine, PlaybackLog.StopIndexPos.iPosition) ;

   iBookmarksNum = 0 ;
   pBookmark = PlaybackLog.pBookmarkFirst ;

   if (pBookmark)
      {
      hDC = GetDC (hWndBookmarks) ;
      if (hDC)
         {
         hFont = (HFONT)SendMessage(hWndBookmarks, WM_GETFONT, 0, 0L);
         if (hFont)
            SelectObject(hDC, hFont);
         }

      while (pBookmark)
         {
         SystemTimeDateString (&(pBookmark->SystemTime), szDate) ;
         SystemTimeTimeString (&(pBookmark->SystemTime), szTime, TRUE) ;
         TSPRINTF (szText, TEXT(" %s  %s  %s"),
            szDate, szTime,
            pBookmark->szComment) ;
         iIndex = LBAdd (hWndBookmarks, szText) ;
         LBSetData (hWndBookmarks, iIndex, pBookmark->iTic) ;

         // get the biggest text width
         if (hDC)
            {
            currentTextExtent = TextWidth (hDC, szText) + xScrollWidth / 2 ;
            if (currentTextExtent > maxTextExtent)
               {
               maxTextExtent = currentTextExtent ;
               }
            }

         pBookmark = pBookmark->pBookmarkNext ;
         }

      LBSetSelection (hWndBookmarks, 0) ;

      iBookmarksNum = LBNumItems (hWndBookmarks) ;
      }

   if (iBookmarksNum == 0)
      {
      DialogEnable (hDlg, IDD_TIMEFRAMEBOOKMARKS, FALSE) ;
      DialogEnable (hDlg, IDD_TIMEFRAMEBOOKMARKGRP, FALSE) ;
      DialogEnable (hDlg, IDD_TIMEFRAMESETSTART, FALSE) ;
      DialogEnable (hDlg, IDD_TIMEFRAMESETSTOP, FALSE) ;
      }
   else
      {
      LBSetHorzExtent (hWndBookmarks, maxTextExtent) ;
      }

   if (hDC)
      {
      ReleaseDC (hWndBookmarks, hDC) ;
      }

   if (TFrameRectWindow.right == TFrameRectWindow.left)
      {
      // we have not initialize this data yet.
      // will get init. after the first time frame window invoke
      WindowCenter (hDlg) ;
      }
   else
      {
      // show it in its previous position
      MoveWindow (hDlg,
                 TFrameRectWindow.left,
                 TFrameRectWindow.top,
                 TFrameRectWindow.right - TFrameRectWindow.left,
                 TFrameRectWindow.bottom - TFrameRectWindow.top,
                 TRUE) ;
      }

   dwCurrentDlgID = HC_PM_idDlgEditTimeFrame ;

   }  // OnInitDialog

void static OnTLineInterval (HDLG hDlg,
                             int iInterval,
                             SYSTEMTIME *pSystemTime)
   {  // OnTLineInterval
   PLOGINDEX      pIndex ;

   pIndex = PlaybackIndexN (iInterval) ;

   if (pIndex)
      *pSystemTime = pIndex->SystemTime ;
   else
      ClearSystemTime (pSystemTime) ;
   }  // OnTLineInterval

void static OnOK (HDLG hDlg)
   {  // OnOK
   LOGPOSITION    lp ;
   int            iIndex ;
   HWND           hWndTLine = DialogControl (hDlg, IDD_TIMEFRAMETIMELINE) ;

   iIndex = TLineStart (hWndTLine) ;
   if (LogPositionN (iIndex, &lp)) ;
      PlaybackLog.StartIndexPos = lp ;

   iIndex = TLineStop (hWndTLine) ;
   if (LogPositionN (iIndex, &lp))
      PlaybackLog.StopIndexPos = lp ;

   PlaybackLog.iSelectedTics =
      PlaybackLog.StopIndexPos.iPosition -
      PlaybackLog.StartIndexPos.iPosition + 1 ;

   PlaybackChart (hWndGraph) ;
   PlaybackAlert (hWndAlert, 0) ;
   PlaybackReport (hWndReport) ;

   WindowInvalidate (PerfmonViewWindow ()) ;

   GetWindowRect (hDlg, &TFrameRectWindow) ;

   dwCurrentDlgID = 0 ;
   }  // OnOK

void static OnCancel (HWND hWnd)
   {
   HDC            hGraphDC ;
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWndGraph) ;
   hGraphDC = GetDC (hWndGraph) ;
   if (!hGraphDC || !pGraph)
      {
      return ;
      }

   TLineRedraw (hGraphDC, pGraph) ;

   GetWindowRect (hWnd, &TFrameRectWindow) ;

   dwCurrentDlgID = 0 ;
   }

void OnSetStartStop (HWND hDlg, BOOL bSetStart)
   {
   int            iTic ;
   int            iStopTic ;
   int            iStartTic ;
   HWND           hWndTLine = DialogControl (hDlg, IDD_TIMEFRAMETIMELINE) ;
   HWND           hWndBookmarks = DialogControl (hDlg, IDD_TIMEFRAMEBOOKMARKS) ;

   iStartTic = TLineStart (hWndTLine) ;
   iStopTic = TLineStop (hWndTLine) ;

   iTic = LBData (hWndBookmarks, LBSelection (hWndBookmarks)) ;
   if ((bSetStart && iStopTic <= iTic) ||
      (!bSetStart && iStartTic >= iTic))
      {
      DlgErrorBox (hDlg, ERR_STOPBEFORESTART) ;
      }
   else
      {
      if (bSetStart)
         {
         TLineSetStart (hWndTLine, iTic) ;
         }
      else
         {
         TLineSetStop (hWndTLine, iTic) ;
         }
      WindowInvalidate (hWndTLine) ;
      }
   }  // OnSetStartStop

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

int FAR WINAPI TimeframeDlgProc (HWND hDlg,
                                 unsigned iMessage,
                                 WPARAM wParam,
                                 LONG lParam)
   {
   BOOL           bHandled ;

   bHandled = TRUE ;
   switch (iMessage)
      {
      case TL_INTERVAL:
         OnTLineInterval (hDlg, wParam, (SYSTEMTIME *) lParam) ;
         break ;

      case WM_INITDIALOG:
         OnInitDialog (hDlg) ;
         return  (TRUE) ;

      case WM_CLOSE:
         OnCancel (hDlg) ;
         EndDialog (hDlg, 0) ;
         break ;

      case WM_COMMAND:
         switch(wParam)
            {
            case IDD_OK:
               SetHourglassCursor() ;
               OnOK (hDlg) ;
               SetArrowCursor() ;
               EndDialog (hDlg, 1) ;
               break ;

            case IDD_TIMEFRAMESETSTART:
            case IDD_TIMEFRAMESETSTOP:
               OnSetStartStop (hDlg, wParam == IDD_TIMEFRAMESETSTART) ;
               break ;

            case IDD_CANCEL:
               OnCancel (hDlg) ;
               GetWindowRect (hDlg, &TFrameRectWindow) ;
               EndDialog (hDlg, 0) ;
               break ;

            case IDD_TIMEFRAMEHELP:
               CallWinHelp (dwCurrentDlgID) ;
               break ;

            default:
               bHandled = FALSE ;
               break;
            }
         break;

      default:
            bHandled = FALSE ;
         break ;
      }  // switch

   return (bHandled) ;
   }  // TimeframeDlgProc

BOOL SetTimeframe (HWND hWndParent)
   {  // SetTimeframe
   if (DialogBox (hInstance, idDlgTimeframe,
                  hWndParent, (DLGPROC) TimeframeDlgProc))
      {
      return (TRUE) ;
      }
   return (FALSE) ;
   }


TIMEFRM.H

BOOL SetTimeframe (HWND hWndParent) ;


TIMELINE.C

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include "perfmon.h"
#include "intrline.h"
#include "pmemory.h"        // for MemoryXXX (mallloc-type) routines
#include "timeline.h"
#include "perfmops.h"       // for SystemTimeDateString, et al.
#include "utils.h"
#include "grafdata.h"       // for GraphData

//==========================================================================//
//                                  Typedefs                                //
//==========================================================================//

typedef struct CHARTDATAPOINTSTRUCT
   {
   int         iLogIndex ;
   int         xDispDataPoint ;
   } CHARTDATAPOINT, *PCHARTDATAPOINT ;

typedef struct TLINESTRUCT
   {  // TLINE
   HWND              hWndILine ;
   HFONT             hFont ;

   SYSTEMTIME        SystemTimeBegin ;
   SYSTEMTIME        SystemTimeEnd ;

   int               yFontHeight ;
   int               xMaxTimeWidth ;
   int               xBegin ;
   int               xEnd ;

   RECT              rectStartDate ;
   RECT              rectStartTime ;
   RECT              rectStopDate ;
   RECT              rectStopTime ;

   PCHARTDATAPOINT   pChartDataPoint ;
   int               iCurrentStartPos ;
   int               iCurrentStopPos ;
   } TLINE ;

typedef TLINE *PTLINE ;

void PlaybackChartDataPoint (PCHARTDATAPOINT pChartDataPoint) ;

// IntrLineFocus is defined and set/clear in Intrline.c
extern BOOL  IntrLineFocus ;

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define dwTLineClassStyle     (CS_HREDRAW | CS_VREDRAW)
#define iTLineClassExtra      (0)
#define iTLineWindowExtra     (sizeof (PTLINE))
#define dwTLineWindowStyle    (WS_CHILD | WS_VISIBLE)

HWND  hTLineWnd ;
BOOL  TLineWindowUp ;

PTLINE TLData (HWND hWndTL)
   {
   return ((PTLINE) GetWindowLong (hWndTL, 0)) ;
   }

PTLINE AllocateTLData (HWND hWndTL)
   {
   PTLINE         pTLine ;
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWndGraph) ;

   pTLine = MemoryAllocate (sizeof (TLINE)) ;

   // see if we have to draw the timeline
   if (pGraph &&
      iPerfmonView == IDM_VIEWCHART &&
      pGraph->pLineFirst &&
      pGraph->gOptions.iGraphOrHistogram == LINE_GRAPH)
      {
      pTLine->pChartDataPoint =
         MemoryAllocate (sizeof(CHARTDATAPOINT) *
         (pGraph->gTimeLine.iValidValues+1)) ;

      if (pTLine->pChartDataPoint != NULL)
         {
         PlaybackChartDataPoint (pTLine->pChartDataPoint) ;
         }
      }

   SetWindowLong (hWndTL, 0, (LONG) pTLine) ;

   return (pTLine) ;
   }

int MaxTimeWidth (HDC hDC,
                  PTLINE pTLine)
/*
   Effect:        Return a reasonable maximum number of pixels to hold
                  expected time and date strings.

   To Do:         When we use the alleged local-date and local-time display
                  functions, we will modify this routine to use them.
*/
   {  // MaxTimeWidth
   return (max (TextWidth (hDC, TEXT(" 99 XXX 99 ")),
                TextWidth (hDC, TEXT(" 99:99:99.9 PM ")))) ;
   }  // MaxTimeWidth

void TLGetSystemTimeN (HWND hWnd,
                       int iInterval,
                       SYSTEMTIME *pSystemTime)
   {  // TLGetSystemTimeN
   SendMessage (WindowParent (hWnd),
                TL_INTERVAL,
                iInterval, (LPARAM) pSystemTime) ;
   }  // TLGetSystemTimeN

void static TLDrawBeginEnd (HDC hDC,
                            PTLINE pTLine)
   {
   TCHAR         szDate [20] ;
   TCHAR         szTime [20] ;

   SetTextAlign (hDC, TA_TOP) ;
   SelectFont (hDC, pTLine->hFont) ;

   // Draw the begin time

   SystemTimeDateString (&(pTLine->SystemTimeBegin), szDate) ;
   SystemTimeTimeString (&(pTLine->SystemTimeBegin), szTime, TRUE) ;

   SetTextAlign (hDC, TA_RIGHT) ;
   TextOut (hDC, pTLine->xBegin, 0,
            szDate, lstrlen (szDate)) ;
   TextOut (hDC, pTLine->xBegin, pTLine->yFontHeight,
            szTime, lstrlen (szTime)) ;

   // Draw The end time

   SystemTimeDateString (&(pTLine->SystemTimeEnd), szDate) ;
   SystemTimeTimeString (&(pTLine->SystemTimeEnd), szTime, TRUE) ;

   SetTextAlign (hDC, TA_LEFT) ;
   TextOut (hDC, pTLine->xEnd, 0,
            szDate, lstrlen (szDate)) ;
   TextOut (hDC,
            pTLine->xEnd,
            pTLine->yFontHeight,
            szTime, lstrlen (szTime)) ;
   }

void TLineRedraw (HDC hGraphDC, PGRAPHSTRUCT pGraph)
   {
   PTLINE         pTLine ;

   if (!hTLineWnd)
      {
      return ;
      }

   pTLine = TLData (hTLineWnd) ;
   if (pTLine == NULL)
      {
      return ;
      }

   if (pTLine->iCurrentStartPos)
      {
      // redraw start line
      PatBlt (hGraphDC, pTLine->iCurrentStartPos, pGraph->rectData.top,
         1, pGraph->rectData.bottom - pGraph->rectData.top + 1,
         DSTINVERT) ;
      }

   if (pTLine->iCurrentStopPos)
      {
      // redraw stop line
      PatBlt (hGraphDC, pTLine->iCurrentStopPos, pGraph->rectData.top,
         1, pGraph->rectData.bottom - pGraph->rectData.top+ 1,
         DSTINVERT) ;
      }
   }  // TLineRedraw

void DrawOneTimeIndicator (PTLINE pTLine,
                           PGRAPHSTRUCT pGraph,
                           HDC hGraphDC,
                           int iPos,
                           int *pCurrentPos)
   {
   int               xPos ;
   PCHARTDATAPOINT   pDataPoint ;

   // check if it is within current selected range
   if (iPos >= PlaybackLog.StartIndexPos.iPosition &&
      iPos <= PlaybackLog.StopIndexPos.iPosition)
      {

      xPos = 0 ;
      pDataPoint = pTLine->pChartDataPoint ;

      // check for the x position of this Log Index
      while (pDataPoint->iLogIndex != 0)
         {
         if (iPos >= pDataPoint->iLogIndex)
            {
            if ((pDataPoint+1)->iLogIndex == 0)
               {
               // we have reached the end
               xPos = pDataPoint->xDispDataPoint ;
               break ;
               }
            else if (iPos <= (pDataPoint+1)->iLogIndex)
               {
               // we have found the Log index
               xPos = (pDataPoint+1)->xDispDataPoint ;
               break ;
               }
            }
         else
            {
            // no need to continue if iPos is smaller than the
            // first Log index on the chart
            break ;
            }

         pDataPoint++ ;
         }

      if (xPos != *pCurrentPos)
         {
         if (*pCurrentPos)
            {
            // erase the old line
            PatBlt (hGraphDC, *pCurrentPos, pGraph->rectData.top,
               1, pGraph->rectData.bottom - pGraph->rectData.top + 1,
               DSTINVERT) ;
            }

         // draw the new line
         *pCurrentPos = xPos ;

         if (xPos > 0)
            {
            PatBlt (hGraphDC, xPos, pGraph->rectData.top,
               1, pGraph->rectData.bottom - pGraph->rectData.top + 1,
               DSTINVERT) ;
            }
         }
      }
   else
      {
      if (*pCurrentPos)
         {
         // erase the old line
         PatBlt (hGraphDC, *pCurrentPos, pGraph->rectData.top,
            1, pGraph->rectData.bottom - pGraph->rectData.top + 1,
            DSTINVERT) ;
         }

      *pCurrentPos = 0 ;
      }
   }  // DrawOneTimeIndicator

void DrawTimeIndicators (PTLINE pTLine, int iStart, int iStop)
   {

   HDC            hGraphDC ;
   PGRAPHSTRUCT   pGraph ;

   pGraph = GraphData (hWndGraph) ;
   hGraphDC = GetDC (hWndGraph) ;
   if (!hGraphDC || !pGraph)
      {
      return ;
      }

   DrawOneTimeIndicator (pTLine, pGraph, hGraphDC, iStart, &pTLine->iCurrentStartPos) ;
   DrawOneTimeIndicator (pTLine, pGraph, hGraphDC, iStop, &pTLine->iCurrentStopPos) ;

   ReleaseDC (hWndGraph, hGraphDC) ;
   }

void static TLDrawStartStop (HWND hWnd,
                             HDC hDC,
                             PTLINE pTLine)
/*
   Effect:        Draw the start and stop date/times on the bottom of the
                  timeline. Draw the start date/time right justified at the
                  outer edge of the start point and the stop date/time left
                  justified with the outer edge of the stop point.

                  Erase previous start and stop date/times in the process.
*/
   {  // TLDrawStartStop
   RECT           rectDate ;
   RECT           rectTime ;
   RECT           rectOpaque ;

   TCHAR          szDate [30] ;
   TCHAR          szTime [30] ;

   int            xStart ;
   int            xStop ;

   int            iStart ;
   int            iStop ;

   SYSTEMTIME     SystemTimeStart ;
   SYSTEMTIME     SystemTimeStop ;

   int            xDateTimeWidth ;

   SelectFont (hDC, pTLine->hFont) ;
   SetTextAlign (hDC, TA_TOP) ;

   //=============================//
   // Get Start Information       //
   //=============================//

   xStart = pTLine->xBegin + ILineXStart (pTLine->hWndILine) ;

   iStart = ILineStart (pTLine->hWndILine) ;
   TLGetSystemTimeN (hWnd, iStart, &SystemTimeStart) ;
   SystemTimeDateString (&SystemTimeStart, szDate) ;
   SystemTimeTimeString (&SystemTimeStart, szTime, TRUE) ;

   xDateTimeWidth = max (TextWidth (hDC, szDate),
                         TextWidth (hDC, szTime)) ;

   //=============================//
   // Write Start Date            //
   //=============================//

   rectDate.left = xStart - xDateTimeWidth ;
   rectDate.top = pTLine->rectStartDate.top ;
   rectDate.right = xStart ;
   rectDate.bottom = pTLine->rectStartDate.bottom ;

   SetTextAlign (hDC, TA_RIGHT) ;
   UnionRect (&rectOpaque, &pTLine->rectStartDate, &rectDate) ;

   ExtTextOut (hDC,
               rectDate.right, rectDate.top,
               ETO_OPAQUE,
               &rectOpaque,
               szDate, lstrlen (szDate),
               NULL) ;
   pTLine->rectStartDate = rectDate ;

   //=============================//
   // Write Start Time            //
   //=============================//

   rectTime.left = rectDate.left ;
   rectTime.top = pTLine->rectStartTime.top ;
   rectTime.right = rectDate.right ;
   rectTime.bottom = pTLine->rectStartTime.bottom ;

   UnionRect (&rectOpaque, &pTLine->rectStartTime, &rectTime) ;

   ExtTextOut (hDC,
               rectTime.right, rectTime.top,
               ETO_OPAQUE,
               &rectOpaque,
               szTime, lstrlen (szTime),
               NULL) ;
   pTLine->rectStartTime = rectTime ;

   if (IntrLineFocus)
      {
      UnionRect (&rectOpaque, &rectDate, &rectTime) ;
      DrawFocusRect (hDC, &rectOpaque) ;
      }

   //=============================//
   // Get Stop Information        //
   //=============================//

   xStop = pTLine->xBegin + ILineXStop (pTLine->hWndILine) ;

   iStop = ILineStop (pTLine->hWndILine) ;
   TLGetSystemTimeN (hWnd, iStop, &SystemTimeStop) ;
   SystemTimeDateString (&SystemTimeStop, szDate) ;
   SystemTimeTimeString (&SystemTimeStop, szTime, TRUE) ;

   xDateTimeWidth = max (TextWidth (hDC, szDate),
                         TextWidth (hDC, szTime)) ;

   //=============================//
   // Write Stop Date             //
   //=============================//

   rectDate.left = xStop ;
   rectDate.top = pTLine->rectStopDate.top ;
   rectDate.right = xStop + xDateTimeWidth ;
   rectDate.bottom = pTLine->rectStopDate.bottom ;

   SetTextAlign (hDC, TA_LEFT) ;
   UnionRect (&rectOpaque, &pTLine->rectStopDate, &rectDate) ;

   ExtTextOut (hDC,
               rectDate.left, rectDate.top,
               ETO_OPAQUE,
               &rectOpaque,
               szDate, lstrlen (szDate),
               NULL) ;
   pTLine->rectStopDate = rectDate ;

   //=============================//
   // Write Stop Time             //
   //=============================//

   rectTime.left = rectDate.left ;
   rectTime.top = pTLine->rectStopTime.top ;
   rectTime.right = rectDate.right ;
   rectTime.bottom = pTLine->rectStopTime.bottom ;

   UnionRect (&rectOpaque, &pTLine->rectStopTime, &rectTime) ;

   ExtTextOut (hDC,
               rectTime.left, rectTime.top,
               ETO_OPAQUE,
               &rectOpaque,
               szTime, lstrlen (szTime),
               NULL) ;
   pTLine->rectStopTime = rectTime ;

   if (IntrLineFocus)
      {
      UnionRect (&rectOpaque, &rectDate, &rectTime) ;
      DrawFocusRect (hDC, &rectOpaque) ;
      }

   if (pTLine->pChartDataPoint)
      {
      DrawTimeIndicators (pTLine, iStart, iStop) ;
      }
   }  // TLDrawStartStop

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

void static OnCreate (HWND hWnd)
   {  // OnCreate
   PTLINE         pTLine ;
   HDC            hDC ;

   pTLine = AllocateTLData (hWnd) ;

   pTLine->hFont = hFontScales ;

   hDC = GetDC (hWnd) ;
   SelectFont (hDC, hFontScales) ;

   pTLine->yFontHeight = FontHeight (hDC, TRUE) ;
   pTLine->xMaxTimeWidth = MaxTimeWidth (hDC, pTLine) ;

   ReleaseDC (hWnd, hDC) ;

   hTLineWnd = hWnd ;
   TLineWindowUp = TRUE ;

   pTLine->hWndILine =
      CreateWindow (szILineClass,                  // class
                    NULL,                          // caption
                    (WS_VISIBLE | WS_CHILD | WS_TABSTOP ),       // window style
                    0, 0,                          // position
                    0, 0,                          // size
                    hWnd,                          // parent window
                    NULL,                          // menu
                    hInstance,                     // program instance
                    NULL) ;                        // user-supplied data
   }  // OnCreate

void static OnDestroy (HWND hWnd)
   {
   PTLINE         pTLine ;

   pTLine = TLData (hWnd) ;

   if (pTLine->pChartDataPoint)
      {
      MemoryFree (pTLine->pChartDataPoint) ;
      }

   MemoryFree (pTLine) ;

   hTLineWnd = 0 ;
   TLineWindowUp = FALSE ;

   }

void static OnSize (HWND hWnd,
                    int xWidth,
                    int yHeight)
/*
   Effect:        Perform all actions needed when the size of the timeline
                  hWnd has changed. In particular, determine the appropriate
                  size for the ILine window and set the rectangles for the
                  top and bottom displays.
*/
   {  // OnSize
   PTLINE         pTLine ;
   int            yLine ;
   int            yDate, yTime ;
   int            xEnd ;

   pTLine = TLData (hWnd) ;

   xEnd = xWidth - pTLine->xMaxTimeWidth ;
   yLine = pTLine->yFontHeight ;
   yDate = yHeight - 2 * yLine ;
   yTime = yHeight - yLine ;

   SetRect (&pTLine->rectStartDate,
            0, yDate, 0, yDate + yLine) ;

   SetRect (&pTLine->rectStartTime,
            0, yTime, 0, yTime + yLine) ;

   SetRect (&pTLine->rectStopDate,
            xEnd, yDate, xEnd, yDate + yLine) ;

   SetRect (&pTLine->rectStopTime,
            xEnd, yTime, xEnd, yTime + yLine) ;

   MoveWindow (pTLine->hWndILine,
               pTLine->xMaxTimeWidth, 2 * pTLine->yFontHeight,
               xWidth - 2 * pTLine->xMaxTimeWidth,
               yHeight - 4 * pTLine->yFontHeight,
               FALSE) ;

   pTLine->xBegin = pTLine->xMaxTimeWidth ;
   pTLine->xEnd = xWidth - pTLine->xMaxTimeWidth ;
   }  // OnSize

void static OnPaint (HWND hWnd)
   {
   HDC            hDC ;
   PAINTSTRUCT    ps ;
   PTLINE         pTLine ;

   hDC = BeginPaint (hWnd, &ps) ;

   pTLine = TLData (hWnd) ;
   TLDrawBeginEnd (hDC, pTLine) ;
   TLDrawStartStop (hWnd, hDC, pTLine) ;

   EndPaint (hWnd, &ps) ;
   }

void static OnILineChanged (HWND hWnd)
   {
   HDC            hDC ;
   PTLINE         pTLine ;

   pTLine = TLData (hWnd) ;

   hDC = GetDC (hWnd) ;
   TLDrawStartStop (hWnd, hDC, pTLine) ;
   ReleaseDC (hWnd, hDC) ;
   }

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

LONG FAR PASCAL TLineWndProc (HWND hWnd,
                              unsigned msg,
                              WPARAM wParam,
                              LONG lParam)
/*
   Note:          This function must be declared in the application's
                  linker-definition file, perfmon.def file.
*/
   {  // TLineWndProc
   BOOL           bCallDefWindowProc ;
   LONG           lReturnValue ;

   bCallDefWindowProc = FALSE ;
   lReturnValue = 0L ;

   switch (msg)
      {  // switch
      case WM_SETFOCUS:
         {
         PTLINE         pTLine ;

         pTLine = TLData (hWnd) ;
         SetFocus (pTLine->hWndILine) ;
         }
         return 0 ;

      case WM_KILLFOCUS:
         return 0 ;

      case WM_COMMAND:
         OnILineChanged (hWnd) ;
         break ;

      case WM_CREATE:
         OnCreate (hWnd) ;
         break ;

      case WM_DESTROY:
         OnDestroy (hWnd) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_SIZE:
         OnSize (hWnd, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      default:
         bCallDefWindowProc = TRUE ;
      }  // switch

   if (bCallDefWindowProc)
      lReturnValue = DefWindowProc (hWnd, msg, wParam, lParam) ;

   return (lReturnValue) ;
   }  // TLineWndProc

BOOL TLineInitializeApplication (void)
/*
   Effect:        Perform all initializations required before an application
                  can create an IntervalLine. In particular, register the
                  IntervalLine window class.

   Called By:     The application, in its InitializeApplication routine.

   Returns:       Whether the class could be registered.
*/
   {  // TLineInitializeApplication
   WNDCLASS       wc ;

   wc.style =           dwTLineClassStyle ;
   wc.lpfnWndProc =     TLineWndProc ;
   wc.cbClsExtra =      iTLineClassExtra ;
   wc.cbWndExtra =      iTLineWindowExtra ;
   wc.hInstance =       hInstance ;
   wc.hIcon =           NULL ;
   wc.hCursor =         LoadCursor (NULL, IDC_ARROW) ;
   wc.hbrBackground =   (HBRUSH) (COLOR_WINDOW + 1) ;
   wc.lpszMenuName =    NULL ;
   wc.lpszClassName =   szTLineClass ;

   return (RegisterClass (&wc)) ;
   }  // TLineInitializeApplication

void TLineSetRange (HWND hWnd,
                    int iBegin,
                    int iEnd)
   {
   PTLINE         pTLine ;

   pTLine = TLData (hWnd) ;

   ILineSetRange (pTLine->hWndILine, iBegin, iEnd) ;
   TLGetSystemTimeN (hWnd, iBegin, &pTLine->SystemTimeBegin) ;
   TLGetSystemTimeN (hWnd, iEnd, &pTLine->SystemTimeEnd) ;
   }

void TLineSetStart (HWND hWnd,
                    int iStart)
   {
   PTLINE         pTLine ;

   pTLine = TLData (hWnd) ;
   ILineSetStart (pTLine->hWndILine, iStart) ;
   }

void TLineSetStop (HWND hWnd,
                   int iStop)
   {
   PTLINE         pTLine ;

   pTLine = TLData (hWnd) ;
   ILineSetStop (pTLine->hWndILine, iStop) ;
   }

int TLineStart (HWND hWnd)
   {
   PTLINE         pTLine ;

   pTLine = TLData (hWnd) ;

   return (ILineStart (pTLine->hWndILine)) ;
   }

int TLineStop (HWND hWnd)
   {
   PTLINE         pTLine ;

   pTLine = TLData (hWnd) ;

   return (ILineStop (pTLine->hWndILine)) ;
   }


TIMELINE.H

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define szTLineClass          TEXT("PerfTLine")
#define szTLineClassA         "PerfTLine"

#define TL_INTERVAL           (WM_USER + 0x301)

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

BOOL TLineInitializeApplication (void) ;

void TLineSetRange (HWND hWnd,
                    int iBegin,
                    int iEnd) ;

void TLineSetStart (HWND hWnd,
                    int iStart) ;

void TLineSetStop (HWND hWnd,
                   int iStop) ;

int TLineStart (HWND) ;

int TLineStop (HWND) ;

void TLineRedraw (HDC hDC, PGRAPHSTRUCT pGraph) ;

extern BOOL TLineWindowUp ;
#define IsTLineWindowUp() (TLineWindowUp)


TOOLBAR.C

//
//                                  Includes
//

#include "perfmon.h"
#include <commctrl.h>
#include "toolbar.h"
#include "status.h"     // for StatusLine & StatusLineReady

#ifdef ADVANCED_PERFMON
TBBUTTON tbButtons[] = {
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,        0 },
   { 0, IDM_VIEWCHART,        TBSTATE_ENABLED,   TBSTYLE_CHECKGROUP, 0 },
   { 1, IDM_VIEWALERT,        TBSTATE_ENABLED,   TBSTYLE_CHECKGROUP, 0 },
   { 2, IDM_VIEWLOG,          TBSTATE_ENABLED,   TBSTYLE_CHECKGROUP, 0 },
   { 3, IDM_VIEWREPORT,       TBSTATE_ENABLED,   TBSTYLE_CHECKGROUP, 0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,        0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,        0 },
   { 4, IDM_TOOLBARADD,       TBSTATE_ENABLED,   TBSTYLE_BUTTON,     0 },
   { 5, IDM_TOOLBARMODIFY,    TBSTATE_ENABLED,   TBSTYLE_BUTTON,     0 },
   { 6, IDM_TOOLBARDELETE,    TBSTATE_ENABLED,   TBSTYLE_BUTTON,     0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,        0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,        0 },
   { 7, IDM_TOOLBARREFRESH,   TBSTATE_ENABLED,   TBSTYLE_BUTTON,     0 },
   { 8, IDM_OPTIONSBOOKMARK,  TBSTATE_ENABLED,   TBSTYLE_BUTTON,     0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,        0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,        0 },
   { 9, IDM_TOOLBAROPTIONS,   TBSTATE_ENABLED,   TBSTYLE_BUTTON,     0 },
} ;
#else
TBBUTTON tbButtons[] = {
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,      0 },
   { 4, IDM_TOOLBARADD,       TBSTATE_ENABLED,   TBSTYLE_BUTTON,   0 },
   { 5, IDM_TOOLBARMODIFY,    TBSTATE_ENABLED,   TBSTYLE_BUTTON,   0 },
   { 6, IDM_TOOLBARDELETE,    TBSTATE_ENABLED,   TBSTYLE_BUTTON,   0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,      0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,      0 },
   { 7, IDM_TOOLBARREFRESH,   TBSTATE_ENABLED,   TBSTYLE_BUTTON,   0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,      0 },
   { 0, 0,                    TBSTATE_ENABLED,   TBSTYLE_SEP,      0 },
   { 9, IDM_TOOLBAROPTIONS,   TBSTATE_ENABLED,   TBSTYLE_BUTTON,   0 },
} ;
#endif

#define TB_ENTRIES sizeof(tbButtons)/sizeof(tbButtons[0])

BOOL CreateToolbarWnd (HWND hWnd)
{

   hWndToolbar = CreateToolbarEx (hWnd,
      WS_CHILD | WS_BORDER | WS_VISIBLE,
      IDM_TOOLBARID,
      10,                  // number of tools inside the bitmap
      hInstance,
      idBitmapToolbar,     // bitmap resource ID (can't use MAKEINTRESOURCE)
      tbButtons,
      TB_ENTRIES,0,0,0,0,sizeof(TBBUTTON)) ;

   return (hWndToolbar ? TRUE : FALSE) ;

}  // ToolbarInitializeApplication

void ToolbarEnableButton (HWND hWndTB, int iButtonNum, BOOL bEnable)
{
   SendMessage (hWndTB, TB_ENABLEBUTTON, iButtonNum, (LONG)bEnable) ;
}  // ToolbarEnableButton

void ToolbarDepressButton (HWND hWndTB, int iButtonNum, BOOL bDepress)
{
   if (iButtonNum >= IDM_TOOLBARADD && iButtonNum <= IDM_TOOLBARBOOKMARK)
      {
      // these buttons are push button and will not stay down after
      // each hit
      SendMessage (hWndTB, TB_PRESSBUTTON, iButtonNum, (LONG)bDepress) ;
      }
   else
      {
      // for the four view buttons, have to use CHECKBUTTON so they
      // will stay down after selected.
      SendMessage (hWndTB, TB_CHECKBUTTON, iButtonNum, (LONG)bDepress) ;
      }
}  // ToolbarDepressButton

void OnToolbarHit (WPARAM wParam, LPARAM lParam)
{

   WORD  ToolbarHit ;

   if (HIWORD(wParam) == TBN_ENDDRAG)
      {
      StatusLineReady (hWndStatus) ;
      }
   else if (HIWORD(wParam) == TBN_BEGINDRAG)
      {
      ToolbarHit = LOWORD (lParam) ;

      if (ToolbarHit >= IDM_TOOLBARADD &&
          ToolbarHit <= IDM_TOOLBARDELETE)
         {
         ToolbarHit -= IDM_TOOLBARADD ;

         switch (iPerfmonView)
            {
            case IDM_VIEWALERT:
               ToolbarHit += IDM_EDITADDALERT ;
               break ;

            case IDM_VIEWLOG:
               ToolbarHit += IDM_EDITADDLOG ;
               break ;

            case IDM_VIEWREPORT:
               ToolbarHit += IDM_EDITADDREPORT ;
               break ;

            case IDM_VIEWCHART:
            default:
               ToolbarHit += IDM_EDITADDCHART ;
               break ;
            }
         }
      else if (ToolbarHit == IDM_TOOLBARBOOKMARK)
         {
         ToolbarHit = IDM_OPTIONSBOOKMARK ;
         }
      else if (ToolbarHit == IDM_TOOLBARREFRESH)
         {
         switch (iPerfmonView)
            {
            case IDM_VIEWALERT:
               ToolbarHit = IDM_OPTIONSREFRESHNOWALERT ;
               break ;

            case IDM_VIEWLOG:
               ToolbarHit = IDM_OPTIONSREFRESHNOWLOG ;
               break ;

            case IDM_VIEWREPORT:
               ToolbarHit = IDM_OPTIONSREFRESHNOWREPORT ;
               break ;

            case IDM_VIEWCHART:
            default:
               ToolbarHit = IDM_OPTIONSREFRESHNOWCHART ;
               break ;
            }
         }
      else if (ToolbarHit == IDM_TOOLBAROPTIONS)
         {
         switch (iPerfmonView)
            {
            case IDM_VIEWALERT:
               ToolbarHit = IDM_OPTIONSALERT ;
               break ;

            case IDM_VIEWLOG:
               ToolbarHit = IDM_OPTIONSLOG ;
               break ;

            case IDM_VIEWREPORT:
               ToolbarHit = IDM_OPTIONSREPORT ;
               break ;

            case IDM_VIEWCHART:
            default:
               ToolbarHit = IDM_OPTIONSCHART ;
               break ;
            }
         }

      StatusLine (hWndStatus, ToolbarHit) ;
      }
}  // OnToolBarHit


TOOLBAR.H

//
//                             Exported Functions
//

BOOL CreateToolbarWnd (HWND hWnd) ;

void ToolbarEnableButton (HWND hWndTB,
                          int iButtonNum,
                          BOOL bEnable) ;

void ToolbarDepressButton (HWND hWndTB,
                           int iButtonNum,
                           BOOL bDepress) ;

void OnToolbarHit (WPARAM wParam,
                   LPARAM lParam) ;


TYPEDEFS.H

//======================================//
// Options Data Type                    //
//======================================//

typedef struct OPTIONSSTRUCT
   {
   BOOL           bMenubar ;
   BOOL           bToolbar ;
   BOOL           bStatusbar ;
   BOOL           bAlwaysOnTop ;
   } OPTIONS ;

//======================================//
// Basic "Derived" Types                //
//======================================//

typedef HANDLE HLIBRARY ;
typedef HANDLE HMEMORY ;
typedef HWND HDLG ;
typedef HWND HCONTROL ;
typedef VOID *LPMEMORY ;

//======================================//
// Perfmon-Specific Types               //
//======================================//

typedef PERF_DATA_BLOCK *PPERFDATA ;
typedef PERF_OBJECT_TYPE *PPERFOBJECT ;
typedef PERF_COUNTER_DEFINITION *PPERFCOUNTERDEF ;
typedef PERF_INSTANCE_DEFINITION *PPERFINSTANCEDEF ;

    // This structure links together the Performance data for multiple
    // systems, each of which has some counter instance which the user
    // is interested in.

typedef struct _COUNTERTEXT {
    struct  _COUNTERTEXT  *pNextTable;
    DWORD   dwLangId;
    DWORD   dwLastId;
    DWORD   dwCounterSize;
    DWORD   dwHelpSize;
    LPWSTR  *TextString;
    LPWSTR  HelpTextString;
} COUNTERTEXT;

typedef COUNTERTEXT *PCOUNTERTEXT;

// System states used mainly by Alert view to report system up/down
#define  SYSTEM_OK               0
#define  SYSTEM_DOWN             1
#define  SYSTEM_DOWN_RPT         2
#define  SYSTEM_RECONNECT        3
#define  SYSTEM_RECONNECT_RPT    4

typedef struct PERFSYSTEMSTRUCT
   {
   struct  PERFSYSTEMSTRUCT *pSystemNext;
   TCHAR   sysName[MAX_SYSTEM_NAME_LENGTH+1];
   HKEY    sysDataKey;
   COUNTERTEXT CounterInfo;
   DWORD   FailureTime;
   LPTSTR  lpszValue;
   BOOL    bSystemNoLongerNeeded;
   BOOL    bSystemCounterNameSaved;

   // the following used by perf data thread
   DWORD           dwThreadID ;
   HANDLE          hThread ;
   DWORD           StateData ;
   HANDLE          hStateDataMutex ;
   HANDLE          hPerfDataEvent ;
   PPERFDATA       pSystemPerfData ;

   // mainly used by Alert to report system up/down
   DWORD           dwSystemState ;

   // system version
   DWORD           SysVersion ;

   } PERFSYSTEM, *PPERFSYSTEM, **PPPERFSYSTEM ;

typedef struct _graph_options {
    BOOL    bLegendChecked ;
    BOOL    bMenuChecked ;
    BOOL    bLabelsChecked;
    BOOL    bVertGridChecked ;
    BOOL    bHorzGridChecked ;
    BOOL    bStatusBarChecked ;
    INT     iVertMax ;
    FLOAT   eTimeInterval ;
    INT     iGraphOrHistogram ;
    INT     GraphVGrid,
            GraphHGrid,
            HistVGrid,
            HistHGrid ;

} GRAPH_OPTIONS ;

//======================================//
// Line Data Type                       //
//======================================//

#define LineTypeChart            1
#define LineTypeAlert            2
#define LineTypeReport           3

typedef struct LINEVISUALSTRUCT
   {
   COLORREF       crColor ;
   int            iColorIndex ;

   int            iStyle ;
   int            iStyleIndex ;

   int            iWidth ;
   int            iWidthIndex ;
   } LINEVISUAL ;

typedef LINEVISUAL *PLINEVISUAL ;

typedef struct _LINESTRUCT
   {
   struct  _LINESTRUCT             *pLineNext;
   int                             bFirstTime;
   int                             iLineType ;
   LPTSTR                          lnSystemName;

   struct _PERF_OBJECT_TYPE        lnObject;
   LPTSTR                          lnObjectName;

   struct _PERF_COUNTER_DEFINITION lnCounterDef;
   LPTSTR                          lnCounterName;

   struct _PERF_INSTANCE_DEFINITION lnInstanceDef;
   LPTSTR                          lnInstanceName;

   LPTSTR                          lnPINName;
   LPTSTR                          lnParentObjName;
   DWORD                           lnUniqueID;     // Of instance, if any

   LONGLONG                        lnNewTime;
   LONGLONG                        lnOldTime;

   LONGLONG                        lnOldTime100Ns ;
   LONGLONG                        lnNewTime100Ns ;

   LONGLONG                        lnaCounterValue[2];
   LONGLONG                        lnaOldCounterValue[2];

   DWORD                           lnCounterType;
   DWORD                           lnCounterLength;
   LONGLONG                        lnPerfFreq ;

   LINEVISUAL                      Visual ;

   //=============================//
   // Chart-related fields        //
   //=============================//

   HPEN                            hPen ;
   int                             iScaleIndex ;
   FLOAT                          eScale;
   FLOAT                           FAR *lnValues;
   int                             *aiLogIndexes ;
   FLOAT                           lnMaxValue ;
   FLOAT                           lnMinValue ;
   FLOAT                           lnAveValue ;
   INT                             lnValidValues;

   //=============================//
   // Alert-related fields        //
   //=============================//

   HBRUSH                  hBrush ;
   BOOL                    bAlertOver ;         // over or under?
   FLOAT                   eAlertValue ;        // value to compare
   LPTSTR                  lpszAlertProgram ;   // program to run
   BOOL                    bEveryTime ;         // run every time or once?
   BOOL                    bAlerted ;           // alert happened on line?

   //=============================//
   // Report-related fields       //
   //=============================//
   int                     iReportColumn ;
   struct  _LINESTRUCT    *pLineCounterNext;
   int                     xReportPos ;
   int                     yReportPos ;
   }LINESTRUCT ;

typedef LINESTRUCT *PLINESTRUCT ;
typedef PLINESTRUCT PLINE ;
typedef PLINE *PPLINE ;

//======================================//
// DISKLINE data type                   //
//======================================//

#define dwLineSignature    (MAKELONG ('L', 'i'))

typedef struct DISKSTRINGSTRUCT
   {
   DWORD          dwLength ;
   DWORD          dwOffset ;
   } DISKSTRING ;
typedef DISKSTRING *PDISKSTRING ;

typedef struct DISKLINESTRUCT
   {
   int            iLineType ;
   DISKSTRING     dsSystemName ;
   DISKSTRING     dsObjectName ;
   DISKSTRING     dsCounterName ;
   DISKSTRING     dsInstanceName ;
   DISKSTRING     dsPINName ;
   DISKSTRING     dsParentObjName ;
   DWORD          dwUniqueID ;
   LINEVISUAL     Visual ;
   int            iScaleIndex ;
   FLOAT          eScale ;
   BOOL           bAlertOver ;
   FLOAT          eAlertValue ;
   DISKSTRING     dsAlertProgram ;
   BOOL           bEveryTime ;
   } DISKLINE ;

typedef DISKLINE *PDISKLINE ;

typedef FLOAT DFN (PLINESTRUCT);
typedef DFN   *PDFN;

typedef struct _TIMELINESTRUCT
{
    INT ppd ;                           // Pixels Per DataPoint
    INT rppd ;                          // Remaining Pixels Per DataPoint
    INT xLastTime ;                     // X coordinate of last time line.
    INT iValidValues ;                  // High water mark for valid data.
}TIMELINESTRUCT;

//======================================//
// Graph Data Type                      //
//======================================//

#define iGraphMaxTics   26

// This structure describes the characteristics of one visual
// graph. It is linked for the day when multiple graphs are
// displayed within one instance of the application.

typedef struct _GRAPHSTRUCT
   {
   BOOL           bManualRefresh ;
   HWND           hWnd ;
   BOOL           bModified ;

   PPERFSYSTEM       pSystemFirst;
   PLINESTRUCT    pLineFirst;

   int            xNumTics ;
   int            yNumTics ;
   int            axTics [iGraphMaxTics] ;
   int            ayTics [iGraphMaxTics] ;

   RECT           rectHorzScale ;
   RECT           rectVertScale ;
   RECT           rectData ;
   HRGN           hGraphRgn ;

   INT            gMaxValues;
   INT            gKnownValue;

   LINEVISUAL     Visual ;

   DWORD          gInterval;
   GRAPH_OPTIONS  gOptions;
   TIMELINESTRUCT gTimeLine;

   PPOINT         pptDataPoints ;
   SYSTEMTIME     *pDataTime ;

   HPEN           hGridPen ;
   HANDLE         hbRed ;
   BOOL           HighLightOnOff ;
   } GRAPHSTRUCT ;
typedef GRAPHSTRUCT *PGRAPHSTRUCT;
// minor version 3 to support alert, report, log intervals in msec
#define ChartMajorVersion    1
#define ChartMinorVersion    3

typedef struct DISKCHARTSTRUCT
   {
   DWORD          dwNumLines ;
   INT            gMaxValues;
   LINEVISUAL     Visual ;
   GRAPH_OPTIONS  gOptions ;
   BOOL           bManualRefresh ;
   OPTIONS        perfmonOptions ;
   } DISKCHART ;

typedef struct _SAVESTRUCT
{
    INT version;
    INT type;
    INT iPerfmonView ;
    INT graph_offset;
    INT alert_offset;
    INT log_offset;
}SAVESTRUCT;
typedef SAVESTRUCT *PSAVESTRUCT;

typedef struct _GRAPH_COUNTERS
{
    INT sys_name_len;
    INT sys_name_offset;
    INT obj_name_len;
    INT obj_name_offset;
    INT cnt_name_len;
    INT cnt_name_offset;
    INT inst_name_len;
    INT inst_name_offset;
    INT PIN_name_len;
    INT PIN_name_offset;
    INT POB_name_len;
    INT POB_name_offset;
    DWORD inst_unique_id;
    DWORD counter_type;
    DWORD counter_length;
    int   iScaleIndex ;
    FLOAT eScale;
    LINEVISUAL    Visual ;
    INT updated;
}GRAPH_COUNTERS;

typedef struct _SAVGRAFSTRUCT
{
    INT preference;
    INT MaxValues;
    RECT GraphArea;
    GRAPH_OPTIONS options;
    INT num_counters;
    GRAPH_COUNTERS counters[1];
}SAVGRAFSTRUCT;
typedef SAVGRAFSTRUCT *PSAVGRAFSTRUCT;

typedef struct _SAVLOGSTRUCT
{
    DWORDlogInterval;
    BOOL    logActive;
    LPTSTRlogFileName;
    DWORDlogFileSize;
}SAVLOGSTRUCT;
typedef SAVLOGSTRUCT *PSAVLOGSTRUCT;

#define DEF_GRAPH_INTERVAL  1000        // milliseconds
#define LINE_GRAPH          1
#define BAR_GRAPH           2
#define DEF_GRAPH_VMAX      100
#define SUCCESS             0
#define MIN_TIMER_INTERVAL 50
#define GRAPH_TIMER_ID      1

#define NO_VALUES_YET            -1
        // initial value for index to known and drawn values
#define MIN_NMAXVALUES           10
        // minimum number of values that a graph needs to be displayed ( >1 )
#define DX_CALIBRATION_LEFT      1
        // space between calibration value and left window edge
#define DX_LEGEND_RIGHT          1
        // space between right window edge and legend
#define DY_AXIS_TOP              0
        // space to allow at top of graph
#define DY_AXIS_BOTTOM           0
        // space to allow at bottom of graph
#define LG_TO_CALIBRATION_RATIO  5
        // width of calibration values * this number can't exceed screen width
#define LG_TO_LEGEND_RATIO       2
        // width of legend * this number can't exceed screen width

// LINEGRAPH
#define DEFAULT_VAL_BOTTOM        0
#define DEFAULT_DVAL_AXISHEIGHT   100
#define DEFAULT_MAX_VALUES        100
#define GRAPH_INWARD_EDGE           5
#define GRAPH_LEFT_PAD              5
#define GRAPH_DOWNWARD_EDGE         5
#define ROOM_FOR_LEGEND            40

// LINEGRAPH DISP
#define DEFAULT_F_DISPLAY_LEGEND  TRUE
#define DEFAULT_F_DISPLAY_CALIBRATION TRUE

#define NONE_LEN            MAX_SYSTEM_NAME_LENGTH + 1

//======================================//
// Log/Playback/Alert Status IDs        //
//======================================//

#define iPMStatusClosed      100
#define iPMStatusPaused      200
#define iPMStatusCollecting  300
#define iPMStatusPlaying     400

//======================================//
// Log Data Type                        //
//======================================//

typedef struct _LOGENTRYSTRUCT
   {
   DWORD          ObjectTitleIndex ;
   TCHAR          szComputer [MAX_SYSTEM_NAME_LENGTH + 1] ;
   TCHAR          szObject [PerfObjectLen + 1] ;
   BOOL           bSaveCurrentName ;
   struct  _LOGENTRYSTRUCT *pNextLogEntry ;
   } LOGENTRY ;

typedef LOGENTRY *PLOGENTRY ;

typedef struct LOGSTRUCT
   {
   int            iStatus ;
   BOOL           bManualRefresh ;
   BOOL           bModified ;

   PPERFSYSTEM    pSystemFirst;
   TCHAR          szFilePath [FilePathLen + 1] ;
   HANDLE         hFile ;
   long           lIndexBlockOffset ;
   int            iIndex ;
   PPERFDATA      pLogData ;
   DWORD          dwDetailLevel ;
   long           lFileSize ;
   DWORD          iIntervalMSecs ;
   int            xCol1Width ;
   PLOGENTRY      pLogEntryFirst ;

   // the following is used for saving system counter names into a
   // log file.  They are reset every the user changes log files.
   BOOL           bSaveCounterName ;
   LPTSTR         pBaseCounterName ;
   long           lBaseCounterNameSize ;
   long           lBaseCounterNameOffset ;

   // this is used for checking system time when re-logging.
   // this is to avoid log data not in chronological order.
   SYSTEMTIME     LastLogTime ;

   // the following is used for wait for data event handles
   HANDLE            *lpHandles ;
   int               NumberOfHandles ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

   } LOG ;

typedef LOG *PLOG ;

//======================================//
// Log File Data Types                  //
//======================================//

#define LogFileSignatureLen      6
#define LogFileBlockMaxIndexes   100

#define LogFileSignature         TEXT("Loges")
#define LogFileVersion           2
#define LogFileRevision          0

#define LogFileIndexData         0x01
#define LogFileIndexBookmark     0x02
#define LogFileIndexNextBlock    0x04
#define LogFileIndexEOF          0x08
#define LogFileIndexCounterName  0x010

typedef struct LOGHEADERSTRUCT
   {  // LOGHEADER
   TCHAR          szSignature [LogFileSignatureLen] ;
   int            iLength ;
   WORD           wVersion ;
   WORD           wRevision ;
   long           lBaseCounterNameOffset ;
   }  LOGHEADER ;

typedef LOGHEADER *PLOGHEADER ;

typedef struct LOGINDEXSTRUCT
   {  // LOGINDEX
   UINT           uFlags ;
   SYSTEMTIME     SystemTime ;
   long           lDataOffset ;
   int            iSystemsLogged ;
   } LOGINDEX ;

typedef LOGINDEX *PLOGINDEX ;

#define LogIndexSignatureLen  7
#define LogIndexSignature     TEXT("Index ")
#define LogIndexSignature1    "Perfmon Index"

typedef struct LOGFILEINDEXBLOCKSTRUCT
   {
   TCHAR          szSignature [LogIndexSignatureLen] ;
   int            iNumIndexes ;
   LOGINDEX       aIndexes [LogFileBlockMaxIndexes] ;
   DWORD          lNextBlockOffset ;
   } LOGINDEXBLOCK ;

typedef LOGINDEXBLOCK *PLOGINDEXBLOCK ;

typedef struct LOGPOSITIONSTRUCT
   {
   PLOGINDEXBLOCK pIndexBlock ;
   int            iIndex ;
   int            iPosition ;
   } LOGPOSITION ;

typedef LOGPOSITION *PLOGPOSITION ;

//======================================//
// Bookmark Data Type                   //
//======================================//

#define BookmarkCommentLen    256

typedef struct BOOKMARKSTRUCT
   {
   struct BOOKMARKSTRUCT *pBookmarkNext;
   SYSTEMTIME     SystemTime ;
   TCHAR          szComment [BookmarkCommentLen] ;
   int            iTic ;
   } BOOKMARK, *PBOOKMARK, **PPBOOKMARK ;

typedef struct _LOGFILECOUNTERNAME
   {
   TCHAR          szComputer [MAX_SYSTEM_NAME_LENGTH] ;
   DWORD          dwLastCounterId ;
   DWORD          dwLangId;
   long           lBaseCounterNameOffset ;
   long           lCurrentCounterNameOffset ;
   long           lMatchLength ;
   long           lUnmatchCounterNames ;
   } LOGFILECOUNTERNAME, *PLOGFILECOUNTERNAME, **PPLOGFILECOUNTERNAME ;

typedef struct COUNTERNAMESTRUCT
   {
   struct COUNTERNAMESTRUCT *pCounterNameNext ;
   LOGFILECOUNTERNAME       CounterName ;
   LPTSTR                   pRemainNames ;
   } LOGCOUNTERNAME, *PLOGCOUNTERNAME ;

typedef struct PLAYBACKLOGSTRUCT
   {
   LPTSTR         szFilePath ;
   LPTSTR         szFileTitle ;
//   TCHAR          szFilePath [FilePathLen + 1] ;
//   TCHAR          szFileTitle [FilePathLen + 1] ;
   int            iStatus ;
   HANDLE         hFile ;
   PLOGHEADER     pHeader ;
   HANDLE         hMapHandle ;
   int            iTotalTics ;
   int            iSelectedTics ;
   LOGPOSITION    BeginIndexPos ;
   LOGPOSITION    EndIndexPos ;
   LOGPOSITION    StartIndexPos ;
   LOGPOSITION    StopIndexPos ;
   PBOOKMARK      pBookmarkFirst ;
   LPTSTR         pBaseCounterNames ;
   long           lBaseCounterNameSize ;
   long           lBaseCounterNameOffset ;
   PLOGCOUNTERNAME   pLogCounterNameFirst ;
   } PLAYBACKLOG ;

//=====================================//
// Log File Counter Name data type     //
//=====================================//

// minor version 3 to support alert, report, log intervals in msec
// minor version 5 to support storing Log file name in setting
//  and start logging after reading the file.
#define LogMajorVersion    1
#define LogMinorVersion    5

typedef struct DISKLOGSTRUCT
   {
   DWORD          dwNumLines ;
   DWORD          dwIntervalSecs ;
   BOOL           bManualRefresh ;
   OPTIONS        perfmonOptions ;
   TCHAR          LogFileName[260] ;
   } DISKLOG ;

//======================================//
// Alert Data Type                      //
//======================================//

typedef struct ALERTSTRUCT
   {
   HWND           hWnd ;
   HWND           hAlertListBox ;
   int            iStatus ;
   BOOL           bManualRefresh ;
   BOOL           bModified ;

   PPERFSYSTEM    pSystemFirst ;
   PLINESTRUCT    pLineFirst ;

   DWORD          iIntervalMSecs ;

   LINEVISUAL     Visual ;

   HFONT          hFontItems ;
   int            yItemHeight ;

   int            xColorWidth ;
   int            xDateWidth ;
   int            xTimeWidth ;
   int            xNumberWidth ;
   int            xConditionWidth ;

   // used in controlling the horz scrollbar
   int            xTextExtent ;

   BOOL           bSwitchToAlert ;
   BOOL           bNetworkAlert ;
   BOOL           bEventLog ;
   TCHAR          MessageName [16] ;

   // used for the network alert thread
   HANDLE         hNetAlertThread ;
   DWORD          dwNetAlertThreadID ;

   // option to turn alert legend on/off
   BOOL           bLegendOn ;
   } ALERT ;

typedef ALERT *PALERT ;

#define AlertMajorVersion    1

// minor version 2 to support Alert msg name
// minor version 3 to support alert, report, log intervals in msec
// minor version 4 to support alert event logging
// minor version 6 to support alert misc options
#define AlertMinorVersion    6

typedef struct DISKALERTSTRUCT
   {
   LINEVISUAL     Visual ;
   DWORD          dwNumLines ;
   DWORD          dwIntervalSecs ;
   BOOL           bManualRefresh ;
   BOOL           bSwitchToAlert ;
   BOOL           bNetworkAlert ;
   TCHAR          MessageName [16] ;
   OPTIONS        perfmonOptions ;
   DWORD          MiscOptions ;
   } DISKALERT ;

//======================================//
// Report Data Type                     //
//======================================//

typedef struct COLUMNGROUPSTRUCT
   {
   struct COLUMNGROUPSTRUCT  *pColumnGroupNext ;
   struct COLUMNGROUPSTRUCT  *pColumnGroupPrevious ;
   struct OBJECTGROUPSTRUCT  *pParentObject ;
   LPTSTR         lpszParentName ;
   LPTSTR         lpszInstanceName ;
   int            ParentNameTextWidth ;
   int            InstanceNameTextWidth ;
   int            xPos ;
   int            xWidth ;
   int            yFirstLine ;
   int            ColumnNumber ;  // start from 0
   } COLUMNGROUP ;

typedef COLUMNGROUP *PCOLUMNGROUP ;

typedef struct COUNTERGROUPSTRUCT
   {
   struct COUNTERGROUPSTRUCT *pCounterGroupNext ;
   struct COUNTERGROUPSTRUCT *pCounterGroupPrevious ;
   struct OBJECTGROUPSTRUCT  *pParentObject ;
   DWORD          dwCounterIndex ;
   PLINE          pLineFirst ;
   int            yLine ;
   int            xWidth ;
   } COUNTERGROUP ;

typedef COUNTERGROUP *PCOUNTERGROUP ;

typedef struct OBJECTGROUPSTRUCT
   {
   struct OBJECTGROUPSTRUCT  *pObjectGroupNext ;
   struct OBJECTGROUPSTRUCT  *pObjectGroupPrevious ;
   struct SYSTEMGROUPSTRUCT  *pParentSystem ;
   LPTSTR         lpszObjectName ;
   PCOUNTERGROUP  pCounterGroupFirst ;
   PCOLUMNGROUP   pColumnGroupFirst ;
   int            yFirstLine ;
   int            yLastLine ;
   int            xWidth ;
   } OBJECTGROUP ;

typedef OBJECTGROUP *POBJECTGROUP ;

typedef struct SYSTEMGROUPSTRUCT
   {  // SystemGroup
   struct SYSTEMGROUPSTRUCT *pSystemGroupNext ;
   struct SYSTEMGROUPSTRUCT *pSystemGroupPrevious ;
   LPTSTR         lpszSystemName ;
   POBJECTGROUP   pObjectGroupFirst ;
   int            yFirstLine ;
   int            yLastLine ;
   int            xWidth ;
   } SYSTEMGROUP ;

typedef  SYSTEMGROUP   *PSYSTEMGROUP ;

enum REPORT_ITEM_TYPE
   {
   REPORT_TYPE_NOTHING,
   REPORT_TYPE_SYSTEM,
   REPORT_TYPE_OBJECT,
   REPORT_TYPE_COLUMN,
   REPORT_TYPE_COUNTER,
   REPORT_TYPE_LINE
   } ;

typedef union REPORT_ITEM_TAG
   {
   PSYSTEMGROUP   pSystem ;
   POBJECTGROUP   pObject ;
   PCOLUMNGROUP   pColumn ;
   PCOUNTERGROUP  pCounter ;
   PLINE          pLine ;
   } REPORT_ITEM ;

typedef struct REPORTSTRUCT
   {  // REPORT
   HWND           hWnd ;
   int            iStatus ;
   BOOL           bManualRefresh ;
   BOOL           bModified ;

   TCHAR          szTitle [120] ;
   SYSTEMTIME     SystemTime ;

   PPERFSYSTEM    pSystemFirst ;
   PLINE          pLineFirst ;

   PSYSTEMGROUP   pSystemGroupFirst ;

   REPORT_ITEM       CurrentItem ;
   enum  REPORT_ITEM_TYPE  CurrentItemType ;

   LINEVISUAL     Visual ;

   DWORD          iIntervalMSecs ;
   FLOAT          eTimeInterval ;
   HFONT          hFont ;
   HFONT          hFontHeaders ;
   int            yLineHeight ;

   int            xMaxCounterWidth ;
   int            xValueWidth ;

   int            xWidth ;
   int            yHeight ;
   } REPORT ;

typedef REPORT *PREPORT ;

// minor version 3 to support alert, report, log intervals in msec
#define ReportMajorVersion    1
#define ReportMinorVersion    3

typedef struct DISKREPORTSTRUCT
   {
   LINEVISUAL     Visual ;
   DWORD          dwNumLines ;
   DWORD          dwIntervalSecs ;
   BOOL           bManualRefresh ;
   OPTIONS        perfmonOptions ;
   } DISKREPORT ;

//======================================//
// File Header Type                     //
//======================================//

#define PerfSignatureLen  20

#define szPerfChartSignature     TEXT("PERF CHART")
#define szPerfAlertSignature     TEXT("PERF ALERT")
#define szPerfLogSignature       TEXT("PERF LOG")
#define szPerfReportSignature    TEXT("PERF REPORT")
#define szPerfWorkspaceSignature TEXT("PERF WORKSPACE")

typedef struct PERFFILEHEADERSTRUCT
   {  // PERFFILEHEADER
   TCHAR          szSignature [PerfSignatureLen] ;
   DWORD          dwMajorVersion ;
   DWORD          dwMinorVersion ;
   BYTE           abyUnused [100] ;
   } PERFFILEHEADER ;

#define WorkspaceMajorVersion    1

// minor version 1 to support window placement data
// minor version 2 to support alert msg name
// minor version 3 to support alert, report, log intervals in msec
// minor version 4 to support alert eventlog
// minor version 5 to support log file name in log setting
// minor version 6 to support alert misc options
#define WorkspaceMinorVersion    6

typedef struct DISKWORKSPACESTRUCT
   {
   INT               iPerfmonView ;
   DWORD             ChartOffset ;
   DWORD             AlertOffset ;
   DWORD             LogOffset ;
   DWORD             ReportOffset ;
   WINDOWPLACEMENT   WindowPlacement ;
   } DISKWORKSPACE ;


UIEXPRT.H

#define FOCUSDLG_SERVERS_ONLY(2)

#define FOCUSDLG_BROWSE_LOGON_DOMAIN         0x00010000
#define FOCUSDLG_BROWSE_WKSTA_DOMAIN         0x00020000
#define FOCUSDLG_BROWSE_OTHER_DOMAINS        0x00040000
#define FOCUSDLG_BROWSE_TRUSTING_DOMAINS     0x00080000
#define FOCUSDLG_BROWSE_WORKGROUP_DOMAINS    0x00100000

#define FOCUSDLG_BROWSE_ALL_DOMAINS   (        \
          FOCUSDLG_BROWSE_LOGON_DOMAIN       | \
          FOCUSDLG_BROWSE_WKSTA_DOMAIN       | \
          FOCUSDLG_BROWSE_OTHER_DOMAINS      | \
          FOCUSDLG_BROWSE_TRUSTING_DOMAINS   | \
          FOCUSDLG_BROWSE_WORKGROUP_DOMAINS  )

typedef UINT (FAR PASCAL *LPFNI_SYSTEMFOCUSDIALOG)(
    HWND    hwndOwner,
    UINT    nSelectionType,
    LPWSTR  pszName,
    UINT    cchBufSize,
    BOOL   *pfOKPressed,
    LPWSTR  pszHelpFile,
    DWORD   nHelpContext
    );


UTILS.C

/*
==============================================================================

  Application:

            Microsoft Windows NT (TM) Performance Monitor

  File:
            utils.c -- miscellaneous utility routines.

            This file contains miscellaneous utiltity routines, mostly
            low-level windows helpers. These routines are not specific
            to the perfmon utillity.

  Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
  Microsoft Confidential.
==============================================================================
*/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdarg.h>  // For ANSI variable args. Dont use UNIX <varargs.h>
#include <stdlib.h>  // For itoa
#include <stdio.h>   // for vsprintf.
#include <string.h>  // for strtok
#include <wchar.h>   // for swscanf

#include "perfmon.h"
#include "pmemory.h"        // for MemoryXXX (mallloc-type) routines
#include "utils.h"
#include "pmhelpid.h"       // IDs for WinHelp
#include "perfmops.h"       // for ConvertDecimalPoint() & ReconvertDecimalPoint()
#include "sizes.h"

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define DOS_FILES                0x0000   // Ordinary files
#define DOS_READONLY             0x0001   // Read-only files
#define DOS_HIDDEN               0x0002   // Hidden files
#define DOS_SYSTEM               0x0004   // System files
#define DOS_SUBDIRECTORIES       0x0010   // Subdirectories
#define DOS_ARCHIVES             0x0020   // Archives
#define DOS_LIB_DIR              0x2000   // LB_DIR flag
#define DOS_DRIVES               0x4000   // Drives
#define DOS_EXCLUSIVE            0x8000   // Exclusive bit
#define DOS_DRIVES_DIRECTORIES   0xC010   // Find drives and directories only

#define WILD_ONE                 '?'
#define WILD_ANY                 '*'

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void ClientRectToScreen (HWND hWnd,
                         LPRECT lpRect)
/*
   Effect:        Remaps lpRect from client coordinates to screen
                  coordinates. Analogous to ClientToScreen for rectangles.

   Note:          To convert a rectangle from the client coordinates of
                  Wnd1 to the client coordinates of Wnd2, call:

                        ClientRectToScreen (hWnd1, &rect) ;
                        ScreenRectToClient (hWnd2, &rect) ;

   See Also:      ClientToScreen (windows), ScreenRectToClient.

   Internals:     Since a rectangle is really only two points, let
                  windows do the work with ClientToScreen.
*/
   {  /* ClientRectToScreen */
   POINT    pt1, pt2 ;

   pt1.x = lpRect->left ;
   pt1.y = lpRect->top ;

   pt2.x = lpRect->right ;
   pt2.y = lpRect->bottom ;

   ClientToScreen (hWnd, &pt1) ;
   ClientToScreen (hWnd, &pt2) ;

   lpRect->left = pt1.x ;
   lpRect->top = pt1.y ;

   lpRect->right = pt2.x ;
   lpRect->bottom = pt2.y ;
   }  // ClientRectToScreen

void ScreenRectToClient (HWND hWnd, LPRECT lpRect)
/*
   Effect:        Remaps lpRect from screen coordinates to client
                  coordinates. Analogous to ScreenToClient for rectangles.

   Note:          To convert a rectangle from the client coordinates of
                  Wnd1 to the client coordinates of Wnd2, call:

                        ClientRectToScreen (hWnd1, &rect) ;
                        ScreenRectToClient (hWnd2, &rect) ;

   See Also:      ScreenToClient (windows), ClientRectToScreen.

   Internals:     Since a rectangle is really only two points, let
                  windows do the work with ScreenToClient.
*/
   {  // ScreenRectToClient
   POINT    pt1, pt2 ;

   pt1.x = lpRect->left ;
   pt1.y = lpRect->top ;

   pt2.x = lpRect->right ;
   pt2.y = lpRect->bottom ;

   ScreenToClient (hWnd, &pt1) ;
   ScreenToClient (hWnd, &pt2) ;

   lpRect->left = pt1.x ;
   lpRect->top = pt1.y ;

   lpRect->right = pt2.x ;
   lpRect->bottom = pt2.y ;
   }  // ScreenRectToClient

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

void Line (HDC hDC,
           HPEN hPen,
           int x1, int y1,
           int x2, int y2)
   {  // Line
   HPEN           hPenPrevious ;

   if (hPen)
      hPenPrevious = SelectPen (hDC, hPen) ;
   MoveToEx (hDC, x1, y1, NULL) ;
   LineTo (hDC, x2, y2) ;
   if (hPen)
      SelectObject (hDC, hPenPrevious) ;
   }  // Line

#if 0
void HLine (HDC hDC,
            HPEN hPen,
            int x1,
            int x2,
            int y)
   {  // HLine
   Line (hDC, hPen, x1, y, x2, y) ;
   }

void VLine (HDC hDC,
            HPEN hPen,
            int x,
            int y1,
            int y2)
   {  // VLine
   Line (hDC, hPen, x, y1, x, y2) ;
   }  // VLine
#endif

#ifdef  KEEP_UTIL
void Fill (HDC hDC,
           DWORD rgbColor,
           LPRECT lpRect)
   {  // Fill
   HBRUSH         hBrush ;

   hBrush = CreateSolidBrush (rgbColor) ;

   FillRect (hDC, lpRect, hBrush) ;

   DeleteBrush (hBrush) ;
   }  // Fill

void ThreeDConvex (HDC hDC,
               int x1, int y1,
               int x2, int y2)
   {  // ThreeDConvex
   HBRUSH         hBrushPrevious ;
   POINT          aPoints [8] ;
   DWORD          aCounts [2] ;
   HPEN           hPenPrevious ;

   //
   //  Draw Face
   //

   hBrushPrevious = SelectBrush (hDC, hBrushFace) ;
   PatBlt (hDC,
           x1 + ThreeDPad, y1 + ThreeDPad,
           x2 - x1 - ThreeDPad, y2 - y1 - ThreeDPad,
           PATCOPY) ;
   SelectBrush (hDC, hBrushPrevious) ;

   //
   //  Draw Highlight
   //

   if (hPenHighlight)
      hPenPrevious = SelectPen (hDC, hPenHighlight) ;

   aPoints [0].x = x1 ;
   aPoints [0].y = y2 - 1 ;   // this works slightly diff. than Line ??
   aPoints [1].x = x1 ;
   aPoints [1].y = y1 ;
   aPoints [2].x = x2 ;
   aPoints [2].y = y1 ;

   aPoints [3].x = x1 + 1 ;
   aPoints [3].y = y2 - 1 ;
   aPoints [4].x = x1 + 1 ;
   aPoints [4].y = y1 + 1 ;
   aPoints [5].x = x2 - 1 ;
   aPoints [5].y = y1 + 1 ;

   aCounts [0] = 3 ;
   aCounts [1] = 3 ;

   PolyPolyline (hDC, aPoints, aCounts, 2) ;

   if (hPenHighlight)
      hPenPrevious = SelectPen (hDC, hPenPrevious) ;

//   HLine (hDC, hPenHighlight, x1, x2, y1) ;              // outside top line
//   HLine (hDC, hPenHighlight, x1 + 1, x2 - 1, y1 + 1) ;  // inside top line
//   VLine (hDC, hPenHighlight, x1, y1, y2) ;              // outside left line
//   VLine (hDC, hPenHighlight, x1 + 1, y1 + 1, y2 - 1) ;  // inside left line

   //
   //  Draw Shadow
   //

   if (hPenShadow)
      hPenPrevious = SelectPen (hDC, hPenShadow) ;

   aPoints [0].x = x1 + 1 ;
   aPoints [0].y = y2 - 1 ;
   aPoints [1].x = x2 ;
   aPoints [1].y = y2 - 1 ;
   aPoints [2].x = x2 ;
   aPoints [2].y = y2 - 2 ;
   aPoints [3].x = x1 + 2 ;
   aPoints [3].y = y2 - 2 ;

   aPoints [4].x = x2 - 1 ;
   aPoints [4].y = y1 ;
   aPoints [5].x = x2 - 1 ;
   aPoints [5].y = y2 - 1;
   aPoints [6].x = x2 - 2 ;
   aPoints [6].y = y2 - 1 ;
   aPoints [7].x = x2 - 2 ;
   aPoints [7].y = y1 ;

   aCounts [0] = 4 ;
   aCounts [1] = 4 ;

   PolyPolyline (hDC, aPoints, aCounts, 2) ;

   if (hPenShadow)
      hPenPrevious = SelectPen (hDC, hPenPrevious) ;

//   HLine (hDC, hPenShadow, x1 + 1, x2, y2 - 1) ;   // outside bottom line
//   HLine (hDC, hPenShadow, x1 + 2, x2, y2 - 2) ;   // inside bottom line
//   VLine (hDC, hPenShadow, x2 - 1, y1 + 1, y2) ;   // outside right line
//   VLine (hDC, hPenShadow, x2 - 2, y1 + 2, y2) ;   // inside right line

   }  // ThreeDConvex

void ThreeDConcave (HDC hDC,
                    int x1, int y1,
                    int x2, int y2,
                    BOOL bFace)
   {  // ThreeDConcave
   HBRUSH         hBrushPrevious ;
   POINT          aPoints [6] ;
   DWORD          aCounts [2] ;
   HPEN           hPenPrevious ;

   //
   //  Draw Face
   //

   if (bFace)
      {
      hBrushPrevious = SelectBrush (hDC, hBrushFace) ;
      PatBlt (hDC,
              x1 + ThreeDPad, y1 + ThreeDPad,
              x2 - x1 - ThreeDPad, y2 - y1 - ThreeDPad,
              PATCOPY) ;
      SelectBrush (hDC, hBrushPrevious) ;
      }

   //
   //  Draw Shadow
   //

#if 1
   if (hPenShadow)
      hPenPrevious = SelectPen (hDC, hPenShadow) ;

   aPoints [0].x = x1 ;
   aPoints [0].y = y2 - 1 ;
   aPoints [1].x = x1 ;
   aPoints [1].y = y1 ;
   aPoints [2].x = x2 ;
   aPoints [2].y = y1 ;

   aPoints [3].x = x1 + 1 ;
   aPoints [3].y = y2 - 1 ;
   aPoints [4].x = x1 + 1 ;
   aPoints [4].y = y1 + 1 ;
   aPoints [5].x = x2 - 1 ;
   aPoints [5].y = y1 + 1 ;

   aCounts [0] = 3 ;
   aCounts [1] = 3 ;

   PolyPolyline (hDC, aPoints, aCounts, 2) ;

   if (hPenShadow)
      hPenPrevious = SelectPen (hDC, hPenPrevious) ;

#else
   HLine (hDC, hPenShadow, x1, x2, y1) ;              // outside top line
   HLine (hDC, hPenShadow, x1 + 1, x2 - 1, y1 + 1) ;  // inside top line
   VLine (hDC, hPenShadow, x1, y1, y2) ;              // outside left line
   VLine (hDC, hPenShadow, x1 + 1, y1 + 1, y2 - 1) ;  // inside left line
#endif

   //
   //  Draw Highlight
   //

#if 1
   if (hPenHighlight)
      hPenPrevious = SelectPen (hDC, hPenHighlight) ;

   aPoints [0].x = x1 + 1 ;
   aPoints [0].y = y2 - 1 ;
   aPoints [1].x = x2 ;
   aPoints [1].y = y2 - 1 ;

   aPoints [2].x = x2 - 1 ;
   aPoints [2].y = y2 - 1 ;
   aPoints [3].x = x2 - 1 ;
   aPoints [3].y = y1 ;

   aCounts [0] = 2 ;
   aCounts [1] = 2 ;

   PolyPolyline (hDC, aPoints, aCounts, 2) ;

   if (hPenHighlight)
      hPenPrevious = SelectPen (hDC, hPenPrevious) ;

#else
   HLine (hDC, hPenHighlight, x1 + 1, x2, y2 - 1) ;   // outside bottom line
   VLine (hDC, hPenHighlight, x2 - 1, y1 + 1, y2) ;   // outside right line
#endif
   }  // ThreeDConcave
#endif // KEEP_UTIL

void ThreeDConvex1 (HDC hDC,
                    int x1, int y1,
                    int x2, int y2)
   {  // ThreeDConvex1
   HBRUSH         hBrushPrevious ;
   POINT          aPoints [6] ;
   DWORD          aCounts [2] ;
   HPEN           hPenPrevious ;

   //
   //  Draw Face
   //
#if 1
   hBrushPrevious = SelectBrush (hDC, hBrushFace) ;
   PatBlt (hDC,
           x1 + 1, y1 + 1,
           x2 - x1 - 1, y2 - y1 - 1,
           PATCOPY) ;
   SelectBrush (hDC, hBrushPrevious) ;

   //
   //  Draw Highlight
   //

   if (hPenHighlight)
      hPenPrevious = SelectPen (hDC, hPenHighlight) ;

   aPoints [0].x = x1 ;
   aPoints [0].y = y2 - 1 ;
   aPoints [1].x = x1 ;
   aPoints [1].y = y1 ;
   aPoints [2].x = x2 ;
   aPoints [2].y = y1 ;

   Polyline (hDC, aPoints, 3) ;

   if (hPenHighlight)
      hPenPrevious = SelectPen (hDC, hPenPrevious) ;

#else
   HLine (hDC, hPenHighlight, x1, x2, y1) ;              // outside top line
   VLine (hDC, hPenHighlight, x1, y1, y2) ;              // outside left line
#endif

   //
   //  Draw Shadow
   //

#if 1
   if (hPenShadow)
      hPenPrevious = SelectPen (hDC, hPenShadow) ;

   aPoints [0].x = x1 + 1 ;
   aPoints [0].y = y2 - 1 ;
   aPoints [1].x = x2 ;
   aPoints [1].y = y2 - 1 ;

   aPoints [2].x = x2 - 1 ;
   aPoints [2].y = y2 - 1 ;
   aPoints [3].x = x2 - 1 ;
   aPoints [3].y = y1 ;

   aCounts [0] = 2 ;
   aCounts [1] = 2 ;

   PolyPolyline (hDC, aPoints, aCounts, 2) ;

   if (hPenShadow)
      hPenPrevious = SelectPen (hDC, hPenPrevious) ;
#else
   HLine (hDC, hPenShadow, x1 + 1, x2, y2 - 1) ;   // outside bottom line
   VLine (hDC, hPenShadow, x2 - 1, y1 + 1, y2) ;   // outside right line
#endif

   }  // ThreeDConvex1

void ThreeDConcave1 (HDC hDC,
                     int x1, int y1,
                     int x2, int y2)
   {  // ThreeDConcave1
   HBRUSH         hBrushPrevious ;
   POINT          aPoints [6] ;
   DWORD          aCounts [2] ;
   HPEN           hPenPrevious ;

   //
   //  Draw Face
   //

   hBrushPrevious = SelectBrush (hDC, hBrushFace) ;
   PatBlt (hDC,
           x1 + 1, y1 + 1,
           x2 - x1 - 1, y2 - y1 - 1,
           PATCOPY) ;
   SelectBrush (hDC, hBrushPrevious) ;

   //
   //  Draw Shadow
   //

#if 1
   if (hPenShadow)
      hPenPrevious = SelectPen (hDC, hPenShadow) ;

   aPoints [0].x = x1 ;
   aPoints [0].y = y2 - 1 ;
   aPoints [1].x = x1 ;
   aPoints [1].y = y1 ;
   aPoints [2].x = x2 ;
   aPoints [2].y = y1 ;

   Polyline (hDC, aPoints, 3) ;

   if (hPenShadow)
      hPenPrevious = SelectPen (hDC, hPenPrevious) ;
#else
   HLine (hDC, hPenShadow, x1, x2, y1) ;              // outside top line
   VLine (hDC, hPenShadow, x1, y1, y2) ;              // outside left line
#endif

   //
   //  Draw Highlight
   //
#if 1
   if (hPenHighlight)
      hPenPrevious = SelectPen (hDC, hPenHighlight) ;

   aPoints [0].x = x1 + 1 ;
   aPoints [0].y = y2 - 1 ;
   aPoints [1].x = x2 ;
   aPoints [1].y = y2 - 1 ;

   aPoints [2].x = x2 - 1 ;
   aPoints [2].y = y2 - 2 ;
   aPoints [3].x = x2 - 1 ;
   aPoints [3].y = y1 ;

   aCounts [0] = 2 ;
   aCounts [1] = 2 ;

   PolyPolyline (hDC, aPoints, aCounts, 2) ;

   if (hPenHighlight)
      hPenPrevious = SelectPen (hDC, hPenPrevious) ;
#else
   HLine (hDC, hPenHighlight, x1 + 1, x2, y2 - 1) ;   // outside bottom line
   VLine (hDC, hPenHighlight, x2 - 1, y1 + 1, y2) ;   // outside right line
#endif

   }  // ThreeDConcave1

int TextWidth (HDC hDC, LPTSTR lpszText)
   {
   SIZE           size ;

   if (!lpszText)
      return (0) ;

   GetTextExtentPoint (hDC, lpszText, lstrlen (lpszText), &size) ;
   return  (size.cx) ;
   }

int _cdecl DlgErrorBox (HWND hDlg, UINT id, ...)
   {
   TCHAR          szMessageFmt [FilePathLen + 1] ;
   TCHAR          szBuffer [FilePathLen * 2] ;
   va_list        vaList ;
   int            NumOfChar ;
   TCHAR          szApplication [WindowCaptionLen] ;

   NumOfChar = StringLoad (id, szMessageFmt) ;

   if (NumOfChar)
      {
      va_start (vaList, id) ;
      TSPRINTF (szBuffer, szMessageFmt, va_arg(vaList, LPTSTR)) ;
      va_end (vaList) ;

      StringLoad (IDS_APPNAME, szApplication) ;

      MessageBox (hDlg, szBuffer, szApplication,
         MB_OK | MB_ICONSTOP | MB_TASKMODAL) ;
      }

   return (0) ;
   }

int FontHeight (HDC hDC,
                 BOOL bIncludeLeading)
   {  // FontHeight
   TEXTMETRIC     tm ;

   GetTextMetrics (hDC, &tm) ;
   if (bIncludeLeading)
      return (tm.tmHeight + tm.tmExternalLeading) ;
   else
      return (tm.tmHeight) ;
   }  // FontHeight

int TextAvgWidth (HDC hDC,
                  int iNumChars)
   {
   TEXTMETRIC     tm ;
   int            xAvgWidth ;

   GetTextMetrics (hDC, &tm) ;

   xAvgWidth = iNumChars * tm.tmAveCharWidth ;

   // add 10% slop
   return (MulDiv (xAvgWidth, 11, 10)) ;
   }

void WindowCenter (HWND hWnd)
/*
   Effect:        Center the window hWnd in the center of the screen.
                  Physically update the windows appearance as well.

   Globals:       xScreenWidth, yScreenHeight.
*/
   {  // WindowCenter
   RECT           rectWindow ;
   int            xWindowWidth, yWindowHeight ;

   GetWindowRect (hWnd, &rectWindow) ;
   xWindowWidth = rectWindow.right - rectWindow.left ;
   yWindowHeight = rectWindow.bottom - rectWindow.top ;

   MoveWindow (hWnd,
               (xScreenWidth - xWindowWidth) / 2,
               (yScreenHeight - yWindowHeight) / 2,
               xWindowWidth,
               yWindowHeight,
               TRUE) ;
   }  // WindowCenter

BOOL DialogMove (HDLG hDlg,
                 WORD wControlID,
                 int xPos,
                 int yPos,
                 int xWidth,
                 int yHeight)
/*
   Effect:        Move the control identified by wControlID in the dialog
                  hDlg to the new position (xPos, yPos), and resize to
                  (xWidth, yHeight). If any of these values are NOCHANGE, retain
                  the current value.

   Examples:      DialogMove (hDlg, IDD_FOO, 10, 20, NOCHANGE, NOCHANGE)
                     moves control but does not resize it

                  DialogMove (hDlg, IDD_FOO, NOCHANGE, NOCHANGE, 100, NOCHANGE)
                     sets width of control to 100
*/
   {  // DialogMove
   HWND        hWndControl ;
   RECT        rectControl ;

   hWndControl = DialogControl (hDlg, wControlID) ;
   if (!hWndControl)
      return (FALSE) ;
   GetWindowRect (hWndControl, &rectControl) ;
   ScreenRectToClient (hDlg, &rectControl) ;

   MoveWindow (hWndControl,
               (xPos == NOCHANGE) ? rectControl.left : xPos,
               (yPos == NOCHANGE) ? rectControl.top : yPos,
               (xWidth == NOCHANGE) ? rectControl.right - rectControl.left : xWidth,
               (yHeight == NOCHANGE) ? rectControl.bottom - rectControl.top : yHeight,
               TRUE) ;

   return (TRUE) ;
   }  // DialogMove

int DialogWidth (HDLG hDlg,
                 WORD wControlID)
   {
   HWND           hWndControl ;
   RECT           rectControl ;

   hWndControl = DialogControl (hDlg, wControlID) ;
   if (!hWndControl)
      return (0) ;

   GetWindowRect (hWndControl, &rectControl) ;
   return (rectControl.right - rectControl.left) ;
   }

int DialogHeight (HDLG hDlg,
                  WORD wControlID)
   {
   HWND           hWndControl ;
   RECT           rectControl ;

   hWndControl = DialogControl (hDlg, wControlID) ;
   if (!hWndControl)
      return (0) ;

   GetWindowRect (hWndControl, &rectControl) ;
   return (rectControl.bottom - rectControl.top) ;
   }

int DialogXPos (HDLG hDlg,
                WORD wControlID)
   {  // DialogXPos
   HWND           hWndControl ;
   RECT           rectControl ;

   hWndControl = DialogControl (hDlg, wControlID) ;
   if (!hWndControl)
      return (0) ;

   GetWindowRect (hWndControl, &rectControl) ;
   ScreenRectToClient (hDlg, &rectControl) ;

   return  (rectControl.left) ;
   }  // DialogXPos

int DialogYPos (HDLG hDlg,
                WORD wControlID)
   {  // DialogYPos
   HWND           hWndControl ;
   RECT           rectControl ;

   hWndControl = DialogControl (hDlg, wControlID) ;
   if (!hWndControl)
      return (0) ;

   GetWindowRect (hWndControl, &rectControl) ;
   ScreenRectToClient (hDlg, &rectControl) ;

   return  (rectControl.top) ;
   }  // DialogYPos

void DialogEnable (HDLG hDlg,
                   WORD wID,
                   BOOL bEnable)
/*
   Effect:        Enable or disable (based on bEnable) the control
                  identified by wID in dialog hDlg.

   See Also:      DialogShow.
*/
   {  // DialogEnable
   HCONTROL       hControl ;

   hControl = GetDlgItem (hDlg, wID) ;
   if (hControl)
      EnableWindow (hControl, bEnable) ;
   }  // DialogEnable

void DialogShow (HDLG hDlg,
                 WORD wID,
                 BOOL bShow)
   {  // DialogShow
   HCONTROL       hControl ;

   hControl = GetDlgItem (hDlg, wID) ;
   if (hControl)
      ShowWindow (hControl, bShow ? SW_SHOW : SW_HIDE) ;
   }  // DialogShow

BOOL _cdecl DialogSetText (HDLG hDlg,
                           WORD wControlID,
                           WORD wStringID,
                           ...)
   {  // DialogSetText
   TCHAR           szFormat [ControlStringLen] ;
   TCHAR           szText [ControlStringLen] ;
   va_list         vaList ;

   if (LoadString (hInstance, wStringID,
                   szFormat, ControlStringLen - 1))
      {
      va_start (vaList, wStringID) ;
      TSPRINTF (szText, szFormat, va_arg(vaList, LPTSTR)) ;
      va_end (vaList) ;

      SetDlgItemText (hDlg, wControlID, szText) ;
      return (TRUE) ;
      }  // if
   else
      return (FALSE) ;
   }  // DialogSetText

// Bug - lpszFormat could contain % in it
#if 0
BOOL _cdecl DialogSetString (HDLG hDlg,
                             WORD wControlID,
                             LPTSTR lpszFormat,
                             ...)
   {  // DialogSetString
   TCHAR          szText [ControlStringLen] ;
   va_list        vaList ;

   va_start (vaList, lpszFormat) ;
   TSPRINTF (szText, lpszFormat, va_arg(vaList, LPTSTR)) ;
   va_end (vaList) ;

   SetDlgItemText (hDlg, wControlID, szText) ;
   return (TRUE) ;
   }  // DialogSetString
#endif

LPTSTR LongToCommaString (LONG lNumber,
                          LPTSTR lpszText)
   {  // LongToCommaString
   BOOL           bNegative ;
   TCHAR          szTemp1 [40] ;
   TCHAR          szTemp2 [40] ;
   LPTSTR         lpsz1 ;
   LPTSTR         lpsz2 ;
   int            i ;
   int            iDigit ;

   // 1. Convert the number to a reversed string.
   lpsz1 = szTemp1 ;
   bNegative = (lNumber < 0) ;
   lNumber = labs (lNumber) ;

   if (lNumber)
      while (lNumber)
         {
         iDigit = (int) (lNumber % 10L) ;
         lNumber /= 10L ;
         *lpsz1++ = (TCHAR) (TEXT('0') + iDigit) ;
         }
   else
      *lpsz1++ = TEXT('0') ;
   *lpsz1++ = TEXT('\0') ;

   // 2. reverse the string and add commas
   lpsz1 = szTemp1 + lstrlen (szTemp1) - 1 ;
   lpsz2 = szTemp2 ;

   if (bNegative)
      *lpsz2++ = TEXT('-') ;

   for (i = lstrlen (szTemp1) - 1;
        i >= 0 ;
        i--)
      {  // for
      *lpsz2++ = *lpsz1-- ;
      if (i && !(i % 3))
         *lpsz2++ = TEXT(',') ;
      }  // for
   *lpsz2++ = TEXT('\0') ;

   return (lstrcpy (lpszText, szTemp2)) ;
   }  // LongToCommaString

BOOL MenuSetPopup (HWND hWnd,
                   int iPosition,
                   WORD  wControlID,
                   LPTSTR lpszResourceID)
   {
   HMENU          hMenuMain ;
   HMENU          hMenuPopup ;
   TCHAR          szTopChoice [MenuStringLen + 1] ;

   hMenuMain = GetMenu (hWnd) ;
   hMenuPopup = LoadMenu (hInstance, lpszResourceID) ;

   if (!hMenuMain || !hMenuPopup)
      return (FALSE) ;

   StringLoad (wControlID, szTopChoice) ;
   return (ModifyMenu (hMenuMain, iPosition, MF_BYPOSITION | MF_POPUP,
                       (UINT) hMenuPopup, szTopChoice)) ;
   }

LPTSTR FileCombine (LPTSTR lpszFileSpec,
                    LPTSTR lpszFileDirectory,
                    LPTSTR lpszFileName)
   {  // FileCombine

   int      stringLen ;
   TCHAR    DIRECTORY_DELIMITER[2] ;

   DIRECTORY_DELIMITER[0] = TEXT('\\') ;
   DIRECTORY_DELIMITER[1] = TEXT('\0') ;

   lstrcpy (lpszFileSpec, lpszFileDirectory) ;

   stringLen = lstrlen (lpszFileSpec) ;
   if (stringLen > 0 &&
       lpszFileSpec [stringLen - 1] != DIRECTORY_DELIMITER [0])
      lstrcat (lpszFileSpec, DIRECTORY_DELIMITER) ;

   lstrcat (lpszFileSpec, lpszFileName) ;

   return (lpszFileSpec) ;
   }  // FileCombine

// This routine extract the filename portion from a given full-path filename
LPTSTR ExtractFileName (LPTSTR pFileSpec)
   {
   LPTSTR   pFileName = NULL ;
   TCHAR    DIRECTORY_DELIMITER1 = TEXT('\\') ;
   TCHAR    DIRECTORY_DELIMITER2 = TEXT(':') ;

   if (pFileSpec)
      {
      pFileName = pFileSpec + lstrlen (pFileSpec) ;

      while (*pFileName != DIRECTORY_DELIMITER1 &&
         *pFileName != DIRECTORY_DELIMITER2)
         {
         if (pFileName == pFileSpec)
            {
            // done when no directory delimiter is found
            break ;
            }
         pFileName-- ;
         }

      if (*pFileName == DIRECTORY_DELIMITER1 ||
         *pFileName == DIRECTORY_DELIMITER2)
         {
         // directory delimiter found, point the
         // filename right after it
         pFileName++ ;
         }
      }
   return pFileName ;
   }  // ExtractFileName

int CBAddInt (HWND hWndCB,
              int iValue)
   {  // CBAddInt
   TCHAR       szValue [ShortTextLen + 1] ;
   CHAR        szCharValue [ShortTextLen + 1] ;

   itoa (iValue, (LPSTR)szCharValue, 10) ;
#ifdef UNICODE
   mbstowcs (szValue, (LPSTR)szCharValue, strlen((LPSTR)szCharValue)+1) ;
   return (CBAdd (hWndCB, szValue)) ;
#else
   return (CBAdd (hWndCB, szCharValue)) ;
#endif

   }  // CBAddInt

void DialogSetInterval (HDLG hDlg,
                        WORD wControlID,
                        int  IntervalMSec)
   {
   TCHAR          szValue [MiscTextLen] ;

   TSPRINTF (szValue, TEXT("%3.3f"),
            (FLOAT)(IntervalMSec) / (FLOAT)1000.0) ;
   ConvertDecimalPoint (szValue) ;
   SetDlgItemText (hDlg, wControlID, szValue) ;
   }

void DialogSetFloat (HDLG hDlg,
                     WORD wControlID,
                     FLOAT eValue)
   {
   TCHAR          szValue [40] ;
   FLOAT          tempValue = eValue ;

   if (tempValue < (FLOAT) 0.0)
      {
      tempValue = - tempValue ;
      }

   if (tempValue < (FLOAT) 1.0E+8)
      {
      TSPRINTF (szValue, TEXT("%1.4f"), eValue) ;
      }
   else
      {
      TSPRINTF (szValue, TEXT("%14.6e"), eValue) ;
      }
   ConvertDecimalPoint (szValue) ;

   SetDlgItemText (hDlg, wControlID, szValue) ;
   }

FLOAT DialogFloat (HDLG hDlg,
                   WORD wControlID,
                   BOOL *pbOK)
/*
   Effect:        Return a floating point representation of the string
                  value found in the control wControlID of hDlg.

   Internals:     We use sscanf instead of atof becuase atof returns a
                  double. This may or may not be the right thing to do.
*/
   {  // DialogFloat
   TCHAR          szValue [ShortTextLen+1] ;
   FLOAT          eValue ;
   int            iNumScanned ;

   DialogText (hDlg, wControlID, szValue) ;
   ReconvertDecimalPoint (szValue) ;
   iNumScanned = swscanf (szValue, TEXT("%e"), &eValue) ;

   if (pbOK)
      *pbOK = (iNumScanned == 1) ;

   return (eValue) ;
   }  // DialogFloat

LPTSTR StringAllocate (LPTSTR lpszText1)
   {  // StringAllocate
   LPTSTR         lpszText2 ;

   if (!lpszText1)
      return (NULL) ;

   if (lstrlen (lpszText1) == 0)
      return (NULL) ;

   lpszText2 = MemoryAllocate ((lstrlen (lpszText1)+1) * sizeof (TCHAR)) ;
   if (lpszText2)
      lstrcpy (lpszText2, lpszText1) ;

   return  (lpszText2) ;
   }  // StringAllocate

int DivRound (int iNumerator, int iDenominator)
/*
   Effect:        Return the quotient (iNumerator / iDenominator).
                  Round the quotient to the nearest integer.
                  This function is similar to normal integer division (/),
                  but normal division always rounds down.

   Note:          Surely there must already be a runtime version of this,
                  but I couldn't find it.

   Note:          This function originally used the runtime div function
                  instead of (/ and %), but the div runtime function is
                  now broken (build 265).
*/
   {  // DivRound
   int            iQuotient ;
   int            iRemainder ;

   iQuotient = iNumerator / iDenominator ;
   iRemainder = iNumerator % iDenominator ;

   if (iRemainder >= (iDenominator / 2))
      iQuotient++ ;

   return (iQuotient) ;
   }

BOOL MenuEnableItem (HMENU hMenu,
                     WORD wID,
                     BOOL bEnable)
/*
   Effect:        Enable or disable, depending on bEnable, the menu item
                  associated with id wID in the menu hMenu.

                  Any disabled menu items are displayed grayed out.

   See Also:      EnableMenuItem (windows).
*/
   {  // MenuEnableItem
   return (EnableMenuItem (hMenu, wID,
                           bEnable ?
                              (MF_ENABLED | MF_BYCOMMAND) :
                              (MF_GRAYED | MF_BYCOMMAND))) ;
   }  // MenuEnableItem

void BitmapDimemsion (HBITMAP hBitmap, int *pHeight, int *pWidth)
   {  // BitmapDimemsion
   BITMAP  bm ;

   GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm) ;
   if (pHeight)
      *pHeight = bm.bmHeight ;
   if (pWidth)
      *pWidth = bm.bmWidth ;
   }  // BitmapDimemsion

int WindowHeight (HWND hWnd)
   {  // WindowHeight
   RECT           rectWindow ;

   GetWindowRect (hWnd, &rectWindow) ;
   return (rectWindow.bottom - rectWindow.top) ;
   }  // WindowHeight

#ifdef KEEP_UTIL
int WindowWidth (HWND hWnd)
   {  // WindowWidth
   RECT           rectWindow ;

   GetWindowRect (hWnd, &rectWindow) ;
   return (rectWindow.right - rectWindow.left) ;
   }  // WindowWidth

void WindowResize (HWND hWnd,
                   int xWidth,
                   int yHeight)
/*
   Effect:        Change the size of the window hWnd, leaving the
                  starting position intact.  Redraw the window.

                  If either xWidth or yHeight is NULL, keep the
                  corresponding dimension unchanged.

   Internals:     Since hWnd may be a child of another parent, we need
                  to scale the MoveWindow arguments to be in the client
                  coordinates of the parent.

*/
   {  // WindowResize
   RECT           rectWindow ;
   HWND           hWndParent ;

   GetWindowRect (hWnd, &rectWindow) ;
   hWndParent = WindowParent (hWnd) ;

   if (hWndParent)
      ScreenRectToClient (hWndParent, &rectWindow) ;

   MoveWindow (hWnd,
               rectWindow.left,
               rectWindow.top,
               xWidth ? xWidth : rectWindow.right - rectWindow.left,
               yHeight ? yHeight : rectWindow.bottom - rectWindow.top,
               TRUE) ;
   }  // WindowResize
#endif

void WindowSetTopmost (HWND hWnd, BOOL bTopmost)
/*
   Effect:        Set or clear the "topmost" attribute of hWnd. If a window
                  is "topmost", it remains ontop of other windows, even ones
                  that have the focus.
*/
   {
   SetWindowPos (hWnd, bTopmost ? HWND_TOPMOST : HWND_NOTOPMOST,
                 0, 0, 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE) ;
   }

void WindowEnableTitle (HWND hWnd, BOOL bTitle)
   {
   DWORD          dwStyle ;

   dwStyle = WindowStyle (hWnd) ;

   if (bTitle)
      dwStyle = WS_TILEDWINDOW | dwStyle ;
   else
      dwStyle =
         dwStyle &
         ~ (WS_DLGFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) ;

   if (!bTitle)
      SetMenu (hWnd, NULL) ;

   WindowSetStyle (hWnd, dwStyle) ;
   SetWindowPos (hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE |
                 SWP_NOZORDER | SWP_FRAMECHANGED );
   }

// removing the following routines since LINK32 is not doing that for us
#ifdef  KEEP_UTIL
int MessageBoxResource (HWND hWndParent,
                        WORD wTextID,
                        WORD wTitleID,
                        UINT uiStyle)
/*
   Effect:        Just like MessageBox, but takes the title and format
                  strings from the resoure. In addition, the format string
                  is used as a printf style format, combined with the
                  additional arguments.
*/
   {  // MessageBoxResource
   TCHAR          szText [MessageLen + 1] ;
   TCHAR          szCaption [WindowCaptionLen + 1] ;

   StringLoad (wTextID, szText) ;
   StringLoad (wTitleID, szCaption) ;

   return (MessageBox (hWndParent, szText, szCaption, uiStyle)) ;
   }  // MessageBoxResource

#define WndProcKey   TEXT("OLDWNDPROC")

FARPROC WindowSetWndProc (HWND hWnd,
                          HANDLE hInstance,
                          FARPROC lpfnNewWndProc)
/*
   Effect:     Replace the window procedure of hWnd with lpfnNewWndProc.
               Return the existing window procedure.

   Note:       For proper subclassing, NewWndProc should pass all
               unhandled messages to the original wndproc.

   Called By:  WindowSubclass, WindowUnsubclass.
*/
   {  // WindowSetWndProc
   FARPROC     lpfnNewProcInstance ;
   FARPROC     lpfnOldProc ;

   lpfnOldProc = (FARPROC) GetWindowLong (hWnd, GWL_WNDPROC) ;
   lpfnNewProcInstance = MakeProcInstance (lpfnNewWndProc, hInstance) ;
   SetWindowLong (hWnd, GWL_WNDPROC, (long) lpfnNewProcInstance) ;

   return (lpfnOldProc) ;
   }  // WindowSetWndProc

WNDPROC WindowGetOriginalWndProc (HWND hWnd)
/*
   Effect:        Return a far pointer to the "original" wndproc for
                  hWnd.

   Assert:        WindowSetOriginalProc was already called on this hWnd.

   See Also:      WindowSetOriginalWndProc.
*/
   {  // WindowGetOriginalWndProc
   return (WNDPROC) GetProp (hWnd, WndProcKey) ;
   }  // WindowGetOriginalWndProc

void WindowSetOriginalWndProc (HWND hWnd,
                               FARPROC lpfnWndProc)
/*
   Effect:        Save away a far pointer to the "original" wndproc for
                  hWnd.

   See Also:      WindowGetOriginalProc.
*/
   {  // WindowSetOriginalProc
   SetProp (hWnd, WndProcKey, (LPSTR) lpfnWndProc) ;
   }  // WindowSetOriginalProc

void WindowSubclass (HWND hWnd,
                     HANDLE hInstance,
                     FARPROC lpfnNewWndProc)
/*
   Effect:        Replace the wndproc for hWnd with lpfnNewWndProc.
                  Save away a pointer to the original procedure.

   See Also:      WindowUnsubclass.
*/
   {  // WindowSubclass
   FARPROC     lpfnOldWndProc ;

   lpfnOldWndProc = WindowSetWndProc (hWnd, hInstance, lpfnNewWndProc) ;
   WindowSetOriginalWndProc (hWnd, lpfnOldWndProc) ;
   }  // WindowSubclass

LONG WindowCallOriginalWndProc (HWND hWnd,
                                UINT msg,
                                WPARAM wParam,
                                LPARAM lParam)
   {
   WNDPROC        lpfnOriginalWndProc ;

   lpfnOriginalWndProc = WindowGetOriginalWndProc (hWnd) ;
   if (lpfnOriginalWndProc)
      return ((LONG) CallWindowProc (lpfnOriginalWndProc,
                                     hWnd, msg, wParam, lParam)) ;
   else return (FALSE) ;
   }

LRESULT APIENTRY FocusCtlWndProc (HWND hWnd,
                                  UINT wMsg,
                                  WPARAM wParam,
                                  LPARAM lParam)
   {  // FocusCtlWndProc
   BOOL           bCallDefProc ;
   LRESULT        lReturnValue ;

   bCallDefProc = TRUE ;
   lReturnValue = 0L ;

   switch (wMsg)
      {  // switch
      case WM_SETFOCUS:
         SendMessage (WindowParent (hWnd),
                      WM_DLGSETFOCUS, WindowID (hWnd), 0) ;
         break ;

      case WM_KILLFOCUS:
         SendMessage (WindowParent (hWnd),
                      WM_DLGKILLFOCUS, WindowID (hWnd), 0) ;
         break ;

      default:
         bCallDefProc = TRUE ;
      }  // switch

   if (bCallDefProc)
      lReturnValue = WindowCallOriginalWndProc (hWnd, wMsg, wParam, lParam) ;

   return (lReturnValue);
   }  // FocusWndProc

BOOL DlgFocus (HDLG hDlg, WORD wControlID)
   {  // DlgFocus
   HWND           hWndControl ;

   hWndControl = DialogControl (hDlg, wControlID) ;
   if (!hWndControl)
      return (FALSE) ;

   WindowSubclass (hWndControl, hInstance, (FARPROC) FocusCtlWndProc) ;
   return (TRUE) ;
   }  // DlgFocus

BOOL DeviceNumColors (HDC hDC)
   {  // DeviceNumColors
   int            nPlanes ;
   int            nBitsPixel ;

   nPlanes = GetDeviceCaps (hDC, PLANES) ;
   nBitsPixel = GetDeviceCaps (hDC, BITSPIXEL) ;

   return (1 << (nPlanes * nBitsPixel)) ;
   }  // DeviceNumColors

void DrawBitmap (HDC hDC,
                 HBITMAP hBitmap,
                 int xPos,
                 int yPos,
                 LONG  lROPCode)
   {  // DrawBitmap
   BITMAP  bm ;
   HDC     hDCMemory ;

   hDCMemory = CreateCompatibleDC (hDC) ;
   SelectObject (hDCMemory, hBitmap) ;

   GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm) ;

   BitBlt (hDC,                     // DC for Destination surface
           xPos, yPos,              // location in destination surface
           bm.bmWidth, bm.bmHeight, // dimension of bitmap
           hDCMemory,               // DC for Source surface
           0, 0,                    // location in source surface
           lROPCode) ;              // ROP code

   DeleteDC (hDCMemory) ;
   }  // DrawBitmap

#endif  // KEEP_UTIL

#ifdef PERFMON_DEBUG

#define MikeBufferSize         256

int _cdecl mike (TCHAR *szFormat, ...)
/*
   Note:          This function returns a value so that it can more easily
                  be used in conditional expressions.
*/
   {  // mike
   TCHAR          szBuffer [MikeBufferSize] ;
   va_list        vaList ;

   va_start (vaList, szFormat) ;
   TSPRINTF (szBuffer, szFormat, va_arg(vaList, LPTSTR)) ;
   va_end (vaList) ;

   MessageBox (NULL, szBuffer, TEXT("Debug"), MB_OK | MB_TASKMODAL) ;
   return (0) ;
   }  // mike

int _cdecl mike1 (TCHAR *szFormat, ...)
/*
   Note:          This function returns a value so that it can more easily
                  be used in conditional expressions.
*/
   {  //  mike1
   TCHAR           szBuffer [MikeBufferSize] ;
   va_list        vaList ;
   HDC            hDC ;
   RECT           rect ;

   va_start (vaList, szFormat) ;
   TSPRINTF (szBuffer, szFormat, va_arg(vaList, LPTSTR)) ;
   va_end (vaList) ;

   rect.left = 0 ;
   rect.right = xScreenWidth ;
   rect.top = 0 ;
   rect.bottom = 20 ;

   hDC = CreateScreenDC () ;
   ExtTextOut (hDC, 0, 0, ETO_OPAQUE, &rect,
               szBuffer, lstrlen (szBuffer), NULL) ;
   DeleteDC (hDC) ;

   return (0) ;
   }  // mike1

int _cdecl mike2 (TCHAR *szFormat, ...)
/*
   Note:          This function returns a value so that it can more easily
                  be used in conditional expressions.
*/
   {  //  mike2
   TCHAR           szBuffer [MikeBufferSize] ;
   va_list        vaList ;

   va_start (vaList, szFormat) ;
   TSPRINTF (szBuffer, szFormat, va_arg(vaList, LPTSTR)) ;
   va_end (vaList) ;

   OutputDebugString (szBuffer) ;

   return (0) ;
   }  // mike2
#endif      // PERFMON_DEBUG

#ifdef KEEP_UTIL
int inttok (LPSTR lpszText, LPSTR lpszDelimiters)
   {  // inttok

   // Inttok only works with LPSTRs because of the atoi & strtok

   LPSTR   lpszToken ;

   lpszToken = strtok (lpszText, lpszDelimiters) ;

   if (lpszToken)
      return (atoi (lpszToken)) ;
   else
      return (0) ;
   }  // inttok

void WindowPlacementToString (PWINDOWPLACEMENT pWP,
                              LPTSTR lpszText)
   {
   TSPRINTF (lpszText, TEXT("%d %d %d %d %d %d %d %d %d"),
            pWP->showCmd,
            pWP->ptMinPosition.x,
            pWP->ptMinPosition.y,
            pWP->ptMaxPosition.x,
            pWP->ptMaxPosition.y,
            pWP->rcNormalPosition.left,
            pWP->rcNormalPosition.top,
            pWP->rcNormalPosition.right,
            pWP->rcNormalPosition.bottom) ;
   }

void StringToWindowPlacement (LPTSTR lpszText,
                              PWINDOWPLACEMENT pWP)
   {  // StringToWindowPlacement
   int            iNumScanned ;

   iNumScanned = swscanf (lpszText, TEXT("%d %d %d %d %d %d %d %d %d"),
            &pWP->showCmd,
            &pWP->ptMinPosition.x,
            &pWP->ptMinPosition.y,
            &pWP->ptMaxPosition.x,
            &pWP->ptMaxPosition.y,
            &pWP->rcNormalPosition.left,
            &pWP->rcNormalPosition.top,
            &pWP->rcNormalPosition.right,
            &pWP->rcNormalPosition.bottom) ;
   }  // StringToWindowPlacement
#endif  // KEEP_UTIL

int LogFontHeight (HDC hDC,
                   int iPointSize)
/*
   Effect:        Return the appropriate pixel height for the lfHeight
                  field of the LOGFONT structure for the requested point
                  size. This size depends on the number of pixels per
                  logical inch of the current display context, hDC.

   Called By:     Any function which wants to create a particular
                  point-height font.
*/
   {  // LogFontHeight
   return (-MulDiv (iPointSize, GetDeviceCaps (hDC, LOGPIXELSY), 72)) ;
   }  // LogFontHeight

// this routine converts the input menu id into help id.
DWORD MenuIDToHelpID (DWORD MenuID)
   {
   DWORD HelpID = 0 ;

   if (MenuID >= IDM_FIRSTMENUID && MenuID <= IDM_LASTMENUID)
      {
      // only special cases...
      if (MenuID >= IDM_OPTIONSREFRESHNOWCHART &&
          MenuID <= IDM_OPTIONSREFRESHNOWREPORT)
         {
         HelpID = HC_PM_MENU_OPTIONSREFRESHNOW ;
         }
      else
         {
         HelpID = MenuID - MENUIDTOHELPID ;
         }
#ifndef ADVANCED_PERFMON
      // need to convert these 2 IDs for Perf. Meter
      if (HelpID == HC_PM_MENU_HELPABOUT)
         {
         HelpID = HC_NTPM_MENU_HELPABOUT ;
         }
      else if (HelpID == HC_PM_MENU_FILEEXIT)
         {
         HelpID = HC_NTPM_MENU_FILEEXIT ;
         }
#endif
      }

   return (HelpID) ;
   }


UTILS.H

//==========================================================================//
//                                  Constants                               //
//==========================================================================//

#define ThreeDPad          2
#define NOCHANGE           -1

#define MENUCLOSING        (0xFFFF0000)

#define WM_DLGSETFOCUS     (WM_USER + 0x201)
#define WM_DLGKILLFOCUS    (WM_USER + 0x202)

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define PinInclusive(x, lo, hi)                       \
   (max (lo, min (x, hi)))

#define PinExclusive(x, lo, hi)                       \
   (max ((lo) + 1, min (x, (hi) - 1)))

#define BoolEqual(a, b)                               \
   ((a == 0) == (b == 0))

//=============================//
// Window Instance Accessors   //
//=============================//

#define WindowParent(hWnd)                            \
   ((HWND) GetWindowLong (hWnd, GWL_HWNDPARENT))

#define WindowID(hWnd)                                \
   GetWindowLong (hWnd, GWL_ID)

#define WindowInstance(hWnd)                          \
   GetWindowWord (hWnd, GWW_HINSTANCE)

#define WindowStyle(hWnd)                             \
   GetWindowLong (hWnd, GWL_STYLE)

#define WindowSetStyle(hWnd, lStyle)                  \
   SetWindowLong (hWnd, GWL_STYLE, lStyle)

#define WindowExStyle(hWnd)                           \
   GetWindowLong (hWnd, GWL_EXSTYLE)

#define WindowSetID(hWnd, wID)                        \
   SetWindowLong (hWnd, GWL_ID, wID)

// All modeless dialogs need to be dispatched separately in the WinMain
// message loop, but only if the dialog exists.

#define ModelessDispatch(hDlg, lpMsg)                 \
   (hDlg ? IsDialogMessage (hDlg, lpMsg) : FALSE)

#define strclr(szString)                              \
   (szString [0] = TEXT('\0'))

#define strempty(lpszString)                          \
   (!(lpszString) || !(lpszString[0]))

#define pstrsame(lpsz1, lpsz2)                        \
   ((!lpsz1 && !lpsz2) || (lpsz1 && lpsz2 && strsame (lpsz1, lpsz2)))

#define pstrsamei(lpsz1, lpsz2)                        \
   ((!lpsz1 && !lpsz2) || (lpsz1 && lpsz2 && strsamei (lpsz1, lpsz2)))

#define StringLoad(wID, szText)                       \
   (LoadString (hInstance, wID,                       \
    szText, sizeof (szText) - sizeof(TCHAR)))

#define WindowInvalidate(hWnd)                        \
   (InvalidateRect (hWnd, NULL, TRUE))

#define WindowShow(hWnd, bShow)                       \
   (ShowWindow (hWnd, (bShow) ? SW_SHOW : SW_HIDE))

#define MenuCheck(hMenu, wID, bCheck)                 \
   (CheckMenuItem (hMenu, wID, (bCheck) ?             \
     (MF_BYCOMMAND | MF_CHECKED) : (MF_BYCOMMAND | MF_UNCHECKED)))

#define DeleteFont(hFont)                             \
   (DeleteObject (hFont))

#define DeleteBitmap(hBitmap)                         \
   (DeleteObject (hBitmap))

#define DialogControl(hDlg, wControlID)               \
   GetDlgItem (hDlg, wControlID)

#define DialogSetInt(hDlg, wControlID, iValue)        \
   (SetDlgItemInt (hDlg, wControlID, iValue, TRUE))

#define DialogText(hDlg, wControlID, szText)          \
   (GetDlgItemText (hDlg, wControlID, szText, sizeof (szText) / sizeof(TCHAR) - 1))

#define DialogInt(hDlg, wControlID)                   \
   (GetDlgItemInt (hDlg, wControlID, NULL, TRUE))

#define strsame(szText1, szText2)                     \
   (!lstrcmp (szText1, szText2))

#define strsamei(szText1, szText2)                     \
   (!lstrcmpi (szText1, szText2))

#define strnsame(szText1, szText2, iLen)              \
   (!lstrncmp (szText1, szText2, iLen))

#define CreateScreenDC()                              \
   CreateDC (TEXT("DISPLAY"), NULL, NULL, NULL)

#define RectContract(lpRect, xAmt, yAmt)              \
   {                                                  \
   (lpRect)->left += (xAmt) ;                         \
   (lpRect)->top += (yAmt) ;                          \
   (lpRect)->right -= (xAmt) ;                        \
   (lpRect)->bottom -= (yAmt) ;                       \
   }

#define IsBW(hDC)                                     \
   (DeviceNumColors (hDC) <= 2)

#ifdef KEEP_PRINT
#define IsPrinterDC(hDC)                              \
   (GetDeviceCaps (hDC, TECHNOLOGY) != DT_RASDISPLAY)
#else
#define IsPrinterDC(hDC)                              \
   (FALSE)
#endif

#define VertInchPixels(hDC, iNumerator, iDenominator) \
   ((iNumerator * GetDeviceCaps (hDC, LOGPIXELSY)) / iDenominator)

#define HorzInchPixels(hDC, iNumerator, iDenominator) \
   ((iNumerator * GetDeviceCaps (hDC, LOGPIXELSX)) / iDenominator)

#define VertPointPixels(hDC, iPoints)                 \
   ((iPoints * GetDeviceCaps (hDC, LOGPIXELSY)) / 72)

#define SimulateButtonPush(hDlg, wControlID)          \
   (PostMessage (hDlg, WM_COMMAND,                    \
                 (WPARAM) MAKELONG (wControlID, BN_CLICKED),  \
                 (LPARAM) DialogControl (hDlg, wControlID)))

// convert an unicode string to OEM string
#define ConvertUnicodeStr(pOemStr, pUnicodeStr)   \
   CharToOemBuff(pUnicodeStr, pOemStr, lstrlen(pUnicodeStr) + 1)

#define CallWinHelp(ContextID)   \
   WinHelp(hWndMain, pszHelpFile, HELP_CONTEXT, ContextID) ;

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

void Fill (HDC hDC,
           DWORD rgbColor,
           LPRECT lpRect) ;

void ScreenRectToClient (HWND hWnd,
                         LPRECT lpRect) ;

int TextWidth (HDC hDC, LPTSTR lpszText) ;

void ThreeDConcave (HDC hDC,
                    int x1, int y1,
                    int x2, int y2,
                    BOOL bFace) ;

void ThreeDConvex (HDC hDC,
                   int x1, int y1,
                   int x2, int y2) ;

void ThreeDConcave1 (HDC hDC,
                     int x1, int y1,
                     int x2, int y2) ;

void ThreeDConvex1 (HDC hDC,
                   int x1, int y1,
                   int x2, int y2) ;

int _cdecl mike (TCHAR *szFormat, ...) ;

int _cdecl DlgErrorBox (HWND hDlg, UINT id, ...) ;

int _cdecl mike1 (TCHAR *szFormat, ...) ;
int _cdecl mike2 (TCHAR *szFormat, ...) ;

int FontHeight (HDC hDC,
                 BOOL bIncludeLeading) ;

int TextAvgWidth (HDC hDC,
                  int iNumChars) ;

void WindowCenter (HWND hWnd) ;

BOOL DialogMove (HDLG hDlg,
                 WORD wControlID,
                 int xPos,
                 int yPos,
                 int xWidth,
                 int yHeight) ;

int DialogWidth (HDLG hDlg,
                 WORD wControlID) ;

int DialogXPos (HDLG hDlg,
                WORD wControlID) ;

int DialogYPos (HDLG hDlg,
                WORD wControlID) ;

void DialogShow (HDLG hDlg,
                 WORD wID,
                 BOOL bShow) ;

BOOL _cdecl DialogSetText (HDLG hDlg,
                           WORD wControlID,
                           WORD wStringID,
                           ...) ;
#if 0
BOOL _cdecl DialogSetString (HDLG hDlg,
                             WORD wControlID,
                             LPTSTR lpszFormat,
                             ...) ;
#endif
#define DialogSetString(hDlg, wControlID, lpszFormat)  \
   SetDlgItemText (hDlg, wControlID, lpszFormat)

LPTSTR LongToCommaString (LONG lNumber,
                         LPTSTR lpszText) ;

BOOL MenuSetPopup (HWND hWnd,
                   int iPosition,
                   WORD  wControlID,
                   LPTSTR lpszResourceID) ;

void DialogEnable (HDLG hDlg,
                   WORD wID,
                   BOOL bEnable) ;

LPTSTR FileCombine (LPTSTR lpszFileSpec,
                   LPTSTR lpszFileDirectory,
                   LPTSTR lpszFileName) ;

LPTSTR ExtractFileName (LPTSTR pFileSpec) ;

int CBAddInt (HWND hWndCB,
              int iValue) ;

FLOAT DialogFloat (HDLG hDlg,
                   WORD wControlID,
                   BOOL *pbOK) ;

LPTSTR StringAllocate (LPTSTR lpszText1) ;

int DivRound (int iNumerator, int iDenominator) ;

BOOL MenuEnableItem (HMENU hMenu,
                     WORD wID,
                     BOOL bEnable) ;

void DrawBitmap (HDC hDC,
                 HBITMAP hBitmap,
                 int xPos,
                 int yPos,
                 LONG  lROPCode) ;

void BitmapDimemsion (HBITMAP hBitmap, int *pHeight, int *pWidth) ;

void WindowResize (HWND hWnd,
                   int xWidth,
                   int yHeight) ;

int WindowHeight (HWND hWnd) ;

void WindowSetTopmost (HWND hWnd, BOOL bTopmost) ;

void WindowEnableTitle (HWND hWnd, BOOL bTitle) ;

void Line (HDC hDC,
           HPEN hPen,
           int x1, int y1,
           int x2, int y2) ;

#define HLine(hDC, hPen, x1, x2, y)          \
   Line (hDC, hPen, x1, y, x2, y) ;

#define VLine(hDC, hPen, x, y1, y2)          \
   Line (hDC, hPen, x, y1, x, y2) ;

int DialogHeight (HDLG hDlg,
                  WORD wControlID) ;

void DialogSetFloat (HDLG hDlg,
                     WORD wControlID,
                     FLOAT eValue) ;

void DialogSetInterval (HDLG hDlg,
                        WORD wControlID,
                        int  IntervalMSec ) ;

int MessageBoxResource (HWND hWndParent,
                        WORD wTextID,
                        WORD wTitleID,
                        UINT uiStyle) ;

void WindowPlacementToString (PWINDOWPLACEMENT pWP,
                              LPTSTR lpszText) ;

void StringToWindowPlacement (LPTSTR lpszText,
                              PWINDOWPLACEMENT pWP) ;

DWORD MenuIDToHelpID (DWORD MenuID) ;


VALUEBAR.C

/*
==============================================================================

  Application:

            Microsoft Windows NT (TM) Performance Monitor

  File:
            status.c - Status window procedure and supporting routines.

            This file contains code creating the status window, which is
            a child of the legend window. The status window shows the
            time duration of the chart, and the last, avg, min and max
            of the currently-selected chart line.

  Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
==============================================================================
*/

//==========================================================================//
//                                  Includes                                //
//==========================================================================//

#include <stdio.h>
#include <stdlib.h>   // for mbstowcs

#include "perfmon.h"
#include "perfmops.h"      // for ConvertDecimalPoint
#include "valuebar.h"

#include "grafdata.h"      // for CurrentGraphLine
#include "graph.h"
#include "playback.h"      // for PlayingBackLog
#include "legend.h"
#include "utils.h"

//==========================================================================//
//                                  Constants                               //
//==========================================================================//
HDC   hVBarDC ;

#define szGraphStatusClass          TEXT("PerfmonGraphStatusClass")
#define dwGraphStatusClassStyle     (CS_HREDRAW | CS_VREDRAW | CS_OWNDC)
#define iGraphStatusClassExtra      (0)
#define iGraphStatusWindowExtra     (0)
#define dwGraphStatusWindowStyle    (WS_CHILD | WS_VISIBLE)

#define szStatusValueFormat         TEXT("%10.3f")
#define szStatusMediumnValueFormat  TEXT("%10.0f")
#define szStatusLargeValueFormat    TEXT("%10.4e")
#define eStatusValueTooLarge        ((FLOAT) 1.0E+20)
#define eStatusValueMax             ((FLOAT) 999999.999)
#define eStatusLargeValueMax        ((FLOAT) 9999999999.0)

#define szValueTooHigh              TEXT("+ + + +")
#define szValueTooLow               TEXT("- - - -")

#define StatusLastElt               0
#define StatusAvgElt                1
#define StatusMinElt                2
#define StatusMaxElt                3
#define StatusTimeElt               4

#define StatusNumElts               5

#define StatusDrawAvg(hDC, eValue, bForceRedraw)   \
   DrawStatusValue (hDC, StatusAvgElt, eValue, bForceRedraw)

#define StatusDrawMax(hDC, eValue, bForceRedraw)   \
   DrawStatusValue (hDC, StatusMaxElt, eValue, bForceRedraw)

#define StatusDrawMin(hDC, eValue, bForceRedraw)   \
   DrawStatusValue (hDC, StatusMinElt, eValue, bForceRedraw)

//==========================================================================//
//                                  Typedefs                                //
//==========================================================================//

typedef struct StatusEltStruct
   {
   TCHAR          szText [20] ;
   int            xTextPos ;
   int            xValuePos ;
   FLOAT          eValue ;
   } StatusEltType ;

// This structure represents the "instance data" for a StatusWindow. The
// information is collected in a structure to ease the conversion of this
// code when later adding multiple graph windows. For now, one copy of this
// struct is defined as local data to this file.
typedef struct StatusDataStruct
   {
   StatusEltType  aElts [StatusNumElts] ;
   SIZE           sizeValue ;
   int            yHeight ;
   } StatusDataType ;

//==========================================================================//
//                                Local Data                                //
//==========================================================================//

StatusDataType    StatusData ;

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define StatusTopMargin()     (2)
#define StatusBottomMargin()  (4)

#define StatusLeftMargin()    (3)
#define StatusTextMargin()    (5 + ThreeDPad)
#define StatusValueMargin()   (3 + ThreeDPad)

//==========================================================================//
//                              Local Functions                             //
//==========================================================================//

void DrawStatusValue (HDC hDC,
                      int iEltOffset,
                      FLOAT eValue,
                      BOOL bForceRedraw)
/*
   Called By:     StatusDrawTime, StatusDrawLast, StatusDrawAvg,
                  StatusDrawMin, StatusDrawMax.
*/
   {  // DrawStatusValue
   RECT           rectValue ;
   TCHAR          szValue [20] ;

   if (!bForceRedraw && eValue == StatusData.aElts[iEltOffset].eValue)
      return ;
   StatusData.aElts[iEltOffset].eValue = eValue ;

   rectValue.left = StatusData.aElts[iEltOffset].xValuePos ;
   rectValue.top = StatusTopMargin () + ThreeDPad + 1;
   rectValue.right = rectValue.left + StatusData.sizeValue.cx ;
   rectValue.bottom = rectValue.top + StatusData.sizeValue.cy ;

   if (eValue > eStatusValueMax)
      {
      if (eValue > eStatusValueTooLarge)
         {
         lstrcpy (szValue, szValueTooHigh) ;
         }
      else
         {
         TSPRINTF (szValue,
            (eValue > eStatusLargeValueMax) ? szStatusLargeValueFormat :
            szStatusMediumnValueFormat,
            eValue) ;
         ConvertDecimalPoint (szValue) ;
         }
      }
   else if (eValue < -eStatusValueMax)
      lstrcpy (szValue, szValueTooLow) ;
   else
      {
      TSPRINTF (szValue, szStatusValueFormat, eValue) ;
      ConvertDecimalPoint (szValue) ;
      }

   ExtTextOut (hDC, rectValue.right, rectValue.top, ETO_OPAQUE, &rectValue,
               szValue, lstrlen (szValue), NULL) ;
   }  // DrawStatusValue

//==========================================================================//
//                              Message Handlers                            //
//==========================================================================//

//void static OnCreate (HWND hWnd)
void OnVBarCreate (HWND hWnd)
/*
   Effect:        Perform any actions needed when a status window is created.
                  In particular, set the instance data to initial values,
                  determine the size and placement of the various elements
                  of the status display.

   Called By:     GraphStatusWndProc only, in response to a WM_CREATE message.
*/
   {  // OnCreate
   TCHAR          szValue [20] ;
   HDC            hDC ;
   int            iLen ;
   int            i ;

   hDC = hVBarDC = GetDC (hWnd) ;
   SelectFont (hDC, hFontScales) ;
   SetBkColor (hDC, ColorBtnFace) ;
   SetTextAlign (hDC, TA_RIGHT | TA_TOP) ;

   //=============================//
   // Load Text Labels            //
   //=============================//

   StringLoad (IDS_STATUSLAST, StatusData.aElts[StatusLastElt].szText) ;
   StringLoad (IDS_STATUSAVG, StatusData.aElts[StatusAvgElt].szText) ;
   StringLoad (IDS_STATUSMIN, StatusData.aElts[StatusMinElt].szText) ;
   StringLoad (IDS_STATUSMAX, StatusData.aElts[StatusMaxElt].szText) ;
   StringLoad (IDS_STATUSTIME, StatusData.aElts[StatusTimeElt].szText) ;

   //=============================//
   // Determine Status Height     //
   //=============================//

   StatusData.yHeight = StatusTopMargin () +
                        StatusBottomMargin () +
                        FontHeight (hDC, TRUE) +
                        2 * ThreeDPad ;

   //=============================//
   // Set position/size of elts   //
   //=============================//

   // Determine the bounding box for each status value by using a max value.
#ifdef JAPAN
   iLen = TSPRINTF (szValue, szStatusMediumnValueFormat, -((FLOAT) 9999.999)) ;
#else
   iLen = TSPRINTF (szValue, szStatusLargeValueFormat, -eStatusValueMax) ;
#endif

   GetTextExtentPoint (hDC, szValue, lstrlen(szValue), &StatusData.sizeValue) ;
   for (i = 0 ;
        i < StatusNumElts ;
        i++)
      {  // for
      StatusData.aElts[i].eValue = (FLOAT) 0.0 ;

      if (i)
         StatusData.aElts[i].xTextPos =
            StatusTextMargin () +
            StatusData.aElts[i - 1].xValuePos +
            StatusData.sizeValue.cx ;
      else
         StatusData.aElts[i].xTextPos = StatusLeftMargin () ;
      StatusData.aElts[i].xValuePos = StatusData.aElts[i].xTextPos +
                                     StatusValueMargin () +
                                     TextWidth (hDC, StatusData.aElts[i].szText) ;
      }  // for
   }  // OnCreate

void static OnPaint (HWND hWnd)
/*
   Effect:        Paint the invalid surface of hWnd. Draw each label, each
                  recessed value box, and each value.

   Called By:     GraphStatusWndProc only, in response to a WM_PAINT message.
*/
   {
   HDC            hDC ;
   PAINTSTRUCT    ps ;
   RECT           rectClient ;
   int            i ;
   PLINESTRUCT    pLine;

   hDC = BeginPaint (hWnd, &ps) ;
   SetBkMode (hDC, TRANSPARENT) ;

   GetClientRect (hWnd, &rectClient) ;
   HLine (hDC, GetStockObject (BLACK_PEN),
          rectClient.left, rectClient.right,
          rectClient.bottom - 1) ;

   if ((pGraphs->gOptions.bStatusBarChecked) &&
       (pGraphs->gOptions.bLegendChecked))
      {  // if
      UINT  hPrevTextAlign = SetTextAlign (hDC, TA_LEFT | TA_TOP) ;

      for (i = 0 ;
           i < StatusNumElts ;
           i++)
         {  // for
         // Draw the label
         TextOut (hDC, StatusData.aElts[i].xTextPos, StatusTopMargin () + ThreeDPad,
                  StatusData.aElts[i].szText,
                  lstrlen (StatusData.aElts[i].szText)) ;

         // Draw the recesed value box
         ThreeDConcave1 (hDC,
                        StatusData.aElts[i].xValuePos - ThreeDPad,
                        StatusTopMargin (),
                        StatusData.aElts[i].xValuePos + StatusData.sizeValue.cx + ThreeDPad,
                        StatusTopMargin () + StatusData.sizeValue.cy + 2 * ThreeDPad ) ;
         }  // for

      // restore TextAlign for drawing values
      SetTextAlign (hDC, hPrevTextAlign) ;

      // Draw the values themselves

      pLine = CurrentGraphLine (hWndGraph) ;
      StatusDrawTime (hDC, TRUE) ;

      StatusDrawLast (hDC, pLine, TRUE) ;

      if (pLine)
         {
         StatusDrawAvg (hDC, pLine->lnAveValue, TRUE) ;
         StatusDrawMin (hDC, pLine->lnMinValue, TRUE) ;
         StatusDrawMax (hDC, pLine->lnMaxValue, TRUE) ;
         }
      else
         {
         StatusDrawAvg (hVBarDC,  (FLOAT)0.0, TRUE) ;
         StatusDrawMax (hVBarDC,  (FLOAT)0.0, TRUE) ;
         StatusDrawMin (hVBarDC,  (FLOAT)0.0, TRUE) ;
         }
      }  // if

   EndPaint (hWnd, &ps) ;
   }  // StatusPaint

//==========================================================================//
//                             Exported Functions                           //
//==========================================================================//

void StatusDrawTime (HDC hDC, BOOL bForceRedraw)
/*
   Called By:     StatusTimer, StatusPaint.
*/
   {  // StatusDrawTime
   FLOAT          eTimeSeconds ;

   if (PlayingBackLog ())
      eTimeSeconds = (FLOAT) PlaybackSelectedSeconds () ;
   else
      eTimeSeconds = pGraphs->gOptions.eTimeInterval *
                     (FLOAT) pGraphs->gMaxValues;

   DrawStatusValue (hDC, StatusTimeElt, eTimeSeconds, bForceRedraw) ;
   }  // StatusDrawTime

void StatusDrawLast (HDC hDC, PLINESTRUCT pLine, BOOL bForceRedraw)
/*
   Called By:     StatusTimer, StatusPaint.
*/
   {
   INT            iKnownValue ;
   int            iMaxValues ;
   FLOAT          eValue ;

   if (!pLine || pGraphs->gKnownValue == -1)
      eValue = (FLOAT) 0.0 ;
   else
      {
      iKnownValue = pGraphs->gKnownValue ;
      iMaxValues  = pGraphs->gMaxValues ;
      eValue = pLine->lnValues [iKnownValue % iMaxValues] ;
      }

   DrawStatusValue (hDC, StatusLastElt, eValue, bForceRedraw) ;
   }

#if 0
void StatusDrawAvg (HDC hDC, PLINESTRUCT pLine, BOOL bForceRedraw)
/*
   Called By:     StatusTimer, StatusPaint.
*/
   {
   FLOAT          eValue ;

   if (!pLine)
      eValue = (FLOAT) 0.0 ;
   else
      eValue = pLine->lnAveValue ;

   DrawStatusValue (hDC, StatusAvgElt, eValue, bForceRedraw) ;
   }

void StatusDrawMax (HDC hDC, PLINESTRUCT pLine, BOOL bForceRedraw)
/*
   Called By:     StatusTimer, StatusPaint.
*/
   {
   FLOAT          eValue ;

   if (!pLine)
      eValue = (FLOAT) 0.0 ;
   else
      eValue = pLine->lnMaxValue ;

   DrawStatusValue (hDC, StatusMaxElt, eValue, bForceRedraw) ;
   }

void StatusDrawMin (HDC hDC, PLINESTRUCT pLine, BOOL bForceRedraw)
/*
   Called By:     StatusTimer, StatusPaint.
*/
   {
   FLOAT          eValue ;

   if (!pLine)
      eValue = (FLOAT) 0.0 ;
   else
      eValue = pLine->lnMinValue ;

   DrawStatusValue (hDC, StatusMinElt, eValue, bForceRedraw) ;
   }
#endif

LRESULT APIENTRY GraphStatusWndProc (HWND hWnd,
                                     WORD wMsg,
                                     WPARAM wParam,
                                     LPARAM lParam)
   {  // GraphStatusWndProc
   BOOL           bCallDefProc ;
   LRESULT        lReturnValue ;

   bCallDefProc = FALSE ;
   lReturnValue = 0L ;

   switch (wMsg)
      {  // switch
      case WM_CREATE:
         //OnCreate (hWnd) ;
         OnVBarCreate (hWnd) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_DESTROY:
         ReleaseDC (hWnd, hVBarDC) ;
         break ;

      default:
         bCallDefProc = TRUE ;
      }  // switch

   if (bCallDefProc)
      lReturnValue = DefWindowProc (hWnd, wMsg, wParam, lParam) ;

   return (lReturnValue);
   }  // GraphStatusWndProc

int ValuebarHeight (HWND hWnd)
/*
   Effect:        A status window has a preferred height, based on the font
                  used in its display. Return the preferred height, determined
                  when the window was created.

   Assert:        OnCreate has already been called, and it set
                  StatusData.yHeight.
*/
   {
   return (StatusData.yHeight) ;
   }

void StatusTimer (HWND hWnd, BOOL bForceRedraw)
/*
   Effect:        Perform any status-window actions necessary when a timer
                  tick has been received. In particular, update (redraw)
                  any of the changed values in the status bar.

   Internals:     Each of these called functions compares the value to be
                  displayed with the previous value and doesn't draw if the
                  values are equal.
*/
   {  // StatusTimer
   PLINESTRUCT pLine;

   pLine = CurrentGraphLine (hWndGraph) ;

   StatusDrawLast (hVBarDC, pLine, bForceRedraw) ;

   if (pLine)
      {
      StatusDrawAvg (hVBarDC, pLine->lnAveValue, bForceRedraw) ;
      StatusDrawMin (hVBarDC, pLine->lnMinValue, bForceRedraw) ;
      StatusDrawMax (hVBarDC, pLine->lnMaxValue, bForceRedraw) ;
      }
   else
      {
      StatusDrawAvg (hVBarDC,  (FLOAT)0.0, bForceRedraw) ;
      StatusDrawMax (hVBarDC,  (FLOAT)0.0, bForceRedraw) ;
      StatusDrawMin (hVBarDC,  (FLOAT)0.0, bForceRedraw) ;
      }
   }  // StatusTimer

HWND CreateGraphStatusWindow (HWND hWndGraph)
   {
   return (CreateWindow (szGraphStatusClass,       // class
                         NULL,                     // caption
                         dwGraphStatusWindowStyle, // window style
                         0, 0,                     // position
                         0, 0,                     // size
                         hWndGraph,                // parent window
                         NULL,                     // menu
                         hInstance,               // program instance
                         NULL)) ;                  // user-supplied data
   }

BOOL GraphStatusInitializeApplication (void)
/*
   Called By:     GraphInitializeApplication only
*/
   {
   WNDCLASS       wc ;

   wc.style          = dwGraphStatusClassStyle ;
   wc.lpfnWndProc    = (WNDPROC) GraphStatusWndProc ;
   wc.hInstance      = hInstance ;
   wc.cbClsExtra     = iGraphStatusClassExtra ;
   wc.cbWndExtra     = iGraphStatusWindowExtra ;
   wc.hIcon          = NULL ;
   wc.hCursor        = LoadCursor (NULL, IDC_ARROW) ;
   wc.hbrBackground  = hbLightGray ;
   wc.lpszMenuName   = NULL ;
   wc.lpszClassName  = szGraphStatusClass ;

   return (RegisterClass (&wc)) ;
   }


VALUEBAR.H

HWND CreateGraphStatusWindow (HWND hWndGraph) ;

BOOL GraphStatusInitializeApplication (void) ;

LRESULT APIENTRY GraphStatusWndProc (HWND hWnd,
                                     WORD wMsg,
                                     WPARAM wParam,
                                     LONG lParam) ;

void StatusDrawAvg (HDC hDC, PLINESTRUCT pLine, BOOL bForceRedraw) ;

void StatusDrawMax (HDC hDC, PLINESTRUCT pLine, BOOL bForceRedraw) ;

void StatusDrawMin (HDC hDC, PLINESTRUCT pLine, BOOL bForceRedraw) ;

void StatusDrawLast (HDC hDC, PLINESTRUCT pLine, BOOL bForceRedraw) ;

void StatusDrawTime (HDC hDC, BOOL bForceRedraw) ;

int ValuebarHeight (HWND hWnd) ;

void StatusTimer (HWND hWnd, BOOL bForecRedraw) ;


WINHELPR.H

/*
==============================================================================

  Application:

            Microsoft Windows NT (TM) Performance Monitor

  File:
            wincrack.h - Windows helper macros.

            This file contains macros for more easily dealing with windows
            messages and objects. Think of it as an extension to windows.h.

   Written by:

            Mike Moskowitz 8 Apr 92.

  Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
==============================================================================
*/

//==========================================================================//
//                                   Macros                                 //
//==========================================================================//

#define SetFont(hWnd, hFont)                          \
   (SendMessage ((hWnd), WM_SETFONT, (WPARAM) hFont, 0))

//======================================//
// Object-differentiation routines      //
//======================================//

// Windows APIs deal with all GDI objects the same. There's a SelectObject,
// no SelectBitmap, SelectFont, etc. We use these instead to make the code
// easier to read. Also, you can redefine one of these to check the
// validity of a particular GDI object type.

#define SelectBitmap(hDC, hBitmap)                    \
   (SelectObject (hDC, hBitmap))

#define SelectFont(hDC, hFont)                        \
   (SelectObject (hDC, hFont))

#define SelectBrush(hDC, hBrush)                      \
   (SelectObject (hDC, hBrush))

#define DeleteBrush(hBrush)                           \
   (DeleteObject (hBrush))

#define SelectPen(hDC, hPen)                          \
   (SelectObject (hDC, hPen))

#define DeletePen(hPen)                               \
   (DeleteObject (hPen))

//======================================//
//                                      //
//======================================//

#define CBData(hWndCB, iIndex)                        \
   (SendMessage (hWndCB, CB_GETITEMDATA, iIndex, 0L))

#define CBSetData(hWndCB, iIndex, lData)              \
   (SendMessage (hWndCB, CB_SETITEMDATA, iIndex, (LONG) lData))

#define CBAdd(hWndCB, lpszText)                       \
   ((int)(DWORD)SendMessage((hWndCB), CB_ADDSTRING,   \
    0, (LPARAM)(LPCSTR)(lpszText)))

#define CBFind(hWndCB, lpszText)                      \
   (SendMessage (hWndCB, CB_FINDSTRING, 0xFFFFFFFF, (LPARAM) lpszText))

#define CBInsert(hWndCB, iIndex, lpszText)            \
   (SendMessage (hWndCB, CB_INSERTSTRING, (WPARAM) iIndex, (LPARAM) lpszText))

#define CBReset(hWndCB)                               \
   ((int)(DWORD)SendMessage((hWndCB), CB_RESETCONTENT,\
    0, (LPARAM)0))

#define CBSelection(hWndCB)                           \
   (SendMessage (hWndCB, CB_GETCURSEL, 0, 0L))

#define CBSetSelection(hWndCB, iIndex)                \
   (SendMessage (hWndCB, CB_SETCURSEL, iIndex, 0L))

#define CBString(hWndCB, iIndex, lpszText)            \
   (SendMessage (hWndCB, CB_GETLBTEXT, iIndex, (LPARAM) lpszText))

#define CBStringLen(hWndCB, iIndex)                   \
   (SendMessage (hWndCB, CB_GETLBTEXTLEN, iIndex, 0L))

//======================================//
// Listbox helpers                      //
//======================================//

#define LBAdd(hWndLB, lpszText)                       \
   (SendMessage (hWndLB, LB_ADDSTRING, 0, (LPARAM) lpszText))

#define LBData(hWndLB, iIndex)                        \
   (SendMessage (hWndLB, LB_GETITEMDATA, iIndex, 0L))

#define LBDelete(hWndLB, iIndex)                      \
   (SendMessage (hWndLB, LB_DELETESTRING, iIndex, 0L))

#define LBFind(hWndLB, lpszText)                      \
   (SendMessage (hWndLB, LB_FINDSTRING, (WPARAM) -1, (LPARAM) lpszText))

#define LBFocus(hWndLB)                               \
   (SendMessage (hWndLB, LB_GETCARETINDEX, 0, 0))

#define LBInsert(hWndLB, iIndex, lpszText)            \
   (SendMessage (hWndLB, LB_INSERTSTRING, (WPARAM) iIndex, (LPARAM) lpszText))

#define LBNumItems(hWndLB)                            \
   ((int) SendMessage (hWndLB, LB_GETCOUNT, 0, 0))

#define LBReset(hWndLB)                               \
   ((int)(DWORD)SendMessage((hWndLB), LB_RESETCONTENT,\
    0, (LPARAM)0))

#define LBSelected(hwndLB, index)                     \
   ((int)(DWORD)SendMessage((hwndLB), LB_GETSEL,      \
    (WPARAM)(int)(index), 0L))

#define LBSelection(hWndLB)                           \
   (SendMessage (hWndLB, LB_GETCURSEL, 0, 0L))

#define LBSetData(hWndLB, iIndex, lData)              \
   (SendMessage (hWndLB, LB_SETITEMDATA, iIndex, (LONG) lData))

#define LBSetSelection(hWndLB, iIndex)                \
   (SendMessage (hWndLB, LB_SETCURSEL, iIndex, 0L))

#define LBString(hwndLB, iIndex, lpszText)            \
   ((int)(DWORD)SendMessage((hwndLB), LB_GETTEXT,     \
    (WPARAM)(int)(iIndex), (LPARAM)(LPCSTR)(lpszText)))

#define MLBSetSelection(hWndMLB, iIndex, bSet)        \
   (SendMessage (hWndMLB, LB_SETSEL, (WPARAM) bSet, (LPARAM) iIndex))

#define LBSetVisible(hWndLB, iIndex)                  \
   (SendMessage (hWndLB, LB_SETCARETINDEX, (WPARAM) iIndex, 0L))

#define LBSetRedraw(hWndLB, bDrawOnOff)               \
   (SendMessage (hWndLB, WM_SETREDRAW, (WPARAM) bDrawOnOff, 0L))

#define LBSetHorzExtent(hWndLB, wExtent)              \
   (SendMessage (hWndLB, LB_SETHORIZONTALEXTENT, (WPARAM)wExtent, 0L))

//======================================//
// Edit helpers                         //
//======================================//

#define EditModified(hWndEdit)                        \
   (SendMessage ((hWndEdit), EM_GETMODIFY, (WPARAM) 0, (LPARAM) 0))

#define EditSetModified(hWndEdit, bModified)                     \
   (SendMessage ((hWndEdit), EM_SETMODIFY, (WPARAM) bModified, 0))

#define EditSetLimit(hWndEdit, iLimit)                \
   (SendMessage ((hWndEdit), EM_LIMITTEXT, (WPARAM) iLimit, 0))
#define EditSetTextPos(hWnd, idControl, iStartPos, iEndPos)    \
   (SendMessage (GetDlgItem(hWnd, idControl), EM_SETSEL, (WPARAM) iStartPos, (LPARAM) iEndPos))

#define EditSetTextEndPos(hWnd, idControl)    \
   EditSetTextPos(hWnd, idControl, 0, 32767)

//======================================//
// Cursor helpers                       //
//======================================//

#define SetHourglassCursor() \
    (SetCursor(LoadCursor(NULL, IDC_WAIT)))

#define SetArrowCursor() \
    (SetCursor(LoadCursor(NULL, IDC_ARROW)))


README.TXT

Process Viewer

SUMMARY
=======

The PView tool reads the information (counters) from the registry that
relate to the threads and processes running on your machine. It also allows
you to change the priority of the processes. There is about a third of the
code necessary to do the same thing on a thread-by-thread basis. If you wish
to learn more, you may want to try to complete the code.

MORE INFORMATION
================

Two list boxes present themselves on startup. One list box is the list of
processes. When a particular process is selected, the other list box will
contain the threads that are associated with the selected process. Clicking
on the Memory Details button brings up another dialog box that contains more
detail on how the memory associated with that process is being used. Another
list box allows you to see how the memory usage breaks down at the DLL
level.

NOTE: There is also code in the program to allow you to look at the threads
on other machines. The implementation and activation of this feature is left
as an exercise for the user.

The way the program works may not be obvious at first. It is important to
note that when the programmer named a function PaintAddressSpace, it wasn't
named this way because it fills the monitored process/thread's memory.
PaintAddressSpace fetches all of the information associated with the
specified process/thread.


INSTDATA.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.
\******************************************************************************/

/******************************************************************************

                            I N S T A N C E   D A T A

    Name:       instdata.c

    Description:
        This module contains functions that access instances of an object
        type in performance data.

    Functions:
        FirstInstance
        NextInstance
        FindInstanceN
        FindInstanceParent
        InstanceName

******************************************************************************/

#include <windows.h>
#include <winperf.h>
#include "perfdata.h"

//*********************************************************************
//
//  FirstInstance
//
//      Returns pointer to the first instance of pObject type.
//      If pObject is NULL then NULL is returned.
//
PPERF_INSTANCE   FirstInstance (PPERF_OBJECT pObject)
{
    if (pObject)
        return (PPERF_INSTANCE)((PCHAR) pObject + pObject->DefinitionLength);
    else
        return NULL;
}

//*********************************************************************
//
//  NextInstance
//
//      Returns pointer to the next instance following pInst.
//
//      If pInst is the last instance, bogus data maybe returned.
//      The caller should do the checking.
//
//      If pInst is NULL, then NULL is returned.
//
PPERF_INSTANCE   NextInstance (PPERF_INSTANCE pInst)
{
PERF_COUNTER_BLOCK *pCounterBlock;

    if (pInst)
        {
        pCounterBlock = (PERF_COUNTER_BLOCK *)((PCHAR) pInst + pInst->ByteLength);
        return (PPERF_INSTANCE)((PCHAR) pCounterBlock + pCounterBlock->ByteLength);
        }
    else
        return NULL;
}

//*********************************************************************
//
//  FindInstanceN
//
//      Returns the Nth instance of pObject type.  If not found, NULL is
//      returned.  0 <= N <= NumInstances.
//

PPERF_INSTANCE FindInstanceN (PPERF_OBJECT pObject, DWORD N)
{
PPERF_INSTANCE pInst;
DWORD          i = 0;

    if (!pObject)
        return NULL;
    else if (N >= (DWORD)(pObject->NumInstances))
        return NULL;
    else
        {
        pInst = FirstInstance (pObject);

        while (i != N)
            {
            pInst = NextInstance (pInst);
            i++;
            }

        return pInst;
        }
}

//*********************************************************************
//
//  FindInstanceParent
//
//      Returns the pointer to an instance that is the parent of pInst.
//
//      If pInst is NULL or the parent object is not found then NULL is
//      returned.
//
PPERF_INSTANCE FindInstanceParent (PPERF_INSTANCE pInst, PPERF_DATA pData)
{
PPERF_OBJECT    pObject;

    if (!pInst)
        return NULL;
    else if (!(pObject = FindObject (pData, pInst->ParentObjectTitleIndex)))
        return NULL;
    else
        return FindInstanceN (pObject, pInst->ParentObjectInstance);
}

//*********************************************************************
//
//  InstanceName
//
//      Returns the name of the pInst.
//
//      If pInst is NULL then NULL is returned.
//
LPTSTR  InstanceName (PPERF_INSTANCE pInst)
{
    if (pInst)
        return (LPTSTR) ((PCHAR) pInst + pInst->NameOffset);
    else
        return NULL;
}


OBJDATA.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.
\******************************************************************************/

/******************************************************************************

                                O B J E C T   D A T A

    Name:       objdata.c

    Description:
        This module contains functions that access objects in performance
        data.

    Functions:
        FirstObject
        NextObject
        FindObject
        FindObjectN

******************************************************************************/

#include <windows.h>
#include <winperf.h>
#include "perfdata.h"

//*********************************************************************
//
//  FirstObject
//
//      Returns pointer to the first object in pData.
//      If pData is NULL then NULL is returned.
//
PPERF_OBJECT FirstObject (PPERF_DATA pData)
{
    if (pData)
        return ((PPERF_OBJECT) ((PBYTE) pData + pData->HeaderLength));
    else
        return NULL;
}

//*********************************************************************
//
//  NextObject
//
//      Returns pointer to the next object following pObject.
//
//      If pObject is the last object, bogus data maybe returned.
//      The caller should do the checking.
//
//      If pObject is NULL, then NULL is returned.
//
PPERF_OBJECT NextObject (PPERF_OBJECT pObject)
{
    if (pObject)
        return ((PPERF_OBJECT) ((PBYTE) pObject + pObject->TotalByteLength));
    else
        return NULL;
}

//*********************************************************************
//
//  FindObject
//
//      Returns pointer to object with TitleIndex.  If not found, NULL
//      is returned.
//
PPERF_OBJECT FindObject (PPERF_DATA pData, DWORD TitleIndex)
{
PPERF_OBJECT pObject;
DWORD        i = 0;

    if (pObject = FirstObject (pData))
        while (i < pData->NumObjectTypes)
            {
            if (pObject->ObjectNameTitleIndex == TitleIndex)
                return pObject;

            pObject = NextObject (pObject);
            i++;
            }

    return NULL;
}

//*********************************************************************
//
//  FindObjectN
//
//      Find the Nth object in pData.  If not found, NULL is returned.
//      0 <= N < NumObjectTypes.
//
PPERF_OBJECT FindObjectN (PPERF_DATA pData, DWORD N)
{
PPERF_OBJECT pObject;
DWORD        i = 0;

    if (!pData)
        return NULL;
    else if (N >= pData->NumObjectTypes)
        return NULL;
    else
        {
        pObject = FirstObject (pData);

        while (i != N)
            {
            pObject = NextObject (pObject);
            i++;
            }

        return pObject;
        }
}


PERFDATA.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.
\******************************************************************************/

/******************************************************************************

                        P E R F O R M A N C E   D A T A

    Name:       perfdata.c

    Description:
        This module together with objdata.c, instdata.c, and cntrdata.c
        access the performance data.

******************************************************************************/

#include <windows.h>
#include <winperf.h>
#include "perfdata.h"
#include <stdlib.h>

LPTSTR      *gPerfTitleSz;
LPTSTR      TitleData;

//*********************************************************************
//
//  GetPerfData
//
//      Get a new set of performance data.
//
//      *ppData should be NULL initially.
//      This function will allocate a buffer big enough to hold the
//      data requested by szObjectIndex.
//
//      *pDataSize specifies the initial buffer size.  If the size is
//      too small, the function will increase it until it is big enough
//      then return the size through *pDataSize.  Caller should
//      deallocate *ppData if it is no longer being used.
//
//      Returns ERROR_SUCCESS if no error occurs.
//
//      Note: the trial and error loop is quite different from the normal
//            registry operation.  Normally if the buffer is too small,
//            RegQueryValueEx returns the required size.  In this case,
//            the perflib, since the data is dynamic, a buffer big enough
//            for the moment may not be enough for the next. Therefor,
//            the required size is not returned.
//
//            One should start with a resonable size to avoid the overhead
//            of reallocation of memory.
//
DWORD   GetPerfData    (HKEY        hPerfKey,
                        LPTSTR      szObjectIndex,
                        PPERF_DATA  *ppData,
                        DWORD       *pDataSize)
{
DWORD   DataSize;
DWORD   dwR;
DWORD   Type;

    if (!*ppData)
        *ppData = (PPERF_DATA) LocalAlloc (LMEM_FIXED, *pDataSize);

    do  {
        DataSize = *pDataSize;
        dwR = RegQueryValueEx (hPerfKey,
                               szObjectIndex,
                               NULL,
                               &Type,
                               (BYTE *)*ppData,
                               &DataSize);

        if (dwR == ERROR_MORE_DATA)
            {
            LocalFree (*ppData);
            *pDataSize += 1024;
            *ppData = (PPERF_DATA) LocalAlloc (LMEM_FIXED, *pDataSize);
            }

        if (!*ppData)
            {
            LocalFree (*ppData);
            return ERROR_NOT_ENOUGH_MEMORY;
            }

        } while (dwR == ERROR_MORE_DATA);

    return dwR;
}

#ifdef UNICODE

#define atoi    atoiW

//*********************************************************************
//
//  atoiW
//
//      Unicode version of atoi.
//
INT atoiW (LPTSTR s)
{
INT i = 0;

 while (iswdigit (*s))
        {
  i = i*10 + (BYTE)*s - L'0';
        s++;
        }

    return i;
}

#endif

//*********************************************************************
//
//  GetPerfTitleSz
//
//      Retrieves the performance data title strings.
//
//   This call retrieves english version of the title strings.
//
//   For NT 3.1, the counter names are stored in the "Counters" value
//   in the ...\perflib\009 key.  For 3.5 and later, the 009 key is no
//      longer used.  The counter names should be retrieved from "Counter 009"
//      value of HKEY_PERFORMANCE_KEY.
//
//      Caller should provide two pointers, one for buffering the title
//      strings the other for indexing the title strings.  This function will
//      allocate memory for the TitleBuffer and TitleSz.  To get the title
//      string for a particular title index one would just index the TitleSz.
//      *TitleLastIdx returns the highest index can be used.  If TitleSz[N] is
//      NULL then there is no Title for index N.
//
//      Example:  TitleSz[20] points to titile string for title index 20.
//
//      When done with the TitleSz, caller should LocalFree(*TitleBuffer).
//
//      This function returns ERROR_SUCCESS if no error.
//
DWORD   GetPerfTitleSz (HKEY    hKeyMachine,
                        HKEY    hKeyPerf,
                        LPTSTR  *TitleBuffer,
                        LPTSTR  *TitleSz[],
                        DWORD   *TitleLastIdx)
{
HKEY  hKey1;
HKEY    hKey2;
DWORD   Type;
DWORD   DataSize;
DWORD   dwR;
DWORD   Len;
DWORD   Index;
DWORD   dwTemp;
BOOL    bNT10;
LPTSTR  szCounterValueName;
LPTSTR  szTitle;

    // Initialize
    //
    hKey1        = NULL;
    hKey2        = NULL;
    *TitleBuffer = NULL;
    *TitleSz     = NULL;

    // Open the perflib key to find out the last counter's index and system version.
    //
    dwR = RegOpenKeyEx (hKeyMachine,
                        TEXT("software\\microsoft\\windows nt\\currentversion\\perflib"),
                        0,
                        KEY_READ,
                        &hKey1);
    if (dwR != ERROR_SUCCESS)
        goto done;

    // Get the last counter's index so we know how much memory to allocate for TitleSz
    //
    DataSize = sizeof (DWORD);
    dwR = RegQueryValueEx (hKey1, TEXT("Last Counter"), 0, &Type, (LPBYTE)TitleLastIdx, &DataSize);
    if (dwR != ERROR_SUCCESS)
        goto done;

    // Find system version, for system earlier than 1.0a, there's no version value.
    //
    dwR = RegQueryValueEx (hKey1, TEXT("Version"), 0, &Type, (LPBYTE)&dwTemp, &DataSize);

    if (dwR != ERROR_SUCCESS)
        // unable to read the value, assume NT 1.0
        bNT10 = TRUE;
    else
        // found the value, so, NT 1.0a or later
        bNT10 = FALSE;

    // Now, get ready for the counter names and indexes.
    //
    if (bNT10)
        {
        // NT 1.0, so make hKey2 point to ...\perflib\009 and get
        //  the counters from value "Counters"
        //
        szCounterValueName = TEXT("Counters");
        dwR = RegOpenKeyEx (hKeyMachine,
                            TEXT("software\\microsoft\\windows nt\\currentversion\\perflib\\009"),
                            0,
                            KEY_READ,
                            &hKey2);
        if (dwR != ERROR_SUCCESS)
            goto done;
        }
    else
        {
        // NT 1.0a or later.  Get the counters in key HKEY_PERFORMANCE_KEY
        //  and from value "Counter 009"
        //
        szCounterValueName = TEXT("Counter 009");
        hKey2 = hKeyPerf;
        }

    // Find out the size of the data.
    //
    dwR = RegQueryValueEx (hKey2, szCounterValueName, 0, &Type, 0, &DataSize);
    if (dwR != ERROR_SUCCESS)
        goto done;

    // Allocate memory
    //
    *TitleBuffer = (LPTSTR)LocalAlloc (LMEM_FIXED, DataSize);
    if (!*TitleBuffer)
        {
        dwR = ERROR_NOT_ENOUGH_MEMORY;
        goto done;
        }

    *TitleSz = (LPTSTR *)LocalAlloc (LPTR, (*TitleLastIdx+1) * sizeof (LPTSTR));
    if (!*TitleSz)
        {
        dwR = ERROR_NOT_ENOUGH_MEMORY;
        goto done;
        }

    // Query the data
    //
    dwR = RegQueryValueEx (hKey2, szCounterValueName, 0, &Type, (BYTE *)*TitleBuffer, &DataSize);
    if (dwR != ERROR_SUCCESS)
        goto done;

    // Setup the TitleSz array of pointers to point to beginning of each title string.
    // TitleBuffer is type REG_MULTI_SZ.
    //
    szTitle = *TitleBuffer;

    while (Len = lstrlen (szTitle))
        {
        Index = atoi (szTitle);

        szTitle = szTitle + Len +1;

        if (Index <= *TitleLastIdx)
            (*TitleSz)[Index] = szTitle;

        szTitle = szTitle + lstrlen (szTitle) +1;
        }

done:

    // Done. Now cleanup!
    //
    if (dwR != ERROR_SUCCESS)
        {
        // There was an error, free the allocated memory
        //
        if (*TitleBuffer) LocalFree (*TitleBuffer);
        if (*TitleSz)     LocalFree (*TitleSz);
        }

    // Close the hKeys.
    //
    if (hKey1) RegCloseKey (hKey1);
    if (hKey2 && hKey2 != hKeyPerf) RegCloseKey (hKey2);

    return dwR;

}


PERFDATA.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.
\******************************************************************************/

/******************************************************************************

                    P E R F O R M A N C E   D A T A

    Name:       perfdata.h

    Description:
        This module contains function prototypes and defines used in
        objdata.c, instdata.c, and cntrdata.c.

******************************************************************************/

typedef PERF_DATA_BLOCK             PERF_DATA,      *PPERF_DATA;
typedef PERF_OBJECT_TYPE            PERF_OBJECT,    *PPERF_OBJECT;
typedef PERF_INSTANCE_DEFINITION    PERF_INSTANCE,  *PPERF_INSTANCE;
typedef PERF_COUNTER_DEFINITION     PERF_COUNTER,   *PPERF_COUNTER;

DWORD   GetPerfData (HKEY       hPerfKey,
                     LPTSTR     szObjectIndex,
                     PPERF_DATA *ppData,
                     DWORD      *pDataSize);

DWORD   GetPerfTitleSz
                    (HKEY       hKeyMachine,
                     HKEY       hKeyPerf,
                     LPTSTR     *TitleBuffer,
                     LPTSTR     *TitleSz[],
                     DWORD      *TitleLastIdx);

PPERF_OBJECT    FirstObject (PPERF_DATA pData);
PPERF_OBJECT    NextObject (PPERF_OBJECT pObject);
PPERF_OBJECT    FindObject (PPERF_DATA pData, DWORD TitleIndex);
PPERF_OBJECT    FindObjectN (PPERF_DATA pData, DWORD N);

PPERF_INSTANCE  FirstInstance (PPERF_OBJECT pObject);
PPERF_INSTANCE  NextInstance (PPERF_INSTANCE pInst);
PPERF_INSTANCE  FindInstanceN (PPERF_OBJECT pObject, DWORD N);
PPERF_INSTANCE  FindInstanceParent (PPERF_INSTANCE pInst, PPERF_DATA pData);
LPTSTR          InstanceName (PPERF_INSTANCE pInst);

PPERF_COUNTER   FirstCounter (PPERF_OBJECT pObject);
PPERF_COUNTER   NextCounter (PPERF_COUNTER pCounter);
PPERF_COUNTER   FindCounter (PPERF_OBJECT pObject, DWORD TitleIndex);
PVOID           CounterData (PPERF_INSTANCE pInst, PPERF_COUNTER pCount);


PVIEWDAT.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.
\******************************************************************************/

/******************************************************************************

                            P V I E W   D A T A

    Name:       pviewdat.c

    Description:
        This module collects the data to be displayed in pview.

******************************************************************************/

#include    <windows.h>
#include    <winperf.h>
#include    "perfdata.h"
#include    "pviewdat.h"
#include    "pviewdlg.h"
#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <tchar.h>

#define NODATA  TEXT("--------")

void    FormatTimeFields
               (double      fTime,
                PTIME_FIELD pTimeFld);

DWORD   PutCounterDWKB
               (HWND            hWnd,
                DWORD           dwItemID,
                PPERF_INSTANCE  pInst,
                PPERF_OBJECT    pObj,
                DWORD           dwCounterIdx);

DWORD   PutCounterHEX
               (HWND            hWnd,
                DWORD           dwItemID,
                PPERF_INSTANCE  pInst,
                PPERF_OBJECT    pObj,
                DWORD           dwCounterIdx);

DWORD   PutCounterDW
               (HWND            hWnd,
                DWORD           dwItemID,
                PPERF_INSTANCE  pInst,
                PPERF_OBJECT    pObj,
                DWORD           dwCounterIdx);

void    PaintAddressSpace
               (HWND            hMemDlg,
                PPERF_INSTANCE  pInst,
                PPERF_OBJECT    pObj,
                DWORD           TotalID,
                DWORD           NoAccessID,
                DWORD           NoAccessIndex,
                DWORD           ReadOnlyID,
                DWORD           ReadOnlyIndex,
                DWORD           ReadWriteID,
                DWORD           ReadWriteIndex,
                DWORD           WriteCopyID,
                DWORD           WriteCopyIndex,
                DWORD           ExecuteID,
                DWORD           ExecuteIndex1,
                DWORD           ExecuteIndex2,
                DWORD           ExecuteIndex3,
                DWORD           ExecuteIndex4);

void    PaintMemDlgAddrData
               (HWND            hMemDlg,
                PPERF_INSTANCE  pInst,
                PPERF_OBJECT    pObj);

void    PaintMemDlgVMData
               (HWND            hMemDlg,
                PPERF_INSTANCE  pInst,
                PPERF_OBJECT    pObj);

void    PaintPviewDlgMemoryData
               (HWND            hPviewDlg,
                PPERF_INSTANCE  pInst,
                PPERF_OBJECT    pObj);

void    RefreshMemoryDlgImageList
               (HWND            hImageList,
                DWORD           ParentIndex,
                PPERF_OBJECT    pImageObj);

WORD    ProcessPriority
               (PPERF_OBJECT    pObject,
                PPERF_INSTANCE  pInstance);

void    SetProcessListText
               (PPERF_INSTANCE pInst,
                PPERF_COUNTER  pCPU,
                PPERF_COUNTER  pPRIV,
                PPERF_COUNTER  pProcID,
                double         fTime,
                LPTSTR         str);

void    SetThreadListText
               (PPERF_INSTANCE  pInst,
                PPERF_COUNTER   pCPU,
                PPERF_COUNTER   pPRIV,
                double          fTime,
                LPTSTR          str);

//*********************************************************************
//
//      FormatTimeFields
//
//  Formats a double value to time fields.
//
void FormatTimeFields   (double      fTime,
                         PTIME_FIELD pTimeFld)
{
INT     i;
double   f;

    f = fTime/3600;

    pTimeFld->Hours = i = (int)f;

    f = f - i;
    pTimeFld->Mins = i = (int)(f = f * 60);

    f = f - i;
    pTimeFld->Secs = i = (int)(f = f * 60);

    f = f - i;
    pTimeFld->mSecs = (int)(f * 1000);
}

//*********************************************************************
//
//      PutCounterDWKB
//
//  Display a DWORD counter's data in KB units.
//
DWORD   PutCounterDWKB (HWND            hWnd,
                        DWORD           dwItemID,
                        PPERF_INSTANCE  pInst,
                        PPERF_OBJECT    pObj,
                        DWORD           dwCounterIdx)
{
PPERF_COUNTER   pCounter;
DWORD           *pdwData;
TCHAR           szTemp[20];

    if (pCounter = FindCounter (pObj, dwCounterIdx))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        wsprintf (szTemp, TEXT("%ld KB"), *pdwData/1024);
        SetDlgItemText (hWnd, dwItemID, szTemp);

        return *pdwData;
        }
    else
        {
        SetDlgItemText (hWnd, dwItemID, NODATA);
        return 0;
        }

}

//*********************************************************************
//
//      PutCounterHEX
//
//  Display a DWORD counter's data in hex.
//
DWORD   PutCounterHEX  (HWND            hWnd,
                        DWORD           dwItemID,
                        PPERF_INSTANCE  pInst,
                        PPERF_OBJECT    pObj,
                        DWORD           dwCounterIdx)
{
PPERF_COUNTER   pCounter;
DWORD           *pdwData;
TCHAR           szTemp[20];

    if (pCounter = FindCounter (pObj, dwCounterIdx))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        wsprintf (szTemp, TEXT("0x%08x"), *pdwData);
        SetDlgItemText (hWnd, dwItemID, szTemp);

        return *pdwData;
        }
    else
        {
        SetDlgItemText (hWnd, dwItemID, NODATA);
        return 0;
        }

}

//*********************************************************************
//
//      PutCounterDWKB
//
//  Display a DWORD counter's data.
//
DWORD   PutCounterDW   (HWND            hWnd,
                        DWORD           dwItemID,
                        PPERF_INSTANCE  pInst,
                        PPERF_OBJECT    pObj,
                        DWORD           dwCounterIdx)
{
PPERF_COUNTER   pCounter;
DWORD           *pdwData;

    if (pCounter = FindCounter (pObj, dwCounterIdx))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        SetDlgItemInt (hWnd, dwItemID, *pdwData, FALSE);

        return *pdwData;
        }
    else
        {
        SetDlgItemText (hWnd, dwItemID, NODATA);
        return 0;
        }

}

//*********************************************************************
//
//      PaintAddressSpace
//
//
void    PaintAddressSpace  (HWND            hMemDlg,
                            PPERF_INSTANCE  pInst,
                            PPERF_OBJECT    pObj,
                            DWORD           TotalID,
                            DWORD           NoAccessID,
                            DWORD           NoAccessIndex,
                            DWORD           ReadOnlyID,
                            DWORD           ReadOnlyIndex,
                            DWORD           ReadWriteID,
                            DWORD           ReadWriteIndex,
                            DWORD           WriteCopyID,
                            DWORD           WriteCopyIndex,
                            DWORD           ExecuteID,
                            DWORD           ExecuteIndex1,
                            DWORD           ExecuteIndex2,
                            DWORD           ExecuteIndex3,
                            DWORD           ExecuteIndex4)
{
PPERF_COUNTER   pCounter;
DWORD           *pdwData;
TCHAR           szTemp[20];

DWORD           dwTotal = 0;
DWORD           dwExecute = 0;
BOOL            bCounter = FALSE;

    dwTotal += PutCounterDWKB (hMemDlg, NoAccessID,  pInst, pObj, NoAccessIndex);
    dwTotal += PutCounterDWKB (hMemDlg, ReadOnlyID,  pInst, pObj, ReadOnlyIndex);
    dwTotal += PutCounterDWKB (hMemDlg, ReadWriteID, pInst, pObj, ReadWriteIndex);
    dwTotal += PutCounterDWKB (hMemDlg, WriteCopyID, pInst, pObj, WriteCopyIndex);

    // execute is the sum of the following
    //
    if (pCounter = FindCounter (pObj, ExecuteIndex1))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwTotal += *pdwData;
        dwExecute += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, ExecuteIndex2))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwTotal += *pdwData;
        dwExecute += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, ExecuteIndex3))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwTotal += *pdwData;
        dwExecute += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, ExecuteIndex4))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwTotal += *pdwData;
        dwExecute += *pdwData;
        bCounter = TRUE;
        }

    if (bCounter)
        {
        wsprintf (szTemp, TEXT("%ld KB"), dwExecute/1024);
        SetDlgItemText (hMemDlg, ExecuteID, szTemp);
        }
    else
        SetDlgItemText (hMemDlg, ExecuteID, NODATA);

    wsprintf (szTemp, TEXT("%ld KB"), dwTotal/1024);
    SetDlgItemText (hMemDlg, TotalID, szTemp);

}

//*********************************************************************
//
//      PaintMemDlgAddrData
//
//  Paint the memory dialog address space data.
//
void    PaintMemDlgAddrData(HWND            hMemDlg,
                            PPERF_INSTANCE  pInst,
                            PPERF_OBJECT    pObj)
{
    PaintAddressSpace (hMemDlg, pInst, pObj,
                       MEMORY_TOTALPRIVATE_COMMIT,
                       MEMORY_PRIVATE_NOACCESS,  PX_PROCESS_PRIVATE_NOACCESS,
                       MEMORY_PRIVATE_READONLY,  PX_PROCESS_PRIVATE_READONLY,
                       MEMORY_PRIVATE_READWRITE, PX_PROCESS_PRIVATE_READWRITE,
                       MEMORY_PRIVATE_WRITECOPY, PX_PROCESS_PRIVATE_WRITECOPY,
                       MEMORY_PRIVATE_EXECUTE,   PX_PROCESS_PRIVATE_EXECUTABLE,
                                                 PX_PROCESS_PRIVATE_EXE_READONLY,
                                                 PX_PROCESS_PRIVATE_EXE_READWRITE,
                                                 PX_PROCESS_PRIVATE_EXE_WRITECOPY);

    PaintAddressSpace (hMemDlg, pInst, pObj,
                       MEMORY_TOTALMAPPED_COMMIT,
                       MEMORY_MAPPED_NOACCESS,  PX_PROCESS_MAPPED_NOACCESS,
                       MEMORY_MAPPED_READONLY,  PX_PROCESS_MAPPED_READONLY,
                       MEMORY_MAPPED_READWRITE, PX_PROCESS_MAPPED_READWRITE,
                       MEMORY_MAPPED_WRITECOPY, PX_PROCESS_MAPPED_WRITECOPY,
                       MEMORY_MAPPED_EXECUTE,   PX_PROCESS_MAPPED_EXECUTABLE,
                                                PX_PROCESS_MAPPED_EXE_READONLY,
                                                PX_PROCESS_MAPPED_EXE_READWRITE,
                                                PX_PROCESS_MAPPED_EXE_WRITECOPY);

    PaintAddressSpace (hMemDlg, pInst, pObj,
                       MEMORY_TOTALIMAGE_COMMIT,
                       MEMORY_IMAGE_NOACCESS,   PX_PROCESS_IMAGE_NOACCESS,
                       MEMORY_IMAGE_READONLY,   PX_PROCESS_IMAGE_READONLY,
                       MEMORY_IMAGE_READWRITE,  PX_PROCESS_IMAGE_READWRITE,
                       MEMORY_IMAGE_WRITECOPY,  PX_PROCESS_IMAGE_WRITECOPY,
                       MEMORY_IMAGE_EXECUTE,    PX_PROCESS_IMAGE_EXECUTABLE,
                                                PX_PROCESS_IMAGE_EXE_READONLY,
                                                PX_PROCESS_IMAGE_EXE_READWRITE,
                                                PX_PROCESS_IMAGE_EXE_WRITECOPY);
}

//*********************************************************************
//
//      PaintMemDlgVMData
//
//  Paint the memory dialog Virtual Memory data.
//
void    PaintMemDlgVMData  (HWND            hMemDlg,
                            PPERF_INSTANCE  pInst,
                            PPERF_OBJECT    pObj)
{

    PutCounterDWKB (hMemDlg, MEMORY_WS,           pInst, pObj, PX_PROCESS_WORKING_SET);
    PutCounterDWKB (hMemDlg, MEMORY_PEAK_WS,      pInst, pObj, PX_PROCESS_PEAK_WS);
    PutCounterDWKB (hMemDlg, MEMORY_PRIVATE_PAGE, pInst, pObj, PX_PROCESS_PRIVATE_PAGE);
    PutCounterDWKB (hMemDlg, MEMORY_VSIZE,        pInst, pObj, PX_PROCESS_VIRTUAL_SIZE);
    PutCounterDWKB (hMemDlg, MEMORY_PEAK_VSIZE,   pInst, pObj, PX_PROCESS_PEAK_VS);
    PutCounterDWKB (hMemDlg, MEMORY_PFCOUNT,      pInst, pObj, PX_PROCESS_FAULT_COUNT);

}

//*********************************************************************
//
//      PaintPviewDlgMemoryData
//
//  Paint the memory data for pview dialog.
//
void    PaintPviewDlgMemoryData    (HWND            hPviewDlg,
                                    PPERF_INSTANCE  pInst,
                                    PPERF_OBJECT    pObj)
{
PPERF_COUNTER   pCounter;
TCHAR           str[20];
DWORD           *pdwData;
DWORD           dwData = 0;
BOOL            bCounter = FALSE;

    if (pCounter = FindCounter (pObj, PX_PROCESS_PRIVATE_NOACCESS))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwData += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, PX_PROCESS_PRIVATE_READONLY))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwData += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, PX_PROCESS_PRIVATE_READWRITE))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwData += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, PX_PROCESS_PRIVATE_WRITECOPY))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwData += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, PX_PROCESS_PRIVATE_EXECUTABLE))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwData += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, PX_PROCESS_PRIVATE_EXE_READONLY))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwData += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, PX_PROCESS_PRIVATE_EXE_READWRITE))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwData += *pdwData;
        bCounter = TRUE;
        }

    if (pCounter = FindCounter (pObj, PX_PROCESS_PRIVATE_EXE_WRITECOPY))
        {
        pdwData = (DWORD *) CounterData (pInst, pCounter);
        dwData += *pdwData;
        bCounter = TRUE;
        }

    if (bCounter)
        {
        wsprintf (str, TEXT("%ld KB"), dwData/1024);
        SetDlgItemText (hPviewDlg, PVIEW_TOTALPRIVATE_COMMIT, str);
        }
    else
        SetDlgItemText (hPviewDlg, PVIEW_TOTALPRIVATE_COMMIT, NODATA);

}

//*********************************************************************
//
//      RefreshMemoryDlg
//
//  Refresh the memory detail dialog.
//
BOOL    RefreshMemoryDlg   (HWND            hMemDlg,
                            PPERF_INSTANCE  pProcessInstance,
                            PPERF_OBJECT    pProcessObject,
                            PPERF_OBJECT    pAddressObject,
                            PPERF_OBJECT    pImageObject)
{
DWORD           *pProcessID1;
DWORD           *pProcessID2;
PPERF_COUNTER   pCounter1;
PPERF_COUNTER   pCounter2;
PPERF_INSTANCE  pAddressInstance;
HWND            hImageList;
TCHAR           szTemp[40];
BOOL            bStat = FALSE;
INT             InstIndex = 0;

    if ((pCounter1 = FindCounter (pProcessObject, PX_PROCESS_ID)) &&
        (pCounter2 = FindCounter (pAddressObject, PX_PROCESS_ID)))
        {
        pProcessID1 = (DWORD *) CounterData (pProcessInstance, pCounter1);

        wsprintf (szTemp, TEXT("%s (%#x)"), InstanceName (pProcessInstance), *pProcessID1);
        SetDlgItemText (hMemDlg, MEMORY_PROCESS_ID, szTemp);

        pAddressInstance = FirstInstance (pAddressObject);

        while (pAddressInstance && InstIndex < pAddressObject->NumInstances)
            {
            pProcessID2 = (DWORD *) CounterData (pAddressInstance, pCounter2);

            if (*pProcessID1 == *pProcessID2)
                {
                PaintMemDlgAddrData (hMemDlg, pAddressInstance, pAddressObject);
                PaintMemDlgVMData (hMemDlg, pProcessInstance, pProcessObject);

                hImageList = GetDlgItem (hMemDlg, MEMORY_IMAGE);
                RefreshMemoryDlgImageList (hImageList, InstIndex, pImageObject);

                bStat = TRUE;
                break;
                }

            pAddressInstance = NextInstance (pAddressInstance);
            InstIndex++;
            }
        }

    return bStat;

}

//*********************************************************************
//
//      RefreshMemoryDlgImageList
//
//  Refresh the image list for memory dialog.
//
void    RefreshMemoryDlgImageList  (HWND            hImageList,
                                    DWORD           ParentIndex,
                                    PPERF_OBJECT    pImageObj)
{
PPERF_INSTANCE  pImageInst;
INT             ListIndex;
INT             InstIndex = 0;

    ListIndex = SendMessage (hImageList, CB_ADDSTRING, 0, (DWORD)TEXT(" Total Commit"));
    SendMessage (hImageList, CB_SETITEMDATA, ListIndex, 0xFFFFFFFF);

    if (pImageObj)
        {
        pImageInst = FirstInstance (pImageObj);

        while (pImageInst && InstIndex < pImageObj->NumInstances)
            {
            if (ParentIndex == pImageInst->ParentObjectInstance)
                {
                ListIndex = SendMessage (hImageList,
                                         CB_ADDSTRING,
                                         0,
                                         (DWORD)InstanceName(pImageInst));
                SendMessage (hImageList, CB_SETITEMDATA, ListIndex, InstIndex);
                }

            pImageInst = NextInstance (pImageInst);
            InstIndex++;
            }
        }
}

//*********************************************************************
//
//      RefreshMemoryDlgImage
//
//
void RefreshMemoryDlgImage (HWND            hMemDlg,
                            DWORD           dwIndex,
                            PPERF_OBJECT    pImageObject)
{
PPERF_INSTANCE  pInst;

    if (pInst = FindInstanceN (pImageObject, dwIndex))
        PaintAddressSpace (hMemDlg, pInst, pImageObject,
                           MEMORY_TOTALIMAGE_COMMIT,
                           MEMORY_IMAGE_NOACCESS,   PX_IMAGE_NOACCESS,
                           MEMORY_IMAGE_READONLY,   PX_IMAGE_READONLY,
                           MEMORY_IMAGE_READWRITE,  PX_IMAGE_READWRITE,
                           MEMORY_IMAGE_WRITECOPY,  PX_IMAGE_WRITECOPY,
                           MEMORY_IMAGE_EXECUTE,    PX_IMAGE_EXECUTABLE,
                                                    PX_IMAGE_EXE_READONLY,
                                                    PX_IMAGE_EXE_READWRITE,
                                                    PX_IMAGE_EXE_WRITECOPY);
}

//*********************************************************************
//
//      RefreshPviewDlgMemoryData
//
//  Update the memory data for pview dialog.  This should be done
//  after the ghCostlyData is collected and is not refreshing.
//
void RefreshPviewDlgMemoryData (HWND            hPviewDlg,
                                PPERF_INSTANCE  pProcessInstance,
                                PPERF_OBJECT    pProcessObject,
                                PPERF_OBJECT    pAddressObject)
{
DWORD           *pProcessID1;
DWORD           *pProcessID2;
PPERF_COUNTER   pCounter1;
PPERF_COUNTER   pCounter2;
PPERF_INSTANCE  pAddressInstance;
INT             i = 0;

    if ((pCounter1 = FindCounter (pProcessObject, PX_PROCESS_ID)) &&
        (pCounter2 = FindCounter (pAddressObject, PX_PROCESS_ID)))
        {
        pProcessID1 = (DWORD *) CounterData (pProcessInstance, pCounter1);

        pAddressInstance = FirstInstance (pAddressObject);

        while (pAddressInstance && i < pAddressObject->NumInstances)
            {
            pProcessID2 = (DWORD *) CounterData (pAddressInstance, pCounter2);

            if (*pProcessID1 == *pProcessID2)
                {
                PaintPviewDlgMemoryData (hPviewDlg, pAddressInstance, pAddressObject);
                break;
                }

            pAddressInstance = NextInstance (pAddressInstance);
            i++;
            }
        }
}

//*********************************************************************
//
//      RefreshPviewDlgThreadPC
//
//  Update the thread PC value.  This should be done after the ghCostlyData
//  is collected and is no refreshing.
//
void RefreshPviewDlgThreadPC   (HWND            hPviewDlg,
                                LPTSTR          szProcessName,
                                LPTSTR          szThreadName,
                                PPERF_OBJECT    pThreadDetailsObject,
                                PPERF_DATA      pCostlyData)
{
PPERF_COUNTER   pCounter;
PPERF_INSTANCE  pInstance;
PPERF_INSTANCE  pParent;
LPTSTR          szInstanceName;
LPTSTR          szParentName;
TCHAR           str[20];
DWORD           *pdwData;
INT             i = 0;

    if (pCounter = FindCounter (pThreadDetailsObject, PX_THREAD_PC))
        {
        pInstance = FirstInstance (pThreadDetailsObject);

        while (pInstance && i < pThreadDetailsObject->NumInstances)
            {
            if (!(szInstanceName = InstanceName (pInstance)))
                // can't find name
                ;
            else if (lstrcmp (szThreadName, szInstanceName))
                // the thread name is different
                ;
            else if (!(pParent = FindInstanceParent (pInstance, pCostlyData)))
                // can't find parent
                ;
            else if (!(szParentName = InstanceName (pParent)))
                // can't find parent's name
                ;
            else if (!lstrcmp (szProcessName, szParentName))
                {
                // Parent's name matches, this is the right one.
                //

                pdwData = CounterData (pInstance, pCounter);
                wsprintf (str, TEXT("0x%08x"), *pdwData);
                SetDlgItemText (hPviewDlg, PVIEW_THREAD_PC, str);

                return;
                }

            pInstance = NextInstance (pInstance);
            i++;
            }
        }

    // We are here only because we can't find the data to display.
    //

    SetDlgItemText (hPviewDlg, PVIEW_THREAD_PC, NODATA);

}

//*********************************************************************
//
//      ProcessPriority
//
//  Returns the process priority dialog item id.
//
WORD    ProcessPriority    (PPERF_OBJECT    pObject,
                            PPERF_INSTANCE  pInstance)
{
PPERF_COUNTER   pCounter;
DWORD           *pdwData;

    if (pCounter = FindCounter (pObject, PX_PROCESS_PRIO))
        {
        pdwData = (DWORD *) CounterData (pInstance, pCounter);

        if (*pdwData < 7)
            return PVIEW_PRIORITY_IDL;
        else if (*pdwData < 10)
            return PVIEW_PRIORITY_NORMAL;
        else
            return PVIEW_PRIORITY_HIGH;
        }
    else
        return PVIEW_PRIORITY_NORMAL;

}

//*********************************************************************
//
//      RefreshPerfData
//
//  Get a new set of performance data.  pData should be NULL initially.
//
PPERF_DATA RefreshPerfData (HKEY        hPerfKey,
                            LPTSTR      szObjectIndex,
                            PPERF_DATA  pData,
                            DWORD       *pDataSize)
{
    if (GetPerfData (hPerfKey, szObjectIndex, &pData, pDataSize) == ERROR_SUCCESS)
        return pData;
    else
        return NULL;
}

//*********************************************************************
//
//      SetProcessListText
//
//  Format the process list text.
//
void SetProcessListText (PPERF_INSTANCE pInst,
                         PPERF_COUNTER  pCPU,
                         PPERF_COUNTER  pPRIV,
                         PPERF_COUNTER  pProcID,
                         double         fTime,
                         LPTSTR         str)
{
DWORD           *pdwProcID;
LARGE_INTEGER   *liCPU;
LARGE_INTEGER   *liPRIV;
double          fCPU = 0;
double          fPRIV = 0;
INT             PcntPRIV = 0;
INT             PcntUSER = 0;
TIME_FIELD      TimeFld;
TCHAR           szTemp[100];

    if (pCPU)
        {
        liCPU = (LARGE_INTEGER *) CounterData (pInst, pCPU);
        fCPU  = Li2Double (*liCPU);
        }

    if (pPRIV)
        {
        liPRIV = (LARGE_INTEGER *) CounterData (pInst, pPRIV);
        fPRIV  = Li2Double (*liPRIV);
        }

    if (fCPU > 0)
        {
        PcntPRIV = (INT)(fPRIV / fCPU * 100 + 0.5);
        PcntUSER = 100 - PcntPRIV;
        }

    if (pProcID)
        {
        pdwProcID = (DWORD *) CounterData (pInst, pProcID);
        wsprintf (szTemp, TEXT("%ls (%#x)"), InstanceName(pInst), *pdwProcID);
        }
    else
        wsprintf (szTemp, TEXT("%ls"), InstanceName(pInst));

    FormatTimeFields (fCPU/1.0e7, &TimeFld);

    wsprintf (str,
              TEXT("%s\t%3ld:%02ld:%02ld.%03ld\t%3ld%%\t%3ld%%"),
              szTemp,
              TimeFld.Hours,
              TimeFld.Mins,
              TimeFld.Secs,
              TimeFld.mSecs,
              PcntPRIV,
              PcntUSER);
}

//*********************************************************************
//
//      RefreshProcessList
//
//  Find all process and update the process list.
//
void RefreshProcessList (HWND           hProcessList,
                         PPERF_OBJECT   pObject)
{
PPERF_INSTANCE  pInstance;
TCHAR           szListText[256];
INT             ListIndex;

PPERF_COUNTER   pCounterCPU;
PPERF_COUNTER   pCounterPRIV;
PPERF_COUNTER   pCounterProcID;
double          fObjectFreq;
double          fObjectTime;
double          fTime;

INT             InstanceIndex = 0;
INT             err;

    if (pObject)
        {
        if ((pCounterCPU    = FindCounter (pObject, PX_PROCESS_CPU))  &&
            (pCounterPRIV   = FindCounter (pObject, PX_PROCESS_PRIV)) &&
            (pCounterProcID = FindCounter (pObject, PX_PROCESS_ID)))
            {

            fObjectFreq = Li2Double (pObject->PerfFreq);
            fObjectTime = Li2Double (pObject->PerfTime);
            fTime = fObjectTime / fObjectFreq;

            pInstance = FirstInstance (pObject);

            while (pInstance && InstanceIndex < pObject->NumInstances)
                {
                SetProcessListText (pInstance,
                                    pCounterCPU,
                                    pCounterPRIV,
                                    pCounterProcID,
                                    fTime,
                                    szListText);

                ListIndex = SendMessage (hProcessList, LB_ADDSTRING, 0, (DWORD)szListText);
                err = SendMessage (hProcessList, LB_SETITEMDATA, ListIndex, InstanceIndex);

                pInstance = NextInstance (pInstance);
                InstanceIndex++;
                }
            }
        }
}

//*********************************************************************
//
//      RefreshProcessData
//
//  Find data for a given process and update.
//
void RefreshProcessData    (HWND            hWnd,
                            PPERF_OBJECT    pObject,
                            DWORD           ProcessIndex)
{
PPERF_INSTANCE  pInstance;

    if (pInstance = FindInstanceN (pObject, ProcessIndex))

{
        PutCounterDWKB (hWnd, PVIEW_WS, pInstance, pObject, PX_PROCESS_WORKING_SET);

        SetDlgItemText (hWnd, PVIEW_TOTALPRIVATE_COMMIT, NODATA);

        // set priority
        //
        CheckRadioButton (hWnd,
                          PVIEW_PRIORITY_HIGH,
                          PVIEW_PRIORITY_IDL,
                          ProcessPriority (pObject, pInstance));
        }
}

//*********************************************************************
//
//      SetThreadListText
//
//  Format the thread list text.
//
void SetThreadListText (PPERF_INSTANCE  pInst,
                        PPERF_COUNTER   pCPU,
                        PPERF_COUNTER   pPRIV,
                        double          fTime,
                        LPTSTR          str)
{
LARGE_INTEGER   *liCPU;
LARGE_INTEGER   *liPRIV;
double          fCPU = 0;
double          fPRIV = 0;
INT             PcntPRIV = 0;
INT             PcntUSER = 0;
TIME_FIELD      TimeFld;
TCHAR           szTemp[100];

    if (pCPU)
        {
        liCPU = (LARGE_INTEGER *) CounterData (pInst, pCPU);
        fCPU  = Li2Double (*liCPU);
        }

    if (pPRIV)
        {
        liPRIV = (LARGE_INTEGER *) CounterData (pInst, pPRIV);
        fPRIV  = Li2Double (*liPRIV);
        }

    if (fCPU > 0)
        {
        PcntPRIV = (INT)(fPRIV / fCPU * 100 + 0.5);
        PcntUSER = 100 - PcntPRIV;
        }

    if (pInst->UniqueID != PERF_NO_UNIQUE_ID)
        wsprintf (szTemp, TEXT("%ls (%#x)"), InstanceName(pInst), pInst->UniqueID);
    else
        wsprintf (szTemp, TEXT("%ls"), InstanceName(pInst));

    FormatTimeFields (fCPU/1.0e7, &TimeFld);

    wsprintf (str,
              TEXT("%s\t%3ld:%02ld:%02ld.%03ld\t%3ld%%\t%3ld %%"),
              szTemp,
              TimeFld.Hours,
              TimeFld.Mins,
              TimeFld.Secs,
              TimeFld.mSecs,
              PcntPRIV,
              PcntUSER);

}

//*********************************************************************
//
//      RefreshThreadList
//
//  Find all threads for a given process and update the thread list.
//
void RefreshThreadList (HWND            hThreadList,
                        PPERF_OBJECT    pObject,
                        DWORD           ParentIndex)
{
PPERF_INSTANCE  pInstance;
TCHAR           szListText[256];
INT             ListIndex;

PPERF_COUNTER   pCounterCPU;
PPERF_COUNTER   pCounterPRIV;
double          fObjectFreq;
double          fObjectTime;
double          fTime;

INT             InstanceIndex = 0;
INT             err;

    if (pObject)
        {
        if ((pCounterCPU  = FindCounter (pObject, PX_THREAD_CPU)) &&
            (pCounterPRIV = FindCounter (pObject, PX_THREAD_PRIV)))
            {

            fObjectFreq = Li2Double (pObject->PerfFreq);
            fObjectTime = Li2Double (pObject->PerfTime);
            fTime = fObjectTime / fObjectFreq;

            pInstance = FirstInstance (pObject);

            while (pInstance && InstanceIndex < pObject->NumInstances)
                {
                if (ParentIndex == pInstance->ParentObjectInstance)
                    {
                    SetThreadListText (pInstance,
                                       pCounterCPU,
                                       pCounterPRIV,
                                       fTime,
                                       szListText);

                    ListIndex = SendMessage (hThreadList,
                                             LB_INSERTSTRING,
                                             (WPARAM)-1,
                                             (DWORD)szListText);
                    err = SendMessage (hThreadList, LB_SETITEMDATA, ListIndex, InstanceIndex);
                    }

                pInstance = NextInstance (pInstance);
                InstanceIndex++;
                }
            }
        }

}

//*********************************************************************
//
//      RefreshThreadData
//
//  Find data for a given thread and update.
//
void RefreshThreadData (HWND              hWnd,
                        PPERF_OBJECT      pThreadObj,
                        DWORD             ThreadIndex,
                        PPERF_OBJECT      pProcessObj,
                        PPERF_INSTANCE    pProcessInst)
{
PPERF_INSTANCE  pInstance;
PPERF_COUNTER   pCounter;
DWORD           *pdwData;
DWORD           *pdwProcPrio;
BOOL            bPrioCounter = TRUE;

    if (pInstance = FindInstanceN (pThreadObj, ThreadIndex))
        {
        SetDlgItemText (hWnd, PVIEW_THREAD_PC, NODATA);

        PutCounterHEX (hWnd, PVIEW_THREAD_START,    pInstance, pThreadObj, PX_THREAD_START);
        PutCounterDW  (hWnd, PVIEW_THREAD_SWITCHES, pInstance, pThreadObj, PX_THREAD_SWITCHES);
        PutCounterDW  (hWnd, PVIEW_THREAD_DYNAMIC,  pInstance, pThreadObj, PX_THREAD_PRIO);
        }

    if (pInstance)
        {
        // get thread base priority
        //

        if (pCounter = FindCounter (pThreadObj, PX_THREAD_BASE_PRIO))
            pdwData = CounterData (pInstance, pCounter);
        else
            bPrioCounter = FALSE;

        // get process priority
        //

        if (pCounter = FindCounter (pProcessObj, PX_PROCESS_PRIO))
            pdwProcPrio = CounterData (pProcessInst, pCounter);
        else
            bPrioCounter = FALSE;
        }
    else
        bPrioCounter = FALSE;

    // set thread base priority
    //

    if (!bPrioCounter)
        CheckRadioButton (hWnd,
                          PVIEW_THREAD_HIGHEST,
                          PVIEW_THREAD_LOWEST,
                          PVIEW_THREAD_NORMAL);
    else
        {
        switch (*pdwData - *pdwProcPrio)
            {
            case 2:
                CheckRadioButton (hWnd,
                                  PVIEW_THREAD_HIGHEST,
                                  PVIEW_THREAD_LOWEST,
                                  PVIEW_THREAD_HIGHEST);
                break;

            case 1:
                CheckRadioButton (hWnd,
                                  PVIEW_THREAD_HIGHEST,
                                  PVIEW_THREAD_LOWEST,
                                  PVIEW_THREAD_ABOVE);
                break;

            case -1:
                CheckRadioButton (hWnd,
                                  PVIEW_THREAD_HIGHEST,
                                  PVIEW_THREAD_LOWEST,
                                  PVIEW_THREAD_BELOW);
                break;

            case -2:
                CheckRadioButton (hWnd,
                                  PVIEW_THREAD_HIGHEST,
                                  PVIEW_THREAD_LOWEST,
                                  PVIEW_THREAD_LOWEST);
                break;

            case 0:
            default:
                CheckRadioButton (hWnd,
                                  PVIEW_THREAD_HIGHEST,
                                  PVIEW_THREAD_LOWEST,
                                  PVIEW_THREAD_NORMAL);
                break;
            }
        }
}


PVIEWDAT.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.
\******************************************************************************/

/******************************************************************************

                            P V I E W   D A T A

    Name:       pviewdat.h

    Description:
        Prototypes of functions used in pviewdat.c

******************************************************************************/

//******************************************************************************
//
//  Titles and indexes.
//
//  See GetPerfTitleSz() in perfdata.c on how to retrieve these data.
//
//  This is not complete, for complete listing
//  see under hkey_local_system
//               \software
//                   \microsoft
//                       \windows nt
//                           \currentversion
//                               \perflib
//                                   \###
//

#define PN_PROCESS                          TEXT("Process")
#define PN_PROCESS_CPU                      TEXT("% Processor Time")
#define PN_PROCESS_PRIV                     TEXT("% Privileged Time")
#define PN_PROCESS_USER                     TEXT("% User Time")
#define PN_PROCESS_WORKING_SET              TEXT("Working Set")
#define PN_PROCESS_PEAK_WS                  TEXT("Working Set Peak")
#define PN_PROCESS_PRIO                     TEXT("Priority Base")
#define PN_PROCESS_ELAPSE                   TEXT("Elapsed Time")
#define PN_PROCESS_ID                       TEXT("ID Process")
#define PN_PROCESS_PRIVATE_PAGE             TEXT("Private Bytes")
#define PN_PROCESS_VIRTUAL_SIZE             TEXT("Virtual Bytes")
#define PN_PROCESS_PEAK_VS                  TEXT("Virtual Bytes Peak")
#define PN_PROCESS_FAULT_COUNT              TEXT("Page Faults/sec")

#define PN_THREAD                           TEXT("Thread")
#define PN_THREAD_CPU                       TEXT("% Processor Time")
#define PN_THREAD_PRIV                      TEXT("% Privileged Time")
#define PN_THREAD_USER                      TEXT("% User Time")
#define PN_THREAD_START                     TEXT("Start Address")
#define PN_THREAD_SWITCHES                  TEXT("Context Switches/sec")
#define PN_THREAD_PRIO                      TEXT("Priority Current")
#define PN_THREAD_BASE_PRIO                 TEXT("Priority Base")
#define PN_THREAD_ELAPSE                    TEXT("Elapsed Time")

#define PN_THREAD_DETAILS                   TEXT("Thread Details")
#define PN_THREAD_PC                        TEXT("User PC")

#define PN_IMAGE                            TEXT("Image")
#define PN_IMAGE_NOACCESS                   TEXT("No Access")
#define PN_IMAGE_READONLY                   TEXT("Read Only")
#define PN_IMAGE_READWRITE                  TEXT("Read/Write")
#define PN_IMAGE_WRITECOPY                  TEXT("Write Copy")
#define PN_IMAGE_EXECUTABLE                 TEXT("Executable")
#define PN_IMAGE_EXE_READONLY               TEXT("Exec Read Only")
#define PN_IMAGE_EXE_READWRITE              TEXT("Exec Read/Write")
#define PN_IMAGE_EXE_WRITECOPY              TEXT("Exec Write Copy")

#define PN_PROCESS_ADDRESS_SPACE            TEXT("Process Address Space")
#define PN_PROCESS_PRIVATE_NOACCESS         TEXT("Reserved Space No Access")
#define PN_PROCESS_PRIVATE_READONLY         TEXT("Reserved Space Read Only")
#define PN_PROCESS_PRIVATE_READWRITE        TEXT("Reserved Space Read/Write")
#define PN_PROCESS_PRIVATE_WRITECOPY        TEXT("Reserved Space Write Copy")
#define PN_PROCESS_PRIVATE_EXECUTABLE       TEXT("Reserved Space Executable")
#define PN_PROCESS_PRIVATE_EXE_READONLY     TEXT("Reserved Space Exec Read Only")
#define PN_PROCESS_PRIVATE_EXE_READWRITE    TEXT("Reserved Space Exec Read/Write")
#define PN_PROCESS_PRIVATE_EXE_WRITECOPY    TEXT("Reserved Space Exec Write Copy")

#define PN_PROCESS_MAPPED_NOACCESS          TEXT("Mapped Space No Access")
#define PN_PROCESS_MAPPED_READONLY          TEXT("Mapped Space Read Only")
#define PN_PROCESS_MAPPED_READWRITE         TEXT("Mapped Space Read/Write")
#define PN_PROCESS_MAPPED_WRITECOPY         TEXT("Mapped Space Write Copy")
#define PN_PROCESS_MAPPED_EXECUTABLE        TEXT("Mapped Space Executable")
#define PN_PROCESS_MAPPED_EXE_READONLY      TEXT("Mapped Space Exec Read Only")
#define PN_PROCESS_MAPPED_EXE_READWRITE     TEXT("Mapped Space Exec Read/Write")
#define PN_PROCESS_MAPPED_EXE_WRITECOPY     TEXT("Mapped Space Exec Write Copy")

#define PN_PROCESS_IMAGE_NOACCESS           TEXT("Image Space No Access")
#define PN_PROCESS_IMAGE_READONLY           TEXT("Image Space Read Only")
#define PN_PROCESS_IMAGE_READWRITE          TEXT("Image Space Read/Write")
#define PN_PROCESS_IMAGE_WRITECOPY          TEXT("Image Space Write Copy")
#define PN_PROCESS_IMAGE_EXECUTABLE         TEXT("Image Space Executable")
#define PN_PROCESS_IMAGE_EXE_READONLY       TEXT("Image Space Exec Read Only")
#define PN_PROCESS_IMAGE_EXE_READWRITE      TEXT("Image Space Exec Read/Write")
#define PN_PROCESS_IMAGE_EXE_WRITECOPY      TEXT("Image Space Exec Write Copy")

DWORD   PX_PROCESS;
DWORD   PX_PROCESS_CPU;
DWORD   PX_PROCESS_PRIV;
DWORD   PX_PROCESS_USER;
DWORD   PX_PROCESS_WORKING_SET;
DWORD   PX_PROCESS_PEAK_WS;
DWORD   PX_PROCESS_PRIO;
DWORD   PX_PROCESS_ELAPSE;
DWORD   PX_PROCESS_ID;
DWORD   PX_PROCESS_PRIVATE_PAGE;
DWORD   PX_PROCESS_VIRTUAL_SIZE;
DWORD   PX_PROCESS_PEAK_VS;
DWORD   PX_PROCESS_FAULT_COUNT;
DWORD   PX_PROCESS_PAGED_POOL_QUOTA;
DWORD   PX_PROCESS_PEAK_PAGED_POOL_QUOTA;
DWORD   PX_PROCESS_NONPAGED_POOL_QUOTA;
DWORD   PX_PROCESS_PEAK_PAGED_POOL;
DWORD   PX_PROCESS_PEAK_NONPAGED_POOL;
DWORD   PX_PROCESS_CUR_PAGED_POOL;
DWORD   PX_PROCESS_CUR_NONPAGED_POOL;
DWORD   PX_PROCESS_PAGED_POOL_LIMIT;
DWORD   PX_PROCESS_NONPAGED_POOL_LIMIT;

DWORD   PX_THREAD;
DWORD   PX_THREAD_CPU;
DWORD   PX_THREAD_PRIV;
DWORD   PX_THREAD_USER;
DWORD   PX_THREAD_START;
DWORD   PX_THREAD_SWITCHES;
DWORD   PX_THREAD_PRIO;
DWORD   PX_THREAD_BASE_PRIO;
DWORD   PX_THREAD_ELAPSE;

DWORD   PX_THREAD_DETAILS;
DWORD   PX_THREAD_PC;

DWORD   PX_IMAGE;
DWORD   PX_IMAGE_NOACCESS;
DWORD   PX_IMAGE_READONLY;
DWORD   PX_IMAGE_READWRITE;
DWORD   PX_IMAGE_WRITECOPY;
DWORD   PX_IMAGE_EXECUTABLE;
DWORD   PX_IMAGE_EXE_READONLY;
DWORD   PX_IMAGE_EXE_READWRITE;
DWORD   PX_IMAGE_EXE_WRITECOPY;

DWORD   PX_PROCESS_ADDRESS_SPACE;
DWORD   PX_PROCESS_PRIVATE_NOACCESS;
DWORD   PX_PROCESS_PRIVATE_READONLY;
DWORD   PX_PROCESS_PRIVATE_READWRITE;
DWORD   PX_PROCESS_PRIVATE_WRITECOPY;
DWORD   PX_PROCESS_PRIVATE_EXECUTABLE;
DWORD   PX_PROCESS_PRIVATE_EXE_READONLY;
DWORD   PX_PROCESS_PRIVATE_EXE_READWRITE;
DWORD   PX_PROCESS_PRIVATE_EXE_WRITECOPY;

DWORD   PX_PROCESS_MAPPED_NOACCESS;
DWORD   PX_PROCESS_MAPPED_READONLY;
DWORD   PX_PROCESS_MAPPED_READWRITE;
DWORD   PX_PROCESS_MAPPED_WRITECOPY;
DWORD   PX_PROCESS_MAPPED_EXECUTABLE;
DWORD   PX_PROCESS_MAPPED_EXE_READONLY;
DWORD   PX_PROCESS_MAPPED_EXE_READWRITE;
DWORD   PX_PROCESS_MAPPED_EXE_WRITECOPY;

DWORD   PX_PROCESS_IMAGE_NOACCESS;
DWORD   PX_PROCESS_IMAGE_READONLY;
DWORD   PX_PROCESS_IMAGE_READWRITE;
DWORD   PX_PROCESS_IMAGE_WRITECOPY;
DWORD   PX_PROCESS_IMAGE_EXECUTABLE;
DWORD   PX_PROCESS_IMAGE_EXE_READONLY;
DWORD   PX_PROCESS_IMAGE_EXE_READWRITE;
DWORD   PX_PROCESS_IMAGE_EXE_WRITECOPY;

#define Li2Double(x) ((double)((x).HighPart) * 4.294967296E9 + (double)((x).LowPart))

typedef     struct _TIME_FIELD
    TIME_FIELD,
    *PTIME_FIELD;

struct _TIME_FIELD
    {
    INT     Hours;
    INT     Mins;
    INT     Secs;
    INT     mSecs;
    };

void RefreshPviewDlgThreadPC
           (HWND            hPviewDlg,
            LPTSTR          szProcessName,
            LPTSTR          szThreadName,
            PPERF_OBJECT    pThreadDetailsObject,
            PPERF_DATA      pCostlyData);

BOOL RefreshMemoryDlg
           (HWND            hMemDlg,
            PPERF_INSTANCE  pProcessInstance,
            PPERF_OBJECT    pProcessObject,
            PPERF_OBJECT    pAddressObject,
            PPERF_OBJECT    pImageObject);

void RefreshMemoryDlgImage
           (HWND           hMemDlg,
            DWORD          dwIndex,
            PPERF_OBJECT   pImageObject);

void RefreshPviewDlgMemoryData
           (HWND            hPviewDlg,
            PPERF_INSTANCE  pProcessInstance,
            PPERF_OBJECT    pProcessObject,
            PPERF_OBJECT    pAddressObject);

PPERF_DATA RefreshPerfData
           (HKEY            hPerfKey,
            LPTSTR          szObjectIndex,
            PPERF_DATA      pData,
            DWORD           *pDataSize);

void RefreshProcessList
           (HWND            hProcessList,
            PPERF_OBJECT    pObject);

void RefreshProcessData
           (HWND             hWnd,
            PPERF_OBJECT     pObject,
            DWORD            ProcessIndex);

void RefreshThreadList
           (HWND             hThreadList,
            PPERF_OBJECT     pObject,
            DWORD            ParentIndex);

void RefreshThreadData
           (HWND             hWnd,
            PPERF_OBJECT     pThreadObj,
            DWORD            ThreadIndex,
            PPERF_OBJECT     pProcessObj,
            PPERF_INSTANCE   pProcessInst);

WORD ProcessPriority
           (PPERF_OBJECT    pObject,
            PPERF_INSTANCE  pInstance);


PVIEWDLG.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 PVIEW_DLG                   100
#define PVIEW_EXIT                  101
#define PVIEW_MEMORY_DETAIL         102
#define PVIEW_TERMINATE             103
#define PVIEW_REFRESH               104
#define PVIEW_COMPUTER              105
#define PVIEW_CONNECT               106
#define PVIEW_PROCESS_LIST          107
#define PVIEW_WS                    108
#define PVIEW_TOTALPRIVATE_COMMIT   109
#define PVIEW_PRIORITY_HIGH         110
#define PVIEW_PRIORITY_NORMAL       111
#define PVIEW_PRIORITY_IDL          112
#define PVIEW_THREAD_HIGHEST        113
#define PVIEW_THREAD_ABOVE          114
#define PVIEW_THREAD_NORMAL         115
#define PVIEW_THREAD_BELOW          116
#define PVIEW_THREAD_LOWEST         117
#define PVIEW_THREAD_LIST           118
#define PVIEW_THREAD_PC             119
#define PVIEW_THREAD_START          120
#define PVIEW_THREAD_SWITCHES       121
#define PVIEW_THREAD_DYNAMIC        122
#define MEMORY_DLG                  1000
#define MEMORY_PROCESS_ID           1001
#define MEMORY_IMAGE_EXECUTE        1002
#define MEMORY_IMAGE_WRITECOPY      1003
#define MEMORY_IMAGE_READWRITE      1004
#define MEMORY_TOTALIMAGE_COMMIT    1005
#define MEMORY_IMAGE_NOACCESS       1006
#define MEMORY_IMAGE_READONLY       1007
#define MEMORY_TOTALMAPPED_COMMIT   1008
#define MEMORY_MAPPED_NOACCESS      1009
#define MEMORY_MAPPED_READONLY      1010
#define MEMORY_MAPPED_READWRITE     1011
#define MEMORY_MAPPED_WRITECOPY     1012
#define MEMORY_MAPPED_EXECUTE       1013
#define MEMORY_TOTALPRIVATE_COMMIT  1014
#define MEMORY_PRIVATE_NOACCESS     1015
#define MEMORY_PRIVATE_READONLY     1016
#define MEMORY_PRIVATE_READWRITE    1017
#define MEMORY_PRIVATE_WRITECOPY    1018
#define MEMORY_PRIVATE_EXECUTE      1019
#define MEMORY_WS                   1020
#define MEMORY_PEAK_WS              1021
#define MEMORY_PRIVATE_PAGE         1022
#define MEMORY_VSIZE                1023
#define MEMORY_PEAK_VSIZE           1024
#define MEMORY_PFCOUNT              1025
#define MEMORY_PAGED                1026
#define MEMORY_PEAK_PAGED           1027
#define MEMORY_NONPAGED             1028
#define MEMORY_QPGPEAK              1029
#define MEMORY_QNONPEAK             1030
#define MEMORY_QPGCUR               1031
#define MEMORY_QNONCUR              1032
#define MEMORY_QPGLIM               1033
#define MEMORY_QNONLIM              1034
#define PVIEW_PROCESS_PRIORITY      124
#define PVIEW_THREAD_PRIORITY       123
#define MEMORY_IMAGE                1035
#define MEMORY_REFRESH              2000
#define MEMORY_REFRESH_ALL          2002
#define PVIEW_REFRESH_COSTLY_DATA   2001


PVIEWER.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.
\******************************************************************************/

/******************************************************************************

                        P R O C E S S   V I E W E R

    Name:       pviewer.c

    Description:
        This program demonstrates the usage of special registry APIs
        for collecting performance data.

        C files used in this app:
            pviewer.c       - this file
            pviewdat.c      - updates the dialog
            perfdata.c      - gets performance data structures
            objdata.c       - access performance data objects
            instdata.c      - access performance data instances
            cntrdata.c      - access performance data counters

******************************************************************************/

#include <windows.h>
#include <winperf.h>
#include "perfdata.h"
#include "pviewdat.h"
#include "pviewdlg.h"
#include <string.h>
#include <stdio.h>

#define INDEX_STR_LEN       10
#define MACHINE_NAME_LEN    MAX_COMPUTERNAME_LENGTH+2
#define MACHINE_NAME_SIZE   MACHINE_NAME_LEN+1

/****
Globals
****/

TCHAR           INDEX_PROCTHRD_OBJ[2*INDEX_STR_LEN];
TCHAR           INDEX_COSTLY_OBJ[3*INDEX_STR_LEN];

TCHAR           gszMachineName[MACHINE_NAME_SIZE];
TCHAR           gszCurrentMachine[MACHINE_NAME_SIZE];

DWORD           gPerfDataSize = 50*1024;            // start with 50K
PPERF_DATA      gpPerfData;

DWORD           gCostlyDataSize = 100*1024;         // start wiih 100K
PPERF_DATA      gpCostlyData;

PPERF_OBJECT    gpProcessObject;                    // pointer to process objects
PPERF_OBJECT    gpThreadObject;                     // pointer to thread objects
PPERF_OBJECT    gpThreadDetailsObject;              // pointer to thread detail objects
PPERF_OBJECT    gpAddressSpaceObject;               // pointer to address space objects
PPERF_OBJECT    gpImageObject;                      // pointer to image objects

HKEY            ghPerfKey = HKEY_PERFORMANCE_DATA;  // get perf data from this key
HKEY            ghMachineKey = HKEY_LOCAL_MACHINE;  // get title index from this key

HCURSOR         ghCursor[2];                        // 0 = arrow, 1 = hourglass

HANDLE          ghMemUpdateEvent;                   // to signal a refresh of mem stats
HANDLE          ghMemUpdateMutex;                   // to restrict overlapping refreshes

HINSTANCE       ghInstance;                         // handle for pviewer app

/****
Prototypes
****/

BOOL CALLBACK   PviewDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
void    PviewDlgRefresh (HWND hWnd);
void    PviewDlgRefreshCostlyData (HWND hPviewDlg);
void    PviewDlgRefreshProcess (HWND hWnd);
void    PviewDlgRefreshThread (HWND hWnd);
void    PviewDlgRefreshCurSelProcess (HWND hWnd);
void    PviewDlgRefreshCurSelThread (HWND hWnd);
WORD    PviewDlgGetCurSelPriority (HWND hWnd);
BOOL    PviewDlgChangePriority (HWND hWnd, DWORD wParam, WORD wItem);
BOOL    PviewDlgTerminateProcess (HWND hPviewDlg);

BOOL CALLBACK   MemDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);
void    MemDlgUpdateThread (HWND hWnd);
void    MemDlgRefresh (HWND hWnd, HWND hPviewDlg);
void    MemDlgRefreshCurSelImage (HWND hMemDlg, HWND hPviewDlg);

INT     GetCurSelText (HWND hList, LPTSTR str);
DWORD   GetCurSelData (HWND hWnd, DWORD dwList);
INT     ReSelectText (HWND hList, INT StartIndex, LPTSTR str);
void    SetPerfIndexes (HWND hWnd);
DWORD   GetTitleIdx (HWND hWnd, LPTSTR TitleSz[], DWORD LastIndex, LPTSTR Name);
void    SetListBoxTabStops (HWND hWnd);
void    SetLocalMachine (void);
BOOL    ConnectComputer (HWND hWnd);
void    DisableControls (HWND hPviewDlg);
void    EnableControls (HWND hPviewDlg);

//********************************************************
//
//  WinMain --
//
//      Build Up: create the program's dialog box,
//          load the desired icons, enter the message
//          loop.
//
//      Tear Down: free up the memory allocated by the
//          dialog box proc, and exit.
//
int WINAPI WinMain (HINSTANCE   hInstance,
                    HINSTANCE   hPrevInstance,
                    LPSTR       lpCmdLine,
                    int         nCmdShow)
{
HANDLE  hWndDialog;
MSG     msg;

    ghInstance = hInstance;

    // load our default cursors
    //
    ghCursor[0] = LoadCursor (0, IDC_ARROW);
    ghCursor[1] = LoadCursor (0, IDC_WAIT);

    // open our dialog box
    //
    hWndDialog = CreateDialogParam (hInstance,
                                    MAKEINTRESOURCE (PVIEW_DLG),
                                    NULL,
                                    (DLGPROC) PviewDlgProc,
                                    (LONG)0);

    // the almighty Windows message loop:
    //
    while (GetMessage (&msg, NULL, 0, 0))
        if (!IsDialogMessage (hWndDialog, &msg))
            {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
            }

    // close up shop
    //
    DestroyWindow (hWndDialog);
    LocalFree (gpPerfData);

    return 0;
}

/*****************
PviewDlg functions
*****************/

//********************************************************
//
//  PviewDlgProc --
//
//      Pview dialog procedure
//
BOOL CALLBACK   PviewDlgProc   (HWND    hWnd,
                                UINT    wMsg,
                                WPARAM  wParam,
                                LPARAM  lParam)
{
WORD    wItem;

    switch (wMsg)
        {

        case WM_INITDIALOG:
            SetClassLong (hWnd, GCL_HICON, (LONG)LoadIcon(ghInstance, TEXT("VIEWPICON")) );
            SetListBoxTabStops (hWnd);
            SendDlgItemMessage (hWnd, PVIEW_COMPUTER, EM_LIMITTEXT, MACHINE_NAME_LEN, 0);
            PostMessage (hWnd, WM_COMMAND, PVIEW_REFRESH, 0);
            break;

        case WM_CLOSE:
            PostQuitMessage (0);
            break;

        case WM_COMMAND:
            //
            // handle our app-specific controls:
            //
            switch (LOWORD (wParam))
                {
                // works just like "close"
                //
                case PVIEW_EXIT:
                    PostQuitMessage (0);
                    break;

                // if somebody moved the highlight in the thread list,
                //  update the view
                //
                case PVIEW_THREAD_LIST:
                    if (HIWORD(wParam) == LBN_DBLCLK || HIWORD(wParam) == LBN_SELCHANGE)
                        {
                        PviewDlgRefreshCurSelThread (hWnd);
                        PostMessage (hWnd, WM_COMMAND, PVIEW_REFRESH_COSTLY_DATA, 0);
                        }
                    break;

                // if somebody clicked on a new process, update all of the
                //  affected information.
                //
                case PVIEW_PROCESS_LIST:
                    if (HIWORD(wParam) == CBN_DBLCLK || HIWORD(wParam) == CBN_SELCHANGE)
                        {
                        PviewDlgRefreshCurSelProcess (hWnd);
                        PostMessage (hWnd, WM_COMMAND, PVIEW_REFRESH_COSTLY_DATA, 0);
                        if (HIWORD(wParam) == CBN_DBLCLK)
                            PostMessage (hWnd, WM_COMMAND, PVIEW_MEMORY_DETAIL, 0);
                        }
                    break;

                // the user wishes to view the memory stats in detail:
                //
                case PVIEW_MEMORY_DETAIL:
                    //
                    // check to see if we can get exclusive access
                    //  to the memory statistics
                    //
                    if (WaitForSingleObject (ghMemUpdateMutex, 0))

                        // we can't, so just return.
                        //
                        return FALSE;

                    else
                        {
                        // we have exclusive access, so start up the
                        //  memory statistics dialog.
                        //
                        // release the mutex first so the dialog can use it.
                        //
                        ReleaseMutex (ghMemUpdateMutex);
                        DialogBoxParam (NULL,
                                        MAKEINTRESOURCE (MEMORY_DLG),
                                        hWnd,
                                        (DLGPROC) MemDlgProc,
                                        (LONG)hWnd);
                        }
                    break;

                // somebody clicked one of the priority radio
                //  buttons.  Find out which one was selected...
                //
                case PVIEW_PRIORITY_HIGH:
                case PVIEW_PRIORITY_NORMAL:
                case PVIEW_PRIORITY_IDL:

                    if (SendDlgItemMessage (hWnd, PVIEW_PRIORITY_HIGH, BM_GETCHECK, 0, 0))
                        wItem = PVIEW_PRIORITY_HIGH;
                    else if (SendDlgItemMessage (hWnd, PVIEW_PRIORITY_NORMAL, BM_GETCHECK, 0, 0))
                        wItem = PVIEW_PRIORITY_NORMAL;
                    else
                        wItem = PVIEW_PRIORITY_IDL;

                    // if the user actually clicked on a NEW state,
                    //  do the change.
                    //
                    if (LOWORD(wParam) != wItem)
                        {
                        // of course, if it's a remote machine, disallow
                        //  the modification.
                        //
                        if (lstrcmp (gszCurrentMachine, gszMachineName))
                            {
                            SendDlgItemMessage (hWnd, wItem, BM_SETCHECK, 1, 0);
                            SetFocus (GetDlgItem (hWnd, wItem));
                            MessageBox (hWnd,
                                        TEXT("Cannot change process priority on remote machine"),
                                        TEXT("Set priority"),
                                        MB_ICONEXCLAMATION|MB_OK);
                            }

                        // at this point, we know we are affecting the local
                        //  machine, and a change has to be made.
                        //  Just Do It(TM).
                        //
                        else if (PviewDlgChangePriority (hWnd, wParam, wItem))
                            PviewDlgRefresh (hWnd);

                        }
                    break;

                case PVIEW_THREAD_HIGHEST:
                case PVIEW_THREAD_ABOVE:
                case PVIEW_THREAD_NORMAL:
                case PVIEW_THREAD_BELOW:
                case PVIEW_THREAD_LOWEST:
                    //
                    // this selection hasn't been fleshed out yet.
                    //
                    PviewDlgRefreshCurSelThread (hWnd);
                    break;

                // terminate the selected process
                //
                case PVIEW_TERMINATE:
                    if (PviewDlgTerminateProcess (hWnd))
                        PviewDlgRefresh (hWnd);
                    break;

                // if the text has changed, we want to connect and
                //  view another system's processes...
                //
                case PVIEW_COMPUTER:
                    if (HIWORD(wParam) == EN_CHANGE)
                        EnableWindow (GetDlgItem (hWnd, PVIEW_CONNECT), TRUE);
                    else
                        return FALSE;
                    break;

                // we were told to connect, go ahead and try...
                //
                case PVIEW_CONNECT:
                    if (ConnectComputer (hWnd))
                        {
                        SetPerfIndexes (hWnd);
                        PviewDlgRefresh (hWnd);
                        }
                    break;

                // refresh the current information displayed
                //
                case PVIEW_REFRESH:
                    if (ConnectComputer (hWnd))
                        SetPerfIndexes (hWnd);
                    PviewDlgRefresh (hWnd);
                    break;

                // refresh the currently updated costly
                //  statistics
                //
                case PVIEW_REFRESH_COSTLY_DATA:
                    if (WaitForSingleObject (ghMemUpdateMutex, 0))
                        return FALSE;

                    PviewDlgRefreshCostlyData (hWnd);
                    ReleaseMutex (ghMemUpdateMutex);
                    break;

                default:
                    return FALSE;
                }
            break;

        default:
            return FALSE;
        }

    return TRUE;
}

//********************************************************
//
//  PviewDlgRefresh --
//
//      Refresh the pview dialog.
//
void    PviewDlgRefresh (HWND hWnd)
{
static  HANDLE  hMemUpdateThread = NULL;
static  DWORD   MemUpdateThreadID;
MSG     Msg;

    SetCursor (ghCursor[1]);

    if (hMemUpdateThread)       // get memory data
        SetEvent (ghMemUpdateEvent);
    else
        hMemUpdateThread = CreateThread (NULL,
                                         0,
                                         (LPTHREAD_START_ROUTINE)MemDlgUpdateThread,
                                         (LPVOID)hWnd,
                                         0,
                                         &MemUpdateThreadID);

    // get performance data
    //
    gpPerfData = RefreshPerfData (ghPerfKey, INDEX_PROCTHRD_OBJ, gpPerfData, &gPerfDataSize);

    gpProcessObject = FindObject (gpPerfData, PX_PROCESS);
    gpThreadObject  = FindObject (gpPerfData, PX_THREAD);

    // refresh
    //
    PviewDlgRefreshProcess (hWnd);
    PviewDlgRefreshThread (hWnd);

    // Remove all mouse and key messages. They are not accepted
    //  while the cursor is a hourglass.
    //
    while (PeekMessage (&Msg, hWnd, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE));
    while (PeekMessage (&Msg, hWnd, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE));

    SetCursor (ghCursor[0]);

}

//********************************************************
//
//  PviewDlgRefreshCostlyData --
//
//      Refresh the costly data.
//
void    PviewDlgRefreshCostlyData (HWND hPviewDlg)
{
LPTSTR          szProcessName;
LPTSTR          szThreadName;
PPERF_INSTANCE  pInstance;
DWORD           dwIndex;

    dwIndex       = GetCurSelData (hPviewDlg, PVIEW_PROCESS_LIST);
    pInstance     = FindInstanceN (gpProcessObject, dwIndex);
    szProcessName = InstanceName (pInstance);

    RefreshPviewDlgMemoryData (hPviewDlg,
                               pInstance,
                               gpProcessObject,
                               gpAddressSpaceObject);

    dwIndex      = GetCurSelData (hPviewDlg, PVIEW_THREAD_LIST);
    pInstance    = FindInstanceN (gpThreadObject, dwIndex);
    szThreadName = InstanceName (pInstance);

    RefreshPviewDlgThreadPC (hPviewDlg,
                             szProcessName,
                             szThreadName,
                             gpThreadDetailsObject,
                             gpCostlyData);

}

//********************************************************
//
//  PviewDlgRefreshProcess --
//
//      Refresh the process list and data in pview dialog.
//
void    PviewDlgRefreshProcess (HWND hWnd)
{
TCHAR   szProcessString[256];
INT     nProcess;
INT     nIndex;
HWND    hProcessList;
DWORD   dwProcessIndex;

    // refresh process list
    //
    hProcessList = GetDlgItem (hWnd, PVIEW_PROCESS_LIST);
    nProcess     = GetCurSelText (hProcessList, szProcessString);

    SendMessage (hProcessList, WM_SETREDRAW, FALSE, 0);
    SendMessage (hProcessList, LB_RESETCONTENT, 0, 0);
    SendMessage (hProcessList, LB_SETITEMDATA, 0, 0);

    RefreshProcessList (hProcessList, gpProcessObject);

    // refresh process data
    //
    if (nProcess != LB_ERR)
        nIndex = ReSelectText (hProcessList, nProcess, szProcessString);
    else
        nIndex = 0;

    dwProcessIndex = SendMessage (hProcessList, LB_GETITEMDATA, nIndex, 0);

    RefreshProcessData (hWnd, gpProcessObject, dwProcessIndex);

    SendMessage (hProcessList, WM_SETREDRAW, TRUE, 0);

}

//********************************************************
//
//  PviewDlgRefreshThread --
//
//      Refresh the thread list and data in pview dialog.
//
void    PviewDlgRefreshThread (HWND hWnd)
{
TCHAR           szThreadString[256];
INT             nThread;
INT             nIndex;
HWND            hThreadList;
DWORD           dwThreadIndex;

PPERF_INSTANCE  pProcessInstance;
DWORD           dwProcessIndex;

    // get process info
    //
    dwProcessIndex = GetCurSelData (hWnd, PVIEW_PROCESS_LIST);
    pProcessInstance = FindInstanceN (gpProcessObject, dwProcessIndex);

    // refresh thread list
    //
    hThreadList  = GetDlgItem (hWnd, PVIEW_THREAD_LIST);
    nThread      = GetCurSelText (hThreadList, szThreadString);

    SendMessage (hThreadList, WM_SETREDRAW, FALSE, 0);
    SendMessage (hThreadList, LB_RESETCONTENT, 0, 0);
    SendMessage (hThreadList, LB_SETITEMDATA, 0, 0);

    RefreshThreadList (hThreadList, gpThreadObject, dwProcessIndex);

    // refresh thread data
    //
    if (nThread != LB_ERR)
        nIndex = ReSelectText (hThreadList, nThread, szThreadString);
    else
        nIndex = 0;

    dwThreadIndex    = SendMessage (hThreadList, LB_GETITEMDATA, nIndex, 0);

    RefreshThreadData (hWnd,
                       gpThreadObject,
                       dwThreadIndex,
                       gpProcessObject,
                       pProcessInstance);

    SendMessage (hThreadList, WM_SETREDRAW, TRUE, 0);

}

//********************************************************
//
//  PviewDlgGetCurSelPriority --
//
//      Get the process priority of currently selected process.
//
WORD    PviewDlgGetCurSelPriority (HWND hWnd)
{
DWORD           dwIndex;
PPERF_INSTANCE  pInst;

    dwIndex = GetCurSelData (hWnd, PVIEW_PROCESS_LIST);
    pInst = FindInstanceN (gpProcessObject, dwIndex);
    return ProcessPriority (gpProcessObject, pInst);
}

//********************************************************
//
//  PviewDlgRefreshCurSelProcess --
//
//      Refresh the data of currently selected process.
//
void    PviewDlgRefreshCurSelProcess (HWND hWnd)
{
DWORD   dwIndex;

    dwIndex = GetCurSelData (hWnd, PVIEW_PROCESS_LIST);
    RefreshProcessData (hWnd, gpProcessObject, dwIndex);

    PviewDlgRefreshThread (hWnd);
}

//********************************************************
//
//  PviewDlgRefreshCurSelThread --
//
//      Refresh the data of currently selected thread.
//
void    PviewDlgRefreshCurSelThread (HWND hWnd)
{
PPERF_INSTANCE  pProcessInstance;
DWORD           dwIndex;

    dwIndex = GetCurSelData (hWnd, PVIEW_PROCESS_LIST);
    pProcessInstance = FindInstanceN (gpProcessObject, dwIndex);

    dwIndex = GetCurSelData (hWnd, PVIEW_THREAD_LIST);

    RefreshThreadData (hWnd,
                       gpThreadObject,
                       dwIndex,
                       gpProcessObject,
                       pProcessInstance);
}

//********************************************************
//
//  PviewDlgChangePriority --
//
//      Change process priority.
//
BOOL    PviewDlgChangePriority (HWND hWnd, DWORD wParam, WORD wItem)
{
DWORD           dwIndex;
PPERF_INSTANCE  pInst;
PPERF_COUNTER   pCountID;
DWORD           *pProcessID;
DWORD           ProcessID;
HANDLE          hProcess;
BOOL            bStat;

    dwIndex = GetCurSelData (hWnd, PVIEW_PROCESS_LIST);
    pInst = FindInstanceN (gpProcessObject, dwIndex);

    if (pCountID = FindCounter (gpProcessObject, PX_PROCESS_ID))
        {
        pProcessID = (DWORD *) CounterData (pInst, pCountID);
        ProcessID = *pProcessID;
        }
    else
        {
        SendDlgItemMessage (hWnd, wItem, BM_SETCHECK, 1, 0);
        SetFocus (GetDlgItem (hWnd, wItem));
        MessageBox (hWnd,
                    TEXT("Cannot find ID for this process"),
                    TEXT("Set priority"),
                    MB_ICONEXCLAMATION|MB_OK);
        return FALSE;
        }

    hProcess = OpenProcess (PROCESS_SET_INFORMATION, FALSE, ProcessID);
    if (!hProcess)
        {
        SendDlgItemMessage (hWnd, wItem, BM_SETCHECK, 1, 0);
        SetFocus (GetDlgItem (hWnd, wItem));
        MessageBox (hWnd,
                    TEXT("Unable to open the process; Priority not changed"),
                    TEXT("Set priority"),
                    MB_ICONEXCLAMATION|MB_OK);
        return FALSE;
        }

    switch (wParam)
        {
        case PVIEW_PRIORITY_HIGH:
            bStat = SetPriorityClass (hProcess, HIGH_PRIORITY_CLASS);
            break;

        case PVIEW_PRIORITY_NORMAL:
            bStat = SetPriorityClass (hProcess, NORMAL_PRIORITY_CLASS);
            break;

        case PVIEW_PRIORITY_IDL:
            bStat = SetPriorityClass (hProcess, IDLE_PRIORITY_CLASS);
            break;

default:
    break;
        }

    CloseHandle (hProcess);

    if (!bStat)
        {
        SendDlgItemMessage (hWnd, wItem, BM_SETCHECK, 1, 0);
        SetFocus (GetDlgItem (hWnd, wItem));
        MessageBox (hWnd,
                    TEXT("Unable to change priority"),
                    TEXT("Set priority"),
                    MB_ICONEXCLAMATION|MB_OK);
        return FALSE;
        }

    return TRUE;

}

//********************************************************
//
//  PviewDlgTerminateProcess --
//
//      Terminate the current selected process.
//
BOOL    PviewDlgTerminateProcess (HWND hPviewDlg)
{
DWORD           dwIndex;
PPERF_INSTANCE  pInst;
PPERF_COUNTER   pCountID;
DWORD           *pProcessID;
DWORD           ProcessID;
HANDLE          hProcess;
TCHAR           szTemp[50];

    dwIndex = GetCurSelData (hPviewDlg, PVIEW_PROCESS_LIST);
    pInst = FindInstanceN (gpProcessObject, dwIndex);

    if (pCountID = FindCounter (gpProcessObject, PX_PROCESS_ID))
        {
        pProcessID = (DWORD *) CounterData (pInst, pCountID);
        ProcessID = *pProcessID;
        }
    else
        {
        MessageBox (hPviewDlg,
                    TEXT("Cannot find ID for this process"),
                    TEXT("Terminate Process"),
                    MB_ICONEXCLAMATION|MB_OK);
        return FALSE;
        }

    wsprintf (szTemp, TEXT("Terminate process %s (ID %#x)?"),
              InstanceName (pInst), ProcessID);

    if (MessageBox (hPviewDlg, szTemp, TEXT("Terminate Process"), MB_ICONSTOP|MB_OKCANCEL) != IDOK)
        return FALSE;

    hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, ProcessID);
    if (!hProcess)
        {
        MessageBox (hPviewDlg,
                    TEXT("Unable to open the process; Process not terminated"),
                    TEXT("Terminate Process"),
                    MB_ICONEXCLAMATION|MB_OK);
        return FALSE;
        }

    if (!TerminateProcess (hProcess, 99))
        {
        MessageBox (hPviewDlg,
                    TEXT("Unable to terminate the process."),
                    TEXT("Terminate Process"),
                    MB_ICONEXCLAMATION|MB_OK);

        CloseHandle (hProcess);
        return FALSE;
        }

    CloseHandle (hProcess);

    return TRUE;

}

/***************
MemDlg functions
***************/

//********************************************************
//
//  MemDlgProc --
//
//      MemoryDlg procedure
//
BOOL CALLBACK   MemDlgProc (HWND    hWnd,
                            UINT    wMsg,
                            WPARAM  wParam,
                            LPARAM  lParam)
{
static HWND hPviewDlg;

    switch (wMsg)
        {
        case WM_INITDIALOG:
            hPviewDlg = (HWND)lParam;
            PostMessage (hWnd, WM_COMMAND, MEMORY_REFRESH, 0);
            break;

        case WM_QUIT:
        case WM_CLOSE:
            EndDialog (hWnd, TRUE);
            break;

        case WM_COMMAND:
            switch (LOWORD (wParam))
                {
                // get the memory statistics for the currently selected
                //  process/thread
                //
                case MEMORY_IMAGE:
                    if (HIWORD(wParam) == CBN_DBLCLK || HIWORD(wParam) == CBN_SELCHANGE)
                        {
                        if (WaitForSingleObject (ghMemUpdateMutex, 0))
                            return FALSE;

                        MemDlgRefreshCurSelImage (hWnd, hPviewDlg);
                        ReleaseMutex (ghMemUpdateMutex);
                        }
                    else
                        return FALSE;
                    break;

                // refresh the current memory statistics,
                //  retry if we can't get the mutex
                //
                case MEMORY_REFRESH:
                    if (WaitForSingleObject (ghMemUpdateMutex, 1000))
                        {
                        // can't get the mutex, retry...
                        //
                        PostMessage (hWnd, WM_COMMAND, MEMORY_REFRESH, 0);
                        return FALSE;
                        }

                    MemDlgRefresh (hWnd, hPviewDlg);
                    ReleaseMutex (ghMemUpdateMutex);
                    break;

                case IDCANCEL:
                case IDOK:
                    EndDialog (hWnd, TRUE);
                    break;

                default:
                    return FALSE;
                }
        default:
            return FALSE;
        }

    return TRUE;

}

//********************************************************
//
//  MemDlgUpdateThread --
//
//      This function runs in a separate thread to collect memory data.
//
void MemDlgUpdateThread (HWND hPviewDlg)
{

    ghMemUpdateMutex = CreateMutex (NULL, TRUE, NULL);
    ghMemUpdateEvent = CreateEvent (NULL, FALSE, FALSE, NULL);

    while (TRUE)
        {
        EnableWindow (GetDlgItem (hPviewDlg, PVIEW_MEMORY_DETAIL), FALSE);

        gpCostlyData = RefreshPerfData (ghPerfKey,
                                        INDEX_COSTLY_OBJ,
                                        gpCostlyData,
                                        &gCostlyDataSize);

        gpAddressSpaceObject  = FindObject (gpCostlyData, PX_PROCESS_ADDRESS_SPACE);
        gpThreadDetailsObject = FindObject (gpCostlyData, PX_THREAD_DETAILS);
        gpImageObject         = FindObject (gpCostlyData, PX_IMAGE);

        EnableWindow (GetDlgItem (hPviewDlg, PVIEW_MEMORY_DETAIL), TRUE);

        ReleaseMutex (ghMemUpdateMutex);

        PostMessage (hPviewDlg, WM_COMMAND, PVIEW_REFRESH_COSTLY_DATA, 0);

        WaitForSingleObject (ghMemUpdateEvent, INFINITE);
        WaitForSingleObject (ghMemUpdateMutex, INFINITE);
        }

}

//********************************************************
//
//  MemDlgRefresh --
//
//      Refresh the memory dialog.
//
void MemDlgRefresh (HWND hMemDlg, HWND hPviewDlg)
{
HWND            hImageList;
DWORD           dwIndex;
BOOL            bStat;
PPERF_INSTANCE  pInstance;

    hImageList = GetDlgItem (hMemDlg, MEMORY_IMAGE);

    SendMessage (hImageList, WM_SETREDRAW, FALSE, 0);
    SendMessage (hImageList, CB_RESETCONTENT, 0, 0);
    SendMessage (hImageList, CB_SETITEMDATA, 0, 0);

    dwIndex = GetCurSelData (hPviewDlg, PVIEW_PROCESS_LIST);
    pInstance = FindInstanceN (gpProcessObject, dwIndex);

    bStat = RefreshMemoryDlg (hMemDlg,
                              pInstance,
                              gpProcessObject,
                              gpAddressSpaceObject,
                              gpImageObject);

    SendMessage (hImageList, WM_SETREDRAW, TRUE, 0);

SendMessage (hImageList, CB_SETCURSEL, 0, 0);

    if (!bStat)
        {
        MessageBox (hMemDlg,
                    TEXT("Unable to retrieve memory detail"),
                    TEXT("Memory detail"),
                    MB_ICONSTOP|MB_OK);
        PostMessage (hMemDlg, WM_CLOSE, 0, 0);
        }

}

//********************************************************
//
//  MemDlgRefreshCurSelImage --
//
//      Refresh the current selected image for memory dialog.
//
void    MemDlgRefreshCurSelImage (HWND hMemDlg, HWND hPviewDlg)
{
HWND    hList;
INT     nIndex;
DWORD   dwIndex;

    hList = GetDlgItem (hMemDlg, MEMORY_IMAGE);
    nIndex = SendMessage (hList, CB_GETCURSEL, 0, 0);

    if (nIndex == CB_ERR)
        nIndex = 0;

    dwIndex = SendMessage (hList, CB_GETITEMDATA, nIndex, 0);

    if (dwIndex == 0xFFFFFFFF)
        MemDlgRefresh (hMemDlg, hPviewDlg);
    else
        RefreshMemoryDlgImage (hMemDlg, dwIndex, gpImageObject);

}

/****************
utility functions
****************/

//********************************************************
//
//  GetCurSelText --
//
//      Get the text of current selection.  Used for later ReSelectText().
//
INT     GetCurSelText (HWND hList, LPTSTR str)
{
INT     Index;
INT     Length;

    Index = SendMessage (hList, LB_GETCURSEL, 0, 0);
    Length = SendMessage (hList, LB_GETTEXT, Index, (DWORD)str);

    return Index;
}

//********************************************************
//
//  GetCurSelData --
//
//      Get the data associated with the current selection.
//
DWORD   GetCurSelData (HWND hWnd, DWORD dwList)
{
HWND    hList;
INT     nIndex;
DWORD   dwIndex;

    hList  = GetDlgItem (hWnd, dwList);
    nIndex = SendMessage (hList, LB_GETCURSEL, 0, 0);

    if (nIndex == LB_ERR)
        nIndex = 0;

    dwIndex = SendMessage (hList, LB_GETITEMDATA, nIndex, 0);

    return dwIndex;
}

//********************************************************
//
//  ReSelectText --
//
//      Reselect the line specified by str.  Returns the new index.  If cannot
//      find the line or any error, then 0 is returned.
//
INT     ReSelectText (HWND hList, INT StartIndex, LPTSTR str)
{
INT     Index;
INT     Length;
TCHAR   SaveChar = TEXT('\0');

    Index = SendMessage (hList, LB_FINDSTRING, StartIndex, (DWORD)str);

    if (Index == LB_ERR)
        {
        Length = lstrlen (str);

        while (Index == LB_ERR && Length)
            {
            SaveChar = str[Length-1];
            str[Length-1] = TEXT('\0');

            Index = SendMessage (hList, LB_FINDSTRING, StartIndex, (DWORD)str);

            str[Length-1] = SaveChar;
            Length--;
            }
        }

    if (Index == LB_ERR)
        return 0;
    else
        {
        SendMessage (hList, LB_SETCURSEL, Index, 0);
        return Index;
        }

}

//********************************************************
//
//  SetPerfIndexes
//
//      Setup the perf data indexes.
//
void    SetPerfIndexes (HWND hWnd)
{
LPTSTR  TitleBuffer;
LPTSTR  *Title;
DWORD   Last;
TCHAR   szTemp[50];
DWORD   dwR;

    dwR = GetPerfTitleSz (ghMachineKey, ghPerfKey, &TitleBuffer, &Title, &Last);

    if (dwR != ERROR_SUCCESS)
        {
        wsprintf (szTemp, TEXT("Unable to retrieve counter indexes, ERROR -> %#x"), dwR);
        MessageBox (hWnd, szTemp, TEXT("Pviewer"), MB_OK|MB_ICONEXCLAMATION);
        return;
        }

    PX_PROCESS                       = GetTitleIdx (hWnd, Title, Last, PN_PROCESS);
    PX_PROCESS_CPU                   = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_CPU);
    PX_PROCESS_PRIV                  = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIV);
    PX_PROCESS_USER                  = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_USER);
    PX_PROCESS_WORKING_SET           = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_WORKING_SET);
    PX_PROCESS_PEAK_WS               = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PEAK_WS);
    PX_PROCESS_PRIO                  = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIO);
    PX_PROCESS_ELAPSE                = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_ELAPSE);
    PX_PROCESS_ID                    = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_ID);
    PX_PROCESS_PRIVATE_PAGE          = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_PAGE);
    PX_PROCESS_VIRTUAL_SIZE          = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_VIRTUAL_SIZE);
    PX_PROCESS_PEAK_VS               = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PEAK_VS);
    PX_PROCESS_FAULT_COUNT           = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_FAULT_COUNT);

    PX_THREAD                        = GetTitleIdx (hWnd, Title, Last, PN_THREAD);
    PX_THREAD_CPU                    = GetTitleIdx (hWnd, Title, Last, PN_THREAD_CPU);
    PX_THREAD_PRIV                   = GetTitleIdx (hWnd, Title, Last, PN_THREAD_PRIV);
    PX_THREAD_USER                   = GetTitleIdx (hWnd, Title, Last, PN_THREAD_USER);
    PX_THREAD_START                  = GetTitleIdx (hWnd, Title, Last, PN_THREAD_START);
    PX_THREAD_SWITCHES               = GetTitleIdx (hWnd, Title, Last, PN_THREAD_SWITCHES);
    PX_THREAD_PRIO                   = GetTitleIdx (hWnd, Title, Last, PN_THREAD_PRIO);
    PX_THREAD_BASE_PRIO              = GetTitleIdx (hWnd, Title, Last, PN_THREAD_BASE_PRIO);
    PX_THREAD_ELAPSE                 = GetTitleIdx (hWnd, Title, Last, PN_THREAD_ELAPSE);

    PX_THREAD_DETAILS                = GetTitleIdx (hWnd, Title, Last, PN_THREAD_DETAILS);
    PX_THREAD_PC                     = GetTitleIdx (hWnd, Title, Last, PN_THREAD_PC);

    PX_IMAGE                         = GetTitleIdx (hWnd, Title, Last, PN_IMAGE);
    PX_IMAGE_NOACCESS                = GetTitleIdx (hWnd, Title, Last, PN_IMAGE_NOACCESS);
    PX_IMAGE_READONLY                = GetTitleIdx (hWnd, Title, Last, PN_IMAGE_READONLY);
    PX_IMAGE_READWRITE               = GetTitleIdx (hWnd, Title, Last, PN_IMAGE_READWRITE);
    PX_IMAGE_WRITECOPY               = GetTitleIdx (hWnd, Title, Last, PN_IMAGE_WRITECOPY);
    PX_IMAGE_EXECUTABLE              = GetTitleIdx (hWnd, Title, Last, PN_IMAGE_EXECUTABLE);
    PX_IMAGE_EXE_READONLY            = GetTitleIdx (hWnd, Title, Last, PN_IMAGE_EXE_READONLY);
    PX_IMAGE_EXE_READWRITE           = GetTitleIdx (hWnd, Title, Last, PN_IMAGE_EXE_READWRITE);
    PX_IMAGE_EXE_WRITECOPY           = GetTitleIdx (hWnd, Title, Last, PN_IMAGE_EXE_WRITECOPY);

    PX_PROCESS_ADDRESS_SPACE         = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_ADDRESS_SPACE);
    PX_PROCESS_PRIVATE_NOACCESS      = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_NOACCESS);
    PX_PROCESS_PRIVATE_READONLY      = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_READONLY);
    PX_PROCESS_PRIVATE_READWRITE     = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_READWRITE);
    PX_PROCESS_PRIVATE_WRITECOPY     = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_WRITECOPY);
    PX_PROCESS_PRIVATE_EXECUTABLE    = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_EXECUTABLE);
    PX_PROCESS_PRIVATE_EXE_READONLY  = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_EXE_READONLY);
    PX_PROCESS_PRIVATE_EXE_READWRITE = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_EXE_READWRITE);
    PX_PROCESS_PRIVATE_EXE_WRITECOPY = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_PRIVATE_EXE_WRITECOPY);

    PX_PROCESS_MAPPED_NOACCESS       = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_MAPPED_NOACCESS);
    PX_PROCESS_MAPPED_READONLY       = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_MAPPED_READONLY);
    PX_PROCESS_MAPPED_READWRITE      = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_MAPPED_READWRITE);
    PX_PROCESS_MAPPED_WRITECOPY      = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_MAPPED_WRITECOPY);
    PX_PROCESS_MAPPED_EXECUTABLE     = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_MAPPED_EXECUTABLE);
    PX_PROCESS_MAPPED_EXE_READONLY   = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_MAPPED_EXE_READONLY);
    PX_PROCESS_MAPPED_EXE_READWRITE  = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_MAPPED_EXE_READWRITE);
    PX_PROCESS_MAPPED_EXE_WRITECOPY  = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_MAPPED_EXE_WRITECOPY);

    PX_PROCESS_IMAGE_NOACCESS        = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_IMAGE_NOACCESS);
    PX_PROCESS_IMAGE_READONLY        = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_IMAGE_READONLY);
    PX_PROCESS_IMAGE_READWRITE       = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_IMAGE_READWRITE);
    PX_PROCESS_IMAGE_WRITECOPY       = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_IMAGE_WRITECOPY);
    PX_PROCESS_IMAGE_EXECUTABLE      = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_IMAGE_EXECUTABLE);
    PX_PROCESS_IMAGE_EXE_READONLY    = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_IMAGE_EXE_READONLY);
    PX_PROCESS_IMAGE_EXE_READWRITE   = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_IMAGE_EXE_READWRITE);
    PX_PROCESS_IMAGE_EXE_WRITECOPY   = GetTitleIdx (hWnd, Title, Last, PN_PROCESS_IMAGE_EXE_WRITECOPY);

    wsprintf (INDEX_PROCTHRD_OBJ, TEXT("%ld %ld"), PX_PROCESS, PX_THREAD);
    wsprintf (INDEX_COSTLY_OBJ, TEXT("%ld %ld %ld"),
              PX_PROCESS_ADDRESS_SPACE, PX_IMAGE, PX_THREAD_DETAILS);

    LocalFree (TitleBuffer);
    LocalFree (Title);

}

//********************************************************
//
//  GetTitleIdx
//
//      Searches Titles[] for Name.  Returns the index found.
//
DWORD   GetTitleIdx (HWND hWnd, LPTSTR Title[], DWORD LastIndex, LPTSTR Name)
{
DWORD   Index;

    for (Index = 0; Index <= LastIndex; Index++)
        if (Title[Index])
            if (!lstrcmpi (Title[Index], Name))
                return Index;

    MessageBox (hWnd, Name, TEXT("Pviewer cannot find index"), MB_OK);
    return 0;
}

//********************************************************
//
//  SetListBoxTabStops --
//
//      Set tab stops in the two list boxes.
//
void    SetListBoxTabStops (HWND hWnd)
{
HWND    hListBox;
INT     Tabs[4] = {22*4, 36*4, 44*4};

    hListBox = GetDlgItem (hWnd, PVIEW_PROCESS_LIST);
    SendMessage (hListBox, LB_SETTABSTOPS, 3, (DWORD)Tabs);

    hListBox = GetDlgItem (hWnd, PVIEW_THREAD_LIST);
    SendMessage (hListBox, LB_SETTABSTOPS, 3, (DWORD)Tabs);
}

//********************************************************
//
//  SetLocalMachine --
//
//      Set local machine as performance data focus.
//
//      Sets    ghPerfKey
//              ghMachineKey
//              gszMachineName
//              gszCurrentMachine
//
void    SetLocalMachine (void)
{
TCHAR   szName[MACHINE_NAME_SIZE];
DWORD   dwSize = MACHINE_NAME_SIZE;

    // close remote connections, if any
    //
    if (ghPerfKey != HKEY_PERFORMANCE_DATA)
        RegCloseKey (ghPerfKey);

    if (ghMachineKey != HKEY_LOCAL_MACHINE)
        RegCloseKey (ghMachineKey);

    // set to registry keys on local machine
    //
    ghPerfKey    = HKEY_PERFORMANCE_DATA;
    ghMachineKey = HKEY_LOCAL_MACHINE;

    // get computer name
    GetComputerName (szName, &dwSize);

    if (szName[0] != '\\' || szName[1] != '\\')     // must have two '\\'
        {
        wsprintf (gszMachineName, TEXT("\\\\%s"), szName);
        lstrcpy (gszCurrentMachine, gszMachineName);
        }
    else
        {
        lstrcpy (gszMachineName, szName);
        lstrcpy (gszCurrentMachine, gszMachineName);
        }

}

//********************************************************
//
//  ConnectComputer --
//
//      Connect to a computer with name entered in PVIEW_COMPUTER.
//      If a new connection is made, then return TRUE else return FALSE.
//
//      Sets    gszCurrentMachine
//              ghPerfKey
//              ghMachineKey
//
BOOL    ConnectComputer (HWND hWnd)
{
DWORD   dwR;
HKEY    hKey;
TCHAR   szTemp[MACHINE_NAME_SIZE];
TCHAR   szTemp2[MACHINE_NAME_SIZE+100];
BOOL    bResult = TRUE;
MSG     Msg;

    SetCursor (ghCursor[1]);

    if (!GetDlgItemText (hWnd, PVIEW_COMPUTER, szTemp, sizeof (szTemp)))
        {
        SetLocalMachine ();
        SetDlgItemText (hWnd, PVIEW_COMPUTER, gszCurrentMachine);
        }

    else if (!lstrcmpi (szTemp, gszCurrentMachine))     // didn't change name
        bResult = FALSE;

    else if (!lstrcmpi (szTemp, gszMachineName))        // local machine
        {
        SetLocalMachine ();
        EnableControls (hWnd);
        }

    else
        {
        // a remote machine, connect to it
        //
        dwR = RegConnectRegistry (szTemp, HKEY_PERFORMANCE_DATA, &hKey);

        if (dwR != ERROR_SUCCESS)
            {
            wsprintf (szTemp2, TEXT("Cannot connect to computer %s"), szTemp);
            MessageBox (hWnd, szTemp2, TEXT(""), MB_ICONEXCLAMATION|MB_OK);

            SetDlgItemText (hWnd, PVIEW_COMPUTER, gszCurrentMachine);

            bResult = FALSE;
            }
        else
            {
            // connected
            //
            lstrcpy (gszCurrentMachine, szTemp);

            if (ghPerfKey != HKEY_PERFORMANCE_DATA)
                RegCloseKey (ghPerfKey);

            ghPerfKey = hKey;

            DisableControls (hWnd);

            // we also need to get the remote machine's title indexes.
            //
            dwR = RegConnectRegistry (gszCurrentMachine, HKEY_LOCAL_MACHINE, &hKey);

            if (ghMachineKey != HKEY_LOCAL_MACHINE)
                RegCloseKey (ghMachineKey);

            if (dwR == ERROR_SUCCESS)
                ghMachineKey = hKey;
            else
                // unable to connect, so we'll use our own indexes.
                //
                ghMachineKey = HKEY_LOCAL_MACHINE;
            }
        }

    // Remove all mouse and key messages. They are not accepted
    //  while the cursor is a hourglass.
    //
    while (PeekMessage (&Msg, hWnd, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE));
    while (PeekMessage (&Msg, hWnd, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE));

    SetCursor (ghCursor[0]);

    EnableWindow (GetDlgItem (hWnd, PVIEW_CONNECT), FALSE);

    return bResult;

}

//********************************************************
//
//  DisableControls --
//
//      Disable controls that don't make sense on remote machine
//
void DisableControls (HWND hPviewDlg)
{
    EnableWindow (GetDlgItem (hPviewDlg, PVIEW_TERMINATE), FALSE);
    EnableWindow (GetDlgItem (hPviewDlg, PVIEW_PRIORITY_HIGH), FALSE);
    EnableWindow (GetDlgItem (hPviewDlg, PVIEW_PRIORITY_NORMAL), FALSE);
    EnableWindow (GetDlgItem (hPviewDlg, PVIEW_PRIORITY_IDL), FALSE);
}

//********************************************************
//
//  EnableControls --
//
//      Enable controls disabled by DisableControl().
//
void EnableControls (HWND hPviewDlg)
{
    EnableWindow (GetDlgItem (hPviewDlg, PVIEW_TERMINATE), TRUE);
    EnableWindow (GetDlgItem (hPviewDlg, PVIEW_PRIORITY_HIGH), TRUE);
    EnableWindow (GetDlgItem (hPviewDlg, PVIEW_PRIORITY_NORMAL), TRUE);
    EnableWindow (GetDlgItem (hPviewDlg, PVIEW_PRIORITY_IDL), TRUE);
}


CNTRDATA.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.
\******************************************************************************/

/******************************************************************************

                            C O U N T E R   D A T A

    Name:       cntrdata.c

    Description:
        This module contains functions that access counters of an instance
        of object in performance data.

    Functions:
        FirstCounter
        NextCounter
        FindCounter
        CounterData

******************************************************************************/

#include <windows.h>
#include <winperf.h>
#include "perfdata.h"

//*********************************************************************
//
//  FirstCounter
//
//      Find the first counter in pObject.
//
//      Returns a pointer to the first counter.  If pObject is NULL
//      then NULL is returned.
//
PPERF_COUNTER FirstCounter (PPERF_OBJECT pObject)
{
    if (pObject)
        return (PPERF_COUNTER)((PCHAR) pObject + pObject->HeaderLength);
    else
        return NULL;
}

//*********************************************************************
//
//  NextCounter
//
//      Find the next counter of pCounter.
//
//      If pCounter is the last counter of an object type, bogus data
//      maybe returned.  The caller should do the checking.
//
//      Returns a pointer to a counter.  If pCounter is NULL then
//      NULL is returned.
//
PPERF_COUNTER NextCounter (PPERF_COUNTER pCounter)
{
    if (pCounter)
        return (PPERF_COUNTER)((PCHAR) pCounter + pCounter->ByteLength);
    else
        return NULL;
}

//*********************************************************************
//
//  FindCounter
//
//      Find a counter specified by TitleIndex.
//
//      Returns a pointer to the counter.  If counter is not found
//      then NULL is returned.
//
PPERF_COUNTER FindCounter (PPERF_OBJECT pObject, DWORD TitleIndex)
{
PPERF_COUNTER pCounter;
DWORD         i = 0;

    if (pCounter = FirstCounter (pObject))
        while (i < pObject->NumCounters)
            {
            if (pCounter->CounterNameTitleIndex == TitleIndex)
                return pCounter;

            pCounter = NextCounter (pCounter);
            i++;
            }

    return NULL;

}

//*********************************************************************
//
//  CounterData
//
//      Returns counter data for an object instance.  If pInst or pCount
//      is NULL then NULL is returne.
//
PVOID CounterData (PPERF_INSTANCE pInst, PPERF_COUNTER pCount)
{
PPERF_COUNTER_BLOCK pCounterBlock;

    if (pCount && pInst)
        {
        pCounterBlock = (PPERF_COUNTER_BLOCK)((PCHAR)pInst + pInst->ByteLength);
        return (PVOID)((PCHAR)pCounterBlock + pCount->CounterOffset);
        }
    else
        return NULL;
}


CLIENT.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1997 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.
\******************************************************************************/

/*++

Copyright 1992 - 1997 Microsoft Corporation

Module Name:

    Client.c

Abstract:

    The Client component of Remote. Connects to the remote
    server using named pipes. It sends its stdin to
    the server and output everything from server to
    its stdout.

Author:

    Rajivendra Nath  2-Jan-1992
    Dave Hart        Summer 1997   single-pipe operation

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <io.h>
#include <string.h>
#include "Remote.h"

BOOL fAsyncPipe = TRUE;    // need this so server has it TRUE

HANDLE*
EstablishSession(
    char *server,
    char *pipe
    );

DWORD
WINAPI
SendServerInp(
    LPVOID pvParam
    );

BOOL
FilterClientInp(
    char *buff,
    int count
    );

BOOL
Mych(
    DWORD ctrlT
    );

VOID
SendMyInfo(
    PHANDLE Pipes
    );

#define ZERO_LENGTH_READ_LIMIT  200

HANDLE MyStdInp;
HANDLE MyStdOut;

//
// ReadPipe and WritePipe are referenced by multiple
// threads so need to be volatile.
//

volatile HANDLE ReadPipe;
volatile HANDLE WritePipe;

CONSOLE_SCREEN_BUFFER_INFO csbi;

char   MyEchoStr[30];
BOOL   CmdSent;
DWORD  LinesToSend=LINESTOSEND;

VOID
Client(
    char* Server,
    char* Pipe
    )
{
    HANDLE *Connection;
    DWORD  dwThreadID;
    HANDLE hThread;
    DWORD  cb;
    OVERLAPPED ol;
    char   rgchBuf[1024];
    DWORD  dwZeroCount = 0;

    MyStdInp=GetStdHandle(STD_INPUT_HANDLE);
    MyStdOut=GetStdHandle(STD_OUTPUT_HANDLE);

    printf("**************************************\n");
    printf("***********     REMOTE    ************\n");
    printf("***********     CLIENT    ************\n");
    printf("**************************************\n");

    if ((Connection=EstablishSession(Server,Pipe))==NULL)
        return;

    ReadPipe=Connection[0];
    WritePipe=Connection[1];

    SetConsoleCtrlHandler((PHANDLER_ROUTINE)Mych,TRUE);

    // Start Thread For Client --> Server Flow
    hThread = (HANDLE)
        _beginthreadex(
            NULL,             // security
            0,                // default stack size
            SendServerInp,    // thread proc
            NULL,             // parm
            0,                // not suspended
            &dwThreadID
            );

    if ( ! hThread)
    {

        Errormsg("REMOTE /C Could Not Create Thread.");
        return;
    }

    ZeroMemory(&ol, sizeof(ol));

    ol.hEvent =
        CreateEvent(
            NULL,      // security
            TRUE,      // auto-reset
            FALSE,     // initially nonsignaled
            NULL       // unnamed
            );

    while (ReadFileSynch(ReadPipe, rgchBuf, sizeof rgchBuf, &cb, 0, &ol)) {

        if (cb) {
           if ( ! WriteFile(MyStdOut, rgchBuf, cb, &cb, NULL)) {
               break;
           }
           dwZeroCount = 0;
        } else {
            if (++dwZeroCount > ZERO_LENGTH_READ_LIMIT) {

                //
                // If we get a bunch of zero length reads in a row,
                // something's broken, don't loop forever.
                // (bug #115866).
                //

                printf("\nREMOTE: bailing out, server must have gone away.\n");
                break;
            }
        }

    }

    CloseHandle(ol.hEvent);

    printf("*** SESSION OVER ***");
    fflush(stdout);

    //
    // Terminate the keyboard reading thread.
    //

    TerminateThread(hThread, 0);
    CloseHandle(hThread);
    CloseClientPipes();

    printf("\n");
    fflush(stdout);

}

DWORD
WINAPI
SendServerInp(
    LPVOID pvParam
    )
{
    DWORD  dread,dwrote;
    OVERLAPPED ol;
    char buff[512];

    UNREFERENCED_PARAMETER(pvParam);

    ZeroMemory(&ol, sizeof(ol));

    ol.hEvent =
        CreateEvent(
            NULL,      // security
            TRUE,      // auto-reset
            FALSE,     // initially nonsignaled
            NULL       // unnamed
            );

    while(ReadFile(MyStdInp,buff,sizeof buff,&dread,NULL))
    {
        if (FilterClientInp(buff,dread))
            continue;
        if (!WriteFileSynch(WritePipe,buff,dread,&dwrote,0,&ol))
            break;
    }

    CloseClientPipes();

    return 0;
}

BOOL
FilterClientInp(
    char *buff,
    int count
    )
{

    if (count==0)
        return(TRUE);

    if (buff[0]==2)     // Adhoc screening of ^B so that i386kd/mipskd
        return(TRUE);   // do not terminate.

    if (buff[0]==COMMANDCHAR)
    {
        switch (buff[1])
        {
        case 'k':
        case 'K':
        case 'q':
        case 'Q':
              CloseClientPipes();
              return(FALSE);

        case 'h':
        case 'H':
              printf("%cM : Send Message\n",COMMANDCHAR);
              printf("%cP : Show Popup on Server\n",COMMANDCHAR);
              printf("%cS : Status of Server\n",COMMANDCHAR);
              printf("%cQ : Quit client\n",COMMANDCHAR);
              printf("%cH : This Help\n",COMMANDCHAR);
              return(TRUE);

        default:
              return(FALSE);
        }

    }
    return(FALSE);
}

BOOL
Mych(
   DWORD ctrlT
   )

{
    char  c[2];
    DWORD tmp;
    OVERLAPPED ol;

    c[0]=CTRLC;

    if (ctrlT==CTRL_C_EVENT)
    {
        ZeroMemory(&ol, sizeof(ol));

        ol.hEvent =
            CreateEvent(
                NULL,      // security
                TRUE,      // auto-reset
                FALSE,     // initially nonsignaled
                NULL       // unnamed
                );

        if (INVALID_HANDLE_VALUE != WritePipe &&
            !WriteFileSynch(WritePipe,c,1,&tmp,0,&ol))
        {
            CloseHandle(ol.hEvent);
            Errormsg("Error Sending ^c");
            return(FALSE);
        }
        CloseHandle(ol.hEvent);
        return(TRUE);
    }
    if ((ctrlT==CTRL_BREAK_EVENT)||
        (ctrlT==CTRL_CLOSE_EVENT)||
        (ctrlT==CTRL_LOGOFF_EVENT)||
        (ctrlT==CTRL_SHUTDOWN_EVENT)
       ) {

       CloseClientPipes();
    }
    return(FALSE);
}

VOID
CloseClientPipes(
    VOID
    )
{
    HANDLE WriteHandle, ReadHandle;

    WriteHandle = (HANDLE) InterlockedExchange(
        (LPLONG) &WritePipe,
        (LONG)   INVALID_HANDLE_VALUE
        );

    if (INVALID_HANDLE_VALUE != WriteHandle) {

        CloseHandle(WriteHandle);

        ReadHandle = (HANDLE) InterlockedExchange(
            (LPLONG) &ReadPipe,
            (LONG)   INVALID_HANDLE_VALUE
            );

        if (INVALID_HANDLE_VALUE != ReadHandle &&
            WriteHandle != ReadHandle) {

            CloseHandle(ReadHandle);
        }
    }
}

VOID
HandleConnectError(
    char *server,
    char *srvpipename
    )
{
    DWORD Err = GetLastError();
    char  msg[128];

    Errormsg("*** Unable to Connect ***");

    //
    // Print a helpful message
    //

    switch(Err)
    {
        case ERROR_FILE_NOT_FOUND:
            sprintf(msg,"invalid pipe name %s", srvpipename);
            break;

        case ERROR_BAD_NETPATH:
            sprintf(msg,"\\\\%s not found", server);
            break;

        default:
            FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|
                           FORMAT_MESSAGE_IGNORE_INSERTS,
                           NULL, Err, 0, msg, sizeof(msg), NULL);
            break;

    }

    printf("Diagnosis: %s\n",msg);

    //
    // If the machine exists but the pipe doesn't do an
    // automatic remote /q to list pipes available on
    // that machine.
    //

    if (ERROR_FILE_NOT_FOUND == Err) {

        printf("\nREMOTE /Q %s\n", server);
        fflush(stdout);
        QueryRemotePipes(server);
    }
}

HANDLE*
EstablishSession(
    char *server,
    char *srvpipename
    )
{
    extern BOOL bForceTwoPipes;
    static HANDLE PipeH[2];
    char   pipenameSrvIn[200];
    char   pipenameSrvOut[200];
    BOOL   fOldServer;
    DWORD  dwError;
    DWORD  RetryCount = 0;

    //
    // Since in single-pipe operation we'll be using the same
    // pipe in two threads, we have to open the handles for
    // overlapped operation, even though we always want
    // synchronous operation.
    //

    sprintf(pipenameSrvIn ,SERVER_READ_PIPE ,server,srvpipename);
    sprintf(pipenameSrvOut,SERVER_WRITE_PIPE,server,srvpipename);

    if (bForceTwoPipes) {

        dwError = ERROR_NOT_SUPPORTED;

    } else {

      RetrySrvBidi:

        if (INVALID_HANDLE_VALUE ==
               (PipeH[1] =
                    CreateFile(
                        pipenameSrvIn,
                        GENERIC_READ | GENERIC_WRITE,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        FILE_FLAG_OVERLAPPED,
                        NULL
                        ))) {

            dwError = GetLastError();

            if (ERROR_PIPE_BUSY == dwError) {

                printf( "All pipe instances busy, waiting for another...\n");

                WaitNamedPipe(
                    pipenameSrvIn,
                    15000
                    );

                if (RetryCount++ < 6) {
                    goto RetrySrvBidi;
                }
            }

            if (ERROR_ACCESS_DENIED != dwError &&
                ERROR_NOT_SUPPORTED != dwError) {

                HandleConnectError(server, srvpipename);
                return NULL;
            }

        } else {

            PipeH[0] = PipeH[1];
            fAsyncPipe = TRUE;

            printf("Connected...\n\n");

            SendMyInfo(PipeH);

            return PipeH;
        }
    }

    //
    // Old remote servers don't allow you to open the
    // server IN pipe for READ access, so go down the
    // old path, notably opening OUT first so the
    // server knows we'll be using both pipes.  We'll
    // also come down this path on Win95 because
    // it doesn't allow you to open an overlapped
    // pipe handle.  Or if remote /c mach pipe /2 is used.
    //

    fOldServer = (ERROR_ACCESS_DENIED == dwError);

  RetrySrvOut:

    if (INVALID_HANDLE_VALUE ==
            (PipeH[0] =
                CreateFile(
                    pipenameSrvOut,
                    GENERIC_READ,
                    0,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL
                    ))) {

        if (ERROR_PIPE_BUSY == GetLastError()) {

            printf( "All OUT pipe instances busy, waiting for another...\n");

            WaitNamedPipe(
                pipenameSrvOut,
                32000              // server recycles abandoned
                );                 // OUT pipe after two minutes

            if (RetryCount++ < 6) {
                goto RetrySrvOut;
            }
        }

        HandleConnectError(server, srvpipename);
        return NULL;

    }

  RetrySrvIn:

    if (INVALID_HANDLE_VALUE ==
           (PipeH[1] =
               CreateFile(
                    pipenameSrvIn,
                    GENERIC_WRITE,
                    0,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL
                    ))) {

        dwError = GetLastError();

        if (ERROR_PIPE_BUSY == dwError) {

            printf( "All IN pipe instances busy, waiting for another...\n");

            WaitNamedPipe(
                pipenameSrvIn,
                15000
                );

            if (RetryCount++ < 6) {
                goto RetrySrvIn;
           }
        }

        HandleConnectError(server, srvpipename);
        return NULL;

    }

    fAsyncPipe = FALSE;

    printf("Connected... %s\n\n",
           fOldServer
               ? "to two-pipe remote server."
               : "using two pipes."
           );

    SendMyInfo(PipeH);

    return PipeH;
}

VOID
SendMyInfo(
    PHANDLE pipeH
    )
{
    HANDLE rPipe=pipeH[0];
    HANDLE wPipe=pipeH[1];

    DWORD  hostlen;
    WORD   BytesToSend=sizeof(SESSION_STARTUPINFO);
    DWORD  tmp;
    OVERLAPPED ol;
    SESSION_STARTUPINFO ssi;
    SESSION_STARTREPLY  ssr;

    ol.hEvent =
        CreateEvent(
            NULL,      // security
            TRUE,      // auto-reset
            FALSE,     // initially nonsignaled
            NULL       // unnamed
            );

    ssi.Size=BytesToSend;
    ssi.Version=VERSION;

    hostlen = sizeof(ssi.ClientName) / sizeof(ssi.ClientName[0]);
    GetComputerName(ssi.ClientName, &hostlen);
    ssi.LinesToSend=LinesToSend;
    ssi.Flag=ClientToServerFlag;

    {
        DWORD NewCode=MAGICNUMBER;
        char  Name[MAX_COMPUTERNAME_LENGTH+1];

        strcpy(Name,(char *)ssi.ClientName);
        memcpy(&Name[11],(char *)&NewCode,sizeof(NewCode));

        //
        // The server needs to know if we're doing single-pipe
        // operation so it can complete the connection properly.
        // So if we are, change the first byte of the first
        // send (the computername, which is later superceded
        // by the one in the SESSION_STARTUPINFO structure)
        // to an illegal computername character, question mark.
        //

        if (wPipe == rPipe) {

             Name[0] = '?';
        }

        WriteFileSynch(wPipe,(char *)Name,HOSTNAMELEN-1,&tmp,0,&ol);
        ReadFileSynch(rPipe ,(char *)&ssr.MagicNumber,sizeof(ssr.MagicNumber),&tmp,0,&ol);

        if (ssr.MagicNumber!=MAGICNUMBER)
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            ErrorExit("Pipe connected but server not recognized.\n");
        }

        //Get Rest of the info-its not the old server

        ReadFileSynch(
            rPipe,
            (char *)&ssr + sizeof(ssr.MagicNumber),
            sizeof(ssr)-sizeof(ssr.MagicNumber),
            &tmp,
            0,
            &ol
            );

    }

    if (!WriteFileSynch(wPipe,(char *)&ssi,BytesToSend,&tmp,0,&ol))
    {
       Errormsg("INFO Send Error");
    }

    CloseHandle(ol.hEvent);
}

VOID
QueryRemotePipes(
    char* pszServer
    )
{
    HANDLE hQPipe;
    DWORD  dwRead;
    DWORD  dwError;
    char   fullname[400];
    char*  msg;
    int    msgLen;

    if (pszServer[0] == '\\' && pszServer[1] == '\\') {
        pszServer += 2;
    }

    printf("Querying server \\\\%s\n", pszServer);

    sprintf(fullname, QUERY_DEBUGGERS_PIPE, pszServer);

    //
    // Send request and display the query result
    //

    hQPipe = CreateFile(fullname,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if(hQPipe == INVALID_HANDLE_VALUE) {

        dwError = GetLastError();

        if (ERROR_FILE_NOT_FOUND == dwError) {

            printf("No Remote servers running on \\\\%s\n", pszServer);

        } else if (ERROR_BAD_NETPATH == dwError) {

            printf("\\\\%s not found on the network\n", pszServer);

        } else {

            FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |
                           FORMAT_MESSAGE_IGNORE_INSERTS,
                           NULL, dwError, 0,
                           fullname, sizeof(fullname), NULL);

            printf("Can't query server %s: %s\n", pszServer, fullname);
        }

        return;
    }

    //  Send Query Command
    if(!WriteFile(hQPipe, "q", 1, &dwRead, NULL)
        || (dwRead != 1))
    {
        printf("\nError: Can't send command\n");
        goto failure;
    }

    if(!ReadFile(hQPipe,
             &msgLen,
             sizeof(int),      // read msg dimension
             &dwRead,
             NULL)
        || (dwRead != sizeof(int)))
    {
        printf("\nError: Can't read message\n");
        goto failure;
    }

    if(!msgLen)
    {
        printf("\nNo visible sessions on server %s", pszServer);
        goto failure;
    }

    if(msgLen > 65535)        // error
    {
        printf("Error querying server %s, got %d for msg length, 65535 max.\n",
               pszServer,
               msgLen
               );
        goto failure;
    }

    if((msg = (char*)malloc(msgLen*sizeof(char))) == NULL)
    {
        printf("\nOut of memory\n");
        goto failure;
    }

    ReadFile(hQPipe,
             msg,
             msgLen * sizeof(char),      // read msg
             &dwRead,
             NULL);

    printf("\nVisible sessions on server %s:\n\n", pszServer);

    printf("%s\n", msg);
    free(msg);

 failure:

    CloseHandle(hQPipe);
}


PIPEEX.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    pipeex.c

Abstract:

    CreatePipe-like function that lets one or both handles be overlapped

Author:

    Dave Hart  Summer 1997

Revision History:

--*/

#include <windows.h>
#include <stdio.h>

ULONG PipeSerialNumber;

BOOL
APIENTRY
MyCreatePipeEx(
    OUT LPHANDLE lpReadPipe,
    OUT LPHANDLE lpWritePipe,
    IN LPSECURITY_ATTRIBUTES lpPipeAttributes,
    IN DWORD nSize,
    DWORD dwReadMode,
    DWORD dwWriteMode
    )

/*++

Routine Description:

    The CreatePipeEx API is used to create an anonymous pipe I/O device.
    Unlike CreatePipe FILE_FLAG_OVERLAPPED may be specified for one or
    both handles.
    Two handles to the device are created.  One handle is opened for
    reading and the other is opened for writing.  These handles may be
    used in subsequent calls to ReadFile and WriteFile to transmit data
    through the pipe.

Arguments:

    lpReadPipe - Returns a handle to the read side of the pipe.  Data
        may be read from the pipe by specifying this handle value in a
        subsequent call to ReadFile.

    lpWritePipe - Returns a handle to the write side of the pipe.  Data
        may be written to the pipe by specifying this handle value in a
        subsequent call to WriteFile.

    lpPipeAttributes - An optional parameter that may be used to specify
        the attributes of the new pipe.  If the parameter is not
        specified, then the pipe is created without a security
        descriptor, and the resulting handles are not inherited on
        process creation.  Otherwise, the optional security attributes
        are used on the pipe, and the inherit handles flag effects both
        pipe handles.

    nSize - Supplies the requested buffer size for the pipe.  This is
        only a suggestion and is used by the operating system to
        calculate an appropriate buffering mechanism.  A value of zero
        indicates that the system is to choose the default buffering
        scheme.

Return Value:

    TRUE - The operation was successful.

    FALSE/NULL - The operation failed. Extended error status is available
        using GetLastError.

--*/

{
    HANDLE ReadPipeHandle, WritePipeHandle;
    DWORD dwError;
    UCHAR PipeNameBuffer[ MAX_PATH ];

    //
    // Only one valid OpenMode flag - FILE_FLAG_OVERLAPPED
    //

    if ((dwReadMode | dwWriteMode) & (~FILE_FLAG_OVERLAPPED)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    //
    //  Set the default timeout to 120 seconds
    //

    if (nSize == 0) {
        nSize = 4096;
        }

    sprintf( PipeNameBuffer,
             "\\\\.\\Pipe\\RemoteExeAnon.%08x.%08x",
             GetCurrentProcessId(),
             PipeSerialNumber++
           );

    ReadPipeHandle = CreateNamedPipeA(
                         PipeNameBuffer,
                         PIPE_ACCESS_INBOUND | dwReadMode,
                         PIPE_TYPE_BYTE | PIPE_WAIT,
                         1,             // Number of pipes
                         nSize,         // Out buffer size
                         nSize,         // In buffer size
                         120 * 1000,    // Timeout in ms
                         lpPipeAttributes
                         );

    if (! ReadPipeHandle) {
        return FALSE;
    }

    WritePipeHandle = CreateFileA(
                        PipeNameBuffer,
                        GENERIC_WRITE,
                        0,                         // No sharing
                        lpPipeAttributes,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL | dwWriteMode,
                        NULL                       // Template file
                      );

    if (INVALID_HANDLE_VALUE == WritePipeHandle) {
        dwError = GetLastError();
        CloseHandle( ReadPipeHandle );
        SetLastError(dwError);
        return FALSE;
    }

    *lpReadPipe = ReadPipeHandle;
    *lpWritePipe = WritePipeHandle;
    return( TRUE );
}


REMOTE.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1997 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.
\******************************************************************************/

/*++

Copyright 1993 - 1997 Microsoft Corporation

Module Name:

    Remote.c

Abstract:

    This module contains the main() entry point for Remote.
    Calls the Server or the Client depending on the first parameter.

Author:

    Rajivendra Nath  2-Jan-1993

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "Remote.h"

char   HostName[HOSTNAMELEN];
char*  ChildCmd;
char*  PipeName;
char*  ServerName;
char * DaclNames[ MAX_DACL_NAMES ];
DWORD  DaclNameCount = 0;
char * DaclDenyNames[ MAX_DACL_NAMES ];
DWORD  DaclDenyNameCount = 0 ;
HANDLE MyStdOut;
HANDLE hAttachedProcess = INVALID_HANDLE_VALUE;
HANDLE hAttachedWriteChildStdIn = INVALID_HANDLE_VALUE;
HANDLE hAttachedReadChildStdOut = INVALID_HANDLE_VALUE;

BOOL   IsAdvertise;
DWORD  ClientToServerFlag;
BOOL   bForceTwoPipes;

char* ColorList[]={"black" ,"blue" ,"green" ,"cyan" ,"red" ,"purple" ,"yellow" ,"white",
                   "lblack","lblue","lgreen","lcyan","lred","lpurple","lyellow","lwhite"};

WORD
GetColorNum(
    char* color
    );

VOID
SetColor(
    WORD attr
    );

BOOL
GetNextConnectInfo(
    char** SrvName,
    char** PipeName
    );

CONSOLE_SCREEN_BUFFER_INFO csbiOriginal;

main(
    int    argc,
    char** argv
    )
{
    WORD  RunType;              // Server or Client end of Remote
    DWORD len=HOSTNAMELEN;
    int   i, FirstArg;

    char  sTitle[120];          // New Title
    char  orgTitle[200];        // Old Title
    BOOL  bPromptForArgs=FALSE; // Is /P option
    WORD  wAttrib;              // Console Attributes
    int   privacy;              // Allows exposing or hidng sessions to remote /q
    BOOL  Deny ;

    GetComputerName((LPTSTR)HostName,&len);

    MyStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

    if (GetConsoleScreenBufferInfo(MyStdOut,&csbiOriginal)) {

        wAttrib = csbiOriginal.wAttributes;
        if (!GetConsoleTitle(orgTitle,sizeof(orgTitle))) {
            orgTitle[0] = 0;
        }

    } else {

        //
        // either stdout is a pipe, or it wasn't opened for
        // GENERIC_READ along with GENERIC_WRITE, in which
        // case our color manipulations will work so we need
        // to pick default colors.
        //

        wAttrib = FOREGROUND_GREEN |
                  FOREGROUND_INTENSITY;

        orgTitle[0] = 0;
    }

    privacy = PRIVACY_DEFAULT;

    //
    // Parameter Processing
    //
    // For Server:
    // Remote /S <Executable>  <PipeName> [Optional Params]
    //
    // For Client:
    // Remote /C <Server Name> <PipeName> [Optional Params]
    // or
    // Remote /P
    // This will loop continously prompting for different
    // Servers and Pipename

    if ((argc<2)||((argv[1][0]!='/')&&(argv[1][0]!='-')))
    {

        DisplayServerHlp();
        DisplayClientHlp();
        return(1);
    }

    switch(argv[1][1])
    {
    case 'c':
    case 'C':

        //
        // Is Client End of Remote
        //

        if ((argc<4)||((argv[1][0]!='/')&&(argv[1][0]!='-')))
        {

            DisplayServerHlp();
            DisplayClientHlp();
            return(1);
        }

        ServerName=argv[2];
        PipeName=argv[3];
        FirstArg=4;
        RunType=RUNTYPE_CLIENT;
        break;

    case 'q':
    case 'Q':

        //
        //  Query for possible conexions
        //

        if ((argc != 3)||((argv[1][0]!='/')&&(argv[1][0]!='-')))
        {

            DisplayServerHlp();
            DisplayClientHlp();
            return(1);
        }

        QueryRemotePipes(argv[2]);  //  Send ServerName as a param
        return(0);

    case 'p':
    case 'P':

        //
        // Is Client End of Remote
        //

        bPromptForArgs=TRUE;
        RunType=RUNTYPE_CLIENT;
        FirstArg=2;
        break;

    case 's':
    case 'S':
        //
        // Is Server End of Remote
        //
        if ((argc<4)||((argv[1][0]!='/')&&(argv[1][0]!='-')))
        {

            DisplayServerHlp();
            DisplayClientHlp();
            return(1);
        }

        ChildCmd=argv[2];
        PipeName=argv[3];
        FirstArg=4;

        RunType=REMOTE_SERVER;
        break;

    case 'a':
    case 'A':
        //
        // Is Server End of Remote Attaching to existing process.
        //
        if ((argc<7)||((argv[1][0]!='/')&&(argv[1][0]!='-')))
        {

            DisplayServerHlp();
            DisplayClientHlp();
            return(1);
        }

        hAttachedProcess = (HANDLE) atoi(argv[2]);
        hAttachedWriteChildStdIn = (HANDLE) atoi(argv[3]);
        hAttachedReadChildStdOut = (HANDLE) atoi(argv[4]);
        ChildCmd=argv[5]; // for display only
        PipeName=argv[6];
        FirstArg=7;

        RunType = REMOTE_SERVER;
        privacy = PRIVACY_VISIBLE;  // presumably ntsd/*kd
        break;

    default:
        DisplayServerHlp();
        DisplayClientHlp();
        return(1);
    }

    if (RunType==REMOTE_SERVER)
    {
        //
        // Base Name of Executable
        // For setting the title
        //

        char *tcmd=ChildCmd;

        while ((*tcmd!=' ')      && (*tcmd!=0))    tcmd++;
        while ((tcmd > ChildCmd) && (*tcmd!='\\')) tcmd--;
        if (*tcmd=='\\') tcmd++;
        sprintf(sTitle,"%-41.40s [Remote /C %s %.30s]",tcmd,HostName,PipeName);
    }

    //
    //Process Common (Optional) Parameters
    //

    for (i=FirstArg;i<argc;i++)
    {

        if ((argv[i][0]!='/')&&(argv[i][0]!='-'))
        {
            printf("Invalid parameter %s:Ignoring\n",argv[i]);
            continue;
        }

        switch(argv[i][1])
        {
        case 'l':    // Only Valid for client End
        case 'L':    // Max Number of Lines to recieve from Server
            i++;
            if (i>=argc)
            {
                printf("Incomplete Param %s..Ignoring\n",argv[i-1]);
                break;
            }
            LinesToSend=(DWORD)atoi(argv[i])+1;
            break;

        case 't':    // Title to be set instead of the default
        case 'T':
            i++;
            if (i>=argc)
            {
                printf("Incomplete Param %s..Ignoring\n",argv[i-1]);
                break;
            }
            sprintf(sTitle,"%s",argv[i]);
            break;

        case 'b':    // Background color
        case 'B':
            i++;
            if (i>=argc)
            {
                printf("Incomplete Param %s..Ignoring\n",argv[i-1]);
                break;
            }
            {
                WORD col=GetColorNum(argv[i]);
                if (col!=0xffff)
                {
                    wAttrib=col<<4|(wAttrib&0x000f);
                }
                break;
            }

        case 'f':    // Foreground color
        case 'F':
            i++;
            if (i>=argc)
            {
                printf("Incomplete Param %s..Ignoring\n",argv[i-1]);
                break;
            }
            {
                WORD col=GetColorNum(argv[i]);
                if (col!=0xffff)
                {
                    wAttrib=col|(wAttrib&0x00f0);
                }
                break;
            }

        case 'v':
        case 'V':
            privacy = PRIVACY_VISIBLE;
            break;

        case '-':
            if( (argv[i][2] == 'v')
                || (argv[i][2] == 'V'))
                privacy = PRIVACY_NOT_VISIBLE;
            else
                printf("Unknown Parameter=%s %s\n",argv[i-1],argv[i]);
            break;

        case 'q':
        case 'Q':
            ClientToServerFlag|=0x80000000;
            break;

        case 'u':
        case 'U':
            if ( (argv[i][2] == 'd') ||
                 (argv[i][2] == 'D' ) )
            {
                Deny = TRUE ;
            }
            else
            {
                Deny = FALSE ;
            }

            i++ ;

            if ( i >= argc )
            {
                printf( "Incomplete Param %s..Ignoring\n", argv[i-1] );
                break;
            }

            if ( Deny )
            {
                if (DaclDenyNameCount == MAX_DACL_NAMES )
                {
                    printf("Too many names specified (max %d).  Ignoring user %s\n",
                            MAX_DACL_NAMES, argv[i] );

                    break;
                }

                DaclDenyNames[ DaclDenyNameCount++ ] = argv[i];

            }
            else
            {
                if (DaclNameCount == MAX_DACL_NAMES )
                {
                    printf("Too many names specified (max %d).  Ignoring user %s\n",
                            MAX_DACL_NAMES, argv[i] );

                    break;
                }

                DaclNames[ DaclNameCount++ ] = argv[i];

            }

            break;

        case '2':
            bForceTwoPipes = TRUE;
            break;

        default:
            printf("Unknown Parameter=%s %s\n",argv[i-1],argv[i]);
            break;

        }

    }

    //
    //Now Set various Parameters
    //

    //
    //Colors
    //

    SetColor(wAttrib);

    if (RunType==RUNTYPE_CLIENT)
    {
        BOOL done=FALSE;
        BOOL gotinfo;

        //
        // Set Client end defaults and start client
        //

        while(!done)
        {
            if (!bPromptForArgs ||
                (gotinfo = GetNextConnectInfo(&ServerName,&PipeName))
               )
            {
                sprintf(sTitle,"Remote /C %s %s",ServerName,PipeName);
                SetConsoleTitle(sTitle);

                //
                // Start Client (Client.C)
                //
                Client(ServerName,PipeName);
            }
            done = !bPromptForArgs || !gotinfo;
        }
    }

    if (RunType==REMOTE_SERVER)
    {
        if (privacy == PRIVACY_VISIBLE ||
             (privacy == PRIVACY_DEFAULT && IsKdString(ChildCmd))) {

            strcat(sTitle, " visible");
            IsAdvertise = TRUE;
        }

        SetConsoleTitle(sTitle);

        i = OverlappedServer(ChildCmd, PipeName);
    }

    //
    //Reset Colors
    //
    SetColor(csbiOriginal.wAttributes);
    SetConsoleTitle(orgTitle);

    return i;
}

/*************************************************************/
VOID
ErrorExit(
    char* str
    )
{
    extern PSZ pszPipeName;
    DWORD dwErr;

    dwErr = GetLastError();

    printf("REMOTE error %d: %s\n", dwErr, str);

    #if DBG
    {
        char szMsg[1024];

        sprintf(szMsg, "REMOTE error %d: %s\n", dwErr, str);
        OutputDebugString(szMsg);

        if (pszPipeName) {               // ad-hoc:  if server
            if (IsDebuggerPresent()) {
                DebugBreak();
            }
        }
    }
    #endif

    exit(1);
}

/*************************************************************/
VOID
DisplayClientHlp()
{
    printf("\n"
           "   To Start the CLIENT end of REMOTE\n"
           "   ---------------------------------\n"
           "   Syntax : REMOTE /C <ServerName> <Unique Id> [Param]\n"
           "   Example: REMOTE /C %s imbroglio\n"
           "            This would connect to a server session on %s with Id\n"
           "            \"imbroglio\" if there is a REMOTE /S <\"Cmd\"> imbroglio\n"
           "            running on %s.\n\n"
           "   To Exit: %cQ (Leaves the Remote Server Running)\n"
           "   [Param]: /L <# of Lines to Get>\n"
           "   [Param]: /F <Foreground color eg blue, lred..>\n"
           "   [Param]: /B <Background color eg cyan, lwhite..>\n"
           "\n"
           "   To Query the visible sessions on a server\n"
           "   -----------------------------------------\n"
           "   Syntax:  REMOTE /Q %s\n"
           "            This would retrieve the available <Unique Id>s\n"
           "            visible connections on the computer named %s.\n"
           "\n",
           HostName, HostName, HostName, COMMANDCHAR, HostName, HostName);
}
/*************************************************************/

VOID
DisplayServerHlp()
{
    printf("\n"
           "   To Start the SERVER end of REMOTE\n"
           "   ---------------------------------\n"
           "   Syntax : REMOTE /S <\"Cmd\">     <Unique Id> [Param]\n"
           "   Example: REMOTE /S \"i386kd -v\" imbroglio\n"
           "            To interact with this \"Cmd\" from some other machine,\n"
           "            start the client end using:  REMOTE /C %s imbroglio\n\n"
           "   To Exit: %cK \n"
           "   [Param]: /F  <Foreground color eg yellow, black..>\n"
           "   [Param]: /B  <Background color eg lblue, white..>\n"
           "   [Param]: /U  username or groupname\n"
           "                specifies which users or groups may connect\n"
           "                may be specified more than once, e.g\n"
           "                /U user1 /U group2 /U user2\n"
           "   [Param]: /UD username or groupname\n"
           "                specifically denies access to that user or group\n"
           "   [Param]: /V  Makes this session visible to remote /Q\n"
           "   [Param]: /-V Hides this session from remote /q (invisible)\n"
           "                By default, if \"Cmd\" looks like a debugger,\n"
           "                the session is visible, otherwise not\n"
           "\n",
           HostName, COMMANDCHAR);
}

WORD
GetColorNum(
    char *color
    )
{
    int i;

    _strlwr(color);
    for (i=0;i<16;i++)
    {
        if (strcmp(ColorList[i],color)==0)
        {
            return(i);
        }
    }
    return ((WORD)atoi(color));
}

VOID
SetColor(
    WORD attr
    )
{
    COORD  origin={0,0};
    DWORD  dwrite;
    FillConsoleOutputAttribute
    (
        MyStdOut,attr,csbiOriginal.dwSize.
        X*csbiOriginal.dwSize.Y,origin,&dwrite
    );
    SetConsoleTextAttribute(MyStdOut,attr);
}

BOOL
GetNextConnectInfo(
    char** SrvName,
    char** PipeName
    )
{
    char *s;
    static char szServerName[64];
    static char szPipeName[32];

    try
    {
        ZeroMemory(szServerName,64);
        ZeroMemory(szPipeName,32);
        SetConsoleTitle("Remote - Prompting for next Connection");
        printf("Debugger machine (server): ");
        fflush(stdout);

        if (((*SrvName=gets(szServerName))==NULL)||
             (strlen(szServerName)==0))
        {
            return(FALSE);
        }

        if (szServerName[0] == COMMANDCHAR &&
            (szServerName[1] == 'q' || szServerName[1] == 'Q')
           )
        {
            return(FALSE);
        }

        if (s = strchr( szServerName, ' ' )) {
            *s++ = '\0';
            while (*s == ' ') {
                s += 1;
            }
            *PipeName=strcpy(szPipeName, s);
            printf(szPipeName);
            fflush(stdout);
        }
        if (strlen(szPipeName) == 0) {
            printf("Target machine (pipe)    : ");
            fflush(stdout);
            if ((*PipeName=gets(szPipeName))==NULL)
            {
                return(FALSE);
            }
        }

        if (s = strchr(szPipeName, ' ')) {
            *s++ = '\0';
        }

        if (szPipeName[0] == COMMANDCHAR &&
            (szPipeName[1] == 'q' || szPipeName[1] == 'Q')
           )
        {
            return(FALSE);
        }
        printf("\n\n");
    }

    except(EXCEPTION_EXECUTE_HANDLER)
    {
        return(FALSE);  // Ignore exceptions
    }
    return(TRUE);
}

/*************************************************************/

VOID
Errormsg(
    char* str
    )
{
    printf("Error (%d) - %s\n",GetLastError(),str);
}

/*************************************************************/

BOOL
IsKdString(
    char* string
    )
{

    char* start;

    //
    // some heuristic for uninvented yet platforms
    // if the first word has "kd" in it ok
    //

    if(    ((start = strstr(string, "kd")) != NULL)
        || ((start = strstr(string, "dbg")) != NULL)
        || ((start = strstr(string, "remoteds")) != NULL)
        || ((start = strstr(string, "ntsd")) != NULL)
        || ((start = strstr(string, "cdb")) != NULL) )
    {
        // is it in the first word?
        while(--start > string)
        {
            if((*start == ' ') || (*start == '\t'))
            {
                while(--start > string)
                    if((*start != '\t') || (*start != ' '))
                        return(FALSE);
            }
        }
        return TRUE;
    }
    return(FALSE);
}

//
// WriteFileSynch is a synchronous WriteFile for overlapped
// file handles.  As a special case, two-pipe client operation
// sets fAsyncPipe FALSE and this routine then passes NULL
// for lpOverlapped.
//

BOOL
FASTCALL
WriteFileSynch(
    HANDLE  hFile,
    LPVOID  lpBuffer,
    DWORD   cbWrite,
    LPDWORD lpNumberOfBytesWritten,
    DWORD   dwFileOffset,
    LPOVERLAPPED lpO
    )
{
    BOOL Success;

    lpO->OffsetHigh = 0;
    lpO->Offset = dwFileOffset;

    Success =
        WriteFile(
            hFile,
            lpBuffer,
            cbWrite,
            lpNumberOfBytesWritten,
            fAsyncPipe ? lpO : NULL
            );

    if ( ! Success ) {

        if (ERROR_IO_PENDING == GetLastError()) {

            Success =
                GetOverlappedResult(
                    hFile,
                    lpO,
                    lpNumberOfBytesWritten,
                    TRUE
                    );
        }
    }

    return Success;
}

BOOL
FASTCALL
ReadFileSynch(
    HANDLE  hFile,
    LPVOID  lpBuffer,
    DWORD   cbRead,
    LPDWORD lpNumberOfBytesRead,
    DWORD   dwFileOffset,
    LPOVERLAPPED lpO
    )
{
    BOOL Success;

    lpO->OffsetHigh = 0;
    lpO->Offset = dwFileOffset;

    Success =
        ReadFile(
            hFile,
            lpBuffer,
            cbRead,
            lpNumberOfBytesRead,
            fAsyncPipe ? lpO : NULL
            );

    if ( ! Success ) {

        if (ERROR_IO_PENDING == GetLastError()) {

            Success =
                GetOverlappedResult(
                    hFile,
                    lpO,
                    lpNumberOfBytesRead,
                    TRUE
                    );
        }
    }

    return Success;
}


REMOTE.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1993 - 1997 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.
\******************************************************************************/

/*++

Copyright 1993 - 1997 Microsoft Corporation

Module Name:

    Remote.h

Abstract:

    This module contains the main() entry point for Remote.
    Calls the Server or the Client depending on the first parameter.

Author:

    Rajivendra Nath  2-Jan-1993

Environment:

    Console App. User mode.

Revision History:

--*/

#if !defined(FASTCALL)
#if defined(_M_IX86)
#define FASTCALL _fastcall
#else
#define FASTCALL
#endif
#endif

#define VERSION         4
#define REMOTE_SERVER       1
#define RUNTYPE_CLIENT      2

#define SERVER_READ_PIPE    "\\\\%s\\PIPE\\%sIN"   //Client Writes and Server Reads
#define SERVER_WRITE_PIPE   "\\\\%s\\PIPE\\%sOUT"  //Server Writes and Client Reads

#define QUERY_DEBUGGERS_PIPE "\\\\%s\\PIPE\\QueryDebuggerPipe"

// PRIVACY_DEFAULT:     this session will be listed only if it looks like a debugging one
// PRIVACY_NON_VISIBLE: whatever the name of command, it will not show up with remote /q
// PRIVACY_VISIBLE:     this session will be visible for querying

#define PRIVACY_DEFAULT       1
#define PRIVACY_VISIBLE       2
#define PRIVACY_NOT_VISIBLE   3

#define COMMANDCHAR         '@' //Commands intended for remote begins with this
#define CTRLC               3

#define CLIENT_ATTR         FOREGROUND_INTENSITY|FOREGROUND_GREEN|FOREGROUND_RED|BACKGROUND_BLUE
#define SERVER_ATTR         FOREGROUND_INTENSITY|FOREGROUND_GREEN|FOREGROUND_BLUE|BACKGROUND_RED

//
//Some General purpose Macros
//
#define MINIMUM(x,y)          ((x)>(y)?(y):(x))
#define MAXIMUM(x,y)          ((x)>(y)?(x):(y))

#define HOSTNAMELEN         MAX_COMPUTERNAME_LENGTH+1

#define CHARS_PER_LINE      45

#define MAGICNUMBER     0x31109000
#define BEGINMARK       '\xfe'
#define ENDMARK         '\xff'
#define LINESTOSEND     200

#define MAX_DACL_NAMES  64

typedef struct
{
    DWORD    Size;
    DWORD    Version;
    char     ClientName[HOSTNAMELEN];
    DWORD    LinesToSend;
    DWORD    Flag;
}   SESSION_STARTUPINFO;

typedef struct
{
    DWORD MagicNumber;      //New Remote
    DWORD Size;             //Size of structure
    DWORD FileSize;         //Num bytes sent
}   SESSION_STARTREPLY;

typedef struct
{
    char* out;              // message
    int  size;              // message length
    int  allocated;         // length of allocated memory
} QUERY_MESSAGE;

VOID
QueryRemotePipes(
    char* serverName
    );

int
OverlappedServer(
    char* ChildCmd,
    char* PipeName
    );

VOID
Client(
    char* ServerName,
    char* PipeName
    );

VOID
ErrorExit(
    char* str
    );

VOID
DisplayClientHlp(
    );

VOID
DisplayServerHlp(
    );

VOID
Errormsg(
    char* str
    );

BOOL
IsKdString(
    char* string
    );

BOOL
FASTCALL
WriteFileSynch(
    HANDLE  hFile,
    LPVOID  lpBuffer,
    DWORD   cbWrite,
    LPDWORD lpNumberOfBytesWritten,
    DWORD   dwFileOffset,
    LPOVERLAPPED lpO
    );

BOOL
FASTCALL
ReadFileSynch(
    HANDLE  hFile,
    LPVOID  lpBuffer,
    DWORD   cbRead,
    LPDWORD lpNumberOfBytesRead,
    DWORD   dwFileOffset,
    LPOVERLAPPED lpO
    );

VOID
CloseClientPipes(
    VOID
    );

extern char   HostName[HOSTNAMELEN];
extern char*  ChildCmd;
extern char*  PipeName;
extern char*  ServerName;
extern HANDLE MyOutHandle;
extern DWORD  LinesToSend;
extern BOOL   IsAdvertise;
extern DWORD  ClientToServerFlag;
extern char * DaclNames[];
extern DWORD  DaclNameCount;
extern char * DaclDenyNames[];
extern DWORD  DaclDenyNameCount;
extern BOOL   fAsyncPipe;
extern HANDLE hAttachedProcess;
extern HANDLE hAttachedWriteChildStdIn;
extern HANDLE hAttachedReadChildStdOut;


REMOTEDS.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

//
// remoteds.c, a "directory service" for the limited job of
// finding remote.exe servers on the same domain/workgroup.
//
// Dave Hart written summer 1997.
//
// Copyright 1997 Microsoft Corp.
//
//
// A handy way to use this program is under remote on a single
// or a few machines:
//
//    remote /s remoteds FindRemote
//
// Clients connect with remote /c machinename FindRemote
//
// Only remote.exe's running debuggers or with /V+ are visible
// via remoteds, as with remote /q.
//
// Remote clients notify remoteds using mailslots, see srvad.c.
//
//

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <process.h>

typedef char RECEIVEBUF[1024];

typedef struct tagSERVERENTRY {
    int     nPID;                   // zero PID means unused slot
    union {
        FILETIME FileTime;
        LARGE_INTEGER liTime;
    };
    char   *pszMachine;
    char   *pszPipe;
    char   *pszChildCmd;
} SERVERENTRY;

#define TABLE_INITIAL_ALLOC 1 // 128       // beginning table size
#define TABLE_ALLOC_DELTA   1 // 16        // grows by this many units

HANDLE       hTableHeap;
SERVERENTRY *Table;
int          nTableSize;
int          nTableHiWater;          // highest used slot so far
CRITICAL_SECTION csTable;

char szPrompt[] = "remote server search> ";

unsigned WINAPI     InteractThread(void * UnusedParm);
unsigned WINAPI     CleanupThread(void * UnusedParm);
VOID     __fastcall UpdateTimeStamp(LPFILETIME lpFileTime);
VOID     __fastcall ReallocTable(int nNewTableSize);

int
main(
    int argc,
    char **argv
    )
{
    char *      pszMailslot = "\\\\.\\MAILSLOT\\REMOTE\\DEBUGGERS";
    HANDLE      hMailslot;
    BOOL        b;
    HANDLE      hThread;
    DWORD       dwTID;
    char *      pszMachine;
    int         cchMachine;
    char *      pszPID;
    int         nPID;
    char *      pszPipe;
    int         cchPipe;
    char *      pszChildCmd;
    int         i;
    int         nFirstAvailable;
    BOOL        fStopping;
    BOOL        fFound;
    int         cb;
    char *      pchStrings;
    char *      pch;
    DWORD       cbRead;
    DWORD       iBuf;
    DWORD       rgcbBuf[2];
    RECEIVEBUF  rgBuf[2];
    RECEIVEBUF  szBuf;
    char        szRemoteCmd[512];

    InitializeCriticalSection(&csTable);

    ReallocTable(TABLE_INITIAL_ALLOC);

    hMailslot =
        CreateMailslot(
            pszMailslot,
            0,
            MAILSLOT_WAIT_FOREVER,
            NULL
            );

    if (INVALID_HANDLE_VALUE == hMailslot) {

        DWORD dwErr = GetLastError();

        if (ERROR_ALREADY_EXISTS == dwErr) {
            printf("Cannot receive on %s,\n"
                   "is remoteds or rdsrelay already running on this machine?\n",
                   pszMailslot);
        } else {
            printf("CreateMailslot(%s) failed error %d\n",
                    pszMailslot,
                    dwErr);
        }
        return 2;
    }

    hThread = (HANDLE) _beginthreadex(
                                      NULL,
                                      0,
                                      InteractThread,
                                      NULL,
                                      0,
                                      &dwTID
                                     );

    if ( ! hThread) {
        printf("Can't start InteractThread %d\n", GetLastError());
        return 3;
    }

    CloseHandle(hThread);

    hThread = (HANDLE) _beginthreadex(
                                      NULL,
                                      0,
                                      CleanupThread,
                                      NULL,
                                      0,
                                      &dwTID
                                     );

    if ( ! hThread) {
        printf("Can't start CleanupThread %d\n", GetLastError());
        return 3;
    }

    CloseHandle(hThread);

    //
    // loop reading and processing mailslot messsages
    //

    iBuf = 0;
    ZeroMemory(rgcbBuf, sizeof(rgcbBuf));
    ZeroMemory(rgBuf, sizeof(rgBuf));

    while(TRUE)
    {
        b = ReadFile(
                hMailslot,
                rgBuf[ iBuf ],
                sizeof(rgBuf[ iBuf ]) - 1,  // so I can null terminate if needed
                &rgcbBuf[ iBuf ],
                NULL
                );

        if ( ! b) {
            printf("ReadFile(hMailslot) failed error %d\n", GetLastError());
            return 4;
        }

        //
        // It's the nature of mailslots and multiple transports
        // that we'll get the identical message several times in
        // quick succession.  Don't waste time searching the table
        // for these duplicates.
        //

        if ( rgcbBuf[0] == rgcbBuf[1] &&
             ! memcmp(rgBuf[0], rgBuf[1], rgcbBuf[0])) {

            continue;               // duplicate
        }

        //
        // Make a working copy into szBuf/cbRead that we can
        // modify so the original buffer is available for
        // detecting received duplicates.
        //

        cbRead = rgcbBuf[ iBuf ];
        CopyMemory(szBuf, rgBuf[ iBuf ], cbRead);

        //
        // Toggle buffers for the next read.
        //

        iBuf = !iBuf;

        if (szBuf[ cbRead - 1 ]) {
            printf("Received string not null terminated.\n");
            szBuf[cbRead] = 0;
        }

        pszMachine = szBuf;

        pch = strchr(szBuf, '\t');

        if (!pch) {
            printf("Received string no 1st tab\n");
            continue;
        }
        *pch = '\0';

        pszPID = ++pch;

        pch = strchr(pch, '\t');

        if (!pch) {
            printf("Received string no 2nd tab\n");
            continue;
        }
        *pch = '\0';

        pszPipe = ++pch;

        pch = strchr(pch, '\t');

        if (!pch) {
            printf("Received string no 3nd tab\n");
            continue;
        }
        *pch = '\0';

        pszChildCmd = ++pch;

        //
        // If it ends with ^B it's going away.
        //

        pch = strchr(pch, '\x2');

        if (pch) {
            *pch = 0;
            fStopping = TRUE;
        } else {
            fStopping = FALSE;
        }

        nPID = strtol(pszPID, NULL, 10);
        _strlwr(pszMachine);
        _strlwr(pszPipe);

        if (fStopping) {

            //
            // display the ending remote's info
            //

            sprintf(szRemoteCmd, "remote /c %s %s", pszMachine, pszPipe);
            printf("\r%-36s %-20s   [stop]\n%s", szRemoteCmd, pszChildCmd, szPrompt);
            fflush(stdout);
        }

        EnterCriticalSection(&csTable);

        nFirstAvailable = -1;

        for (i = 0, fFound = FALSE;
             i <= nTableHiWater;
             i++) {

            if (-1 == nFirstAvailable && 0 == Table[i].nPID) {
                nFirstAvailable = i;
            }

            if (Table[i].nPID == nPID &&
                ! strcmp(Table[i].pszMachine, pszMachine) &&
                ! strcmp(Table[i].pszPipe, pszPipe)) {

                fFound = TRUE;
                break;
            }
        }

        if (fFound) {

            if (fStopping) {

                //
                // Remove it from the table
                //

                free(Table[i].pszMachine);
                ZeroMemory(&Table[i], sizeof(Table[i]));

                if (nTableHiWater == i) {
                    nTableHiWater--;
                }

            } else { // starting

                // printf("Found at slot %d\n", i);
                // timestamp is updated below
            }

        } else if ( ! fStopping) {

            //
            // we have a new entry, display it
            //

            sprintf(szRemoteCmd, "remote /c %s %s", pszMachine, pszPipe);
            printf("\r%-36s %-20s   [start]\n%s", szRemoteCmd, pszChildCmd, szPrompt);
            fflush(stdout);

            //
            // Does it fit in the table or do we need to grow it?
            //

            if (-1 == nFirstAvailable) {

                if (++nTableHiWater >= nTableSize) {
                    ReallocTable(nTableSize + TABLE_ALLOC_DELTA);
                }

                i = nTableHiWater;

            } else {

                i = nFirstAvailable;
            }

            //
            // Fill in a server entry in table, if we can
            // allocate memory for the strings.
            //

            cb = (cchMachine  = strlen(pszMachine) + 1) +
                 (cchPipe     = strlen(pszPipe) + 1) +
                 (              strlen(pszChildCmd) + 1);

            pchStrings = malloc(cb);

            if (pchStrings) {

                Table[i].nPID = nPID;
                UpdateTimeStamp(&Table[i].FileTime);

                Table[i].pszMachine = pchStrings;
                strcpy(Table[i].pszMachine, pszMachine);

                Table[i].pszPipe = Table[i].pszMachine + cchMachine;
                strcpy(Table[i].pszPipe, pszPipe);

                Table[i].pszChildCmd = Table[i].pszPipe + cchPipe;
                strcpy(Table[i].pszChildCmd, pszChildCmd);
            }

        }

        UpdateTimeStamp(&Table[i].FileTime);

        LeaveCriticalSection(&csTable);

    }   // while (TRUE)

    return 0;    // never executed
}

//
// InteractThread lets the user query the list of remote servers.
//

unsigned WINAPI InteractThread(void * UnusedParm)
{
    char szQuery[1024];
    char szLowerQuery[1024];
    char szRemoteCmd[400];
    int  i;
    BOOL fAll;

 Help:
    printf("Enter a string to search for, a machine or pipe name or command.\n");
    printf("Enter * to list all remote servers.\n");
    printf("Exit with ^B.\n");

    while (TRUE) {

        fputs(szPrompt, stdout);
        fflush(stdout);
        gets(szQuery);
        _strlwr( strcpy(szLowerQuery, szQuery) );

        if (!strlen(szLowerQuery) ||
            !strcmp(szLowerQuery, "?") ||
            !strcmp(szLowerQuery, "h") ||
            !strcmp(szLowerQuery, "help")) {

            goto Help;
        }

        if (2 == szLowerQuery[0]) {           // ^B

            ExitProcess(0);
        }

        fAll = ! strcmp(szLowerQuery, "*");

        EnterCriticalSection(&csTable);

        for (i = 0; i <= nTableHiWater; i++) {
            if (Table[i].nPID) {
                if (fAll ||
                    strstr(Table[i].pszMachine, szLowerQuery) ||
                    strstr(Table[i].pszPipe, szLowerQuery) ||
                    strstr(Table[i].pszChildCmd, szLowerQuery)) {

                    sprintf(szRemoteCmd, "remote /c %s %s", Table[i].pszMachine, Table[i].pszPipe);
                    printf("%-40s %s\n", szRemoteCmd, Table[i].pszChildCmd);
                }
            }
        }

        LeaveCriticalSection(&csTable);

    }

    return 0;    // never executed
}

//
// CleanupThread scavenges for old entries and frees them.
// remote /s sends a broadcast at least every 2 hours.
// We get some of them.  Age out entries after 12 hours.
//

unsigned WINAPI CleanupThread(void * UnusedParm)
{
    LARGE_INTEGER liNow;
    LARGE_INTEGER liTimeout;
    int i;
    char szRemoteCmd[400];

    liTimeout.QuadPart = (LONGLONG)10000000 * 60 * 60 * 12;  // 12 hours

    while (TRUE) {

        Sleep(15 * 60 * 1000);    // 10 minutes

        UpdateTimeStamp((LPFILETIME)&liNow);

        EnterCriticalSection(&csTable);

        for (i = nTableHiWater; i >= 0; i--) {

            if (Table[i].nPID) {

                if (liNow.QuadPart - Table[i].liTime.QuadPart > liTimeout.QuadPart) {

                    //
                    // display the ending remote's info
                    //

                    sprintf(szRemoteCmd, "remote /c %s %s", Table[i].pszMachine, Table[i].pszPipe);
                    printf("\r%-36s %-20s   [aged out]\n%s", szRemoteCmd, Table[i].pszChildCmd, szPrompt);
                    fflush(stdout);

                    free(Table[i].pszMachine);
                    ZeroMemory(&Table[i], sizeof(Table[i]));

                    if (nTableHiWater == i) {
                        nTableHiWater--;
                    }
                }

            }

        }

        LeaveCriticalSection(&csTable);
    }

    return 0;    // never executed
}

VOID __fastcall UpdateTimeStamp(LPFILETIME lpFileTime)
{
    SYSTEMTIME SystemTime;

    GetSystemTime(&SystemTime);
    SystemTimeToFileTime(&SystemTime, lpFileTime);
}

VOID __fastcall ReallocTable(int nNewTableSize)
{
    SERVERENTRY *pTableSave = Table;

    EnterCriticalSection(&csTable);

    nTableSize = nNewTableSize;

    if ( ! hTableHeap) {

        hTableHeap = HeapCreate(
                         HEAP_NO_SERIALIZE,
                         (TABLE_INITIAL_ALLOC + 1) * sizeof(Table[0]),  // size
                         50000 * sizeof(Table[0])                       // max
                         );
        Table = HeapAlloc(
                    hTableHeap,
                    HEAP_ZERO_MEMORY,
                    nTableSize * sizeof(Table[0])
                    );
    } else {

        Table = HeapReAlloc(
                    hTableHeap,
                    HEAP_ZERO_MEMORY,
                    Table,
                    nTableSize * sizeof(Table[0])
                    );
    }

    if (!Table) {
        printf("\nremoteds: Out of memory allocating remote server table\n");
        exit(ERROR_NOT_ENOUGH_MEMORY);
    }

    LeaveCriticalSection(&csTable);

    if (Table != pTableSave && pTableSave) {
        printf("\nremoteds:  remote server table moved in HeapRealloc from %x to %x.\n", pTableSave, Table);
        fflush(stdout);
    }
}


SERVER.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    Server.h

Abstract:

    The server component of Remote, rewritten using
    ReadFileEx/WriteFileEx completion routines.

Author:

    Dave Hart  30 May 1997

Environment:

    Console App. User mode.

Revision History:

--*/

#include <lm.h>                // needed for NET_API_STATUS below

#if !defined(SERVER_H_NOEXTERN)
#define SRVEXTERN extern
#else
#define SRVEXTERN
#endif

#if DBG
  DWORD Trace;         // bits set in here trigger trace printfs

  #define TR_SESSION            (0x01)
  #define TR_CHILD              (0x02)
  #define TR_SHAKE              (0x04)
  #define TR_CONNECT            (0x08)
  #define TR_QUERY              (0x10)
  #define TR_COPYPIPE           (0x20)
#endif

#if DBG
  #define TRACE(tracebit, printfargs)                        \
              ((Trace & (TR_##tracebit)                      \
                   ? (printf printfargs, fflush(stdout), 0)  \
                   : 0))
#else
  #define TRACE(tracebit, printfargs)    (0)
#endif

#if defined(ASSERT)
#undef ASSERT
#endif

#if DBG
  #define ASSERT(exp)  ((exp) || (ErrorExit("Assertion failed in " __FILE__ ": " #exp ),0))
#else
  #define ASSERT(exp)  (0)
#endif

//
// Size of transfer buffers
//

#define BUFFSIZE      (4 * 1024)

//
// ServerFlags bit values in REMOTE_CLIENT below
//

#define SFLG_CLOSING               0x01
#define SFLG_HANDSHAKING           0x02
#define SFLG_READINGCOMMAND        0x04
#define SFLG_LOCAL                 0x08

#define SFLG_VALID                 \
            (SFLG_CLOSING        | \
             SFLG_HANDSHAKING    | \
             SFLG_READINGCOMMAND | \
             SFLG_LOCAL)

//
// Per-client state
//

typedef struct tagREMOTE_CLIENT {
    LIST_ENTRY Links;
    DWORD   dwID;           // 1, 2, ...
    DWORD   ServerFlags;
    DWORD   Flag;           //from Client's ClientToServerFlag
    DWORD   cbWrite;        //zero if no read temp/write client ops pending
    HANDLE  PipeReadH;      //Client sends its StdIn  through this
    HANDLE  PipeWriteH;     //Client gets  its StdOut through this
    DWORD   dwFilePos;      //offset of temp file where next read begins
    OVERLAPPED ReadOverlapped;
    OVERLAPPED WriteOverlapped;
    HANDLE  rSaveFile;      //Sessions read handle to SaveFile
    DWORD   cbReadTempBuffer;
    DWORD   cbWriteBuffer;
    DWORD   cbCommandBuffer;
    char    HexAsciiId[8];         // dwID as 8 hex chars -- no terminator
    char    Name[HOSTNAMELEN];     //Name of client Machine;
    char    UserName[16];          //Name of user on client machine.
    BYTE    ReadBuffer[BUFFSIZE];
    BYTE    ReadTempBuffer[BUFFSIZE];
    BYTE    WriteBuffer[BUFFSIZE];
    BYTE    CommandBuffer[BUFFSIZE];
} REMOTE_CLIENT, *PREMOTE_CLIENT;

//
// Client lists, see srvlist.c
//

SRVEXTERN LIST_ENTRY       HandshakingListHead;
SRVEXTERN CRITICAL_SECTION csHandshakingList;

SRVEXTERN LIST_ENTRY       ClientListHead;
SRVEXTERN CRITICAL_SECTION csClientList;

SRVEXTERN LIST_ENTRY       ClosingClientListHead;
SRVEXTERN CRITICAL_SECTION csClosingClientList;

SRVEXTERN DWORD   dwNextClientID;
SRVEXTERN LPSTR   pszPipeName;
SRVEXTERN HANDLE  ChldProc;
SRVEXTERN DWORD   pidChild;
SRVEXTERN HANDLE  hWriteChildStdIn;
SRVEXTERN BOOL    bShuttingDownServer;
SRVEXTERN HANDLE  hHeap;

SRVEXTERN volatile DWORD cPendingCtrlCEvents;

SRVEXTERN OSVERSIONINFO OsVersionInfo;

// File containing all that was output by child process.
// Each connection opens a handle to this file
// and sends its contents through PipeWriteH.

SRVEXTERN HANDLE  hWriteTempFile;

SRVEXTERN char    SaveFileName[64]; //Name of above file - all new sessions need

//
// Generic "wide-open" security descriptor as well
// as the possibly-restricted pipe SD.
//

SRVEXTERN SECURITY_DESCRIPTOR sdPublic;
SRVEXTERN SECURITY_ATTRIBUTES saPublic;
SRVEXTERN SECURITY_ATTRIBUTES saPipe;

//
// To minimize client "all pipe instances are busy" errors,
// we wait on connection to several instances of the IN pipe,
// the sole pipe used by single-pipe clients.  Because of the
// requirement to support two-pipe clients (old software as
// well as new software on Win95), we cannot easily create
// and wait for connection on several instances of the OUT pipe.
// This is because two-pipe clients connect to both pipes before
// handshaking commences, and they connect to OUT first.  If we
// had several OUT pipe instances waiting, when an IN pipe was
// connected by the two-pipe client, we wouldn't know which of
// the possibly several connected OUT pipe instances to pair
// it with.  With only one OUT pipe, at IN connect time we need
// to distinguish two-pipe from one-pipe clients so a one-pipe
// client doesn't sneak in between the OUT and IN connects of
// a two-pipe client and wrongly be paired with the OUT pipe.
// To do so we look at the first byte of the initial write
// from the client (of the computername and magic value), if
// it's a question mark we know we have a new client and won't
// accidentally link it to a connected OUT instance.
//

#define CONNECT_COUNT  3

SRVEXTERN DWORD      cConnectIns;
SRVEXTERN OVERLAPPED rgolConnectIn[CONNECT_COUNT];
SRVEXTERN HANDLE     rghPipeIn[CONNECT_COUNT];

SRVEXTERN OVERLAPPED olConnectOut;
SRVEXTERN BOOL       bOutPipeConnected;
SRVEXTERN HANDLE     hPipeOut;
SRVEXTERN HANDLE     hConnectOutTimer;

//
// Indexes into rghWait array for multiple-wait
//

#define WAITIDX_CHILD_PROCESS           0
#define WAITIDX_READ_STDIN_DONE         1
#define WAITIDX_QUERYSRV_WAIT           2
#define WAITIDX_PER_PIPE_EVENT          3
#define WAITIDX_CONNECT_OUT             4
#define WAITIDX_CONNECT_IN_BASE         5
#define MAX_WAIT_HANDLES                (WAITIDX_CONNECT_IN_BASE + CONNECT_COUNT)

SRVEXTERN HANDLE rghWait[MAX_WAIT_HANDLES];

SRVEXTERN OVERLAPPED ReadChildOverlapped;
SRVEXTERN HANDLE     hReadChildOutput;
SRVEXTERN BYTE       ReadChildBuffer[BUFFSIZE];

SRVEXTERN PREMOTE_CLIENT pLocalClient;

typedef struct tagCOPYPIPE {
    HANDLE     hRead;
    HANDLE     hWrite;
} COPYPIPE, *PCOPYPIPE;

SRVEXTERN COPYPIPE rgCopyPipe[2];

SRVEXTERN volatile DWORD dwWriteFilePointer;   // used by SrvCtrlHand (thread)

SRVEXTERN OVERLAPPED QueryOverlapped;
SRVEXTERN HANDLE hQPipe;

SRVEXTERN OVERLAPPED olMainThread;

BOOL
APIENTRY
MyCreatePipeEx(
    OUT LPHANDLE lpReadPipe,
    OUT LPHANDLE lpWritePipe,
    IN LPSECURITY_ATTRIBUTES lpPipeAttributes,
    IN DWORD nSize,
    DWORD dwReadMode,
    DWORD dwWriteMode
    );

DWORD
WINAPI
CopyPipeToPipe(
    LPVOID   lpCopyPipeData
    );

DWORD
WINAPI
CopyStdInToPipe(
    LPVOID   lpCopyPipeData
    );

VOID
FASTCALL
StartSession(
    PREMOTE_CLIENT pClient
    );

VOID
FASTCALL
StartLocalSession(
    VOID
    );

VOID
FASTCALL
StartReadClientInput(
    PREMOTE_CLIENT pClient
    );

VOID
WINAPI
ReadClientInputCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    );

VOID
WINAPI
WriteChildStdInCompleted(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    );

#define OUT_PIPE -1

VOID
FASTCALL
CreatePipeAndIssueConnect(
    int  nIndex   // IN pipe index or OUT_PIPE
    );

VOID
FASTCALL
HandleOutPipeConnected(
    VOID
    );

VOID
APIENTRY
ConnectOutTimerFired(
    LPVOID pArg,
    DWORD  dwTimerLo,
    DWORD  dwTimerHi
    );

VOID
FASTCALL
HandleInPipeConnected(
    int nIndex
    );

VOID
FASTCALL
HandshakeWithRemoteClient(
    PREMOTE_CLIENT pClient
    );

VOID
FASTCALL
StartChildOutPipeRead(
    VOID
    );

VOID
WINAPI
ReadChildOutputCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    );

VOID
WINAPI
WriteTempFileCompleted(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    );

VOID
FASTCALL
StartServerToClientFlow(
    VOID
    );

VOID
FASTCALL
StartReadTempFile(
    PREMOTE_CLIENT pClient
    );

VOID
WINAPI
ReadTempFileCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    );

VOID
FASTCALL
StartWriteSessionOutput(
    PREMOTE_CLIENT pClient
    );

BOOL
FASTCALL
WriteSessionOutputCompletedCommon(
    PREMOTE_CLIENT pClient,
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    );

VOID
WINAPI
WriteSessionOutputCompletedWriteNext(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    );

VOID
WINAPI
WriteSessionOutputCompletedReadNext(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    );

VOID
FASTCALL
HandshakeWithRemoteClient(
    PREMOTE_CLIENT pClient
    );

VOID
WINAPI
ReadClientNameCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    );

VOID
WINAPI
WriteServerReplyCompleted(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    );

VOID
WINAPI
ReadClientStartupInfoSizeCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    );

VOID
WINAPI
ReadClientStartupInfoCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    );

PCHAR
GetFormattedTime(
    BOOL bDate
    );

HANDLE
ForkChildProcess(          // Creates a new process
    char *cmd,             // Redirects its stdin,stdout
    PHANDLE in,            // and stderr - returns the
    PHANDLE out            // corresponding pipe ends.
    );

BOOL
FilterCommand(             //Filters input from client
    REMOTE_CLIENT *cl,      //for commands intended for REMOTE
    char *buff,
    int dread
    );

BOOL
WINAPI
SrvCtrlHand(
    DWORD event
    );

DWORD
WINAPI
SendStatus(
    LPVOID   lpSendStatusParm
    );

DWORD
WINAPI
ShowPopup(
    void *vpArg
    );

VOID
RemoveInpMark(
    char* Buff,
    DWORD Size
    );

VOID
CloseClient(
    REMOTE_CLIENT *Client
    );

PSECURITY_DESCRIPTOR
FormatSecurityDescriptor(
    CHAR * * DenyNames,
    DWORD    DenyCount,
    CHAR * * Names,
    DWORD    Count
    );

BOOL
FASTCALL
HandleSessionError(
    PREMOTE_CLIENT pClient,
    DWORD         dwError
    );

VOID
FASTCALL
CleanupTempFiles(
    PSZ pszTempDir
    );

VOID
FASTCALL
SetupSecurityDescriptors(
    VOID
    );

VOID
FASTCALL
RuntimeLinkAPIs(
    VOID
    );

VOID
FASTCALL
InitializeClientLists(
    VOID
    );

VOID
FASTCALL
AddClientToHandshakingList(
    PREMOTE_CLIENT pClient
    );

VOID
FASTCALL
MoveClientToNormalList(
    PREMOTE_CLIENT pClient
    );

VOID
FASTCALL
MoveClientToClosingList(
    PREMOTE_CLIENT pClient
    );

PREMOTE_CLIENT
FASTCALL
RemoveFirstClientFromClosingList(
    VOID
    );

VOID
InitAd(
   BOOL IsAdvertise
   );

VOID
ShutAd(
   BOOL IsAdvertise
   );

VOID
APIENTRY
AdvertiseTimerFired(
    LPVOID pArg,
    DWORD  dwTimerLo,
    DWORD  dwTimerHi
    );

VOID
WINAPI
WriteMailslotCompleted(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    );

VOID
FASTCALL
InitializeQueryServer(
    VOID
    );

VOID
FASTCALL
QueryWaitCompleted(
    VOID
    );

VOID
FASTCALL
StartServingQueryPipe(
    VOID
    );

DWORD
WINAPI
QueryHandlerThread(
    LPVOID   lpUnused
    );

BOOL
CALLBACK
EnumWindowProc(
    HWND hWnd,
    LPARAM lParam
    );

//
// Declare pointers to runtime-linked functions
//

HANDLE
(WINAPI *pfnCreateWaitableTimer)(
    LPSECURITY_ATTRIBUTES lpTimerAttributes,
    BOOL bManualReset,
    LPCSTR lpTimerName
    );

BOOL
(WINAPI *pfnSetWaitableTimer)(
    HANDLE hTimer,
    const LARGE_INTEGER *lpDueTime,
    LONG lPeriod,
    PTIMERAPCROUTINE pfnCompletionRoutine,
    LPVOID lpArgToCompletionRoutine,
    BOOL fResume
    );

BOOL
(WINAPI *pfnCancelWaitableTimer)(
    HANDLE hTimer
    );

BOOL
(WINAPI *pfnCancelIo)(
    HANDLE hFile
    );

#define CANCELIO(hFile)  (pfnCancelIo) ? ( pfnCancelIo(hFile) ) : 0;

NET_API_STATUS
(NET_API_FUNCTION *pfnNetWkstaGetInfo)(
    IN  LPTSTR  servername OPTIONAL,
    IN  DWORD   level,
    OUT LPBYTE  *bufptr
    );

NET_API_STATUS
(NET_API_FUNCTION *pfnNetApiBufferFree)(
    IN LPVOID Buffer
    );


SRVAD.C

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include "Remote.h"
#include "Server.h"

//
// This module uses mailslots to broadcast the existence of
// this remote server to allow a form of browsing for
// remote server instances.  This is disabled in the
// customer version of remote.exe, and can be disabled
// in the internal version using the /v- switch to
// remote /s.
//
// remoteds.c implements a listener that allows searching.
//

#define  INITIAL_SLEEP_PERIOD (35 * 1000)          // 35 seconds before first
#define  INITIAL_AD_RATE      (10 * 60 * 1000)     // 10 minutes between 1 & 2,
#define  MAXIMUM_AD_RATE      (120 * 60 * 1000)    // doubling until 120 minutes max

OVERLAPPED olMailslot;
HANDLE     hAdTimer = INVALID_HANDLE_VALUE;
HANDLE     hMailslot = INVALID_HANDLE_VALUE;
DWORD      dwTimerInterval;      // milliseconds
BOOL       bSynchAdOnly;
BOOL       bSendingToMailslot;
char       szMailslotName[64];    // netbios names are short
char       szSend[1024];

#define MAX_MAILSLOT_SPEWS 2
DWORD      dwMailslotErrors;

VOID
InitAd(
   BOOL IsAdvertise
   )
{
    DWORD           cb;
    PWKSTA_INFO_101 pwki101;
    LARGE_INTEGER   DueTime;

    if (IsAdvertise) {

        // Unless Win32s or Win9x support named pipe servers...

        ASSERT(OsVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT);

        // Problems with overlapped writes to a mailslot sometimes
        // cause remote.exe to zombie on exit on NT4, undebuggable
        // and unkillable because of an abandoned RDR1 IRP which
        // never completes.
        //
        // So on NT4 we only send messages at startup and shutdown
        // and send them synchronously using a nonoverlapped handle.
        //

        bSynchAdOnly = (OsVersionInfo.dwMajorVersion <= 4);

        //
        // Get currently active computername and browser/mailslot
        // domain/workgroup using one call to NetWkstaGetInfo.
        // This is unicode-only, we'll use wsprintf's %ls to
        // convert to 8-bit characters.
        //
        // remoteds.exe needs to be run on a workstation that is
        // part of the domain or workgroup of the same name,
        // and be in broadcast range, to receive our sends.
        //

        if (pfnNetWkstaGetInfo(NULL, 101, (LPBYTE *) &pwki101)) {
            printf("REMOTE: unable to get computer/domain name, not advertising.\n");
            return;
        }

        wsprintf(
            szMailslotName,
            "\\\\%ls\\MAILSLOT\\REMOTE\\DEBUGGERS",
            pwki101->wki101_langroup
            );

        wsprintf(
            szSend,
            "%ls\t%d\t%s\t%s",
            pwki101->wki101_computername,
            GetCurrentProcessId(),
            PipeName,
            ChildCmd
            );

        pfnNetApiBufferFree(pwki101);
        pwki101 = NULL;

        //
        // Broadcast mailslots are limited to 400 message bytes
        //

        szSend[399] = 0;

        if (bSynchAdOnly) {

            hMailslot =
                CreateFile(
                    szMailslotName,
                    GENERIC_WRITE,
                    FILE_SHARE_WRITE,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL
                    );

            if ( ! WriteFile(
                       hMailslot,
                       szSend,
                       strlen(szSend) + 1,
                       &cb,
                       NULL
                       )) {

                printf("REMOTE: WriteFile Failed on mailslot, error %d\n", GetLastError());
            }

        } else {  // we can do async mailslot I/O

            //
            // Create a waitable timer and set it to fire first in
            // INITIAL_SLEEP_PERIOD milliseconds by calling the
            // completion routine AdvertiseTimerFired.  It will
            // be given an inital period of INITIAL_AD_RATE ms.
            //

            hAdTimer =
                pfnCreateWaitableTimer(
                    NULL,               // security
                    FALSE,              // bManualReset, we want auto-reset
                    NULL                // unnamed
                    );

            DueTime.QuadPart = Int32x32To64(INITIAL_SLEEP_PERIOD, -10000);
            dwTimerInterval = INITIAL_AD_RATE;

            pfnSetWaitableTimer(
                hAdTimer,
                &DueTime,
                dwTimerInterval,
                AdvertiseTimerFired,
                0,                     // arg to compl. rtn
                TRUE
                );

        }
    }
}

VOID
ShutAd(
   BOOL IsAdvertise
   )
{
    DWORD cb;
    BOOL  b;

    if (IsAdvertise) {

        if (INVALID_HANDLE_VALUE != hAdTimer) {

            pfnCancelWaitableTimer(hAdTimer);
            CloseHandle(hAdTimer);
            hAdTimer = INVALID_HANDLE_VALUE;
        }

        if (INVALID_HANDLE_VALUE != hMailslot &&
            ! bSendingToMailslot) {

            //
            // Tell any listening remoteds's we're
            // outta here.  Do this by tacking on
            // a ^B at the end of the string (as
            // in Bye).
            //

            strcat(szSend, "\x2");

            if (bSynchAdOnly) {   // overlapped handle or not?
                b = WriteFile(
                        hMailslot,
                        szSend,
                        strlen(szSend) + 1,
                        &cb,
                        NULL
                        );
            } else {
                b = WriteFileSynch(
                        hMailslot,
                        szSend,
                        strlen(szSend) + 1,
                        &cb,
                        0,
                        &olMainThread
                        );
            }

            if ( ! b ) {

                printf("REMOTE: WriteFile Failed on mailslot, error %d\n", GetLastError());
            }

        }

        if (INVALID_HANDLE_VALUE != hMailslot) {

            printf("\rREMOTE: closing mailslot...       ");
            fflush(stdout);
            CloseHandle(hMailslot);
            hMailslot = INVALID_HANDLE_VALUE;
            printf("\r                                  \r");
            fflush(stdout);
        }
    }
}

VOID
APIENTRY
AdvertiseTimerFired(
    LPVOID pArg,
    DWORD  dwTimerLo,
    DWORD  dwTimerHi
    )
{
    UNREFERENCED_PARAMETER( pArg );
    UNREFERENCED_PARAMETER( dwTimerLo );
    UNREFERENCED_PARAMETER( dwTimerHi );

    if (INVALID_HANDLE_VALUE == hMailslot) {

        hMailslot =
            CreateFile(
                szMailslotName,
                GENERIC_WRITE,
                FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_OVERLAPPED,
                NULL
                );
    }

    if (INVALID_HANDLE_VALUE != hMailslot) {

        ZeroMemory(&olMailslot, sizeof(olMailslot));

        bSendingToMailslot = TRUE;

        if ( ! WriteFileEx(
                   hMailslot,
                   szSend,
                   strlen(szSend) + 1,
                   &olMailslot,
                   WriteMailslotCompleted
                   )) {

            bSendingToMailslot = FALSE;

            if (++dwMailslotErrors <= MAX_MAILSLOT_SPEWS) {

                DWORD dwError;
                char szErrorText[512];

                dwError = GetLastError();

                FormatMessage(
                    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                    NULL,
                    dwError,
                    0,
                    szErrorText,
                    sizeof szErrorText,
                    NULL
                    );

                //
                // FormatMessage has put a newline at the end of szErrorText
                //

                printf(
                    "REMOTE: Advertisement failed, mailslot error %d:\n%s",
                    dwError,
                    szErrorText
                    );
            }

            //
            // Try reopening the mailslot next time, can't hurt.
            //

            CloseHandle(hMailslot);
            hMailslot = INVALID_HANDLE_VALUE;
        }
    }
}

VOID
WINAPI
WriteMailslotCompleted(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    )
{
    LARGE_INTEGER DueTime;

    bSendingToMailslot = FALSE;

    if (dwError ||
        (strlen(szSend) + 1) != cbWritten) {

            if (++dwMailslotErrors <= MAX_MAILSLOT_SPEWS) {
                printf("REMOTE: write failed on mailslot, error %d cb %d (s/b %d)\n",
                    dwError, cbWritten, (strlen(szSend) + 1));
            }
        return;
    }

    //
    // If we succeeded in writing the mailslot, double the timer interval
    // up to the limit.
    //

    if (dwTimerInterval < MAXIMUM_AD_RATE) {

        dwTimerInterval = max(dwTimerInterval * 2, MAXIMUM_AD_RATE);

        DueTime.QuadPart = Int32x32To64(dwTimerInterval, -10000);

        if (INVALID_HANDLE_VALUE != hAdTimer) {

            pfnSetWaitableTimer(
                hAdTimer,
                &DueTime,
                dwTimerInterval,
                AdvertiseTimerFired,
                0,                     // arg to compl. rtn
                TRUE
                );
        }
    }
}


SRVCHILD.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1992 - 1997 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.
\******************************************************************************/

/*++

Copyright 1992 - 1997 Microsoft Corporation

Module Name:

    SrvChild.c

Abstract:

    The server component of Remote. It spawns a child process
    and redirects the stdin/stdout/stderr of child to itself.
    Waits for connections from clients - passing the
    output of child process to client and the input from clients
    to child process.

Author:

    Rajivendra Nath  2-Jan-1992
    Dave Hart        30 May 1997 split from Server.c

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <io.h>
#include <string.h>
#include "Remote.h"
#include "Server.h"

VOID
FASTCALL
StartChildOutPipeRead(
    VOID
    )
{
    ReadChildOverlapped.OffsetHigh =
        ReadChildOverlapped.Offset = 0;

    if ( ! ReadFileEx(
               hReadChildOutput,
               ReadChildBuffer,
               sizeof(ReadChildBuffer) - 1,                  // allow for null term
               &ReadChildOverlapped,
               ReadChildOutputCompleted
               )) {

        if (INVALID_HANDLE_VALUE != hWriteChildStdIn) {

            CANCELIO( hWriteChildStdIn );
            CloseHandle( hWriteChildStdIn );
            hWriteChildStdIn = INVALID_HANDLE_VALUE;
        }
    }
}

VOID
WINAPI
ReadChildOutputCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    )
{
    UNREFERENCED_PARAMETER(lpO);

    //
    // We can get called after hWriteTempFile
    // is closed after the child has exited.
    //

    if (! dwError &&
        INVALID_HANDLE_VALUE != hWriteTempFile) {

        //
        // Start a write to the temp file.
        //

        ReadChildOverlapped.OffsetHigh = 0;
        ReadChildOverlapped.Offset = dwWriteFilePointer;

        if ( ! WriteFileEx(
                   hWriteTempFile,
                   ReadChildBuffer,
                   cbRead,
                   &ReadChildOverlapped,
                   WriteTempFileCompleted
                   )) {

            dwError = GetLastError();

            if (ERROR_DISK_FULL == dwError) {

                printf("Remote: disk full writing temp file %s, exiting\n", SaveFileName);

                if (INVALID_HANDLE_VALUE != hWriteChildStdIn) {

                    CANCELIO( hWriteChildStdIn );
                    CloseHandle( hWriteChildStdIn );
                    hWriteChildStdIn = INVALID_HANDLE_VALUE;
                }

            } else {

                ErrorExit("WriteFileEx for temp file failed.");
            }
        }
    }
}

VOID
WINAPI
WriteTempFileCompleted(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    )
{
    UNREFERENCED_PARAMETER(lpO);

    if (dwError) {

        if (ERROR_DISK_FULL == dwError) {

            printf("Remote: disk full writing temp file %s, exiting\n", SaveFileName);

            if (INVALID_HANDLE_VALUE != hWriteChildStdIn) {

                CANCELIO( hWriteChildStdIn );
                CloseHandle( hWriteChildStdIn );
                hWriteChildStdIn = INVALID_HANDLE_VALUE;
            }

            return;

        } else {

            SetLastError(dwError);
            ErrorExit("WriteTempFileCompleted may need work");
        }
    }

    dwWriteFilePointer += cbWritten;

    TRACE(CHILD, ("Wrote %d bytes to temp file\n", cbWritten));

    StartServerToClientFlow();

    //
    // Start another read against the child input.
    //

    StartChildOutPipeRead();
}


SRVCTOS.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvCtoS.c

Abstract:

    This file implements the client-to-server flow
    of data for remote server.  The data is the keyboard
    or piped input that the client received and sent
    over the wire to us, bracketed by BEGINMARK and ENDMARK
    bytes so we can display nice attribution comments in
    brackets next to input lines.

Author:

    Dave Hart  30 May 1997

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include "Remote.h"
#include "Server.h"

VOID
FASTCALL
StartReadClientInput(
    PREMOTE_CLIENT pClient
    )
{
    //
    // Start read of data from this client's stdin.
    //

    if ( ! ReadFileEx(
               pClient->PipeReadH,
               pClient->ReadBuffer,
               BUFFSIZE - 1,                  // allow for null term
               &pClient->ReadOverlapped,
               ReadClientInputCompleted
               )) {

        CloseClient(pClient);
    }
}

VOID
WINAPI
ReadClientInputCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, ReadOverlapped);

    if (HandleSessionError(pClient, dwError) ||
        !cbRead) {

        return;
    }

    pClient->ReadBuffer[cbRead] = 0;

    if (FilterCommand(pClient, pClient->ReadBuffer, cbRead)) {

        //
        // Local command, don't pass it to child app, just
        // start another client read.
        //

        if ( ! ReadFileEx(
                   pClient->PipeReadH,
                   pClient->ReadBuffer,
                   BUFFSIZE - 1,                  // allow for null term
                   &pClient->ReadOverlapped,
                   ReadClientInputCompleted
                   )) {

            CloseClient(pClient);
        }

    } else {

        //
        // Write buffer to child stdin.
        //

        if ( ! WriteFileEx(
                   hWriteChildStdIn,
                   pClient->ReadBuffer,
                   cbRead,
                   &pClient->ReadOverlapped,
                   WriteChildStdInCompleted
                   )) {

            // Child is going away.  Let this client's chain of IO stop.
        }
    }
}

VOID
WINAPI
WriteChildStdInCompleted(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, ReadOverlapped);

    if (HandleSessionError(pClient, dwError)) {

        return;
    }

    //
    // Start another read against the client input.
    //

    StartReadClientInput(pClient);
}


SRVHSHAK.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvHShak.c

Abstract:

    The server component of Remote.  Handshake with
    client at start of session.

Author:

    Dave Hart  30 May 1997

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include "Remote.h"
#include "Server.h"

VOID
FASTCALL
HandshakeWithRemoteClient(
    PREMOTE_CLIENT pClient
    )
{
    pClient->ServerFlags |= SFLG_HANDSHAKING;

    AddClientToHandshakingList(pClient);

    //
    // Read hostname from client
    //

    ZeroMemory(
        &pClient->ReadOverlapped,
        sizeof(pClient->ReadOverlapped)
        );

    if ( ! ReadFileEx(
               pClient->PipeReadH,
               pClient->Name,
               HOSTNAMELEN - 1,
               &pClient->ReadOverlapped,
               ReadClientNameCompleted
               )) {

        CloseClient(pClient);
    }
}

VOID
WINAPI
ReadClientNameCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;
    SESSION_STARTREPLY ssr;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, ReadOverlapped);

    if (pClient->ServerFlags & SFLG_CLOSING) {

        return;
    }

    if (dwError) {
        CloseClient(pClient);
        return;
    }

    if ((HOSTNAMELEN - 1) != cbRead) {
        printf("ReadClientNameCompleted read %d s/b %d.\n", cbRead, (HOSTNAMELEN - 1));
        CloseClient(pClient);
        return;
    }

    //
    // The client name read is 15 bytes always.  The last four
    // should match MAGICNUMBER, which conveniently has the
    // low byte zeroed to terminate the client name after 11
    // characters.
    //

    if (MAGICNUMBER != *(DWORD *)&pClient->Name[11]) {

        pClient->Name[11] = 0;
        CloseClient(pClient);
        return;
    }

    //
    // Now we can tell if this is a single-pipe or two-pipe
    // client, because single-pipe clients replace the
    // first byte of the computername with the illegal
    // character '?'.
    //

    if ('?' == pClient->Name[0]) {

        pClient->PipeWriteH = pClient->PipeReadH;

        TRACE(CONNECT, ("Client %d pipe %x is single-pipe.\n", pClient->dwID, pClient->PipeWriteH));

        //
        // In order for things to work reliably for 2-pipe clients
        // when there are multiple remote servers on the same pipename,
        // we need to tear down the listening OUT pipe and recreate it so
        // that the oldest listening IN pipe will be from the same process
        // as the oldest listening OUT pipe.
        //

        if (1 == cConnectIns) {

            TRACE(CONNECT, ("Recycling OUT pipe %x as well for round-robin behavior.\n",
                            hPipeOut));

            CANCELIO(hPipeOut);
            DisconnectNamedPipe(hPipeOut);
            CloseHandle(hPipeOut);
            hPipeOut = INVALID_HANDLE_VALUE;
            bOutPipeConnected = FALSE;

            CreatePipeAndIssueConnect(OUT_PIPE);
        }

    } else {

        if ( ! bOutPipeConnected ) {

            printf("Remote: %x two-pipe client connected to IN pipe but not OUT?\n", pClient);
            CloseClient(pClient);
            return;
        }

        bOutPipeConnected = FALSE;

        if (INVALID_HANDLE_VALUE != hConnectOutTimer) {
            pfnCancelWaitableTimer(hConnectOutTimer);
        }

        pClient->PipeWriteH = hPipeOut;
        hPipeOut = INVALID_HANDLE_VALUE;

        TRACE(CONNECT, ("Client %d is dual-pipe IN %x OUT %x.\n", pClient->dwID, pClient->PipeReadH, pClient->PipeWriteH));

        CreatePipeAndIssueConnect(OUT_PIPE);
    }

    TRACE(SHAKE, ("Read client name %s\n", pClient->Name));

    //
    // Send our little pile of goodies to the client
    //

    ssr.MagicNumber = MAGICNUMBER;
    ssr.Size = sizeof(ssr);
    ssr.FileSize = dwWriteFilePointer;

    //
    // Copy ssr structure to a buffer that will be around
    // for the entire I/O.
    //

    CopyMemory(pClient->WriteBuffer, &ssr, sizeof(ssr));

    if ( ! WriteFileEx(
               pClient->PipeWriteH,
               pClient->WriteBuffer,
               sizeof(ssr),
               &pClient->WriteOverlapped,
               WriteServerReplyCompleted
               )) {

        CloseClient(pClient);
    }
}

VOID
WINAPI
WriteServerReplyCompleted(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, WriteOverlapped);

    if (pClient->ServerFlags & SFLG_CLOSING) {

        return;
    }

    if (HandleSessionError(pClient, dwError)) {
        return;
    }

    TRACE(SHAKE, ("Wrote server reply\n"));

    //
    // Read the size of the SESSION_STARTUPINFO the client is
    // sending us, to deal gracefully with different versions
    // on client and server.
    //

    if ( ! ReadFileEx(
               pClient->PipeReadH,
               pClient->ReadBuffer,
               sizeof(DWORD),
               &pClient->ReadOverlapped,
               ReadClientStartupInfoSizeCompleted
               )) {

        CloseClient(pClient);
    }
}

VOID
WINAPI
ReadClientStartupInfoSizeCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;
    DWORD dwSize;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, ReadOverlapped);

    if (HandleSessionError(pClient, dwError)) {

        return;
    }

    if (cbRead != sizeof(DWORD)) {

        CloseClient(pClient);
        return;
    }

    //
    // Sanity check the size
    //

    dwSize = *(DWORD *)pClient->ReadBuffer;

    if (dwSize > 1024) {
        CloseClient(pClient);
        return;
    }

    //
    // Squirrel away the size in the write buffer,
    // since during handshaking we never have both a
    // read and write pending this is OK.
    //

    *(DWORD *)pClient->WriteBuffer = dwSize;

    TRACE(SHAKE, ("Read client reply size %d\n", dwSize));

    //
    // Read the rest of the SESSION_STARTUPINFO into the read buffer
    // after the size.
    //

    RtlZeroMemory(
        &pClient->ReadOverlapped,
        sizeof(pClient->ReadOverlapped)
        );

    if ( ! ReadFileEx(
               pClient->PipeReadH,
               pClient->ReadBuffer + sizeof(DWORD),
               dwSize - sizeof(DWORD),
               &pClient->ReadOverlapped,
               ReadClientStartupInfoCompleted
               )) {

        CloseClient(pClient);
    }
}

VOID
WINAPI
ReadClientStartupInfoCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;
    DWORD dwSize;
    SESSION_STARTUPINFO ssi;
    char  Buf[256];

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, ReadOverlapped);

    if (HandleSessionError(pClient, dwError)) {

        return;
    }

    dwSize = *(DWORD *)pClient->WriteBuffer;

    if (cbRead != (dwSize - sizeof(ssi.Size))) {

        CloseClient(pClient);
        return;
    }

    CopyMemory(&ssi, pClient->ReadBuffer, min(dwSize, sizeof(ssi)));

    CopyMemory(pClient->Name, ssi.ClientName, sizeof(pClient->Name));
    pClient->Flag = ssi.Flag;

    if (ssi.Version != VERSION) {

        printf("Remote Warning: Server Version=%d Client Version=%d for %s\n", VERSION, ssi.Version, pClient->Name);
    }

    TRACE(SHAKE, ("Read client info, new name %s, %d lines\n", pClient->Name, ssi.LinesToSend));

    //
    // Set temp file position according to the client's
    // requested lines to send.  The heuristic of 45 chars
    // per average line is used by the client.  However since old clients
    // hardcode this knowledge and sit and spin trying to read that many
    // bytes before completing initialization, and because we might not send
    // that many due to stripping BEGINMARK and ENDMARK characters, we
    // use 50 chars per line to calculate the temp file position in hopes
    // the extra bytes will overcome the missing MARK characters.
    //

    pClient->dwFilePos = dwWriteFilePointer > (ssi.LinesToSend * 50)
                             ? dwWriteFilePointer - (ssi.LinesToSend * 50)
                             : 0;

    //
    // This client's ready to roll.
    //

    pClient->ServerFlags &= ~SFLG_HANDSHAKING;

    MoveClientToNormalList(pClient);

    //
    // Start read operation against this client's input.
    //

    StartReadClientInput(pClient);

    //
    // Announce the connection.
    //

    sprintf(Buf,
            "\n**Remote: Connected to %s %s%s [%s]\n",
            pClient->Name,
            pClient->UserName,
            (pClient->PipeReadH != pClient->PipeWriteH)
              ? " (two pipes)"
              : "",
            GetFormattedTime(TRUE));

    if (WriteFileSynch(hWriteTempFile,Buf,strlen(Buf),&dwSize,dwWriteFilePointer,&olMainThread)) {
        dwWriteFilePointer += dwSize;
        StartServerToClientFlow();
    }

    //
    // Start write cycle for client output from the temp
    // file.
    // not needed because of StartServerToClientFlow() just above
    // StartReadTempFile(pClient);

}


SRVLIST.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvList.c

Abstract:

    The server component of Remote.  This module
    implements three lists of REMOTE_CLIENT structures,
    for handshaking, connected, and closing clients.
    To simplify the interface items always progress
    through the three lists in order, with list node
    memory being freed as it is removed from the
    closing list.

Author:

    Dave Hart  30 May 1997

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include "Remote.h"
#include "Server.h"
#include "SrvList.h"

VOID
FASTCALL
InitializeClientLists(
    VOID
    )
{
    InitializeCriticalSection( &csHandshakingList );
    InitializeCriticalSection( &csClientList );
    InitializeCriticalSection( &csClosingClientList );

    InitializeListHead( &HandshakingListHead );
    InitializeListHead( &ClientListHead );
    InitializeListHead( &ClosingClientListHead );
}

VOID
FASTCALL
AddClientToHandshakingList(
    PREMOTE_CLIENT pClient
    )
{
    EnterCriticalSection( &csHandshakingList );

    InsertTailList( &HandshakingListHead, &pClient->Links );

    LeaveCriticalSection( &csHandshakingList );
}

VOID
FASTCALL
MoveClientToNormalList(
    PREMOTE_CLIENT pClient
    )
{
    EnterCriticalSection( &csHandshakingList );

    RemoveEntryList( &pClient->Links );

    LeaveCriticalSection( &csHandshakingList );

    EnterCriticalSection( &csClientList );

    InsertTailList( &ClientListHead, &pClient->Links );

    LeaveCriticalSection( &csClientList );
}

VOID
FASTCALL
MoveClientToClosingList(
    PREMOTE_CLIENT pClient
    )
{
    EnterCriticalSection( &csClientList );

    RemoveEntryList( &pClient->Links );

    LeaveCriticalSection( &csClientList );

    EnterCriticalSection( &csClosingClientList );

    InsertTailList( &ClosingClientListHead, &pClient->Links );

    LeaveCriticalSection( &csClosingClientList );
}

PREMOTE_CLIENT
FASTCALL
RemoveFirstClientFromClosingList(
    VOID
    )
{
    PREMOTE_CLIENT pClient;

    EnterCriticalSection( &csClosingClientList );

    if (IsListEmpty(&ClosingClientListHead)) {

        pClient = NULL;

    } else {

        pClient = (PREMOTE_CLIENT) RemoveHeadList( &ClosingClientListHead );

        ZeroMemory( &pClient->Links, sizeof(&pClient->Links) );

    }

    LeaveCriticalSection( &csClosingClientList );

    return pClient;
}


SRVLIST.H

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvList.h

Abstract:

    List package for SrvList.c

Environment:

    Console App. User mode.

Revision History:

--*/

//
//  Doubly-linked list manipulation routines.  Implemented as macros
//  but logically these are procedures.
//

//
//  VOID
//  InitializeListHead(
//      PLIST_ENTRY ListHead
//      );
//

#define InitializeListHead(ListHead) (\
    (ListHead)->Flink = (ListHead)->Blink = (ListHead))

//
//  BOOLEAN
//  IsListEmpty(
//      PLIST_ENTRY ListHead
//      );
//

#define IsListEmpty(ListHead) \
    ((ListHead)->Flink == (ListHead))

//
//  PLIST_ENTRY
//  RemoveHeadList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveHeadList(ListHead) \
    (ListHead)->Flink;\
    {RemoveEntryList((ListHead)->Flink)}

//
//  PLIST_ENTRY
//  RemoveTailList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveTailList(ListHead) \
    (ListHead)->Blink;\
    {RemoveEntryList((ListHead)->Blink)}

//
//  VOID
//  RemoveEntryList(
//      PLIST_ENTRY Entry
//      );
//

#define RemoveEntryList(Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_Flink;\
    _EX_Flink = (Entry)->Flink;\
    _EX_Blink = (Entry)->Blink;\
    _EX_Blink->Flink = _EX_Flink;\
    _EX_Flink->Blink = _EX_Blink;\
    }

//
//  VOID
//  InsertTailList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertTailList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Blink = _EX_ListHead->Blink;\
    (Entry)->Flink = _EX_ListHead;\
    (Entry)->Blink = _EX_Blink;\
    _EX_Blink->Flink = (Entry);\
    _EX_ListHead->Blink = (Entry);\
    }

//
//  VOID
//  InsertHeadList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertHeadList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Flink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Flink = _EX_ListHead->Flink;\
    (Entry)->Flink = _EX_Flink;\
    (Entry)->Blink = _EX_ListHead;\
    _EX_Flink->Blink = (Entry);\
    _EX_ListHead->Flink = (Entry);\
    }


SRVMAIN.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvMain.c

Abstract:

    The server component of Remote. It spawns a child process
    and redirects the stdin/stdout/stderr of child to itself.
    Waits for connections from clients - passing the
    output of child process to client and the input from clients
    to child process.

    This version uses overlapped I/O to do in one thread what
    the original uses 9 for.  Almost.  Because there is no way to
    get overlapped stdin/stdout handles, two threads sit around
    doing blocking I/O on stdin and stdout.  3 is better than 9.

    Unfortunately there's no CreatePipe()
    or equivalent option to open an overlapped handle to an anonymous
    pipe, so I stole the source for NT CreatePipe and hacked it to
    accept flags indicating overlapped for one or both ends of the
    anonymous pipe.  In our usage the child end handles are not
    overlapped but the server end handles are.

Author:

    Dave Hart  30 May 1997 after Server.c by
    Rajivendra Nath  2-Jan-1992

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <io.h>
#include <string.h>
#if DBG
    #undef NDEBUG           // so asserts work on chk builds
#endif
#include "Remote.h"
#define SERVER_H_NOEXTERN
#include "Server.h"

DWORD cbRemoteClient = sizeof(REMOTE_CLIENT);  // for debugging

/*************************************************************/
int
OverlappedServer(                    //Main routine for server.
    char* pszChildCmd,
    char* pszPipeNameArg
    )
{
    int    i;
    BOOL   b;
    DWORD  cWait;
    DWORD  dwWait;
    PREMOTE_CLIENT pClientRemove;

#if DBG
    // Trace = -1;   // all TR_ bits on (and then some)
#endif

    //
    // Initialize globals
    //

    pszPipeName = pszPipeNameArg;

    dwNextClientID = 1;           // local client will be 1
    cConnectIns = CONNECT_COUNT;
    cWait = MAX_WAIT_HANDLES;

    hHeap = HeapCreate(
                0,
                3 * sizeof(REMOTE_CLIENT),    // initial size
                3000 * sizeof(REMOTE_CLIENT)  // max
                );

    OsVersionInfo.dwOSVersionInfoSize = sizeof OsVersionInfo;
    b = GetVersionEx(&OsVersionInfo);
    ASSERT( b );

    printf("**************************************\n");
    printf("***********     REMOTE    ************\n");
    printf("***********     SERVER    ************\n");
    printf("**************************************\n");
    fflush(stdout);

    //
    // Setup the ACLs we need, taking into account any /u switches
    //

    SetupSecurityDescriptors();

    printf("To Connect: Remote /C %s %s\n\n", HostName, pszPipeName);
    fflush(stdout);

    //
    // runtime link to NT-only kernel32 APIs so we can
    // load on Win95 for client use.
    //

    RuntimeLinkAPIs();

    //
    // Setup our three lists of clients:  handshaking,
    // connected, and closing/closed.
    //

    InitializeClientLists();

    //
    // set _REMOTE environment variable to the pipe name (why?)
    //

    SetEnvironmentVariable("_REMOTE", pszPipeName);

    //
    // Create a tempfile for storing Child process output.
    //

    {
        char szTempDirectory[MAX_PATH + 1];

        GetTempPath(sizeof(szTempDirectory), szTempDirectory);

        //
        // Before we litter the temp directory with more REMnnn.TMP
        // files, let's delete all the orphaned ones we can.  This
        // will fail for temp files open by other remote servers.
        //

        CleanupTempFiles(szTempDirectory);

        GetTempFileName(szTempDirectory, "REM", 0, SaveFileName);
    }

    if ( ! (hWriteTempFile =
            CreateFile(
                SaveFileName,                       /* name of the file  */
                GENERIC_READ | GENERIC_WRITE,       /* access (read/write) mode */
                FILE_SHARE_READ | FILE_SHARE_WRITE, /* share mode   */
                NULL,                               /* security descriptor  */
                CREATE_ALWAYS,                      /* how to create    */
                FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
                NULL
                ))) {

        ErrorExit("Could not Create Temp File");
    }

    //
    // We don't want to have multiple IN pipes created and
    // awaiting connection simultaneously if there are
    // multiple remote server processes sharing different
    // sessions under the same pipe name.  This would be
    // hairy for several reasons including breaking the
    // current round-robin behavior of connections, since
    // the oldest server pipe is connected first.  So
    // we create/open a named event based on the pipe name and
    // set the event so that any other remote servers on the
    // same pipe will fall back to a single IN pipe listening.
    //

    {
        char szPerPipeEventName[1024];

        sprintf(
            szPerPipeEventName,
            "MSRemoteSrv%s",
            pszPipeName
            );

        rghWait[WAITIDX_PER_PIPE_EVENT] =
            CreateEvent(
                    &saPublic,  // security
                    TRUE,       // manual reset (synchronization)
                    FALSE,      // initially nonsignaled
                    szPerPipeEventName
                    );

        if (! rghWait[WAITIDX_PER_PIPE_EVENT]) {

            ErrorExit("Unable to create per-pipe event.");
        }

        if (ERROR_ALREADY_EXISTS == GetLastError()) {

            TRACE(CONNECT, ("Found previous server on '%s', using 1 listening pipe.\n", pszPipeName));

            SetEvent(rghWait[WAITIDX_PER_PIPE_EVENT]);

            for (i = 1; i < (int) cConnectIns; i++) {

                rghPipeIn[i] = INVALID_HANDLE_VALUE;
            }

            cWait = MAX_WAIT_HANDLES - cConnectIns + 1;
            cConnectIns = 1;

            //
            // We don't want to wait on the event handle, but it's easier
            // to have a handle in its slot, so dupe a handle to our own
            // process.  Note we toss the value of the created event handle
            // without closing it -- we want it to stay around but we're
            // done with it.
            //

            DuplicateHandle(
                GetCurrentProcess(),
                GetCurrentProcess(),
                GetCurrentProcess(),
                &rghWait[WAITIDX_PER_PIPE_EVENT],
                0,
                FALSE,
                DUPLICATE_SAME_ACCESS
                );

        }
    }

    //
    // Create the event for the OVERLAPPED structure
    // used by the main server thread for WriteFileSynch calls.
    //

    olMainThread.hEvent =
        CreateEvent(
            NULL,      // security
            TRUE,      // auto-reset
            FALSE,     // initially nonsignaled
            NULL       // unnamed
            );

    //
    // Create the events for the OVERLAPPED structures
    // used for ConnectNamedPipe operations.
    //

    olConnectOut.hEvent =
        rghWait[WAITIDX_CONNECT_OUT] =
            CreateEvent(
                NULL,    // security
                TRUE,    // manual reset as ConnectNamedPipe demands
                FALSE,   // initially nonsignaled
                NULL
                );

    for (i = 0;
         i < (int) cConnectIns;
         i++) {

        rgolConnectIn[i].hEvent =
            rghWait[WAITIDX_CONNECT_IN_BASE + i] =
                CreateEvent(
                    NULL,    // security
                    TRUE,    // manual reset as ConnectNamedPipe demands
                    FALSE,   // initially nonsignaled
                    NULL
                    );

    }

    //
    // Create a timer we'll use to detect 2-pipe clients connected to
    // OUT without ever connecting to IN so we can recycle our single
    // OUT instance and allow other two-pipe clients in again.
    // NT 3.51 doesn't have waitable timers, so we don't do that
    // error handling on that OS.  Same as old remote.exe.
    //

    if (pfnCreateWaitableTimer) {
        hConnectOutTimer =
            pfnCreateWaitableTimer(
                NULL,               // security
                FALSE,              // bManualReset, we want auto-reset
                NULL                // unnamed
                );
    } else {
        hConnectOutTimer = INVALID_HANDLE_VALUE;
    }

    //
    // Start the command as a child process
    //

    if (hAttachedProcess != INVALID_HANDLE_VALUE) {

        ChldProc = hAttachedProcess;
        hWriteChildStdIn = hAttachedWriteChildStdIn;
        hReadChildOutput = hAttachedReadChildStdOut;

    } else {

        ChldProc =
             ForkChildProcess(
                 ChildCmd,
                 &hWriteChildStdIn,
                 &hReadChildOutput
                 );
    }

    rghWait[WAITIDX_CHILD_PROCESS] = ChldProc;

    //
    // Set ^c/^break handler.  It will kill the child process on
    // ^break and pass ^c through to it.
    //

    SetConsoleCtrlHandler(SrvCtrlHand, TRUE);

    //
    // Setup local session and start first read against its input.
    // This starts a chain of completion routines that continues
    // until this server exits.
    //

    StartLocalSession();

    //
    // Start a read operation on the child output pipe.
    // This starts a chain of completion routines that continues
    // until the child terminates.
    //

    StartChildOutPipeRead();

    //
    // Start several async ConnectNamedPipe operations, to reduce the chance
    // of a client getting pipe busy errors.  Since there is no
    // completion port version of ConnectNamedPipe, we'll wait on the
    // events in the main loop below that indicate completion.
    //

    CreatePipeAndIssueConnect(OUT_PIPE);

    for (i = 0;
         i < (int) cConnectIns;
         i++) {

        CreatePipeAndIssueConnect(i);
    }

    InitAd(IsAdvertise);

    //
    // We may need to service the query pipe for remote /q clients.
    //

    InitializeQueryServer();

    //
    // main loop of thread, waits for ConnectNamedPipe completions
    // and handles them while remaining alertable for completion
    // routines to get called.
    //

    while (1) {

        dwWait =
            WaitForMultipleObjectsEx(
                cWait,
                rghWait,
                FALSE,          // wait on any handle, not all
                30 * 1000,      // ms
                TRUE            // alertable (completion routines)
                );

        if (WAIT_IO_COMPLETION == dwWait) {

            //
            // A completion routine was called.
            //

            continue;
        }

        if (WAIT_TIMEOUT == dwWait) {

            //
            // Presumably since we've timed out for 30 seconds
            // with no IO completion, closing clients have
            // finished any pending IOs and the memory can be
            // released.
            //

            while (pClientRemove = RemoveFirstClientFromClosingList()) {

                HeapFree(hHeap, 0, pClientRemove);
            }

            continue;
        }

        if (WAITIDX_CONNECT_OUT == dwWait) {

            HandleOutPipeConnected();
            continue;
        }

        if (WAITIDX_CONNECT_IN_BASE <= dwWait &&
            (WAITIDX_CONNECT_IN_BASE + CONNECT_COUNT) > dwWait) {

            HandleInPipeConnected( dwWait - WAITIDX_CONNECT_IN_BASE );
            continue;
        }

        if (WAITIDX_QUERYSRV_WAIT == dwWait ||
            WAITIDX_QUERYSRV_WAIT + WAIT_ABANDONED_0 == dwWait ) {

            //
            // The remote server which was handling the query pipe
            // has gone away.  We'll try to take over.
            //

            QueryWaitCompleted();

            continue;
        }

        if (WAITIDX_PER_PIPE_EVENT == dwWait) {

            //
            // Another server is starting on this same
            // pipename.  To be most compatible we need
            // to fall back to listening on only one
            // IN pipe instance.
            //

            if (1 != cConnectIns) {

                TRACE(CONNECT,
                      ("Another server starting on '%s', falling back to 1 IN listening pipe.\n",
                       pszPipeName
                       ));

                for (i = 1; i < (int) cConnectIns; i++) {

                    CANCELIO( rghPipeIn[i] );
                    DisconnectNamedPipe( rghPipeIn[i] );
                    CloseHandle( rghPipeIn[i] );
                    rghPipeIn[i] = INVALID_HANDLE_VALUE;

                }

                cWait = MAX_WAIT_HANDLES - cConnectIns + 1;

                cConnectIns = 1;

                //
                // We don't want to wait on the event handle, but it's easier
                // to have a handle in its slot, so dupe a handle to our own
                // process.  We toss the event handle without closing it so
                // it will stay around for future remote servers on the same
                // pipe name.
                //

                DuplicateHandle(
                    GetCurrentProcess(),
                    GetCurrentProcess(),
                    GetCurrentProcess(),
                    &rghWait[WAITIDX_PER_PIPE_EVENT],
                    0,
                    FALSE,
                    DUPLICATE_SAME_ACCESS
                    );
            }

            continue;
        }

        if (WAITIDX_CHILD_PROCESS == dwWait ||
            WAITIDX_READ_STDIN_DONE == dwWait) {

            if (INVALID_HANDLE_VALUE != hConnectOutTimer) {

                CloseHandle(hConnectOutTimer);
                hConnectOutTimer = INVALID_HANDLE_VALUE;
            }

            //
            // Cancel ConnectNamedPipe operations and close
            // the pipes
            //

            if (INVALID_HANDLE_VALUE != hPipeOut) {

                DisconnectNamedPipe( hPipeOut );
                CANCELIO( hPipeOut );
                CloseHandle( rghWait[WAITIDX_CONNECT_OUT] );
                rghWait[WAITIDX_CONNECT_OUT] = INVALID_HANDLE_VALUE;
            }

            for (i = 0;
                 i < (int) cConnectIns;
                 i++) {

                if (INVALID_HANDLE_VALUE != rghPipeIn[i]) {

                    TRACE(CONNECT, ("Tearing down listening IN pipe #%d.\n", i + 1));

                    DisconnectNamedPipe( rghPipeIn[i] );
                    CANCELIO( rghPipeIn[i] );
                    CloseHandle( rghPipeIn[i] );
                    rghPipeIn[i] = INVALID_HANDLE_VALUE;
                }

            }

            //
            // Cancel read against child process in/out pipes
            //

            if (INVALID_HANDLE_VALUE != hWriteChildStdIn) {

                CANCELIO( hWriteChildStdIn );
                CloseHandle( hWriteChildStdIn );
                hWriteChildStdIn = INVALID_HANDLE_VALUE;
            }

            if (INVALID_HANDLE_VALUE != hReadChildOutput) {

                CANCELIO( hReadChildOutput );
                CloseHandle( hReadChildOutput );
                hReadChildOutput = INVALID_HANDLE_VALUE;
            }

            //
            // Cancel client I/Os
            //

            bShuttingDownServer = TRUE;

            //
            // Note that CloseClient will remove entries from this list,
            // so we walk it starting at the head at each step.
            //

            for (pClientRemove = (PREMOTE_CLIENT) ClientListHead.Flink;
                 pClientRemove != (PREMOTE_CLIENT) &ClientListHead;
                 pClientRemove = (PREMOTE_CLIENT)  ClientListHead.Flink ) {

                CloseClient(pClientRemove);
            }

            //
            // on our way out...
            //

            break;
        }

        //
        // Unexpected WaitForMulipleObjectsEx return
        //

        printf("Remote: unknown wait return %d\n", dwWait);
        ErrorExit("fix srvmain.c");

    } // endless loop

    ShutAd(IsAdvertise);

    while (i = 0, GetExitCodeProcess(ChldProc, &i) &&
           STILL_ACTIVE == i) {

        printf("\nRemote: Waiting for child to exit.\n");
        WaitForSingleObjectEx(ChldProc, 10 * 1000, TRUE);
    }

    //
    // For some interesting reason when we're attached to
    // a debugger like ntsd and it exits, our printf
    // below comes out *after* the cmd.exe prompt, making
    // it look like we hung on exit even though cmd.exe is
    // patiently awaiting a command.  So suppress it.
    //

    if (hAttachedProcess == INVALID_HANDLE_VALUE) {
        printf("\nRemote exiting. Child (%s) exit code was %d.\n", ChildCmd, i);
    }

    CANCELIO(hWriteTempFile);
    CloseHandle(hWriteTempFile);
    hWriteTempFile = INVALID_HANDLE_VALUE;

    //
    // Flush any pending completion routines.
    //

    while (WAIT_IO_COMPLETION == SleepEx(50, TRUE)) {
        ;
    }

    if (!DeleteFile(SaveFileName)) {

        printf("Remote: Temp File %s not deleted..\n",SaveFileName);
    }

    return i;
}

VOID
FASTCALL
StartLocalSession(
    VOID
    )
{
    DWORD dwThreadId;
    char szHexAsciiId[9];

    pLocalClient = HeapAlloc(
                       hHeap,
                       HEAP_ZERO_MEMORY,
                       sizeof(*pLocalClient)
                       );

    if (!pLocalClient) {

        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        ErrorExit("Unable to allocate local client.");
    }

    pLocalClient->dwID = dwNextClientID++;
    sprintf(szHexAsciiId, "%08x", pLocalClient->dwID);
    CopyMemory(pLocalClient->HexAsciiId, szHexAsciiId, sizeof(pLocalClient->HexAsciiId));

    strcpy(pLocalClient->Name, "Local");
    pLocalClient->ServerFlags = SFLG_LOCAL;

    //
    // we need overlapped handles to stdin/stdout,
    // and woefully DuplicateHandle can't do it.
    // So we'll create two anonymous pipes and two
    // threads to shuffle data between stdin/stdout
    // and the pipes.  The server end of the pipes
    // is opened overlapped, the "client" end (used
    // by the threads) is not overlapped.
    //

    rgCopyPipe[0].hRead = GetStdHandle(STD_INPUT_HANDLE);
    if ( ! MyCreatePipeEx(&pLocalClient->PipeReadH, &rgCopyPipe[0].hWrite, NULL, 0, FILE_FLAG_OVERLAPPED, 0)) {
        ErrorExit("Cannot create local input pipe");
    }

    rgCopyPipe[1].hWrite = GetStdHandle(STD_OUTPUT_HANDLE);
    if ( ! MyCreatePipeEx(&rgCopyPipe[1].hRead, &pLocalClient->PipeWriteH, NULL, 0, 0, FILE_FLAG_OVERLAPPED)) {
        ErrorExit("Cannot create local output pipe");
    }

    rghWait[WAITIDX_READ_STDIN_DONE] = (HANDLE)
        _beginthreadex(
            NULL,                    // security
            0,                       // default stack size
            CopyPipeToPipe,          // proc
            (LPVOID) &rgCopyPipe[0], // parm
            0,                       // flags
            &dwThreadId
            );

    CloseHandle( (HANDLE)
        _beginthreadex(
            NULL,                    // security
            0,                       // default stack size
            CopyPipeToPipe,          // proc
            (LPVOID) &rgCopyPipe[1], // parm
            0,                       // flags
            &dwThreadId
            )
        );

    StartSession( pLocalClient );
}

//
// Two of these threads to deal with non-overlapped stdin/stdout.
// CRT is OK.
//

DWORD
WINAPI
CopyPipeToPipe(
    LPVOID   lpCopyPipeData
    )
{
    PCOPYPIPE psd = (PCOPYPIPE) lpCopyPipeData;
    DWORD cb;
    char achBuffer[BUFFSIZE];

    while (1) {
        if ( ! ReadFile(
                   psd->hRead,
                   achBuffer,
                   sizeof(achBuffer),
                   &cb,
                   NULL
                   )) {

            TRACE(COPYPIPE, ("CopyPipeToPipe ReadFile %s failed, exiting thread.\n",
                             (psd == &rgCopyPipe[0])
                                 ? "stdin"
                                 : "local client output pipe"));
            break;
        }

        if ( ! WriteFile(
                   psd->hWrite,
                   achBuffer,
                   cb,
                   &cb,
                   NULL
                   )) {

            TRACE(COPYPIPE, ("CopyPipeToPipe WriteFile %s failed, exiting thread.\n",
                             (psd == &rgCopyPipe[0])
                                 ? "local client input pipe"
                                 : "stdout"));
            break;
        }
    }

    return 0;
}

VOID
FASTCALL
StartSession(
    PREMOTE_CLIENT pClient
    )
{
    pClient->rSaveFile =
        CreateFile(
            SaveFileName,
            GENERIC_READ,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_OVERLAPPED,
            NULL
            );

    if ( ! pClient->rSaveFile) {

        printf("Remote:Cannot open ReadHandle to temp file:%d\n",GetLastError());

    } else {

        pClient->UserName[0] = 0;

        GetNamedPipeHandleState(
            pClient->PipeReadH,
            NULL,
            NULL,
            NULL,
            NULL,
            pClient->UserName,
            sizeof(pClient->UserName)
            );

        //
        // For every client except the local
        // stdin/stdout client, there's a copy of remote.exe
        // running in client mode on the other side.  Do
        // handshaking with it to setup options and check
        // versions.  HandshakeWithRemoteClient will start
        // the "normal" I/O cycle once the handshake cycle is
        // done.  Note it returns as soon as the first handshake
        // I/O is submitted.
        //

        if (pClient->ServerFlags & SFLG_LOCAL) {

            AddClientToHandshakingList(pClient);
            MoveClientToNormalList(pClient);

            //
            // Start read operation against this client's input.
            //

            StartReadClientInput(pClient);

            //
            // Start write cycle for client output from the temp
            // file.
            //

            StartReadTempFile(pClient);

        } else {

            HandshakeWithRemoteClient(pClient);
        }
    }
}

VOID
FASTCALL
CreatePipeAndIssueConnect(
    int  nIndex   // IN pipe index or OUT_PIPE
    )
{
    BOOL b;
    DWORD dwError;
    char szPipeName[BUFFSIZE];

    if (OUT_PIPE == nIndex) {
        TRACE(CONNECT, ("Creating listening OUT pipe.\n"));
    } else {
        TRACE(CONNECT, ("Creating listening IN pipe #%d.\n", nIndex + 1));
    }

    if (OUT_PIPE == nIndex) {

        sprintf(szPipeName, SERVER_WRITE_PIPE, ".", pszPipeName);

        hPipeOut =
            CreateNamedPipe(
                szPipeName,
                PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
                PIPE_TYPE_BYTE,
                PIPE_UNLIMITED_INSTANCES,
                0,
                0,
                0,
                &saPipe
                );

        if (INVALID_HANDLE_VALUE == hPipeOut) {

            ErrorExit("Unable to CreateNamedPipe OUT");
        }

        b = ConnectNamedPipe(hPipeOut, &olConnectOut);

        if ( ! b ) {

            dwError = GetLastError();

            if (ERROR_PIPE_CONNECTED == dwError) {

                b = TRUE;
            }
        }

        if ( b ) {

            TRACE(CONNECT, ("Quick connect on OUT pipe.\n"));

            HandleOutPipeConnected();

        } else {

            if (ERROR_IO_PENDING != dwError) {

                ErrorExit("ConnectNamedPipe out failed");
            }
        }

    } else {

        sprintf(szPipeName, SERVER_READ_PIPE, ".", pszPipeName);

        rghPipeIn[nIndex] =
            CreateNamedPipe(
                szPipeName,
                PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                PIPE_TYPE_BYTE,
                PIPE_UNLIMITED_INSTANCES,
                0,
                0,
                0,
                &saPipe
                );

        if (INVALID_HANDLE_VALUE == rghPipeIn[nIndex]) {

            if (ERROR_ACCESS_DENIED == GetLastError()) {
                if (DaclNameCount) {
                    ErrorExit("Unable to CreateNamedPipe, are YOU in the list of permitted users?");
                } else {
                    ErrorExit("Unable to CreateNamedPipe, maybe old remote server on same pipe name?");
                }
            } else {
                ErrorExit("Unable to CreateNamedPipe IN");
            }
        }

        b = ConnectNamedPipe(rghPipeIn[nIndex], &rgolConnectIn[nIndex]);

        if ( ! b ) {

            dwError = GetLastError();

            if (ERROR_PIPE_CONNECTED == dwError) {
                b = TRUE;
            }
        }

        if ( b ) {

            TRACE(CONNECT, ("Quick connect on IN pipe #%d.\n", nIndex));

            HandleInPipeConnected(nIndex);

        } else {

            if (ERROR_IO_PENDING != dwError) {

                ErrorExit("ConnectNamedPipe in failed");
            }
        }

    }

    if (OUT_PIPE == nIndex) {
        TRACE(CONNECT, ("Listening OUT pipe handle %x.\n", hPipeOut));
    } else {
        TRACE(CONNECT, ("Listening IN pipe #%d handle %x.\n", nIndex + 1, rghPipeIn[nIndex]));
    }
}

VOID
FASTCALL
HandleOutPipeConnected(
    VOID
    )
{
    LARGE_INTEGER DueTime;

    ResetEvent(rghWait[WAITIDX_CONNECT_OUT]);

    bOutPipeConnected = TRUE;

    TRACE(CONNECT, ("Two-pipe caller connected to OUT pipe %x.\n",
                    hPipeOut));

    //
    // Start a 1 minute timer in case we don't get a connection
    // on an IN pipe from this client, we'll recycle the OUT
    // pipe.
    //

    if (INVALID_HANDLE_VALUE != hConnectOutTimer) {

        DueTime.QuadPart = Int32x32To64(60 * 1000, -10000);

        pfnSetWaitableTimer(
            hConnectOutTimer,
            &DueTime,
            0,                     // not periodic, single-fire
            ConnectOutTimerFired,
            0,                     // arg to compl. rtn
            TRUE
            );
    }
}

VOID
APIENTRY
ConnectOutTimerFired(
    LPVOID pArg,
    DWORD  dwTimerLo,
    DWORD  dwTimerHi
    )
{
    UNREFERENCED_PARAMETER( pArg );
    UNREFERENCED_PARAMETER( dwTimerLo );
    UNREFERENCED_PARAMETER( dwTimerHi );

    //
    // We've had a connected OUT pipe for a minute now,
    // only two-pipe clients connect to that and they
    // immediately connect to IN afterwards.  Presumably
    // the client died between these two operations.  Until
    // we recycle the OUT pipe all two-pipe clients are
    // unable to connect getting pipe busy errors.
    //

    if ( ! bOutPipeConnected ) {

        TRACE(CONNECT, ("ConnectOut timer fired but Out pipe not connected.\n"));
        return;
    }

    TRACE(CONNECT, ("Two-pipe caller hung for 1 minute, recycling OUT pipe %x.\n",
                    hPipeOut));

    bOutPipeConnected = FALSE;

    CANCELIO(hPipeOut);
    DisconnectNamedPipe(hPipeOut);
    CloseHandle(hPipeOut);
    hPipeOut = INVALID_HANDLE_VALUE;

    CreatePipeAndIssueConnect(OUT_PIPE);

    //
    // In order for things to work reliably for 2-pipe clients
    // when there are multiple remote servers on the same pipename,
    // we need to tear down the listening IN pipe and recreate it so
    // that the oldest listening OUT pipe will be from the same process
    // as the oldest listening IN pipe.
    //

    if (1 == cConnectIns) {

        TRACE(CONNECT, ("Recycling IN pipe %x as well for round-robin behavior.\n",
                        rghPipeIn[0]));

        CANCELIO(rghPipeIn[0]);
        DisconnectNamedPipe(rghPipeIn[0]);
        CloseHandle(rghPipeIn[0]);
        rghPipeIn[0] = INVALID_HANDLE_VALUE;

        CreatePipeAndIssueConnect(0);
    }
}

VOID
FASTCALL
HandleInPipeConnected(
    int nIndex
    )
{
    PREMOTE_CLIENT pClient;
    char szHexAsciiId[9];

    ResetEvent(rghWait[WAITIDX_CONNECT_IN_BASE + nIndex]);

    if (nIndex >= (int) cConnectIns) {

        //
        // The I/O was cancelled on the excess
        // listening pipes, causing the event to
        // fire.
        //

        ASSERT(INVALID_HANDLE_VALUE == rghPipeIn[nIndex]);

        TRACE(CONNECT, ("IN pipe #%d, handle %x listen cancelled.\n",
                        nIndex + 1, rghPipeIn[nIndex]));

        return;
    }

    TRACE(CONNECT, ("Caller connected to IN pipe #%d, handle %x.\n",
                    nIndex + 1, rghPipeIn[nIndex]));

    //
    // A client is fully connected, but we don't know if
    // it's a single-pipe or two-pipe client.  Until
    // we do its PipeWriteH will be invalid.  We'll figure
    // it out in ReadClientNameCompleted.
    //

    pClient = HeapAlloc(
                  hHeap,
                  HEAP_ZERO_MEMORY,
                  sizeof(*pClient)
                  );

    if ( ! pClient) {

        printf("Out of memory connecting client, hanging up.\n");

        CloseHandle( rghPipeIn[nIndex] );
        rghPipeIn[nIndex] = INVALID_HANDLE_VALUE;
        CreatePipeAndIssueConnect( nIndex );

        if (bOutPipeConnected) {

             //
             // Hang up on the two-pipe caller connected to the
             // OUT pipe as well -- it may be this client or it
             // may be another, no way to tell, and really no
             // great need to because if it's another caller
             // we probably wouldn't be able to allocate memory
             // for it either.
             //
             // Also if we're using a single IN pipe for
             // multiple-server round-robin behavior we
             // want to recycle both pipes at the same time.
             //

            TRACE(CONNECT, ("Also hanging up on connected two-pipe caller on OUT pipe %x.\n",
                            hPipeOut));

            bOutPipeConnected = FALSE;

            if (INVALID_HANDLE_VALUE != hConnectOutTimer) {
                 pfnCancelWaitableTimer(hConnectOutTimer);
            }

            DisconnectNamedPipe(hPipeOut);
            CloseHandle(hPipeOut);
            hPipeOut = INVALID_HANDLE_VALUE;

            CreatePipeAndIssueConnect( OUT_PIPE );
        }

    } else {

        //
        // Initialize the Client
        //

        pClient->dwID = dwNextClientID++;
        sprintf(szHexAsciiId, "%08x", pClient->dwID);
        CopyMemory(pClient->HexAsciiId, szHexAsciiId, sizeof(pClient->HexAsciiId));

        pClient->PipeReadH   = rghPipeIn[nIndex];
        rghPipeIn[nIndex] = INVALID_HANDLE_VALUE;

        pClient->PipeWriteH  = INVALID_HANDLE_VALUE;

        TRACE(CONNECT, ("Handshaking new client %d (%x) on IN pipe handle %x.\n",
                        pClient->dwID, pClient, pClient->PipeReadH));

        //
        // Start another connect operation to replace this completed one.
        //

        CreatePipeAndIssueConnect( nIndex );

        //
        // Start session I/Os with the new client.  This will link it
        // into the handshaking list.
        //

        StartSession( pClient );

    }

}


SRVQUERY.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvQuery.c

Abstract:

    The server component of Remote.   Respond to client
    "remote /q" requests to list available remote servers
    on this machine.

Author:

    Dave Hart  30 May 1997
        derived from code by Mihai Costea in server.c.

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <io.h>
#include <string.h>
#include "Remote.h"
#include "Server.h"

VOID
FASTCALL
InitializeQueryServer(
    VOID
    )
{
    //
    // hQPipe is the handle to the listening query pipe,
    // if we're serving it.
    //

    hQPipe = INVALID_HANDLE_VALUE;

    QueryOverlapped.hEvent =
        CreateEvent(
            NULL,       // security
            TRUE,       // manual-reset
            FALSE,      // initially nonsignaled
            NULL        // unnamed
            );

    rghWait[WAITIDX_QUERYSRV_WAIT] =
        CreateMutex(
            &saPublic,   // security
            FALSE,       // not owner in case we open not create
            "MS RemoteSrv Q Mutex"
            );

    if (INVALID_HANDLE_VALUE == rghWait[WAITIDX_QUERYSRV_WAIT]) {

        ErrorExit("Remote: Unable to create/open query server mutex.\n");
    }
}

VOID
FASTCALL
QueryWaitCompleted(
    VOID
    )
{
    HANDLE hWait;
    DWORD dwThreadId;
    BOOL b;
    DWORD dwRead;

    //
    // The remote server (not us) which was servicing the query
    // pipe has left the arena.  Or someone has connected.
    //

    hWait = rghWait[WAITIDX_QUERYSRV_WAIT];

    if (hWait == QueryOverlapped.hEvent) {

        //
        // We're the query server and someone has connected.
        // Start a thread to service them.
        //

        b = GetOverlappedResult(hQPipe, &QueryOverlapped, &dwRead, TRUE);

        if ( !b && ERROR_PIPE_CONNECTED != GetLastError()) {

            TRACE(QUERY,("Connect Query Pipe returned %d\n", GetLastError()));

            if (INVALID_HANDLE_VALUE != hQPipe) {

                CloseHandle(hQPipe);
                hQPipe = INVALID_HANDLE_VALUE;
            }

        } else {

            TRACE(QUERY, ("Client connected to query pipe.\n"));

            ResetEvent(hWait);

            CloseHandle( (HANDLE)
                _beginthreadex(
                        NULL,             // security
                        0,                // default stack size
                        QueryHandlerThread,
                        (LPVOID) hQPipe,  // parameter
                        0,                // not suspended
                        &dwThreadId
                        ));

            hQPipe = INVALID_HANDLE_VALUE;
        }

    } else {

        TRACE(QUERY, ("Remote server entered query mutex, will handle queries.\n"));

        rghWait[WAITIDX_QUERYSRV_WAIT] = QueryOverlapped.hEvent;
    }

    //
    // Either a client has connected and we've handed that pipe
    // off to a query thread to deal with, or we're just starting
    // to serve the query pipe, or we had an error from
    // ConnectNamedPipe.  In any case we want to create another
    // query pipe instance and start listening on it.
    //

    ASSERT(INVALID_HANDLE_VALUE == hQPipe);

    StartServingQueryPipe();
}

VOID
FASTCALL
StartServingQueryPipe(
    VOID
    )
{
    BOOL  b;
    DWORD dwThreadId;
    char  fullname[BUFFSIZE];

    sprintf(fullname, QUERY_DEBUGGERS_PIPE, ".");

    do {      // hand off each pipe as connected until IO_PENDING

        hQPipe =
            CreateNamedPipe(
                fullname,
                PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                PIPE_TYPE_BYTE | PIPE_WAIT,
                PIPE_UNLIMITED_INSTANCES,
                0,
                0,
                0,
                &saPublic
                );

        if (INVALID_HANDLE_VALUE == hQPipe) {

            ErrorExit("Unable to create query server pipe.");
        }

        b = ConnectNamedPipe(hQPipe, &QueryOverlapped);

        if ( ! b && ERROR_PIPE_CONNECTED == GetLastError()) {

            b = TRUE;
        }

        if (b) {

            //
            // That was fast.
            //

            TRACE(QUERY, ("Client connected quickly to query pipe.\n"));

            CloseHandle( (HANDLE)
                _beginthreadex(
                    NULL,              // security
                    0,                 // default stack size
                    QueryHandlerThread,
                    (LPVOID) hQPipe,   // parameter
                    0,                 // not suspended
                    &dwThreadId
                    ));

            hQPipe = INVALID_HANDLE_VALUE;

        } else if (ERROR_IO_PENDING == GetLastError()) {

            //
            // The main thread will call QueryWaitCompleted when
            // someone connects.
            //

            TRACE(QUERY, ("Awaiting query pipe connect\n"));

        } else {

            sprintf(fullname, "Remote: error %d connecting query pipe.\n", GetLastError());

            OutputDebugString(fullname);
            ErrorExit(fullname);
        }

    } while (b);
}

DWORD
WINAPI
QueryHandlerThread(
    LPVOID   lpvArg
    )
{
    HANDLE hQueryPipe = (HANDLE) lpvArg;
    DWORD cb;
    BOOL  b;
    OVERLAPPED ol;
    QUERY_MESSAGE QData;
    char  pIn[1];

    ZeroMemory(&ol, sizeof(ol));

    ol.hEvent =
        CreateEvent(
            NULL,       // security
            TRUE,       // manual-reset
            FALSE,      // initially nonsignaled
            NULL        // unnamed
            );

    // get command

    b = ReadFileSynch(
            hQueryPipe,
            pIn,
            1,
            &cb,
            0,
            &ol
            );

    if ( ! b || 1 != cb ) {
        TRACE(QUERY, ("Query server unable to read byte from query pipe.\n"));
        goto failure;
    }

    TRACE(QUERY, ("Query server read command '%c'\n", pIn[0]));

        //
        // !!!!!!
        // REMOVE 'h' support, it's only here for transitional compatibility
        // with 1570+ remote /q original server implementation.
        //

        if(pIn[0] == 'h') {

            DWORD dwMinusOne = (DWORD) -1;

            b = WriteFileSynch(
                    hQueryPipe,
                    &dwMinusOne,
                    sizeof(dwMinusOne),
                    &cb,
                    0,
                    &ol
                    );

            if ( !b || sizeof(dwMinusOne) != cb )
            {
                goto failure;
            }
        }

    if(pIn[0] == 'q') {

        QData.size  = 0;
        QData.allocated = 0;
        QData.out   = NULL;

        EnumWindows(EnumWindowProc, (LPARAM)&QData);

        b = WriteFileSynch(
                hQueryPipe,
                &QData.size,
                sizeof(QData.size),
                &cb,
                0,
                &ol
                );

        if ( ! b || sizeof(int) != cb) {

            TRACE(QUERY, ("Remote: Can't write query length\n"));
            goto failure;
        }

        if (QData.size) {         // anything to say?

            b = WriteFileSynch(
                     hQueryPipe,
                     QData.out,
                     QData.size * sizeof(char),
                     &cb,
                     0,
                     &ol
                     );

            free(QData.out);

            if ( ! b || QData.size * sizeof(char) != cb) {

                TRACE(QUERY, ("Remote: Can't write query"));
                goto failure;
            }

            TRACE(QUERY, ("Sent query response\n"));
        }
    }

    FlushFileBuffers(hQueryPipe);

  failure:
    DisconnectNamedPipe(hQueryPipe);
    CloseHandle(hQueryPipe);
    CloseHandle(ol.hEvent);

    return 0;
}

BOOL
CALLBACK
EnumWindowProc(
    HWND hWnd,
    LPARAM lParam
    )
{
    #define MAX_TITLELEN 200
    QUERY_MESSAGE *pQm;
    int titleLen;
    char title[MAX_TITLELEN];
    char* tmp;

    pQm = (QUERY_MESSAGE*)lParam;

    if(titleLen = GetWindowText(hWnd, title, sizeof(title)/sizeof(title[0])))
    {
        //
        // search for all windows that are visible
        //

        if (strstr(title, "] visible") &&
            strstr(title, "[Remote "))
        {
            if(pQm->size)                           // if message not empty
                pQm->out[(pQm->size)++] = '\n';     // overwrite ending null with \n
            else
            {
                pQm->out  = (char*)malloc(MAX_TITLELEN);     // first allocation
                if(!pQm->out)
                {
                    printf("\nOut of memory\n");
                    return FALSE;
                }
                pQm->allocated = MAX_TITLELEN;
            }

            // fill the result

            if((pQm->size + titleLen) >= pQm->allocated)
            {
                tmp = (char*)realloc(pQm->out, pQm->allocated + MAX_TITLELEN);
                if(!tmp)
                {
                    printf("\nOut of memory\n");
                    free(pQm->out);
                    pQm->size = 0;
                    return FALSE;
                }
                pQm->out = tmp;
                pQm->allocated += MAX_TITLELEN;
            }
            strcpy(pQm->out + pQm->size, title);
            pQm->size += titleLen;
        }
    }

    return TRUE;
    #undef MAX_TITLELEN
}


SRVSTOC.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvStoC.c

Abstract:

    This file implements the server-to-client flow
    of data for remote server.  The data is the output
    of the child program intermingled with client input.

Author:

    Dave Hart  30 May 1997

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include "Remote.h"
#include "Server.h"

VOID
FASTCALL
StartServerToClientFlow(
    VOID
    )
{
    PREMOTE_CLIENT pClient;

    //
    // Start read operations against the temp file for
    // all active clients that aren't currently doing
    // read temp/write client operations and that are
    // fully connected.
    //

    for (pClient = (PREMOTE_CLIENT) ClientListHead.Flink;
         pClient != (PREMOTE_CLIENT) &ClientListHead;
         pClient = (PREMOTE_CLIENT) pClient->Links.Flink ) {

        if (! pClient->cbWrite) {

            StartReadTempFile( pClient );
        }
    }
}

VOID
FASTCALL
StartReadTempFile(
    PREMOTE_CLIENT pClient
    )
{
    //
    // pClient->cbWrite is used dually.  WriteSessionOutputCompleted
    // uses it when 0 bytes are written to know how much to ask
    // to write when it resubmits the request.  We use it to
    // indicate whether a read temp/write session chain of I/Os
    // is currently active for this client.
    //

    if (pClient->cbWrite) {

        ErrorExit("StartReadTempFile entered with nonzero cbWrite.");
    }

    if (dwWriteFilePointer > pClient->dwFilePos) {

        pClient->cbWrite = min(BUFFSIZE,
                               dwWriteFilePointer - pClient->dwFilePos);

        pClient->WriteOverlapped.OffsetHigh = 0;
        pClient->WriteOverlapped.Offset = pClient->dwFilePos;

        if ( ! ReadFileEx(
                   pClient->rSaveFile,
                   pClient->ReadTempBuffer,
                   pClient->cbWrite,
                   &pClient->WriteOverlapped,
                   ReadTempFileCompleted
                   )) {

            if (ERROR_HANDLE_EOF == GetLastError()) {

                pClient->cbWrite = 0;

            } else {

                TRACE(SESSION, ("ReadFileEx for temp file failed error %d, closing client.\n", GetLastError()));

                CloseClient(pClient);
            }
        }

    }
}

VOID
WINAPI
ReadTempFileCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, WriteOverlapped);

    if (HandleSessionError(pClient, dwError)) {

        return;
    }

    if (cbRead != pClient->cbWrite) {

        TRACE(SESSION, ("Read %d from temp file asked for %d\n", cbRead, pClient->cbWrite));
    }

    if (cbRead) {

        pClient->cbReadTempBuffer = cbRead;
        pClient->dwFilePos += cbRead;

        StartWriteSessionOutput(pClient);

    } else {

        //
        // Note that the server to client flow is halting for now
        // for this client.
        //

        pClient->cbWrite = 0;
    }
}

VOID
FASTCALL
StartWriteSessionOutput(
    PREMOTE_CLIENT pClient
    )
{
    DWORD cbRead;
    char *pch;

    cbRead = pClient->cbReadTempBuffer;

    //
    // We need to split commands from other text read
    // from the temp file and hold off on writing them
    // to the client until we make sure we're not the
    // client that submitted it.  This isn't perfect
    // since we match on client name which can be
    // duplicated but it solves the problem of
    // duplicated input most of the time.
    //

    for (pch = pClient->ReadTempBuffer;
         pch < pClient->ReadTempBuffer + cbRead;
         pch++) {

        if ( ! (pClient->ServerFlags & SFLG_READINGCOMMAND) ) {

            if (BEGINMARK == *pch) {

                pClient->ServerFlags |= SFLG_READINGCOMMAND;

                if (pch != pClient->ReadTempBuffer &&
                    pClient->cbWriteBuffer) {

                    //
                    // Start a write of everything we've come across
                    // before the start of this command, with
                    // WriteSessionOutputCompletedWriteNext specified
                    // so we can continue processing the remainder
                    // of pReadTempBuffer.
                    //

                    pClient->cbReadTempBuffer -= ( pch - pClient->ReadTempBuffer) + 1;
                    cbRead = pClient->cbReadTempBuffer;

                    #if DBG
                        if (pClient->cbReadTempBuffer == (DWORD)-1) {
                            ErrorExit("cbReadTempBuffer underflow.");
                        }
                    #endif

                    MoveMemory(pClient->ReadTempBuffer, pch + 1, cbRead);

                    pClient->cbWrite = pClient->cbWriteBuffer;

                    pClient->WriteOverlapped.OffsetHigh = 0;
                    pClient->WriteOverlapped.Offset = 0;

                    if ( ! WriteFileEx(
                               pClient->PipeWriteH,
                               pClient->WriteBuffer,
                               pClient->cbWrite,
                               &pClient->WriteOverlapped,
                               WriteSessionOutputCompletedWriteNext
                               )) {

                        CloseClient(pClient);
                    }

                    TRACE(SESSION, ("%x Wrote %d bytes pre-command output\n", pClient, pClient->cbWrite));

                    pClient->cbWriteBuffer = 0;

                    return;
                }

            } else {

                if (pClient->cbWriteBuffer == BUFFSIZE) {

                    ErrorExit("cbWriteBuffer overflow");
                }

                pClient->WriteBuffer[ pClient->cbWriteBuffer++ ] = *pch;
            }

        } else {

            if (ENDMARK == *pch ||
                pClient->cbCommandBuffer == BUFFSIZE) {

                pClient->ServerFlags &= ~SFLG_READINGCOMMAND;

                //
                // Preceding ENDMARK is the pClient in hex ascii of the
                // client that generated the command, not null terminated.
                //

                if (ENDMARK == *pch) {

                    pClient->cbCommandBuffer -=
                        min(pClient->cbCommandBuffer, sizeof(pClient->HexAsciiId));

                }

                //
                // We hide each client's input from their output pipe
                // because their local remote.exe has already displayed it.
                //

                if ( pClient->cbCommandBuffer &&
                     ! (ENDMARK == *pch &&
                        ! memcmp(
                              pch - sizeof(pClient->HexAsciiId),
                              pClient->HexAsciiId,
                              sizeof(pClient->HexAsciiId)))) {

                    //
                    // Start a write of the accumulated command with
                    // WriteSessionOutputCompletedWriteNext specified
                    // so we can continue processing the remainder
                    // of pReadTempBuffer.
                    //

                    pClient->cbReadTempBuffer -= (pch - pClient->ReadTempBuffer) + 1;
                    MoveMemory(pClient->ReadTempBuffer, pch + 1, pClient->cbReadTempBuffer);

                    pClient->cbWrite = pClient->cbCommandBuffer;
                    pClient->cbCommandBuffer = 0;

                    pClient->WriteOverlapped.OffsetHigh = 0;
                    pClient->WriteOverlapped.Offset = 0;

                    if ( ! WriteFileEx(
                               pClient->PipeWriteH,
                               pClient->CommandBuffer,
                               pClient->cbWrite,
                               &pClient->WriteOverlapped,
                               WriteSessionOutputCompletedWriteNext
                               )) {

                        CloseClient(pClient);
                        return;

                    } else {

                        TRACE(SESSION, ("%x Wrote %d bytes command\n", pClient, pClient->cbWrite));

                        return;

                    }

                } else {

                    //
                    // We're eating this command for this session.
                    //

                    pClient->cbCommandBuffer = 0;
                }

            } else {

                pClient->CommandBuffer[ pClient->cbCommandBuffer++ ] = *pch;

            }
        }
    }

    //
    // We're done with the ReadTempBuffer.
    //

    pClient->cbReadTempBuffer = 0;

    if (pClient->cbWriteBuffer) {

        pClient->cbWrite = pClient->cbWriteBuffer;

        pClient->WriteOverlapped.OffsetHigh = 0;
        pClient->WriteOverlapped.Offset = 0;

        if ( ! WriteFileEx(
                   pClient->PipeWriteH,
                   pClient->WriteBuffer,
                   pClient->cbWrite,
                   &pClient->WriteOverlapped,
                   WriteSessionOutputCompletedReadNext
                   )) {

            CloseClient(pClient);
            return;

        } else {

            TRACE(SESSION, ("%x Wrote %d bytes normal\n", pClient, pClient->cbWrite));

            pClient->cbWriteBuffer = 0;
        }

    } else {

        //
        // Write buffer is empty.
        //

        pClient->cbWrite = 0;

        StartReadTempFile(pClient);

    }
}

BOOL
FASTCALL
WriteSessionOutputCompletedCommon(
    PREMOTE_CLIENT pClient,
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    )
{
    if (HandleSessionError(pClient, dwError)) {

        return TRUE;
    }

    if (!pClient->cbWrite) {

        ErrorExit("Zero cbWrite in WriteSessionOutputCompletedCommon");
    }

    if (!cbWritten && pClient->cbWrite) {

        printf("WriteSessionOutput zero bytes written of %d.\n", pClient->cbWrite);
        ErrorExit("WriteSessionOutputCompletedCommon failure");

        return TRUE;
    }

    #if DBG
        if (cbWritten != pClient->cbWrite) {
            printf("%x cbWritten %d cbWrite %d\n", pClient, cbWritten, pClient->cbWrite);
        }
    #endif

    return FALSE;
}

VOID
WINAPI
WriteSessionOutputCompletedWriteNext(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, WriteOverlapped);

    if (WriteSessionOutputCompletedCommon(
            pClient,
            dwError,
            cbWritten,
            WriteSessionOutputCompletedWriteNext
            )) {

        return;
    }

    StartWriteSessionOutput(pClient);
}

VOID
WINAPI
WriteSessionOutputCompletedReadNext(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, WriteOverlapped);

    if (WriteSessionOutputCompletedCommon(
            pClient,
            dwError,
            cbWritten,
            WriteSessionOutputCompletedReadNext
            )) {

        return;
    }

    //
    // Start another temp file read.
    //

    pClient->cbWrite = 0;

    StartReadTempFile(pClient);
}


SRVUTIL.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 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.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvUtil.c

Abstract:

    The server component of Remote. It spawns a child process
    and redirects the stdin/stdout/stderr of child to itself.
    Waits for connections from clients - passing the
    output of child process to client and the input from clients
    to child process.

Author:

    Rajivendra Nath  2-Jan-1992
    Dave Hart  30 May 1997 split from Server.c

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <io.h>
#include <string.h>
#include "Remote.h"
#include "Server.h"

#define COMMANDFORMAT     "%c%-20s    [%-12s %s]\n%08x%c"
#define CMDSTRING(OutBuff,OutSize,InpBuff,Client,szTime,ForceShow) \
{                                                                  \
    char *pch;                                                     \
                                                                   \
    for (pch = InpBuff;                                            \
         *pch;                                                     \
         pch++) {                                                  \
                                                                   \
        if (ENDMARK == *pch ||                                     \
            BEGINMARK == *pch) {                                   \
                                                                   \
            *pch = '`';                                            \
        }                                                          \
    }                                                              \
                                                                   \
    OutSize =                                                      \
        sprintf(                                                   \
            (OutBuff),                                             \
            COMMANDFORMAT,                                         \
            BEGINMARK,                                             \
            (InpBuff),                                             \
            (Client)->Name,                                        \
            (szTime),                                              \
            (ForceShow) ? 0 : (Client)->dwID,                      \
            ENDMARK                                                \
            );                                                     \
}

/*************************************************************/
// GetFormattedTime -- returns pointer to formatted time
//
// returns pointer to static buffer, only the main thread
// should use this.
//

PCHAR
GetFormattedTime(
    BOOL bDateToo
    )
{
    static char szTime[64];
    int cch = 0;

    if (bDateToo) {

        cch =
            GetDateFormat(
                LOCALE_USER_DEFAULT,
                0,
                NULL,    // current date
                "ddd",   // short day of week
                szTime,
                sizeof szTime
                );

        // cch includes null terminator, change it to
        // a space to separate from time.

        szTime[ cch - 1 ] = ' ';
    }

    //
    // Get time and format to characters
    //

    GetTimeFormat(
        LOCALE_USER_DEFAULT,
        TIME_NOSECONDS,
        NULL,   // use current time
        NULL,   // use default format
        szTime + cch,
        (sizeof szTime) - cch );

    return szTime;
}

/*************************************************************/

BOOL
FilterCommand(
    REMOTE_CLIENT *cl,
    char *buff,
    int dread
    )
{
    char       tmpchar;
    DWORD      tmp;
    int        len;
    DWORD      ThreadID;
    char       inp_buff[2048];
    char       ch[3];

    if (dread==0)
        return(FALSE);

    buff[dread]=0;

    if (buff[0]==COMMANDCHAR)
    {

        switch(buff[1])
        {
        case 'k':
        case 'K':

                if (INVALID_HANDLE_VALUE != hWriteChildStdIn) {

                    printf("Remote: killing child softly, @K again to be more convincing.\n");

                    CANCELIO( hWriteChildStdIn );
                    CloseHandle( hWriteChildStdIn );
                    hWriteChildStdIn = INVALID_HANDLE_VALUE;

                    GenerateConsoleCtrlEvent(CTRL_CLOSE_EVENT, 0);
                    SleepEx(200, TRUE);
                    cPendingCtrlCEvents++;
                    GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
                    SleepEx(20, TRUE);
                    GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0);

                } else {

                    printf("Remote: Resorting to TerminateProcess.\n");

                    TerminateProcess(ChldProc, ERROR_PROCESS_ABORTED);
                }

                 break;
        case 's':
        case 'S':
                CloseHandle( (HANDLE)
                    _beginthreadex(
                        NULL,             // security
                        0,                // default stack size
                        SendStatus,
                        (void *) cl->PipeWriteH,
                        0,                // not suspended
                        &ThreadID
                        ));
                break;

        case 'p':
        case 'P':
            {
                char  *msg;

                msg = HeapAlloc(                    // freed by ShowPopup
                          hHeap,
                          HEAP_ZERO_MEMORY,
                          4096
                          );

                if ( ! msg) {
                    break;
                }

                sprintf(msg,"From %s %s [%s]\n\n%s\n",cl->Name,cl->UserName,GetFormattedTime(TRUE),&buff[2]);

                if (WriteFileSynch(hWriteTempFile,msg,strlen(msg),&tmp,dwWriteFilePointer,&olMainThread)) {
                    dwWriteFilePointer += tmp;
                    StartServerToClientFlow();
                }

                CloseHandle( (HANDLE)
                    CreateThread(                              // no CRT for ShowPopup
                        NULL,             // security
                        0,                // default stack size
                        ShowPopup,
                        (void *) msg,
                        0,                // not suspended
                        &ThreadID
                        ));

                break;
             }

        case 'm':
        case 'M':
                buff[dread-2]=0;
                CMDSTRING(inp_buff,len,buff,cl,GetFormattedTime(TRUE),TRUE);

                if (WriteFileSynch(hWriteTempFile,inp_buff,len,&tmp,dwWriteFilePointer,&olMainThread)) {
                    dwWriteFilePointer += tmp;
                    StartServerToClientFlow();
                }
                break;

        case '@':
                buff[dread-2]=0;
                CMDSTRING(inp_buff,len,&buff[1],cl,GetFormattedTime(FALSE),FALSE);
                if (WriteFileSynch(hWriteTempFile,inp_buff,len,&tmp,dwWriteFilePointer,&olMainThread)) {
                    dwWriteFilePointer += tmp;
                    StartServerToClientFlow();
                }
                //
                // Remove the first @ sign
                //
                MoveMemory(buff,&buff[1],dread-1);
                buff[dread-1]=' ';
                return(FALSE); //Send it it to the chile process

        default :
                sprintf(inp_buff,"%s","** Unknown Command **\n");
                if (WriteFileSynch(hWriteTempFile,inp_buff,strlen(inp_buff),&tmp,dwWriteFilePointer,&olMainThread)) {
                    dwWriteFilePointer += tmp;
                    // we do this below // StartServerToClientFlow();
                }
        case 'h':
        case 'H':
                sprintf(inp_buff,"%cM: To Send Message\n",COMMANDCHAR);
                if (WriteFileSynch(hWriteTempFile,inp_buff,strlen(inp_buff),&tmp,dwWriteFilePointer,&olMainThread)) {
                    dwWriteFilePointer += tmp;
                }
                sprintf(inp_buff,"%cP: To Generate popup\n",COMMANDCHAR);
                if (WriteFileSynch(hWriteTempFile,inp_buff,strlen(inp_buff),&tmp,dwWriteFilePointer,&olMainThread)) {
                    dwWriteFilePointer += tmp;
                }
                sprintf(inp_buff,"%cK: To kill the server\n",COMMANDCHAR);
                if (WriteFileSynch(hWriteTempFile,inp_buff,strlen(inp_buff),&tmp,dwWriteFilePointer,&olMainThread)) {
                    dwWriteFilePointer += tmp;
                }
                sprintf(inp_buff,"%cH: This Help\n",COMMANDCHAR);
                if (WriteFileSynch(hWriteTempFile,inp_buff,strlen(inp_buff),&tmp,dwWriteFilePointer,&olMainThread)) {
                    dwWriteFilePointer += tmp;
                }
                StartServerToClientFlow();
                break;
        }
        return(TRUE);
    }

    if ((buff[0]<26))
    {
        BOOL ret=FALSE;

        sprintf(ch, "^%c", buff[0] + 'A' - 1);

        if (buff[0]==CTRLC)
        {
            // show this even to this client
            CMDSTRING(inp_buff,len,ch,cl,GetFormattedTime(FALSE),TRUE);

            cPendingCtrlCEvents++;
            GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
            ret = TRUE;  // Already sent to child

        } else {

            CMDSTRING(inp_buff,len,ch,cl,GetFormattedTime(FALSE),FALSE);
        }

        if (WriteFileSynch(hWriteTempFile,inp_buff,len,&tmp,dwWriteFilePointer,&olMainThread)) {
            dwWriteFilePointer += tmp;
            StartServerToClientFlow();
        }
        return(ret); //FALSE:send it to child StdIn
    }

    tmpchar=buff[dread-2]; //must be 13;but just incase
    buff[dread-2]=0;
    CMDSTRING(inp_buff,len,buff,cl,GetFormattedTime(FALSE),FALSE);
    buff[dread-2]=tmpchar;
    if (WriteFileSynch(hWriteTempFile,inp_buff,len,&tmp,dwWriteFilePointer,&olMainThread)) {
        dwWriteFilePointer += tmp;
        StartServerToClientFlow();
    }
    return(FALSE);
}

/*************************************************************/
HANDLE
ForkChildProcess(           // Creates a new process
    char *cmd,              // Redirects its stdin,stdout
    PHANDLE inH,            // and stderr - returns the
    PHANDLE outH            // corresponding pipe ends.
    )

{
    SECURITY_ATTRIBUTES lsa;
    STARTUPINFO         si;
    PROCESS_INFORMATION pi;
    HANDLE ChildIn;
    HANDLE ChildOut, ChildOutDup;
    HANDLE hWriteChild;
    HANDLE hReadChild;
    BOOL Success;

    BOOL                                     // pipeex.c
    APIENTRY
    MyCreatePipeEx(
        OUT LPHANDLE lpReadPipe,
        OUT LPHANDLE lpWritePipe,
        IN LPSECURITY_ATTRIBUTES lpPipeAttributes,
        IN DWORD nSize,
        DWORD dwReadMode,
        DWORD dwWriteMode
        );

    lsa.nLength=sizeof(SECURITY_ATTRIBUTES);
    lsa.lpSecurityDescriptor=NULL;
    lsa.bInheritHandle=TRUE;

    //
    // Create Parent_Write to ChildStdIn Pipe.  Then
    // duplicate the parent copy to a noninheritable
    // handle and close the inheritable one so that
    // the child won't be holding open a handle to
    // the server end of its stdin pipe when we try
    // to nuke that pipe to close the child.
    //

    Success = MyCreatePipeEx(
                  &ChildIn,
                  &hWriteChild,
                  &lsa,
                  0,
                  0,
                  FILE_FLAG_OVERLAPPED) &&

              DuplicateHandle(
                  GetCurrentProcess(),
                  hWriteChild,
                  GetCurrentProcess(),
                  inH,
                  0,                       // ignored b/c SAME_ACCESS
                  FALSE,                   // not inheritable
                  DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE
                  );

    if (!Success) {
        ErrorExit("Could Not Create Parent-->Child Pipe");
    }

    //
    //Create ChildStdOut/stderr to Parent_Read pipe
    //

    Success = MyCreatePipeEx(
                  &hReadChild,
                  &ChildOut,
                  &lsa,
                  0,
                  FILE_FLAG_OVERLAPPED,
                  0) &&

              DuplicateHandle(
                  GetCurrentProcess(),
                  hReadChild,
                  GetCurrentProcess(),
                  outH,
                  0,                       // ignored b/c SAME_ACCESS
                  FALSE,                   // not inheritable
                  DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE
                  ) &&

              DuplicateHandle(
                  GetCurrentProcess(),
                  ChildOut,
                  GetCurrentProcess(),
                  &ChildOutDup,
                  0,                       // ignored b/c SAME_ACCESS
                  TRUE,                    // inheritable
                  DUPLICATE_SAME_ACCESS
                  );

    if (!Success) {
        ErrorExit("Could Not Create Child-->Parent Pipe");
    }

    ZeroMemory(&si, sizeof(si));
    si.cb            = sizeof(STARTUPINFO);
    si.dwFlags       = STARTF_USESTDHANDLES;
    si.hStdInput     = ChildIn;
    si.hStdOutput    = ChildOut;
    si.hStdError     = ChildOutDup;
    si.wShowWindow   = SW_SHOW;

    //
    // Create Child Process
    //

    if ( ! CreateProcess(
               NULL,
               cmd,
               NULL,
               NULL,
               TRUE,
               GetPriorityClass( GetCurrentProcess() ),
               NULL,
               NULL,
               &si,
               &pi)) {

        if (GetLastError()==2) {
            printf("Executable %s not found\n",cmd);
        } else {
            printf("CreateProcess(%s) failed, error %d.\n", cmd, GetLastError());
        }
        ErrorExit("Could Not Create Child Process");
    }

    //
    // Close unneccesary Handles
    //

    CloseHandle(ChildIn);
    CloseHandle(ChildOut);
    CloseHandle(ChildOutDup);
    CloseHandle(pi.hThread);

    pidChild = pi.dwProcessId;

    return(pi.hProcess);
}

//
// SendStatus runs as its own thread, with C runtime available.
//

DWORD
WINAPI
SendStatus(
    LPVOID   lpSendStatusParm
    )
{
    HANDLE hClientPipe = (HANDLE) lpSendStatusParm;
    char *pch;
    DWORD tmp;
    PREMOTE_CLIENT pClient;
    OVERLAPPED ol;
    char  buff[2048];
    char szSep[] = " ------------------------------\n";

    //
    // Since we're in our own thread we need our own
    // overlapped structure for our client pipe writes.
    //

    ZeroMemory(&ol, sizeof(ol));

    ol.hEvent =
        CreateEvent(
            NULL,      // security
            TRUE,      // auto-reset
            FALSE,     // initially nonsignaled
            NULL       // unnamed
            );

    //
    // Dump the closing client list
    //

    pch = buff;

    EnterCriticalSection(&csClosingClientList);

    for (pClient = (PREMOTE_CLIENT) ClosingClientListHead.Flink;
         pClient != (PREMOTE_CLIENT) &ClosingClientListHead;
         pClient = (PREMOTE_CLIENT) pClient->Links.Flink ) {

         if (pch + 60 > buff + sizeof(buff)) {

            break;
         }

         pch += sprintf(pch, "%d: %s %s (Disconnected)\n", pClient->dwID, pClient->Name, pClient->UserName);
    }

    LeaveCriticalSection(&csClosingClientList);

    WriteFileSynch(hClientPipe, buff, pch - buff, &tmp, 0, &ol);

    WriteFileSynch(hClientPipe, szSep, sizeof(szSep) - 1, &tmp, 0, &ol);

    //
    // Dump the normal client list
    //

    pch = buff;

    EnterCriticalSection(&csClientList);

    for (pClient = (PREMOTE_CLIENT) ClientListHead.Flink;
         pClient != (PREMOTE_CLIENT) &ClientListHead;
         pClient = (PREMOTE_CLIENT) pClient->Links.Flink ) {

         if (pch + 60 > buff + sizeof(buff)) {

            break;
         }

         pch += sprintf(pch, "%d: %s %s\n", pClient->dwID, pClient->Name, pClient->UserName);
    }

    LeaveCriticalSection(&csClientList);

    WriteFileSynch(hClientPipe, buff, pch - buff, &tmp, 0, &ol);

    WriteFileSynch(hClientPipe, szSep, sizeof(szSep) - 1, &tmp, 0, &ol);

    //
    // Dump the handshaking client list
    //

    pch = buff;

    EnterCriticalSection(&csHandshakingList);

    for (pClient = (PREMOTE_CLIENT) HandshakingListHead.Flink;
         pClient != (PREMOTE_CLIENT) &HandshakingListHead;
         pClient = (PREMOTE_CLIENT) pClient->Links.Flink ) {

         if (pch + 60 > buff + sizeof(buff)) {

            break;
         }

         pch += sprintf(pch, "%d: %s %s (Connecting)\n", pClient->dwID, pClient->Name, pClient->UserName);
    }

    LeaveCriticalSection(&csHandshakingList);

    WriteFileSynch(hClientPipe, buff, pch - buff, &tmp, 0, &ol);

    WriteFileSynch(hClientPipe, szSep, sizeof(szSep) - 1, &tmp, 0, &ol);

    //
    // Dump summary information.
    //

    pch = buff;

    pch += sprintf(pch, "REMOTE /C %s %s\n", HostName, PipeName);
    pch += sprintf(pch, "Command: %s\n", ChildCmd);
    pch += sprintf(pch, "Windows NT %d.%d build %d \n",
                   OsVersionInfo.dwMajorVersion,
                   OsVersionInfo.dwMinorVersion,
                   OsVersionInfo.dwBuildNumber);

    WriteFileSynch(hClientPipe, buff, pch - buff, &tmp, 0, &ol);

    WriteFileSynch(hClientPipe, szSep, sizeof(szSep) - 1, &tmp, 0, &ol);

    CloseHandle(ol.hEvent);

    return 0;
}

/*************************************************************/

DWORD                // NO CRT for ShowPopup
WINAPI
ShowPopup(
    void *vpArg
    )
{
    char *msg = (char *) vpArg;

    MessageBox(GetActiveWindow(),msg,"** REMOTE.EXE **",MB_OK|MB_SETFOREGROUND);
    HeapFree(hHeap, 0, msg);
    return(0);

}

/*************************************************************/

//
// SrvCtrlHand is the console event handler for the server side
// of remote.  If our stdin is a console handle, we've disabled
// generation of ^C events by the console code.  Therefore
// any we see are either generated by us for the benefit of
// our child processes sharing the console, or generated by
// some other process.  We want to ignore the ones we generate
// (since we're already done with everything that needs to be
// done at that point), and also ignore ^C's generated by
// other processes since we don't need to do anything with those.
// For example if someone runs:
//
// remote /s "remote /s cmd inner" outer
//
// Then local keyboard ^C's will be read by the outer remote.exe
// from its stdin handle, then it will generate a CTRL_C_EVENT that
// all processes in the console will see, including both remote.exe's
// and the child cmd.exe.  So the handler needs do nothing but indicate
// the event was handled by returning TRUE so the default handler
// won't kill us.  For ^BREAK we want to specifically kill our child
// process so that cmd.exe and others that ignore ^BREAK will go away.
// Of course this won't kill our grandchildren and so on.  Oh well.
//
// For all other events we return FALSE and let the default handler
// have it.
//

BOOL
WINAPI
SrvCtrlHand(
    DWORD event
    )
{
    BOOL bRet = FALSE;
    DWORD cb;
    DWORD dwTempFileOffset;
    OVERLAPPED ol;
    char szTime[64];
    char szCmd[128];

    if (event == CTRL_BREAK_EVENT) {

        TerminateProcess(ChldProc, 3);
        bRet = TRUE;

    } else if (event == CTRL_C_EVENT) {

        if ( ! cPendingCtrlCEvents ) {

            //
            // This came from the local keyboard or
            // was generated by another process in
            // this console.  Echo it as a local
            // command.  We have use GetTimeFormat
            // here not our GetFormattedTime since
            // the latter is for the use of the
            // main thread only.
            //

            GetTimeFormat(
                LOCALE_USER_DEFAULT,
                TIME_NOSECONDS,
                NULL,   // use current time
                NULL,   // use default format
                szTime,
                sizeof(szTime)
                );

            CMDSTRING(szCmd, cb, "^C", pLocalClient, szTime, TRUE);

            ZeroMemory(&ol, sizeof(ol));
            ol.hEvent =
                CreateEvent(
                    NULL,      // security
                    TRUE,      // auto-reset
                    FALSE,     // initially nonsignaled
                    NULL       // unnamed
                    );

            //
            // Practically all writes to the tempfile are happening on
            // the primary server thread.  We're on a Ctrl-C thread.
            // We can't start the server to client I/O going after
            // writing because we're on the wrong thread, so we
            // punt.  To fix this we need an event we can signal
            // that causes the main thread to call StartServerToClientFlow.
            //

            dwTempFileOffset = dwWriteFilePointer;
            dwWriteFilePointer += cb;
            WriteFileSynch(hWriteTempFile, szCmd, cb, &cb, dwTempFileOffset, &ol);
            // wrong thread // StartServerToClientFlow();

            CloseHandle(ol.hEvent);

        } else {

            //
            // We generated this event in response to a ^C received from
            // a client, it's already been displayed to all clients.
            //

            cPendingCtrlCEvents--;
        }

        bRet = TRUE;

    }

    return bRet;
}

/*************************************************************/

PSECURITY_DESCRIPTOR
FormatSecurityDescriptor(
    CHAR * * DenyNames,
    DWORD    DenyCount,
    CHAR * * Names,
    DWORD    Count)
{
    PSECURITY_DESCRIPTOR    Sd;
    PACL    Acl;
    DWORD   i;
    PSID    Sids;
    DWORD   SidLength ;
    CHAR    ReferencedDomain[ MAX_PATH ];
    UCHAR   SidBuffer[ 8 * sizeof(DWORD) + 8 ];
    DWORD   DomainLen ;
    SID_NAME_USE    Use;
    DWORD   SdLen;

    SdLen = sizeof(SECURITY_DESCRIPTOR) +
                        DenyCount * (sizeof( ACCESS_DENIED_ACE ) ) +
                        DenyCount * GetSidLengthRequired( 8 ) +
                        Count * (sizeof( ACCESS_ALLOWED_ACE ) ) + sizeof(ACL) +
                        (Count * GetSidLengthRequired( 8 ) );

    Sd = LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT, SdLen );

    if ( !Sd )
    {
        ErrorExit("Could not allocate SD");
    }

    InitializeSecurityDescriptor( Sd, SECURITY_DESCRIPTOR_REVISION );

    Acl = (PACL)( (PUCHAR) Sd + sizeof( SECURITY_DESCRIPTOR) );

    InitializeAcl( Acl, SdLen - sizeof( SECURITY_DESCRIPTOR) ,
                    ACL_REVISION );

    Sids = SidBuffer;
    for (i = 0 ; i < DenyCount ; i ++ )
    {
        SidLength = sizeof( SidBuffer );

        DomainLen = MAX_PATH ;

        if (! LookupAccountName(NULL,
                                DenyNames[ i ],
                                Sids,
                                &SidLength,
                                ReferencedDomain,
                                &DomainLen,
                                &Use ) )
        {
            _snprintf( ReferencedDomain, MAX_PATH, "Unable to find account %s", DenyNames[ i ]);
            ErrorExit( ReferencedDomain );
        }

        //
        // Got the sid.  Now, add it as an access denied ace:
        //

        AddAccessDeniedAce( Acl,
                            ACL_REVISION,
                            FILE_GENERIC_READ |
                                FILE_GENERIC_WRITE |
                                FILE_CREATE_PIPE_INSTANCE,
                            Sids );

    }

    for (i = 0 ; i < Count ; i ++ )
    {
        SidLength = sizeof( SidBuffer );

        DomainLen = MAX_PATH ;

        if (! LookupAccountName(NULL,
                                Names[ i ],
                                Sids,
                                &SidLength,
                                ReferencedDomain,
                                &DomainLen,
                                &Use ) )
        {
            _snprintf( ReferencedDomain, MAX_PATH, "Unable to find account %s", Names[ i ]);
            ErrorExit( ReferencedDomain );
        }

        //
        // Got the sid.  Now, add it as an access allowed ace:
        //

        AddAccessAllowedAce(Acl,
                            ACL_REVISION,
                            FILE_GENERIC_READ |
                                FILE_GENERIC_WRITE |
                                FILE_CREATE_PIPE_INSTANCE,
                            Sids );

    }

    //
    // Now the ACL should be complete, so set it into the SD and return:
    //

    SetSecurityDescriptorDacl( Sd, TRUE, Acl, FALSE );

    return Sd ;

}

/*************************************************************/

VOID
CloseClient(
    REMOTE_CLIENT *pClient
    )
{
    DWORD tmp;
    char  Buf[200];

    #if DBG
        if (pClient->ServerFlags & ~SFLG_VALID) {

            printf("pClient %x looks nasty in CloseClient.\n", pClient);
            ErrorExit("REMOTE_CLIENT structure corrupt.");
        }
    #endif

    //
    // If we're still active (on the normal client list)
    // start tearing things down and move to the closing
    // list.
    //

    if (pClient->ServerFlags & SFLG_CLOSING) {

        return;
    }

    if (pClient->ServerFlags & SFLG_HANDSHAKING) {

        MoveClientToNormalList(pClient);
    }

    MoveClientToClosingList(pClient);

    pClient->ServerFlags |= SFLG_CLOSING;

    if (pClient->PipeWriteH != INVALID_HANDLE_VALUE) {

        TRACE(CONNECT, ("Disconnecting %d PipeWriteH (%x).\n", pClient->dwID, pClient->PipeWriteH));
        CANCELIO(pClient->PipeWriteH);
        DisconnectNamedPipe(pClient->PipeWriteH);
        CloseHandle(pClient->PipeWriteH);
    }

    if (pClient->PipeReadH != INVALID_HANDLE_VALUE &&
        pClient->PipeReadH != pClient->PipeWriteH) {

        TRACE(CONNECT, ("Disconnecting %d PipeReadH (%x).\n", pClient->dwID, pClient->PipeReadH));
        CANCELIO(pClient->PipeReadH);
        DisconnectNamedPipe(pClient->PipeReadH);
        CloseHandle(pClient->PipeReadH);
    }

    if (pClient->rSaveFile != INVALID_HANDLE_VALUE) {

        CANCELIO(pClient->rSaveFile);
        CloseHandle(pClient->rSaveFile);
    }

    pClient->rSaveFile =
        pClient->PipeWriteH =
            pClient->PipeReadH =
                INVALID_HANDLE_VALUE;

    if ( ! bShuttingDownServer ) {

        sprintf(Buf, "\n**Remote: Disconnected from %s %s [%s]\n", pClient->Name, pClient->UserName, GetFormattedTime(TRUE));

        if (WriteFileSynch(hWriteTempFile,Buf,strlen(Buf),&tmp,dwWriteFilePointer,&olMainThread)) {
            dwWriteFilePointer += tmp;
            StartServerToClientFlow();
        }
    }

    return;
}

BOOL
FASTCALL
HandleSessionError(
    PREMOTE_CLIENT pClient,
    DWORD         dwError
    )
{

    if (pClient->ServerFlags & SFLG_CLOSING) {

        return TRUE;
    }

    if (dwError) {

        if (ERROR_BROKEN_PIPE == dwError ||
            ERROR_OPERATION_ABORTED == dwError ||
            ERROR_NO_DATA == dwError ) {

            CloseClient(pClient);
            return TRUE;
        }

        SetLastError(dwError);
        ErrorExit("Unhandled session error.");
    }

    return FALSE;
}

VOID
FASTCALL
CleanupTempFiles(
    PSZ pszTempDir
    )
{
    HANDLE          hSearch;
    WIN32_FIND_DATA FindData;
    char            szPath[MAX_PATH + 1];

char            szFile[MAX_PATH + 1];

    //
    // pszTempDir, from GetTempPath, has a trailing backslash.
    //

    sprintf(szPath, "%sREM*.tmp", pszTempDir);

    hSearch = FindFirstFile(
                  szPath,
                  &FindData
                  );

    if (INVALID_HANDLE_VALUE != hSearch) {

        do {

            sprintf(szFile, "%s%s", pszTempDir, FindData.cFileName);

            DeleteFile(szFile);

        } while (FindNextFile(hSearch, &FindData));

        FindClose(hSearch);
    }

}

VOID
FASTCALL
SetupSecurityDescriptors(
    VOID
    )
{
    int i;

    //
    // Initialize the wide-open security descriptor.
    //

    InitializeSecurityDescriptor(
        &sdPublic,
        SECURITY_DESCRIPTOR_REVISION
        );

    SetSecurityDescriptorDacl(
        &sdPublic,
        TRUE,
        NULL,
        FALSE
        );

    saPublic.nLength = sizeof(saPublic);
    saPublic.lpSecurityDescriptor = &sdPublic;

    //
    // if /u was specified once or more, build the security descriptor to
    // enforce it.
    //

    saPipe.nLength = sizeof(saPipe);

    if ( DaclNameCount  || DaclDenyNameCount ) {

        saPipe.lpSecurityDescriptor =
            FormatSecurityDescriptor(
                DaclDenyNames,
                DaclDenyNameCount,
                DaclNames,
                DaclNameCount
                );

        if (DaclNameCount) {

            printf( "\nProtected Server!  Only the following users or groups can connect:\n" );

            for (i = 0 ; i < (int) DaclNameCount ; i++) {

                printf( "    %s\n", DaclNames[i] );
            }
        }

        if (DaclDenyNameCount) {

            printf( "The following users or groups explicitly cannot connect:\n" );

            for (i = 0 ; i < (int) DaclDenyNameCount ; i++) {

                printf("    %s\n", DaclDenyNames[i] );
            }
        }

    } else {

        saPipe.lpSecurityDescriptor = &sdPublic;
    }
}

VOID
FASTCALL
RuntimeLinkAPIs(
    VOID
    )
{
    HANDLE hmodKernel32;
    HANDLE hmodNetApi32;

    hmodKernel32 = LoadLibrary("kernel32");
    hmodNetApi32 = LoadLibrary("netapi32");

    pfnCreateWaitableTimer = (void *)
        GetProcAddress(
            hmodKernel32,
            "CreateWaitableTimerA"
            );

    pfnSetWaitableTimer = (void *)
        GetProcAddress(
            hmodKernel32,
            "SetWaitableTimer"
            );

    pfnCancelWaitableTimer = (void *)
        GetProcAddress(
            hmodKernel32,
            "CancelWaitableTimer"
            );

    pfnCancelIo = (void *)
        GetProcAddress(
            hmodKernel32,
            "CancelIo"
            );

    pfnNetWkstaGetInfo = (void *)
        GetProcAddress(
            hmodNetApi32,
            "NetWkstaGetInfo"
            );

    pfnNetApiBufferFree = (void *)
        GetProcAddress(
            hmodNetApi32,
            "NetApiBufferFree"
            );

    //
    // We do without Waitable Timers and CancelIo on 3.51
    //

    if (!pfnNetWkstaGetInfo ||
        !pfnNetApiBufferFree) {

        SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
        ErrorExit("Remote server requires Windows NT.");
    }

}


README.TXT

Demonstration of the CreateDesktop and SwitchDesktop Functions

SUMMARY
=======

The SWITCHER tool allows you to manage multiple desktops. Invoking SWITCHER
creates or opens a number of desktops and allows you to switch between them
with the click of a button.

MORE INFORMATION
================

Usage
-----

switcher [-t numthreads]

numthreads: The number of desktops you wish to manage. The maximum is 15.

A small viewport to each desktop appears in the client area. To switch to a
particular desktop, right-click its viewport. To temporarily enlarge the
view of the desktop, left-click it. A preview window will appear on the left
side of your screen until you release the button.

To invoke an application on a desktop, enter its name in the edit control
and hit Enter or click the Run Me button. If you exit SWITCHER then restart,
applications you invoked on other desktops should still be running. To add
another desktop, click the New Desktop button.

Note: You can run Program Manager, File Manager, or the Windows Explorer on
any desktop. Each instance of these execute applications on the proper
desktop.


SWITCHER.C

/****************************************************************************
*  This is a part of the Microsoft Source Code Samples.
*  Copyright (C) 1995 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: desktop.c

        PURPOSE: Allow user to switch between active desktops on the User's
        WindowStation

        USAGE: desktop [-t #threads]
        #threads is the number of desktops and corresponding threads to create

        APIS of Importance:
         CreateDesktop()
         SwitchDesktop()
         GetUserObjectInformation()
         GetThreadDesktop()
         SetThreadDesktop()
         CloseDesktop()
         OpenDesktop()

        FUNCTIONS:
        WinMain
        StartNewDesktop
        CreateAllDesktops
        LoadResources
        InitApplication
        ThreadInit
        SaveScreen
        PaintMainWnd
        RunApp
        GetFontHeight
        TitleWindow
        CreateControls
        WndProc
        PreviewWndProc
        EditProc

        COMMENTS: This application demonstrates the multiple desktop
        capabilities of Windows NT 3.51, PPC release.

****************************************************************************/

#include <windows.h>   // required for all Windows applications

#include "switcher.h"   // specific to this program

//
// Array of string resources
//
TCHAR SwitchStrings[LAST_STRING-FIRST_STRING + 1][MAXSTRLEN];
#define PSZ(x) SwitchStrings[x-FIRST_STRING]

//
// Structure used for thread-specific data
//
typedef struct _tdata
{
   HDESK hDesk;     // desktop assigned to new thread
   int index;       // index into deskArray
   HWND hWndStatic; // "Run:" static control
   HWND hWndEdit;   // edit control for user input
   HWND hWndBtn;    // button for user input
   HWND hWndNew;    // button for new desktop
} ThreadData;

int     gMaxIndex;                  // Highest index for array of desk handles
HWND    gBaseWindow;                // Window handle of default desktop window
HDESK   gDeskArray[MAX_THREADS];    // Global array of desktop handles
HWND    hWndArray[MAX_THREADS];     // Global array of window handles
HDC     gHDCArray[MAX_THREADS];     // global array of memory device contexts
                                    // these DCs store snapshots of the desktops
int gWidth, gHeight;                // dimensions of desktop rectangles

//
// Keep track of how big the controls need to be to match the
// active system fixed font. These will help determine the
// minimum size of the switcher window.
//
int gStaticWidth;    //  Edit control label
int gEditWidth;      //  Edit control
int gBtnWidth;       //  Button to run app in edit control
int gNewWidth;       //  Button to create new desktop

HINSTANCE ghInst = NULL;               // Global hInstance
#define DEFAULT_DESKTOP  gDeskArray[0] // For easy reading
LONG APIENTRY EditProc (HWND, UINT, WPARAM, LPARAM);
TCHAR szAppName[]     = TEXT("Desktop Switcher!");
TCHAR szClassName[]   = TEXT("SwitcherWindow");
TCHAR szPreviewClass[]= TEXT("PreviewWindow");

/****************************************************************************
        FUNCTION: StartNewDesktop

        PURPOSE: Create or open a handle to a desktop and put a switcher thread
        on it.

        ARGUMENTS:
           int nCount - Which desktop number this is

        Assumes gDeskArray[nCount] == NULL

****************************************************************************/

void StartNewDesktop (int nCount)
{
   ThreadData *ptd;
   TCHAR szDesk[50];
   DWORD tID;
   ptd = (ThreadData*)GlobalAlloc(GMEM_FIXED,sizeof(ThreadData));
   if (ptd)
   {
       ptd->index = nCount;
       //
       // Give the desktop a name.
       //
       wsprintf (szDesk, PSZ(IDS_DESKTOPNAME), nCount+1);
       //
       // First, try to open an existing desktop
       //
       if ( !(ptd->hDesk = OpenDesktop (szDesk, 0, FALSE, GENERIC_ALL)))
       {
       //
       // Failing an open, Create it
       //
          if (!(ptd->hDesk= CreateDesktop (szDesk, NULL,
                                        NULL,0,MAXIMUM_ALLOWED,
                                        NULL)))
          {

                 MessageBox (NULL, PSZ(IDS_CREATEERROR),
                          PSZ(IDS_ERRCAPTION), MB_OK);
                 //
                 //Mark this array slot as invalid
                 //
                 gDeskArray[nCount] = NULL;

          }

       }
       if (ptd->hDesk)
       {
       //
       // Save the handle to the global array. Start the new thread
       //
           gDeskArray[ptd->index] = ptd->hDesk;
           CloseHandle(CreateThread(NULL, 0,
                        (LPTHREAD_START_ROUTINE)ThreadInit,
                        (LPVOID)ptd, 0, &tID));
       }
   }
    else
   {
       //
       // Out of memory
       //
       MessageBox (NULL, PSZ(IDS_CREATEERROR),
                      PSZ(IDS_MEMERRCAPTION), MB_OK);
       //
       //Mark this array slot as invalid
       //
       gDeskArray[nCount] = NULL;
   }

}
/****************************************************************************

        FUNCTION: CreateAllDesktops (cThreads)

        PURPOSE: Creates desktops and assigns a switcher thread to each.
        Updates the global desktop array.

        ARGUMENTS:
          int cThreads - Number of threads/desktops to open or create

****************************************************************************/

void CreateAllDesktops (int cThreads)
{
   ThreadData *ptdDefault;

   //
   // Make sure we allocate for the default desktop first
   //
   ptdDefault = (ThreadData *)GlobalAlloc (GMEM_FIXED, sizeof(ThreadData));
   if (!ptdDefault) {
      return;
   }
   while (cThreads)
   {
      StartNewDesktop (cThreads);
      cThreads--;
   }
   //
   // Main thread is one of the running threads too
   //
   ptdDefault->index = 0;
   ptdDefault->hDesk = DEFAULT_DESKTOP;
   ThreadInit((LPVOID)ptdDefault);

}

/****************************************************************************

        FUNCTION: WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

        PURPOSE: Creates the threads and desktops
        COMMENTS: Each thread has a separate desktop assigned to it.

****************************************************************************/
int CALLBACK WinMain(
        HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,
        int nCmdShow)
{

    int cThreads;                // number of desktops

    ghInst = hInstance;

    if (hPrevInstance)
    {       // Other instances of app running?
            return (FALSE);     // Exit
    } else {
       if (!InitApplication ()) {
          return FALSE;
       }
    }

    // parse command line to determine number of desktops
    // Assume 9 threads

    cThreads = 9;
    lpCmdLine = GetCommandLineA();

    //
    // Get past .exe name
    //
    while (*lpCmdLine != ' ' && *lpCmdLine != 0)
        lpCmdLine++;

    //
    // Find the parameters
    //
    while (*lpCmdLine != 0)
    {

        // Eat white space

        if (*lpCmdLine == ' ')
        {
            lpCmdLine++;
            continue;
        }

        //
        // Do we have a dash? If not, just exit the loop
        //
        if (*lpCmdLine++ != '-')
            break;

        switch (*lpCmdLine++)
        {
           case 't':
           case 'T':
            //
            // How many threads?
            //

              while (*lpCmdLine == ' ')
                lpCmdLine++;

              if (*lpCmdLine == 0 || *lpCmdLine == '-')
                continue;

              cThreads = 0;
              while (*lpCmdLine >= '0' && *lpCmdLine <= '9')
                 cThreads = cThreads * 10 + (*lpCmdLine++ - 0x30);
              break;

        }
    }

    // Create the threads - if zero was specified, default to 1.
    // What does 0 threads mean?
    if (cThreads == 0)
       cThreads = 1;
    else if (cThreads > MAX_THREADS)
       cThreads = MAX_THREADS;
    //
    // Account for the main thread - only create extras
    //
    cThreads--;
    gMaxIndex = cThreads;          // Keep track of the highest array index

    //
    // Assign this here, since threads reference it
    //
    DEFAULT_DESKTOP = GetThreadDesktop(GetCurrentThreadId());
    CreateAllDesktops (cThreads);
    return 0;
}

/*************************************************************
   FUNCTION: LoadResources

   PURPOSE: Load string table entries

   ARGUMENTS: None

   RETURNS: True if all strings are loaded, False otherwise

*************************************************************/

BOOL LoadResources (void)
{
   int i;
   for (i=0;i<(LAST_STRING-FIRST_STRING+1);i++)
   {
      if (!LoadString (ghInst, FIRST_STRING + i, SwitchStrings[i], MAXSTRLEN))
          return FALSE;
   }
   return TRUE;
}
/*************************************************************

   FUNCTION: InitApplication

   PURPOSE: Register the window class and init global variables

   ARGUMENTS:

   RETURNS:
     TRUE if window class registration and other initialization succeeds

**************************************************************/

BOOL InitApplication (void) {

   WNDCLASS wc;
   HWND hTemp;
   HWINSTA hWndSta;
   RECT rect;

   if (!LoadResources ())
       return FALSE;
   //
   // Initialize the gHDCArray to all NULLS
   //
   ZeroMemory (gHDCArray, sizeof (gHDCArray));
   hTemp = GetDesktopWindow(); // Call this so User will assign us a WindowStation.
   //
   // Initialize gWidth and gHeight
   // Get the size of the screen, make gWidth/gHeight == scrnW/scrnH
   //
   GetClientRect (hTemp, &rect);
   gWidth = rect.right/DIVISOR;
   gHeight = rect.bottom/DIVISOR;

   //
   // Make sure this app has a windowstation
   //
   hWndSta = GetProcessWindowStation();
   if (!hWndSta)
   {
      MessageBox (NULL, PSZ(IDS_WNDSTAERROR), PSZ(IDS_ERRCAPTION), MB_OK);
      return FALSE;
   }
   //
   // Register the main window class
   //
   wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc = WndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   wc.hInstance = ghInst;
   wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
   wc.hCursor = LoadCursor (NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH);
   wc.lpszMenuName = NULL;
   wc.lpszClassName = szClassName;

   if (!RegisterClass (&wc))
   {
      return FALSE;
   }

   //
   // Register the preview window class
   //
   wc.style = 0;
   wc.lpfnWndProc = PreviewWndProc;
   wc.lpszClassName = szPreviewClass;

   if (!RegisterClass (&wc)) {
      return FALSE;
   }

   return TRUE;
}

/*******************************************************************

   FUNCTION: ThreadInit

   PURPOSE: Given a desktop handle, create a window on it to allow switching
   among desktops.

   ARGUMENTS:
     LPVOID tData - Thread-specific data

   RETURNS:
     nothing
********************************************************************/

void ThreadInit(LPVOID tData)
{
    MSG msg;
    HWND hWnd;
    HDC hTemp;
    int width;           // window width
    USEROBJECTFLAGS uof; // To set Desktop attributes

    uof.fInherit = FALSE;        // If an app inherits multiple desktop handles,
                                 // it could run on any one of those desktops
    uof.fReserved = FALSE;
    //
    // Let other account processes hook this desktop
    //
    uof.dwFlags = DF_ALLOWOTHERACCOUNTHOOK;
    SetUserObjectInformation (((ThreadData*)tData)->hDesk,
                              UOI_FLAGS,
                              (LPVOID)&uof,
                              sizeof(uof));
    //
    // Make sure the handle is valid
    //
    if (gDeskArray[((ThreadData*)tData)->index])
    {
      //
      // Assign new desktop to this thread
      //
      SetThreadDesktop (((ThreadData*)tData)->hDesk);
      //  create the cool switcher window
      if ((gMaxIndex+1) * gWidth > MINWINDOWWIDTH)
      {
         width = (gMaxIndex+1) * gWidth;
      }
      else
      {
         width = MINWINDOWWIDTH;
      }
      hWnd = CreateWindow (szClassName,
                     szAppName,
                     WS_MINIMIZEBOX|WS_OVERLAPPED|WS_VISIBLE|WS_BORDER|WS_CAPTION|WS_SYSMENU,
                     0, 0, width, 30+gHeight + CONTROLHEIGHT,
                     NULL, NULL, ghInst, tData);
      if (!hWnd) // bag it
      {
         gDeskArray[((ThreadData*)tData)->index] = NULL;
         GlobalFree (tData);
         return;
      }

      //
      //update the global window array
      //
      hWndArray[((ThreadData*)tData)->index] = hWnd;

    }
    else
    {

       GlobalFree (tData);
       return;
    }

    //
    // Acquire and dispatch messages until a WM_QUIT message is received.
    //
    while (GetMessage(&msg, NULL,  0, 0))
    {
                 TranslateMessage(&msg);// Translates virtual key codes
                 DispatchMessage(&msg); // Dispatches message to window
    }
    //
    // Switch back to the default desktop and close the user-created one
    //
    SetThreadDesktop (DEFAULT_DESKTOP);
    SwitchDesktop (DEFAULT_DESKTOP);
    CloseDesktop (((ThreadData*)tData)->hDesk);
    //
    // NULL out the global array entry so other threads won't try to switch to
    // this desktop
    //
    gDeskArray[((ThreadData*)tData)->index] = NULL;
    //
    // cleanup
    //
    hTemp = gHDCArray[((ThreadData*)tData)->index];
    gHDCArray[((ThreadData*)tData)->index] = NULL;
    DeleteObject (hTemp);
    GlobalFree (tData);

}

/***********************************************************************

   FUNCTION: SaveScreen

   PURPOSE: Save a snapshot of the desktop to its corresponding
   memory DC. StretchBlt!

   ARGUMENTS:
     int index - Index of memory DC to save bits to

   RETURNS: nothing
************************************************************************/

void SaveScreen (int index) {
   HDC hdc;
   int xSize, ySize;

   xSize = GetSystemMetrics (SM_CXSCREEN);
   ySize = GetSystemMetrics (SM_CYSCREEN);

   if (hdc = CreateDC (TEXT("DISPLAY"), NULL, NULL, NULL))
   {
      //
      // Copy the desktop to a memory DC
      //
      StretchBlt (gHDCArray[index], 0, 0, gWidth*2, gHeight*2,
               hdc, 0, 0, xSize, ySize, SRCCOPY);
      DeleteDC (hdc);
   }
}

/*******************************************************************************

     FUNCTION: PaintMainWnd

     PURPOSE: Draw the main window. This window has rectangles with miniature snapshots
     of each desktop. The snapshots are retrieved from the gHDCArray and StretchBlt'd to
     the right size.

     ARGUMENTS:
       HWND hWnd - Window to draw

    RETURNS: nothing

*******************************************************************************/

void PaintMainWnd (HWND hWnd)
{
   PAINTSTRUCT ps;
   RECT rect;
   HPEN hPen, hOld;
   ThreadData *ptd;
   TCHAR szName[4];  // short name!
   int myThread;
   HDC hDC;          // always need a dc for drawing
   int i;            // my favorite loop counter

   //
   // get the array index of this window
   //
   ptd = (ThreadData *)GetWindowLong (hWnd, GWL_USERDATA);
   myThread = ptd->index;

   //
   //Draw a rectangle for each desktop
   //
   hDC = BeginPaint (hWnd, &ps);
   if (GetClientRect (hWnd, &rect))
   {
      int right, left;
      //
      // leave space for edit control and button
      //
      rect.bottom -= CONTROLHEIGHT;
      hPen = CreatePen (PS_SOLID | PS_INSIDEFRAME, 2, RGB(255,16,16));
      hOld = SelectObject (hDC, hPen);

      //
      // draw each desktop rectangle
      //
      for (i=0;i<=gMaxIndex;i++)
      {
         right = gWidth * i + gWidth;
         left = right - gWidth;
         //
         // If no snapshot is available, be boring
         //
         if (!gHDCArray[i])
         {
            Rectangle (hDC, left, rect.top, right, gHeight);
            wsprintf (szName, TEXT("%d"), i+1);
            TextOut (hDC, left+(gWidth/2),
                     gHeight/2, szName,
                     lstrlen (szName));
         }
         else
         {
            //
            // BitBlt the snapshot into the rectangle
            //
            StretchBlt (hDC, left, rect.top, gWidth, gHeight,
                    gHDCArray[i], 0, 0, gWidth*2, gHeight*2, SRCCOPY);
            //
            // draw lines around the rectangle
            //
            MoveToEx (hDC, left, rect.top, NULL);
            LineTo (hDC, left, gHeight);
            MoveToEx (hDC, right, rect.top, NULL);
            LineTo (hDC, right, gHeight);
            //
            // underline the active one
            //
            if (myThread == i)
            {
               MoveToEx (hDC, left, gHeight, NULL);
               LineTo (hDC, right, gHeight);
            }
         }
      }
      //
      // cleanup
      //
      SelectObject (hDC, hOld);
      DeleteObject (hPen);
   }

   EndPaint (hWnd, &ps);

}

/****************************************************************************

      FUNCTION: RunApp (HWND)

      PURPOSE: Create a process, using contents of HWND's edit control
      as the command line.

      ARGUMENTS:
        HWND hWnd - Handle of active switcher window

      RETURNS: nothing

      COMMENTS: Make sure proper desktop is passed in STARTUPINFO

 ****************************************************************************/

void RunApp (HWND hWnd)
{
   TCHAR szDesk[100];          // data holder
   TCHAR szExec[100];       // Command line
   STARTUPINFO sui;         // Process startup info
   PROCESS_INFORMATION pi;  // info returned from CreateProcess
   ThreadData *ptd;

   ptd = (ThreadData*)GetWindowLong (hWnd, GWL_USERDATA);
   //
   // Most sui members will be 0
   //
   ZeroMemory ((PVOID)&sui, sizeof(sui));
   //
   //Get the command line to execute
   //
   GetDlgItemText (hWnd, IDC_RUNME, szExec, 100*sizeof(TCHAR));
   //
   //Get the current desktop name
   //
   GetUserObjectInformation (GetThreadDesktop (GetCurrentThreadId()),
                             UOI_NAME,
                             szDesk,
                             100*sizeof(TCHAR),
                             NULL);
   sui.cb = sizeof (sui);
   //
   // Need the lpDesktop member so the new process runs on this desktop
   // The lpDesktop member was reserved in previous versions of NT
   //
   sui.lpDesktop = szDesk;
   CreateProcess (NULL,   // image name
                  szExec, // command line
                  NULL,   // process security attributes
                  NULL,   // thread security attributes
                  TRUE,   // inherit handles
                  CREATE_DEFAULT_ERROR_MODE|CREATE_SEPARATE_WOW_VDM,
                  NULL,   // environment block
                  NULL,   // current directory
                  &sui,   // STARTUPINFO
                  &pi);   // PROCESS_INFORMATION

}
/****************************************************************************
   FUNCTION: GetFontHeight

   PURPOSE: Set up widths for controls based on size of the system
   font.

   ARGUMENTS:
     HWND hWnd - Window whose DC to use.

   RETURNS:
      Return the height of the system fixed font to use for
   the controls.

****************************************************************************/

LONG GetFontHeight (HWND hWnd)
{
   HDC hdc;
   TEXTMETRIC tm;
   SIZE size;
   #define MARGIN 7  // extra space on the button around the text

   hdc = GetDC (hWnd);
   if (!GetTextMetrics (hdc, &tm))
   {
      //
      // Use defaults
      //
      gStaticWidth = STATICWIDTH;
      gBtnWidth    = BTNWIDTH;
      gEditWidth   = EDITWIDTH;
      gNewWidth    = BTNWIDTH + 25;
      return CONTROLHEIGHT;
   }

   //
   // GetTextExtentPoint32 fills in size with the width and height of
   // a string.
   //
   GetTextExtentPoint32 (hdc, PSZ(IDS_RUNLABEL), lstrlen(PSZ(IDS_RUNLABEL)), &size);
   gStaticWidth = size.cx + MARGIN;
   gEditWidth   = EDITWIDTH;
   GetTextExtentPoint32 (hdc, PSZ(IDS_BTNLABEL), lstrlen(PSZ(IDS_BTNLABEL)), &size);
   gBtnWidth = size.cx + MARGIN;
   GetTextExtentPoint32 (hdc, PSZ(IDS_NEWLABEL), lstrlen(PSZ(IDS_NEWLABEL)), &size);
   gNewWidth = size.cx + MARGIN;
   ReleaseDC (hWnd, hdc);
   return tm.tmHeight + 2;

}

/****************************************************************************
        FUNCTION: TitleWindow

        PURPOSE: Give a switcher window an appropriate title, using its
        desktop name.

        ARGUMENTS:
          HWND hWnd - Window to title

        RETURNS: nothing

****************************************************************************/

void TitleWindow (HWND hWnd)
{
   TCHAR *szTitle, *szName;
   UINT nBytes = 0;

   //
   // How long is the desktop name?
   //
   GetUserObjectInformation (GetThreadDesktop(GetCurrentThreadId()),
                             UOI_NAME,
                             (LPVOID)&nBytes, // not used since cbInfo is 0
                             0,
                             &nBytes);
   szName = (LPTSTR)GlobalAlloc (GPTR, nBytes);
   if (!szName)
   {
      return;
   }
   //
   // Now get the desktop name
   //
   GetUserObjectInformation (GetThreadDesktop(GetCurrentThreadId()),
                             UOI_NAME,
                             (LPVOID)szName,
                             nBytes,
                             &nBytes);
   //
   // Now make the window title
   //
   szTitle = (LPTSTR)GlobalAlloc (
           GPTR,
           (lstrlen(szAppName)+lstrlen(TEXT(" - "))) * sizeof(TCHAR) + nBytes);

   if (!szTitle)
   {
      GlobalFree (szName);
      return;
   }
   wsprintf (szTitle, TEXT("%s - %s"), szAppName, szName);
   SetWindowText (hWnd, szTitle);
   //
   // Cleanup
   //
   GlobalFree (szName);
   GlobalFree (szTitle);
}

/****************************************************************************

        FUNCTION: CreateControls (ThreadData *, HWND)

        PURPOSE: Creates UI controls on a switcher window

        ARGUMENTS:
          ThreadData *ptd  - Thread specific data to use/init
          HWND hWnd        - Parent window

        RETURNS:
           nothing

****************************************************************************/

void CreateControls (ThreadData *ptd, HWND hWnd)
{
   LONG oldproc;

   //
   // Create the edit control label
   //
   ptd->hWndStatic = CreateWindow (TEXT("static"), PSZ(IDS_RUNLABELHOT),
                                   WS_CHILD | WS_VISIBLE,
                                   0,0,0,0, hWnd, (HMENU)IDC_STATIC,
                                   ghInst, NULL);
   //
   // Create the edit control
   //
   ptd->hWndEdit = CreateWindow (TEXT("Edit"), TEXT(""),
                         WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
                         0,0,0,0, hWnd, (HMENU)IDC_RUNME,
                         ghInst, NULL);

   //
   // set the edit control proc and save the default one
   //
   oldproc = GetWindowLong (ptd->hWndEdit, GWL_WNDPROC);
   SetWindowLong (ptd->hWndEdit, GWL_WNDPROC, (LONG)EditProc);
   SetWindowLong (ptd->hWndEdit, GWL_USERDATA, oldproc);

   //
   // Create the execution button
   //
   ptd->hWndBtn = CreateWindow (TEXT("Button"), PSZ(IDS_BTNLABEL),
                                WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
                                0,0,0,0, hWnd, (HMENU)IDC_RUNMEBTN,
                                ghInst, NULL);
   //
   // Create a button for creating new desktops
   //
   ptd->hWndNew = CreateWindow (TEXT("button"), PSZ(IDS_NEWLABELHOT),
                                WS_CHILD | WS_VISIBLE,
                                0,0,0,0, hWnd, (HMENU)IDC_NEWDSKBTN,
                                ghInst, NULL);

}
/****************************************************************************

        FUNCTION: WndProc(UINT, WPARAM, LPARAM)

        PURPOSE:  Processes messages to the Switcher window

        MESSAGES:
          WM_RBUTTONDOWN - Switch to desktop whose rectangle is under the mouse
          WM_CLOSE       - Send WM_CLOSE to all windows in hWndArray
          WM_LBUTTONDOWN - Create a preview window to display a larger view of
                           a desktop until WM_LBUTTONUP
          WM_COMMAND     - Respond to button pushes or edit control entry
          WM_SYSCHAR     - ALT+R sets focus to the edit control
                           ALT+N creates a new desktop
          WM_CHAR        - Carriage return executes command line from
                           the edit control
          WM_HOTKEY
          WM_KEYDOWN     - Respond to function keys by switching to
                           the appropriate desktop. Example, F2 means switch
                           to Desktop2. Ctrl-F# allows switching without
                           giving focus to the switcher window
          WM_LBUTTONUP   - Close the active preview window
          WM_CREATE      - Initialize controls and global array entries
          WM_SIZE        - Size the child controls correctly

        COMMENTS:

****************************************************************************/

LONG APIENTRY WndProc(
    HWND hWnd,
    UINT message,      // type of message
    WPARAM wParam,     // additional information
    LPARAM lParam)     // additional information
{

    int newThread;      // Thread index to switch to
    int i;
    ThreadData *ptd;
    static HWND hShowing = NULL;           // which preview window is being shown
    static LONG fntHeight = CONTROLHEIGHT; // height for the edit control
    switch (message)
    {
       case WM_CREATE:
       {
          HDC hDC;
          HBITMAP hBmp;

         // Create edit control, button, and label at the bottom of the window
         // This will allow the user to input a program to run

         SetWindowLong (hWnd, GWL_USERDATA,
                        (LONG)((CREATESTRUCT *)lParam)->lpCreateParams);
         ptd = (ThreadData *)GetWindowLong (hWnd, GWL_USERDATA);
         CreateControls (ptd, hWnd);
         fntHeight = GetFontHeight (hWnd);
         //
         // initialize the DC array entry
         //
         hDC = CreateDC (TEXT("DISPLAY"), NULL, NULL, NULL);
         gHDCArray[ptd->index] = CreateCompatibleDC (hDC);
         //
         // Halftone is the best stretching algorithm
         //
         SetStretchBltMode (gHDCArray[ptd->index], HALFTONE);
         SetBrushOrgEx (gHDCArray[ptd->index], 0, 0, NULL);
         //
         // Use a bitmap the same size as the desktop preview rectangles
         //
         hBmp = CreateCompatibleBitmap (hDC, gWidth*2, gHeight*2);
         SelectObject (gHDCArray[ptd->index], hBmp);
         DeleteDC (hDC);
         SaveScreen (ptd->index);
         TitleWindow (hWnd);
         //
         // Register hot keys
         //
         for (i=0;i<10;i++)
         {
            RegisterHotKey (hWnd, VK_F1+i, MOD_CONTROL, VK_F1+i);
         }
         return 0;
       }

       case WM_SIZE:
       {
         //
         // Put the child controls at the right places
         //
          #define PADDING 5

          RECT rect;
          ThreadData *ptd;
          if (GetClientRect (hWnd, &rect))
          {
            ptd = (ThreadData *)GetWindowLong (hWnd, GWL_USERDATA);
            MoveWindow (ptd->hWndStatic, 0, rect.bottom - CONTROLHEIGHT,
                        gStaticWidth, fntHeight + PADDING, TRUE);

            MoveWindow (ptd->hWndEdit, gStaticWidth + 5,
                        rect.bottom - fntHeight - PADDING,
                        gEditWidth, fntHeight+PADDING, TRUE);

            MoveWindow (ptd->hWndBtn, gStaticWidth + gEditWidth + 10,
                        rect.bottom - fntHeight - PADDING,

gBtnWidth, fntHeight+PADDING, TRUE);

            MoveWindow (ptd->hWndNew, gStaticWidth+gEditWidth+gBtnWidth+15,
                        rect.bottom - fntHeight- PADDING,
                        gNewWidth, fntHeight+PADDING, TRUE);

          }
          return 0;
       }
       case WM_PAINT:
          PaintMainWnd (hWnd);
          return 0;

       case WM_RBUTTONDOWN:
       {
          //
          // Find the rectangle in which the button was pressed
          //
          POINTS pts;
          ThreadData *ptd;
          ptd = (ThreadData *)GetWindowLong(hWnd, GWL_USERDATA);
          pts = MAKEPOINTS (lParam);
          if (pts.y > gHeight)
          {
             return 1;
          }
          newThread = pts.x/gWidth;

          //
          // Get a snapshot of the current desktop
          //
          SaveScreen (ptd->index);

          //
          // Switch to the selected desktop
          //
          if (!gDeskArray[newThread])
          {
             StartNewDesktop (newThread);
          }
          if (!SwitchDesktop (gDeskArray[newThread]))
             MessageBox (hWnd,
                         PSZ(IDS_BADDESKTOP),
                         PSZ(IDS_ERRCAPTION), MB_OK);

          return 0;
       }

       case WM_LBUTTONDOWN:
       //
       // show the preview window
       //
       {
          POINTS pts;
          POINT ptl;
          int *index;

          pts = MAKEPOINTS (lParam);
          if (pts.y > gHeight)
          {
             return 1;
          }
          newThread = pts.x/gWidth;
          index = GlobalAlloc (GMEM_FIXED, sizeof(int));
          if (!index)
          {
             return 1;
          }
          *index = newThread;
          //
          // Want to show the preview window where the button was clicked.
          // Map the given points to screen coords.
          // ClientToScreen is expecting a POINT structure, not a POINTS
          //
          ptl.x = (LONG)pts.x;
          ptl.y = (LONG)pts.y;
          ClientToScreen (hWnd, &ptl);
          hShowing = CreateWindow (szPreviewClass, TEXT(""),
                                  WS_POPUP | WS_VISIBLE | WS_BORDER,
                                  ptl.x+3,
                                  ptl.y+3,
                                  gWidth*2,
                                  gHeight*2,
                                  hWnd,
                                  (HMENU)0, ghInst, (LPVOID)index);
          return 0;
       }

       case WM_CHAR:
          if (wParam == VK_RETURN)
          {
              PostMessage (hWnd, WM_COMMAND, (WPARAM)IDC_RUNMEBTN, 0);
          }
          return 0;

       case WM_SYSCHAR:
       {
          ThreadData *ptd;
          ptd = (ThreadData *)GetWindowLong(hWnd, GWL_USERDATA);
          switch (wParam)
          {
             // alt+r == focus on the edit control
             case TEXT('r'):
             case TEXT('R'):
                if (GetKeyState (VK_MENU))
                {
                   SetFocus (ptd->hWndEdit);
                }
                return 0;
             // alt+n = create a new desktop
             case TEXT('n'):
             case TEXT('N'):
                if (GetKeyState (VK_MENU))
                {
                   PostMessage (hWnd, WM_COMMAND, (WPARAM)IDC_NEWDSKBTN, 0);
                }
          }
          return 0;
       }
       case WM_HOTKEY:
       case WM_KEYDOWN:
          //
          // F1-F9 switches to corresponding desktop
          //

          if ((wParam >= VK_F1 && wParam <= VK_F10)
              && (wParam - VK_F1 <= (UINT)gMaxIndex))
          {
             LONG x, y;
             x = (wParam - VK_F1) * gWidth + 2;
             y = gHeight - 4;
             PostMessage (hWnd, WM_RBUTTONDOWN, 0, MAKELPARAM (x, y));
          }
          return 0;

       case WM_SETFOCUS:
       case WM_NCLBUTTONUP:
       case WM_LBUTTONUP:

         //
         // destroy the preview window
         //
         if (hShowing)
         {
            DestroyWindow (hShowing);
            hShowing = NULL;
         }
         return 0;

       case WM_CLOSE:
         //
         // to be safe, check for a preview window
         //
         if (hShowing)
         {
            DestroyWindow (hShowing);
            hShowing = NULL;
         }
         //
         // go to the default desktop so the DestroyWindow calls all succeed
         //
         SwitchDesktop (DEFAULT_DESKTOP);
         //
         // kill the window on this desktop
         // all the windows will be destroyed if this is the default desktop
         //
         for (i=gMaxIndex;i>=0;i--)
         {
            DestroyWindow (hWndArray[i]);
         }
         //
         // Unregister the hot keys
         //
         for (i=0;i<10;i++)
         {
            UnregisterHotKey (hWnd,VK_F1+i);
         }
         return 0;

       case WM_DESTROY:  // message: window being destroyed

         PostQuitMessage(0);
         return 0;

       case WM_COMMAND:
       {

          switch (LOWORD(wParam))
          {
             case IDC_RUNMEBTN:
             {
                RunApp (hWnd);
                return 0;
             }

             case IDC_NEWDSKBTN:
             //
             // Create a new desktop and resize the windows to show it.
             //
             {
                RECT rect;
                int i;
                if (gMaxIndex + 1 < MAX_THREADS)
                {
                   gMaxIndex++;
                   StartNewDesktop (gMaxIndex);
                   GetWindowRect (hWnd,&rect);
                   for (i=0;i<gMaxIndex;i++)
                   {
                      MoveWindow (hWndArray[i],
                               rect.left, rect.top,
                               rect.right + gWidth, rect.bottom-rect.top,
                               TRUE);

                   }
                }
               return 0;

             }

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

    default:          // Passes it on if unprocessed
        return (DefWindowProc (hWnd, message, wParam, lParam));
    }

}

/***********************************************************

     FUNCTION: PreviewWndProc

     PURPOSE: Displays an enlarged view of the last snapshot of a desktop

************************************************************/

LONG APIENTRY PreviewWndProc (HWND hWnd,
                              UINT msg,
                              WPARAM wParam,
                              LPARAM lParam)
{

   int *index;
   switch (msg)
   {
      case WM_CREATE:
         //
         // save the index
         //
         SetWindowLong (hWnd, GWL_USERDATA,
                       (LONG)((CREATESTRUCT *)lParam)->lpCreateParams);

         return 0;

      case WM_PAINT:
      {
         HDC hdc;
         PAINTSTRUCT ps;
         index = (int *)GetWindowLong (hWnd, GWL_USERDATA);
         hdc = BeginPaint (hWnd, &ps);
         //
         // slap in the desktop picture
         //
         BitBlt (hdc, 0, 0, gWidth*2, gHeight*2,
                     gHDCArray[*index], 0, 0, SRCCOPY);
         EndPaint (hWnd, &ps);
         return 0;
      }
      case WM_LBUTTONUP:
      {
         //
         // In case the button is released in my client area
         //
         HWND hp;
         hp = GetWindow (hWnd, GW_OWNER);
         PostMessage (hp, msg, wParam, lParam);
         return 0;
      }
      case WM_CLOSE:
      {
         //
         // cleanup the index pointer
         //
         index = (int *)GetWindowLong (hWnd, GWL_USERDATA);
         GlobalFree (index);

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

   }
}

/********************************************

     FUNCTION: EditProc

     PURPOSE: subclass the edit control to handle carriage returns

 ********************************************/

 LONG APIENTRY EditProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
    if (WM_CHAR == msg && (TCHAR)wParam == 0xD)
    {
       PostMessage (GetParent (hWnd), WM_COMMAND, (WPARAM)IDC_RUNMEBTN, 0);
       return 0;
    }
    //
    // call the default edit control procedure
    //
    return CallWindowProc ( (WNDPROC)GetWindowLong (hWnd, GWL_USERDATA),
                            hWnd,  msg, wParam, lParam);
 }


SWITCHER.H

#define MAX_THREADS 10
#define CONTROLHEIGHT 22
#define DIVISOR 10         // screensize/divisor == desktop rectangle size

void ThreadInit(LPVOID tData);      // Startup routine
BOOL InitApplication (void);
LONG APIENTRY WndProc(HWND, UINT, WPARAM, LPARAM); // window procedure
LONG APIENTRY PreviewWndProc (HWND, UINT, WPARAM, LPARAM);

#define FIRST_STRING 901
#define LAST_STRING  911
#define MAXSTRLEN     30
//
// String table IDs
//
#define IDS_DESKTOPNAME     901
#define IDS_CREATEERROR     902
#define IDS_ERRCAPTION      903
#define IDS_MEMERRCAPTION   904
#define IDS_WNDSTAERROR     905
#define IDS_RUNLABEL        906
#define IDS_RUNLABELHOT     907
#define IDS_BTNLABEL        908
#define IDS_NEWLABEL        909
#define IDS_NEWLABELHOT     910
#define IDS_BADDESKTOP      911

// Control IDs

#define IDC_STATIC -1
#define STATICWIDTH 32
#define IDC_RUNME  10001
#define EDITWIDTH 90
#define IDC_RUNMEBTN   10002
#define BTNWIDTH 72
#define IDC_NEWDSKBTN  10003

#define MINWINDOWWIDTH STATICWIDTH+EDITWIDTH+2*BTNWIDTH+30


README.TXT

Unicode Conversion Utility

The UCONVERT tool converts text files between the 16-bit Unicode character
encoding and other traditional character encodings. It may also be used to
install conversion tables which are used by the MultiByteToWideChar and
WideCharToMultiByte functions.


INSTALL.C

/**************************************************************************\
* install.c -- install c_*.nls conversion tables.
*
*         Steve Firebaugh
*         Microsoft Developer Support
*         Copyright 1992 - 1998 Microsoft Corporation
*
*
*
* Note, this module must have UNICODE defined because the registry
*  code will not work without it.
*
\**************************************************************************/
#define UNICODE

#include <windows.h>
#include <commdlg.h>
#include <string.h>
#include <stdio.h>
#include "uconvert.h"
#include "install.h"

/**************************************************************************\
*  Global variables.
\**************************************************************************/

/* This is the registry key that we store conversion table information under. */
TCHAR NlsRegEntryStr[]=TEXT("SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage");

/***************************************************************************\
*    FUNCTION: InstallTableProc
*
* Dialog window procedure for the Install *.nls tables dialog.
*
\***************************************************************************/
LRESULT CALLBACK InstallTableProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

  switch (message) {

    /**********************************************************************\
    *  WM_INITDIALOG
    *
    * Fill dialog with a list of the currently existing tables.
    \**********************************************************************/
    case WM_INITDIALOG:
      if (!ListInstalledTables (GetDlgItem (hwnd, DID_LISTBOX), LB_ADDSTRING, FALSE))
        EndDialog (hwnd, FALSE);
        return TRUE;
    break;

    case WM_COMMAND:
      switch (wParam) {
        case IDCANCEL:
        case IDOK:
          EndDialog (hwnd, TRUE);
        break;

        /**********************************************************************\
        *  WM_COMMAND, BID_ADD
        *
        * Use common dialog, get new *.nls name, and try to install it.
        \**********************************************************************/
        case BID_ADD:
          if (GetTableFileNames(hwnd))
            ListInstalledTables (GetDlgItem (hwnd, DID_LISTBOX), LB_ADDSTRING, FALSE);
        break;
      }
    break; /* end WM_COMMAND */

    case WM_SYSCOMMAND:
      if (wParam == SC_CLOSE)
        EndDialog (hwnd, TRUE);
    break; /* end WM_SYSCOMMAND */

  } /* end switch */
  return FALSE;
}

/***************************************************************************\
*    FUNCTION: GetTableFileNames
*
* Throw up a common dialog to the user, and let them search for the *.nls
*  file to install.
*
* LIMITATION:  Currently only works for one file at a time.
*  Should rewrite to accept multiple files.
*
\***************************************************************************/
int GetTableFileNames (HWND hwnd)
{
    OPENFILENAME OpenFileName;

    /* buffers for the file names. */
    TCHAR szFile[MAX_PATH],szFileTitle[MAX_PATH];
    TCHAR szFilter[MAX_PATH], buffer[50];
    TCHAR *p;

    /* Build up the correct filter strings for OPENFILENAME structure */

    p = szFilter;
    lstrcpy (buffer,LoadResourceString(IDS_FILE_FILTER_SPEC4));
    lstrcpy (p,buffer);
    p += lstrlen (buffer) +1;
    lstrcpy (buffer,TEXT("*.nls"));
    lstrcpy (p,buffer);
    p += lstrlen (buffer) +1;

    lstrcpy (p,TEXT("\0"));

    wsprintf (szFile, TEXT(""));
    wsprintf (szFileTitle, TEXT(""));

    OpenFileName.lStructSize       = sizeof(OPENFILENAME);
    OpenFileName.hwndOwner         = hwnd;
    OpenFileName.hInstance         = NULL;
    OpenFileName.lpstrFilter       = szFilter;
    OpenFileName.lpstrCustomFilter = NULL;
    OpenFileName.nMaxCustFilter    = 0L;
    OpenFileName.nFilterIndex      = 1L;
    OpenFileName.lpstrFile         = szFile;
    OpenFileName.nMaxFile          = MAX_PATH;
    OpenFileName.lpstrFileTitle    = szFileTitle;
    OpenFileName.nMaxFileTitle     = MAX_PATH;
    OpenFileName.lpstrInitialDir   = NULL;
    OpenFileName.lpstrTitle        = LoadResourceString(IDS_TABLE_FILE_TITLE);

    OpenFileName.nFileOffset       = 0;
    OpenFileName.nFileExtension    = 0;
    OpenFileName.lpstrDefExt       = NULL;

    OpenFileName.lCustData         = 0;
    OpenFileName.lpfnHook          = NULL;
    OpenFileName.lpTemplateName    = NULL;

//    OpenFileName.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT;
    OpenFileName.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST ;

    if (!GetOpenFileName(&OpenFileName)) return 0;

    return (InstallFile (szFile,szFileTitle));
}

/***************************************************************************\
*    FUNCTION: InstallFile
*
* Given full path name, and just file name, copy that file into the
*  system directory, and change the registry to indicate that the file
*  has now been "installed."
*
\***************************************************************************/
int InstallFile (TCHAR* szPathAndName, TCHAR* szName)
{
TCHAR szTargetFile[MAX_PATH], buffer[MAX_PATH];
TCHAR keyname[MAX_PATH];
HKEY hKey;
int cp, nChar;
LONG rValue;

    /* First verify that they have selected a valid file name. */
    CharLowerBuff (szName,lstrlen (szName));
    if (myScanf (szName, &cp) != 1) {
      MessageBox (NULL, LoadResourceString(IDS_INCORRECT_FILE_TYPE),
                        NULL, MB_ICONSTOP | MB_OK);
      return FALSE;
    }

    /* Build up a complete path name for the target file.
     *  Get the system directory, and prepend it before szName.
     */
    GetSystemDirectory (buffer, MAX_PATH);
    nChar = wsprintf (szTargetFile, TEXT("%s\\%s"), buffer, szName);

    if (nChar >= MAX_PATH) {
      MessageBox (NULL, LoadResourceString(IDS_FILENAME_OVERFLOW),NULL, MB_ICONSTOP | MB_OK);
      return FALSE;
    }

    /* Now, try to open the registry for writing... This may fail if
     *  the current user has insufficient privilege, or it may fail
     *  for other, unforeseen, reasons.
     */
    rValue = RegOpenKeyEx (HKEY_LOCAL_MACHINE, NlsRegEntryStr, 0, KEY_SET_VALUE, &hKey);
    if (rValue == ERROR_ACCESS_DENIED) {
      MessageBox (NULL, LoadResourceString(IDS_LOGON_AS_ADMIN),
               LoadResourceString(IDS_ACCESS_DENIED), MB_ICONSTOP | MB_OK);
      return FALSE;
    }
    if (rValue != ERROR_SUCCESS) {
      MessageBox (NULL, LoadResourceString(IDS_REGOPENKEYEX_FAILED),NULL, MB_ICONSTOP | MB_OK);
      return FALSE;
    }

    /* Try to copy file... one reason for failure is file already
     *  exists.  If so, query the user to try again.
     *  If fails again, just report problem and exit.
     */
    if (!CopyFile (szPathAndName, szTargetFile, TRUE)) {

      /* if failure was from existing file, query user preference
       *  regarding replacing it.
       */
      if (GetLastError() == ERROR_FILE_EXISTS) {
        if (MessageBox (NULL, LoadResourceString(IDS_FILE_ALREADY_EXISTS),
                        LoadResourceString(IDS_APP_WARNING),
                        MB_ICONEXCLAMATION | MB_YESNO) == IDNO) {
          goto close_and_exit;
        } else {
          if (!CopyFile (szPathAndName, szTargetFile, FALSE)) {
            MessageBox (NULL, LoadResourceString(IDS_FILE_CP_FAILED_AGAIN),
                          NULL, MB_ICONSTOP | MB_OK);
            goto close_and_exit;
          }
        }

      /* no duplicate file, CopyFile() failed for other reasons
       *  report failure and return.
       */
      } else {
        MessageBox (NULL, LoadResourceString(IDS_FILE_CP_FAILED),
                      NULL, MB_ICONSTOP | MB_OK);
        goto close_and_exit;

      }
    }

    /* Finally, write the new key value to the registry. */
    if (myScanf (szName, &cp) == 1) {
      wsprintf (keyname, TEXT("%d"), cp);
      RegSetValueEx (hKey, keyname, 0, REG_SZ, (LPBYTE)szName,
                     (DWORD)((lstrlen(szName) +1)*sizeof(TCHAR)));

    } else
      MessageBox (NULL, szName, LoadResourceString(IDS_FILE_PARSE_FAILED),
              MB_ICONSTOP | MB_OK);

close_and_exit:

    RegCloseKey (hKey);

    return TRUE;
}

/***************************************************************************\
*    FUNCTION: ListInstalledTables
*
* Display the *.nls conversion tables currently installed, according to the
*  registry.  Display either the file name, or just the codepage number.
*
* hwndFill - listbox or combobox to fill with names.
* message - LB_ADDSTRING or CB_ADDSTRING
* NumberOnly - FALSE then use full file name, TRUE then just use number.
*
* CRITERION for table being installed:
*  value in registry is c_* where * is number.
*
\***************************************************************************/
int ListInstalledTables (HWND hwndFill, UINT message, int NumberOnly)
{

  TCHAR szKeyname[MAX_PATH], szValue[MAX_PATH];
  DWORD cBytesName, cBytesValue, iSubKey;
  int  cp;
  HKEY hKey;

  /* open the registry key for reading.  If failure, report and exit. */
  if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, NlsRegEntryStr, 0, KEY_QUERY_VALUE, &hKey)) {
    MessageBox (NULL, LoadResourceString(IDS_REGOPENKEYEX_FAILED),
            NULL, MB_ICONSTOP | MB_OK);
    return FALSE;
  }

  /* empty the current contents. */
  if (message == LB_ADDSTRING)
    SendMessage (hwndFill, LB_RESETCONTENT, 0, 0);
  else if (message == CB_ADDSTRING)
    SendMessage (hwndFill, CB_RESETCONTENT, 0, 0);

  iSubKey = 0;
  cBytesName = cBytesValue = MAX_PATH;
  while (!RegEnumValue (hKey, iSubKey, szKeyname, &cBytesName, NULL, NULL, (LPBYTE)szValue, &cBytesValue)) {

    if (myScanf (szValue, &cp) == 1) {

      /* if we are to display only the number, then reformat szValue string */
      if (NumberOnly)
        wsprintf (szValue, TEXT("%d"), cp);

      SendMessage (hwndFill, message, 0 ,(LPARAM) szValue);
    }

    iSubKey++;
    cBytesName = cBytesValue = MAX_PATH;  // undoc.ed feature, must be set each time.
  }

  RegCloseKey (hKey);

  return TRUE;
}

/***************************************************************************\
*    FUNCTION: myScanf
*
* Convert a string into a number (like sscanf).
*  However, this function works independent of UNICODE turned on.
*
* NOT a general function... looking for "c_%d.nls"
*
\***************************************************************************/
int myScanf (TCHAR* pSource, int* pValue)
{
char ansibuffer[MAX_PATH];
int iStrLen;

  iStrLen = lstrlen (pSource);
  if (iStrLen == 0) return 0;

#ifdef UNICODE
  WideCharToMultiByte (CP_ACP, 0, pSource, -1,
           ansibuffer, MAX_PATH, NULL, NULL);
#else
  lstrcpy (ansibuffer, pSource);
#endif

  CharLowerBuffA (ansibuffer,lstrlenA (ansibuffer));

  return (sscanf (ansibuffer, "c_%d.nls", pValue)); // leave off TEXT()
}


INSTALL.H

/**************************************************************************\
* install.h -- header file supporting nls table installing.
*
* Kept separate because install.* may later be used independently as
*  a control panel applet.
\**************************************************************************/

/**************************************************************************\
*  Function prototypes, window procedures first.
\**************************************************************************/

LRESULT CALLBACK InstallTableProc (HWND, UINT, WPARAM, LPARAM);

int InstallFile (TCHAR*,TCHAR*);
int ListInstalledTables (HWND, UINT, int);
int GetTableFileNames (HWND);
int myScanf (TCHAR*, int*);

/**************************************************************************\
*  Defined constants.
\**************************************************************************/

/* IDs from the Install dialog box. */
#define DID_LISTBOX  1000
#define BID_ADD      1001


DIALOGS.C

/**************************************************************************\
* dialogs.c -- dialog procedure support for uconvert
*
*         Steve Firebaugh
*         Microsoft Developer Support
*         Copyright 1992 - 1998 Microsoft Corporation
*
\**************************************************************************/
#define UNICODE

#include <windows.h>
#include "uconvert.h"
#include "install.h"

/* Define affecting the positioning of child windows in dialog. */
#define DLGBORDER    GetSystemMetrics (SM_CXFRAME)*2

/***************************************************************************\
*    FUNCTION: ConversionOptionsProc
*
* Fill Dlg with state information on WM_INITDIALOG.
*  Take it back down and change internal state on WM_COMMAND, IDOK.
*
* Global variables:
*  gMBFlags
*  gWCFlags
*  glpDefaultChar
*  glpUsedDefaultChar
*
\***************************************************************************/
LRESULT CALLBACK ConversionOptionsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

  switch (message) {

    /******************************************************************\
    *  WM_INITDIALOG
    *
    * Set radio buttons appropriately.
    \******************************************************************/
    case WM_INITDIALOG:
      if (gMBFlags & MB_PRECOMPOSED)
        SendDlgItemMessage (hwnd, DID_PRECOMPOSED, BM_SETCHECK, 1, 0);

      if (gMBFlags & MB_COMPOSITE)
        SendDlgItemMessage (hwnd, DID_COMPOSITE, BM_SETCHECK, 1, 0);

      if (gMBFlags & MB_USEGLYPHCHARS)
        SendDlgItemMessage (hwnd, DID_USEGLYPHCHARS, BM_SETCHECK, 1, 0);

      if (gWCFlags & WC_COMPOSITECHECK)
        SendDlgItemMessage (hwnd, DID_COMPOSITECHECK, BM_SETCHECK, 1, 0);

      if (gWCFlags & WC_DISCARDNS)
        SendDlgItemMessage (hwnd, DID_DISCARDNS, BM_SETCHECK, 1, 0);

      if (gWCFlags & WC_SEPCHARS)
        SendDlgItemMessage (hwnd, DID_SEPCHARS, BM_SETCHECK, 1, 0);

      if (gWCFlags & WC_DEFAULTCHAR)
        SendDlgItemMessage (hwnd, DID_DEFAULTCHAR, BM_SETCHECK, 1, 0);

      SendDlgItemMessage (hwnd, DID_EFDEFAULTCHAR, EM_LIMITTEXT, 1, 0);
      SetDlgItemTextA (hwnd, DID_EFDEFAULTCHAR, glpDefaultChar);

      if (gUsedDefaultChar)
        SendDlgItemMessage (hwnd, DID_USEDDEFAULTCHAR, BM_SETCHECK, 1, 0);

      SetFocus (GetDlgItem (hwnd, IDOK));
    return FALSE;

    case WM_COMMAND:
      switch (LOWORD (wParam)) {
        case IDCANCEL:
          EndDialog (hwnd, FALSE);
        break;

        /******************************************************************\
        *  WM_COMMAND, IDOK
        *
        * Get state from radio buttons and others.
        \******************************************************************/
        case IDOK: {

          if (SendDlgItemMessage(hwnd, DID_PRECOMPOSED, BM_GETCHECK, 0,0))
            gMBFlags |= MB_PRECOMPOSED;
          else
            gMBFlags &= ~MB_PRECOMPOSED;

          if (SendDlgItemMessage(hwnd, DID_COMPOSITE, BM_GETCHECK, 0,0))
            gMBFlags |= MB_COMPOSITE;
          else
            gMBFlags &= ~MB_COMPOSITE;

          if (SendDlgItemMessage(hwnd, DID_USEGLYPHCHARS, BM_GETCHECK, 0,0))
            gMBFlags |= MB_USEGLYPHCHARS;
          else
            gMBFlags &= ~MB_USEGLYPHCHARS;

          if (SendDlgItemMessage(hwnd, DID_DISCARDNS, BM_GETCHECK, 0,0))
            gWCFlags |= WC_DISCARDNS;
          else
            gWCFlags &= ~WC_DISCARDNS;

          if (SendDlgItemMessage(hwnd, DID_COMPOSITECHECK, BM_GETCHECK, 0,0))
            gWCFlags |= WC_COMPOSITECHECK;
          else
            gWCFlags &= ~WC_COMPOSITECHECK;

          if (SendDlgItemMessage(hwnd, DID_SEPCHARS, BM_GETCHECK, 0,0))
            gWCFlags |= WC_SEPCHARS;
          else
            gWCFlags &= ~WC_SEPCHARS;

          if (SendDlgItemMessage(hwnd, DID_DEFAULTCHAR, BM_GETCHECK, 0,0))
            gWCFlags |= WC_DEFAULTCHAR;
          else
            gWCFlags &= ~WC_DEFAULTCHAR;

          GetDlgItemTextA (hwnd, DID_EFDEFAULTCHAR, glpDefaultChar, 2); // CHAR + NULL

          EndDialog (hwnd, TRUE);
        } break;

        /******************************************************************\
        *  WM_COMMAND, DID_*
        *
        * Manage radio button pattern.
        \******************************************************************/
        case DID_PRECOMPOSED:
          SendDlgItemMessage (hwnd, DID_PRECOMPOSED, BM_SETCHECK, TRUE, 0);
          SendDlgItemMessage (hwnd, DID_COMPOSITE,   BM_SETCHECK, FALSE, 0);
        break;

        case DID_COMPOSITE:
          SendDlgItemMessage (hwnd, DID_PRECOMPOSED, BM_SETCHECK, FALSE, 0);
          SendDlgItemMessage (hwnd, DID_COMPOSITE,   BM_SETCHECK, TRUE, 0);
        break;

      }
    break; /* end WM_COMMAND */

    case WM_SYSCOMMAND:
      if (LOWORD (wParam) == SC_CLOSE)
          EndDialog (hwnd, FALSE);
    break;

  } /* end switch */
  return FALSE;
}

/***************************************************************************\
*    FUNCTION: SourceOptionsProc
*
* Fill Dlg with state information on WM_INITDIALOG.
*  Take it back down and change internal state on WM_COMMAND, IDOK.
*
* Global variables:
*   gTypeSource
*   giSourceCodePage
*   giDestinationCodePage
*
\***************************************************************************/
LRESULT CALLBACK SourceOptionsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
TCHAR buffer[50];

  switch (message) {

    /******************************************************************\
    *  WM_INITDIALOG
    *
    * Set radio buttons appropriately.
    \******************************************************************/
    case WM_INITDIALOG:
      SetWindowText (hwnd, LoadResourceString(IDS_INTERPRET_SOURCE_AS));
      ListInstalledTables (GetDlgItem (hwnd, DID_CODEPAGELIST),CB_ADDSTRING, TRUE);
      if (gTypeSource == TYPEUNICODE)
        SendDlgItemMessage (hwnd, DID_RBUNICODE, BM_SETCHECK, 1, 0);

      /*
       * if gTypeSource == TYPECODEPAGE, then see if it is one of the
       *  standard radio buttons.  If so, check the right one.
       *  if not, check, "other" and select correct entry in combobox.
       */
      else if (gTypeSource == TYPECODEPAGE) {
        if (giSourceCodePage == GetACP())
          SendDlgItemMessage (hwnd, DID_RBANSICP, BM_SETCHECK, 1, 0);
        else if (giSourceCodePage == GetOEMCP())
          SendDlgItemMessage (hwnd, DID_RBOEMCP, BM_SETCHECK, 1, 0);
        else {
          SendDlgItemMessage (hwnd, DID_RBOTHERCP, BM_SETCHECK, 1, 0);
          wsprintf (buffer, TEXT("%d"), giSourceCodePage);
          SendDlgItemMessage (hwnd, DID_CODEPAGELIST, CB_SELECTSTRING, 0, (LPARAM)buffer);
        }
      } else {
        // OK to not be specified here.  Wait for user to make choice.
      }

      SetFocus (GetDlgItem (hwnd, IDOK));
    return FALSE;

    case WM_COMMAND:
      switch (LOWORD (wParam)) {
        case IDCANCEL:
          EndDialog (hwnd, FALSE);
        break;

        /******************************************************************\
        *  WM_COMMAND, IDOK
        *
        * Get state from radio buttons and others.
        \******************************************************************/
        case IDOK: {
          BOOL success;

          if (SendDlgItemMessage(hwnd, DID_RBUNICODE, BM_GETCHECK, 0,0)) {
            gTypeSource = TYPEUNICODE;
          } else if (SendDlgItemMessage(hwnd, DID_RBANSICP, BM_GETCHECK, 0,0)) {
            gTypeSource = TYPECODEPAGE;
            giSourceCodePage=GetACP();
          } else if (SendDlgItemMessage(hwnd, DID_RBOEMCP, BM_GETCHECK, 0,0)) {
            gTypeSource = TYPECODEPAGE;
            giSourceCodePage=GetOEMCP();
          } else if (SendDlgItemMessage(hwnd, DID_RBOTHERCP, BM_GETCHECK, 0,0)) {
            gTypeSource = TYPECODEPAGE;
            giSourceCodePage=GetDlgItemInt (hwnd, DID_CODEPAGELIST, &success, FALSE);
          } else
            gTypeSource = TYPEUNKNOWN;
          EndDialog (hwnd, TRUE);
        } break;
      }
    break; /* end WM_COMMAND */

    case WM_SYSCOMMAND:
      if (LOWORD (wParam) == SC_CLOSE)
          EndDialog (hwnd, FALSE);
    break;

  } /* end switch */
  return FALSE;
}

/***************************************************************************\
*    FUNCTION: DestinationOptionsProc
*
* Fill Dlg with state information on WM_INITDIALOG.
*  Take it back down and change internal state on WM_COMMAND, IDOK.
*
*
* Global variables:
*   gTypeSource
*   giSourceCodePage
*   giDestinationCodePage
*
\***************************************************************************/
LRESULT CALLBACK DestinationOptionsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
TCHAR buffer[50];

  switch (message) {

    /******************************************************************\
    *  WM_INITDIALOG
    *
    * Set radio buttons appropriately.
    \******************************************************************/
    case WM_INITDIALOG:
      SetWindowText (hwnd, LoadResourceString(IDS_CONVERT_DEST_TO));
      ListInstalledTables (GetDlgItem (hwnd, DID_CODEPAGELIST),CB_ADDSTRING, TRUE);

      /* if source is unicode, destination will be code page. */
      if (gTypeSource == TYPEUNICODE) {
        EnableWindow (GetDlgItem (hwnd, DID_RBUNICODE), FALSE);

        if (giDestinationCodePage == GetACP())
          SendDlgItemMessage (hwnd, DID_RBANSICP, BM_SETCHECK, 1, 0);
        else if (giDestinationCodePage == GetOEMCP())
          SendDlgItemMessage (hwnd, DID_RBOEMCP, BM_SETCHECK, 1, 0);
        else {
          SendDlgItemMessage (hwnd, DID_RBOTHERCP, BM_SETCHECK, 1, 0);
          wsprintf (buffer, TEXT("%d"), giDestinationCodePage);
          SendDlgItemMessage (hwnd, DID_CODEPAGELIST, CB_SELECTSTRING, 0, (LPARAM)buffer);
        }

      /* otherwise destination is unicode, so disable most of the checkboxes. */
      } else if (gTypeSource == TYPECODEPAGE) {
        SendDlgItemMessage (hwnd, DID_RBUNICODE, BM_SETCHECK, 1, 0);
        EnableWindow (GetDlgItem (hwnd, DID_RBANSICP), FALSE);
        EnableWindow (GetDlgItem (hwnd, DID_RBOEMCP), FALSE);
        EnableWindow (GetDlgItem (hwnd, DID_RBOTHERCP), FALSE);
        EnableWindow (GetDlgItem (hwnd, DID_CODEPAGELIST), FALSE);
      } else {
        EndDialog (hwnd, FALSE);  // shouldn't get here.
      }

      SetFocus (GetDlgItem (hwnd, IDOK));
    return FALSE;

    case WM_COMMAND:
      switch (LOWORD (wParam)) {
        case IDCANCEL:
          EndDialog (hwnd, FALSE);
        break;

        /******************************************************************\
        *  WM_COMMAND, IDOK
        *
        * Get state from radio buttons and others.
        \******************************************************************/
        case IDOK: {
          BOOL success;
          if (SendDlgItemMessage(hwnd, DID_RBUNICODE, BM_GETCHECK, 0,0)) {
            // Do nothing. gTypeSource already implies dest <-> unicode.
          } else if (SendDlgItemMessage(hwnd, DID_RBANSICP, BM_GETCHECK, 0,0)) {
            giDestinationCodePage=GetACP();
          } else if (SendDlgItemMessage(hwnd, DID_RBOEMCP, BM_GETCHECK, 0,0)) {
            giDestinationCodePage=GetOEMCP();
          } else if (SendDlgItemMessage(hwnd, DID_RBOTHERCP, BM_GETCHECK, 0,0)) {
            giDestinationCodePage=GetDlgItemInt (hwnd, DID_CODEPAGELIST, &success, FALSE);
          }

          EndDialog (hwnd, TRUE);
        } break;
      }
    break; /* end WM_COMMAND */

    case WM_SYSCOMMAND:
      if (LOWORD (wParam) == SC_CLOSE)
          EndDialog (hwnd, FALSE);
    break;

  } /* end switch */
  return FALSE;
}

/***************************************************************************\
*    FUNCTION: ViewSourceProc
*
* Fill Text, Name, and Type information into the dialog.
*  Set a proper font to display the text depending on what type it is.
*
\***************************************************************************/
LRESULT CALLBACK ViewSourceProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
RECT rect;

  switch (message) {

    /******************************************************************\
    *  WM_INITDIALOG
    \******************************************************************/
    case WM_INITDIALOG:

      /* Text is unicode... use *W() variants of functions. */
      if (gTypeSource == TYPEUNICODE) {
        WCHAR buffer[MAX_PATH];
        LOGFONTW logfont;
        HFONT hFont;

        SetWindowTextW (GetDlgItem (hwnd, DID_TEXT), (LPCWSTR)pSourceData);
        GetWindowTextW (hwndName0, buffer, MAX_PATH);
        SetWindowTextW (GetDlgItem (hwnd, DID_NAME), buffer);
        GetWindowTextW (hwndCodePage0, buffer, MAX_PATH);
        SetWindowTextW (GetDlgItem (hwnd, DID_TYPE), buffer);

        GetObjectW (GetStockObject (SYSTEM_FONT), sizeof(LOGFONTW), &logfont);
        logfont.lfCharSet = UNICODE_CHARSET;
        lstrcpyW (logfont.lfFaceName, L"Lucida Sans Unicode");
        hFont = CreateFontIndirectW (&logfont);
        SendMessageW (GetDlgItem (hwnd, DID_TEXT), WM_SETFONT, (WPARAM) hFont, 0);

      /* Text is codepage... use *A() variants of functions. */
      } else {
        char buffer[MAX_PATH];
        LOGFONTA logfont;
        HFONT hFont;

        SetWindowTextA (GetDlgItem (hwnd, DID_TEXT), pSourceData);
        GetWindowTextA (hwndName0, buffer, MAX_PATH);
        SetWindowTextA (GetDlgItem (hwnd, DID_NAME), buffer);
        GetWindowTextA (hwndCodePage0, buffer, MAX_PATH);
        SetWindowTextA (GetDlgItem (hwnd, DID_TYPE), buffer);

        GetObjectA (GetStockObject (SYSTEM_FONT), sizeof(LOGFONTA), &logfont);
        logfont.lfCharSet = giSourceCodePage;
        lstrcpyA (logfont.lfFaceName, "System");
        hFont = CreateFontIndirectA (&logfont);
        SendMessageA (GetDlgItem (hwnd, DID_TEXT), WM_SETFONT, (WPARAM) hFont, 0);

      }
      SetWindowText (hwnd, LoadResourceString(IDS_VIEW_SOURCE_TITLE));
      GetClientRect (hwnd, &rect);
      SendMessage (hwnd, WM_SIZE, 0,
                 MAKELPARAM ((rect.right - rect.left), (rect.bottom - rect.top)));
      return TRUE;
    break;

    case WM_SIZE: {
      HWND hwndText;

      hwndText = GetDlgItem (hwnd, DID_TEXT);
      MoveWindow (hwndText, DLGBORDER, 60, (int) LOWORD(lParam) - 2*DLGBORDER,
                                (int) HIWORD(lParam) - 60 - DLGBORDER , TRUE);
    }

    case WM_COMMAND:
      switch (LOWORD (wParam)) {
        case IDCANCEL:
        case IDOK:
          EndDialog (hwnd, TRUE);
      }
    break; /* end WM_COMMAND */

    case WM_SYSCOMMAND:
      if (LOWORD (wParam) == SC_CLOSE)
          EndDialog (hwnd, FALSE);
    break;

  } /* end switch */
  return FALSE;
}

/***************************************************************************\
*    FUNCTION: ViewDestinationProc
*
* Fill Text, Name, and Type information into the dialog.
*  Set a proper font to display the text depending on what type it is.
*
\***************************************************************************/
LRESULT CALLBACK ViewDestinationProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
RECT rect;

  switch (message) {

    /******************************************************************\
    *  WM_INITDIALOG
    \******************************************************************/
    case WM_INITDIALOG:

      /* Destination text is unicode... use *W() variants of functions. */
      if (gTypeSource != TYPEUNICODE) {
        WCHAR buffer[MAX_PATH];
        LOGFONTW logfont;
        HFONT hFont;

        SetWindowTextW (GetDlgItem (hwnd, DID_TEXT), (LPCWSTR)pDestinationData);
        GetWindowTextW (hwndName1, buffer, MAX_PATH);
        SetWindowTextW (GetDlgItem (hwnd, DID_NAME), buffer);
        GetWindowTextW (hwndCodePage1, buffer, MAX_PATH);
        SetWindowTextW (GetDlgItem (hwnd, DID_TYPE), buffer);

        GetObjectW (GetStockObject (SYSTEM_FONT), sizeof(LOGFONTW), &logfont);
        logfont.lfCharSet = UNICODE_CHARSET;
        lstrcpyW (logfont.lfFaceName, L"Lucida Sans Unicode");
        hFont = CreateFontIndirectW (&logfont);
        SendMessageW (GetDlgItem (hwnd, DID_TEXT), WM_SETFONT, (WPARAM) hFont, 0);

      /* Destination text is codepage... use *A() variants of functions. */
      } else {
        char buffer[MAX_PATH];
        LOGFONTA logfont;
        HFONT hFont;

        SetWindowTextA (GetDlgItem (hwnd, DID_TEXT), pDestinationData);
        GetWindowTextA (hwndName1, buffer, MAX_PATH);
        SetWindowTextA (GetDlgItem (hwnd, DID_NAME), buffer);
        GetWindowTextA (hwndCodePage1, buffer, MAX_PATH);
        SetWindowTextA (GetDlgItem (hwnd, DID_TYPE), buffer);

        GetObjectA (GetStockObject (SYSTEM_FONT), sizeof(LOGFONTA), &logfont);
        logfont.lfCharSet = giDestinationCodePage;
        lstrcpyA (logfont.lfFaceName, "System");
        hFont = CreateFontIndirectA (&logfont);
        SendMessageA (GetDlgItem (hwnd, DID_TEXT), WM_SETFONT, (WPARAM) hFont, 0);

      }
      SetWindowText (hwnd, LoadResourceString(IDS_VIEW_DEST_TITLE));
      GetClientRect (hwnd, &rect);
      SendMessage (hwnd, WM_SIZE, 0,
                 MAKELPARAM ((rect.right - rect.left),(rect.bottom - rect.top)));

      return TRUE;
    break;

    case WM_SIZE: {
      HWND hwndText;

      hwndText = GetDlgItem (hwnd, DID_TEXT);
      MoveWindow (hwndText, DLGBORDER, 60, (int) LOWORD(lParam) - 2*DLGBORDER,
                                (int) HIWORD(lParam) - 60 - DLGBORDER , TRUE);
    }

    case WM_COMMAND:
      switch (LOWORD (wParam)) {
        case IDCANCEL:
        case IDOK:
          EndDialog (hwnd, TRUE);
      }
    break; /* end WM_COMMAND */

    case WM_SYSCOMMAND:
      if (LOWORD (wParam) == SC_CLOSE)
          EndDialog (hwnd, FALSE);
    break;

  } /* end switch */
  return FALSE;
}


UCONVERT.C

/**************************************************************************\
* uconvert.c -- convert to/from unicode using
*                MulitByteToWideChar & WideCharToMulitByte
*
*         Steve Firebaugh
*         Microsoft Developer Support
*         Copyright 1992 - 1998 Microsoft Corporation
*
\**************************************************************************/
#define UNICODE

#include <windows.h>
#include <commdlg.h>
#include "uconvert.h"
#include "install.h"

/**************************************************************************\
*  Global variables.
\**************************************************************************/

HANDLE hInst;

/* declare global HWNDs for the child windows.
*   They are created at WM_CREATE  of the main window.
*   Also used in the "View" dialogs.
*/
HWND hwndLabel0, hwndLabel1;
HWND hwndName0, hwndName1;
HWND hwndSize0, hwndSize1;
HWND hwndCodePage0, hwndCodePage1;
HWND hwndByteOrder0, hwndByteOrder1;
HWND hwndButton0, hwndButton1;

/* Global variables storing the source and destination "type" information.
*
* used to communicate between main wnd proc, and *OptionsProc.
*
* gTypeSource - stores the type interpretation of the source data
*       (and implicitly the destination data.)
*   TYPEUNKNOWN: indeterminant... not set. Can not do conversion.
*   TYPEUNICODE: source unicode & destination giDestinationCodePage.
*   TYPECODEPAGE: source giSourceCodePage & destination unicode.
*
* giSourceCodePage stores valid source code page iff gTypeSource == TRUE
* giDestinationCodePage stores valid destination code page iff gTypeSource == FALSE
*
*/
int gTypeSource;
UINT giSourceCodePage;
UINT giDestinationCodePage;

/* Pointers to the source and destination data, and the
 *  count of bytes in each of the buffers.
 */
#define NODATA 0
PBYTE pSourceData =       NULL;
PBYTE pDestinationData =  NULL;
int   nBytesSource =      NODATA;
int   nBytesDestination = NODATA;

/* Conversion Options variables. */
DWORD gMBFlags = MB_PRECOMPOSED;
DWORD gWCFlags = 0;

char  glpDefaultChar[4] = "?";
BOOL  gUsedDefaultChar = FALSE;

/* Handling the Byte Order Mark (BOM).
*
* If the input file begins with a BOM, then we know it is unicode,
*  we skip over the BOM and decrement the size of data by SIZEOFBOM.
*
*
* Before writing data that we know is unicode, write the szBOM string
*  to the file.
*
* Notice that this means that the file sizes we show in the window
*  do NOT include the BOM.
*/

char szBOM[] = "\377\376";  // 0xFF, 0xFE  // leave off TEXT() macro.
char szRBOM[] = "\376\377";  // 0xFF, 0xFE  // leave off TEXT() macro.
#define SIZEOFBOM 2

/* Title of main window */
TCHAR TitleMBToWC[]= TEXT("UConvert -- MultiByteToWideChar()");
TCHAR TitleWCToMB[]= TEXT("UConvert -- WideCharToMultiByte()");
TCHAR TitleUnknown[]= TEXT("UConvert.");

/* file name of the online help file */
TCHAR szHelpPathName[] = TEXT("uconvert.HLP");

/* Strings used to fill onscreen windows. */
TCHAR szBlank[] = TEXT("");

/* MessageBox() strings and flags. */
TCHAR MBTitle[30]= TEXT("");
UINT  MBFlags = MB_OK | MB_ICONEXCLAMATION;

/* misc. defines affecting size and placement of child windows */
#define BORDER    GetSystemMetrics (SM_CXFRAME)*4
#define WHEIGHT   GetSystemMetrics (SM_CYMENU)

/**************************************************************************\
*
*  function:  WinMain()
*
*
\**************************************************************************/
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{
    MSG    msg;
    WNDCLASS  wc;
    HWND   hwndMain;
    HACCEL haccel;

    UNREFERENCED_PARAMETER( lpCmdLine );
    UNREFERENCED_PARAMETER(  nCmdShow );
    hInst = hInstance;

    /* Check for previous instance.  If none, then register class. */
    if (!hPrevInstance) {

      wc.style = 0;
      wc.lpfnWndProc = (WNDPROC)MainWndProc;

      wc.cbClsExtra = 0;
      wc.cbWndExtra = 0;
      wc.hInstance = hInstance;
      wc.hIcon = LoadIcon(hInstance, TEXT("uconvertIcon"));
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = GetStockObject (LTGRAY_BRUSH);
      wc.lpszMenuName =  TEXT("uconvertMenu");
      wc.lpszClassName = TEXT("uconvert");

      if (!RegisterClass(&wc)) return (FALSE);

    }  /* class registered o.k. */

    /* Create the main window.  Return false if CreateWindow() fails */
    hwndMain = CreateWindow(
        TEXT("uconvert"),
        TitleUnknown,
        (WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX)) | WS_VISIBLE,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        512,  // Big enough for most of the text.
        16*WHEIGHT,
        NULL, NULL, hInst, NULL);

    if (!hwndMain) return (FALSE);

    /* Load the accelerator table that provides clipboard support. */
    haccel = LoadAccelerators (hInst, TEXT("uconvertAccel"));

    LoadString(hInst,IDS_APP_WARNING,MBTitle,sizeof(MBTitle));

    /* Loop getting messages and dispatching them. */
    while (GetMessage(&msg, NULL, 0,0)) {
      if (!TranslateAccelerator(hwndMain, haccel, &msg)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }

    return (msg.wParam);
}

/**************************************************************************\
*
*  function:  MainWndProc()
*
*
* On WM_CREATE create all of the child windows.
* On WM_DESTROY make sure that all dynamically allocated memory is freed.
* On WM_PAINT, outline many of the child windows.
* On WM_COMMAND, respond to the command messages properly.
*
\**************************************************************************/
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

/* misc. variables used in multiple messages cases. */
RECT clientrect;
RECT rect;
TCHAR buffer[50];
static TCHAR szFilter[MAX_PATH];

  switch (message) {

    /**********************************************************************\
    *  WM_CREATE
    *
    * Create all of the child windows used on this main window.
    *  Assign the HWNDs to the correct static variables.
    *
    \**********************************************************************/
    case WM_CREATE: {
      GetClientRect (hwnd, &clientrect);

      /* Create Source Windows. */
      CopyRect (&rect, &clientrect);
      rect.right = (clientrect.right - clientrect.left) /2;
      InflateRect (&rect, -2*BORDER, -BORDER);
      createwindows(&rect,
                   hwnd,
                   &hwndLabel0,
                   &hwndName0,
                   &hwndSize0,
                   &hwndCodePage0,
                   &hwndByteOrder0,
                   &hwndButton0);

      /* Create Destination Windows. */
      CopyRect (&rect, &clientrect);
      rect.left = (clientrect.right - clientrect.left) /2;
      InflateRect (&rect, -2*BORDER, -BORDER);
      createwindows(&rect,
                   hwnd,
                   &hwndLabel1,
                   &hwndName1,
                   &hwndSize1,
                   &hwndCodePage1,
                   &hwndByteOrder1,
                   &hwndButton1);

      /* fill in window information that is different for source/destination */
      SetWindowText (hwndLabel0, LoadResourceString(IDS_SOURCE));
      SetWindowText (hwndLabel1, LoadResourceString(IDS_DESTINATION));

      SetWindowText (hwndButton0, LoadResourceString(IDS_VIEW_SOURCE_BTN));
      SetWindowText (hwndButton1, LoadResourceString(IDS_VIEW_DESTINATION_BTN));

      SetWindowLong (hwndButton0, GWL_ID, BID_VIEWSOURCE      );
      SetWindowLong (hwndButton1, GWL_ID, BID_VIEWDESTINATION );

      gTypeSource = TYPEUNKNOWN;
      giSourceCodePage =      GetACP();  // Just some reasonable initializer.
      giDestinationCodePage = GetACP();  // Just some reasonable initializer.

      /* initialize source & destination data correctly */
      SendMessage (hwnd, WM_COMMAND, MID_CLEARSOURCE,      0);
      SendMessage (hwnd, WM_COMMAND, MID_CLEARDESTINATION, 0);

      /* Build up the correct filter strings for OPENFILENAME structure
       *  Do it here so that we only have to do it once.
       */
      {
        TCHAR *p;

        p = szFilter;
        lstrcpy (buffer,LoadResourceString(IDS_FILE_FILTER_SPEC1));
        lstrcpy (p,buffer);
        p += lstrlen (buffer) +1;
        lstrcpy (buffer,TEXT("*.*"));
        lstrcpy (p,buffer);
        p += lstrlen (buffer) +1;

        lstrcpy (buffer,LoadResourceString(IDS_FILE_FILTER_SPEC2));
        lstrcpy (p,buffer);
        p += lstrlen (buffer) +1;
        lstrcpy (buffer,TEXT("*.txt"));
        lstrcpy (p,buffer);
        p += lstrlen (buffer) +1;

        lstrcpy (buffer,LoadResourceString(IDS_FILE_FILTER_SPEC3));
        lstrcpy (p,buffer);
        p += lstrlen (buffer) +1;
        lstrcpy (buffer,TEXT("*.utf"));
        lstrcpy (p,buffer);
        p += lstrlen (buffer) +1;

        lstrcpy (p,TEXT("\0"));
      }
    } break; /* end WM_CREATE */

    /**********************************************************************\
    *  WM_DESTROY
    *
    * Release the Online help, and free allocated memory if any.
    \**********************************************************************/
    case WM_DESTROY:
      WinHelp( hwnd, szHelpPathName, (UINT) HELP_QUIT, (DWORD) NULL );
      ManageMemory (MMFREE, MMSOURCE,      0, pSourceData);
      ManageMemory (MMFREE, MMDESTINATION, 0, pDestinationData);
      PostQuitMessage(0);
    break;

    /**********************************************************************\
    *  WM_CTLCOLOR*
    *
    * Set the background of the child controls to be gray here.
    \**********************************************************************/
    case WM_CTLCOLORBTN:
    case WM_CTLCOLORSTATIC: {
      HDC hdc;

      hdc = (HDC) wParam;
      SetBkMode (hdc, TRANSPARENT);
      return (LRESULT)GetStockObject (LTGRAY_BRUSH);
    } break;

    /**********************************************************************\
    *  WM_PAINT
    *
    * Simply draw the two vertical divider lines, and 3D frame the children.
    *
    \**********************************************************************/
    case WM_PAINT: {
      HDC hdc;
      PAINTSTRUCT ps;

      hdc = BeginPaint(hwnd, &ps);
      GetClientRect (hwnd, &clientrect);

      /* draw vertical separator line in the center */
      rect.left = (clientrect.right - clientrect.left ) /2 -1;
      rect.top = clientrect.top;
      rect.right = rect.left +1;;
      rect.bottom = clientrect.bottom;
      FrameRect (hdc, &rect, GetStockObject (GRAY_BRUSH));
      SelectObject (hdc, GetStockObject (WHITE_PEN));
      MoveToEx (hdc, rect.right, rect.top, NULL);
      LineTo (hdc,rect.right, rect.bottom);

      /* draw 3D outlines of child windows. */
      framechildwindow (hdc, hwnd, hwndName0);
      framechildwindow (hdc, hwnd, hwndSize0);
      framechildwindow (hdc, hwnd, hwndCodePage0);
      framechildwindow (hdc, hwnd, hwndByteOrder0);

      framechildwindow (hdc, hwnd, hwndName1);
      framechildwindow (hdc, hwnd, hwndSize1);
      framechildwindow (hdc, hwnd, hwndCodePage1);
      framechildwindow (hdc, hwnd, hwndByteOrder1);

      /* underline the labels */
      underlinechildwindow (hdc, hwnd, hwndLabel0);
      underlinechildwindow (hdc, hwnd, hwndLabel1);

      EndPaint (hwnd, &ps);
    } break; /* end WM_PAINT */

    /**********************************************************************\
    *  WMU_ADJUSTFORNEWSOURCE
    *
    * lParam - szName of source (file, clipboard, ...)
    *
    * global - nBytesSource
    *
    * "user message."  Set the text of the Source windows
    \**********************************************************************/
    case WMU_ADJUSTFORNEWSOURCE: {
      LPVOID szName;

      szName = (LPVOID) lParam;

      /* Set Window text appropriately */
      SetWindowText (hwndName0, szName);
      wsprintf (buffer, LoadResourceString(IDS_BYTES), nBytesSource);
      SetWindowText (hwndSize0, buffer);
      SetWindowText (hwndByteOrder0, szBlank);

      /* Clear the destination data if any to avoid user confusion. */
      SendMessage (hwnd, WM_COMMAND, MID_CLEARDESTINATION, 0);

      /* Reset the "type strings" based on new gTypeSource. */
      SendMessage (hwnd, WMU_SETTYPESTRINGS, 0,0);
    } break;

    /**********************************************************************\
    *  WMU_SETTYPESTRINGS
    *
    * "user message."  Set the text of the "type" windows to reflect
    *  the state stored in gTypeSource and gi*CodePage.
    *
    \**********************************************************************/
    case WMU_SETTYPESTRINGS:
      switch (gTypeSource) {
        case TYPEUNICODE:
          SetWindowText (hwndCodePage0, TEXT("Unicode"));
          wsprintf (buffer, LoadResourceString(IDS_CODE_PAGE),
                  giDestinationCodePage);
          SetWindowText (hwndCodePage1, buffer);
          SetWindowText (hwnd, TitleWCToMB);
        break;
        case TYPECODEPAGE:
          wsprintf (buffer, LoadResourceString(IDS_CODE_PAGE),
                  giSourceCodePage);
          SetWindowText (hwndCodePage0, buffer);
          SetWindowText (hwndCodePage1, TEXT("Unicode"));
          SetWindowText (hwnd, TitleMBToWC);
        break;
        case TYPEUNKNOWN:
          SetWindowText (hwndCodePage0, szBlank);
          SetWindowText (hwndCodePage1, szBlank);
          SetWindowText (hwnd, TitleUnknown);
        break;
      } /* end switch gTypeSource */
    break;

    /**********************************************************************\
    *  WM_INITMENU
    *
    * Manage the enabled state of all of the menus.
    *  Notice that the button enabled state is taken care of in ManageMemory().
    *
    * In general, this is dependent upon pSourceData & pDestinationData.
    *  They are either NULL or non-NULL, and menu items are dependent upon
    *  this state.
    *
    * One exception is the "Paste from Clipboard menu" which is enabled
    *  conditional upon there being text data in the clipboard.
    *
    \**********************************************************************/
    case WM_INITMENU:

      /* Adjust the "Paste from Clipboard menu" */
      OpenClipboard (hwnd);
      if (IsClipboardFormatAvailable (CF_UNICODETEXT) ||
          IsClipboardFormatAvailable (CF_OEMTEXT) ||
          IsClipboardFormatAvailable (CF_TEXT))
        EnableMenuItem (GetMenu (hwnd),MID_PASTESOURCE,      MF_ENABLED);
      else
        EnableMenuItem (GetMenu (hwnd),MID_PASTESOURCE,      MF_GRAYED);
      CloseClipboard ();

      /* Adjust the source data dependent menus. */
      if (pSourceData != NULL) {
        EnableMenuItem (GetMenu (hwnd),MID_SOURCEOPT,        MF_ENABLED);
        EnableMenuItem (GetMenu (hwnd),MID_SWAPSOURCE,       MF_ENABLED);
        EnableMenuItem (GetMenu (hwnd),MID_CLEARSOURCE,      MF_ENABLED);
        EnableMenuItem (GetMenu (hwnd),MID_CONVERTNOW,       MF_ENABLED);
        EnableMenuItem (GetMenu (hwnd),MID_CONVERSIONOPT,    MF_ENABLED);
        EnableMenuItem (GetMenu (hwnd),MID_DESTINATIONOPT,   MF_ENABLED);
      } else {
        EnableMenuItem (GetMenu (hwnd),MID_SOURCEOPT,        MF_GRAYED);
        EnableMenuItem (GetMenu (hwnd),MID_SWAPSOURCE,       MF_GRAYED);
        EnableMenuItem (GetMenu (hwnd),MID_CLEARSOURCE,      MF_GRAYED);
        EnableMenuItem (GetMenu (hwnd),MID_CONVERTNOW,       MF_GRAYED);
        EnableMenuItem (GetMenu (hwnd),MID_CONVERSIONOPT,    MF_GRAYED);
        EnableMenuItem (GetMenu (hwnd),MID_DESTINATIONOPT,   MF_GRAYED);
      }

      /* Adjust the destination data dependent menus. */
      if (pDestinationData != NULL) {
        EnableMenuItem (GetMenu (hwnd),MID_SAVEAS,           MF_ENABLED);
        EnableMenuItem (GetMenu (hwnd),MID_SWAPDESTINATION,  MF_ENABLED);
        EnableMenuItem (GetMenu (hwnd),MID_COPYDESTINATION,  MF_ENABLED);
        EnableMenuItem (GetMenu (hwnd),MID_CLEARDESTINATION, MF_ENABLED);
      } else {
        EnableMenuItem (GetMenu (hwnd),MID_SAVEAS,           MF_GRAYED);
        EnableMenuItem (GetMenu (hwnd),MID_SWAPDESTINATION,  MF_GRAYED);
        EnableMenuItem (GetMenu (hwnd),MID_COPYDESTINATION,  MF_GRAYED);
        EnableMenuItem (GetMenu (hwnd),MID_CLEARDESTINATION, MF_GRAYED);
      }

    break;

    /**********************************************************************\
    *  WM_COMMAND
    *
    * Just switch() on the command ID.  Notice that menu and button
    *  command messages are treated the same.
    *
    \**********************************************************************/
    case WM_COMMAND:
      switch (LOWORD(wParam)) {

        /******************************************************************\
        *  WM_COMMAND, MID_OPEN
        *
        * Put up common dialog, try to open & read file.
        *  Fill windows with correct text & fill pSourceData.
        \******************************************************************/
        case MID_OPEN  : {
          HANDLE hFile;
          DWORD nBytesRead;
          TCHAR szFile[MAX_PATH],szFileTitle[MAX_PATH];

          /* First set up the structure for the GetOpenFileName
           *  common dialog.
           */
          {
            OPENFILENAME OpenFileName;
            /* buffers for the file names. */

            wsprintf (szFile, szBlank);
            wsprintf (szFileTitle, szBlank);

            OpenFileName.lStructSize       = sizeof(OPENFILENAME);
            OpenFileName.hwndOwner         = hwnd;
            OpenFileName.hInstance         = (HANDLE) hInst;
            OpenFileName.lpstrFilter       = szFilter; // built in WM_CREATE
            OpenFileName.lpstrCustomFilter = NULL;
            OpenFileName.nMaxCustFilter    = 0L;
            OpenFileName.nFilterIndex      = 1L;
            OpenFileName.lpstrFile         = szFile;
            OpenFileName.nMaxFile          = MAX_PATH;
            OpenFileName.lpstrFileTitle    = szFileTitle;
            OpenFileName.nMaxFileTitle     = MAX_PATH;
            OpenFileName.lpstrInitialDir   = NULL;
            OpenFileName.lpstrTitle        = LoadResourceString(IDS_OPEN_FILE_TITLE);

            OpenFileName.nFileOffset       = 0;
            OpenFileName.nFileExtension    = 0;
            OpenFileName.lpstrDefExt       = NULL;

            OpenFileName.lCustData         = 0;
            OpenFileName.lpfnHook          = NULL;
            OpenFileName.lpTemplateName    = NULL;

            OpenFileName.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;

            if (!GetOpenFileName(&OpenFileName)) return 0;
          }

          /* User has filled in the file information.
           *  Try to open that file for reading.
           */
          hFile = CreateFile(szFile,
                       GENERIC_READ,
                       0,
                       NULL,
                       OPEN_EXISTING,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL);
          if (hFile == INVALID_HANDLE_VALUE) {
            MessageBox (hwnd, LoadResourceString(IDS_OPEN_FILE_FAILED),
                    MBTitle, MBFlags);
            return 0;
          }

          /* make sure file is not too big... i.e. > 2^32
           *  If it is OK, write the file size in hwndSize0
           */
          {
            BY_HANDLE_FILE_INFORMATION bhfi;

            GetFileInformationByHandle (hFile, &bhfi);
            if (bhfi.nFileSizeHigh != 0) {
              MessageBox (hwnd, LoadResourceString(IDS_FILE_TOO_BIG),
                      MBTitle, MBFlags);
              CloseHandle (hFile);
              return 0;
            }

            nBytesSource= bhfi.nFileSizeLow;

          }

          /* Allocate space for string, including potential UNICODE_NULL */
          pSourceData = ManageMemory (MMALLOC, MMSOURCE, nBytesSource +2, pSourceData);
          if (pSourceData == NULL) {
            CloseHandle (hFile);
            return 0;
          }

          if (nBytesSource < SIZEOFBOM) {
              gTypeSource = TYPEUNKNOWN;
              goto no_bom;
          }

          /* first read two bytes and look for BOM */
          if (!ReadFile (hFile, pSourceData,SIZEOFBOM, &nBytesRead, NULL)) {
            MessageBox (hwnd, LoadResourceString(IDS_READFILE_FAILED),
                    MBTitle, MBFlags);
            CloseHandle (hFile);
            pSourceData = ManageMemory (MMFREE, MMSOURCE, 0, pSourceData);
            return 0;
          }

          /* If file begins with BOM, then we know the type,
           *  we'll decrement the number of bytes by two,
           *  and read the rest of the data.
           */
          if (IsBOM (pSourceData)) {
            gTypeSource = TYPEUNICODE;
            nBytesSource -=SIZEOFBOM;

          /* If file begins with Reverse BOM, then we know the type,
           *  we'll decrement the number of bytes by two,
           *  and read the rest of the data, and post a message so
           *  that we know to swap the order later.
           */
          } else if (IsRBOM (pSourceData)) {
            gTypeSource = TYPEUNICODE;
            nBytesSource -=SIZEOFBOM;
            MessageBox (hwnd, LoadResourceString(IDS_SWAPPING_BYTE_ORDER),
                    MBTitle, MBFlags);
            PostMessage (hwnd, WM_COMMAND, MID_SWAPSOURCE, 0);

          /* Oops, does not begin with BOM.
           *  Reset file pointer, and read data.
           */
          } else {
            gTypeSource = TYPEUNKNOWN;
            SetFilePointer (hFile, -SIZEOFBOM, NULL, FILE_CURRENT);
          }

          no_bom:

          /* try to read all of it into memory */
          if (!ReadFile (hFile, pSourceData,nBytesSource, &nBytesRead, NULL)) {
            MessageBox (hwnd, LoadResourceString(IDS_READFILE_FAILED),
                    MBTitle, MBFlags);
            CloseHandle (hFile);
            pSourceData = ManageMemory (MMFREE, MMSOURCE, 0, pSourceData);
            return 0;
          }

          CloseHandle (hFile);

          /* If we don't know the file type at this point,
           *  try to determine if it is unicode.
           */
          if (gTypeSource == TYPEUNKNOWN) {
            if (IsUnicode (pSourceData)) {
              gTypeSource = TYPEUNICODE;
              pSourceData[nBytesSource]   = 0;  // UNICODE_NULL
              pSourceData[nBytesSource+1] = 0;
            } else {
              gTypeSource = TYPECODEPAGE;
              pSourceData[nBytesSource] = 0;
            }
          }

          SendMessage (hwnd, WMU_ADJUSTFORNEWSOURCE, 0, (LPARAM)szFile);

        } break;  /* end case MID_OPEN */

        /******************************************************************\
        *  WM_COMMAND, MID_SAVEAS
        *
        * Put up common dialog, try to open file, and write data to it.
        \******************************************************************/
        case MID_SAVEAS: {
          HANDLE hFile;
          DWORD nBytesRead;
          TCHAR szFile[MAX_PATH],szFileTitle[MAX_PATH];

          if (nBytesDestination == NODATA ) {
            MessageBox (hwnd, LoadResourceString(IDS_NOTEXT_TO_SAVE),
                    MBTitle, MBFlags);
            return 0;
          }

          /* Set up the structure for the GetSaveFileName
           *  common dialog.
           */
          {
            OPENFILENAME OpenFileName;
            /* buffers for the file names. */

            wsprintf (szFile, szBlank);
            wsprintf (szFileTitle, szBlank);

            OpenFileName.lStructSize       = sizeof(OPENFILENAME);
            OpenFileName.hwndOwner         = hwnd;
            OpenFileName.hInstance         = (HANDLE) hInst;
            OpenFileName.lpstrFilter       = szFilter;
            OpenFileName.lpstrCustomFilter = NULL;
            OpenFileName.nMaxCustFilter    = 0L;
            OpenFileName.nFilterIndex      = 1L;
            OpenFileName.lpstrFile         = szFile;
            OpenFileName.nMaxFile          = MAX_PATH;
            OpenFileName.lpstrFileTitle    = szFileTitle;
            OpenFileName.nMaxFileTitle     = MAX_PATH;
            OpenFileName.lpstrInitialDir   = NULL;
            OpenFileName.lpstrTitle        = LoadResourceString(IDS_SAVE_AS_TITLE);

            OpenFileName.nFileOffset       = 0;
            OpenFileName.nFileExtension    = 0;
            OpenFileName.lpstrDefExt       = NULL;

            OpenFileName.lCustData         = 0;
            OpenFileName.lpfnHook          = NULL;
            OpenFileName.lpTemplateName    = NULL;

            OpenFileName.Flags = OFN_HIDEREADONLY;

            if (!GetSaveFileName(&OpenFileName)) return 0;
          }

          /* User has filled in the file information.
           *  Try to open that file for writing.
           */
          hFile = CreateFile(szFile,
                      GENERIC_WRITE,
                      0,
                      NULL,
                      CREATE_ALWAYS,
                      FILE_ATTRIBUTE_NORMAL,
                      NULL);

          if (hFile == INVALID_HANDLE_VALUE) {
            MessageBox (hwnd, LoadResourceString(IDS_CREATEFILE_FAILED),
                    MBTitle, MBFlags);
            return 0;
          }

          /* if destination is unicode, try to write BOM first.
           *  unless the bytes have been swapped
           *  (criterion: hwndByteOrder contains text)
           *  in which case, write a Reverse Byte Order Mark.
           */
          if (gTypeSource == TYPECODEPAGE) {
            if (GetWindowTextLength (hwndByteOrder1) == 0) {

              if (!WriteFile (hFile, szBOM, SIZEOFBOM, &nBytesRead, NULL)) {
                MessageBox (hwnd, LoadResourceString(IDS_WRITEFILE_FAILED),
                        MBTitle, MBFlags);
                CloseHandle (hFile);
                return 0;
              }

            }else {
              if (!WriteFile (hFile, szRBOM, SIZEOFBOM, &nBytesRead, NULL)) {
                MessageBox (hwnd, LoadResourceString(IDS_WRITEFILE_FAILED),
                        MBTitle, MBFlags);
                CloseHandle (hFile);
                return 0;
              }

            }
          }

          /* try to write all of it into memory */
          if (!WriteFile (hFile, pDestinationData,nBytesDestination, &nBytesRead, NULL)) {
            MessageBox (hwnd, LoadResourceString(IDS_WRITEFILE_FAILED),
                    MBTitle, MBFlags);
            CloseHandle (hFile);
            return 0;
          }

          SetWindowText (hwndName1, szFile);
          CloseHandle (hFile);

        } break;

        /**********************************************************************\
        *  WM_COMMAND, MID_PASTESOURCE
        *
        * Paste the clipboard's prefered data format into the source.
        *  Fills pSourceData.
        \**********************************************************************/
        case MID_PASTESOURCE: {
          UINT  iFormat;
          PVOID pData;

          OpenClipboard (hwnd);

          iFormat = 0;
          while (iFormat = EnumClipboardFormats(iFormat))
            if ((iFormat == CF_UNICODETEXT) || (iFormat == CF_OEMTEXT) || (iFormat == CF_TEXT)) {

              HGLOBAL hMem;

              hMem = GetClipboardData (iFormat);
              pData = GlobalLock(hMem);

              switch (iFormat) {
                case CF_UNICODETEXT:
                  nBytesSource = lstrlenW (pData) *2;
                  pSourceData= ManageMemory (MMALLOC, MMSOURCE, nBytesSource+2, pSourceData);
                  lstrcpyW ((LPVOID)pSourceData, pData);
                  gTypeSource = TYPEUNICODE;
                break;

                case CF_OEMTEXT:
                  nBytesSource = lstrlenA (pData);
                  pSourceData= ManageMemory (MMALLOC, MMSOURCE, nBytesSource+1, pSourceData);
                  lstrcpyA (pSourceData, pData);
                  gTypeSource = TYPECODEPAGE;
                  giSourceCodePage = GetOEMCP();
                break;

                case CF_TEXT:
                  nBytesSource = lstrlenA (pData);
                  pSourceData= ManageMemory (MMALLOC, MMSOURCE, nBytesSource+1, pSourceData);
                  lstrcpyA (pSourceData, pData);
                  gTypeSource = TYPECODEPAGE;
                  giSourceCodePage = GetACP();
                break;

                default: break;  // shouldn't get here
              } /* end switch (iFormat) */

              SendMessage (hwnd, WMU_ADJUSTFORNEWSOURCE, 0,

(LPARAM)LoadResourceString(IDS_FROM_CLIPBOARD));

              GlobalUnlock(hMem);
            break;  /* break out of while loop. */
            } /* end if iFormat */

          CloseClipboard ();

        } break;

        /**********************************************************************\
        *  WM_COMMAND, MID_COPYDESTINATION
        *
        * Copy destination data to the clipboard.
        \**********************************************************************/
        case MID_COPYDESTINATION:
        {
          HGLOBAL hMem;
          if (pDestinationData == NULL) return FALSE;

          if (gTypeSource != TYPEUNICODE) {
              if (!(hMem = GlobalAlloc(
                      GMEM_MOVEABLE | GMEM_DDESHARE,
                      (lstrlenW((LPWSTR)pDestinationData)+1) * 2))) {
                  return FALSE;
              }
              lstrcpyW(GlobalLock(hMem), (LPWSTR)pDestinationData);
              GlobalUnlock(hMem);
          } else {
              if (!(hMem = GlobalAlloc(
                      GMEM_MOVEABLE | GMEM_DDESHARE,
                      lstrlenA(pDestinationData)+1))) {
                  return FALSE;
              }
              lstrcpyA(GlobalLock(hMem), pDestinationData);
              GlobalUnlock(hMem);
          }

          OpenClipboard (hwnd);
          EmptyClipboard();

          /* if source NOT unicode, then destination is, else look at dest CP */
          if (gTypeSource != TYPEUNICODE)
            SetClipboardData (CF_UNICODETEXT, hMem);
          else if (giDestinationCodePage == GetOEMCP())
            SetClipboardData (CF_OEMTEXT, hMem);
          else
            SetClipboardData (CF_TEXT, hMem);

          CloseClipboard ();

        break;
        }

        /******************************************************************\
        *  WM_COMMAND, MID_CONVERTNOW
        *
        * This is where the conversion actually takes place.
        *  In either case, make the call twice.  Once to determine how
        *  much memory is needed, allocate space, and then make the call again.
        *
        *  If conversion succeeds, it fills pDestinationData.
        \******************************************************************/
        case MID_CONVERTNOW: {
          int nBytesNeeded, nWCharNeeded, nWCharSource;

          if (nBytesSource == NODATA ) {
            MessageBox (hwnd, LoadResourceString(IDS_LOAD_SOURCE_FILE),
                    MBTitle, MBFlags);
            return 0;
          }

          /* Converting UNICODE -> giDestinationCodePage*/
          if (gTypeSource == TYPEUNICODE) {

            nWCharSource = nBytesSource/2;

            /* Query the number of bytes required to store the Dest string */
            nBytesNeeded = WideCharToMultiByte(giDestinationCodePage, gWCFlags,
                             (LPWSTR)pSourceData, nWCharSource,
                             NULL, 0,
                             glpDefaultChar, &gUsedDefaultChar);

            /* Allocate the required amount of space */

            if (nBytesNeeded == 0) {
                MessageBox (hwnd, LoadResourceString(IDS_FIRSTCALL_FAILED),
                        MBTitle, MBFlags);
                break;
            }

            /* We need more 1 byte for '\0' */
            pDestinationData= ManageMemory (MMALLOC, MMDESTINATION, nBytesNeeded + 2, pDestinationData);

            /* Do the conversion */
            nBytesDestination = WideCharToMultiByte(giDestinationCodePage, gWCFlags,
                             (LPWSTR)pSourceData, nWCharSource,
                             pDestinationData, nBytesNeeded, glpDefaultChar, &gUsedDefaultChar);
            if (nBytesNeeded == 0) {
                MessageBox (hwnd, LoadResourceString(IDS_FIRSTCALL_FAILED),
                        MBTitle, MBFlags);
                break;
            }
            *(LPSTR)((LPSTR)pDestinationData + nBytesNeeded) = '\0';
          }

          /* converting giSourceCodePage -> UNICODE */
          else if (gTypeSource == TYPECODEPAGE) {

            /* Query the number of WChar required to store the Dest string */
            nWCharNeeded = MultiByteToWideChar(giSourceCodePage, gMBFlags,
                             pSourceData, nBytesSource, NULL, 0 );

            /* Allocate the required amount of space */

            /* We need more 2 bytes for '\0' */
            pDestinationData= ManageMemory (MMALLOC, MMDESTINATION, (nWCharNeeded+1)*2, pDestinationData);

            /* Do the conversion */
            nWCharNeeded = MultiByteToWideChar(giSourceCodePage, gMBFlags,
                             pSourceData, nBytesSource,
                             (LPWSTR)pDestinationData, nWCharNeeded);

            *(LPWSTR)((LPWSTR)pDestinationData + nWCharNeeded) = L'\0';

            /* MultiByteToWideChar returns # WCHAR, so multiply by 2 */
            nBytesDestination = 2*nWCharNeeded ;
          } else {
            MessageBox (hwnd, LoadResourceString(IDS_SOURCE_TYPE_UNKNOWN),
                    MBTitle, MBFlags);
            return 0;
          }

          /* code common to all conversions... */
          SetWindowText (hwndName1, LoadResourceString(IDS_DATA_NOT_SAVED));
          wsprintf (buffer, LoadResourceString(IDS_BYTES), nBytesDestination);
          SetWindowText (hwndSize1, buffer);
          SetWindowText (hwndByteOrder1, szBlank);

          /* Throw up "Save as" dialog to help the user along.
           *  They can always <esc> if need be.
           */
          SendMessage (hwnd, WM_COMMAND, MID_SAVEAS, 0);

        } break; /* end  case BID_CONVERT */

        /******************************************************************\
        *  WM_COMMAND, BID_VIEWSOURCE
        *
        \******************************************************************/
        case BID_VIEWSOURCE:
          if (gTypeSource == TYPEUNICODE)
            DialogBoxW (hInst, L"ShowTextDlg", hwnd, (DLGPROC)ViewSourceProc);
          else
            DialogBoxA (hInst, "ShowTextDlg", hwnd, (DLGPROC)ViewSourceProc);
        break;

        /******************************************************************\
        *  WM_COMMAND, BID_VIEWDESTINATION
        *
        \******************************************************************/
        case BID_VIEWDESTINATION:
          if (gTypeSource == TYPEUNICODE)
            DialogBoxA (hInst, "ShowTextDlg", hwnd, (DLGPROC)ViewDestinationProc);
          else
            DialogBoxW (hInst, L"ShowTextDlg", hwnd, (DLGPROC)ViewDestinationProc);
        break;

        /******************************************************************\
        *  WM_COMMAND, MID_SOURCEOPT
        *
        * Allows user to change interpretation options for the source data.
        *
        *  Put up appropriate dialog box, and reset window text in response.
        \******************************************************************/
        case MID_SOURCEOPT:
          if (DialogBox (hInst, TEXT("DataOptionsDlg"), hwnd, (DLGPROC)SourceOptionsProc)) {
            SendMessage (hwnd, WMU_SETTYPESTRINGS, 0,0);
            SendMessage (hwnd, WM_COMMAND, MID_CLEARDESTINATION, 0);
          }
        break;

        /******************************************************************\
        *  WM_COMMAND, MID_DESTINATIONOPT
        *
        * Allows user to change options for destination data.
        *
        *  Put up appropriate dialog box, and reset window text in response.
        \******************************************************************/
        case MID_DESTINATIONOPT:
          if (DialogBox (hInst, TEXT("DataOptionsDlg"), hwnd, (DLGPROC)DestinationOptionsProc)) {
            SendMessage (hwnd, WMU_SETTYPESTRINGS, 0,0);
            SendMessage (hwnd, WM_COMMAND, MID_CLEARDESTINATION, 0);
          }
        break;

        /******************************************************************\
        *  WM_COMMAND, MID_CONVERSIONOPT
        *
        \******************************************************************/
        case MID_CONVERSIONOPT:
          if (DialogBox (hInst, TEXT("ConversionOptionsDlg"), hwnd, (DLGPROC)ConversionOptionsProc)) {
            SendMessage (hwnd, WM_COMMAND, MID_CLEARDESTINATION, 0);
          }
        break;

        /******************************************************************\
        *  WM_COMMAND, MID_SWAPSOURCE
        *
        * Allows user to reverse byte order of data.
        *
        \******************************************************************/
        case MID_SWAPSOURCE: {
          int i, end;
          BYTE temp;

          if (pSourceData == NULL) return FALSE;

          end =  nBytesSource - 2;
          for (i = 0; i<= end; i+=2) {
            temp             = pSourceData[i];
            pSourceData[i]   = pSourceData[i+1];
            pSourceData[i+1] = temp;
          }

          if (GetWindowTextLength (hwndByteOrder0) == 0)
            SetWindowText (hwndByteOrder0,
                    LoadResourceString(IDS_BYTE_ORDER_REVERSED));
          else
            SetWindowText (hwndByteOrder0, szBlank);

          /* Since source is different, invalidate Destination data. */
          SendMessage (hwnd, WM_COMMAND, MID_CLEARDESTINATION, 0);

        } break;

        /******************************************************************\
        *  WM_COMMAND, MID_SWAPDESTINATION
        *
        * Allows user to reverse byte order of data.
        *
        \******************************************************************/
        case MID_SWAPDESTINATION: {
          int i, end;
          BYTE temp;

          if (pDestinationData == NULL) return FALSE;

          end =  nBytesDestination - 2;
          for (i = 0; i<= end; i+=2) {
            temp             = pDestinationData[i];
            pDestinationData[i]   = pDestinationData[i+1];
            pDestinationData[i+1] = temp;
          }

          if (GetWindowTextLength (hwndByteOrder1) == 0)
            SetWindowText (hwndByteOrder1,
                    LoadResourceString(IDS_BYTE_ORDER_REVERSED));
          else
            SetWindowText (hwndByteOrder1, szBlank);

        } break;

        /**********************************************************************\
        *  WM_COMMAND, MID_CLEARDESTINATION
        *
        * Clear the destination information.  May cause data to be lost.
        \**********************************************************************/
        case MID_CLEARDESTINATION:
          SetWindowText (hwndSize1, szBlank);
          SetWindowText (hwndName1, szBlank);
          SetWindowText (hwndByteOrder1, szBlank);
          pDestinationData= ManageMemory (MMFREE, MMDESTINATION, 0, pDestinationData);
        break;

        /**********************************************************************\
        *  WM_COMMAND, MID_CLEARSOURCE
        *
        * Clear the SOURCE information.  May cause data to be lost.
        \**********************************************************************/
        case MID_CLEARSOURCE:
          SetWindowText (hwndSize0, szBlank);
          SetWindowText (hwndName0, szBlank);
          SetWindowText (hwndByteOrder0, szBlank);
          pSourceData= ManageMemory (MMFREE, MMSOURCE, 0, pSourceData);
        break;

        /******************************************************************\
        *  WM_COMMAND, MID_INSTALLTABLES
        *
        \******************************************************************/
        case MID_INSTALLTABLES:
          DialogBox (hInst, TEXT("InstallTableDlg"), hwnd, (DLGPROC)InstallTableProc);
        break;

        /* Simply call WinHelp to display the OnLine help file. */
        case MID_HELP:
          WinHelp( hwnd, szHelpPathName, HELP_INDEX, (DWORD) NULL );
        break;

        /* No-op Window procedure to simply display the dialog box. */
        case MID_ABOUT:
          ShellAbout (hwnd, TEXT("UConvert"), NULL, LoadIcon (hInst, TEXT("uconvertIcon")));
        break;

        /* Just close the window. */
        case MID_EXIT:
          PostMessage (hwnd, WM_CLOSE, 0,0);
        break;

      } /* end switch (LOWORD(wParam)) */
    break;  /* end WM_COMMAND */

    default: break;
  } /* end switch */

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

/**************************************************************************\
*
*  function:  IsUnicode()
*
* HACK... eventually use a proper function for IsUnicode
*  Use function from unipad?
*
\**************************************************************************/
BOOL IsUnicode (PBYTE pb)
{
  return (IsBOM (pb));
}

/**************************************************************************\
*
*  function:  IsBOM()
*
* true iff pb points to a Byte Order Mark.
*
\**************************************************************************/
BOOL IsBOM (PBYTE pb)
{
  if ((*pb == 0xFF) & (*(pb+1) == 0xFE))  // BOM
    return TRUE;
  else
    return FALSE;
}

/**************************************************************************\
*
*  function:  IsRBOM()
*
* true iff pb points to a reversed Byte Order Mark.
*
\**************************************************************************/
BOOL IsRBOM (PBYTE pb)
{
  if ((*pb == 0xFE) & (*(pb+1) == 0xFF))  // RBOM
    return TRUE;
  else
    return FALSE;
}

/**************************************************************************\
*
*  function:  framechildwindow()
*
* Simply draw a 3D frame around child window.
*
\**************************************************************************/
VOID framechildwindow (HDC hdc, HWND hwndParent, HWND hwndChild)
{
RECT rect;

      GetWindowRect (hwndChild, &rect);

      /* minor hack... assumes RECT is two points, right field starting first */
      ScreenToClient (hwndParent, (LPPOINT)&rect);
      ScreenToClient (hwndParent, (LPPOINT)&(rect.right));

      InflateRect (&rect, 1, 1);
      FrameRect (hdc, &rect, GetStockObject (GRAY_BRUSH));
      InflateRect (&rect, -1, -1);
      SelectObject (hdc, GetStockObject (WHITE_PEN));
      MoveToEx (hdc, rect.right, rect.top, NULL);
      LineTo (hdc,rect.right, rect.bottom);
      LineTo (hdc,rect.left, rect.bottom);

    return;
}

/**************************************************************************\
*
*  function:  underlinechildwindow()
*
* Underline child window.
*
\**************************************************************************/
VOID underlinechildwindow (HDC hdc, HWND hwndParent, HWND hwndChild)
{
RECT rect;

      GetWindowRect (hwndChild, &rect);

      /* minor hack... assumes RECT is two points, right field starting first */
      ScreenToClient (hwndParent, (LPPOINT)&rect);
      ScreenToClient (hwndParent, (LPPOINT)&(rect.right));

      InflateRect (&rect, 1, 1);
      rect.top = rect.bottom-1;
      FrameRect (hdc, &rect, GetStockObject (GRAY_BRUSH));
      SelectObject (hdc, GetStockObject (WHITE_PEN));
      MoveToEx (hdc, rect.right, rect.bottom, NULL);
      LineTo (hdc,rect.left, rect.bottom);

    return;
}

/**************************************************************************\
*
*  function:  createwindows()
*
* Create the child windows and pass the handles back in parameters.
*  Each Window is created relative to (inside of) prect.
*  top is a spacial pointer to the Y coordinate of the next window.
*
\**************************************************************************/
VOID createwindows(PRECT prect,
                   HWND  hwndParent,
                   HWND* hwndLabel,
                   HWND* hwndName,
                   HWND* hwndSize,
                   HWND* hwndCodePage,
                   HWND* hwndByteOrder,
                   HWND* hwndButton)
{
int top;

  top = prect->top;
  *hwndLabel = CreateWindow(
          TEXT("STATIC"),
          szBlank,
          WS_CHILD | WS_VISIBLE | SS_CENTER,
          prect->left,
          top,
          prect->right - prect->left,
          WHEIGHT,
          hwndParent, NULL, hInst, 0);

  top += WHEIGHT*5/2;
  *hwndName = CreateWindow(
          TEXT("STATIC"),
          szBlank,
          WS_CHILD | WS_VISIBLE | SS_RIGHT,
          prect->left,
          top,
          prect->right - prect->left,
          WHEIGHT,
          hwndParent, NULL, hInst, 0);

  top += WHEIGHT*2;
  *hwndSize = CreateWindow(
          TEXT("STATIC"),
          szBlank,
          WS_CHILD | WS_VISIBLE | SS_LEFT,
          prect->left,
          top,
          (prect->right - prect->left) *3/4,
          WHEIGHT,
          hwndParent, NULL, hInst, 0);

  top += WHEIGHT*2;
  *hwndCodePage = CreateWindow(
          TEXT("STATIC"),
          szBlank,
          WS_CHILD | WS_VISIBLE | SS_LEFT,
          prect->left,
          top,
          (prect->right - prect->left) *3/4,
          WHEIGHT,
          hwndParent, NULL, hInst, 0);

  top += WHEIGHT*2;
  *hwndByteOrder = CreateWindow(
          TEXT("STATIC"),
          szBlank,
          WS_CHILD | WS_VISIBLE | SS_LEFT,
          prect->left,
          top,
          (prect->right - prect->left) *3/4,
          WHEIGHT,
          hwndParent, NULL, hInst, 0);

  top += WHEIGHT*2;
  *hwndButton = CreateWindow(
          TEXT("BUTTON"),
          TEXT("ViewText"),
          WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
          prect->left,
          top,
          prect->right - prect->left,
          WHEIGHT*9/7,
          hwndParent, NULL, hInst, 0);

  return;
}

/**************************************************************************\
*
*  function:  ManageMemory()
*
* Do all memory management here for the source and destination pointers.
*  We also enable/disable the "View..." buttons to reflect existence of data.
*
*
* PARAMETERS
*
*  message : {MMALLOC, MMFREE}
*    Alloc when requested by MMALLOC, and free the existing, passed in, pointer.
*    Free when requested by MMFREE.
*  sourcedestination : {MMSOURCE, MMDESTINATION}
*  nBytes - number to alloc on MMALLOC messages.
*  p - old pointer to be freed.
*
*
* GLOBALS
*
*  hwndButton0, hwndButton1
*
\**************************************************************************/
LPVOID ManageMemory (UINT message, UINT sourcedestination, DWORD nBytes, LPVOID p)
{
  switch (message) {
    case MMFREE :
      if (sourcedestination == MMSOURCE)
        EnableWindow (hwndButton0, FALSE);
      else if (sourcedestination == MMDESTINATION)
        EnableWindow (hwndButton1, FALSE);

      if (p != NULL) GlobalFree (GlobalHandle (p));
      return NULL;
    break;

    case MMALLOC :
      if (sourcedestination == MMSOURCE)
        EnableWindow (hwndButton0, TRUE);
      else if (sourcedestination == MMDESTINATION)
        EnableWindow (hwndButton1, TRUE);

      if (p != NULL) GlobalFree (GlobalHandle (p));
      p = (LPVOID) GlobalAlloc (GPTR, nBytes);
      return p;
    break;

  } /* end switch (message) */
  return NULL;
}

/**************************************************************************\
*
*  function:  LoadResourceString()
*
*  Loads a resource string from string table and returns a pointer
*  to the string.
*
*  PARAMETERS: wID - resource string id
*
\**************************************************************************/
LPTSTR LoadResourceString(UINT wID)
{
    static TCHAR szBuf[512];

    LoadString((HANDLE)GetModuleHandle(NULL),wID,szBuf,sizeof(szBuf));
    return szBuf;
}


UCONVERT.H

/**************************************************************************\
* uconvert.h -- header file for uconvert program.
*
* Function prototypes, global variables, & preprocessor defines.
\**************************************************************************/

/**************************************************************************\
*  Function prototypes, window procedures first.
\**************************************************************************/

LRESULT CALLBACK MainWndProc            (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK AboutProc              (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK SourceOptionsProc      (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK DestinationOptionsProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ConversionOptionsProc  (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ViewSourceProc         (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ViewDestinationProc    (HWND, UINT, WPARAM, LPARAM);

BOOL IsUnicode (PBYTE );
BOOL IsBOM (PBYTE );
BOOL IsRBOM (PBYTE );

VOID framechildwindow     (HDC, HWND, HWND);
VOID underlinechildwindow (HDC, HWND, HWND);

VOID createwindows(PRECT, HWND, HWND*, HWND*, HWND*, HWND*, HWND*, HWND*);

LPVOID ManageMemory (UINT, UINT, DWORD, LPVOID);

LPTSTR LoadResourceString(UINT);

/**************************************************************************\
*  Global variables (declared in uconvert.c).
\**************************************************************************/

/* Child windows appearing on the main frame window. */
extern HWND hwndLabel0,     hwndLabel1;
extern HWND hwndName0,      hwndName1;
extern HWND hwndSize0,      hwndSize1;
extern HWND hwndCodePage0,  hwndCodePage1;
extern HWND hwndByteOrder0, hwndByteOrder1;
extern HWND hwndButton0,    hwndButton1;

/* Information specifying which is unicode and what the other code page is. */
extern int  gTypeSource;
extern UINT giSourceCodePage;
extern UINT giDestinationCodePage;

/* pointers to global source & destination data, and byte count. */
extern PBYTE pSourceData;
extern PBYTE pDestinationData;
extern int   nBytesSource;
extern int   nBytesDestination;

/* Conversion Options variables. */
extern DWORD gMBFlags;
extern DWORD gWCFlags;
extern char  glpDefaultChar[];
extern BOOL  gUsedDefaultChar;

/**************************************************************************\
*  Defined constants.
\**************************************************************************/

/* Messages that can be send to ManageMemory() as first param */
#define MMALLOC 1
#define MMFREE  2
/* Messages that can be send to ManageMemory() as second param */
#define MMSOURCE       3
#define MMDESTINATION  4

/* Allowed values for the global variable gTypeSource */
#define TYPEUNKNOWN     0
#define TYPEUNICODE     1
#define TYPECODEPAGE    2

/* "user message."  Used by main window.  */
#define  WMU_SETTYPESTRINGS     WM_USER +1
#define  WMU_ADJUSTFORNEWSOURCE WM_USER +2

/* menu IDs */
#define  MID_OPEN                    1001
#define  MID_SAVEAS                  1002
#define  MID_EXIT                    1010

#define  MID_COPYDESTINATION         2001
#define  MID_PASTESOURCE             2002
#define  MID_CLEARSOURCE             2003
#define  MID_CLEARDESTINATION        2004

#define  MID_CONVERTNOW              3001
#define  MID_SOURCEOPT               3002
#define  MID_DESTINATIONOPT          3003
#define  MID_CONVERSIONOPT           3004
#define  MID_SWAPSOURCE              3005
#define  MID_SWAPDESTINATION         3006
#define  MID_INSTALLTABLES           3007

#define  MID_HELP                    4001
#define  MID_ABOUT                   4002

/* button IDs (should be disjoint from menu IDs) */
#define  BID_VIEWSOURCE                59
#define  BID_VIEWDESTINATION           60

/* dialog IDs */
#define  DID_NAME                     100
#define  DID_TYPE                     101
#define  DID_FONT                     102
#define  DID_TEXT                     103

#define  DID_CODEPAGELIST             300
#define  DID_RBUNICODE                301
#define  DID_RBANSICP                 302
#define  DID_RBOEMCP                  303
#define  DID_RBOTHERCP                304
#define  DID_CBBYTEORDER              305

#define  DID_PRECOMPOSED              401
#define  DID_COMPOSITE                402
#define  DID_USEGLYPHCHARS            403

#define  DID_COMPOSITECHECK           451
#define  DID_DISCARDNS                452
#define  DID_SEPCHARS                 453
#define  DID_DEFAULTCHAR              454
#define  DID_EFDEFAULTCHAR            455
#define  DID_USEDDEFAULTCHAR          456

/* Define a value for the LOGFONT.lfCharSet
 *  This should be included in wingdi.h, but it
 *  was removed because the font mapper is not
 *  using it anyway in version 1.0.  Currently
 *  scheduled to be included in NT ver 1.1.
 */
#define UNICODE_CHARSET  1

/* string id */
#define  IDS_SOURCE                   501
#define  IDS_DESTINATION              502
#define  IDS_VIEW_SOURCE_BTN          503
#define  IDS_VIEW_DESTINATION_BTN     504
#define  IDS_FILE_FILTER_SPEC1        505
#define  IDS_FILE_FILTER_SPEC2        506
#define  IDS_FILE_FILTER_SPEC3        507
#define  IDS_CODE_PAGE                508
#define  IDS_OPEN_FILE_TITLE          509
#define  IDS_OPEN_FILE_FAILED         510
#define  IDS_FILE_TOO_BIG             511
#define  IDS_READFILE_FAILED          512
#define  IDS_SWAPPING_BYTE_ORDER      513
#define  IDS_NOTEXT_TO_SAVE           514
#define  IDS_SAVE_AS_TITLE            515
#define  IDS_CREATEFILE_FAILED        516
#define  IDS_WRITEFILE_FAILED         517
#define  IDS_FROM_CLIPBOARD           518
#define  IDS_LOAD_SOURCE_FILE         519
#define  IDS_SOURCE_TYPE_UNKNOWN      520
#define  IDS_DATA_NOT_SAVED           521
#define  IDS_BYTES                    522
#define  IDS_BYTE_ORDER_REVERSED      533
#define  IDS_APP_WARNING              534
#define  IDS_INTERPRET_SOURCE_AS      535
#define  IDS_CONVERT_DEST_TO          536
#define  IDS_VIEW_SOURCE_TITLE        537
#define  IDS_VIEW_DEST_TITLE          538
#define  IDS_FILE_FILTER_SPEC4        539
#define  IDS_TABLE_FILE_TITLE         540
#define  IDS_INCORRECT_FILE_TYPE      541
#define  IDS_FILENAME_OVERFLOW        542
#define  IDS_LOGON_AS_ADMIN           543
#define  IDS_ACCESS_DENIED            544
#define  IDS_REGOPENKEYEX_FAILED      545
#define  IDS_FILE_ALREADY_EXISTS      546
#define  IDS_FILE_CP_FAILED_AGAIN     547
#define  IDS_FILE_CP_FAILED           548
#define  IDS_FILE_PARSE_FAILED        549
#define  IDS_FIRSTCALL_FAILED         550


README.TXT

WALKER

The WALKER tool allows you to view information about the virtual memory of a
process.


PDEBUG.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 "pwalk.h"
#include <memory.h>

/* module globals */
LPVOID  PMAAddress;
LPVOID  PMABuffer;
DWORD  PMASize;
extern  char  szCurPath[MAX_PATH];

/* local debug functions */
BOOL   WINAPI DebugEventThread (DBGPROCESS *);
BOOL   WINAPI CreateDebugEvents (LPHANDLE);
void   WINAPI AddThreadNode (DBGPROCESS *, DWORD, HANDLE, int, LPTHREAD_START_ROUTINE, HANDLE);
int    WINAPI HandleBreakPoint(DBGPROCESS *, DWORD);
void   WINAPI RemoveThreadNode (DBGPROCESS *, DWORD);
void   WINAPI AddDllNode (DBGPROCESS *, LOAD_DLL_DEBUG_INFO *);
void   WINAPI RemoveDllNode (DBGPROCESS *, LOAD_DLL_DEBUG_INFO *);
void   WINAPI SuspendDebuggeeProcess (DBGPROCESS *);
void   WINAPI ResumeDebuggeeProcess (DBGPROCESS *);
void   WINAPI NameObjects (HANDLE, LPVOID, LPVMOBJECT, int, char *, char *);
BOOL   WINAPI VMCompare (LPVMOBJECT, LPVMOBJECT);
BOOL   WINAPI InterruptThread_HookProcess (DBGPROCESS *, LPPROCESS_STATE);
void   WINAPI ResetInterruptedThread (DBGPROCESS *, LPPROCESS_STATE);
void   WINAPI RecordException (DBGPROCESS *, DEBUG_EVENT *);
HANDLE WINAPI FindThreadHandle (DBGPROCESS *, DWORD);

/* start debug thread, and return event active handle */
DBGPROCESS* WINAPI StartChildProcess (
    HWNDhWnd,
    char*lpszModule,
    LPHANDLElpDbgEvents)
{
    DWORD  TID;
    HANDLE  hDebugHeap;
    DBGPROCESS  *lpDbgProcess;
    int   i;

    /* create unique debug events using debuggee process ID */
    if (!CreateDebugEvents (lpDbgEvents))
return NULL;

    /* create serialized heap of dynamic size */
    if (!(hDebugHeap = HeapCreate (0, sizeof (DBGPROCESS) + sizeof (DBGTHREAD), 0)))
{
/* close all event handles */
for (i=0; i<nDEBUGEVENTS; i++)
    CloseHandle (lpDbgEvents[i]);

return NULL;
}

    /* allocate and initialize debug heap structure */
    lpDbgProcess = (DBGPROCESS *)HeapAlloc (hDebugHeap, 0, sizeof (DBGPROCESS));
    lpDbgProcess->hDbgHeap = hDebugHeap;
    strcpy (lpDbgProcess->szModule, lpszModule);
    lpDbgProcess->hWnd = hWnd;
    lpDbgProcess->lpThreads = NULL;
    lpDbgProcess->lpSection = NULL;
    lpDbgProcess->lpERs = NULL;

    /* create debug thread */
    if (!(CreateThread ((LPSECURITY_ATTRIBUTES)NULL,
4096,
(LPTHREAD_START_ROUTINE)DebugEventThread,
(LPVOID)lpDbgProcess,
0,
&TID)))
return NULL;

    /* wait 15 seconds for debugger to complete initialization, else error */
    if (WAIT_TIMEOUT == WaitForSingleObject (lpDbgEvents[ACKNOWLEDGE], 15000))
{
HeapDestroy (lpDbgProcess->hDbgHeap);

/* close all event handles */
for (i=0; i<nDEBUGEVENTS; i++)
    CloseHandle (lpDbgEvents[i]);

return NULL;
}

    /* reset acknowledge event */
    ResetEvent (lpDbgEvents[ACKNOWLEDGE]);

    /* successfull thread and event start */
    return lpDbgProcess;
}

/* function notifies debug thread to terminate, frees handles, and destroys heap */
void   WINAPI CloseChildProcess (
    DBGPROCESS *lpDbgProcess,
    LPHANDLE   lpDbgEvents)
{
    int  i;
    DBGTHREAD *pNode = lpDbgProcess->lpThreads;

    /* set close event for debug thread and wait for acknowledge */
    SetEvent (lpDbgEvents[CLOSEDEBUGGER]);
    WaitForSingleObject (lpDbgEvents[ACKNOWLEDGE], INFINITE);

    /* close all event handles */
    for (i=0; i<nDEBUGEVENTS; i++)
CloseHandle (lpDbgEvents[i]);

    /* close all thread handles in the list */
    while (pNode != NULL)
{
RemoveThreadNode (lpDbgProcess, pNode->dwThreadID);
pNode = lpDbgProcess->lpThreads;
}

    /* destroy the debug heap */
    HeapDestroy (lpDbgProcess->hDbgHeap);
}

/* local function creates debug event objects for thread synchronization */
BOOL WINAPI CreateDebugEvents (
    LPHANDLElpDbgEvents)
{
    char    szEvent[MAX_PATH];

    LoadString (GetModuleHandle (NULL), IDS_DBGEVNTACTIVE, szEvent, sizeof (szEvent));
    if (!(lpDbgEvents[DEBUGACTIVE] = CreateEvent ((LPSECURITY_ATTRIBUTES)NULL,
  TRUE,
  TRUE,
  szEvent)))
return FALSE;

    LoadString (GetModuleHandle (NULL), IDS_DBGEVNTCLOSE, szEvent, sizeof (szEvent));
    if (!(lpDbgEvents[CLOSEDEBUGGER] = CreateEvent ((LPSECURITY_ATTRIBUTES)NULL,
   TRUE,
   FALSE,
   szEvent)))
{
CloseHandle (lpDbgEvents[DEBUGACTIVE]);
return FALSE;
}

    LoadString (GetModuleHandle (NULL), IDS_DBGEVNTSTOP, szEvent, sizeof (szEvent));
    if (!(lpDbgEvents[SUSPENDDEBUGGER] = CreateEvent ((LPSECURITY_ATTRIBUTES)NULL,
      TRUE,
      FALSE,
      szEvent)))
{
CloseHandle (lpDbgEvents[DEBUGACTIVE]);
CloseHandle (lpDbgEvents[CLOSEDEBUGGER]);
return FALSE;
}

    LoadString (GetModuleHandle (NULL), IDS_DBGEVNTSTART, szEvent, sizeof (szEvent));
    if (!(lpDbgEvents[RESUMEDEBUGGER] = CreateEvent ((LPSECURITY_ATTRIBUTES)NULL,
     TRUE,
     FALSE,
     szEvent)))
{
CloseHandle (lpDbgEvents[DEBUGACTIVE]);
CloseHandle (lpDbgEvents[CLOSEDEBUGGER]);
CloseHandle (lpDbgEvents[SUSPENDDEBUGGER]);
return FALSE;
}

    LoadString (GetModuleHandle (NULL), IDS_DBGEVNTREAD, szEvent, sizeof (szEvent));
    if (!(lpDbgEvents[READMEMORY] = CreateEvent ((LPSECURITY_ATTRIBUTES)NULL,
 TRUE,
 FALSE,
 szEvent)))
{
CloseHandle (lpDbgEvents[DEBUGACTIVE]);
CloseHandle (lpDbgEvents[CLOSEDEBUGGER]);
CloseHandle (lpDbgEvents[SUSPENDDEBUGGER]);
CloseHandle (lpDbgEvents[RESUMEDEBUGGER]);
return FALSE;
}

    LoadString (GetModuleHandle (NULL), IDS_DBGEVNTWRITE, szEvent, sizeof (szEvent));
    if (!(lpDbgEvents[WRITEMEMORY] = CreateEvent ((LPSECURITY_ATTRIBUTES)NULL,
  TRUE,
  FALSE,
  szEvent)))
{
CloseHandle (lpDbgEvents[DEBUGACTIVE]);
CloseHandle (lpDbgEvents[CLOSEDEBUGGER]);
CloseHandle (lpDbgEvents[SUSPENDDEBUGGER]);
CloseHandle (lpDbgEvents[RESUMEDEBUGGER]);
CloseHandle (lpDbgEvents[READMEMORY]);
return FALSE;
}

    LoadString (GetModuleHandle (NULL), IDS_DBGEVNTACK, szEvent, sizeof (szEvent));
    if (!(lpDbgEvents[ACKNOWLEDGE] = CreateEvent ((LPSECURITY_ATTRIBUTES)NULL,
  TRUE,
  FALSE,
  szEvent)))
{
CloseHandle (lpDbgEvents[DEBUGACTIVE]);
CloseHandle (lpDbgEvents[CLOSEDEBUGGER]);
CloseHandle (lpDbgEvents[SUSPENDDEBUGGER]);
CloseHandle (lpDbgEvents[RESUMEDEBUGGER]);
CloseHandle (lpDbgEvents[READMEMORY]);
CloseHandle (lpDbgEvents[WRITEMEMORY]);
return FALSE;
}

    /* success */
    return TRUE;
}

/* main daddyo thread that is the debugger residing over a debuggee */
BOOL WINAPI DebugEventThread (
    DBGPROCESS  *lpDbgProcess)
{
    DEBUG_EVENT    de;
    HANDLE   hDbgEvent[nDEBUGEVENTS];
    STARTUPINFO    si;
    PROCESS_INFORMATION    pi;
    HANDLE   hChildProcess;
    BOOL   bHooked, bUnHooked;
//    PROCESS_STATE   ProcessState;

    bHooked = FALSE;
    bUnHooked = FALSE;

    /* initialize process startup information */
    si.cb       = sizeof (si);
    si.lpReserved      = NULL;
    si.lpDesktop       = NULL;
    si.lpTitle       = NULL;
    si.dwX       = 0;
    si.dwY       = 0;
    si.dwXSize       = 0;
    si.dwYSize       = 0;
    si.dwXCountChars   = 0;
    si.dwYCountChars   = 0;
    si.dwFillAttribute = 0;
    si.dwFlags       = STARTF_FORCEONFEEDBACK | STARTF_USESHOWWINDOW;
    si.wShowWindow     = SW_SHOWNORMAL;
    si.cbReserved2     = 0;
    si.lpReserved2     = NULL;

    /* create debug process on module name */
    if (!CreateProcess (lpDbgProcess->szModule,
NULL,
(LPSECURITY_ATTRIBUTES)NULL,
(LPSECURITY_ATTRIBUTES)NULL,
FALSE,
DEBUG_PROCESS,
NULL,
NULL,
(LPSTARTUPINFO)&si,
(LPPROCESS_INFORMATION)&pi))
{
ReportError (IDS_ERRCREATEPROCESS);
return FALSE;
}
    /* open process for all access */
    if ((hChildProcess = OpenProcess (PROCESS_ALL_ACCESS,
      FALSE,
      pi.dwProcessId)) == NULL)
{
ReportError (IDS_ERROPENPROCESS);
TerminateProcess (pi.hProcess, 0);
return FALSE;
}

    /* store process info */
    lpDbgProcess->hProcess = hChildProcess;
    lpDbgProcess->dwPriority = GetPriorityClass (pi.hProcess);
    lpDbgProcess->dwProcessID = pi.dwProcessId;
    lpDbgProcess->bActive = TRUE;

    /* close process and thread handles in pi structure */
    CloseHandle (pi.hThread);
    CloseHandle (pi.hProcess);

    /* open debug events */
    CreateDebugEvents (hDbgEvent);

    /* signale completion of task */
    SetEvent (hDbgEvent[ACKNOWLEDGE]);

    /* start debug event loop */
    while (TRUE)
{
int    nIndex;

/* wait for debugger active */
switch (nIndex = WaitForMultipleObjects (nDEBUGEVENTS, hDbgEvent, FALSE, INFINITE))
    {
    case CLOSEDEBUGGER:
{
int    i;

/* terminate debuggee process */
TerminateProcess (lpDbgProcess->hProcess, 0);
CloseHandle (lpDbgProcess->hProcess);

/* signal completion of task */
SetEvent (hDbgEvent[ACKNOWLEDGE]);

/* close all debug events */
for (i=0; i<nDEBUGEVENTS; i++)
    CloseHandle (hDbgEvent[i]);

/* exit debugger now */
return TRUE;
}
break;

    case SUSPENDDEBUGGER:
SuspendDebuggeeProcess (lpDbgProcess);
ResetEvent (hDbgEvent[DEBUGACTIVE]);
ResetEvent (hDbgEvent[SUSPENDDEBUGGER]);
break;

    case RESUMEDEBUGGER:
ResumeDebuggeeProcess (lpDbgProcess);
SetEvent (hDbgEvent[DEBUGACTIVE]);
ResetEvent (hDbgEvent[RESUMEDEBUGGER]);
break;

    case READMEMORY:
{
MEMORY_BASIC_INFORMATION    mbi;
DWORD    Protect = 0;

/* reset event so we don't do repeat */
ResetEvent (hDbgEvent [READMEMORY]);

/* if not committed memory abort */
if (!VirtualQueryEx (lpDbgProcess->hProcess,
     PMAAddress,
     &mbi,
     sizeof (MEMORY_BASIC_INFORMATION)) ||
    mbi.State != MEM_COMMIT)
    {
    PMASize = 0;
    SetEvent (hDbgEvent [ACKNOWLEDGE]);
    break;
    }

/* if guarded memory, change protection temporarily */
if (!(mbi.Protect & PAGE_READONLY) &&
    !(mbi.Protect & PAGE_READWRITE))
    VirtualProtectEx (lpDbgProcess->hProcess,
      PMAAddress,
      PMASize,
      PAGE_READONLY,
      &Protect);

if (!ReadProcessMemory (lpDbgProcess->hProcess,
PMAAddress,
PMABuffer,
PMASize,
NULL))
    {
    if (mbi.AllocationProtect != PAGE_READONLY &&
mbi.AllocationProtect != PAGE_READWRITE)
NotifyUser (NULL, IDS_ERROR, 0, "BaseProtect NOACCESS", 0);
    else
{
ReportError (IDS_ERRREADPROCESSMEMORY);
PMASize = 0;
}
    }

/* reset protection if changed */
if (Protect)
    VirtualProtectEx (lpDbgProcess->hProcess,
      PMAAddress,
      PMASize,
      Protect,
      &Protect);

/* acknowledge success */
SetEvent (hDbgEvent [ACKNOWLEDGE]);
}
break;

    case WRITEMEMORY:
if (!WriteProcessMemory (lpDbgProcess->hProcess,
 PMAAddress,
 PMABuffer,
 PMASize,
 NULL))
    {
    ReportError (IDS_ERRWRITEPROCESSMEMORY);
    PMASize = 0;
    }

ResetEvent (hDbgEvent [WRITEMEMORY]);
SetEvent (hDbgEvent [ACKNOWLEDGE]);
break;

    case DEBUGACTIVE:
/* if debug active */
if ((WaitForDebugEvent (&de, (DWORD)100)))
    {
    if (de.dwProcessId == lpDbgProcess->dwProcessID)
{
switch (de.dwDebugEventCode)
    {
    case EXIT_PROCESS_DEBUG_EVENT:
SetStatusText (lpDbgProcess->hWnd, IDS_EXITPROCESS, 0);

/* uninitialize probe dll */
ResetProbe ();

/* process is going away so notify main window */
SendNotifyMessage (lpDbgProcess->hWnd,
   WM_COMMAND,
   IDM_PROCESSUNLOAD,
   0);
break;

    case LOAD_DLL_DEBUG_EVENT:
SetStatusText (lpDbgProcess->hWnd, IDS_LOADDLL, 0);
AddDllNode (lpDbgProcess,
    (LOAD_DLL_DEBUG_INFO *)&(de.u.LoadDll));
break;

    case UNLOAD_DLL_DEBUG_EVENT:
SetStatusText (lpDbgProcess->hWnd, IDS_UNLOADDLL, 0);
RemoveDllNode (lpDbgProcess,
       (LOAD_DLL_DEBUG_INFO *)&(de.u.LoadDll));
break;

    case CREATE_PROCESS_DEBUG_EVENT:
SetStatusText (lpDbgProcess->hWnd, IDS_CREATEPROCESS, 0);
/* add first thread to linked list of dbg structures */
AddThreadNode (lpDbgProcess,
de.dwThreadId,
de.u.CreateProcessInfo.hThread,
GetThreadPriority (de.u.CreateProcessInfo.hThread),
de.u.CreateProcessInfo.lpStartAddress,
de.u.CreateProcessInfo.hFile);
lpDbgProcess->hFile = de.u.CreateProcessInfo.hFile;
lpDbgProcess->lpImage = de.u.CreateProcessInfo.lpBaseOfImage;
lpDbgProcess->dwDbgInfoOffset = de.u.CreateProcessInfo.dwDebugInfoFileOffset;
lpDbgProcess->nDbgInfoSize = de.u.CreateProcessInfo.nDebugInfoSize;
break;

    case CREATE_THREAD_DEBUG_EVENT:
SetStatusText (lpDbgProcess->hWnd, IDS_CREATETHREAD, 0);
/* add thread to linked list of dbg structures */
AddThreadNode (lpDbgProcess,
 de.dwThreadId,
 de.u.CreateThread.hThread,
 GetThreadPriority (de.u.CreateThread.hThread),
 de.u.CreateThread.lpStartAddress,
 NULL);
break;

    case EXIT_THREAD_DEBUG_EVENT:
SetStatusText (lpDbgProcess->hWnd, IDS_EXITTHREAD, 0);
/* remove thread record */
RemoveThreadNode (lpDbgProcess,
    de.dwThreadId);
break;

    case EXCEPTION_DEBUG_EVENT:
switch (de.u.Exception.ExceptionRecord.ExceptionCode)
    {
    case EXCEPTION_BREAKPOINT:

HandleBreakPoint(lpDbgProcess, de.dwThreadId);
SetStatusText (lpDbgProcess->hWnd,
       IDS_BREAKPOINTEXCEPTION,
       RGB (0, 0xff, 0));

/* post message to get ball rolling in main thread */
PostMessage (lpDbgProcess->hWnd,
 WM_COMMAND,
 IDM_PROCESSREWALK,
 0);
break;

    case EXCEPTION_ACCESS_VIOLATION:
/* record exception information */
SetStatusText (lpDbgProcess->hWnd,
       IDS_ACCESSVIOLATIONEXCEPTION,
       RGB (0xff, 0, 0));
RecordException (lpDbgProcess, &de);
break;

    default:
SetStatusText (lpDbgProcess->hWnd,
       IDS_UNHANDLEDEXCEPTION,
       RGB (0xff, 0, 0));
RecordException (lpDbgProcess, &de);
break;
    }
break;

    case RIP_EVENT:
SetStatusText (lpDbgProcess->hWnd,
       IDS_RIPEVENT,
       RGB (0, 0xff, 0));
break;

    case OUTPUT_DEBUG_STRING_EVENT:
SetStatusText (lpDbgProcess->hWnd,
       IDS_OUTPUTDEBUGSTRING,
       RGB (0, 0xff, 0));
break;

    default:
break;
    }
}

            if(de.u.Exception.ExceptionRecord.ExceptionCode != EXCEPTION_BREAKPOINT)
        ContinueDebugEvent (de.dwProcessId, de.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
            else
        ContinueDebugEvent (de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
    }
break;
    }

}

    return TRUE;
}

void WINAPI SuspendDebuggeeProcess (
    DBGPROCESS  *lppr)
{
    DBGTHREAD *lpth = (DBGTHREAD *)lppr->lpThreads;

    while (lpth)
{
SuspendThread (lpth->hThread);
lpth = (DBGTHREAD *)lpth->Next;
}

    /* inform user via status bar */
    SetStatusText (lppr->hWnd, IDS_PROCESSSUSPENDED, RGB (0xff, 0, 0));
    lppr->bActive = FALSE;
}

void WINAPI ResumeDebuggeeProcess (
    DBGPROCESS  *lppr)
{
    DBGTHREAD *lpth = (DBGTHREAD *)lppr->lpThreads;

    while (lpth)
{
ResumeThread (lpth->hThread);
lpth = (DBGTHREAD *)lpth->Next;
}

    /* inform user via status bar */
    SetStatusText (lppr->hWnd, IDS_PROCESSRESUMED, RGB (0, 0xff, 0));
    lppr->bActive = TRUE;
}

void WINAPI AddThreadNode (
    DBGPROCESS      *lppr,
    DWORD      dwThreadID,
    HANDLE      hThread,
    int       nPriority,
    LPTHREAD_START_ROUTINE    lpStart,
    HANDLE      hFile)
{
    DBGTHREAD *lpth;
    DBGTHREAD *pNode = lppr->lpThreads;

    /* allocate thread node off heap */
    lpth = (DBGTHREAD *)HeapAlloc (lppr->hDbgHeap, 0, sizeof (DBGTHREAD));

    /* initialize thread data */
    lpth->lpStartAddress = lpStart;
    lpth->nPriority = nPriority;
    lpth->dwThreadID = dwThreadID;
    lpth->hThread = hThread;
    lpth->Next = NULL;

    /* retieve section names for executable module if file handle passed */
    if (hFile)
{
RetrieveModuleName (lppr->szModule, hFile);
lppr->lpSection = NULL;
RetrieveSectionNames (lppr->hDbgHeap, hFile, &(lppr->lpSection));
}

    /* set linked list pointers */
    while (pNode && pNode->Next)
pNode = (DBGTHREAD *)pNode->Next;

    if (!pNode)
lppr->lpThreads = lpth;
    else
(DBGTHREAD *)pNode->Next = lpth;
}

void WINAPI RemoveThreadNode (
    DBGPROCESS  *lppr,
    DWORD  dwThreadID)
{
    DBGTHREAD *pNode = lppr->lpThreads;
    DBGTHREAD *lpth;

    while (pNode->Next &&
   ((DBGTHREAD *)(pNode->Next))->dwThreadID != dwThreadID)
pNode = (DBGTHREAD *)pNode->Next;

    if (pNode->Next)
{
lpth = (DBGTHREAD *)pNode->Next;
pNode->Next = ((DBGTHREAD *)(pNode->Next))->Next;
CloseHandle (lpth->hThread);
HeapFree (lppr->hDbgHeap, 0, (LPSTR)lpth);
}

    else if (pNode->dwThreadID == dwThreadID)
{
lpth = pNode;
lppr->lpThreads = NULL;
CloseHandle (lpth->hThread);
HeapFree (lppr->hDbgHeap, 0, (LPSTR)lpth);
}
}

/* find a thread's handle based on thread ID */
HANDLEWINAPI FindThreadHandle (
    DBGPROCESS  *lppr,
    DWORD  dwThreadId)
{
    DBGTHREAD *pNode = lppr->lpThreads;

    while (pNode != NULL)
if (pNode->dwThreadID == dwThreadId)
    return (pNode->hThread);
    return NULL;
}

void WINAPI AddDllNode (
    DBGPROCESS   *lppr,
    LOAD_DLL_DEBUG_INFO    *lpdbgDll)
{
    DBGDLL    *lpdll;
    DBGDLL    *pNode = lppr->lpDlls;

    /* allocate Dll node off heap */
    if ((lpdll = (DBGDLL *)HeapAlloc (lppr->hDbgHeap, 0, sizeof (DBGDLL))) == NULL)
ReportError (IDS_ERRHEAPALLOC);

    /* initialize Dll structure */
    lpdll->hFile = lpdbgDll->hFile;
    lpdll->lpBaseOfDll = lpdbgDll->lpBaseOfDll;
    lpdll->dwDebugInfoFileOffset = lpdbgDll->dwDebugInfoFileOffset;
    lpdll->nDebugInfoSize = lpdbgDll->nDebugInfoSize;
    lpdll->fUnicode = lpdbgDll->fUnicode;
    lpdll->Next = NULL;
    lpdll->lpSection = NULL;

RetrieveModuleName (lpdll->szImageName, lpdbgDll->hFile);

    /* get section names for DLL */
    RetrieveSectionNames (lppr->hDbgHeap, lpdbgDll->hFile, &(lpdll->lpSection));

    /* set linked list pointers */
    while (pNode && pNode->Next)
pNode = (DBGDLL *)pNode->Next;

    if (!pNode)
lppr->lpDlls = lpdll;
    else
(DBGDLL *)pNode->Next = lpdll;
}

void WINAPI RemoveDllNode (
    DBGPROCESS   *lppr,
    LOAD_DLL_DEBUG_INFO    *lpdbgDll)
{
    DBGDLL  *pNode = lppr->lpDlls;
    DBGDLL  *lpdll;
    SECTIONINFO    *pSection, *pNext;

    while (pNode->Next &&
   ((DBGDLL *)(pNode->Next))->lpBaseOfDll != lpdbgDll->lpBaseOfDll)
pNode = (DBGDLL *)pNode->Next;

    if (pNode->Next)
{
lpdll = (DBGDLL *)pNode->Next;
pNode->Next = ((DBGDLL *)(pNode->Next))->Next;

pSection = pNext = lpdll->lpSection;
while (pNext)
    {
    pNext = (SECTIONINFO *)pSection->Next;
    HeapFree (lppr->hDbgHeap, 0, (LPSTR)pSection);
    pSection = pNext;
    }

HeapFree (lppr->hDbgHeap, 0, (LPSTR)lpdll);
}

    else if (pNode->lpBaseOfDll == lpdbgDll->lpBaseOfDll)
{
lpdll = pNode;
lppr->lpDlls = NULL;

pSection = pNext = lpdll->lpSection;
while (pNext)
    {
    pNext = (SECTIONINFO *)pSection->Next;
    HeapFree (lppr->hDbgHeap, 0, (LPSTR)pSection);
    pSection = pNext;
    }

HeapFree (lppr->hDbgHeap, 0, (LPSTR)lpdll);
}
}

void WINAPI RecordException (
    DBGPROCESS   *lppr,
    DEBUG_EVENT    *de)
{
    DBGEXCEPTREC    *lper;
    DBGEXCEPTREC    *per = lppr->lpERs;
    HANDLE    hThread;
    int     i;

    /* allocate Dll node off heap */
    if ((lper = (DBGEXCEPTREC *)HeapAlloc (lppr->hDbgHeap, 0, sizeof (DBGEXCEPTREC))) == NULL)
ReportError (IDS_ERRHEAPALLOC);

    /* initialize exception record structure */
    lper->dwThreadId = de->dwThreadId;
    lper->dwFirstChance = de->u.Exception.dwFirstChance;
    lper->ExceptRecord.ExceptionCode = de->u.Exception.ExceptionRecord.ExceptionCode;
    lper->ExceptRecord.ExceptionFlags = de->u.Exception.ExceptionRecord.ExceptionFlags;
    lper->ExceptRecord.ExceptionRecord = NULL;
    lper->ExceptRecord.ExceptionAddress = de->u.Exception.ExceptionRecord.ExceptionAddress;
    lper->ExceptRecord.NumberParameters = de->u.Exception.ExceptionRecord.NumberParameters;
    for (i=0; i<EXCEPTION_MAXIMUM_PARAMETERS; i++)
lper->ExceptRecord.ExceptionInformation[i] =
   de->u.Exception.ExceptionRecord.ExceptionInformation[i];

    /* get exception thread handle */
    hThread = FindThreadHandle (lppr, de->dwThreadId);
    lper->Context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext (hThread, &(lper->Context));

    /* set linked list pointers */
    while (per && per->Next)
per = (DBGEXCEPTREC *)per->Next;
    if (!per)
lppr->lpERs = lper;
    else
(DBGEXCEPTREC *)per->Next = lper;
}

/*
 * MIPS/ALPHA/PPC must increment the FIR/Iar on a breakpoint
 * in order to fetch the next instruction
 */

int WINAPI HandleBreakPoint(
    DBGPROCESS      *lppr,
    DWORD      dwThreadID
    )
{
    CONTEXT   ThreadContext;
    DBGTHREAD *pNode = lppr->lpThreads;
    HANDLE    hThread;

    while (pNode && pNode->dwThreadID != dwThreadID)
pNode = (DBGTHREAD *)pNode->Next;

    if(!pNode)
return(0);
    else
{
        hThread = (HANDLE) pNode->hThread;

        ThreadContext.ContextFlags = CONTEXT_CONTROL;
        if (!GetThreadContext (hThread, &ThreadContext))
    {
    ReportError (IDS_ERRGETTHREADCONTEXT);
            return(0);
    }

#ifdef _PPC_
        ThreadContext.Iar += 4;
#elif !defined _X86_
        ThreadContext.Fir += 4;
#endif

        if (!SetThreadContext (hThread, &ThreadContext))
    {
    ReportError (IDS_ERRSETTHREADCONTEXT);
    return (0);
    }

return (1);
}
}

int   WINAPI WhereIsStack (
    HANDLE  hThreadContext)
{
    CONTEXTThreadContext;

    ThreadContext.ContextFlags = CONTEXT_CONTROL;
    if (!GetThreadContext (hThreadContext, &ThreadContext))
ReportError (IDS_ERRGETTHREADCONTEXT);

#ifdef _X86_
    return ThreadContext.Esp;
#elif defined (_PPC_)
    return (int) ThreadContext.Gpr1;
#else /* _MIPS_ */
    return (int) ThreadContext.IntSp;
#endif

}

/* function walks memory regions of process */
int    WINAPI WalkProcess (
    HANDLE    hChildProcess,
    LPVOID    *lpWalk,
    LPINT     *lpObjects)
{
    LPVMOBJECT  lpList;
    LPVOID  lpMem = 0;
    LPVOID  lpStack = 0;
    int   nCnt, i;
    SYSTEM_INFO   si;

    /* if pointer exists, reset to no commit */
    if (*lpWalk)
{
if (!VirtualFree (*lpWalk, 0, MEM_DECOMMIT))
    ReportError (IDS_ERRVIRTUALFREE);
}

    /* else perform initial reserve */
    else
if ((*lpWalk = VirtualAlloc (NULL,
     TOTALVMRESERVE,
     MEM_RESERVE,
     PAGE_NOACCESS)) == NULL)
    ReportError (IDS_ERRVIRTUALALLOC);

    /* initialize list pointer to beginning of walker list */
    lpList = (LPVMOBJECT)*lpWalk;

    /* Get maximum address range from system info */
    GetSystemInfo(&si);

    /* walk process addresses */
    while (lpMem < si.lpMaximumApplicationAddress)
{
try  /* virtual memory exception handler automatically commits mem */
    {
    /* touch memory in VMOBJECT structure that exists after mbi field to trigger
       access violation for a new page of memory.  Do this here since VirtualQueryEx
       does not seem to generate exceptions, rather it just fails calls */
    *lpList->szObjType = 0;
    *lpList->szModule = 0;
    *lpList->szSection = 0;
    lpList->bNew = 0;

    VirtualQueryEx (hChildProcess,
    lpMem,
    &(lpList->mbi),
    sizeof (MEMORY_BASIC_INFORMATION));

    /* increment lpMem to next region of memory */
    lpMem = (LPVOID)((DWORD)lpList->mbi.BaseAddress +
     (DWORD)lpList->mbi.RegionSize);

    lpList++;
    }

except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION)
    {
    /* commit next page of walker list memory */
    if (((int)lpList + 4096) < (int)*lpWalk + TOTALVMRESERVE)
VirtualAlloc ((LPVOID)((int)lpList + sizeof (VMOBJECT)),
      4096,
      MEM_COMMIT,
      PAGE_READWRITE);
    else
{
NotifyUser (NULL,
    IDS_ERROR,
    IDS_NOTENOUGHMEM,
    " reserved for Objects",
    MB_OK);
return 0;
}
    }
}

    /* allocate objects index array */
    if (*lpObjects)
LocalFree (*lpObjects);
    nCnt = (((int)(LPVOID)lpList)-(int)*lpWalk)/sizeof (VMOBJECT);
    *lpObjects = LocalAlloc (LPTR, nCnt * sizeof (int));
    for (i=0; i<nCnt; i++)
(*lpObjects)[i] = i;

    /* return number of item in list */
    return (nCnt);
}

void WINAPI AnalyzeProcess (
    DBGPROCESS  *lpDbgProcess,
    LPVMOBJECT  lpVMObject,
    int   nObjects)
{
    DBGTHREAD*pTh = lpDbgProcess->lpThreads;
    DBGDLL*pDll = lpDbgProcess->lpDlls;
    int nStack;
    int nThreadCnt = 0;
    charszNum[10];
    charszObjDesc[MAX_PATH];
    SECTIONINFO  *pSection;
    int i;
    MEMORY_BASIC_INFORMATIONmbi;
    LPPROBElpProbe;

    /* name default heap in process if available */
    if (lpProbe = RetrieveProbeData ())
{
VirtualQueryEx (lpDbgProcess->hProcess, (PVOID)lpProbe->hDefHeap, &mbi, sizeof (mbi));

/* ignore invalid regions with a base region of 0 */
if (mbi.AllocationBase)
    {
    /* find all objects with same base region */
    for (i=0; i<nObjects; i++)
{
if (lpVMObject[i].mbi.AllocationBase == mbi.AllocationBase)
    {
    strcpy (lpVMObject[i].szObjType, "heap");
    strcpy (lpVMObject[i].szModule, "process default");
    }
}
    }
}

    /* name stack object for each thread in process */
    while (pTh != NULL)
{
/* get stack location for thread */
nStack = WhereIsStack (pTh->hThread);

strcpy (szObjDesc, "Thread ");
strcat (szObjDesc, itoa (nThreadCnt, szNum, 10));

/* locate base region */
VirtualQueryEx (lpDbgProcess->hProcess, (PVOID)nStack, &mbi, sizeof (mbi));

/* ignore invalid regions with a base region of 0 */
if (mbi.AllocationBase)
    {
    /* find all objects with same base region */
    for (i=0; i<nObjects; i++)
{
if (lpVMObject[i].mbi.AllocationBase == mbi.AllocationBase)
    {
    strcpy (lpVMObject[i].szObjType, "stack");
    strcpy (lpVMObject[i].szModule, szObjDesc);
    }
}
    }

/* locate and identify the guard page in each stack.
   The guard page is a single page of committed memory at the lower end of
   committed memory and immediately adjacent to the stack's reserved memory.
   If there is no more reserved stack space left, the guard page will be the
   last page of committed memory.  A stack cannot exist without a guard page.
   There will be at most three regions of memory in the stack: the actual stack
   space is committed, the guard page is committed and the remaining address
   space is marked reserved. */
/* locate lowest region of stack, since objects array is not sorted until after
   this function, it is always in ascending address order :) */
i=0;
while (i<nObjects &&
       (int)lpVMObject[i].mbi.BaseAddress < nStack &&
       lpVMObject[i].mbi.AllocationBase != mbi.AllocationBase)
    i++;

if (i<nObjects &&
    lpVMObject[i].mbi.AllocationBase == mbi.AllocationBase)
    {
    if (lpVMObject[i].mbi.State == MEM_RESERVE)
i++;

    /* identify guard page in section field */
    strcpy (lpVMObject[i].szSection, "guard");
    }

/* increment thread count */
nThreadCnt++;

/* increment list pointer */
pTh = (DBGTHREAD *)pTh->Next;
}

    /* name DLL objects in process */
    while (pDll != NULL)
{
/* locate dll base region */
VirtualQueryEx (lpDbgProcess->hProcess, pDll->lpBaseOfDll, &mbi, sizeof (mbi));

/* ignore invalid regions with a base region of 0 */
if (mbi.AllocationBase)
    {
    /* find all objects with same base region */
    for (i=0; i<nObjects; i++)
{
if (lpVMObject[i].mbi.AllocationBase == mbi.AllocationBase)
    {
    strcpy (lpVMObject[i].szObjType, "dll");
    strcpy (lpVMObject[i].szModule, pDll->szImageName);
    }
}
    }

/* name dll sections */
pSection = (SECTIONINFO *)pDll->lpSection;
while (pSection != NULL)
    {
    i = 0;
    while (i++<nObjects)
{
if (lpVMObject[i].mbi.BaseAddress ==
((char *)pDll->lpBaseOfDll + pSection->uVirtualAddress))
    {
    strcpy (lpVMObject[i].szSection, pSection->szSection);
    break;
    }
}
    pSection = (SECTIONINFO *)pSection->Next;
    }

/* increment list pointer */
pDll = (DBGDLL *)pDll->Next;
}

    /* locate exe base region */
    VirtualQueryEx (lpDbgProcess->hProcess, lpDbgProcess->lpImage, &mbi, sizeof (mbi));

    /* ignore invalid regions with a base region of 0 */
    if (mbi.AllocationBase)
{
/* find all objects with same base region */
for (i=0; i<nObjects; i++)
    {
    if (lpVMObject[i].mbi.AllocationBase == mbi.AllocationBase)
{
strcpy (lpVMObject[i].szObjType, "exe");
strcpy (lpVMObject[i].szModule, lpDbgProcess->szModule);
}
    }
}

    /* name exe sections */
    pSection = lpDbgProcess->lpSection;
    while (pSection != NULL)
{
i = 0;
while (i++<nObjects)
    {
    if (lpVMObject[i].mbi.BaseAddress ==
    ((char *)lpDbgProcess->lpImage + pSection->uVirtualAddress))
{
strcpy (lpVMObject[i].szSection, pSection->szSection);
break;
}
    }
pSection = (SECTIONINFO *)pSection->Next;
}
}

/* find all occurrances of objects having same base region */
void WINAPI NameObjects (
    HANDLE  hProcess,
    LPVOID  lpAddress,
    LPVMOBJECT  lpVMObj,
    int   nObjects,
    char  *lpszObjType,
    char  *lpszModule)
{
    int i;
    MEMORY_BASIC_INFORMATIONmbi;

    /* locate base region */
    VirtualQueryEx (hProcess, lpAddress, &mbi, sizeof (mbi));

    /* ignore invalid regions with a base region of 0 */
    if (!mbi.AllocationBase)
return;

    /* find all objects with same base region */
    for (i=0; i<nObjects; i++)
{
if (lpVMObj[i].mbi.AllocationBase == mbi.AllocationBase)
    {
    strcpy (lpVMObj[i].szObjType, lpszObjType);
    strcpy (lpVMObj[i].szModule, lpszModule);
    }
}
}

void WINAPI IdentifyNewObjects (
    LPVMOBJECT  lpVMObjectOld,
    int   nObjectsOld,
    LPVMOBJECT  lpVMObject,
    int   nObjects)
{
    int    i, j;

    for (i=0; i<nObjects; i++)
{
for (j=0; j<nObjectsOld; j++)
    {
    if (VMCompare (lpVMObject+i, lpVMObjectOld+j))
goto NEXT;
    }

/* if not found must be a new item */
lpVMObject[i].bNew = TRUE;
NEXT:;
}
}

BOOL WINAPI VMCompare (
    LPVMOBJECT  lpVM1,
    LPVMOBJECT  lpVM2)
{

    /* compare memory info */
    if (lpVM1->mbi.AllocationBase      != lpVM2->mbi.AllocationBase  ||
lpVM1->mbi.BaseAddress       != lpVM2->mbi.BaseAddress  ||
lpVM1->mbi.RegionSize       != lpVM2->mbi.RegionSize   ||
lpVM1->mbi.Protect       != lpVM2->mbi.Protect  ||
lpVM1->mbi.AllocationProtect   != lpVM2->mbi.AllocationProtect  ||
lpVM1->mbi.State       != lpVM2->mbi.State  ||
lpVM1->mbi.Type        != lpVM2->mbi.Type     )
return FALSE;

    /* compare character information */
    if (memcmp ((LPVOID)lpVM1->szObjType, (LPVOID)lpVM2->szObjType, 12) ||
memcmp ((LPVOID)lpVM1->szSection,
(LPVOID)lpVM2->szSection,
IMAGE_SIZEOF_SHORT_NAME)||
memcmp ((LPVOID)lpVM1->szModule, (LPVOID)lpVM2->szModule, MAX_PATH))
return FALSE;

    /* if still here, must be a match */
    return TRUE;
}

/* test to see if all memory objects in range are committed */
BOOL   WINAPI CommittedMemoryRange (
    int   i,
    int   j,
    LPVMOBJECT  lpvm,
    int   *Objects)
{
    int    k;

    /* test each memory object in range */
    for (k = min(i, j); k <= max (i, j); k++)
/* report any non committed memory regions */
if (lpvm[Objects[k]].mbi.State != MEM_COMMIT)
    return FALSE;

    /* report all committed range */
    return TRUE;
}

/* signal debugger thread to access process memory */
BOOL WINAPI AccessProcessMemory (
    HANDLE    hMemoryEvent,
    HANDLE    hAckEvent,
    LPVOID    lpAddress,
    LPVOID    lpBuffer,
    DWORD     *dwSize)
{
    DWORD    dwResult;

    /* copy data to module globals */
    PMAAddress = lpAddress;
    PMABuffer = lpBuffer;
    PMASize = *dwSize;

    /* signal debugger thread to read memory from process */
    SetEvent (hMemoryEvent);

    /* wait on debugger thread to signal completion of memory task */
    dwResult = WaitForSingleObject (hAckEvent, 100000);
    ResetEvent (hAckEvent);

    /* reset size accessed to verify operation */
    *dwSize = PMASize;

    return (dwResult == WAIT_OBJECT_0 &&
    PMASize != 0);
}

BOOL WINAPI InterruptThread_HookProcess (
    DBGPROCESS       *lpDbgProcess,
    LPPROCESS_STATE    lpState)
{
    DBGDLL*pDlls = lpDbgProcess->lpDlls;
    int nBytes, nLen, i;
    MEMORY_BASIC_INFORMATIONmbi;
    LPVOIDlpLoadLibrary = NULL;
    charszKernel[] = "KERNEL32.DLL";
    charszFunction[] = "LoadLibraryA";
    BYTEpCode[PAGESIZE];
    BYTEpStack[PAGESIZE];
    HANDLEhDll;

    /* initialize stack and code pages */
    for (i=0; i<PAGESIZE; i++)
{
pStack[i] = 0;
pCode[i] = 0;
}

    /* find kernel32 Dll */
    while (pDlls != NULL)
{
if (!stricmp (pDlls->szImageName, szKernel))
    break;
pDlls = (DBGDLL *)pDlls->Next;
}

    /* if DLL not loaded abort */
    if (pDlls == NULL)
return FALSE;

    /* load the dll in this process, find the function offset in this
       process and normalize to the offset in the child process */
    hDll = LoadLibrary (szKernel);
    lpLoadLibrary = GetProcAddress (hDll, szFunction);
    VirtualQuery (lpLoadLibrary, &mbi, sizeof (mbi));
    (int)lpLoadLibrary += ((int)pDlls->lpBaseOfDll - (int)mbi.AllocationBase);
    FreeLibrary (hDll);

    /* get thread context information and save for replacement */
    lpState->Context.ContextFlags = CONTEXT_FULL;
    if (!GetThreadContext (lpDbgProcess->lpThreads->hThread, &(lpState->Context)))
return FALSE;

#ifdef _X86_
    lpState->Eip = (LPVOID)lpState->Context.Eip;
    lpState->Esp = (LPVOID)lpState->Context.Esp;
#elif defined(_PPC_)
    lpState->Eip = (LPVOID)lpState->Context.Iar;
    lpState->Esp = (LPVOID)lpState->Context.Gpr1;
#else /* MIPS */
    lpState->Eip = (LPVOID)lpState->Context.Fir;
    lpState->Esp = (LPVOID)lpState->Context.IntSp;
#endif

    /* locate first writeable code page in exe module */
    lpState->pCodePage = lpDbgProcess->lpImage;
    VirtualQueryEx (lpDbgProcess->hProcess, lpState->pCodePage, &mbi, sizeof (mbi));
    while (!(mbi.Protect & PAGE_READWRITE) &&
   mbi.AllocationBase == lpDbgProcess->lpImage)
{
(int)lpState->pCodePage = (int)mbi.BaseAddress + mbi.RegionSize;
VirtualQueryEx (lpDbgProcess->hProcess, lpState->pCodePage, &mbi, sizeof (mbi));
}

    if (mbi.AllocationBase != lpDbgProcess->lpImage)
return FALSE;

    /* save code page for reset process */
    ReadProcessMemory (lpDbgProcess->hProcess, lpState->pCodePage, lpState->Code, PAGESIZE, &nBytes);

    /* write DLL path to code page */
    strcpy ((char *)pCode, szCurPath);
    strcat ((char *)pCode, "\\probe.dll");
    nLen = strlen ((char *)pCode) + 1;

    /* find current stack page and save  */

#ifdef _X86_
    lpState->pStackPage = (LPVOID)((((int)lpState->Context.Esp)/PAGESIZE) * PAGESIZE);
#elif defined(_PPC_)
    lpState->pStackPage = (LPVOID)((((int)lpState->Context.Gpr1)/PAGESIZE) * PAGESIZE);
#else /* MIPS */
    lpState->pStackPage = (LPVOID)((((int)lpState->Context.IntSp)/PAGESIZE) * PAGESIZE);
#endif

    ReadProcessMemory (lpDbgProcess->hProcess, lpState->pStackPage, lpState->Stack, PAGESIZE, &nBytes);

    /* push address of DLL string on stack */
    *((int *)(pStack+4092)) = (int)lpState->pCodePage;

    /* push return address on stack */
    *((int *)(pStack+4088)) = (int)lpState->pCodePage+nLen;

    /* return to Int 3 breakpoint instruction */
    pCode[nLen] = 0xCC;

    /* write new code and stack pages */
    WriteProcessMemory (lpDbgProcess->hProcess, lpState->pCodePage, pCode, PAGESIZE, &nBytes);
    WriteProcessMemory (lpDbgProcess->hProcess, lpState->pStackPage, pStack, PAGESIZE, &nBytes);

    /* update Eip to execute at actual LoadLibrary function */
    /* adjust stack pointer to point to return address */

#ifdef _X86_
    lpState->Context.Eip = (UINT)lpLoadLibrary;
    lpState->Context.Esp = (UINT)lpState->pStackPage+4088;
#elif defined(_PPC_)
    lpState->Context.Iar = (UINT)lpLoadLibrary;
    lpState->Context.Gpr1 = (UINT)lpState->pStackPage+4088;
#else /* _MIPS_ */
    lpState->Context.Fir = (UINT)lpLoadLibrary;
    lpState->Context.IntSp = (UINT)lpState->pStackPage+4088;
#endif

    if (!SetThreadContext (lpDbgProcess->lpThreads->hThread, &(lpState->Context)))
{
ReportError (IDS_ERRSETTHREADCONTEXT);
/* replace code and stack pages */
WriteProcessMemory (lpDbgProcess->hProcess, lpState->pCodePage, lpState->Code, PAGESIZE, &nBytes);
WriteProcessMemory (lpDbgProcess->hProcess, lpState->pStackPage, lpState->Stack, PAGESIZE, &nBytes);
return FALSE;
}

    return TRUE;
}

void WINAPI ResetInterruptedThread (
    DBGPROCESS       *lpDbgProcess,
    LPPROCESS_STATE    lpState)
{
    int    nBytes;

    /* return thread context information */

#ifdef _X86_
    lpState->Context.Eip = (UINT)lpState->Eip;
    lpState->Context.Esp = (UINT)lpState->Esp;
#elif defined(_PPC_)
    lpState->Context.Iar = (UINT)lpState->Eip;
    lpState->Context.Gpr1 = (UINT)lpState->Esp;
#else /* MIPS */
    lpState->Context.Fir = (UINT)lpState->Eip;
    lpState->Context.IntSp = (UINT)lpState->Esp;
#endif

    SetThreadContext (lpDbgProcess->lpThreads->hThread, &(lpState->Context));

    /* return code and stack information */
    WriteProcessMemory (lpDbgProcess->hProcess, lpState->pCodePage, lpState->Code, PAGESIZE, &nBytes);
    WriteProcessMemory (lpDbgProcess->hProcess, lpState->pStackPage, lpState->Stack, PAGESIZE, &nBytes);
}


PEFILE.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 "pefile.h"

PIMAGE_SECTION_HEADERWINAPI IDSectionHeaderOffset (LPVOID);

BOOL WINAPI DllMain (
    HANDLE    hModule,
    DWORD     dwFunction,
    LPVOID    lpNot)
{
    return TRUE;
}

/* return offset to file header */
PIMAGE_FILE_HEADER   WINAPI FileHeaderOffset (
    LPVOID    lpFile)
{
    int      ImageHdrOffset = 0;

    /* if DOS based file, skip DOS header and file signature */
    if (*((USHORT *)lpFile) == IMAGE_DOS_SIGNATURE)
{
/* file image header offset exists after DOS header and nt signature */
ImageHdrOffset = (int)((ULONG *)lpFile)[15] + sizeof (ULONG);
if (*((ULONG *)((char *)lpFile + ImageHdrOffset - sizeof (ULONG))) != IMAGE_NT_SIGNATURE)
    return NULL;
}

    /* optional header exists immediately after file header and image header */
    return (PIMAGE_FILE_HEADER)((int)lpFile + ImageHdrOffset);
}

/* return optional header data */
PIMAGE_OPTIONAL_HEADERWINAPI OptionalHeaderOffset (
    LPVOID    lpFile)
{
    int      ImageHdrOffset = 0;

    /* if DOS based file, skip DOS header and file signature */
    if (*((USHORT *)lpFile) == IMAGE_DOS_SIGNATURE)
{
/* file image header offset exists after DOS header and nt signature */
ImageHdrOffset = (int)((ULONG *)lpFile)[15] + sizeof (ULONG);
if (*((ULONG *)((char *)lpFile + ImageHdrOffset - sizeof (ULONG))) != IMAGE_NT_SIGNATURE)
    return NULL;
}

    /* optional header exists immediately after file header and image header */
    return (PIMAGE_OPTIONAL_HEADER)((char *)lpFile + ImageHdrOffset + sizeof (IMAGE_FILE_HEADER));
}

/* return pointer to first section header */
PIMAGE_SECTION_HEADERWINAPI SectionHeaderOffset (
    LPVOID    lpFile)
{
    int      ImageHdrOffset = 0;

    /* if DOS based file, skip DOS header and file signature */
    if (*((USHORT *)lpFile) == IMAGE_DOS_SIGNATURE)
{
/* file image header offset exists after DOS header and nt signature */
ImageHdrOffset = (int)((ULONG *)lpFile)[15] + sizeof (ULONG);
if (*((ULONG *)((char *)lpFile + ImageHdrOffset - sizeof (ULONG))) != IMAGE_NT_SIGNATURE)
    return NULL;
}

    /* optional header exists immediately after file header and image header */
    return (PIMAGE_SECTION_HEADER)((int)OptionalHeaderOffset (lpFile) +
(int)((PIMAGE_FILE_HEADER)((int)lpFile + ImageHdrOffset))->SizeOfOptionalHeader);
}

/* return offset to first IMAGE_IMPORT_DIRECTORY entry */
PIMAGE_IMPORT_DIRECTORY  WINAPI ImportDirectoryOffset (
LPVOID  lpFile)
{
    PIMAGE_OPTIONAL_HEADER   poh = OptionalHeaderOffset (lpFile);
    PIMAGE_SECTION_HEADER    psh = SectionHeaderOffset (lpFile);
    int      nSections = NumOfSections (lpFile);
    int      i = 0;
    LPVOID     VAImportDir;

    VAImportDir = (LPVOID)poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

    /* locate section containing import directory */
    while (i++<nSections)
{
if (psh->VirtualAddress <= (DWORD)VAImportDir &&
    psh->VirtualAddress + psh->SizeOfRawData > (DWORD)VAImportDir)
    break;
psh++;
}

    if (i > nSections)
return 0;

    /* return image import directory offset */
    return (PIMAGE_IMPORT_DIRECTORY)(((int)lpFile + (int)VAImportDir - psh->VirtualAddress) +
   (int)psh->PointerToRawData);
}

/* return pointer to image directory section header */
PIMAGE_SECTION_HEADERWINAPI IDSectionHeaderOffset (
    LPVOID    lpFile)
{
    int      ImageHdrOffset = 0;
    PIMAGE_OPTIONAL_HEADER   poh = OptionalHeaderOffset (lpFile);
    PIMAGE_SECTION_HEADER    psh = SectionHeaderOffset (lpFile);
    int      nSections = NumOfSections (lpFile);
    int      i = 0;
    LPVOID     VAImportDir;

    VAImportDir = (LPVOID)poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

    /* locate section containing import directory */
    while (i++<nSections)
{
if (psh->VirtualAddress <= (DWORD)VAImportDir &&
    psh->VirtualAddress + psh->SizeOfRawData > (DWORD)VAImportDir)
    break;
psh++;
}

    if (i > nSections)
return 0;
    else
return psh;
}

/* return the total number of sections in the module */
int   WINAPI NumOfSections (
    LPVOID    lpFile)
{
    int      ImageHdrOffset = 0;

    /* if DOS based file, skip DOS header and file signature */
    if (*((USHORT *)lpFile) == IMAGE_DOS_SIGNATURE)
{
/* file image header offset exists after DOS header and nt signature */
ImageHdrOffset = (int)((ULONG *)lpFile)[15] + sizeof (ULONG);
if (*((ULONG *)((char *)lpFile + ImageHdrOffset - sizeof (ULONG))) != IMAGE_NT_SIGNATURE)
    return 0;
}

    /* section total is found in the file header */
    return (int)((PIMAGE_FILE_HEADER)((int)lpFile + ImageHdrOffset))->NumberOfSections;
}

/* retrieve name of module from module's open file handle */
void WINAPI RetrieveModuleName (
    char      *lpszModule,
    HANDLE    hFile)
{
    HANDLE     hMapFile;
    LPVOID     lpFile;
    char     *lpszName;
    int      nSections;
    ULONG     VAExportDir;
    int      i=0;
    int      ImageHdrOffset;
    PIMAGE_SECTION_HEADER    psh;
    PIMAGE_FILE_HEADER     pfh;
    PIMAGE_OPTIONAL_HEADER   poh;
    PIMAGE_EXPORT_DIRECTORY  ped;

    /* memory map handle to DLL for easy access */
    hMapFile = CreateFileMapping (hFile,
  (LPSECURITY_ATTRIBUTES)NULL,
  PAGE_READONLY,
  0,
  0,
  NULL);

    /* map view of entire file */
    lpFile = MapViewOfFile (hMapFile, FILE_MAP_READ, 0, 0, 0);

    /* if DOS based file */
    if (*((USHORT *)lpFile) == IMAGE_DOS_SIGNATURE)
{
/* file image header offset exists after DOS header and nt signature */
ImageHdrOffset = (int)((ULONG *)lpFile)[15] + sizeof (ULONG);
if (*((ULONG *)((char *)lpFile + ImageHdrOffset - sizeof (ULONG))) !=
    IMAGE_NT_SIGNATURE)
    {
    strcpy (lpszModule, "Error, no IMAGE_NT_SIGNATURE");
    goto EXIT;
    }
}

    pfh = (PIMAGE_FILE_HEADER)((char *)lpFile + ImageHdrOffset);

    /* if optional header exists and exports directory exists proceed */
    if (pfh->SizeOfOptionalHeader)
{
/* locate virtual address for Export Image Directory in OptionalHeader */
poh = (PIMAGE_OPTIONAL_HEADER)((char *)pfh + sizeof (IMAGE_FILE_HEADER));
VAExportDir = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

/* locate section where export virtual address is located */
psh = (PIMAGE_SECTION_HEADER)((char *)poh + pfh->SizeOfOptionalHeader);
nSections = pfh->NumberOfSections;
while (i++<nSections)
    {
    if (psh->VirtualAddress <= VAExportDir &&
psh->VirtualAddress + psh->SizeOfRawData > VAExportDir)
break;
    psh++;
    }

/* locate export image directory */
if (i < nSections)
    ped = (PIMAGE_EXPORT_DIRECTORY)((char *)lpFile +
(VAExportDir - psh->VirtualAddress) + psh->PointerToRawData);
else
    {
    strcpy (lpszModule, "IMAGE_EXPORT_DIRECTORY not found");
    goto EXIT;
    }

/* read name from export directory */
lpszName = (char *)lpFile + ped->Name + (psh->PointerToRawData - psh->VirtualAddress);
strcpy (lpszModule, lpszName);
}

    else
strcpy (lpszModule, "Error, no IMAGE_OPTIONAL_HEADER");

EXIT:
    /* clean up before exiting */
    UnmapViewOfFile (lpFile);
    CloseHandle (hMapFile);
}

/* retieve section names from module file handle */
void WINAPI RetrieveSectionNames (
    HANDLE  hHeap,
    HANDLE  hFile,
    SECTIONINFO   **pSection)
{
    HANDLE     hMapFile;
    LPVOID     lpFile;
    int      nSections;
    int      i=0;
    int      ImageHdrOffset;
    PIMAGE_SECTION_HEADER    psh;
    PIMAGE_FILE_HEADER     pfh;
    SECTIONINFO      *ps;

    /* memory map handle to DLL for easy access */
    hMapFile = CreateFileMapping (hFile,
  (LPSECURITY_ATTRIBUTES)NULL,
  PAGE_READONLY,
  0,
  0,
  NULL);

    /* map view of entire file */
    lpFile = MapViewOfFile (hMapFile, FILE_MAP_READ, 0, 0, 0);

    /* if DOS based file */
    if (*((USHORT *)lpFile) == IMAGE_DOS_SIGNATURE)
{
/* file image header offset exists after DOS header and nt signature */
ImageHdrOffset = (int)((ULONG *)lpFile)[15] + sizeof (ULONG);
if (*((ULONG *)((char *)lpFile + ImageHdrOffset - sizeof (ULONG))) !=
    IMAGE_NT_SIGNATURE)
    goto EXIT;
}

    pfh = (PIMAGE_FILE_HEADER)((char *)lpFile + ImageHdrOffset);

    /* if optional header exists, offset first section header */
    psh = (PIMAGE_SECTION_HEADER)((char *)pfh +
      sizeof (IMAGE_FILE_HEADER) + pfh->SizeOfOptionalHeader);

    /* allocate one section header for each section */
    ps = *pSection = (SECTIONINFO *)HeapAlloc (hHeap,
       HEAP_ZERO_MEMORY,
       sizeof (SECTIONINFO));
    nSections = pfh->NumberOfSections;
    while (TRUE)
{
strcpy (ps->szSection, psh[i].Name);
ps->uVirtualAddress = psh[i].VirtualAddress;
ps->uSize = psh[i].SizeOfRawData;

if (++i >= nSections)
    break;

/* allocate heap memory for sections */
ps->Next = (LPSECTIONINFO)HeapAlloc (hHeap,
     HEAP_ZERO_MEMORY,
     sizeof (SECTIONINFO));
ps = (SECTIONINFO *)ps->Next;
}

EXIT:
    /* clean up before exiting */
    UnmapViewOfFile (lpFile);
    CloseHandle (hMapFile);
}

/* function returns the entry point for an exe module lpFile must
   be a memory mapped file pointer to the beginning of the image file */
LPVOIDWINAPI GetModuleEntryPoint (
    LPVOID    lpFile)
{
    PIMAGE_OPTIONAL_HEADER   poh = OptionalHeaderOffset (lpFile);

    if (poh != NULL)
return (LPVOID)(poh->AddressOfEntryPoint);
    else
return NULL;
}

/* retrieve entry point */
LPVOIDWINAPI GetImageBase (
    LPVOID    lpFile)
{
    PIMAGE_OPTIONAL_HEADER   poh = OptionalHeaderOffset (lpFile);

    if (poh != NULL)
return (LPVOID)(poh->ImageBase);
    else
return NULL;
}

/* get import modules names separated by null terminators, return module count */
int  WINAPI GetImportModuleNames (
    LPVOID    lpFile,
    HANDLE    hHeap,
    char      **pszModules)
{
    PIMAGE_IMPORT_DIRECTORY  pid = ImportDirectoryOffset (lpFile);
    PIMAGE_SECTION_HEADER    pidsh = IDSectionHeaderOffset (lpFile);
    BYTE     *pData = (BYTE *)pid;
    int      nCnt = 0, nSize = 0, i;
    char     *pModule[1024];  /* hardcoded maximum number of modules?? */
    char     *psz;

    /* extract all import modules */
    while (pid->dwRVAModule)
{
/* allocate temporary buffer for absolute string offsets */
pModule[nCnt] = (char *)(pData + (pid->dwRVAModule-pidsh->VirtualAddress));
nSize += strlen (pModule[nCnt]) + 1;

/* increment to the next import directory entry */
pid++;
nCnt++;
}

    /* copy all strings to one chunk of heap memory */
    *pszModules = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nSize);
    psz = *pszModules;
    for (i=0; i<nCnt; i++)
{
strcpy (psz, pModule[i]);
psz += strlen (psz) + 1;
}

    return nCnt;
}

/* get import module function names separated by null terminators, return function count */
int  WINAPI GetImportFunctionNamesByModule (
    LPVOID    lpFile,
    HANDLE    hHeap,
    char      *pszModule,
    char      **pszFunctions)
{
    PIMAGE_IMPORT_DIRECTORY  pid = ImportDirectoryOffset (lpFile);
    PIMAGE_SECTION_HEADER    pidsh = IDSectionHeaderOffset (lpFile);
    DWORD     dwBase = ((DWORD)pid - pidsh->VirtualAddress);
    int      nCnt = 0, nSize = 0;
    DWORD     dwFunction;
    char     *psz;

    /* find module's pid */
    while (pid->dwRVAModule &&
   strcmp (pszModule, (char *)(pid->dwRVAModule+dwBase)))
pid++;

    /* count functions and total space required for them */
    nSize += strlen ((char *)((*(DWORD *)(pid->dwRVAFirstFunction + dwBase)) + dwBase+2)) + 1;

    /* last image directory does not have a separate function list, so improvise */
    if (!(dwFunction = pid->dwRVAFunctionList))
dwFunction = pid->dwRVAFirstFunction + 4;

    while (dwFunction &&
   *(char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2))
{
nSize += strlen ((char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2)) + 1;
dwFunction += 4;
nCnt++;
}

    /* allocate memory off heap for function names */
    *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nSize);
    psz = *pszFunctions;
    strcpy (psz, (char *)((*(DWORD *)(pid->dwRVAFirstFunction + dwBase)) + dwBase+2));
    psz += strlen ((char *)((*(DWORD *)(pid->dwRVAFirstFunction + dwBase)) + dwBase+2)) + 1;

    /* last image directory does not have a separate function list, so improvise */
    if (!(dwFunction = pid->dwRVAFunctionList))
dwFunction = pid->dwRVAFirstFunction + 4;

    while (dwFunction &&
   *((char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2)))
{
strcpy (psz, (char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2));
psz += strlen ((char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2)) + 1;
dwFunction += 4;
}

    return nCnt;
}

/* get exported function names separated by null terminators, return count of functions */
int  WINAPI GetExportFunctionNames (
    LPVOID    lpFile,
    HANDLE    hHeap,
    char      **pszFunctions)
{

    return 0;
}


PEFILE.H

#include <windows.h>

#define IDS_ERRBADFILENAME    1000

typedef struct SECTIONINFO   *LPSECTIONINFO;
typedef struct tagDbgSection
    {
    char    szSection[IMAGE_SIZEOF_SHORT_NAME];
    ULONG    uVirtualAddress;
    ULONG    uSize;
    LPSECTIONINFO   Next;
    }SECTIONINFO;

typedef struct tagImportDirectory
    {
    DWORD    dwRVAFunctionList;
    DWORD    dwDummy1;
    DWORD    dwDummy2;
    DWORD    dwRVAModule;
    DWORD    dwRVAFirstFunction;
    }IMAGE_IMPORT_DIRECTORY, * PIMAGE_IMPORT_DIRECTORY;

/* global prototypes for functions in pefile.c */
PIMAGE_FILE_HEADERWINAPI FileHeaderOffset (LPVOID);
PIMAGE_OPTIONAL_HEADERWINAPI OptionalHeaderOffset (LPVOID);
PIMAGE_SECTION_HEADERWINAPI SectionHeaderOffset (LPVOID);
PIMAGE_IMPORT_DIRECTORY WINAPI ImportDirectoryOffset (LPVOID);
intWINAPI NumOfSections (LPVOID);
voidWINAPI RetrieveModuleName (char *, HANDLE);
voidWINAPI RetrieveSectionNames (HANDLE, HANDLE, SECTIONINFO **);
LPVOIDWINAPI GetModuleEntryPoint (LPVOID);
LPVOIDWINAPI GetImageBase (LPVOID);
intWINAPI GetImportModuleNames (LPVOID, HANDLE, char  **);
intWINAPI GetImportFunctionNamesByModule (LPVOID, HANDLE, char *, char  **);
intWINAPI GetExportFunctionNames (LPVOID, HANDLE, char **);


PROBE.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 "pwalk.h"

/* process  globals */
HANDLE    hMMFile = 0;
LPPROBE     lpProbe = NULL;
CONTEXT     gContext;
DWORD    gReturn;
LPVOID    gLocation;
CRITICAL_SECTION    gCS;

BOOL   WINAPI ProbeProcess (HANDLE);
void   WINAPI FreeProbe ();
void   WINAPI GetFuncAddrInChildProc (char *, char *, DBGDLL *, LPVOID *);

/* entry point for DLL loading and unloading */
BOOL WINAPI DllMain (
    HANDLE    hModule,
    DWORD     dwFunction,
    LPVOID    lpNot)
{
    switch (dwFunction)
{
case DLL_PROCESS_ATTACH:
    ProbeProcess (hModule);
    break;

case DLL_PROCESS_DETACH:
    FreeProbe ();
    break;

default:
    break;
}

    return TRUE;
}

/* function initializes port structures */
BOOL WINAPI ProbeProcess (
    HANDLE    hDLL)
{
    charszMapFileName[MAX_PATH];

    /* load name for global file mapping */
    LoadString (hDLL, IDS_MAPFILENAME, szMapFileName, MAX_PATH);

    /* ProcessWalker creates file mapping then child process attaches */
    if ((hMMFile = OpenFileMapping (FILE_MAP_WRITE, FALSE, szMapFileName)))
{
/* map global view of file for hook function access */
lpProbe = (LPPROBE)MapViewOfFile (hMMFile, FILE_MAP_WRITE, 0, 0, 0);

/* initialize known process specific information in probe */
lpProbe->hProcess = GetCurrentProcess ();
lpProbe->hDefHeap = GetProcessHeap ();
}

    else
if (!(hMMFile = CreateFileMapping ((HANDLE)0xffffffff,
   NULL,
   PAGE_READWRITE,
   0,
   0x100000,
   szMapFileName)))
    return FALSE;
else
    {
    lpProbe = (LPPROBE)MapViewOfFile (hMMFile, FILE_MAP_WRITE, 0, 0, 0);
    ResetProbe ();
    }

    /* success */
    return TRUE;
}

/* release process objects */
void WINAPI FreeProbe ()
{
    if (lpProbe != NULL)
/* remove child process objects */
UnmapViewOfFile ((char *)lpProbe);

    CloseHandle (hMMFile);
}

/* function passes pointer to probe info back to ProcessWalker */
LPPROBE  WINAPI RetrieveProbeData ()
{
    return lpProbe;
}

/* reset probe data to null */
void  WINAPI ResetProbe ()
{
    lpProbe->hProcess = NULL;
    lpProbe->hDefHeap = NULL;
}


PSTAT.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 "pwalk.h"

#define ONE_K      1024
#define ONE_MEG       1048576

extern HFONT     hFont;
extern LPVOID     lpWalkerList;
extern DBGPROCESS    *lpChildProcess;
extern int     yChar;
extern SIZE     sChar0;

/* static variables that determine size of 3D status box */
intxSTATBOX;
intcxSTATBOX;
intdxSTATBOX;
intySTATBOX;
intdyuSTATBOX;
intdylSTATBOX;

void WINAPI TallyProcessStats (HWND, int *, int *, int *);
void WINAPI DrawBorders (HWND, LPPAINTSTRUCT);
void WINAPI DrawStatusText (HWND, LPPAINTSTRUCT, char *, COLORREF);

/* status window proc */
LONG WINAPI StatusWndProc (
    HWND    hWnd,
    UINT    uMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
  LONG      lRet = 1;
static  BOOL      bDefault;
static  char      szDefault[MAX_PATH];
static  COLORREF    crText;

    switch (uMsg)
{
case WM_CREATE:
    {
    RECT    rc;

    SetWindowLong (hWnd,
   WXB_HPENHILITE,
   (LONG)CreatePen (PS_SOLID, 0, GetSysColor (COLOR_BTNHIGHLIGHT)));
    SetWindowLong (hWnd,
   WXB_HPENSHADOW,
   (LONG)CreatePen (PS_SOLID, 0, GetSysColor (COLOR_BTNSHADOW)));

    /* initialize default status text */
    LoadString (GetModuleHandle (NULL),
IDS_STATUSREADY,
szDefault,
sizeof (szDefault));
    crText = GetSysColor (COLOR_WINDOWTEXT);

    /* add rewalk button */
    GetClientRect (GetParent (hWnd), &rc);
    CreateWindow ("button",
  "Rewalk",
  WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
  rc.right-74, -1, 75, sChar0.cy+10*GetSystemMetrics (SM_CYBORDER),
  hWnd,
  (HMENU)IDC_REWALK,
  GetModuleHandle (NULL),
  NULL);

    bDefault = TRUE;
    }
    break;

case WM_SIZE:
    {
    RECT    rc;

    GetClientRect (GetParent (hWnd), &rc);

    xSTATBOX = 10 * GetSystemMetrics (SM_CXBORDER);
    cxSTATBOX = rc.right/3 + 50;
    dxSTATBOX = 4 * GetSystemMetrics (SM_CXBORDER);
    ySTATBOX = 2 * GetSystemMetrics (SM_CYBORDER);
    dyuSTATBOX = 1 * GetSystemMetrics (SM_CYBORDER);
    dylSTATBOX = 2 * GetSystemMetrics (SM_CYBORDER);

    /* move child windows in view */
    MoveWindow (GetDlgItem (hWnd, IDC_REWALK),
rc.right-74,
-1,
75,
yChar+10*GetSystemMetrics (SM_CYBORDER),
TRUE);

    /* make sure we repaint correctly */
    InvalidateRect (hWnd, NULL, TRUE);
    }
    break;

case WM_TIMER:
    {
    RECT    rcStatus;

    KillTimer (hWnd, IDT_STATUS);
    bDefault = TRUE;
    /* invalidate status text area */
    SetRect (&rcStatus,
     xSTATBOX+1,
     ySTATBOX+1,
     xSTATBOX+cxSTATBOX,
     ySTATBOX+dylSTATBOX+dyuSTATBOX+yChar);
    InvalidateRect (hWnd, &rcStatus, TRUE);
    crText = GetSysColor (COLOR_WINDOWTEXT);
    }
    break;

case WM_SETTEXT:
    {
    RECT    rcStatus;

    /* release current string */
    if (GetWindowLong (hWnd, WXB_LPWINDOWTEXT))
LocalFree ((HLOCAL)GetWindowLong (hWnd, WXB_LPWINDOWTEXT));
    SetWindowLong (hWnd, WXB_LPWINDOWTEXT, 0);

    /* if null string, reset to default */
    if ((char *)lParam == NULL)
{
bDefault = TRUE;
crText = GetSysColor (COLOR_WINDOWTEXT);
}
    else
{
crText = (COLORREF)wParam;

/* allocate space for new string */
SetWindowLong (hWnd,
       WXB_LPWINDOWTEXT,
       (LONG)LocalAlloc (LPTR, strlen ((LPSTR)lParam)+1));
strcpy ((char *)GetWindowLong (hWnd, WXB_LPWINDOWTEXT), (char *)lParam);

/* restart five second timer */
KillTimer (hWnd, IDT_STATUS);
SetTimer (hWnd, IDT_STATUS, 5000, NULL);

bDefault = FALSE;
}

    /* invalidate status text area */
    SetRect (&rcStatus,
     xSTATBOX+1,
     ySTATBOX+1,
     xSTATBOX+cxSTATBOX,
     ySTATBOX+dylSTATBOX+dyuSTATBOX+yChar);
    InvalidateRect (hWnd, &rcStatus, TRUE);
    }
    break;

case WM_PAINT:
    {
    PAINTSTRUCT    ps;
    RECT   rc;

    BeginPaint (hWnd, &ps);
    GetClientRect (hWnd, &rc);

    /* draw 3D effects in window */
    DrawBorders (hWnd, &ps);

    /* update text */
    if (bDefault)
DrawStatusText (hWnd, &ps, szDefault, crText);
    else
DrawStatusText (hWnd,
&ps,
(char *)GetWindowLong (hWnd, WXB_LPWINDOWTEXT),
crText);

    EndPaint (hWnd, &ps);
    }
    break;

/* user defined message for updating child process information */
case UM_UPDATE:
    EnableWindow (GetDlgItem (hWnd, IDC_REWALK), ((LPVOID)wParam != NULL));
    break;

case WM_COMMAND:
    if (LOWORD (wParam) == IDC_REWALK)
PostMessage (GetParent (hWnd), WM_COMMAND, IDM_PROCESSREWALK, 0);
    break;

case WM_DESTROY:
    /* free pen objects */
    DeleteObject ((HPEN)GetWindowLong (hWnd, WXB_HPENHILITE));
    DeleteObject ((HPEN)GetWindowLong (hWnd, WXB_HPENSHADOW));

    /* free local memory from window text */
    if (GetWindowLong (hWnd, WXB_LPWINDOWTEXT))
LocalFree ((HLOCAL)GetWindowLong (hWnd, WXB_LPWINDOWTEXT));
    break;

default:
    lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);
    break;
}

    return lRet;
}

/* system statistics window proc */
LONG WINAPI SysStatWndProc (
    HWND    hWnd,
    UINT    uMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
    LONG    lRet = 1;

static  MEMORYSTATUS  msInfo, msInfoOld;
static  SYSTEM_INFO  sysInfo, sysInfoOld;
static  DWORD   dwFreeDiskOld, dwFreeDisk;
static  DWORD   dwTotalDiskOld, dwTotalDisk;

    switch (uMsg)
{
case WM_CREATE:
    /* get system info */
    GlobalMemoryStatus (&msInfo);
    GetSystemInfo (&sysInfo);
    GetFreeDiskSpace (&dwTotalDisk, &dwFreeDisk);

    /* update old data with current data */
    msInfoOld = msInfo;
    sysInfoOld = sysInfo;
    dwTotalDiskOld = dwTotalDisk;
    dwFreeDiskOld = dwFreeDisk;

    /* create buttons controls */
    CreateWindow ("button",
  "Refresh",
  WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
  100, 210, 75, 25,
  hWnd,
  (HMENU)IDC_REFRESH,
  GetModuleHandle (NULL),
  NULL);

    CreateWindow ("button",
  "Cancel",
  WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
  205, 210, 75, 25,
  hWnd,
  (HMENU)IDC_CANCEL,
  GetModuleHandle (NULL),
  NULL);
    break;

case WM_CHAR:
    switch (wParam)
{
case VK_RETURN:
    GlobalMemoryStatus (&msInfo);
    GetSystemInfo (&sysInfo);
    GetFreeDiskSpace (&dwTotalDisk, &dwFreeDisk);
    InvalidateRect (hWnd, NULL, TRUE);
    UpdateWindow (hWnd);
    break;

case VK_ESCAPE:
    DestroyWindow (hWnd);
    break;
}
    break;

case WM_COMMAND:
    switch (LOWORD (wParam))
{
case IDC_REFRESH:
    /* save previous values */
    msInfoOld = msInfo;
    sysInfoOld = sysInfo;
    dwTotalDiskOld = dwTotalDisk;
    dwFreeDiskOld = dwFreeDisk;

    /* get current values */
    GlobalMemoryStatus (&msInfo);
    GetSystemInfo (&sysInfo);
    GetFreeDiskSpace (&dwTotalDisk, &dwFreeDisk);
    InvalidateRect (hWnd, NULL, TRUE);
    UpdateWindow (hWnd);
    break;

case IDC_CANCEL:
    DestroyWindow (hWnd);
    break;
}
    break;

case WM_PAINT:
    {
    PAINTSTRUCT    ps;
    char   szText[100];
    HFONT   hOldFont;
    int    nLen;
    SIZE   size;
    POINT   pt;
    COLORREF   crText;

    BeginPaint (hWnd, &ps);

    /* set background mode to transparent */
    SetBkMode (ps.hdc, TRANSPARENT);
    hOldFont = SelectObject (ps.hdc, hFont);

    /* draw vertical separating line */
    MoveToEx (ps.hdc, 395, 5, &pt);
    LineTo (ps.hdc, 395, 200);

    /* refresh change header */
    wsprintf (szText, "%13s", "current value");
    SetRect (&(ps.rcPaint), 0, 5, 395, 20);
    ExtTextOut (ps.hdc, 275, 5, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* refresh change header */
    wsprintf (szText, "%10s", "changed by");
    SetRect (&(ps.rcPaint), 395, 5, 500, 20);
    ExtTextOut (ps.hdc, 400, 5, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* memory load statistic */
    nLen = wsprintf (szText, "%30s     %10lu", "Relative memory load [0-100]: ", msInfo.dwMemoryLoad);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 20, 395, 35);
    ExtTextOut (ps.hdc, 10, 20, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
    if (msInfo.dwMemoryLoad > msInfoOld.dwMemoryLoad)
{
crText = SetTextColor (ps.hdc, RGB (0, 100, 0));
wsprintf (szText, "+%-1lu", (msInfo.dwMemoryLoad-msInfoOld.dwMemoryLoad));
SetRect (&(ps.rcPaint), 395, 20, 500, 35);
ExtTextOut (ps.hdc, 400, 20, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}
    else if (msInfo.dwMemoryLoad < msInfoOld.dwMemoryLoad)
{
crText = SetTextColor (ps.hdc, RGB (100, 0, 0));
wsprintf (szText, "-%-1lu", (msInfoOld.dwMemoryLoad-msInfo.dwMemoryLoad));
SetRect (&(ps.rcPaint), 395, 20, 500, 65);
ExtTextOut (ps.hdc, 400, 20, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}

    /* pagesize */
    nLen = wsprintf (szText, "%22s             %10lub", "Pagesize granularity: ", sysInfo.dwPageSize);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 35, 395, 50);
    ExtTextOut (ps.hdc, 10, 35, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* total physical memory */
    nLen = wsprintf (szText, "%23s            %10luKb", "Total physical memory: ", msInfo.dwTotalPhys/ONE_K);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 50, 395, 65);
    ExtTextOut (ps.hdc, 10, 50, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* available physical memory */
    nLen = wsprintf (szText, "%27s        %10luKb", "Available physical memory: ", msInfo.dwAvailPhys/ONE_K);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 65, 395, 80);
    ExtTextOut (ps.hdc, 10, 65, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
    if (msInfo.dwAvailPhys > msInfoOld.dwAvailPhys)
{
crText = SetTextColor (ps.hdc, RGB (0, 100, 0));
wsprintf (szText, "+%-1luKb", (msInfo.dwAvailPhys-msInfoOld.dwAvailPhys)/ONE_K);
SetRect (&(ps.rcPaint), 395, 65, 500, 80);
ExtTextOut (ps.hdc, 400, 65, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}
    else if (msInfo.dwAvailPhys < msInfoOld.dwAvailPhys)
{
crText = SetTextColor (ps.hdc, RGB (100, 0, 0));
wsprintf (szText, "-%-1luKb", (msInfoOld.dwAvailPhys-msInfo.dwAvailPhys)/ONE_K);
SetRect (&(ps.rcPaint), 395, 65, 500, 80);
ExtTextOut (ps.hdc, 400, 65, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}

    /* total pagefile space */
    nLen = wsprintf (szText, "%24s           %10luMb", "Maximum pagefile space: ", msInfo.dwTotalPageFile/ONE_MEG);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 80, 395, 95);
    ExtTextOut (ps.hdc, 10, 80, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
    if (msInfo.dwTotalPageFile > msInfoOld.dwTotalPageFile)
{
crText = SetTextColor (ps.hdc, RGB (0, 100, 0));
wsprintf (szText, "+%-1luMb", (msInfo.dwTotalPageFile-msInfoOld.dwTotalPageFile)/ONE_MEG);
SetRect (&(ps.rcPaint), 395, 80, 500, 95);
ExtTextOut (ps.hdc, 400, 80, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}
    else if (msInfo.dwTotalPageFile < msInfoOld.dwTotalPageFile)
{
crText = SetTextColor (ps.hdc, RGB (100, 0, 0));
wsprintf (szText, "-%-1luMb", (msInfoOld.dwTotalPageFile-msInfo.dwTotalPageFile)/ONE_MEG);
SetRect (&(ps.rcPaint), 395, 80, 500, 95);
ExtTextOut (ps.hdc, 400, 65, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}

    /* available pagefile space */
    nLen = wsprintf (szText, "%26s         %10luMb", "Available pagefile space: ", (msInfo.dwAvailPageFile)/ONE_MEG);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 95, 395, 110);
    ExtTextOut (ps.hdc, 10, 95, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
    if (msInfo.dwAvailPageFile > msInfoOld.dwAvailPageFile)
{
crText = SetTextColor (ps.hdc, RGB (0, 100, 0));
wsprintf (szText, "+%-1luKb", (msInfo.dwAvailPageFile-msInfoOld.dwAvailPageFile)/ONE_K);
SetRect (&(ps.rcPaint), 395, 95, 500, 110);
ExtTextOut (ps.hdc, 400, 95, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}
    else if (msInfo.dwAvailPageFile < msInfoOld.dwAvailPageFile)
{
crText = SetTextColor (ps.hdc, RGB (100, 0, 0));
wsprintf (szText, "-%-1luKb", (msInfoOld.dwAvailPageFile-msInfo.dwAvailPageFile)/ONE_K);
SetRect (&(ps.rcPaint), 395, 95, 500, 110);
ExtTextOut (ps.hdc, 400, 95, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}

    /* total virtual address space */
    nLen = wsprintf (szText, "%29s      %10luMb", "Total virtual address space: ", msInfo.dwTotalVirtual/ONE_MEG);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 110, 395, 125);
    ExtTextOut (ps.hdc, 10, 110, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* available virtual address space */
    nLen = wsprintf (szText, "%33s  %10luMb", "Available virtual address space: ", msInfo.dwAvailVirtual/ONE_MEG);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 125, 395, 140);
    ExtTextOut (ps.hdc, 10, 125, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* lowest available virtual address */
    nLen = wsprintf (szText, "%25s            %#08lx", "Minimum virtual address: ", sysInfo.lpMinimumApplicationAddress);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 140, 395, 155);
    ExtTextOut (ps.hdc, 10, 140, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* highest available virtual address */
    nLen = wsprintf (szText, "%25s            %#08lx", "Maximum virtual address: ", sysInfo.lpMaximumApplicationAddress);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 155, 395, 170);
    ExtTextOut (ps.hdc, 10, 155, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* total disk space */
    nLen = wsprintf (szText, "%24s           %10luMb", "Total local disk space: ", dwTotalDisk/ONE_MEG);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 170, 395, 185);
    ExtTextOut (ps.hdc, 10, 170, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);

    /* free disk disk space */
    nLen = wsprintf (szText, "%23s            %10luMb", "Free local disk space: ", dwFreeDisk/ONE_MEG);
    GetTextExtentPoint (ps.hdc, szText, nLen, &size);
    SetRect (&(ps.rcPaint), 0, 185, 395, 200);
    ExtTextOut (ps.hdc, 10, 185, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
    if (dwFreeDisk > dwFreeDiskOld)
{
crText = SetTextColor (ps.hdc, RGB (0, 100, 0));
wsprintf (szText, "+%-1luMb", (dwFreeDisk-dwFreeDiskOld)/ONE_MEG);
SetRect (&(ps.rcPaint), 395, 185, 500, 200);
ExtTextOut (ps.hdc, 400, 185, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}
    else if (dwFreeDisk < dwFreeDiskOld)
{
crText = SetTextColor (ps.hdc, RGB (100, 0, 0));
wsprintf (szText, "-%-1luMb", (dwFreeDiskOld-dwFreeDisk)/ONE_MEG);
SetRect (&(ps.rcPaint), 395, 185, 500, 200);
ExtTextOut (ps.hdc, 400, 185, ETO_CLIPPED, &(ps.rcPaint), szText, strlen (szText), 0);
SetTextColor (ps.hdc, crText);
}

    /* replace old font */
    SelectObject (ps.hdc, hOldFont);
    EndPaint (hWnd, &ps);
    }
    break;

default:
    lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);
}

    return lRet;
}

/* process statistics window proc */
LONG WINAPI ProStatWndProc (
    HWND    hWnd,
    UINT    uMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
  LONG  lRet = 1;
static  int nTotalFree, nTotalCommit, nTotalReserve;

    switch (uMsg)
{
case WM_CREATE:
    {
    /* create buttons controls */
    CreateWindow ("button",
  "Refresh",
  WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
  45, 55, 75, 25,
  hWnd,
  (HMENU)IDC_REFRESH,
  GetModuleHandle (NULL),
  NULL);

    CreateWindow ("button",
  "Rewalk",
  WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
  140, 55, 75, 25,
  hWnd,
  (HMENU)IDM_PROCESSREWALK,
  GetModuleHandle (NULL),
  NULL);

    CreateWindow ("button",
  "Cancel",
  WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
  235, 55, 75, 25,
  hWnd,
  (HMENU)IDC_CANCEL,
  GetModuleHandle (NULL),
  NULL);

    /* tally process statictics */
    TallyProcessStats (hWnd, &nTotalFree, &nTotalReserve, &nTotalCommit);
    }
    break;

case WM_CHAR:
    switch (wParam)
{
case VK_RETURN:
    InvalidateRect (hWnd, NULL, TRUE);
    UpdateWindow (hWnd);
    break;

case VK_ESCAPE:
    DestroyWindow (hWnd);
    break;
}
    break;

case WM_PAINT:
    {
    PAINTSTRUCT    ps;
    HFONT   hOldFont;
    char   szText[100];

    BeginPaint (hWnd, &ps);
    hOldFont = SelectObject (ps.hdc, hFont);
    SetBkMode (ps.hdc, TRANSPARENT);

    /* total free memory */
    wsprintf (szText,
      "%19s          %10luKb",
      "Total free memory: ",
      nTotalFree/ONE_K);
    SetRect (&(ps.rcPaint), ps.rcPaint.left, 0, ps.rcPaint.right, 20);
    ExtTextOut (ps.hdc,
10,
5,
ETO_CLIPPED,
&(ps.rcPaint),
szText,
strlen (szText),
0);

    /* total committed memory */
    wsprintf (szText,
      "%24s     %10luKb",
      "Total committed memory: ",
      nTotalCommit/ONE_K);
    SetRect (&(ps.rcPaint), ps.rcPaint.left, 20, ps.rcPaint.right, 35);
    ExtTextOut (ps.hdc,
10,
20,
ETO_CLIPPED,
&(ps.rcPaint),
szText,
strlen (szText),
0);

    /* total reserved memory */
    wsprintf (szText,
      "%22s       %10luKb",
      "Total reserved memory:",
      nTotalReserve/ONE_K);
    SetRect (&(ps.rcPaint), ps.rcPaint.left, 35, ps.rcPaint.right, 50);
    ExtTextOut (ps.hdc,
10,
35,
ETO_CLIPPED,
&(ps.rcPaint),
szText,
strlen (szText),
0);

    SelectObject (ps.hdc, hOldFont);
    EndPaint (hWnd, &ps);
    }
    break;

case WM_COMMAND:
    switch (LOWORD (wParam))
{
case IDM_PROCESSREWALK:
    {
    /* send message to parent to rewalk process */
    SendMessage (GetParent (hWnd), uMsg, wParam, lParam);

    /* tally new process statistics */
    TallyProcessStats (hWnd, &nTotalFree, &nTotalReserve, &nTotalCommit);

    InvalidateRect (hWnd, NULL, TRUE);
    UpdateWindow (hWnd);
    }
    break;

case IDC_REFRESH:
    InvalidateRect (hWnd, NULL, TRUE);
    UpdateWindow (hWnd);
    break;

case IDC_CANCEL:
    DestroyWindow (hWnd);
    break;
}
    break;

default:
    lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);
}

    return lRet;
}

/* determine process statistics from existing information */
void WINAPI TallyProcessStats (
    HWND    hWnd,
    int     *nTotalFree,
    int     *nTotalReserve,
    int     *nTotalCommit)
{
    LPVMOBJECT  lpVMO = (LPVMOBJECT)lpWalkerList;
    int   nRegions, i;

    *nTotalFree = 0;
    *nTotalCommit = 0;
    *nTotalReserve = 0;

    /* determine number of memory regions by number of entries in listbox */
    nRegions = (int)SendMessage (GetWindow (GetParent (hWnd), GW_CHILD),
 LB_GETCOUNT,
 0,
 0);

    /* get stats for all memory regions in process */
    for (i=0; i<nRegions; i++)
{
if (lpVMO[i].mbi.State & MEM_FREE)
    *nTotalFree += lpVMO[i].mbi.RegionSize;
else if (lpVMO[i].mbi.State & MEM_COMMIT)
    *nTotalCommit += lpVMO[i].mbi.RegionSize;
else
    *nTotalReserve += lpVMO[i].mbi.RegionSize;
}
}

void WINAPI DrawBorders (
    HWND     hWnd,
    LPPAINTSTRUCT    lpps)
{
    POINT   pt;
    HPEN   hOldPen;

    /* draw window hilites for 3D affect */
    hOldPen = SelectObject (lpps->hdc, (HPEN)GetWindowLong (hWnd, WXB_HPENHILITE));
    /* top line across window */
    MoveToEx (lpps->hdc, lpps->rcPaint.left, lpps->rcPaint.top, &pt);
    LineTo (lpps->hdc, lpps->rcPaint.right, lpps->rcPaint.top);
    /* bottom line in status box */
    MoveToEx (lpps->hdc,
      xSTATBOX,
      ySTATBOX+yChar+dyuSTATBOX+dylSTATBOX,
      &pt);
    LineTo (lpps->hdc,
    xSTATBOX+cxSTATBOX,
    ySTATBOX+yChar+dylSTATBOX+dyuSTATBOX);
    /* right line in status box */
    MoveToEx (lpps->hdc, xSTATBOX+cxSTATBOX, ySTATBOX, &pt);
    LineTo (lpps->hdc,
    xSTATBOX+cxSTATBOX,
    ySTATBOX+yChar+dylSTATBOX+dyuSTATBOX+1);

    /* draw window shadows for 3D affect */
    hOldPen = SelectObject (lpps->hdc, (HPEN)GetWindowLong (hWnd, WXB_HPENSHADOW));
    /* top line in status box */
    MoveToEx (lpps->hdc, xSTATBOX, ySTATBOX, &pt);
    LineTo (lpps->hdc, xSTATBOX+cxSTATBOX+1, ySTATBOX);
    /* left line in status box */
    MoveToEx (lpps->hdc, xSTATBOX, ySTATBOX, &pt);
    LineTo (lpps->hdc, xSTATBOX, ySTATBOX+yChar+dylSTATBOX+dyuSTATBOX+1);

    SelectObject (lpps->hdc, hOldPen);
}

void WINAPI DrawStatusText (
    HWND     hWnd,
    LPPAINTSTRUCT    lpps,
    char     *lpszText,
    COLORREF     crText)
{
    HFONT   hOldFont = SelectObject (lpps->hdc, hFont);
    RECT    rcStatus;

    SetBkColor (lpps->hdc, GetSysColor (COLOR_BTNFACE));
    SetTextColor (lpps->hdc, crText);

    SetRect (&rcStatus,
     xSTATBOX+1,
     ySTATBOX+1,
     xSTATBOX+cxSTATBOX,
     ySTATBOX+dylSTATBOX+dyuSTATBOX+yChar);

    ExtTextOut (lpps->hdc,
xSTATBOX+dxSTATBOX,
ySTATBOX+dyuSTATBOX,
ETO_OPAQUE | ETO_CLIPPED,
&rcStatus,
lpszText,
strlen (lpszText),
NULL);

    SelectObject (lpps->hdc, hOldFont);
}

void WINAPI SetStatusText (
    HWNDhWnd,
    int nIDS_STATUS,
    COLORREFcrText)
{
    char    szStatus[MAX_PATH];

    LoadString (GetModuleHandle (NULL), nIDS_STATUS, szStatus, sizeof (szStatus));
    SendMessage (GetDlgItem (hWnd, IDC_STATUSWND), WM_SETTEXT, crText, (LONG)szStatus);
}


PVIEW.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 "pwalk.h"

#define BYTE_WIN_WIDTH    77
#define CLINESSHOW   8
#define ABS(x) ((x) < 0? -(x) : (x))

/* externally defined system constants */
extern int    xChar,
    yChar,
    xScreen,
    yScreen,
    yFrame,
    xFrame,
    yCaption,
    xVScrollBar;
extern HFONT hFont;
extern HWND  hMemWnd;

voidWINAPI VScrollBytes (HWND, int, int, LPMEMVIEW);
voidWINAPI KeyToScrollMsg (HWND, WPARAM);
voidWINAPI DisplayBytes (HDC, LPRECT, LPMEMVIEW, LPMEMVIEW);
voidWINAPI GetBytesLine (LPBYTE, UINT, DWORD, int, char *);
BOOLWINAPI parcmp (LPBYTE, LPBYTE, int, LPBYTE);

HWND   WINAPI EnumViewWindows (
    HWND    hWndParent,
    HWND    hWndLast)
{

    HMENU   hMenu = GetMenu (hWndParent);
    HMENU   hViewMenu = GetSubMenu (hMenu, 2);
    char    szClass[MAX_PATH];
    char    szCaption[MAX_PATH];
    HWND    hWnd;
    int     i;

static  int  nWindows;
static  ATOM  *aMenuItems = NULL;
static  int  iWnd;

    /* load window class name */
    LoadString (GetModuleHandle (NULL), IDS_MEMVIEWCLASS, szClass, MAX_PATH);

    if (hWndLast == NULL)
{
/* start with first window in menu list */
iWnd = 0;

/* determine number of windows */
nWindows = GetMenuItemCount (hViewMenu) - 5;

/* if no popup windows, just return */
if (nWindows <= 0)
    return NULL;

/* free memory from last time, if any */
if (aMenuItems != NULL)
    LocalFree ((HANDLE)aMenuItems);

/* get menuitem IDs */
aMenuItems = (ATOM *)LocalAlloc (LPTR, nWindows*sizeof (ATOM));
for (i=5; i-5<nWindows; i++)
    aMenuItems[i-5] = (ATOM)GetMenuItemID (hViewMenu, i);
}

    else
if (iWnd >= nWindows)
    return (NULL);

    GetAtomName (aMenuItems[iWnd], szCaption, MAX_PATH);
    hWnd = FindWindow (szClass, szCaption);
    iWnd++;

    return (hWnd);
}

/* activate view window identified by atom */
void   WINAPI ActivateViewWindow (
    ATOM    aCaption)
{
    char    szClass[MAX_PATH];
    char    szCaption[MAX_PATH];
    HWND    hWnd;

    GetAtomName (aCaption, szCaption, MAX_PATH);
    LoadString (GetModuleHandle (NULL), IDS_MEMVIEWCLASS, szClass, MAX_PATH);
    hWnd = FindWindow (szClass, szCaption);
    BringWindowToTop (hWnd);
}

/* function creates a window for viewing memory */
HWND  WINAPI ViewMemory (
    HWND      hWndParent,
    char      *lpszObject,
    LPVOID    lpMem,
    int       nSize,
    int       nBase)
{
    char      szClass[MAX_PATH];

    /* load resource strings for class and window title */
    LoadString (GetModuleHandle (NULL), IDS_MEMVIEWCLASS, szClass, MAX_PATH);

    /* create memory view window */
    return (CreateWindow (szClass,
  lpszObject,
  WS_POPUPWINDOW | WS_VISIBLE | WS_CAPTION |
  WS_VSCROLL | WS_THICKFRAME | WS_MAXIMIZEBOX,
  nSize,   /* use xposition to pass memory size to window */
  nBase,   /* use yposition to pass base memory location  */
  BYTE_WIN_WIDTH * xChar + xVScrollBar + 2*xFrame + xChar,
  yScreen/2,
  hWndParent,
  NULL,
  GetModuleHandle (NULL),
  lpMem));
}

LONG WINAPI MemWndProc (
    HWND      hWnd,
    UINT      uMsg,
    WPARAM    wParam,
    LPARAM    lParam)
{
    LONG       lRet = 1;
    MEMVIEW    *pmv = (LPMEMVIEW)GetWindowLong (hWnd, WXB_LPMEMVIEW);

    switch (uMsg)
{
case WM_CREATE:
    pmv = (LPMEMVIEW)LocalAlloc (LPTR, sizeof (MEMVIEW));
    SetWindowLong (hWnd, WXB_LPMEMVIEW, (LONG)pmv);
    SetWindowLong (hWnd, WXB_LPOLDMEMVIEW, 0);

    pmv->lpMem = ((LPCREATESTRUCT)lParam)->lpCreateParams;
    pmv->nSize = ((LPCREATESTRUCT)lParam)->x;
    pmv->nBase = ((LPCREATESTRUCT)lParam)->y;
    pmv->nLines = (pmv->nSize+15)/16;
    pmv->nExtraBytes = (pmv->nSize & 0x0000000F);
    pmv->PosV = 0;

    /* reposition window with proper x,y positions */
    MoveWindow (hWnd,
15,
15,
((LPCREATESTRUCT)lParam)->cx,
((LPCREATESTRUCT)lParam)->cy,
TRUE);
            break;

case WM_SIZE:
    pmv->xWin = LOWORD (lParam);
    pmv->yWin = HIWORD (lParam);

    pmv->RangeV = pmv->nLines-pmv->yWin/yChar;

    if (pmv->RangeV < 0)
pmv->RangeV = 0;

    if (pmv->PosV > pmv->RangeV)
pmv->PosV = pmv->RangeV;

    SetScrollRange (hWnd, SB_VERT, 0, pmv->RangeV, FALSE);
    SetScrollPos (hWnd, SB_VERT, pmv->PosV, TRUE);
            break;

case WM_PAINT:
    {
    PAINTSTRUCT    ps;

    BeginPaint (hWnd, &ps);
    DisplayBytes (ps.hdc,
  &(ps.rcPaint),
  pmv,
  (LPMEMVIEW)GetWindowLong (hWnd, WXB_LPOLDMEMVIEW));
    EndPaint (hWnd, &ps);
    }
            break;

case UM_UPDATE:
    InvalidateRect (hWnd, NULL, TRUE);
    UpdateWindow (hWnd);
    break;

        case WM_KEYDOWN:
    KeyToScrollMsg (hWnd, wParam);
            break;

        case WM_VSCROLL:
    VScrollBytes (hWnd, LOWORD (wParam), GetScrollPos (hWnd, SB_VERT), pmv);
            break;

case WM_DESTROY:
    {
    char szCaption[MAX_PATH];
    LPMEMVIEW lpmv;

    /* free virtual memory block  and local memory buffer */
    VirtualFree (pmv->lpMem, 0, MEM_RELEASE);
    LocalFree ((HANDLE)pmv);

    /* remove any old view memory */
    if ((lpmv = (LPMEMVIEW)GetWindowLong (hWnd, WXB_LPOLDMEMVIEW)) != NULL)
{
VirtualFree (lpmv->lpMem, 0, MEM_RELEASE);
LocalFree ((HANDLE)lpmv);
}

    /* send message to parent to remove menuitem */
    GetWindowText (hWnd, szCaption, MAX_PATH);
    SendMessage (GetParent (hWnd), WM_COMMAND, IDM_REMOVEVIEWWND, (LONG)szCaption);
    }
            break;

        default:
    return (DefWindowProc(hWnd, uMsg, wParam, lParam));
            break;
}
    return 0L;
}

void  WINAPI VScrollBytes (
    HWND hWnd,
    int  cmd,
    int  pos,
    LPMEMVIEW lpmv)
{
    int    nScrollInc;

    switch (cmd)
{
        case SB_TOP:
    nScrollInc = -lpmv->PosV;
            break ;

        case SB_BOTTOM:
    nScrollInc = lpmv->RangeV - lpmv->PosV;
            break ;

        case SB_LINEUP:
            nScrollInc = -1;
            break;

        case SB_LINEDOWN:
            nScrollInc = 1;
            break;

        case SB_PAGEDOWN:
    nScrollInc = max (1, lpmv->yWin/yChar);
            break;

        case SB_PAGEUP:
    nScrollInc = min (-1, -(lpmv->yWin/yChar));
            break;

        case SB_THUMBPOSITION:
    nScrollInc = pos - lpmv->PosV;
            break;

case SB_THUMBTRACK:
    nScrollInc = pos - lpmv->PosV;
            break;

        default:
            nScrollInc = 0;
}

    if (nScrollInc > lpmv->RangeV-lpmv->PosV)
nScrollInc = lpmv->RangeV-lpmv->PosV;

    if (nScrollInc < -lpmv->PosV)
nScrollInc = -lpmv->PosV;

    if (nScrollInc)
{
lpmv->PosV += nScrollInc;

if (ABS (nScrollInc) < lpmv->yWin/yChar)
    ScrollWindow (hWnd, 0, -(yChar*nScrollInc), NULL, NULL);
else
    InvalidateRect (hWnd, NULL, TRUE);

SetScrollPos (hWnd, SB_VERT, lpmv->PosV, TRUE);
UpdateWindow(hWnd);
}
}

void  WINAPI KeyToScrollMsg (
    HWND      hWnd,
    WPARAM    wParam)
{
    int       i, nKeys;
    static    struct
{
        WORD wVirtKey;
        int  iMessage;
        WORD wRequest;
}key2scroll [] = {
 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,
 };

    nKeys = sizeof key2scroll / sizeof key2scroll[0];

    for (i=0; i<nKeys; i++)
{
        if (wParam == key2scroll[i].wVirtKey)
    {
            SendMessage (hWnd, key2scroll[i].iMessage, key2scroll[i].wRequest, 0L);
            return;
    }
}
}

void  WINAPI DisplayBytes (
    HDC  hDC,
    RECT *pRect,
    LPMEMVIEW lpmv,
    LPMEMVIEW lpmvOld)
{
    UINT      i, r, iFirst, iLast, y;
    char      szBuf[MAX_PATH], szText[MAX_PATH];
    LPBYTE    pMem = (LPBYTE)lpmv->lpMem;
    LPBYTE    pOldMem;
    BYTE      bRes[16];
    COLORREF  crOldColor = 0;
    SIZE      sz;
    int       cx;

    if (lpmvOld != NULL)
{
pOldMem = (LPBYTE)lpmvOld->lpMem;

/* normalize to beginning of section relative to current memory */
(int)pOldMem += lpmvOld->nBase - lpmv->nBase;
}

    SelectObject (hDC, hFont);
    SetTextColor (hDC, GetSysColor (COLOR_WINDOWTEXT));
    SetBkColor (hDC, GetSysColor (COLOR_WINDOW));

    y = lpmv->PosV;

    iFirst = y + pRect->top/yChar-1;

    if (iFirst == 0xFFFFFFFFL)
iFirst = 0;

    iLast = min (y + pRect->bottom/yChar+1, (UINT)lpmv->nLines-1);
    pMem += iFirst << 4;
    pOldMem += iFirst << 4;

    if (lpmv->nExtraBytes)
        iLast--;

    /* paint complete lines (lines with 16 bytes) */
    GetBytesLine (NULL, 0, 0, 0, NULL);

    y = (iFirst - y) * yChar;
    for (i = iFirst; i<iLast; i++)
{
GetBytesLine (pMem, i, lpmv->nBase, 16, szBuf);

if (lpmvOld != NULL &&
    ((int)pMem-(int)lpmv->lpMem + lpmv->nBase) >= lpmvOld->nBase &&
    ((int)pMem-(int)lpmv->lpMem + lpmv->nBase) < (lpmvOld->nBase + lpmvOld->nSize) &&
    !parcmp (pMem, pOldMem, 16, bRes))
    {
    /* write line number and leading space */
    strncpy (szText, szBuf, 12);
    szText[12] = 0;
    GetTextExtentPoint (hDC, szText, 12, &sz);
    cx = xChar;
    SetTextColor (hDC, GetSysColor (COLOR_HIGHLIGHTTEXT));
    TextOut (hDC, cx, LOWORD (y), szText, 12);

    /* check each hex byte for change */
    for (r=0; r<16; r++)
{
if (bRes[r])
    SetTextColor (hDC, RGB (191, 0, 0));
else
    SetTextColor (hDC, GetSysColor(COLOR_WINDOWTEXT));

/* draw next byte */
szText[0] = szBuf[3*r+12];
szText[1] = szBuf[3*r+1+12];
szText[2] = szBuf[3*r+2+12];
szText[3] = 0;
cx += sz.cx;
GetTextExtentPoint (hDC, szText, 3, &sz);
TextOut (hDC, cx, LOWORD (y), szText, 3);
}

    /* check each ascii byte */
    for (r=0; r<16; r++)
{
if (bRes[r])
    SetTextColor (hDC, RGB (191, 0, 0));
else
    SetTextColor (hDC, GetSysColor(COLOR_WINDOWTEXT));

/* draw next byte */
szText[0] = szBuf[r+60];
szText[1] = 0;
cx += sz.cx;
GetTextExtentPoint (hDC, szText, 1, &sz);
TextOut (hDC, cx, LOWORD (y), szText, 3);
}
    }

else
    {
    SetTextColor (hDC, GetSysColor(COLOR_WINDOWTEXT));
    TextOut (hDC, xChar, LOWORD (y), szBuf, lstrlen (szBuf));
    }

pMem += 16;
pOldMem += 16;
y += yChar;
}

    /* paint last partial line if any (line with less than 16 bytes) */
    if (lpmv->nExtraBytes)
{
GetBytesLine (pMem, i, lpmv->nBase, lpmv->nExtraBytes, szBuf);

if (lpmvOld != NULL &&
    ((int)pMem-(int)lpmv->lpMem + lpmv->nBase) >= lpmvOld->nBase &&
    ((int)pMem-(int)lpmv->lpMem + lpmv->nBase) < (lpmvOld->nBase + lpmvOld->nSize) &&
    !parcmp (pMem, pOldMem, lpmv->nExtraBytes, bRes))
    {
    /* write line number and leading space */
    strncpy (szText, szBuf, 12);
    szText[12] = 0;
    GetTextExtentPoint (hDC, szText, 12, &sz);
    cx = xChar;
    SetTextColor (hDC, RGB (191, 0, 0));
    TextOut (hDC, cx, LOWORD (y), szText, 12);

    /* check each hex byte for change */
    for (r=0; r<(UINT)lpmv->nExtraBytes; r++)
{
if (bRes[r])
    SetTextColor (hDC, RGB (191, 0, 0));
else
    SetTextColor (hDC, GetSysColor(COLOR_WINDOWTEXT));

/* draw next byte */
szText[0] = szBuf[3*r+12];
szText[1] = szBuf[3*r+1+12];
szText[2] = szBuf[3*r+2+12];
szText[3] = 0;
cx += sz.cx;
GetTextExtentPoint (hDC, szText, 3, &sz);
TextOut (hDC, cx, LOWORD (y), szText, 3);
}

    /* check each ascii byte */
    for (r=0; r<(UINT)lpmv->nExtraBytes; r++)
{
if (bRes[r])
    SetTextColor (hDC, RGB (191, 0, 0));
else
    SetTextColor (hDC, GetSysColor(COLOR_WINDOWTEXT));

/* draw next byte */
szText[0] = szBuf[r+60];
szText[1] = 0;
cx += sz.cx;
GetTextExtentPoint (hDC, szText, 1, &sz);
TextOut (hDC, cx, LOWORD (y), szText, 1);
}
    }

else
    {
    SetTextColor (hDC, GetSysColor(COLOR_WINDOWTEXT));
    TextOut(hDC, xChar, y, szBuf, lstrlen (szBuf));
    }
}

    SelectObject(hDC, GetStockObject(SYSTEM_FONT));
}

void  WINAPI GetBytesLine (
    LPBYTE   pBytes,
    UINT     LineNum,
    DWORD    dwBase,
    int      nBytes,
    char     *szLine)
{
    int      j;
    unsigned char    ch;
    char     szAscii[18];
    static BOOL      bDBCS;

    if (NULL == pBytes) {
bDBCS = FALSE;
return;
    }

    wsprintf (szLine, "%#08lx  ", dwBase + (LineNum*16));

    for (j = 0; j < nBytes; j++)
{
ch = *pBytes++;
wsprintf (szLine, "%s%02X ", (LPSTR)szLine, (WORD)ch);

if (bDBCS)
    {
    szAscii[j] = (0 == j) ? '.' : ch;
    bDBCS = FALSE;
    }
else if (IsDBCSLeadByte(ch))
    {
    szAscii[j] = ch;
    bDBCS = TRUE;
    }
else
    {
    szAscii[j] = ((ch & 0x7F) >= ' ') ? ch : '.';
    }
}

    if (bDBCS && 16 == nBytes)
szAscii[j++] = *pBytes;
    szAscii[j] = 0;
    wsprintf(szLine, "%-59s%s", (LPSTR)szLine, (LPSTR)szAscii);
}

BOOLWINAPI parcmp (
    LPBYTE    pMem,
    LPBYTE    pOldMem,
    int       nWidth,
    LPBYTE    pRes)
{
    BOOL    bResult = TRUE;
    int     i;

    /* clear result array */
    for (i=0; i<16; i++)
pRes[i] = 0;

    for (i=0; i<nWidth; i++)
{
if (pMem[i] ^ pOldMem[i])
    {
    pRes[i] = 1;
    bResult = FALSE;
    }
}

    return bResult;
}

BOOL WINAPI AddrDlgProc (
    HWND      hDlg,
    UINT      uMsg,
    WPARAM    wParam,
    LPARAM    lParam)
{
    BOOL    bRet = TRUE;
    char    szAddr[MAX_PATH];
    int     nAddr;
    HWND    hAddr = GetDlgItem (hDlg, IDC_ADDR);

    switch (uMsg)
{
case WM_INITDIALOG:
    SetFocus (hAddr);
    bRet = FALSE;
    break;

case WM_COMMAND:
    switch (LOWORD (wParam))
{
case IDC_HEX:
    if (HIWORD (wParam) == BN_CLICKED)
{
GetWindowText (hAddr, szAddr, MAX_PATH);

if (SendMessage (GetDlgItem (hDlg, IDC_HEX), BM_GETCHECK, 0, 0))
    {
    /* convert to hex and return to control */
    nAddr = atoi (szAddr);
    wsprintf (szAddr, "%16x", nAddr);
    SetWindowText (hAddr, szAddr);
    }
else
    {
    /* convert to decimal and return to control */
    sscanf (szAddr, "%16x", &nAddr);
    SetWindowText (hAddr, itoa (nAddr, szAddr, 10));
    }

/* select entire string for easy change */
SendMessage (hAddr, EM_SETSEL, 0, (LONG)-1);
SetFocus (hAddr);
}
    break;

case IDOK:
    GetWindowText (hAddr, szAddr, MAX_PATH);

    /* if hex, convert to decimal integer */
    if (SendMessage (GetDlgItem (hDlg, IDC_HEX), BM_GETCHECK, 0, 0))
sscanf (szAddr, "%16x", &nAddr);
    else
nAddr = atoi (szAddr);

    EndDialog (hDlg, nAddr);
    break;

case IDCANCEL:
    EndDialog (hDlg, 0);
    break;
}
    break;

default:
    bRet = FALSE;
}

    return (bRet);
}


PWALK.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.
*
*
*  Note:  pwalk will only work correctly with Win32 applications.
*
*
\******************************************************************************/

#include "pwalk.h"

#define strtok      My_mbstok

#define MAX_DRIVES    26    /* maximum number of logical drives */

/* system constants used externally */
int     xChar,
     yChar,
     xScreen,
     yScreen,
     yFrame,
     xFrame,
     yCaption,
     xVScrollBar;

char     szCaptionText[] ="Address\0State\0Prot\0Size\0BaseAddr\0Object\0Section\0Name\0";
char     szFormat[] = "%08lX~%s ~%s ~%lu~%08lX~%s ~%s ~%s ~";
SIZE                 sChar0;
char     szFormatPages[] = "%05lX~%s ~%s ~%lu~%05lX~%s ~%s ~%s ~";
int                  taColumns[] = {TA_RIGHT,  TA_LEFT, TA_LEFT, TA_RIGHT,
                                    TA_RIGHT, TA_RIGHT, TA_LEFT,  TA_LEFT,
                                   };
int                  xColumns[]  = {      8,       9,      17,       31,
                                         40,      46,      47,       55,
                                   };
BOOL     bNumbersAsBytes = TRUE;
HFONT     hFontVar;

char     szFilePath[MAX_PATH] = "";
char     szFilename[MAX_PATH] = "";
HFONT     hFont;
LPVOID     lpWalkerList = NULL;
int     *Objects;
int     nSortType = IDM_SORTADDRESS;
HWND     hWndSysStat, hWndProStat, hInitDlg, hMemWnd;
HANDLE     hChildEvents[nDEBUGEVENTS];
DBGPROCESS     *lpChildProcess = NULL;
HMENU     hPopup[MENUPOPUPS];
char     szCurPath[MAX_PATH];

/* local functions */
BOOL   WINAPI InitEnvironment (HANDLE, int, char *);
void   WINAPI InitMenu (HWND);
void   WINAPI DrawListItem (DRAWITEMSTRUCT *);
int    WINAPI MakeVMQString (int, char *);
DWORD  WINAPI VMExceptionFilter (EXCEPTION_POINTERS *);
void   WINAPI SortList (HWND, int);
BOOL   WINAPI ViewableMemorySelection (HWND);
static void TextOutFields (HDC, int, LPRECT, LPSTR);

/****************************************************************************
    My_mbschr:  strchr() DBCS version
****************************************************************************/
unsigned char * _CRTAPI1 My_mbschr(
    unsigned char *psz, unsigned short uiSep)
{
    while (*psz != '\0' && *psz != uiSep) {
        psz = CharNext(psz);
    }
    if (*psz == '\0' && uiSep != '\0') {
        return NULL;
    } else {
        return psz;
    }
}
/****************************************************************************
    My_mbstok:  strtok() DBCS version
****************************************************************************/
unsigned char * _CRTAPI1 My_mbstok(
    unsigned char *pszSrc, unsigned char *pszSep)
{
    static char *pszSave = NULL;
    char *pszHead;
    char *psz;

    if (pszSrc == NULL) {
        if (pszSave == NULL) {
            return NULL;
        } else {
            psz = pszSave;
        }
    } else {
        psz = pszSrc;
    }

    /*********************************************/
    /* Skip delimiters to find a head of a token */
    /*********************************************/
    while (*psz) {
        if (IsDBCSLeadByte(*psz)) {
            break;
        } else if (NULL == My_mbschr(pszSep, *psz)) {
            break;
        }
        psz++;
    }
    if (*psz == '\0') {
        //No more token
        return (pszSave = NULL);
    }
    pszHead = psz;

    /******************************/
    /* Search a Tail of the token */
    /******************************/
    while (*psz) {
        if (IsDBCSLeadByte(*psz)) {
            psz += 2;
            continue;
        } else if (NULL != My_mbschr(pszSep, *psz)) {
            break;
        }
        psz++;
    }
    if (*psz == '\0') {
        pszSave = NULL;
    } else {
        //Found next delimiter
        pszSave = psz + 1;
        *psz = '\0';
    }
    return pszHead;
}

/* entry point of this executable */
int WINAPI WinMain (hInstance, hPrevInstance, lpCmdLine, nCmdShow)
    HINSTANCE hInstance;
    HINSTANCE hPrevInstance;
    LPSTR     lpCmdLine;
    int       nCmdShow;
{
    MSG      msg;
    char     *lpszCmdLine = NULL;
    char     *lpCL;
    BOOL     bSwitch;

    /* set current path for use later */
    GetCurrentDirectory (MAX_PATH, szCurPath);

    // parse and copy command line parameters to local memory
    lpCL = GetCommandLine ();
    if (lpszCmdLine = (char *)LocalAlloc (LPTR, strlen (lpCL) + 1))
GetCmdLine (lpCL, lpszCmdLine, &bSwitch);

    /* start window environment */
    if (!InitEnvironment (hInstance, nCmdShow, IsValidFile (lpszCmdLine) ? lpszCmdLine : NULL))
return FALSE;

    /* free memory allocated for lpCmdLine */
    if (lpszCmdLine)
LocalFree ((HLOCAL)lpszCmdLine);

    /* main window message loop */
    while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}

    /* return success of application */
    return TRUE;
}

/*  start app */
BOOL WINAPI InitEnvironment (
    HANDLE    hInstance,
    int       nCmdShow,
    char      *lpszCmdLine)
    {
    WNDCLASS   wc;
    char       szClass[MAX_PATH];
    char       szTitle[MAX_PATH];
    char       szFilename[MAX_PATH];
    HWND       hWnd;

    /* register system statistics window class */
    LoadString (hInstance, IDS_SYSSTATCLASS, szClass, sizeof (szClass));
    wc.style     = 0;
    wc.lpfnWndProc   = (WNDPROC)SysStatWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon     = LoadIcon (hInstance, MAKEINTRESOURCE (IDR_SYSSTATICON));
    wc.hCursor     = LoadCursor (0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szClass;
    if (!RegisterClass (&wc) )
return FALSE;

    /* register process statistics window class */
    LoadString (hInstance, IDS_PROSTATCLASS, szClass, sizeof (szClass));
    wc.style     = 0;
    wc.lpfnWndProc   = (WNDPROC)ProStatWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon     = LoadIcon (hInstance, MAKEINTRESOURCE (IDR_PROSTATICON));
    wc.hCursor     = LoadCursor (0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szClass;
    if (!RegisterClass (&wc) )
return FALSE;

    /* register the status bar window class */
    LoadString (hInstance, IDS_STATUSCLASS, szClass, sizeof (szClass));
    wc.style     = 0;
    wc.lpfnWndProc   = (WNDPROC)StatusWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = STATUSWXB;
    wc.hInstance     = hInstance;
    wc.hIcon     = (HICON)NULL;
    wc.hCursor     = LoadCursor (0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szClass;
    if (!RegisterClass (&wc) )
return FALSE;

    /* register the main frame window class */
    LoadString (hInstance, IDS_MEMVIEWCLASS, szClass, sizeof (szClass));
    wc.style     = 0;
    wc.lpfnWndProc   = (WNDPROC)MemWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = VIEWWXB;
    wc.hInstance     = hInstance;
    wc.hIcon     = LoadIcon (hInstance, MAKEINTRESOURCE (IDR_MAINICON));
    wc.hCursor     = LoadCursor (0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szClass;
    if (!RegisterClass (&wc) )
return FALSE;

    /* register the main frame window class */
    LoadString (hInstance, IDS_WALKERCLASS, szClass, sizeof (szClass));
    wc.style     = 0;
    wc.lpfnWndProc   = (WNDPROC)WalkerWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon     = LoadIcon (hInstance, MAKEINTRESOURCE (IDR_MAINICON));
    wc.hCursor     = LoadCursor (0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = MAKEINTRESOURCE (IDR_WALKERMENU);
    wc.lpszClassName = szClass;
    if (!RegisterClass (&wc) )
return FALSE;

    /* create window caption */
    LoadString (hInstance, IDS_CAPTION, szTitle, sizeof (szTitle));
    if (lpszCmdLine != NULL &&
((lpChildProcess = StartChildProcess (NULL, lpszCmdLine, hChildEvents)) != NULL))
GetFileFromPath (lpszCmdLine, szFilename);
    else
LoadString (hInstance, IDS_SELF, szFilename, MAX_PATH);
    strcat (szTitle, szFilename);

    /* create main frame window */
    hWnd = CreateWindow (szClass,
 szTitle,
 WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
 CW_USEDEFAULT,
 0,
 CW_USEDEFAULT,
 0,
 NULL,
 NULL,
 hInstance,
 NULL);

    /* update parent window handle in child process information structure */
    if (lpChildProcess != NULL)
lpChildProcess->hWnd = hWnd;

    if (!hWnd)
return 0;

    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
    return TRUE;
}

/* main window procedure */
LONG WINAPI WalkerWndProc (
    HWND    hWnd,
    UINT    uMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
       LONG    lRet = 1;

static LOGFONT lf_Font = {
 -10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET,
 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
 DEFAULT_QUALITY, FIXED_PITCH | FF_DONTCARE,
                         "Courier",
 };
static LOGFONT lf_FontVar = {
   -10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET,
 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
 DEFAULT_QUALITY, 0, // FIXED_PITCH | FF_DONTCARE,
                         "MS Sans Serif",
 };

    /* On a Japanese system, change the fonts...  should generalize to all FE */
    if (PRIMARYLANGID(GetUserDefaultLangID ()) == LANG_JAPANESE) {
        lf_Font.lfCharSet = SHIFTJIS_CHARSET;
        lstrcpy (lf_Font.lfFaceName , "Terminal");

        lf_FontVar.lfHeight= -12;
        lf_FontVar.lfCharSet = SHIFTJIS_CHARSET;
        lstrcpy (lf_Font.lfFaceName , " l r  S V b N");
    }

    switch (uMsg)
{
/* initialize menu before drawing */
case WM_INITMENU:
    InitMenu (hWnd);
    break;

/* display status messages for menu commands */
case WM_MENUSELECT:
    {
    char     *lpszMenuString;

    lpszMenuString = LocalAlloc (LPTR, MAX_PATH);

    if (HIWORD (wParam) ==  0xFFFF)
{
LocalFree (lpszMenuString);
lpszMenuString = NULL;
}

    else if (HIWORD (wParam) & MF_POPUP)
{
int i;
HMENU hPopupMenu = GetSubMenu ((HMENU)lParam, LOWORD (wParam));

lpszMenuString = LocalAlloc (LPTR, MAX_PATH);

for (i=0; i<MENUPOPUPS; i++)
    {
    if (hPopup[i] == hPopupMenu)
{
LoadString (GetModuleHandle (NULL),
    i+IDM_POPUPMENUS,
    lpszMenuString,
    MAX_PATH);
break;
}
    }
}
    else
LoadString (GetModuleHandle (NULL),
    LOWORD (wParam),
    lpszMenuString,
    MAX_PATH);

    /* send the status string, gray if necessary */
    SendMessage (GetDlgItem (hWnd, IDC_STATUSWND),
 WM_SETTEXT,
 (HIWORD (wParam) & MF_GRAYED) ?
     (LPARAM)GetSysColor (COLOR_GRAYTEXT):
     0,
 (WPARAM)lpszMenuString);

    LocalFree (lpszMenuString);
    }
    break;

case WM_CREATE:
    {
    HCURSOR  hOldCursor;
    HDC   hDC;
    TEXTMETRIC  tm;
    char  szWndClass[MAX_PATH];
    HWND  hList, hStatus;
    int   i=0, j, k;
    HMENU  hMenu;

    /* build array of popup menu handles */
    hMenu = GetMenu (hWnd);
    for (k=0; k<MENUPOPUPS-(i-1); k++)
{
hPopup[i] = GetSubMenu (hMenu, k);
j=0;
while ((hPopup[i+j+1] = GetSubMenu (hPopup[i], j)) != NULL)
    j++;
if (j)
    i+=j;
i++;
}

    /* put hourglass cursor up */
    hOldCursor = (HCURSOR)SetClassLong (hWnd, GCL_HCURSOR, 0);
    SetCursor (LoadCursor (0, IDC_WAIT));

    hDC = GetDC(hWnd);

    /* want a font with point size of 10 (smallest size it comes in) */
    lf_Font.lfHeight = -(10 * GetDeviceCaps(hDC, LOGPIXELSY)/72);
    hFont = CreateFontIndirect(&lf_Font);
    hFontVar = CreateFontIndirect(&lf_FontVar);

            // find the width of a '0' in the variable font
            //
            SelectObject(hDC, hFontVar);
            GetTextExtentPoint (hDC, "0", 1, &sChar0);

            SelectObject(hDC, hFont);

    /* initialize system constants */
    GetTextMetrics(hDC, &tm);
    yChar = tm.tmHeight;
    xChar = tm.tmAveCharWidth;
    xScreen = GetSystemMetrics(SM_CXSCREEN);
    yScreen = GetSystemMetrics(SM_CYSCREEN);
    yFrame = GetSystemMetrics(SM_CYFRAME);
    xFrame = GetSystemMetrics(SM_CXFRAME);
    yCaption = GetSystemMetrics(SM_CYCAPTION);
    xVScrollBar = GetSystemMetrics(SM_CXVSCROLL);

    //Actually, it's not good that a width of each column
    // depends on a width of "0"!!
    if (xChar > sChar0.cx) {
//sChar0.cx = xChar;
sChar0.cx = sChar0.cx * 12 / 10;//sChar0.cx *= 1.2
    }

            SelectObject(hDC, GetStockObject(SYSTEM_FONT));
            ReleaseDC(hWnd, hDC);

    /* create listbox for client area */
    LoadString (GetModuleHandle (NULL),
IDS_LISTBOX,
szWndClass,
sizeof (szWndClass));
    hList = CreateWindow (szWndClass,
  NULL,
  WS_CHILD | WS_VISIBLE | WS_VSCROLL |
  LBS_EXTENDEDSEL | LBS_NOTIFY | LBS_OWNERDRAWFIXED |
  LBS_WANTKEYBOARDINPUT | LBS_NOINTEGRALHEIGHT,
  0, 0, 0, 0,
  hWnd,
  (HMENU)IDC_LISTBOX,
  GetModuleHandle (NULL),
  NULL);

    /* if listbox failed, abort app */
    if (!IsWindow (hList))
DestroyWindow(hWnd);
    SendMessage (hList, WM_SETFONT, (WPARAM)hFontVar, 0L);

    /* create status window for client area */
    LoadString (GetModuleHandle (NULL),
IDS_STATUSCLASS,
szWndClass,
sizeof (szWndClass));
    if (!(hStatus = CreateWindow (szWndClass,
  NULL,
  WS_CHILD | WS_VISIBLE | WS_BORDER,
  0, 0, 0, 0,
  hWnd,
  (HMENU)IDC_STATUSWND,
  GetModuleHandle (NULL),
  NULL)))
ReportError (IDS_ERRCREATEWINDOW);

    /* initialize status window */
    SendMessage (GetDlgItem (hWnd, IDC_STATUSWND),
 UM_UPDATE,
 (WPARAM)lpChildProcess,
 0);

    /* if child process post message to display initialization dialog */
    if (lpChildProcess != NULL)
PostMessage (hWnd, UM_STARTINITDIALOG, 0, 0);

    /* remove hourglass cursor */
    SetClassLong (hWnd, GCL_HCURSOR, (LONG)hOldCursor);
    SetCursor (hOldCursor);
    }
    break;

case UM_STARTINITDIALOG:
    /* start modal initializing information window */
    DialogBoxParam (GetModuleHandle (NULL),
    (char *)IDD_INITIALIZING,
    hWnd,
    InitDlgProc,
    (LPARAM)&hInitDlg);
    hInitDlg = NULL;
    break;

case WM_SETFOCUS:
    /* keep focus in listbox when possible */
    SetFocus (GetDlgItem (hWnd, IDC_LISTBOX));
    break;

case WM_MEASUREITEM:
            ((MEASUREITEMSTRUCT FAR *)lParam)->itemHeight = sChar0.cy;
            break;

case WM_DRAWITEM:
    DrawListItem ((DRAWITEMSTRUCT FAR *)lParam);
            break;

case WM_PAINT:
    {
    PAINTSTRUCT ps;
    RECT rc;

    /* draw the caption line above the list box */
    BeginPaint(hWnd, &ps);
    SetRect(&rc, 0, 0, GetSystemMetrics (SM_CXSCREEN), sChar0.cy);

    SelectObject(ps.hdc, hFontVar);
    SetTextColor(ps.hdc, GetSysColor(COLOR_CAPTIONTEXT));
    SetBkColor(ps.hdc, GetSysColor(COLOR_ACTIVECAPTION));
            TextOutFields (ps.hdc, 6, &rc, szCaptionText);
    SelectObject(ps.hdc, GetStockObject(SYSTEM_FONT));
    EndPaint(hWnd, &ps);
    }
    break;

case WM_SIZE:
    /* size listbox and status bar */
    if ((wParam == SIZE_RESTORED) || (wParam == SIZE_MAXIMIZED))
{
int    yBorder = GetSystemMetrics (SM_CYBORDER);
int    xBorder = GetSystemMetrics (SM_CXBORDER);
int    yStatus = yChar + 10*yBorder;

/* size listbox */
MoveWindow(GetDlgItem (hWnd, IDC_LISTBOX),
   0,
   sChar0.cy,
   LOWORD(lParam),
   HIWORD(lParam)-(sChar0.cy + yStatus - yBorder),
   TRUE);

/* size status bar */
MoveWindow(GetDlgItem (hWnd, IDC_STATUSWND),
   0-xBorder,
   HIWORD(lParam)-yStatus+yBorder,
   LOWORD(lParam) + 2*xBorder,
   yStatus,
   TRUE);
}
    break;

case WM_COMMAND:
    {
    switch (LOWORD (wParam))
{
case IDM_EXIT:
    SendMessage (hWnd, WM_CLOSE, 0, 0);
    break;

case IDM_PROCESSUNLOAD:
    {
    char    szFilename[MAX_PATH];
    char    szTitle[MAX_PATH];
    HWND    hViewWnd = NULL;

    /* close child process */
    CloseChildProcess (lpChildProcess, hChildEvents);
    lpChildProcess = NULL;
    SendMessage (GetDlgItem (hWnd, IDC_LISTBOX), LB_RESETCONTENT, 0, 0);
    SendMessage (GetDlgItem (hWnd, IDC_STATUSWND),
 UM_UPDATE,
 0,
 0);

    /* reset caption */
    LoadString (GetModuleHandle (NULL),
IDS_CAPTION,
szTitle,
MAX_PATH);
    LoadString (GetModuleHandle (NULL),
IDS_SELF,
szFilename,
MAX_PATH);
    strcat (szTitle, szFilename);
    SetWindowText (hWnd, szTitle);

    if (IsWindow (hWndSysStat))
{
InvalidateRect (hWndSysStat, NULL, TRUE);
UpdateWindow (hWndSysStat);
}

    if (IsWindow (hWndProStat))
DestroyWindow (hWndProStat);

    while ((hViewWnd = EnumViewWindows (hWnd, hViewWnd)) != NULL)
DestroyWindow (hViewWnd);
    }
    break;

case IDM_PROCESSLOAD:
    {
    char      szTitle[MAX_PATH];
    char      szFilePath[MAX_PATH];
    HWND      hViewWnd = NULL;

    /* detaching from old process, okay?? */
    if (lpChildProcess != NULL)
{
strcpy (szTitle, "Detach from process ");
strcat (szTitle, lpChildProcess->szModule);
strcat (szTitle, "?");

LoadString (GetModuleHandle (NULL),
    IDS_WALKERCLASS,
    szFilePath,
    MAX_PATH);
if (IDYES != MessageBox (hWnd,
 szTitle,
 szFilePath,
 MB_YESNO | MB_ICONQUESTION))
    break;
}

    /* call open file dialog to get filename of exe, and validate */
    *szFilePath = 0;
    if (GetFileName (hWnd, szFilePath, NULL))
{
if (IsValidFile (szFilePath))
    {
    if (lpChildProcess != NULL)
{
/* close any open view windowsfor this process */
while ((hViewWnd = EnumViewWindows (hWnd, hViewWnd)) != NULL)
    DestroyWindow (hViewWnd);

CloseChildProcess (lpChildProcess, hChildEvents);
SendMessage (GetDlgItem (hWnd, IDC_LISTBOX),
     LB_RESETCONTENT,
     0,
     0);
SendMessage (GetDlgItem (hWnd, IDC_STATUSWND),
     UM_UPDATE,
     0,
     0);
}

    if ((lpChildProcess =
 StartChildProcess (hWnd, szFilePath, hChildEvents)) != NULL)
{
/* force rewalk of process */
PostMessage (hWnd, UM_STARTINITDIALOG, 0, 0);
SendMessage (GetDlgItem (hWnd, IDC_STATUSWND),
     UM_UPDATE,
     (WPARAM)lpChildProcess,
     0);

/* load new window caption */
LoadString (GetModuleHandle (NULL),
    IDS_CAPTION,
    szTitle,
    MAX_PATH);

GetFileFromPath (szFilePath, szFilename);
strcat (szTitle, szFilename);
SetWindowText (hWnd, szTitle);
}
    }
}
    }
    break;

case IDM_PROCESSREWALK:
    {
    HWND      hList = GetDlgItem (hWnd, IDC_LISTBOX);
    int       nCnt, nNewCnt, i;
    LPVOID    lpNewList=NULL, lpTempList=NULL;
    HWND      hViewWnd = NULL;

    /* clear listbox of current contents, but first find out how many exist */
    nCnt = SendMessage (hList, LB_GETCOUNT, 0, 0);
    SendMessage (hList, WM_SETREDRAW, 0, 0);
    SendMessage (hList, LB_RESETCONTENT, 0, 0);

    /* walk process address space */
    if (lpChildProcess != NULL)
{
nNewCnt = WalkProcess (lpChildProcess->hProcess, &lpNewList, &Objects);
AnalyzeProcess (lpChildProcess, (LPVMOBJECT)lpNewList, nNewCnt);

/* indentify which objects are new */
if (nCnt)
    IdentifyNewObjects (lpWalkerList, nCnt, lpNewList, nNewCnt);

/* free old list and update cnt */
lpTempList = lpWalkerList;
lpWalkerList = lpNewList;
VirtualFree (lpTempList, TOTALVMRESERVE, MEM_DECOMMIT);
VirtualFree (lpTempList, 0, MEM_RELEASE);
nCnt = nNewCnt;
}

    for (i=0; i<nCnt; i++)
SendMessage (hList, LB_ADDSTRING, 0, i);

    /* sort if other than by address is selected */
    if (nSortType != IDM_SORTADDRESS)
SortList (hList, nSortType);

    /* reenable redraw of listbox */
    SendMessage (hList, WM_SETREDRAW, 1, 0);
    InvalidateRect (hList, NULL, TRUE);
    UpdateWindow (hList);

    /* if any memory view windows, send update message */
    while ((hViewWnd = EnumViewWindows (hWnd, hViewWnd)) != NULL)
{
LPMEMVIEW    pmv, pmvOld;
int    nAddress, nSize;
MEMORY_BASIC_INFORMATION    mbi;
char    *szCaption;

/* retrieve view memory range */
szCaption = HeapAlloc (GetProcessHeap (), HEAP_ZERO_MEMORY, MAX_PATH);
GetWindowText (hViewWnd, szCaption, MAX_PATH);

/* validate range */
sscanf (strtok (szCaption, "-"), "%8x", &nAddress);
sscanf (strtok (NULL, " \0"), "%8x", &nSize);
nSize -= nAddress;
VirtualQueryEx (lpChildProcess->hProcess,
(LPVOID)nAddress,
&mbi,
sizeof (MEMORY_BASIC_INFORMATION));

if (mbi.State != MEM_COMMIT)
    {
    NotifyUser (hWnd, IDS_ERROR, IDS_NOTCOMMITTEDMEMORY, NULL, 0);
    DestroyWindow (hViewWnd);
    goto NOT;
    }

/* if size of committed region changed, update caption */
if (mbi.RegionSize != (DWORD)nSize)
    {
    wsprintf (szCaption,
      "%4lx-%-4lx",
      (DWORD)mbi.BaseAddress,
      (DWORD)mbi.BaseAddress+mbi.RegionSize);
    SetWindowText (hViewWnd, szCaption);
    }

/* free default heap memory */
HeapFree (GetProcessHeap (), 0, szCaption);

/* if an old view structure existed, release virtual memory */
if ((pmvOld = (LPMEMVIEW)GetWindowLong (hViewWnd, WXB_LPOLDMEMVIEW)) != NULL)
    VirtualFree (pmvOld->lpMem, 0, MEM_RELEASE);

pmvOld = (LPMEMVIEW)GetWindowLong (hViewWnd, WXB_LPMEMVIEW);
/* save past pmv for update comparison */
SetWindowLong (hViewWnd,
       WXB_LPOLDMEMVIEW,
       (LONG)pmvOld);

/* allocate memory structure for view memory object */
pmv = (LPMEMVIEW)LocalAlloc (LPTR, sizeof (MEMVIEW));

/* copy old mem view to new mem view */
for (i=0; i<sizeof (MEMVIEW); i++)
    ((LPBYTE)pmv)[i] = ((LPBYTE)pmvOld)[i];

/* update structure for new mem structure */
pmv->nBase = (int)mbi.BaseAddress;
pmv->nSize = (int)mbi.RegionSize;

if ((pmv->lpMem = VirtualAlloc (NULL, pmv->nSize, MEM_COMMIT, PAGE_READWRITE)) == NULL)
    {
    ReportError (IDS_ERRVIRTUALALLOC);
    DestroyWindow (hViewWnd);
    }

else if (AccessProcessMemory (hChildEvents[READMEMORY],
      hChildEvents[ACKNOWLEDGE],
      (LPVOID)nAddress,
      pmv->lpMem,
      &(pmv->nSize))  && pmv->nSize)
    {
    pmv->nLines = (pmv->nSize+15)/16;
    pmv->nExtraBytes = (pmv->nSize & 0x0000000F);
    SetWindowLong (hViewWnd, WXB_LPMEMVIEW, (LONG)pmv);

    /* post message to view window to update */
    PostMessage (hViewWnd, UM_UPDATE, 0, 0);
    }

else
    {
    NotifyUser (hWnd, IDS_ERROR, IDS_COULDNOTREADPROCESS, NULL, 0);
    DestroyWindow (hViewWnd);
    }

}

NOT:
    /* if initialization dialog, send notification to remove */
    if (IsWindow (hInitDlg))
PostMessage (hInitDlg, UM_ENDDIALOG, 0, 0);
    }
    break;

case IDM_PROCESSSUSPEND:
    SetEvent (hChildEvents[SUSPENDDEBUGGER]);
    break;

case IDM_PROCESSRESUME:
    SetEvent (hChildEvents[RESUMEDEBUGGER]);
    break;

case IDM_VIEWSYSSTAT:
    /* if window exists, destroy it */
    if (IsWindow (hWndSysStat))
{
DestroyWindow (hWndSysStat);
CheckMenuItem (GetMenu (hWnd), wParam, MF_UNCHECKED);
}
    else
{
charszClass[100];
charszTitle[100];
RECTrc;

GetWindowRect (hWnd, &rc);
LoadString (GetModuleHandle (NULL), IDS_SYSSTATCLASS, szClass, 100);
LoadString (GetModuleHandle (NULL), IDS_SYSSTATTITLE, szTitle, 100);
hWndSysStat = CreateWindow (szClass,
    szTitle,
    WS_POPUP | WS_CAPTION | WS_MINIMIZEBOX |
    WS_SYSMENU | WS_DLGFRAME | WS_VISIBLE,
    rc.left+50, rc.top+50, 500, 270,
    hWnd,
    NULL,
    GetModuleHandle (NULL),
    NULL);
UpdateWindow (hWndSysStat);
ShowWindow (hWndSysStat, SW_SHOWNORMAL);
CheckMenuItem (GetMenu (hWnd), wParam, MF_CHECKED);
}
    break;

case IDM_VIEWPROSTAT:
    /* if window exists, destroy it */
    if (IsWindow (hWndProStat))
{
DestroyWindow (hWndProStat);
CheckMenuItem (GetMenu (hWnd), wParam, MF_UNCHECKED);
}
    else
{
charszClass[100];
charszTitle[100];
RECTrc;

GetWindowRect (hWnd, &rc);
LoadString (GetModuleHandle (NULL), IDS_PROSTATCLASS, szClass, 100);
LoadString (GetModuleHandle (NULL), IDS_PROSTATTITLE, szTitle, 100);
hWndProStat = CreateWindow (szClass,
    szTitle,
    WS_POPUP | WS_CAPTION | WS_MINIMIZEBOX |
    WS_SYSMENU | WS_DLGFRAME | WS_VISIBLE,
    rc.left+75, rc.top+75, 355, 120,
    hWnd,
    NULL,
    GetModuleHandle (NULL),
    NULL);
UpdateWindow (hWndProStat);
ShowWindow (hWndProStat, SW_SHOWNORMAL);
CheckMenuItem (GetMenu (hWnd), wParam, MF_CHECKED);
}
    break;

/* accept bouble click messages from listbox only */
case IDC_LISTBOX:
    if (HIWORD (wParam) != LBN_DBLCLK)
break;

case IDM_VIEWMEMORY:
    if (ViewableMemorySelection (hWnd))
{
char  szBuff[50];
HWND  hList = GetDlgItem (hWnd, IDC_LISTBOX);
int  iCaret = SendMessage (hList, LB_GETCARETINDEX, 0, 0);
DWORD  nAddress =
  (DWORD)((LPVMOBJECT)lpWalkerList)[Objects[iCaret]].mbi.BaseAddress;
int  nSize = ((LPVMOBJECT)lpWalkerList)[Objects[iCaret]].mbi.RegionSize;
LPVOID  lpMem;
HCURSOR   hOldCursor;

if ((lpMem = VirtualAlloc (NULL, nSize, MEM_COMMIT, PAGE_READWRITE)) == NULL)
    {
    ReportError (IDS_ERRVIRTUALALLOC);
    break;
    }

/* put wait cursor up */
hOldCursor = (HCURSOR)SetClassLong (hWnd, GCL_HCURSOR, 0);
SetCursor (LoadCursor (0, IDC_WAIT));

/* signal debugger thread to read process memory */
if (AccessProcessMemory (hChildEvents[READMEMORY],
 hChildEvents[ACKNOWLEDGE],
 (LPVOID)nAddress,
 lpMem,
 &nSize)  && nSize)
    {
    wsprintf (szBuff, "%4lx-%-4lx", nAddress, nAddress+nSize);
    ViewMemory (hWnd, szBuff, lpMem, nSize, nAddress);

    /* if first view window, add separator */
    if (GetMenuItemCount (GetSubMenu (GetMenu (hWnd), 2)) == 5)
AppendMenu (GetSubMenu (GetMenu (hWnd), 2),
    MF_SEPARATOR,
    0,
    NULL);

    AppendMenu (GetSubMenu (GetMenu (hWnd), 2),
MF_STRING | MF_CHECKED,
AddAtom (szBuff),
szBuff);
    }

else
    NotifyUser (hWnd, IDS_ERROR, IDS_COULDNOTREADPROCESS, NULL, 0);

/* replace wait cursor with old cursor */
SetClassLong (hWnd, GCL_HCURSOR, (LONG)hOldCursor);
SetCursor (hOldCursor);
}
    else
{
NotifyUser (hWnd, IDS_ERROR, IDS_NOTCOMMITTEDMEMORY, NULL, 0);
break;
}
    break;

case IDM_VIEWADDRESS:
    {
    int nAddress;
    MEMORY_BASIC_INFORMATIONmbi;
    LPVOIDlpMem;
    charszBuff[MAX_PATH];
    int nLine;
    HWNDhViewWnd;

    if (nAddress = DialogBox (GetModuleHandle (NULL), (char *)IDD_ADDR, hWnd, AddrDlgProc))
{
VirtualQueryEx (lpChildProcess->hProcess,
(LPVOID)nAddress,
&mbi,
sizeof (MEMORY_BASIC_INFORMATION));

if (mbi.State != MEM_COMMIT)

{
    NotifyUser (hWnd, IDS_ERROR, IDS_NOTCOMMITTEDMEMORY, NULL, 0);
    break;
    }

if ((lpMem = VirtualAlloc (NULL, mbi.RegionSize, MEM_COMMIT, PAGE_READWRITE)) == NULL)
    {
    ReportError (IDS_ERRVIRTUALALLOC);
    break;
    }

/* signal debugger thread to read process memory */
if (AccessProcessMemory (hChildEvents[READMEMORY],
 hChildEvents[ACKNOWLEDGE],
 (LPVOID)mbi.BaseAddress,
 lpMem,
 &(mbi.RegionSize))  && mbi.RegionSize)
    {
    wsprintf (szBuff,
      "%4lx-%-4lx",
      (int)mbi.BaseAddress,
      (int)mbi.BaseAddress+mbi.RegionSize);

    hViewWnd = ViewMemory (hWnd, szBuff, lpMem, mbi.RegionSize, (int)mbi.BaseAddress);

    /* if first view window, add separator */
    if (GetMenuItemCount (GetSubMenu (GetMenu (hWnd), 2)) == 4)
AppendMenu (GetSubMenu (GetMenu (hWnd), 2),
    MF_SEPARATOR,
    0,
    NULL);

    AppendMenu (GetSubMenu (GetMenu (hWnd), 2),
MF_STRING | MF_CHECKED,
AddAtom (szBuff),
szBuff);

    /* send WM_VSCROLL message to scroll address into view */
    nLine = (nAddress - (int)mbi.BaseAddress)/16 - 5;
    PostMessage (hViewWnd, WM_VSCROLL, MAKELONG (SB_THUMBPOSITION, nLine), 0);
    }

else
    NotifyUser (hWnd, IDS_ERROR, IDS_COULDNOTREADPROCESS, NULL, 0);
}
    }
    break;

case IDM_REMOVEVIEWWND:
    {
    ATOM    aCaption = FindAtom ((char *)lParam);
    HMENU   hMenu = GetMenu (hWnd);
    HMENU   hViewMenu = GetSubMenu (hMenu, 2);

    RemoveMenu (hMenu, (UINT)aCaption, MF_BYCOMMAND);
    DeleteAtom (aCaption);

    /* there are 4 menuitems in the view menu without view windows open */
    if (GetMenuItemCount (hViewMenu) == 6)
RemoveMenu (hViewMenu, 5, MF_BYPOSITION);
    }
    break;

case IDM_SORTADDRESS:
case IDM_SORTSTATE:
case IDM_SORTPROTECTION:
case IDM_SORTSIZE:
case IDM_SORTBASEADDRESS:
    {
    HWND    hList = GetDlgItem (hWnd, IDC_LISTBOX);
    HCURSOR hOldCursor;

    if (nSortType != (int)LOWORD (wParam))
{
/* put wait cursor up */
hOldCursor = (HCURSOR)SetClassLong (hWnd, GCL_HCURSOR, 0);
SetCursor (LoadCursor (0, IDC_WAIT));

/* reset menuitems to indicate which sort method is being used */
CheckMenuItem (GetMenu (hWnd), nSortType, MF_UNCHECKED);
CheckMenuItem (GetMenu (hWnd), wParam, MF_CHECKED);

/* save new sort type and resort */
SortList (hList, nSortType = wParam);

/* repaint after sorting */
InvalidateRect (hList, NULL, TRUE);
UpdateWindow (hList);

/* replace wait cursor with old cursor */
SetClassLong (hWnd, GCL_HCURSOR, (LONG)hOldCursor);
SetCursor (hOldCursor);
 }
    }
    break;

                case IDM_OPTBYTES:
                case IDM_OPTPAGES:
                    {
                    HWND hList = GetDlgItem (hWnd, IDC_LISTBOX);
                    bNumbersAsBytes = (LOWORD(wParam) == IDM_OPTBYTES);
                    InvalidateRect (hList, NULL, TRUE);
                    }
                    break;

default:
    /* if popup window, bring to front */
    ActivateViewWindow (LOWORD (wParam));
    lRet = TRUE;
    break;
}
    }
    break;

case WM_CLOSE:
case WM_QUERYENDSESSION:
    /* if child process is active, close it first then exit */
    if (lpChildProcess != NULL)
CloseChildProcess (lpChildProcess, hChildEvents);

    /* destroy this window */
    DestroyWindow (hWnd);
    break;

case WM_DESTROY:
    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;
}

/* initialize all menuitems */
void WINAPI InitMenu (
    HWND    hWnd)
{
    HMENUhMenu = GetMenu (hWnd);

    /* if child process exists enable options */
    EnableMenuItem (hMenu,
    IDM_PROCESSREWALK,
    MF_BYCOMMAND | ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem (hMenu,
    IDM_PROCESSUNLOAD,
    MF_BYCOMMAND | ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));

    /* check appropriate sort menuitem */
    CheckMenuItem (hMenu, nSortType, MF_CHECKED);

    /* check view as bytes/pages menuitems */
    CheckMenuItem (hMenu, IDM_OPTBYTES, bNumbersAsBytes ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem (hMenu, IDM_OPTPAGES, bNumbersAsBytes ? MF_UNCHECKED : MF_CHECKED);

    /* enable process and selection stat windows only when child process exists */
    EnableMenuItem (hMenu,
    IDM_VIEWPROSTAT,
    MF_BYCOMMAND | ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem (hMenu,
    IDM_VIEWSYSSTAT,
    MF_BYCOMMAND | ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));

    /* check all appropriate view menuitem */
    CheckMenuItem (hMenu,
   IDM_VIEWSYSSTAT,
   IsWindow (hWndSysStat) ? MF_CHECKED : MF_UNCHECKED);
    CheckMenuItem (hMenu,
   IDM_VIEWPROSTAT,
   IsWindow (hWndProStat) ? MF_CHECKED : MF_UNCHECKED);

    /* if child process exists */
    if (lpChildProcess != NULL)
{
/* child process is active */
if (lpChildProcess->bActive)
    {
    EnableMenuItem (hMenu, IDM_PROCESSRESUME, MF_GRAYED);
    EnableMenuItem (hMenu, IDM_PROCESSSUSPEND, MF_ENABLED);
    }

else
    {
    EnableMenuItem (hMenu, IDM_PROCESSSUSPEND, MF_GRAYED);
    EnableMenuItem (hMenu, IDM_PROCESSRESUME, MF_ENABLED);
    }
}

    /* diasble both menuitems */
    else
{
EnableMenuItem (hMenu, IDM_PROCESSSUSPEND, MF_GRAYED);
EnableMenuItem (hMenu, IDM_PROCESSRESUME, MF_GRAYED);
}

    /* sort only when process exists */
    EnableMenuItem (hMenu,
    IDM_SORTADDRESS,
    ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem (hMenu,
    IDM_SORTSTATE,
    ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem (hMenu,
    IDM_SORTPROTECTION,
    ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem (hMenu,
    IDM_SORTSIZE,
    ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem (hMenu,
    IDM_SORTBASEADDRESS,
    ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));

    /* if child process & selection, and selection is committed memory */
    if (lpChildProcess != NULL &&
ViewableMemorySelection (hWnd))
EnableMenuItem (hMenu, IDM_VIEWMEMORY, MF_ENABLED);
    else
EnableMenuItem (hMenu, IDM_VIEWMEMORY, MF_GRAYED);

/* View address if child process */
EnableMenuItem (hMenu,
IDM_VIEWADDRESS,
    ((lpChildProcess != NULL) ? MF_ENABLED : MF_GRAYED));

}

int WINAPI MakeVMQString (
    int   nItem,
    char  *lpszMem)
{
    char  szState[10], szProtection[3];
    LPVMOBJECT  lpVMObject;
    int           nLen;
    int           nBaseAddr;
    int           nRegionSize;
    int           nAllocBase;
    LPSTR         lpszFormat;

    /* lookup object offset in array index */
    lpVMObject = ((VMOBJECT *)lpWalkerList)+Objects[nItem];

    /* determine state of memory object */
    if (lpVMObject->mbi.State & MEM_COMMIT)
strcpy (szState, "Commit ");
    else if (lpVMObject->mbi.State & MEM_RESERVE)
strcpy (szState, "Reserve");
    else
strcpy (szState, "Free   ");

    /* determine protection of memory */
    if (lpVMObject->mbi.Protect & PAGE_READWRITE)
strcpy (szProtection, "RW");
    else if (lpVMObject->mbi.Protect & PAGE_READONLY)
strcpy (szProtection, "RO");
    else
strcpy (szProtection, "NA");

    lpszFormat = szFormat;
    nBaseAddr = (int)(lpVMObject->mbi.BaseAddress);
    nRegionSize = lpVMObject->mbi.RegionSize;
    nAllocBase = (int)(lpVMObject->mbi.AllocationBase);

    if (!bNumbersAsBytes)
       {
       nBaseAddr /= PAGESIZE;
       nRegionSize /= PAGESIZE;
       nAllocBase /= PAGESIZE;
       lpszFormat = szFormatPages;
       }

    /* create list object */
    wsprintf(lpszMem,
     lpszFormat,
     nBaseAddr,
     szState,
     szProtection,
     nRegionSize,
     nAllocBase,
     lpVMObject->szObjType,
     lpVMObject->szSection,
            lpVMObject->szModule);

    /* return length of resulting string */
    nLen = strlen (lpszMem);

    // convert the ~ separators to \0 for the benefit of
    // TextOutFields
    //
    while (*lpszMem)
       {
       if (*lpszMem == '~') {
           *lpszMem = 0;
           // CharNext() returns the same pointer, if 'lpszMem' points '\0'.
           // So, we must not call CharNext() in this case.
           ++lpszMem;
       } else {
           lpszMem = CharNext(lpszMem);
       }
       }

    return nLen;
}

static void TextOutFields (
   HDC    hDC,
   int    x,
   LPRECT lprc,
   LPSTR  lpszItems)
   {
   int eto = ETO_CLIPPED | ETO_OPAQUE;
   int ii = 0;
   PSTR psz = lpszItems;
   int nLen;

   // copy fields until we get one of zero size.
   //
   do {
      SetTextAlign (hDC, taColumns[ii]);

      ExtTextOut(hDC,
                 lprc->left + (xColumns[ii] * sChar0.cx) + x,
                 lprc->top,
                 eto,
                 lprc,
                 psz,
                 nLen = strlen(psz),
                 0L);

      psz += (nLen + 1);
      eto = ETO_CLIPPED;
      ++ii;
      } while (nLen);
   }

void WINAPI DrawListItem(
    DRAWITEMSTRUCT *lpItem)
{
    DWORD dwBkColor=0xffffffff, dwTextColor=0xffffffff;
    char  szListItem[200];
    int   nLen;

    /* Make sure it is the list box with a valid item ID */
    if (lpItem->CtlType != ODT_LISTBOX ||
lpItem->CtlID != IDC_LISTBOX)
        return;

    if (lpItem->itemAction & (ODA_DRAWENTIRE | ODA_SELECT))
{
        /* Alter the bk and text color for selected items */
if (lpItem->itemState & ODS_SELECTED)
    {
    dwBkColor = SetBkColor (lpItem->hDC, GetSysColor(COLOR_HIGHLIGHT));
    dwTextColor = SetTextColor (lpItem->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
    }

/* change TextColor for new entries too */
else if ((((LPVMOBJECT)lpWalkerList)+Objects[lpItem->itemData])->bNew)
    {
    dwBkColor = SetBkColor (lpItem->hDC, GetSysColor(COLOR_HIGHLIGHT));
    dwTextColor = SetTextColor (lpItem->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
    }

/* make listbox string from virtual memory object */
nLen = MakeVMQString (lpItem->itemData, szListItem);
        TextOutFields (lpItem->hDC, 6, &lpItem->rcItem, szListItem);

/* Restore previous bk and text color if necessary */
if (dwBkColor != 0xffffffff)
            SetBkColor(lpItem->hDC, dwBkColor);
if (dwTextColor != 0xffffffff)
    SetTextColor(lpItem->hDC, dwTextColor);

        if (lpItem->itemState & ODS_FOCUS)
    lpItem->itemAction |= ODA_FOCUS;

}

    if (lpItem->itemAction & ODA_FOCUS)
        DrawFocusRect(lpItem->hDC, &lpItem->rcItem);
}

/* perform bubble sort on indexes to virtual memory objects as stored in
   ownerdraw listbox do not actually sort the objects, just the indexes  */
void  WINAPI SortList (
    HWND    hList,
    int     nSort)
{
    int   nItems = SendMessage (hList, LB_GETCOUNT, 0, 0);
    int   i, j, t;
    LPVMOBJECT  lpVMO = (LPVMOBJECT)lpWalkerList;

    /* loop through all items in list box */
    for (i=0; i<nItems-1; i++)
for (j=i+1; j<nItems; j++)
    {
    /* compare on sort order */
    switch (nSort)
{
case IDM_SORTADDRESS:
    if (lpVMO[Objects[i]].mbi.BaseAddress > lpVMO[Objects[j]].mbi.BaseAddress)
{
/* swap */
t = Objects[j];
Objects[j] = Objects[i];
Objects[i] = t;
}
    break;

case IDM_SORTSTATE:
    if ((lpVMO[Objects[i]].mbi.State > lpVMO[Objects[j]].mbi.State) ||
(lpVMO[Objects[i]].mbi.State == lpVMO[Objects[j]].mbi.State &&
 lpVMO[Objects[i]].mbi.BaseAddress > lpVMO[Objects[j]].mbi.BaseAddress))
{
/* swap */
t = Objects[j];
Objects[j] = Objects[i];
Objects[i] = t;
}
    break;

case IDM_SORTPROTECTION:
    if ((lpVMO[Objects[i]].mbi.Protect > lpVMO[Objects[j]].mbi.Protect) ||
(lpVMO[Objects[i]].mbi.Protect == lpVMO[Objects[j]].mbi.Protect &&
 lpVMO[Objects[i]].mbi.BaseAddress > lpVMO[Objects[j]].mbi.BaseAddress))
{
/* swap */
t = Objects[j];
Objects[j] = Objects[i];
Objects[i] = t;
}
    break;

case IDM_SORTSIZE:
    if ((lpVMO[Objects[i]].mbi.RegionSize > lpVMO[Objects[j]].mbi.RegionSize) ||
(lpVMO[Objects[i]].mbi.RegionSize == lpVMO[Objects[j]].mbi.RegionSize &&
 lpVMO[Objects[i]].mbi.BaseAddress > lpVMO[Objects[j]].mbi.BaseAddress))
{
/* swap */
t = Objects[j];
Objects[j] = Objects[i];
Objects[i] = t;
}
    break;

case IDM_SORTBASEADDRESS:
    if ((lpVMO[Objects[i]].mbi.AllocationBase > lpVMO[Objects[j]].mbi.AllocationBase) ||
(lpVMO[Objects[i]].mbi.AllocationBase == lpVMO[Objects[j]].mbi.AllocationBase &&
 lpVMO[Objects[i]].mbi.BaseAddress > lpVMO[Objects[j]].mbi.BaseAddress))
{
/* swap */
t = Objects[j];
Objects[j] = Objects[i];
Objects[i] = t;
}
    break;
}
    }
}

/* get free disk space on all fixed drives */
BOOL   WINAPI GetFreeDiskSpace (
    LPDWORD    lpdwTotalSpace,
    LPDWORD    lpdwFreeSpace)
{
    DWORD    dwBytesPerSector, dwSectorsPerCluster, dwFreeClusters, dwTotalClusters;
    DWORD    dwDriveMask = GetLogicalDrives();
    int      i;
    char     szDir[4];

    *lpdwTotalSpace = 0;
    *lpdwFreeSpace = 0;
    szDir[1] = TEXT(':');
    szDir[2] = TEXT('\\');
    szDir[3] = 0;

    /* enumerate all logical, fixed drives */
    for (i = 0; i < MAX_DRIVES; dwDriveMask >>= 1, i++)
{
/* if logical drive exists */
if (dwDriveMask & 0x01)
    {
    szDir[0] = TEXT('A') + i;

    /* if it is a fixed drive */
    if (GetDriveType(szDir) == DRIVE_FIXED)
{
/* determine free space and total capacity */
GetDiskFreeSpace (szDir,
  &dwSectorsPerCluster,
  &dwBytesPerSector,
  &dwFreeClusters,
  &dwTotalClusters);

*lpdwTotalSpace += dwTotalClusters * dwSectorsPerCluster * dwBytesPerSector;
*lpdwFreeSpace += dwFreeClusters * dwSectorsPerCluster * dwBytesPerSector;
}
    }
}

    return (*lpdwTotalSpace || *lpdwFreeSpace);
}

/* generic message notification */
int WINAPI NotifyUser (
    HWND    hWndParent,
    int     nTitle,
    int     nError,
    char    *lpszAppend,
    UINT    uFlags)
{
    char    szError[MAX_PATH];
    char    szTitle[MAX_PATH];

    LoadString (GetModuleHandle (NULL), nTitle, szTitle, MAX_PATH);
    LoadString (GetModuleHandle (NULL), nError, szError, MAX_PATH);

    if (lpszAppend != NULL && *lpszAppend != 0)
strcat (szError, lpszAppend);

    if (!uFlags)
uFlags = MB_ICONSTOP | MB_OK | MB_TASKMODAL | MB_SETFOREGROUND;

    /* return message box response */
    return (MessageBox (hWndParent, szError, szTitle, uFlags));
}

void   WINAPI ReportError (
    int     nIDS_CAPTION)
{
    char    *lpszError;
    char    szText[MAX_PATH];

    /* get formatted error message from system */
    if (!FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError (),
MAKELONG (MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), 0),
(LPTSTR)&lpszError,
0,
NULL))
return;

    /* if resource string provided, load caption string */
    if (nIDS_CAPTION)
LoadString (GetModuleHandle (NULL), nIDS_CAPTION, szText, MAX_PATH);
    else
strcpy (szText, "Error");

    MessageBox (NULL,
lpszError,
szText,
MB_ICONSTOP | MB_OK | MB_TASKMODAL | MB_SETFOREGROUND);
}

BOOL WINAPI ViewableMemorySelection (
    HWND    hWnd)
{
    HWND   hList = GetDlgItem (hWnd, IDC_LISTBOX);
    int    iCaret = SendMessage (hList, LB_GETCARETINDEX, 0, 0);
    int    iAnchor = SendMessage (hList, LB_GETANCHORINDEX, 0, 0);

    if (iCaret > -1 &&
iAnchor > -1 &&
SendMessage (hList, LB_GETSEL, iCaret, 0) &&
CommittedMemoryRange (iCaret,
      iAnchor,
      (LPVMOBJECT)lpWalkerList,
      Objects))
return TRUE;
    else
return FALSE;
}

BOOL WINAPI InitDlgProc (
    HWND      hDlg,
    UINT      uMsg,
    WPARAM    wParam,
    LPARAM    lParam)
{
    switch (uMsg)
{
case WM_INITDIALOG:
    *(HANDLE *)lParam = hDlg;
    break;

case WM_CLOSE:
case UM_ENDDIALOG:
    EndDialog (hDlg, TRUE);
    break;

case WM_COMMAND:
    if (LOWORD (wParam) == IDCANCEL ||
LOWORD (wParam) == IDOK)
EndDialog (hDlg, TRUE);
    break;

default:
    return FALSE;
}

    return TRUE;
}


PWALK.H

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include "pefile.h"

#define IDR_MAINICON     20
#define IDR_SYSSTATICON      21
#define IDR_PROSTATICON      22
#define IDR_WALKERMENU     30

#define IDS_WALKERCLASS      11000
#define IDS_SYSSTATCLASS     11001
#define IDS_PROSTATCLASS     11002
#define IDS_STATUSCLASS      11004
#define IDS_MEMVIEWCLASS     11005
#define IDS_SYSSTATTITLE     11010
#define IDS_PROSTATTITLE     11011
#define IDS_MEMVIEWTITLE     11013
#define IDS_CAPTION     11020
#define IDS_UNAVAILABLE      11021
#define IDS_LISTBOX     11022
#define IDS_EXEFILEEXT     11023
#define IDS_FILEOPENTITLE     11024
#define IDS_SELF     11025
#define IDS_ERROR     11030
#define IDS_NOTENOUGHMEM     11031
#define IDS_HEAPCREATEFAILED     11032
#define IDS_DBGEVNTACTIVE     11042
#define IDS_DBGEVNTCLOSE     11043
#define IDS_DBGEVNTSTOP      11044
#define IDS_DBGEVNTSTART     11045
#define IDS_DBGEVNTREAD      11046
#define IDS_DBGEVNTWRITE     11047
#define IDS_DBGEVNTACK     11048
#define IDS_EXCEPTION     11050
#define IDS_APPEXCEPTION     11051
#define IDS_CHILDAPPEXCEPTION     11052
#define IDS_ERRDUPLICATEHANDLE     11060
#define IDS_ERRGETTHREADCONTEXT      11061
#define IDS_ERRCREATEPROCESS     11062
#define IDS_ERROPENPROCESS     11063
#define IDS_ERRCREATEFILEMAPPING     11064
#define IDS_ERRHEAPALLOC     11065
#define IDS_ERRREADPROCESSMEMORY     11066
#define IDS_ERRCREATEWINDOW     11067
#define IDS_ERRVIRTUALFREE     11068
#define IDS_ERRVIRTUALALLOC     11069
#define IDS_STATUSREADY      11070
#define IDS_NOTCOMMITTEDMEMORY     11071
#define IDS_ERRWRITEPROCESSMEMORY    11072
#define IDS_COULDNOTREADPROCESS      11073
#define IDS_ERRSETTHREADCONTEXT      11074
#define IDS_EXITPROCESS      12000
#define IDS_EXITTHREAD     12001
#define IDS_CREATEPROCESS     12002
#define IDS_CREATETHREAD     12003
#define IDS_LOADDLL     12004
#define IDS_UNLOADDLL     12005
#define IDS_OUTPUTDEBUGSTRING     12006
#define IDS_RIPEVENT     12007
#define IDS_BREAKPOINTEXCEPTION      12008
#define IDS_ACCESSVIOLATIONEXCEPTION 12009
#define IDS_UNHANDLEDEXCEPTION     12010
#define IDS_PROCESSSUSPENDED     12011
#define IDS_PROCESSRESUMED     12012
#define IDS_PROCESSINIT      12013
#define IDS_MAPFILENAME      12014
#define IDS_INITPROCESSFAILURE     12015
#define IDS_INITPROBE     12016
#define IDS_HOOKPROCESS      12017

#define IDC_LISTBOX     100
#define IDC_STATUSWND     101
#define IDC_REFRESH     200
#define IDC_CANCEL     201
#define IDC_REWALK     202

#define IDM_POPUPMENUS     1000
#define IDM_POPUPPROCESS     1000
#define IDM_POPUPSORT     1001
#define IDM_POPUPVIEW     1002
#define IDM_POPUPOPTIONS     1003
#define MENUPOPUPS     4

#define IDM_PROCESSREWALK     2000
#define IDM_PROCESSLOAD      2001
#define IDM_PROCESSUNLOAD     2002
#define IDM_PROCESSSUSPEND     2003
#define IDM_PROCESSRESUME     2004
#define IDM_EXIT     2010
#define IDM_SORTADDRESS      2020
#define IDM_SORTSTATE     2021
#define IDM_SORTPROTECTION     2022
#define IDM_SORTSIZE     2023
#define IDM_SORTBASEADDRESS     2024
#define IDM_VIEWSYSSTAT      2030
#define IDM_VIEWPROSTAT      2031
#define IDM_VIEWMEMORY     2033
#define IDM_VIEWADDRESS      2034
#define IDM_OPTBYTES                 2040
#define IDM_OPTPAGES                 2041

#define IDD_ADDR     3000
#define IDC_ADDR     3001
#define IDC_HEX      3002

#define IDD_INITIALIZING     4000
#define UM_ENDDIALOG     WM_USER+2

#define TOTALVMRESERVE     0x00100000
#define PAGESIZE     0x1000
#define IDM_REMOVEVIEWWND     WM_USER+0

#define IDT_STATUS     3000
#define UM_UPDATE     WM_USER+1
#define UM_STARTINITDIALOG     WM_USER+3

#define STATUSWXB     12
#define WXB_HPENHILITE     0
#define WXB_HPENSHADOW     4
#define WXB_LPWINDOWTEXT     8

#define VIEWWXB      8
#define WXB_LPMEMVIEW     0
#define WXB_LPOLDMEMVIEW     4

/* define event handle array indeces */
#define CLOSEDEBUGGER     0
#define SUSPENDDEBUGGER      1
#define RESUMEDEBUGGER     2
#define READMEMORY     3
#define WRITEMEMORY     4
#define DEBUGACTIVE     5
#define ACKNOWLEDGE     6
#define nDEBUGEVENTS     7

typedef struct DBGTHREAD    *LPDBGTHREAD;
typedef struct tagDbgThread
    {
    HANDLE      hThread;
    DWORD      dwThreadID;
    LPTHREAD_START_ROUTINE    lpStartAddress;
    int       nPriority;
    LPDBGTHREAD       Next;
    }DBGTHREAD;

typedef struct DBGDLL *LPDBGDLL;
typedef struct tagDbgDll
    {
    HANDLE  hFile;
    LPVOID  lpBaseOfDll;
    DWORD  dwDebugInfoFileOffset;
    DWORD  nDebugInfoSize;
    char  szImageName[MAX_PATH];
    WORD  fUnicode;
    LPDBGDLL  Next;
    SECTIONINFO   *lpSection;  /* from PEFILE.H */
    }DBGDLL;

typedef struct DBGEXCEPTREC    *LPDBGEXCEPTREC;
typedef struct tagDbgExcept
    {
    EXCEPTION_RECORDExceptRecord;
    CONTEXTContext;
    DWORDdwThreadId;
    DWORDdwFirstChance;
    LPDBGEXCEPTRECNext;
    }DBGEXCEPTREC;

typedef struct tagDbgProcess
    {
    HANDLE hDbgHeap;
    DWORD dwProcessID;
    HANDLE hProcess;
    HANDLE hFile;
    LPVOID lpImage;
    DWORD dwDbgInfoOffset;
    DWORD nDbgInfoSize;
    DBGTHREAD *lpThreads;
    DBGDLL *lpDlls;
    SECTIONINFO  *lpSection;
    HWND hWnd;
    DWORD dwPriority;
    BOOL bActive;
    char szModule[MAX_PATH];
    DBGEXCEPTREC *lpERs;
    }DBGPROCESS;

typedef struct tagVMObject
    {
    MEMORY_BASIC_INFORMATIONmbi;
    charszObjType[12];
    charszModule[MAX_PATH];
    charszSection[IMAGE_SIZEOF_SHORT_NAME];
    BOOLbNew;
    }VMOBJECT, * LPVMOBJECT;

/* struct for memory view window */
typedef struct tagMemView
    {
    LPVOID    lpMem;
    int       nBase;
    int       nSize;
    int       xWin, yWin;
    int       PosV, RangeV;
    long      nLines;
    int       nExtraBytes;
} MEMVIEW, * LPMEMVIEW;

/* struct for process probe dll */
typedef struct tagProbe
    {
    HANDLE hProcess;
    HANDLE hDefHeap;
    }PROBE, * LPPROBE;

/* struct representing process state */
typedef struct tagProcessState
    {
    CONTEXT    Context;
    LPVOID     Esp;
    LPVOID     pStackPage;
    BYTE       Stack[PAGESIZE];
    LPVOID     Eip;
    LPVOID     pCodePage;
    BYTE       Code[PAGESIZE];
    }PROCESS_STATE, * LPPROCESS_STATE;

/* prototypes for functions in pwalk.c */
LONG   WINAPI WalkerWndProc (HWND, UINT, WPARAM, LPARAM);
int    WINAPI NotifyUser (HWND, int, int, char *, UINT);
void   WINAPI ReportError (int);
BOOL   WINAPI GetFreeDiskSpace (LPDWORD, LPDWORD);
BOOL   WINAPI InitDlgProc (HWND, UINT, WPARAM, LPARAM);

/* prototypes for functions in pwalkio.c */
BOOL   WINAPI GetFileName (HWND, char *, char *);
void   WINAPI GetFileFromPath (char *, char *);
BOOL   WINAPI GetCmdLine (char *, char *, BOOL *);
BOOL   WINAPI IsValidFile (char *);

/* prototypes for functions in pstat.c */
LONG   WINAPI SysStatWndProc (HWND, UINT, WPARAM, LPARAM);
LONG   WINAPI ProStatWndProc (HWND, UINT, WPARAM, LPARAM);
LONG   WINAPI StatusWndProc (HWND, UINT, WPARAM, LPARAM);
void   WINAPI SetStatusText (HWND, int, COLORREF);

/* prototypes for functions in pview.c */
HWND   WINAPI ViewMemory (HWND, char *, LPVOID, int, int);
LONG   WINAPI MemWndProc (HWND, UINT, WPARAM, LPARAM);
void   WINAPI ActivateViewWindow (ATOM);
HWND   WINAPI EnumViewWindows (HWND, HWND);
BOOL   WINAPI AddrDlgProc (HWND, UINT, WPARAM, LPARAM);

/* prototypes for functions in pdebug.c */
DBGPROCESS* WINAPI StartChildProcess (HWND, char *, LPHANDLE);
void   WINAPI CloseChildProcess (DBGPROCESS *, LPHANDLE);
int    WINAPI WhereIsStack (HANDLE);
int    WINAPI WalkProcess (HANDLE, LPVOID *, LPINT *);
void   WINAPI AnalyzeProcess (DBGPROCESS *, LPVMOBJECT, int);
void   WINAPI IdentifyNewObjects (LPVMOBJECT, int, LPVMOBJECT, int);
BOOL   WINAPI CommittedMemoryRange (int, int, LPVMOBJECT, int *);
BOOL   WINAPI AccessProcessMemory (HANDLE, HANDLE, LPVOID, LPVOID, DWORD *);

/* prototypes for functions in probe.c, a DLL */
voidWINAPI ResetProbe ();
LPPROBE WINAPI RetrieveProbeData ();


PWALKIO.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 "pwalk.h"
#include <commdlg.h>

/* global file name variables */
charszExeFilterSpec[128] = "Executable Files (*.EXE)\0*.EXE\0";

/* call the OpenFile common dialog to get a filename */
BOOL WINAPI GetFileName (
    HWND    hWnd,
    char    *lpszFilePath,
    char    *lpszExt)
{
    OPENFILENAME    ofn;
    char    szFileOpen[25];
    char    szExt[10];
    char    szTitle[MAX_PATH];

    *szTitle = 0;

    if (lpszExt && *lpszExt)
strcpy (szExt, lpszExt);
    else
LoadString ((HANDLE)GetModuleHandle (NULL),
    IDS_EXEFILEEXT,
    szExt,
    sizeof (szExt));

    LoadString ((HANDLE)GetModuleHandle (NULL),
        IDS_FILEOPENTITLE,
szFileOpen,
sizeof (szFileOpen));

    ofn.lStructSize  = sizeof (OPENFILENAME);
    ofn.hwndOwner  = NULL;
    ofn.lpstrFilter  = szExeFilterSpec;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 0;
    ofn.lpstrFile  = lpszFilePath;
    ofn.nMaxFile  = MAX_PATH;
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrFileTitle  = szTitle;
    ofn.nMaxFileTitle     = MAX_PATH;
    ofn.lpstrTitle  = szFileOpen;
    ofn.lpstrDefExt  = szExt;
    ofn.Flags  = OFN_FILEMUSTEXIST;

    /* call common open dialog and return result */
    return (GetOpenFileName ((LPOPENFILENAME)&ofn));
}

/* invokes the saveas common dialog to retrieve a file name */
/* function retrieves the filename from the path */
void WINAPI GetFileFromPath (
    char    *lpszFullPath,
    char    *lpszFile)
{
    char    *lpPtr = lpszFullPath + strlen (lpszFullPath);

    /* file is at end of path, so search backwards to first \ or : char */
    while (lpPtr > lpszFullPath)
    {
    if (*lpPtr == '\\' ||
        *lpPtr == ':')
        {
        lpPtr++;
        break;
        }
    lpPtr = CharPrev(lpszFullPath, lpPtr);
    }

    /* return filename if found, or full path passed in */
    strcpy (lpszFile, lpPtr);
}

/* validate filename as executable image  */
BOOL WINAPI IsValidFile (
    char    *lpszFilename)
{
    OFSTRUCTof;
    int nLen;
    char*pStr;

    /* validate filename pointer */
    if (lpszFilename == NULL ||
!*lpszFilename)
return FALSE;

    /* open the file for existance */
    if (OpenFile (lpszFilename, &of, OF_EXIST) == -1)
/* fail validation */
return FALSE;

    /* test the extension is .EXE */
    nLen = strlen (lpszFilename);
    pStr = lpszFilename + nLen - 4;
    if (!stricmp (pStr, ".EXE"))
/* pass validation */
return TRUE;

    /* fail validation */
    return FALSE;
}

/* get win32 command line parameters */
BOOL WINAPI GetCmdLine(
    char    *lpStr,
    char    *lpszCmdLine,
    BOOL    *bBkgnd)
{
    if (*lpStr)
{
/* skip application name which precedes parameters */
while (*lpStr != ' ' && *lpStr != 0)
lpStr = CharNext(lpStr);

/* skip spaces */
while (*lpStr == ' ' && *lpStr != 0)
lpStr = CharNext(lpStr);

/* indeed command line parameter(s) present */
if (*lpStr != 0)
    {
    /* if background switch, set flag and remove switch from command line */
    if ((*lpStr == '/' || *lpStr == '-') &&
(*(lpStr+1) == 'b' || *(lpStr+1) == 'B'))
{
*bBkgnd = TRUE;
lpStr += 2;

if (*lpStr == 0)
    *lpszCmdLine = 0;
else
    strcpy (lpszCmdLine, lpStr);
}
    /* maybe switch is embedded in parameter(s) somewhere */
    else
{
char*pStr = lpStr;
char*pCmdLine = lpszCmdLine;
inti, nCnt;

while (*pStr != 0)
    {
    /* background switch is set, so prepare parameters and set flag */
    if ((*pStr == '/' || *pStr == '-') &&
(*(pStr+1) == 'b' || *(pStr+1) == 'B'))
{
*bBkgnd = TRUE;

/* copy from beg. of lpStr to *pStr to lpszCmdLine */
nCnt = pStr - lpStr;
for (i=0; i<nCnt; i++)
    lpszCmdLine[i] = lpStr[i];
lpszCmdLine[i] = 0;
strcat (lpszCmdLine, (pStr+2));

/* break from loop */
break;
}

    pStr = CharNext(pStr);
    }

/* no switch found, can only edit one file, remove extra parameters */
if (*pStr == 0)
    {
    pStr = lpStr;

    while (*pStr != ' ' && *pStr != 0)
pStr = CharNext(pStr);

    if (*pStr == ' ')
*pStr = 0;

    strcpy (lpszCmdLine, lpStr);
    }
}
    }
else
    return FALSE;
}
    else
return FALSE;

    return TRUE;
}


README.TXT

A GUI Schedule Service Manager

SUMMARY
=======

The WINAT tool provides a GUI interface to the AT command. This program
simplifies the way you use and manage AT commands on your computer.

MORE INFORMATION
================

The AT command is normally accessed through the command prompt. AT is
essentially an interface to the schedule service.


LISTHAPI.H

/*
 * LISTHAPI.H
 *
 * Include file that prototypes the exported functions that are in the
 * LISTHSCR DLL to support horizontal listbox scrolling.
 *
 */

BOOL FAR PASCAL FInitListboxExtents(HWND);
WORD FAR PASCAL WAddExtentEntry(HWND, LPSTR);
WORD FAR PASCAL WRemoveExtentEntry(HWND, WORD);
BOOL FAR PASCAL FFreeListboxExtents(HWND);
void FAR PASCAL ResetListboxExtents(HWND);


LISTHSCR.C

/*
 * LISTHSCR.C
 *
 * Added functions to support horizontal listbox scrolling.  This
 * DLL is generalized to support any listbox.  The FInitListboxExtents
 * function allocates local memory (from the DLLs DATA segment) for
 * the list of string extents to go in the listbox.  The local handle
 * is then assigned as a property of the window, so every other
 * function first looks at this property.
 *
 * This means that any number of horizontal scrolling listboxes can
 * be used in the system and make use of these functions, as long
 * as the DLLs memory is not full.
 *
 */

#include <windows.h>
#include "listhscr.h"

/*
 * This is just the label of the property given to each listbox
 * that asks for an extent list.
 */

char szXTList[]="XTList";

/*
 * FInitListboxExtents
 *
 * Purpose:
 *  Simple helper function to initialize everything for maintaining
 *  horizontal extents in a listbox.  This function allocates memory
 *  to hold the list of extents and assigns it to the window as a property.
 *
 * Parameters:
 *  hList       HWND of the listbox concerned.
 *
 * Return Value:
 *  BOOL        TRUE if the function was successful.
 *              FALSE if memory could not be allocated.
 */

BOOL FAR PASCAL FInitListboxExtents(HWND hList)
    {
    HANDLE      hMem;
    WORD        *pw;

    /*
     * Initially allocate 260 bytes, or 130 WORDs since the majority
     * of listbox usage will not require a reallocation, and
     * allocating 256 bytes is just as efficient as allocating 2
     * bytes, if not more so because of reduces overhead.
     *
     * The extra two words store the current number of extent entries
     * and the maximum number possible in this memory block.
     *
     */

    hMem=LocalAlloc(LMEM_MOVEABLE | LMEM_ZEROINIT, CBALLOCUNIT + sizeof(WORD)<<1);

    if (hMem==NULL)
        return FALSE;

    /*
     * Set the first two words in the memory to the appropriate values.
     * If we can't lock it we can;'t use it!
     */
    pw=(WORD *)LocalLock(hMem);

    if (pw==NULL)
        {
        LocalFree(hMem);
        return FALSE;
        }

    *pw=0;                  //cExtentEntries
    *(pw+1)=CALLOCUNITS;    //cExtentEntriesMax

    LocalUnlock(hMem);

    /*
     * Assign the memory handle as a property of the listbox.  This allows
     * this code to take any hList and get it's extent entry list,
     * therefore having full support for multiple listboxes.
     */
    SetProp(hList, (LPSTR)szXTList, hMem);
    return TRUE;
    }

/*
 * FFreeListboxExtents
 *
 * Purpose:
 *  Release any memory used for storing the extents of the
 *  horizontal listbox.  This MUST be called when the listbox
 *  is destroyed, like in the WM_DESTROY case of the parent window.
 *
 * Parameters:
 *  hList       HWND of the listbox concerned.
 *
 * Return Value:
 *  BOOL        TRUE if the function was successful.
 *              FALSE if there is an error.
 */

BOOL FAR PASCAL FFreeListboxExtents(HWND hList)
    {
    HANDLE      hMem;
    BOOL        fSuccess;

    //Load the handle to free.
    hMem=GetProp(hList, (LPSTR)szXTList);

    /*
     * Return a BOOL on the result.  An app could keep calling this
     * function until it worked since hMem is still around.
     */
    fSuccess=(BOOL)LocalFree(hMem);

    if (fSuccess)
        RemoveProp(hList, (LPSTR)szXTList); //Only if handle was freed!

    return fSuccess;
    }

/*
 * ResetListboxExtents
 *
 * Purpose:
 *  Deletes all extents in the extent list to be used AFTER an
 *  LB_RESETCONTENT is sent to the listbox.
 *
 * Parameters:
 *  hList       HWND of the listbox.
 *
 * Return Value:
 *  none
 *
 */

void FAR PASCAL ResetListboxExtents(HWND hList)
    {
    FFreeListboxExtents(hList);
    FInitListboxExtents(hList);

    SendMessage(hList, LB_SETHORIZONTALEXTENT, 0, 0L);

    //This is required to remove the scrollbar.
    SendMessage(hList, LB_DELETESTRING, 0, 0L);
    return;
    }

/*
 * WAddExtentEntry
 *
 * Purpose:
 *  Facilitates handling of the horizontal listbox by keeping
 *  track of the pixel width of the longest string in the listbox.
 *  The number of pixels that the listbox scrolls is the width
 *  of the longest string.
 *
 * Parameters:
 *  hList       HWND of the listbox.
 *  psz         Pointer to string that is added.  This must be passed
 *              instead of an index into the listbox since this must
 *              be called before the string is added if the scrollbar
 *              is to be maintained properly.
 *
 * Return Value:
 *  WORD        0 if the string added was not the longest string in
 *              the listbox and therefore did not change the visibility
 *              of the horizontal scrollbar.
 *
 *              wExtent if the added string was the longest, thus either
 *              making the scrollbar visible or changing the extent.
 *
 *              -1 on an error.
 *
 */

WORD FAR PASCAL WAddExtentEntry(HWND hList, LPSTR psz)
    {
    HANDLE      hMem;
    WORD        cExtentEntries;
    WORD        cExtentEntriesMax;
    WORD        *pw;       //Pointer to extent memory.
    WORD        wExtent;
    WORD        i=0;
    WORD        iRev;

    hMem=GetProp(hList, (LPSTR)szXTList);

    if (hMem==NULL)
return ((WORD)-1);

    pw=(WORD *)LocalLock(hMem);

    if (pw==NULL)
return ((WORD)-1);

    //Load the values and set pointer to start of list.
    cExtentEntries=*pw++;
    cExtentEntriesMax=*pw++;

    //Reallocate if necessary.
    if (cExtentEntries==cExtentEntriesMax)
        {
        LocalUnlock(hMem);

        //This call takes care of cExtentEntriesMax
        if (!FReAllocExtentList(hMem, TRUE))
    return ((WORD)-1);

        cExtentEntriesMax += CALLOCUNITS;
        pw=(WORD *)LocalLock(hMem);

        if (pw==NULL)
    return ((WORD)-1);

        pw+=2;  //Skip the two counters.
        }

    wExtent=WGetListboxStringExtent(hList, psz);

    /*
     * Insert the new extent into the list.  This list is just a sorted
     * list (descending) of the largest to smallest extents in the
     * listbox.  When deleting a string, we just need to look in this
     * list for it's extent and remove that entry.
     *
     * Yeah, this can be inefficient, but this is not a real case for
     * optimization.
     *
     */

    if (cExtentEntries==0)
        pw[0]=wExtent;
    else
        {
        i=IFindExtentInList(pw, wExtent, cExtentEntries);

        for (iRev=cExtentEntries+1; iRev > i; iRev--)
            pw[iRev]=pw[iRev-1];

        pw[i]=wExtent;
        }

    cExtentEntries++;

    //Save these values back.  pw must be decremented first.
    *(--pw)=cExtentEntriesMax;
    *(--pw)=cExtentEntries;

    LocalUnlock(hMem);

    /*
     * Check if the one we added is now the first.  If so, then
     * we need to reset the horizontal extent.
     */

    if (i==0)
       {
        SendMessage(hList, LB_SETHORIZONTALEXTENT, wExtent, 0L);
        return wExtent;
       }

    return ((WORD)0);
    }

/*
 * WRemoveExtentEntry
 *
 * Purpose:
 *  Facilitates handling of the horizontal listbox by keeping
 *  track of the pixel width of the longest string in the listbox.
 *  The number of pixels that the listbox scrolls is the width
 *  of the longest string.
 *
 * Parameters:
 *  hList       HWND of the listbox.
 *  iSel        WORD index of the string to be removed.
 *
 * Return Value:
 *  WORD        0 if the string removed did not affect the visibilty
 *              of the horizontal scrollbar, i.e. if there still is
 *              a longer string or there is no scrollbar in the first
 *              place.
 *
 *              wExtent of the new longest string if the one removed
 *              was the longest.
 *
 *              -1 on an error.
 */

WORD FAR PASCAL WRemoveExtentEntry(HWND hList, WORD iSel)
    {
    WORD        *pw;       //Pointer to extent memory.
    WORD        cExtentEntries;
    WORD        cExtentEntriesMax;
    WORD        wExtent;
    WORD        i;
    WORD        iSave;
    HANDLE      hMem;
    HANDLE      hMemT;
    char        *pch;
    WORD        cb;

    hMem=GetProp(hList, (LPSTR)szXTList);

    if (hMem==NULL)
return ((WORD)-1);

    pw=(WORD *)LocalLock(hMem);

    if (pw==NULL)
return ((WORD)-1);

    //Load the values and set pointer to start of list.
    cExtentEntries=*pw++;
    cExtentEntriesMax=*pw++;

    if (cExtentEntries==0)
        {
        LocalUnlock(hMem);
return ((WORD)-1);
        }

    //Free up memory if necessary.  No reallocating smaller is not fatal.
    if ((cExtentEntriesMax-cExtentEntries)==CALLOCUNITS)
        {
        LocalUnlock(hMem);

        if (!FReAllocExtentList(hMem, FALSE))
    return ((WORD)-1);

        cExtentEntriesMax += CALLOCUNITS;
        pw=(WORD *)LocalLock(hMem);

        if (pw==NULL)
    return ((WORD)-1);

        pw+=2;  //Skip the two counters.
        }

    cb=(WORD)SendMessage(hList, LB_GETTEXTLEN, iSel, 0L);

    //Temporary memory to copy the listbox string so we can get the extent.
    hMemT=LocalAlloc(LMEM_MOVEABLE, cb+2);  //One extra to be safe.
    pch=LocalLock(hMemT);

    if (pch==NULL)
        {
        LocalUnlock(hMem);
        LocalFree(hMemT);
return ((WORD)-1);
        }

    cb=(WORD)SendMessage(hList, LB_GETTEXT, iSel, (LONG)(LPSTR)pch);

    wExtent=WGetListboxStringExtent(hList, (LPSTR)pch);

    LocalUnlock(hMemT);
    LocalFree(hMemT);

    /*
     * Find the extent in the list and remove it.  If it's the first,
     * then reset the horizontal extent to the second.
     */

    i=IFindExtentInList(pw, wExtent, cExtentEntries);
    iSave=i;

    while (i < cExtentEntries)
        pw[i++]=pw[i+1];

    cExtentEntries--;

    //Save these values back.  pw must be decremented first.
    *(--pw)=cExtentEntriesMax;
    *(--pw)=cExtentEntries;

    LocalUnlock(hMem);

    if (iSave==0)
        {
        /*
         * Before we change the horizontal extent, we must make sure that
         * the origin of the listbox is visible through forcing a scroll.
         * If this is not done, and the listbox is scrolled one or
         * more pixels to the right, the scrollbar WILL NOT disappear
         * even if all remaining strings fit inside the client area
         * of the listbox.
         *
         * This is only done here since this the only case where this
         * might happen is when we change the extent.
         */
        SendMessage(hList, WM_HSCROLL, SB_TOP, MAKELONG(0, hList));
        SendMessage(hList, LB_SETHORIZONTALEXTENT, pw[2], 0L);

        return pw[2];
        }

    return ((WORD)0);
    }

/*
 * FReAllocExtentList
 *
 * Purpose:
 *  Handles reallocation of the list in blocks of +/- CBALLOCUNIT
 *
 * Parameters:
 *  fGrow       BOOL if TRUE, instructs this function to allocate
 *              an additional ALLOCUNIT.
 *              If FALSE, shrinks the memory block by an ALLOCUNIT.
 *
 * Return Value:
 *  BOOL        TRUE if successfully reallocated.  FALSE otherwise.
 *
 */

BOOL FReAllocExtentList(HANDLE hMem, BOOL fGrow)
    {
    WORDwSize;

    /*
     * Allocate an additional 128 entries.  A 256 byte block is a
     * decent reallocation size.
     */
    wSize=LocalSize((HLOCAL)hMem);
    wSize+=(fGrow) ?  ((int)CBALLOCUNIT) : (-(int)CBALLOCUNIT);

    /*
     * This returns FALSE if the realloc was unsuccessful.  TRUE
     * otherwise because the return handle is  !=0
     *
     */
    return (BOOL)LocalReAlloc(hMem, wSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
    }

/*
 * WGetListboxStringExtent
 *
 * Purpose:
 *  Returns the extent, in pixels, of a string that will be or is
 *  in a listbox.  The hDC of the listbox is used and an extra
 *  average character width is added to the extent to insure that
 *  a horizontal scrolling listbox that is based on this extent
 *  will scroll such that the end of the string is visible.
 *
 * Parameters:
 *  hList       HWND handle to the listbox.
 *  psz         LPSTR pointer to string in question.
 *
 * Return Value:
 *  WORD        Extent of the string relative to listbox.
 *
 */

WORD WGetListboxStringExtent(HWND hList, LPSTR psz)
    {
    TEXTMETRIC  tm;
    HDC         hDC;
    HFONT       hFont;
    WORD        wExtent;

    /*
     * Make sure we are using the correct font.
     */
    hDC=GetDC(hList);
    hFont=(HFONT)SendMessage(hList, WM_GETFONT, 0, 0L);

    if (hFont!=NULL)
        SelectObject(hDC, hFont);

    GetTextMetrics(hDC, &tm);

    /*
     * Add one average text width to insure that we see the end of the
     * string when scrolled horizontally.
     */

    //
    // changed to GetTextExtentPoint - KoryG - 3/24/92
    //
    {
        SIZE Size;

        GetTextExtentPoint(hDC, psz, lstrlen(psz), &Size);
        // fudge for tabs!
        wExtent=(WORD)Size.cx + (WORD)tm.tmAveCharWidth + 180;
    }

    ReleaseDC(hList, hDC);

    return wExtent;
    }

/*
 * IFindExtentInList
 *
 * Purpose:
 *  Does an binary search on the sorted extent list and returns
 *  an index to the one that matches.  If there is no match,
 *  the index gives the point where the extent entry should go.
 *
 *  Note that an altered search algorithm is used since the list
 *  is descending instead of ascending.
 *
 * Parameters:
 *  pw             WORD * pointer to extent list.
 *  wExtent        WORD extent to find or find an index for.
 *  cExtentEntries WORD count of entries in list.
 *
 * Return Value:
 *  iExtent    WORD index into lpw where wExtent exists or where
 *             it should be inserted.
 *
 */

WORD IFindExtentInList(WORD *pw, WORD wExtent, WORD cExtentEntries)
    {
    int     i = 0;      //These MUST be signed!
    int     iPrev;
    int     iMin;
    int     iMax;

    //Set upper limits on search.
    iMin=0;
    iMax=cExtentEntries+1;

    do
        {
        iPrev=i;
        i=(iMin + iMax) >> 1;

        if (i==iPrev)
            {
            i++;
            break;
            }

        //Change the min and max depending on which way we need to look.
        if (wExtent < pw[i])   // < since list is descending. > otherwise
            iMin=i;
        else
            iMax=i;

        if (iMax==iMin)
            break;
        }
    while (wExtent != pw[i]);

    /*
     * When we get here, i is either where wExtent is or where it should
     * go--so return it.
     */
    return i;
    }


LISTHSCR.H

/*
 * LISTHSCR.H
 *
 * Private definitions and prototypes for the listhscr DLL.
 */

//Unit of allocation for extent list.
#define CBALLOCUNIT     (sizeof(WORD)<<7)

//Number of units per allocation unit.
#define CALLOCUNITS     128

/*
 * Private prototypes
 */

BOOL FReAllocExtentList(HANDLE, BOOL);
WORD WGetListboxStringExtent(HWND, LPSTR);
WORD IFindExtentInList(WORD *, WORD, WORD);


MYNETMSG.H

//
// MessageId: APE2_GEN_ERROR
//
// MessageText:
//
//  Error%0
//
#define APE2_GEN_ERROR                    4342

//
// MessageId: APE2_AT_DEL_WARNING
//
// MessageText:
//
//  This operation will clear the AT schedule file.
//
#define APE2_AT_DEL_WARNING               4630

//
// MessageId: APE2_AT_DI_COMMAND
//
// MessageText:
//
//  Command:       %0
//
#define APE2_AT_DI_COMMAND                4641


WINAT.C

//===========================================================================
//
// Module: WINAT.C
//
// Purpose: Main program for WINAT.exe.  This program is a GUI Windows
//          NT AT Command Scheduler.  The base code was taken from the
//          Windows NT AT command files.  I have preserved (mostly) the
//          same algorithms and data structures.
//
// Author: Kory Gill
//
// Date  : March 1993
//
// Copyright: Copyright 1993 - 1998 Microsoft Corporation
//
// Original banner from Windows NT at.c
//
//      /*++
//
//      Copyright 1987 - 1998  Microsoft Corporation
//
//      Module Name:
//
//          atcmd.c
//
//      Abstract:
//
//          Code for AT command, to be used with SCHEDULE service on
//          Windows NT.
//
//          The module was taken from LanManager\at.c and then modified
//          considerably to work with NT Schedule service.
//
//      Author:
//
//          Vladimir Z. Vulovic                     06 - November - 1992
//
//      Environment:
//
//          User Mode - Win32
//
//      Revision History:
//
//          06-Nov-1992
//              Created
//
//          20-Feb-1993
//              Get rid of hard coded strings and parse/print time according
//              to user profile
//
//      --*/
//
//===========================================================================

//===========================================================================
// includes
//===========================================================================

#include <windows.h>    // required for all Windows applications
#include "lmcons.h"     // LAN Manager defines
#include "MYnetmsg.h"   // APE2 defines
#include "lmerr.h"      // LAN Manager error messages
#include "lmat.h"       // AT Command prototypes
#include "lmapibuf.h"   // NetApiBufferFree
#include "WINAT.h"      // specific to this program
#include "listhapi.h"   // hotizontal listbox routines
#include <assert.h>     // assert
#include <stdio.h>      // assert

//===========================================================================
// global variables
//===========================================================================

ATTIME  AtTime[NUM_TIMES] =                 // time strings and ms since 12am
    {
"00:00",  0, "00:30",  0, "01:00",  0, "01:30",  0,
"02:00",  0, "02:30",  0, "03:00",  0, "03:30",  0,
"04:00",  0, "04:30",  0, "05:00",  0, "05:30",  0,
"06:00",  0, "06:30",  0, "07:00",  0, "07:30",  0,
"08:00",  0, "08:30",  0, "09:00",  0, "09:30",  0,
"10:00",  0, "10:30",  0, "11:00",  0, "11:30",  0,
"12:00",  0, "12:30",  0, "13:00",  0, "13:30",  0,
"14:00",  0, "14:30",  0, "15:00",  0, "15:30",  0,
"16:00",  0, "16:30",  0, "17:00",  0, "17:30",  0,
"18:00",  0, "18:30",  0, "19:00",  0, "19:30",  0,
"20:00",  0, "20:30",  0, "21:00",  0, "21:30",  0,
"22:00",  0, "22:30",  0, "23:00",  0, "23:30",  0
    };
AT_INFO GlobalAtInfo;                       // at information structure
BOOL    fTimer;                             // flag for timer events
DWORD   GlobalJobId;                        // job id as dword
DWORD   dwDisposition;                      // used for registry calls
DWORD   dwH = DWH;                          // height of main window
DWORD   dwTime;                             // time in milliseconds
DWORD   dwW = DWW;                          // width of main window
DWORD   dwX = DWX;                          // x location of main window
DWORD   dwY = DWY;                          // y location of main window
FARPROC lpAmPmProc;                                                     // subclass for add/change AmPm edit control
FARPROC lpAmPmDefProc;                                          // default handler for add/change AmPm edit control
FARPROC lpDownButtonProc;                                       // subclass for spin button
FARPROC lpDownButtonDefProc;                            // default handler for spin buttons
FARPROC lpHourProc;                                                     // subclass for add/change hour edit control
FARPROC lpHourDefProc;                                          // default handler for add/change hour edit control
FARPROC lpMinProc;                                                      // subclass for add/change min edit control
FARPROC lpMinDefProc;                                           // default handler for add/change min edit control
FARPROC lpUpButtonProc;                                         // subclass for spin button
FARPROC lpUpButtonDefProc;                                      // default handler for spin buttons
HANDLE  hInst;                              // instance
HCURSOR hCursorOld;                         // old cursor
HCURSOR hCursorWait;                        // hourglass cursor
#ifdef JAPAN
#else
HFONT   hFont;                              // handle to font
#endif
HANDLE  GlobalMessageHandle;                // handle to netmsg.dll
HKEY    hkHandle;                           // hangle to registry
HWND    hCurrentFocus = NULL;                           // handle to window with focus (used for hour/min/ampm spin buttons)
HWND    hGlobalhWnd;                        // handle to main window
HWND    hGlobalAddChg = NULL;               // handle to Add/Change dialog when active
HWND    hHour = NULL;                       // handle to hour
HWND    hMin = NULL;                        // handle to minutes
HWND    hAmPm = NULL;                       // handle to ampm
LOGFONT lf;                                 // font
PWSTR   GlobalComputerName;                 // currently selected comp. name
UCHAR   szRegistryPath[] = SZREGPATH;       // path to registry for stored info
UCHAR   szAppName[] = SZAPPTITLE;           // name of this application
UCHAR   szAtCommand[MAX_ATCMD_LEN+1];       // entire line in atcommand listbox
UCHAR   szCommandM[MAXCOMMANDLEN+1];        // multibyte command
UCHAR   szComputerM[MAXCOMPUTERNAMELEN+1];  // multibyte comp. name
UCHAR   szDateAbbr[4];                      // store day of month as string
#ifdef JAPAN
UCHAR   szDayAbbr[7][8]   =                 // abbreviations
    {
        "   j   ", "   j   ", "   j   ", "   j   ", "   j   ", " y j   ", "   j   "
    };
#else
UCHAR   szDayAbbr[7][4]   =                 // abbreviations
    {
"M ", "T ", "W ", "Th ", "F ", "Sa ", "Su "
    };
#endif
UCHAR   szDefault[] = " ";                  // default char for wide conversion
UCHAR   szError[] = SZGENERROR;             // error message
UCHAR   szNoEntries[] = SZNOENTRIES;        // error message
#ifdef JAPAN
UCHAR   szTime[32];                         // store time as string
#else
UCHAR   szTime[8];                          // store time as string
#endif
UCHAR   szTitle[]   = SZAPPTITLE;           // title bar text
UCHAR   szWhenDay[MAXWHENDAYLEN + 1];       // used to build when/day string
#ifdef JAPAN
#else
UINT    uAmPmChars;                                                     // count of characters in add/change AmPm edit control
#endif
UINT    uHourChars;                                                     // count of characters in add/change hour edit control
UINT    uMinChars;                                                      // count of characters in add/change min edit control
UINT    uTimer;                             // handle of timer
WCHAR   szCommandW[MAXCOMMANDLEN+1];        // widechar command
WCHAR   szComputerW[MAXCOMPUTERNAMELEN+1];  // widechar comp. name

// jean-marc - start
UCHAR   gszCommandLine[MAXCOMPUTERNAMELEN+1]; // stores the passed in commandline
// jean-marc - end

//===========================================================================
// FUNCTION: AddChangeDlgProc()
//===========================================================================

BOOL APIENTRY
AddChangeDlgProc(
    HWND hWnd,         // window handle
    UINT message,      // type of message
    UINT uParam,       // additional information
    LONG lParam        // additional information
    )
{

    NET_API_STATUS  status;
    PAT_INFO        pAtInfo = NULL;
    SYSTEMTIME      SystemTime;
    UCHAR           szAtCommand[MAX_ATCMD_LEN];
    UCHAR           szTemp[30];
    char *          ptr;
    int             JobId;
    int             i;
    int             wmEvent;
    int             wmId;
    static BOOL     fChange;
    BOOL                        Translated;
    UINT                        Value;
    int                         Delta;

    // owner draw stuff
    static LPDRAWITEMSTRUCT  lpdis;          // Long Pointer to owner-draw struct
    LPMEASUREITEMSTRUCT      lpmis;          // Long Pointer to o-d measure struct
    static HBITMAP           hButtonUpDef,   // for button's regular state bm
     hButtonUpDep,   // for button's pushed  state bm
     hButtonDownDef, // for button's regular state bm
     hButtonDownDep; // for button's pushed  state bm

    switch (message) {

case WM_SPINBUTTON:

    if ( (hCurrentFocus != hHour) && (hCurrentFocus != hMin) && (hCurrentFocus != hAmPm) ) {

break;

    }

if ( lParam == UP ) {

Delta = UP;

} else {

Delta = DOWN;

}

if ( uParam == (UINT)GetDlgItem( hWnd, IDC_HOUR ) ) {

Value = GetDlgItemInt( hWnd, IDC_HOUR, &Translated, FALSE );

if ( Translated == 0 ) {

SetDlgItemInt( hWnd, IDC_HOUR, DEFAULT_HOUR, FALSE );

} else {

if ( ( Value >= 12 || Value < 1 ) && Delta == UP ) {

Value = 1;

} else if ( ( Value > 12 || Value <= 1 ) && Delta == DOWN ) {

Value = 12;

} else {

if ( (Value == 11 && Delta == UP) || (Value == 12 && Delta == DOWN) ) {

GetDlgItemText( hWnd, IDC_AMPM, (LPTSTR)&szTemp, sizeof(szTemp) );

#ifdef JAPAN
        if ( !strcmp(szTemp, "   O" ) ) {
        SetDlgItemText( hWnd, IDC_AMPM,  (LPCTSTR)"    " );
        } else {
SetDlgItemText( hWnd, IDC_AMPM,  (LPCTSTR)"   O" );
}
#else
if ( szTemp[0] == 'A' || szTemp[0] == 'a' ) {

SetDlgItemText( hWnd, IDC_AMPM,  (LPCTSTR)"PM" );

} else {

SetDlgItemText( hWnd, IDC_AMPM,  (LPCTSTR)"AM" );

}
#endif
}

Value += Delta;

}

SetDlgItemInt( hWnd, IDC_HOUR, Value, FALSE );

}

SendDlgItemMessage( hWnd, IDC_HOUR, EM_SETSEL, 0, -1 );
//SetFocus( (HWND)uParam );

} else if ( uParam == (UINT)GetDlgItem( hWnd, IDC_MIN ) ) {

Value = GetDlgItemInt( hWnd, IDC_MIN, &Translated, FALSE );

if ( Translated == 0 ) {

SetDlgItemInt( hWnd, IDC_MIN, DEFAULT_MIN, FALSE );

} else {

if ( ( Value >= 59 || Value < 0 ) && Delta == UP ) {

Value = 0;

} else if ( ( Value > 59 || Value <= 0 ) && Delta == DOWN ) {

Value = 59;

} else {

Value += Delta;

}

}

if ( Value > 9 ) {

SetDlgItemInt( hWnd, IDC_MIN, Value, FALSE );

} else {

szTemp[0] = '0';
szTemp[1] = '0' + Value;
szTemp[2] = '\0';

SetDlgItemText( hWnd, IDC_MIN, szTemp );
    }

SendDlgItemMessage( hWnd, IDC_MIN, EM_SETSEL, 0, -1 );
//SetFocus( (HWND)uParam );

} else if ( uParam == (UINT)GetDlgItem( hWnd, IDC_AMPM ) ) {

GetDlgItemText( hWnd, IDC_AMPM, (LPTSTR)&szTemp, sizeof(szTemp) );

#ifdef JAPAN
    if ( !strcmp(szTemp, "   O") ) {
    SetDlgItemText( hWnd, IDC_AMPM,  (LPCTSTR)"    " );
    } else {
SetDlgItemText( hWnd, IDC_AMPM,  (LPCTSTR)"   O" );
}
#else
if ( szTemp[0] == 'A' || szTemp[0] == 'a' ) {

SetDlgItemText( hWnd, IDC_AMPM,  (LPCTSTR)"PM" );

} else {

SetDlgItemText( hWnd, IDC_AMPM,  (LPCTSTR)"AM" );

}
#endif

SendDlgItemMessage( hWnd, IDC_AMPM, EM_SETSEL, 0, -1 );
//SetFocus( (HWND)uParam );

}

break;

case WM_MEASUREITEM:

    lpmis = (LPMEASUREITEMSTRUCT)lParam; // for convenience
    lpmis->itemWidth  = 7;
    lpmis->itemHeight = 7;

    break;

case WM_DRAWITEM:

    lpdis = (LPDRAWITEMSTRUCT)lParam;    // for convenience

    switch (lpdis->itemAction) {

case ODA_DRAWENTIRE:
    if ( lpdis->CtlID == IDD_DOWNBUT ) {
DrawBitmap(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, hButtonDownDef, SRCCOPY);
    } else {
DrawBitmap(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, hButtonUpDef, SRCCOPY);
    }

    break;

case ODA_SELECT:
    // handle select state -- text is right & down 2 pixels
    // also handle focus state since it's associated
    if (lpdis->itemState & ODS_SELECTED) {
if ( lpdis->CtlID == IDD_DOWNBUT ) {
DrawBitmap(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, hButtonDownDep, SRCCOPY);
    } else {
DrawBitmap(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, hButtonUpDep, SRCCOPY);
    }
    }
    else {
if ( lpdis->CtlID == IDD_DOWNBUT ) {
DrawBitmap(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, hButtonDownDef, SRCCOPY);
    } else {
DrawBitmap(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top, hButtonUpDef, SRCCOPY);
    }

if ( hCurrentFocus != NULL ) {

SetFocus( hCurrentFocus );

}

    }
    break;

case ODA_FOCUS:
    if (lpdis->itemState & ODS_FOCUS) {

    break;
}

break;

default:

    break;

    }  //itemAction

    break;

case WM_INITDIALOG:

/* load bitmaps and get area of text rectangle for owner-draw button */

    hButtonUpDef = LoadBitmap(hInst,"BUTTONUPDEF"); // regular state
    hButtonUpDep   = LoadBitmap(hInst,"BUTTONUPDEP");   // pushed  state

    hButtonDownDef = LoadBitmap(hInst,"BUTTONDOWNDEF"); // regular state
    hButtonDownDep = LoadBitmap(hInst,"BUTTONDOWNDEP");   // pushed  state

    hCurrentFocus = NULL;
    hHour = GetDlgItem( hWnd, IDC_HOUR );
    hMin  = GetDlgItem( hWnd, IDC_MIN );
    hAmPm = GetDlgItem( hWnd, IDC_AMPM );

#ifdef JAPAN
#else
SendDlgItemMessage( hWnd, IDC_NEWCOMMAND, WM_SETFONT, (WPARAM)hFont, 1L );
SendDlgItemMessage( hWnd, IDC_DAYS, WM_SETFONT, (WPARAM)hFont, 1L );
SendDlgItemMessage( hWnd, IDC_HOUR, WM_SETFONT, (WPARAM)hFont, 1L );
SendDlgItemMessage( hWnd, IDC_MIN, WM_SETFONT,  (WPARAM)hFont, 1L );
SendDlgItemMessage( hWnd, IDC_AMPM, WM_SETFONT, (WPARAM)hFont, 1L );
#endif

    // fill in Days
#ifdef JAPAN
            SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"   j  ");
            SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"   j  ");
            SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"   j  ");
            SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"   j  ");
            SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"   j  ");
            SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)" y j  ");
            SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"   j  ");
#else
    SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"Monday");
    SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"Tuesday");
    SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"Wednesday");
    SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"Thursday");
    SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"Friday");
    SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"Saturday");
    SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)"Sunday");
#endif

    for ( i=1; i<=9; ++i ) {

UCHAR szDateAbbr[2];

szDateAbbr[0] = i + '0';
szDateAbbr[1] = '\0';

SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)szDateAbbr );

    }

for ( i=10; i<=31; ++i ) {

UCHAR szDateAbbr[3];

szDateAbbr[0] = i/10 + '0';
szDateAbbr[1] = i%10 + '0';
szDateAbbr[2] = '\0';

SendDlgItemMessage(hWnd, IDC_DAYS, LB_ADDSTRING, 0, (LPARAM)szDateAbbr );

    }

if ( lParam == IDC_ADD ) {

    SendDlgItemMessage(hWnd, IDC_DAYS, LB_SETCURSEL, 0, 0);

    // fill in Time

SetDlgItemInt( hWnd, IDC_HOUR, DEFAULT_HOUR, FALSE );

if ( DEFAULT_MIN > 9 ) {

SetDlgItemInt( hWnd, IDC_MIN, DEFAULT_MIN, FALSE );

} else {

szTemp[0] = '0';
szTemp[1] = '0' + DEFAULT_MIN;
szTemp[2] = '\0';

SetDlgItemText( hWnd, IDC_MIN, szTemp );
    }

SetDlgItemText( hWnd, IDC_AMPM, (LPCTSTR)DEFAULT_AMPM );

    // set the Every radio button

    SendDlgItemMessage(hWnd, IDC_EVERY, BM_SETCHECK, TRUE, 0);

    fChange = FALSE;

#ifdef JAPAN
        lstrcpy( szTemp, "         " );
#else
lstrcpy( szTemp, "Add Command" );
#endif

SetWindowText( hWnd, szTemp );

} else {

    fChange = TRUE;

#ifdef JAPAN
lstrcpy( szTemp, "        X" );
#else
lstrcpy( szTemp, "Change Command" );
#endif

SetWindowText( hWnd, szTemp );

// fill in current command info

SendDlgItemMessage(
hGlobalhWnd,
IDC_ATCOMMANDS,
LB_GETTEXT,
SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_GETCURSEL, 0, 0 ),
(LPARAM)szAtCommand
);

// get job id

    JobId = 0;

    ptr   = szAtCommand;

    while( *ptr >= '0' && *ptr <= '9' ) {

JobId *= 10;
JobId += (UINT)*ptr - '0';

ptr++;

    }

    // get info on this job

    status = NetScheduleJobGetInfo(
    GlobalComputerName,
JobId,
(LPBYTE *)&pAtInfo
);

    if ( status != NERR_Success ) {

//MessageBox( hWnd, "Problem getting Job Info.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

MessagePrint( status );

EndDialog( hWnd, 0 );

    }

    // fill in command

    WideCharToMultiByte(
(UINT)CP_ACP,
(DWORD)0,
(LPWSTR)pAtInfo->Command,
MAX_ATCMD_LEN,
    (LPSTR)szCommandM,
    MAX_ATCMD_LEN,
    (LPSTR)NULL, (LPBOOL)NULL
    );

SetDlgItemText( hWnd, IDC_NEWCOMMAND, szCommandM );

// set days this command runs

if ( pAtInfo->DaysOfWeek != 0) {

for ( i=0; i<7; ++i ) {

if ( ( pAtInfo->DaysOfWeek & (1 << i) ) != 0 ) {

SendDlgItemMessage( hWnd, IDC_DAYS, LB_SETSEL, TRUE, i );

}

}

}

if ( pAtInfo->DaysOfMonth != 0) {

for ( i=0; i<31; ++i ) {

if ( ( pAtInfo->DaysOfMonth & (1L << i) ) != 0 ) {

SendDlgItemMessage( hWnd, IDC_DAYS, LB_SETSEL, TRUE, i+7 );

}

}

}

// fill in every/today/tomorrow/next flag

if ( pAtInfo->Flags & JOB_RUN_PERIODICALLY ) {

SendDlgItemMessage( hWnd, IDC_EVERY, BM_SETCHECK, TRUE, 0 );

SendMessage( hWnd, WM_COMMAND, IDC_EVERY, 0 );

} else if ( (pAtInfo->DaysOfWeek != 0) || (pAtInfo->DaysOfMonth != 0) ) {

SendDlgItemMessage( hWnd, IDC_NEXT, BM_SETCHECK, TRUE, 0 );

SendMessage( hWnd, WM_COMMAND, IDC_NEXT, 0 );

} else if ( pAtInfo->Flags & JOB_RUNS_TODAY ) {

SendDlgItemMessage( hWnd, IDC_TODAY, BM_SETCHECK, TRUE, 0 );

SendMessage( hWnd, WM_COMMAND, IDC_TODAY, 0 );

} else {

SendDlgItemMessage( hWnd, IDC_TOMORROW, BM_SETCHECK, TRUE, 0 );

SendMessage( hWnd, WM_COMMAND, IDC_TOMORROW, 0 );

}

    // set time for this command

{

    UINT  uJobTime = (UINT)(pAtInfo->JobTime / 1000 / 60 / 60);
UINT  uAM = TRUE;

if ( uJobTime == 0 ) {

uJobTime = 12;

} else if ( uJobTime == 12 ) {

    uAM = FALSE;

} else if ( uJobTime > 12 ) {

    uJobTime -= 12;

    uAM = FALSE;

}

SetDlgItemInt( hWnd, IDC_HOUR, uJobTime, FALSE );

#ifdef JAPAN
               uAM ? SetDlgItemText( hWnd, IDC_AMPM, "   O" ) : SetDlgItemText( hWnd, IDC_AMPM, "    " );
#else
uAM ? SetDlgItemText( hWnd, IDC_AMPM, "AM" ) : SetDlgItemText( hWnd, IDC_AMPM, "PM" );
#endif

if ( (UINT)(pAtInfo->JobTime / 1000 / 60 % 60) > 9 ) {

SetDlgItemInt( hWnd, IDC_MIN, (UINT)(pAtInfo->JobTime / 1000 / 60 % 60), FALSE );

} else {

szTemp[0] = '0';
szTemp[1] = '0' + (UINT)(pAtInfo->JobTime / 1000 / 60 % 60);
szTemp[2] = '\0';

SetDlgItemText( hWnd, IDC_MIN, szTemp );
    }

    }

// free buffer

NetApiBufferFree( (LPVOID)pAtInfo );

}

    // set up subclass functions

lpAmPmProc = MakeProcInstance( (FARPROC)AmPmProc, hInst );
lpAmPmDefProc = (FARPROC) SetWindowLong( GetDlgItem( hWnd, IDC_AMPM ), GWL_WNDPROC, (LONG)lpAmPmProc );
lpDownButtonProc = MakeProcInstance( (FARPROC)DownButtonProc, hInst );
lpDownButtonDefProc = (FARPROC) SetWindowLong( GetDlgItem( hWnd, IDD_DOWNBUT ), GWL_WNDPROC, (LONG)lpDownButtonProc );
lpUpButtonProc = MakeProcInstance( (FARPROC)UpButtonProc, hInst );
lpUpButtonDefProc = (FARPROC) SetWindowLong( GetDlgItem( hWnd, IDD_UPBUT ), GWL_WNDPROC, (LONG)lpUpButtonProc );
lpHourProc = MakeProcInstance( (FARPROC)HourProc, hInst );
lpHourDefProc = (FARPROC) SetWindowLong( GetDlgItem( hWnd, IDC_HOUR ), GWL_WNDPROC, (LONG)lpHourProc );
lpMinProc = MakeProcInstance( (FARPROC)MinProc, hInst );
lpMinDefProc = (FARPROC) SetWindowLong( GetDlgItem( hWnd, IDC_MIN ), GWL_WNDPROC, (LONG)lpMinProc );

// will default to two chars

uHourChars = 2;
uMinChars = 2;

hGlobalAddChg = hWnd;

break;

case WM_COMMAND:    // message: command from application menu

wmId    = LOWORD(uParam);
wmEvent = HIWORD(uParam);

switch (wmId) {

case IDC_TODAY:
case IDC_TOMORROW:

    GetLocalTime( &SystemTime );

    if ( SystemTime.wDayOfWeek == 0 ) {

SystemTime.wDayOfWeek = 7;

    }

for( i=0; i<NUMDAYITEMS; ++i ) {

SendDlgItemMessage( hWnd, IDC_DAYS, LB_SETSEL, FALSE, i );

}

SendDlgItemMessage(
hWnd,
IDC_DAYS,
LB_SETSEL,
TRUE,
wmId == IDC_TOMORROW ? SystemTime.wDayOfWeek : SystemTime.wDayOfWeek - 1 );

    EnableWindow(GetDlgItem(hWnd, IDC_DAYS), FALSE);

    break;

case IDC_EVERY:

    EnableWindow(GetDlgItem(hWnd, IDC_DAYS), TRUE);

    break;

case IDC_NEXT:

    EnableWindow(GetDlgItem(hWnd, IDC_DAYS), TRUE);

    break;

case IDCANCEL:

    hCurrentFocus = NULL;
    DeleteObject( hButtonUpDef );
    DeleteObject( hButtonUpDep );
    DeleteObject( hButtonDownDep );
    DeleteObject( hButtonDownDef );

EndDialog( hWnd, IDCANCEL );

break;

case IDOK:

    if ( ValidateAddChangeArguments( hWnd ) == TRUE ) {

if( fChange == TRUE) {

status = JobDel( DEL_ID_CHANGE, hGlobalhWnd);

}

status = JobAdd();

    if ( status == NERR_Success ) {

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_BOTTOM );

hCurrentFocus = NULL;
DeleteObject( hButtonUpDef );
    DeleteObject( hButtonUpDep );
    DeleteObject( hButtonDownDep );
    DeleteObject( hButtonDownDef );

    EndDialog( hWnd, IDOK );

    } else {

//MessageBox( hWnd, "Problem adding command.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

MessagePrint( status );

    }

    }

    break;

case IDC_ADDCHANGEHELP:

#ifdef KKBUGFIX
    if (!WinHelp (hWnd, "WINAT.HLP", HELP_CONTENTS,0L)) {
#else
    if (!WinHelp (hWnd, "WINAT.HLP", HELP_KEY,(DWORD)(LPSTR)"CONTENTS")) {
#endif
MessageBox (GetFocus(),
#ifdef JAPAN
" w   v   N            ",
#else
"Unable to activate help",
#endif
SZWINDOWTITLE, MB_SYSTEMMODAL|MB_OK|MB_ICONHAND);
    }

    break;

    }

    break;

    }

    return (0);

}

//===========================================================================
// FUNCTION: AmPmProc()
//===========================================================================
LRESULT APIENTRY AmPmProc( HWND hWnd, UINT message, UINT uParam, LONG lParam )
{
// jean-marc - start
    // NOTE: the dialog resource for this field includes the ES_READONLY flag
    // to prevent the delete key from working, otherwise the delete key was
    // going through by somehow by-passing the WM_CHAR case.

    switch (message)
    {
      case WM_SETFOCUS:
hCurrentFocus = hWnd;
break;

      case WM_CHAR:
      {
switch(uParam)
{
  case 'A':
  case 'a':
#ifdef JAPAN
    SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)"   O" );
#else
    SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)"AM");
#endif
    return 0;

  case 'P':
  case 'p':
#ifdef JAPAN
    SendMessage( hWnd, WM_SETTEXT, 0, (LPARAM)"    " );
#else
    SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)"PM");
#endif
    return 0;

  default:
    MessageBeep(MB_OK);
    return 0;
}
      }
      return 0;
    }

    return CallWindowProc( (WNDPROC)lpAmPmDefProc, hWnd, message, uParam, lParam ) ;
// jean-marc - end
}

//===========================================================================
// FUNCTION: AreYouSure()
//
// Arguments:
//
//   Uses globals.
//
// Return Value:
//
//   ID_YES if the yes button pressed
//   ID_NO  if the  no button pressed
//
//===========================================================================
UINT
AreYouSure(
    HWND hWnd
    )
{

    return ( MessageBox(
 hWnd,
#ifdef JAPAN
                 " I         R }   h               H",
                 "     m F",
#else
 "Remove the selected command?",
 "Confirm Remove",
#endif
 MB_YESNO | MB_ICONEXCLAMATION
 )
   );

}

//===========================================================================
// FUNCTION: UpButtonProc()
//===========================================================================
LRESULT APIENTRY UpButtonProc( HWND hWnd, UINT message, UINT uParam, LONG lParam )
{
// jean-marc - start
#define TIME_DELAY 150
#define TIME_DELAY 150

  DWORD dwEventTime;

  switch (message)
  {
    case WM_LBUTTONDOWN:
      if (hCurrentFocus == hHour || hCurrentFocus == hMin || hCurrentFocus == hAmPm)
      {
dwEventTime = GetTickCount() - TIME_DELAY;
do
{
  if (dwEventTime + TIME_DELAY > GetTickCount())
    continue;
  SendMessage(hGlobalAddChg, WM_SPINBUTTON, (UINT)hCurrentFocus, (LONG)UP);
  InvalidateRect(hCurrentFocus, NULL, TRUE);
  UpdateWindow(hCurrentFocus);
  dwEventTime = GetTickCount();
}
while (GetAsyncKeyState(VK_LBUTTON) & 0x8000);
      }
      break;
  }

  return CallWindowProc((WNDPROC)lpUpButtonDefProc, hWnd, message, uParam, lParam);
// jean-marc - end
}

//===========================================================================
// FUNCTION: DownButtonProc()
//===========================================================================
LRESULT APIENTRY DownButtonProc( HWND hWnd, UINT message, UINT uParam, LONG lParam )
{
// jean-marc - start
#define TIME_DELAY 150

  DWORD dwEventTime;

  switch (message)
  {
    case WM_LBUTTONDOWN:
      if (hCurrentFocus == hHour || hCurrentFocus == hMin || hCurrentFocus == hAmPm)
      {
dwEventTime = GetTickCount() - TIME_DELAY;
do
{
  if (dwEventTime + TIME_DELAY > GetTickCount())
    continue;
  SendMessage(hGlobalAddChg, WM_SPINBUTTON, (UINT)hCurrentFocus, (LONG)DOWN);
  InvalidateRect(hCurrentFocus, NULL, TRUE);
  UpdateWindow(hCurrentFocus);
  dwEventTime = GetTickCount();
}
while (GetAsyncKeyState(VK_LBUTTON) & 0x8000);
      }
      break;
  }

  return CallWindowProc((WNDPROC)lpDownButtonDefProc, hWnd, message, uParam, lParam);
// jean-marc - end
}

/****************************************************************************

    FUNCTION: DrawBitmap

    PURPOSE:  Draw default or pushed button bitmap

****************************************************************************/

void DrawBitmap(
HDC     hDC,
LONG    xStart,
LONG    yStart,
HBITMAP hBitmap,
DWORD   rop)
{
  BITMAP        bm;
  HDC           hMemDC;
  POINT                 pt;

  hMemDC = CreateCompatibleDC (hDC);
  SelectObject (hMemDC, hBitmap);
  GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm);
  pt.x = bm.bmWidth;
  pt.y = bm.bmHeight;
  BitBlt (hDC, xStart, yStart, pt.x, pt.y, hMemDC, 0, 0, rop);
  DeleteDC (hMemDC);
}

//===========================================================================
// FUNCTION: HourProc()
//===========================================================================
LRESULT APIENTRY HourProc( HWND hWnd, UINT message, UINT uParam, LONG lParam )

{
    UINT  uCurVal;
    UCHAR szCurVal[3];

    switch ( message ) {

    case WM_COPY:
    case WM_PASTE:
    case WM_CUT:

// cut and paste dont seem to work...

break;

// jean-marc - start
      case WM_SETFOCUS:
hCurrentFocus = hWnd;
break;
// jean-marc - end

case WM_CHAR:

if ( uParam == VK_BACK ) {

if ( uHourChars > 0 ) {

--uHourChars;

}

return CallWindowProc( (WNDPROC)lpHourDefProc, hWnd, message, uParam, lParam ) ;

} else {

uHourChars = (UINT) SendMessage( hWnd, WM_GETTEXT, 2, (LPARAM)szCurVal );

if ( uHourChars == 0 ) {

uCurVal = 0;

    } else if ( uHourChars == 1 ) {

uCurVal = szCurVal[0] - '0';

    } else if ( uHourChars == 2 ) {

uCurVal  = szCurVal[0] - '0';
uCurVal *= 10;
uCurVal  = szCurVal[1] - '0';

    } else if ( uHourChars > 2 ) {

uHourChars = 0;

SendMessage( hWnd, WM_SETTEXT, 0, (LPARAM)"" );

return( 0 );

    }

}

if ( ( uParam == ' ' )                                     ||  // space
     ( uParam < '0' || uParam > '9')                       ||  // not a number
     ( uParam == '0' && uCurVal == 0 )                     ||  // trying to enter 0 as first char
     ( (uParam > '2' && uCurVal >= 1) && uHourChars >= 1)  ||  // greater than 12
     ( uParam <= '2' && uCurVal > 1)                           // greater than 12
   ) {

 MessageBeep( MB_OK );

 return( 0 );

} else if ( uHourChars < 2 ) {

++uHourChars;

} else {

    MessageBeep( MB_OK );

    return( 0 );

}

break;

} // switch

return CallWindowProc( (WNDPROC)lpHourDefProc, hWnd, message, uParam, lParam ) ;

}

//===========================================================================
// FUNCTION: JobAdd()
//
// Routine Description:
//
//     Adds a new item to schedule.
//
// Arguments:
//
//     None.  Uses globals.
//
// Return Value:
//
//     NET_API_STATUS return value of remote api call
//
//===========================================================================
NET_API_STATUS
JobAdd(
    VOID
    )
{

    NET_API_STATUS          status;

    status = NetScheduleJobAdd(
GlobalComputerName,
(LPBYTE)&GlobalAtInfo,
&GlobalJobId
);

    if ( status != NERR_Success ) {

/*MessageBox(
    hGlobalhWnd,
    ( status == NERR_ServiceNotInstalled ? "The service has not been started" : "Message could not be added." ),
    SZWINDOWTITLE,
    MB_OK | MB_ICONEXCLAMATION
    );
*/

MessagePrint( status );
    }

    return ( status );

}

//===========================================================================
//
// FUNCTION: JobDel()
//
//
// Routine Description:
//
//     This does all of the processing necessary to dump out the entire
//     schedule file.  It loops through on each record and formats its
//     information for printing and then goes to the next.
//
// Arguments:
//
//     DEL_ALL if to delete all jobs, DEL_ID otherwise.
//     Uses globals.
//
// Return Value:
//
//     ERROR_SUCCESS                       if everything enumerated OK
//     error returned by remote api        otherwise
//
//===========================================================================
NET_API_STATUS
JobDel(
    UINT Method,
    HWND hWnd
    )
{

    DWORD                       dwSelection;
    NET_API_STATUS  status;
    UCHAR           szAtCommand[MAX_ATCMD_LEN];
    UINT            JobId;
    UINT                        uSelection;
    UINT                        uCount;
    char *          ptr;

    if ( Method != DEL_ID_CHANGE ) {

if ( AreYouSure(hWnd) == IDNO ) {

return( ERROR_SUCCESS );

}

    }

    switch( Method ) {

case DEL_ALL:

    status = NetScheduleJobDel(
    GlobalComputerName,
    0,
    (DWORD)-1
    );

    if ( status == NERR_Success ) {

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_NONE );
break;

    }

    break;

case DEL_ID:
case DEL_ID_CHANGE:

    // compute id

    uSelection = SendDlgItemMessage( hWnd, IDC_ATCOMMANDS, LB_GETCURSEL, 0, 0 );
    uCount     = SendDlgItemMessage( hWnd, IDC_ATCOMMANDS, LB_GETCOUNT,  0, 0 );

    SendDlgItemMessage(
hWnd,
IDC_ATCOMMANDS,
LB_GETTEXT,
uSelection,
(LPARAM)szAtCommand
);

    JobId = 0;
    ptr   = szAtCommand;

    while( *ptr >= '0' && *ptr <= '9' ) {

JobId *= 10;
JobId += (UINT)*ptr - '0';

ptr++;

    }

    status = NetScheduleJobDel(
    GlobalComputerName,
    JobId,
    JobId
    );

    if ( status != NERR_Success) {

//MessageBox( hWnd, "Problem deleteing a job.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

MessagePrint( status );

break;

    }

    if ( uCount == 1 ) {

// this was the only job

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_NONE );

    } else if ( uSelection == 0 ) {

// the first/top job was selected and there is more than 1 job

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_TOP );

    } else if ( ( uCount - 1 ) == uSelection ) {

// the last/bottom job was selected

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_BOTTOM );

    } else {

// the 2nd thru last-1 job was selected

dwSelection  = 0;
dwSelection |= uSelection << 16;
dwSelection |= SCS_SELINHIGHWORD;

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)dwSelection );

    }

    break;

    }

    return( status );

}

//===========================================================================
//
// FUNCTION: JobEnum()
//
//
// Routine Description:
//
//     This does all of the processing necessary to dump out the entire
//     schedule file.  It loops through on each record and formats its
//     information for printing and then goes to the next.
//
// Arguments:
//
//     None.  Uses globals.
//
// Return Value:
//
//     ERROR_SUCCESS                       if everything enumerated OK
//     error returned by remote api        otherwise
//
//===========================================================================
NET_API_STATUS
JobEnum(
    DWORD SetCurrentSelection
    )
{

    BOOL            fFlag;
    BOOL            first = TRUE;
    DWORD           EntriesRead;
    DWORD           ResumeJobId = 0;
    DWORD           TotalEntries;
    LPVOID          EnumBuffer = NULL;
    NET_API_STATUS  status = NERR_Success;
    PAT_ENUM        pAtEnum;
    //UCHAR           szTemp[80];
    static UINT         uSelection;
    static UINT         uTopIndex;
    int             i;

    // initialize to current selection

uSelection = SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_GETCURSEL,   0, 0 );
uTopIndex  = SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_GETTOPINDEX, 0, 0 );

if ( uSelection == LB_ERR ) {

uSelection = 0;

}

    for ( ; ;) {

status = NetScheduleJobEnum(
GlobalComputerName,
(LPBYTE *)&EnumBuffer,
(DWORD)-1,
&EntriesRead,
&TotalEntries,
&ResumeJobId
);

if ( status != ERROR_SUCCESS  &&  status != ERROR_MORE_DATA) {

    //
    // not needded since both the if/else(MessagePrint) will set this text now
    //
    //SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_RESETCONTENT, 0, 0 );
    //
    //SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_ADDSTRING, 0, (LPARAM)szError );

    EnableWindow( GetDlgItem( hGlobalhWnd, IDC_ADD    ), FALSE );
    EnableWindow( GetDlgItem( hGlobalhWnd, IDC_CHANGE ), FALSE );
EnableWindow( GetDlgItem( hGlobalhWnd, IDC_REMOVE ), FALSE );

if ( status == NERR_ServiceNotInstalled ) {

SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_RESETCONTENT, 0, 0 );

#ifdef JAPAN
                SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_ADDSTRING, 0, (LPARAM)"                 N  " );

#else
SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_ADDSTRING, 0, (LPARAM)"Schedule service not started." );
#endif

if ( ServiceNotStartedHandler() == FALSE ) {

return( status );

} else {

    continue;

}

} else  {

/*wsprintf( szTemp, "The computer %s cannot be found.\nError %u.", &szComputerM[2], status );

    MessageBox(
 hGlobalhWnd,
szTemp,
SZWINDOWTITLE,
MB_OK | MB_ICONEXCLAMATION
);
    */

MessagePrint( status );

    return( status );

}

}

assert( status == ERROR_SUCCESS ? TotalEntries == EntriesRead : TotalEntries > EntriesRead );

if ( TotalEntries == 0) {

    break;  //  no items found

}

if ( first == TRUE) {

    ResetListboxExtents( GetDlgItem( hGlobalhWnd, IDC_ATCOMMANDS ) );
    SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_RESETCONTENT, 0, 0 );

    first = FALSE;

}

for ( pAtEnum = EnumBuffer;  EntriesRead-- > 0;  pAtEnum++) {

    // not sure what this is doing...

    /*
    if ( pAtEnum->Flags & JOB_EXEC_ERROR) {

if ( MessageGet( APE2_GEN_ERROR, &smallBuffer, 0 ) == 0) {

    // error reported already
    exit( -1);

}

printf( DUMP_FMT1, smallBuffer );
LocalFree( smallBuffer );

    } else {

printf( DUMP_FMT1, L"");

    }
    */

    // replate with...

    if ( pAtEnum->Flags & JOB_EXEC_ERROR) {

MessagePrint( APE2_GEN_ERROR );

    }

    szWhenDay[0] = '\0';

    if ( pAtEnum->Flags & JOB_RUN_PERIODICALLY ) {

#ifdef JAPAN
                lstrcat( szWhenDay, "   " );
#else
lstrcat( szWhenDay, "Each " );
#endif

    } else if ( pAtEnum->DaysOfWeek != 0 || pAtEnum->DaysOfMonth != 0 ) {

#ifdef JAPAN
                lstrcat( szWhenDay, "     " );
#else
lstrcat( szWhenDay, "Next " );
#endif

    } else if ( pAtEnum->Flags & JOB_RUNS_TODAY ) {

#ifdef JAPAN
                lstrcat( szWhenDay, "     " );
#else
lstrcat( szWhenDay, "Today " );
#endif

    } else {

#ifdef JAPAN
                lstrcat( szWhenDay, "     " );
#else
lstrcat( szWhenDay, "Tomorrow " );
#endif

    }

    if ( pAtEnum->DaysOfWeek != 0 ) {

for ( i=0; i<7; ++i ) {

    if ( ( pAtEnum->DaysOfWeek & ( 1 << i ) ) != 0 ) {

lstrcat( szWhenDay, szDayAbbr[i] );

    }

}

    }

    if ( pAtEnum->DaysOfMonth != 0 ) {

for ( i=0; i<31; ++i ) {

    if ( ( pAtEnum->DaysOfMonth & ( 1L << i ) ) != 0 ) {

wsprintf( szDateAbbr, "%d ", i+1 );

lstrcat( szWhenDay, szDateAbbr );
#ifdef JAPAN
                        lstrcat( szWhenDay, "  " );
#endif
    }

}

    }

    dwTime     = pAtEnum->JobTime / 1000 / 60;       // total minutes

    szTime[0]  = (UCHAR)( dwTime / 600 ) + '0';      // 10s of hours
    dwTime    %= 600;

    szTime[1]  = (UCHAR)( dwTime / 60 )  + '0';      // 1s of hours
    dwTime    %= 60;

    szTime[2]  = ':';

    szTime[3]  = (UCHAR)( dwTime / 10 )  + '0';      // 10s of minutes
    dwTime    %= 10;

    szTime[4]  = (UCHAR)( dwTime )       + '0';      // 1s of minutes

    szTime[5]  = '\0';

    // convert this 24-hour time to AM/PM form

    szTime[5] = 'A';
    szTime[6] = 'M';
    szTime[7] = '\0';

// if it is past 1000 but before 2000 (24-hour time)

    if ( szTime[0] == '1' ) {

// if it is past 1100

if ( szTime[1] >= '2' ) {

szTime[5] = 'P';

}

    if( szTime [1] > '2' ) {

szTime[0] -= 1;
szTime[1] -= 2;

    }

    } else if ( szTime[0] == '2' ) {

// it is 2000 or later

szTime[5] = 'P';

    // if it is 2200 or later

    if( szTime [1] >= '2' ) {

szTime[0] -= 1;
szTime[1] -= 2;

    } else {

szTime[0] -= 2;
szTime[1] += 8;

    }

}

if ( szTime[0] == '0' ) {

if ( szTime[1] == '0' ) {

// 12 am or pm

szTime[0] = '1';
szTime[1] = '2';

} else {

szTime[0] = ' ';

}

}

#ifdef JAPAN
            {
                char szBuf[sizeof(szTime)];

                strcpy(szBuf, szTime);
                strcpy(szTime, szBuf[5] == 'A' ? "   O " : "     ");
                szBuf[5] = '\0';
                strcat(szTime, szBuf);
            }
#endif

    WideCharToMultiByte(
CP_ACP,
0,
pAtEnum->Command,
-1,
szCommandM,
MAXCOMMANDLEN,
szDefault,
&fFlag
);

    wsprintf( szAtCommand, "%d\t", pAtEnum->JobId );
    lstrcat( szAtCommand, szWhenDay  );
    lstrcat( szAtCommand, "\t"       );
    lstrcat( szAtCommand, szTime     );
    lstrcat( szAtCommand, "\t"       );
    lstrcat( szAtCommand, szCommandM );

    WAddExtentEntry( GetDlgItem( hGlobalhWnd, IDC_ATCOMMANDS ), szAtCommand );
    SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_ADDSTRING, 0, (LPARAM)szAtCommand );

}

if ( EnumBuffer != NULL ) {

    (VOID)NetApiBufferFree( (LPVOID)EnumBuffer );
    EnumBuffer = NULL;

}

if ( status == ERROR_SUCCESS ) {

    break;  //  we have read & displayed all the items

}

    } // for

    if ( first == TRUE) {

ResetListboxExtents( GetDlgItem( hGlobalhWnd, IDC_ATCOMMANDS ) );
SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_RESETCONTENT, 0, 0 );
WAddExtentEntry( GetDlgItem( hGlobalhWnd, IDC_ATCOMMANDS ), szNoEntries );
SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_ADDSTRING, 0, (LPARAM)szNoEntries );

EnableWindow( GetDlgItem( hGlobalhWnd, IDC_ADD ), TRUE );
EnableWindow( GetDlgItem( hGlobalhWnd, IDC_CHANGE ), FALSE );
EnableWindow( GetDlgItem( hGlobalhWnd, IDC_REMOVE ), FALSE );

    } else {

switch ( LOWORD( SetCurrentSelection ) ) {

case SCS_BOTTOM:

uSelection = SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_GETCOUNT, 0, 0 ) - 1;

SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_SETCURSEL,  uSelection,     0 );

break;

    case SCS_NOCHANGE:

// uSelection has already been set

    SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_SETCURSEL,  uSelection, 0 );

    break;

case SCS_TOP:

uSelection = 0;

SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_SETCURSEL,  uSelection,     0 );

break;

case SCS_SELINHIGHWORD:

    uSelection = HIWORD( SetCurrentSelection );

SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_SETCURSEL,  uSelection,     0 );

break;

case SCS_NONE:
default:

break;

}

SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_SETTOPINDEX,  uTopIndex,    0 );

EnableWindow( GetDlgItem( hGlobalhWnd, IDC_ADD ), TRUE );
EnableWindow( GetDlgItem( hGlobalhWnd, IDC_CHANGE ), TRUE );
EnableWindow( GetDlgItem( hGlobalhWnd, IDC_REMOVE ), TRUE );

    }

    return( ERROR_SUCCESS );

}

//===========================================================================
// FUNCTION: MessagePrint()
//===========================================================================
DWORD
MessagePrint(
    IN      DWORD       MessageId,
    ...
    )
/*++

Routine Description:

    Finds the unicode message corresponding to the supplied message id,
    merges it with caller supplied string(s), and prints the resulting
    string.

Arguments:

    MessageId       -   message id

Return Value:

    Count of characters, not counting the terminating null character,
    printed by this routine.  Zero return value indicates failure.

--*/
{
    va_list             arglist;
    UCHAR *             buffer = NULL;
    UCHAR *                     ptr;
    DWORD               length;
    LPVOID              lpSource;
    DWORD               dwFlags = FORMAT_MESSAGE_ARGUMENT_ARRAY |
  FORMAT_MESSAGE_ALLOCATE_BUFFER;

    va_start( arglist, MessageId );

    if ( MessageId < NERR_BASE) {
//
//  Get message from system.
//
lpSource = NULL; // redundant step according to FormatMessage() spec
dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;

    } else if (  ( MessageId >= APE2_AT_DEL_WARNING
    &&  MessageId <= APE2_AT_DI_COMMAND)
      //
      // I cannot find these definitions!!!!
      //
      //|| ( MessageId >= IDS_LOAD_LIBRARY_FAILURE
      //      &&  MessageId <= IDS_USAGE )
      ) {
//
//  Get message from this module.
//
lpSource = NULL;
dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;

    } else {
//
//  Get message from netmsg.dll.
//
lpSource = GlobalMessageHandle;
dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
    }

    length = FormatMessage(
    dwFlags,                                          //  dwFlags
    lpSource,                                         //  lpSource
    MessageId,                                        //  MessageId
    0L,                                               //  dwLanguageId
    (LPTSTR)&buffer,                                 //  lpBuffer
    0,                                                //  size
    (va_list *) &arglist                              //  lpArguments
    );

    if ( length != 0) {

//change to MessageBox
//printf( "%ws", buffer );

ptr = buffer;

while ( *ptr ) {
#ifdef DBCS
            if (IsDBCSLeadByte(*ptr)) {
                ptr++;
            } else
#endif
    if ( ( *ptr < ' ' ) || ( *ptr > '~' ) ) {

*ptr = ' ';

    }

ptr++;

}

SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_RESETCONTENT, 0, 0 );

SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_ADDSTRING, 0, (LPARAM)(LPTSTR)buffer );

MessageBox( hGlobalhWnd, (LPCTSTR)buffer, SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

LocalFree( buffer );

    }

    return( length);

} // MessagePrint()

//===========================================================================
// FUNCTION: MinProc()
//===========================================================================
LRESULT APIENTRY MinProc( HWND hWnd, UINT message, UINT uParam, LONG lParam )
{
    UINT  uCurVal;
    UCHAR szCurVal[3];

    switch ( message ) {

    case WM_COPY:
    case WM_PASTE:
    case WM_CUT:

// cut and paste dont seem to work...

break;

// jean-marc - start
      case WM_SETFOCUS:
hCurrentFocus = hWnd;
break;
// jean-marc - end

case WM_CHAR:

if ( uParam == VK_BACK ) {

if ( uMinChars > 0 ) {

--uMinChars;

}

return CallWindowProc( (WNDPROC)lpMinDefProc, hWnd, message, uParam, lParam ) ;

} else {

uMinChars = (UINT) SendMessage( hWnd, WM_GETTEXT, 2, (LPARAM)szCurVal );

if ( uMinChars == 0 ) {

uCurVal = 0;

    } else if ( uMinChars == 1 ) {

uCurVal = szCurVal[0] - '0';

    } else if ( uMinChars == 2 ) {

uCurVal  = szCurVal[0] - '0';
uCurVal *= 10;
uCurVal  = szCurVal[1] - '0';

    } else if ( uMinChars > 2 ) {

uMinChars = 0;

SendMessage( hWnd, WM_SETTEXT, 0, (LPARAM)"" );

return( 0 );

    }

}

if ( ( uParam == ' ' )                  ||  // space
     ( uParam < '0' || uParam > '9' )   ||  // not a number
     ( uCurVal >= 6  )                      // greater than 59
   ) {

 MessageBeep( MB_OK );

 return( 0 );

} else if ( uMinChars < 2 ) {

++uMinChars;

} else {

    MessageBeep( MB_OK );

    return( 0 );

}

break;

} // switch

return CallWindowProc( (WNDPROC)lpMinDefProc, hWnd, message, uParam, lParam ) ;

}

//===========================================================================
// FUNCTION: Refresh()
//===========================================================================
UINT
Refresh(UINT Command)
{

    static BOOL fOkayToRefresh;
static BOOL     fOkayToRefreshSave;

switch( Command ) {

case REFRESH_FALSE:

    fOkayToRefresh = FALSE;

break;

case REFRESH_TRUE:

    fOkayToRefresh = TRUE;

break;

case REFRESH_QUERY:

return( fOkayToRefresh );

break;

case REFRESH_SAVE:

    fOkayToRefreshSave = fOkayToRefresh;

break;

case REFRESH_RESTORE:

    fOkayToRefresh = fOkayToRefreshSave;

break;
}

return( REFRESH_ERROR );

}

//===========================================================================
// FUNCTION: SelectComputerProc()
//===========================================================================
BOOL APIENTRY SelectComputerProc(
HWND hWnd,         // window handle
UINT message,      // type of message
UINT uParam,       // additional information
LONG lParam)       // additional information
{

    DWORD           dwLen = MAXCOMPUTERNAMELEN + 1;
    UCHAR           szSelectComputer[MAXCOMPUTERNAMELEN+1];
    int             wmEvent;
    int             wmId;
// jean-marc - start
    UCHAR           szComputerMTemp[MAXCOMPUTERNAMELEN+1];  // multibyte comp. name
// jean-marc - end

    switch (message) {

case WM_INITDIALOG:
// jean-marc - start
    // set the text limit for the IDC_SELECTCOMPUTER edit field to be
    // MAXCOMPUTERNAMELEN
    SendDlgItemMessage(hWnd,
       IDC_SELECTCOMPUTER,
       EM_LIMITTEXT,
       MAXCOMPUTERNAMELEN,
       0);
// jean-marc - end

    if ( !GetComputerName( szSelectComputer, &dwLen ) ) {

#ifdef JAPAN
MessageBox( hWnd, " R   s   [ ^       G   [.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#else
MessageBox( hWnd, "Problem getting computer name.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#endif
} else {

SetDlgItemText( hWnd, IDC_SELECTCOMPUTER, szSelectComputer );

}

break;

case WM_COMMAND:    // message: command from application menu

    // Message packing of uParam and lParam have changed for Win32, let us
    // handle the differences in a conditional compilation:

wmId    = LOWORD(uParam);
wmEvent = HIWORD(uParam);

switch (wmId) {

case IDCANCEL:

EndDialog( hWnd, IDCANCEL );

break;

case IDOK:

    GetDlgItemText( hWnd, IDC_SELECTCOMPUTER, &szComputerMTemp[0], MAXCOMPUTERNAMELEN );

// jean-marc - start
    // make sure backslashes are there, if not then add them
    if (szComputerMTemp[0] == '\\' && szComputerMTemp[1] == '\\')
      lstrcpy(szComputerM, szComputerMTemp);
    else if (szComputerMTemp[0] != '\\' && szComputerMTemp[1] != '\\')
      wsprintf(szComputerM, "\\\\%s", szComputerMTemp);
    else if (szComputerMTemp[0] == '\\' || szComputerMTemp[1] == '\\')
      wsprintf(szComputerM, "\\%s", szComputerMTemp);
    if (lstrlen(szComputerM) > MAXCOMPUTERNAMELEN || lstrlen(szComputerM) == 2)
#ifdef JAPAN
                    MessageBox( hWnd, " R   s   [ ^     P     P T    (   p)                           B", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#else
      MessageBox( hWnd, "Computer names are 1 to 15 characters long.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#endif
// jean-marc - end

    else {

    SetDlgItemText( hGlobalhWnd, IDC_COMPUTER, &szComputerM[2] );

MultiByteToWideChar(
CP_ACP,
MB_PRECOMPOSED,
szComputerM,
-1,
szComputerW,
MAXCOMPUTERNAMELEN
);

GlobalComputerName = szComputerW;

Refresh( REFRESH_TRUE );

    EndDialog( hWnd, IDOK );

}

    break;

    }

break;

    }

    return (0);

}

//===========================================================================
// FUNCTION: ServiceNotStartedHandler()
//===========================================================================
BOOL
ServiceNotStartedHandler(VOID)
{

    BOOL            Done;
    SC_HANDLE       schSCManager;
    SC_HANDLE       schService;
    SERVICE_STATUS  ssServiceStatus;
    UCHAR           szService[] = "Schedule";
    UCHAR           szTemp[80];

#ifdef JAPAN
    wsprintf( szTemp, "%s    X P W   [   T [ r X   J n               B\n J n         H", &szComputerM[2] );
#else
    wsprintf( szTemp, "Schedule service on %s not started.\nWould you like to start it?", &szComputerM[2] );
#endif

if ( MessageBox( hGlobalhWnd, szTemp, SZWINDOWTITLE, MB_YESNO ) == IDYES ) {

    schSCManager = OpenSCManager( szComputerM, NULL, SC_MANAGER_ALL_ACCESS );

    if ( schSCManager == NULL ) {

#ifdef JAPAN
    wsprintf( szTemp, " T [ r X R   g   [    } l [ W    A N Z X G   [\n\nError %d.", GetLastError() );
#else
wsprintf( szTemp, "Problem accessing Service Control Manager.\n\nError %d.", GetLastError() );
#endif

MessageBox( hGlobalhWnd, szTemp, SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

    return( FALSE );

    } else {

schService = OpenService( schSCManager, szService, SERVICE_ALL_ACCESS );

if ( schService == NULL ) {

#ifdef JAPAN
    wsprintf( szTemp, " X P [ W   [    T [ r X  I [ v    G   [\n\nError %d.", GetLastError() );
#else
wsprintf( szTemp, "Problem opening Schedule service.\n\nError %d.", GetLastError() );
#endif

MessageBox( hGlobalhWnd, szTemp, SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

return( FALSE );

    } else {

if ( StartService( schService, 0, NULL ) == FALSE ) {

#ifdef JAPAN
    wsprintf( szTemp, " X P W   [    T [ r X  J n  G   [\n\nError %d.", GetLastError() );
#else
wsprintf( szTemp, "Problem starting Schedule service.\n\nError %d.", GetLastError() );
#endif

MessageBox( hGlobalhWnd, szTemp, SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

return( FALSE );

    } else {

// wait for service to start

Done = FALSE;

while( !Done ) {

if ( QueryServiceStatus( schService, &ssServiceStatus ) == FALSE ) {

#ifdef JAPAN
    wsprintf( szTemp, " T [ r X  X e C ^ X     G   [\n\nError %d.", GetLastError() );
#else
wsprintf( szTemp, "Problem getting service status.\n\nError %d.", GetLastError() );
#endif

MessageBox( hGlobalhWnd, szTemp, SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

return( FALSE );

} else {

switch ( ssServiceStatus.dwCurrentState ) {

case SERVICE_START_PENDING:

Sleep( ssServiceStatus.dwWaitHint );

break;

case SERVICE_RUNNING:

    Done = TRUE;

break;

default:

#ifdef JAPAN
wsprintf( szTemp, " \           T [ r X  X e C ^ X\n\nState %d.", ssServiceStatus.dwCurrentState );
#else
wsprintf( szTemp, "Unexpected service state.\n\nState %d.", ssServiceStatus.dwCurrentState );
#endif

MessageBox( hGlobalhWnd, szTemp, SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

Done = TRUE;

break;

}

}

}

    }

CloseServiceHandle(schService);

}

CloseServiceHandle(schSCManager);

    }

SendDlgItemMessage( hGlobalhWnd, IDC_ATCOMMANDS, LB_RESETCONTENT, 0, 0 );

SendDlgItemMessage(
hGlobalhWnd,
IDC_ATCOMMANDS,
LB_ADDSTRING,
0,
#ifdef JAPAN
        (LPARAM)" X P W   [    T [ r X         J n           B" );
#else
(LPARAM)"The Schedule service was started successfully." );
#endif

MessageBox(
hGlobalhWnd,
#ifdef JAPAN
" X P W   [    T [ r X         J n           B",
#else
"The Schedule service was started successfully.",
#endif
SZWINDOWTITLE,
MB_OK );

return( TRUE );

} else {

    Refresh( REFRESH_FALSE );

MessageBox(
hGlobalhWnd,
#ifdef JAPAN
" \         I     X V           B \     X V             A[ t @ C  ]   j   [  [ R   s   [ ^   I  ]   I             B",
#else
"The display will not be refreshed automatically.  Chose File, Select Computer to refresh the display.",
#endif
SZWINDOWTITLE,
MB_OK | MB_ICONEXCLAMATION );

}

return( FALSE );

}

//===========================================================================
// FUNCTION: ValidateAddChangeArguments()
//===========================================================================
BOOL
ValidateAddChangeArguments(
HWND hWnd
)
{

    SYSTEMTIME              SysTime;
#ifdef JAPAN
    UCHAR                   szAmPm[5];
#else
    UCHAR                   szAmPm[3];
#endif
    UINT                    Items[NUMDAYITEMS];
    UINT                    NumSelected;
    UINT                    i;

// initialize

    memset( (PBYTE)&GlobalAtInfo, '\0', sizeof(GlobalAtInfo) );

// command

    SendDlgItemMessage( hWnd, IDC_NEWCOMMAND, WM_GETTEXT, MAXCOMMANDLEN, (LPARAM)szCommandM );

    if ( szCommandM[0] == 0 ) {

MessageBox(
    hWnd,
#ifdef JAPAN
            " R }   h                   B",
#else
    "Enter a command.",
#endif
    SZWINDOWTITLE,
    MB_OK | MB_ICONEXCLAMATION
    );

SetFocus( GetDlgItem( hWnd, IDC_NEWCOMMAND ) );

return ( FALSE );

    }

MultiByteToWideChar(
CP_ACP,
MB_PRECOMPOSED,
szCommandM,
-1,
szCommandW,
MAXCOMMANDLEN
);

    GlobalAtInfo.Command = szCommandW;

// hours

    GlobalAtInfo.JobTime = GetDlgItemInt( hWnd, IDC_HOUR, NULL, FALSE );

    GetDlgItemText( hWnd, IDC_AMPM, (LPTSTR)&szAmPm, sizeof(szAmPm) );

#ifdef JAPAN
    if (strcmp(szAmPm, "   O")) {
#else
    if ( szAmPm[0] == 'P' || szAmPm[0] == 'p' ) {
#endif
if( GlobalAtInfo.JobTime != 12 ) {

GlobalAtInfo.JobTime += 12;

}

    } else if( GlobalAtInfo.JobTime == 12 ) {

GlobalAtInfo.JobTime = 0;

    }

    // convert to minutes

GlobalAtInfo.JobTime *= 60;

// add minutes

GlobalAtInfo.JobTime += GetDlgItemInt( hWnd, IDC_MIN, NULL, FALSE );

// convert to milliseconds

GlobalAtInfo.JobTime *= 60 * 1000;

GetLocalTime( &SysTime );

    if ( SendDlgItemMessage( hWnd, IDC_TODAY, BM_GETCHECK, 0, 0 ) ) {

if ( (DWORD)( (SysTime.wHour * 60 + SysTime.wMinute) * 60 * 1000) >= GlobalAtInfo.JobTime ) {

MessageBox(
hWnd,
#ifdef JAPAN
" w                               B     R }   h         s         B",
#else
"The time you have selected has already passed.  The command will run tomorrow.",
#endif
SZWINDOWTITLE,
MB_OK | MB_ICONEXCLAMATION
);

}

}

if ( SendDlgItemMessage( hWnd, IDC_EVERY, BM_GETCHECK, 0, 0 ) ||
     SendDlgItemMessage( hWnd, IDC_NEXT, BM_GETCHECK, 0, 0 )    ) {

// day
// NOTE: I am assuming that this lisbox will always be correct
//       and if you select Today/Tomorrow the proper day is selected.

NumSelected = SendDlgItemMessage( hWnd, IDC_DAYS, LB_GETSELCOUNT, 0, 0 );

if ( NumSelected == 0 ) {

MessageBox(
    hWnd,
#ifdef JAPAN
            " R }   h     s         I             B",
#else
    "Select the day(s) this command will run.",
#endif
    SZWINDOWTITLE,
    MB_OK | MB_ICONEXCLAMATION
    );

SetFocus( GetDlgItem( hWnd, IDC_DAYS ) );

return ( FALSE );

}

SendDlgItemMessage( hWnd, IDC_DAYS, LB_GETSELITEMS, NUMDAYITEMS, (LPARAM)Items );

for( i=0; i<NumSelected; ++i ) {

if ( Items[i] > 6 ) {

// days of month
//0-based, 7 and up are dates (1-31)

    GlobalAtInfo.DaysOfMonth |= ( 1 << ( Items[i]-7 ) );

} else {

    // days of week
    // 0-based, first 7 are days (M-Su)

GlobalAtInfo.DaysOfWeek |= ( 1 << Items[i] );

}

}

// flags

if ( SendDlgItemMessage( hWnd, IDC_EVERY, BM_GETCHECK, 0, 0 ) ) {

    GlobalAtInfo.Flags |= JOB_RUN_PERIODICALLY;

}

} else if ( SendDlgItemMessage( hWnd, IDC_TOMORROW, BM_GETCHECK, 0, 0 ) ) {

i = SendDlgItemMessage( hWnd, IDC_DAYS, LB_GETCURSEL, 0, 0 );

if ( i == 0 ) {

    // set to Sunday

GlobalAtInfo.DaysOfWeek = ( 1 << 6 );

} else {

    // increment to tomorrow

GlobalAtInfo.DaysOfWeek |= ( 1 << i );

}

}

return( TRUE );

}

//===========================================================================
// FUNCTION: WINATDlgProc()
//===========================================================================
BOOL APIENTRY WINATDlgProc(
HWND hWnd,         // window handle
UINT message,      // type of message
UINT uParam,       // additional information
LONG lParam)       // additional information
{

    FARPROC         lpAddChangeDlgProc;  // pointer to the "AddChange" function
    FARPROC         lpProcSelect;
    NET_API_STATUS  status;
    int             wmEvent;
    int             wmId;
    int                         iDlgRetCode;
// jean-marc - start
    LOGFONT         lfComputerName;
    static HFONT    hFontComputerName = NULL;
    BOOL            bGetComputerName = FALSE;
// jean-marc - end

    switch (message) {

case WM_INITDIALOG:
// jean-marc - start
    // retrieve the font of the field IDC_COMPUTER
    hFontComputerName = (HFONT)SendDlgItemMessage(hWnd,
  IDC_COMPUTER,
  WM_GETFONT,
  0,
  0L);

    // now get the logfont description of the font handle
    if (GetObject(hFontComputerName, sizeof(LOGFONT), &lfComputerName))
    {
// now make the font underlined
lfComputerName.lfUnderline = 1;

// next recreate a font handle from this logfont
hFontComputerName = CreateFontIndirect(&lfComputerName);

// finally set the font handle for the edit field
if (hFontComputerName)
  SendDlgItemMessage(hWnd,
     IDC_COMPUTER,
     WM_SETFONT,
     (WPARAM)hFontComputerName,
     1L);
    }
// jean-marc - end
#ifdef JAPAN
#else
    lf.lfEscapement    = 0;
    lf.lfOrientation   = 0;
    lf.lfOutPrecision  = OUT_DEFAULT_PRECIS;
    lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
    lf.lfHeight        = 14;
    lf.lfWidth         = 0; //6; //5
    lf.lfWeight        = 0;

    lf.lfItalic        = 0;
    lf.lfUnderline     = 0;
    lf.lfStrikeOut     = 0;

    lf.lfPitchAndFamily=VARIABLE_PITCH | FF_SWISS;

    hFont = CreateFontIndirect( &lf );

    if ( hFont != NULL ) {

SendDlgItemMessage( hWnd, IDC_ATCOMMANDS, WM_SETFONT, (WPARAM)hFont, 1L );

    } else {

MessageBox( hWnd, "Problem creating font.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );

    }
#endif // !JAPAN

    hCursorWait = LoadCursor( NULL, IDC_WAIT );

    if ( !FInitListboxExtents( GetDlgItem( hWnd, IDC_ATCOMMANDS ) ) ) {

#ifdef JAPAN
                MessageBox( hWnd, "       l     s", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#else
MessageBox( hWnd, "Scrolling broken", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#endif

    }

    hGlobalhWnd = hWnd;

    // get values from registry

    SendMessage(hWnd, WM_COMMAND, IDM_GET_FROM_REGISTRY, 0);

    // set tabstops for commands

    {

#ifdef JAPAN
                UINT Tabs[3] = { 22, 72, 124 };
#else
UINT Tabs[3] = { 25, 110, 145 };
#endif

SendDlgItemMessage( hWnd, IDC_ATCOMMANDS, LB_SETTABSTOPS, 3, (LPARAM)Tabs );

    }

// get local computer name

{

DWORD dwLen = MAXCOMPUTERNAMELEN;

// jean-marc - start
// process the commandline
if (gszCommandLine[0])
{
  // make sure backslashes are there, if not then add them
  if (gszCommandLine[0] == '\\' && gszCommandLine[1] == '\\')
    lstrcpy(szComputerM, gszCommandLine);
  else if (gszCommandLine[0] != '\\' && gszCommandLine[1] != '\\')
    wsprintf(szComputerM, "\\\\%s", gszCommandLine);
  else if (gszCommandLine[0] == '\\' || gszCommandLine[1] == '\\')
    wsprintf(szComputerM, "\\%s", gszCommandLine);
  if (lstrlen(szComputerM) > MAXCOMPUTERNAMELEN || lstrlen(szComputerM) == 2)
  {
#ifdef JAPAN
                        MessageBox( hWnd, " R   s   [ ^     P     P T    (   p)                           B", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#else
    MessageBox( hWnd, "Computer names are 1 to 15 characters long.\nUsing local computer name.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#endif
    bGetComputerName = GetComputerName( &szComputerM[2], &dwLen );
    if (!bGetComputerName)
#ifdef JAPAN
MessageBox( hWnd, " R   s   [ ^       G   [", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#else
      MessageBox( hWnd, "Problem getting computer name", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#endif
  }
  else
    bGetComputerName = TRUE;
}
else
{
  bGetComputerName = GetComputerName( &szComputerM[2], &dwLen );
  if (!bGetComputerName)
#ifdef JAPAN
    MessageBox( hWnd, " R   s   [ ^       G   [", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#else
    MessageBox( hWnd, "Problem getting computer name", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#endif
}
// jean-marc - end

    if (bGetComputerName) {

    szComputerM[0] = '\\';
szComputerM[1] = '\\';

    MultiByteToWideChar(
CP_ACP,
MB_PRECOMPOSED,
szComputerM,
-1,
szComputerW,
MAXCOMMANDLEN
);

    // set GlobalComputerName for all functions to use
// only Select Computer will ever change this value

    GlobalComputerName = szComputerW;

SendDlgItemMessage( hWnd, IDC_COMPUTER, WM_SETTEXT, 0, (LPARAM)&szComputerM[2]  );

}

}

    // set initial states of buttons to disabled

    EnableWindow( GetDlgItem( hGlobalhWnd, IDC_ADD    ), FALSE );
    EnableWindow( GetDlgItem( hGlobalhWnd, IDC_CHANGE ), FALSE );
EnableWindow( GetDlgItem( hGlobalhWnd, IDC_REMOVE ), FALSE );

// initialize the commands window

#ifdef JAPAN
        SendDlgItemMessage( hWnd, IDC_ATCOMMANDS, LB_ADDSTRING, 0, (LPARAM)"        " );
#else
SendDlgItemMessage( hWnd, IDC_ATCOMMANDS, LB_ADDSTRING, 0, (LPARAM)"Initializing" );
#endif

// set up timer to go off very soon after the dlgbox is created

    uTimer = SetTimer( hWnd, 1, 1000, NULL );
    fTimer = FIRST_TIME;
    Refresh( REFRESH_TRUE );

    GlobalMessageHandle = LoadLibrary( "netmsg.dll" );

    if ( GlobalMessageHandle == NULL ) {

;//                MessagePrint( IDS_LOAD_LIBRARY_FAILURE, GetLastError() );

    }

    break;

case WM_TIMER:

if ( fTimer == FIRST_TIME ) {

uTimer = SetTimer( hWnd, 1, REFRESH_RATE, NULL );

fTimer = !FIRST_TIME;

}

    if ( Refresh( REFRESH_QUERY ) == TRUE ) {

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_NOCHANGE );

    }

    break;

case WM_COMMAND:    // message: command from application menu

    // Message packing of uParam and lParam have changed for Win32, let us
    // handle the differences in a conditional compilation:

wmId    = LOWORD(uParam);
wmEvent = HIWORD(uParam);

switch (wmId) {

case IDC_ATCOMMANDS:

    if ( LBN_DBLCLK == wmEvent ) {

if ( IsWindowEnabled( GetDlgItem( hWnd, IDC_CHANGE ) ) ) {

PostMessage( hWnd, WM_COMMAND, IDC_CHANGE, 0 );

}

    }

break;

case IDC_REFRESH:

    if ( Refresh( REFRESH_QUERY ) == TRUE ) {

    Refresh( REFRESH_FALSE );

    hCursorOld = SetCursor( hCursorWait );

    status = JobEnum( lParam );

    SetCursor( hCursorOld );

    if ( status == NERR_Success || status == ERROR_SUCCESS ) {

Refresh( REFRESH_TRUE );

    }

}

    break;

case IDC_ADD:

    Refresh( REFRESH_FALSE );

    lpAddChangeDlgProc = MakeProcInstance((FARPROC)AddChangeDlgProc, hInstance);

    iDlgRetCode = DialogBoxParam(
hInst,
"ADDCHANGEDLG",
hWnd,
(DLGPROC)lpAddChangeDlgProc,
IDC_ADD);

    hGlobalAddChg = NULL;

    FreeProcInstance(lpAddChangeDlgProc);
    FreeProcInstance(lpAmPmProc);
    FreeProcInstance(lpAmPmDefProc);
    FreeProcInstance(lpHourProc);
    FreeProcInstance(lpHourDefProc);
    FreeProcInstance(lpMinProc);
    FreeProcInstance(lpMinDefProc);
    FreeProcInstance(lpDownButtonProc);
    FreeProcInstance(lpDownButtonDefProc);
    FreeProcInstance(lpUpButtonProc);
    FreeProcInstance(lpUpButtonDefProc);

    Refresh( REFRESH_TRUE );

    if ( iDlgRetCode == IDOK ) {

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_BOTTOM );

    }

    break;

case IDC_CHANGE:

Refresh( REFRESH_FALSE );

    lpAddChangeDlgProc = MakeProcInstance((FARPROC)AddChangeDlgProc, hInstance);

    iDlgRetCode = DialogBoxParam(
hInst,
"ADDCHANGEDLG",
hWnd,
(DLGPROC)lpAddChangeDlgProc,
IDC_CHANGE);

    hGlobalAddChg = NULL;

    FreeProcInstance(lpAddChangeDlgProc);
    FreeProcInstance(lpAmPmProc);
    FreeProcInstance(lpAmPmDefProc);
    FreeProcInstance(lpHourProc);
    FreeProcInstance(lpHourDefProc);
    FreeProcInstance(lpMinProc);
    FreeProcInstance(lpMinDefProc);
    FreeProcInstance(lpDownButtonProc);
    FreeProcInstance(lpDownButtonDefProc);
    FreeProcInstance(lpUpButtonProc);
    FreeProcInstance(lpUpButtonDefProc);

Refresh( REFRESH_TRUE );

    if ( iDlgRetCode == IDOK ) {

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_BOTTOM );

    }

    break;

case IDC_REMOVE:

    status = JobDel( DEL_ID, hWnd );

break;

case IDM_GET_FROM_REGISTRY:

    RegCreateKeyEx(REG_HKEY,
   szRegistryPath,
   0,
   "",
   REG_OPTION_NON_VOLATILE,
   KEY_ALL_ACCESS,
   NULL,
   &hkHandle,
   &dwDisposition);

    if ( dwDisposition == REG_CREATED_NEW_KEY ) {

RegSetValueEx(hkHandle, "dwX", 0, REG_DWORD, (LPBYTE)DWX, sizeof(DWORD));
RegSetValueEx(hkHandle, "dwY", 0, REG_DWORD, (LPBYTE)DWY, sizeof(DWORD));

    }

    {
DWORD dwType = REG_DWORD;
DWORD dwSize = sizeof(DWORD);

RegQueryValueEx(hkHandle, "dwX", 0, &dwType, (LPBYTE)&dwX, &dwSize);
RegQueryValueEx(hkHandle, "dwY", 0, &dwType, (LPBYTE)&dwY, &dwSize);

    }

    RegCloseKey(hkHandle);

    {
RECT Rect;

if(!GetWindowRect(hWnd, &Rect)) {

    MessageBox(
hWnd,
#ifdef JAPAN
                                " E B   h E   u     G   [",
#else
"Problem getting window coordinates.",
#endif
SZWINDOWTITLE,
MB_OK | MB_ICONEXCLAMATION
);

} else {

    dwW = Rect.right  - Rect.left;
    dwH = Rect.bottom - Rect.top;

}

    }

    MoveWindow(hWnd, (INT)dwX, (INT)dwY, (INT)dwW, (INT)dwH, TRUE);

    break;

 case IDM_SAVE_TO_REGISTRY:

    RegCreateKeyEx(REG_HKEY,
   szRegistryPath,
   0,
   "",
   REG_OPTION_NON_VOLATILE,
   KEY_ALL_ACCESS,
   NULL,
   &hkHandle,
   &dwDisposition
   );

    {
RECT Rect;

if(!GetWindowRect(hWnd, &Rect)) {

    dwX = DWX;
    dwY = DWY;

    MessageBox(
hWnd,
#ifdef JAPAN
                                " E B   h E   u     G   [",
#else
"Problem getting coordinates.",
#endif
SZWINDOWTITLE,
MB_OK | MB_ICONEXCLAMATION
);

} else {

    dwX = Rect.left;
    dwY = Rect.top;

}

    }

    RegSetValueEx(hkHandle, "dwX", 0, REG_DWORD, (LPBYTE)&dwX, sizeof(DWORD));
    RegSetValueEx(hkHandle, "dwY", 0, REG_DWORD, (LPBYTE)&dwY, sizeof(DWORD));

    RegCloseKey(hkHandle);

    break;

case IDM_ABOUT:

Refresh( REFRESH_SAVE );
    Refresh( REFRESH_FALSE );

// jean-marc - start
    if ( ShellAbout( hGlobalhWnd, SZWINDOWTITLE, NULL, LoadIcon( hInst, (LPSTR)SZAPPTITLE ) ) == -1 ) {
//old line    if ( ShellAbout( hGlobalhWnd, SZWINDOWTITLE, SZCREDITS, LoadIcon( hInst, (LPSTR)SZAPPTITLE ) ) == -1 ) {
// jean-marc - end

#ifdef JAPAN
                    MessageBox( hGlobalhWnd, "       s  ", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#else
MessageBox( hGlobalhWnd, "Out of memory error.", SZWINDOWTITLE, MB_OK | MB_ICONEXCLAMATION );
#endif

    }

Refresh( REFRESH_RESTORE );

break;

case IDM_SELECTCOMPUTER:

    Refresh( REFRESH_SAVE );
    Refresh( REFRESH_FALSE );

    lpProcSelect = MakeProcInstance((FARPROC)SelectComputerProc, hInst);

iDlgRetCode = DialogBox(hInst,
"SelectComputer",
hWnd,
(DLGPROC)lpProcSelect);

FreeProcInstance(lpProcSelect);

    Refresh( REFRESH_TRUE );

if ( iDlgRetCode == IDOK ) {

SendMessage( hWnd, WM_COMMAND, IDC_REFRESH, (LPARAM)SCS_TOP );

}

break;

case IDM_EXIT:

    Refresh( REFRESH_FALSE );
    FFreeListboxExtents( GetDlgItem( hWnd, IDC_ATCOMMANDS ) );
    ShowWindow( hWnd, SW_RESTORE );
    SendMessage( hWnd, WM_COMMAND, IDM_SAVE_TO_REGISTRY, 0 );
#ifdef JAPAN
#else
    DeleteObject( hFont );
// jean-marc - start
    if (hFontComputerName)
      DeleteObject(hFontComputerName);
// jean-marc - end
#endif
    KillTimer( hWnd, 1 );
    EndDialog( hWnd, 0 );

break;

case IDC_HELP:

#ifdef KKBUGFIX
    if (!WinHelp (hWnd, "WINAT.HLP", HELP_CONTENTS,0L)) {
#else
    if (!WinHelp (hWnd, "WINAT.HLP", HELP_KEY,(DWORD)(LPSTR)"CONTENTS")) {
#endif
MessageBox (GetFocus(),
#ifdef JAPAN
" w   v   N            ",
#else
"Unable to activate help",
#endif
SZWINDOWTITLE, MB_SYSTEMMODAL|MB_OK|MB_ICONHAND);
    }
break;

case IDM_HELPCONTENTS:

#ifdef KKBUGFIX
    if (!WinHelp (hWnd, "WINAT.HLP", HELP_CONTENTS,0L)) {
#else
    if (!WinHelp (hWnd, "WINAT.HLP", HELP_KEY,(DWORD)(LPSTR)"CONTENTS")) {
#endif
MessageBox (GetFocus(),
#ifdef JAPAN
" w   v   N            ",
#else
"Unable to activate help",
#endif
SZWINDOWTITLE, MB_SYSTEMMODAL|MB_OK|MB_ICONHAND);
    }

    break;

case IDM_HELPSEARCH:

    if (!WinHelp(hWnd, "WINAT.HLP", HELP_PARTIALKEY, (DWORD)(LPSTR)"")) {
MessageBox (GetFocus(),
#ifdef JAPAN
" w   v   N            ",
#else
"Unable to activate help",
#endif
SZWINDOWTITLE, MB_SYSTEMMODAL|MB_OK|MB_ICONHAND);
    }

    break;

case IDM_HELPHELP:

    if(!WinHelp(hWnd, (LPSTR)NULL, HELP_HELPONHELP, 0)) {
MessageBox (GetFocus(),
#ifdef JAPAN
" w   v   N            ",
#else
"Unable to activate help",
#endif
SZWINDOWTITLE, MB_SYSTEMMODAL|MB_OK|MB_ICONHAND);
    }

    break;

    }

    break;

case WM_CLOSE:
case WM_ENDSESSION:

    SendMessage(hWnd, WM_COMMAND, IDM_EXIT, 0);

    break;

    }

    return (0);

}

//===========================================================================
// FUNCTION: WinMain()
//===========================================================================
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{

    FARPROC   lpWINATDlgProc;
    WNDCLASS  wc;

    hInst       = hInstance;

// Fill in window class structure with parameters that describe the
// main window.

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = (WNDPROC)DefDlgProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = DLGWINDOWEXTRA;
    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;

    // Register the window class

    RegisterClass(&wc);

// jean-marc - start
    // store the commandline (only copy over MAXCOMPUTERNAMELEN characters)
    memset(&gszCommandLine, 0, sizeof(gszCommandLine));
    strncpy(gszCommandLine, lpCmdLine, MAXCOMPUTERNAMELEN);
    // the commandline will actually be processed from within the WINATDlgProc
// jean-marc - end

    //
    // this program is only a single dialog box
    //

    lpWINATDlgProc = MakeProcInstance((FARPROC)WINATDlgProc, hInstance);

    DialogBox(hInstance,
"WINATDLG",
0,
(DLGPROC)lpWINATDlgProc
);

    FreeProcInstance(lpWINATDlgProc);

    return 0;

}


WINAT.H

//============================================================================
//
// Module: WINAT.H
//
// Purpose: Header file for WINAT.C
//
// Author: Kory Gill
// Date  : March 1993
//
// Copyright: Copyright 1993 - 1998 Microsoft Corporation-1995
//
//============================================================================

//============================================================================
// defines
//============================================================================
#define WM_SPINBUTTON      (WM_USER + 0)

#define DAYSINWEEK         (7)
#ifdef JAPAN
#define DEFAULT_AMPM       "   O"
#else
#define DEFAULT_AMPM       "AM"
#endif
#define DEFAULT_HOUR       (12)
#define DEFAULT_MIN        (0)
#define DEL_ALL            (1)
#define DEL_ID             (0)
#define DEL_ID_CHANGE      (2)
#define DOWN               (-1)
#define DWH                (320)
#define DWW                (570)
#define DWX                (0)
#define DWY                (0)
#define FIRST_TIME         (0)
#define REG_HKEY   HKEY_CURRENT_USER   // old: HKEY_LOCAL_MACHINE
#define MAXCOMMANDLEN      (128)
#define MAXCOMPUTERNAMELEN (17)                            // 15 char name, 2 '\'
#define MAXWHENDAYLEN      (108+TIME_LEN+1)
#define MAX_ATCMD_LEN      (5+MAXWHENDAYLEN+MAXCOMMANDLEN) //id, tab, when/days, tab, cmd
#define MAX_SELCOMMANDS    (256)
#define NUMDAYITEMS        (DAYSINWEEK+31)
#define NUM_TIMES          (48)
#define REFRESH_ERROR      (5)
#define REFRESH_FALSE      (0)
#define REFRESH_QUERY      (2)
#define REFRESH_RATE       (15000)
#define REFRESH_RESTORE    (4)
#define REFRESH_SAVE       (3)
#define REFRESH_TRUE       (1)
#define SCS_BOTTOM   (0)
#define SCS_NOCHANGE       (1)
#define SCS_TOP            (2)
#define SCS_SELINHIGHWORD  (3)
#define SCS_NONE   (4)
#define SZAPPTITLE   "WINAT"
#ifdef JAPAN
#define SZWINDOWTITLE      " R }   h  X P W   [  "
#define SZCREDITS   "Developed for Microsoft\rby Kory Gill"
#define SZGENERROR         " w     R   s   [ ^     A N Z X G   ["
#define SZNOENTRIES        " o ^ R }   h    "
#else
#define SZWINDOWTITLE      "Command Scheduler"
#define SZCREDITS   "Developed for Microsoft\rby Kory Gill"
#define SZGENERROR         "Problem accessing computer specified."
#define SZNOENTRIES        "No entries in list."
#endif
#define SZREGPATH          "Software\\Microsoft\\Resource Kit\\Winat"
#define TIME_LEN           (5)
#define UP                 (1)

//============================================================================
// user-defined types
//============================================================================

typedef struct _ATTIME {
    UCHAR szTime[TIME_LEN+1];
    DWORD dwMsSinceMidnight;
} ATTIME, *PATTIME;

//============================================================================
// internal function prototypes
//============================================================================

BOOL APIENTRY    AddChangeDlgProc            ( HWND, UINT, UINT, LONG      );
UINT             AreYouSure                  ( HWND                        );
LRESULT APIENTRY AmPmProc                    ( HWND, UINT, UINT, LONG      );
WCHAR *          AsciizToUnicode             ( CHAR *                      );
LRESULT APIENTRY DownButtonProc ( HWND, UINT, UINT, LONG      );
void             DrawBitmap                  ( HDC, LONG, LONG, HBITMAP, DWORD );
LRESULT APIENTRY HourProc                    ( HWND, UINT, UINT, LONG      );
NET_API_STATUS   JobAdd                      ( VOID                        );
NET_API_STATUS   JobEnum                     ( DWORD                       );
NET_API_STATUS   JobDel                      ( UINT, HWND                  );
DWORD            MessagePrint                ( DWORD, ...                  );
LRESULT APIENTRY MinProc                     ( HWND, UINT, UINT, LONG      );
UINT             Refresh                     ( UINT                        );
BOOL APIENTRY    SelectComputerProc          ( HWND, UINT, UINT, LONG      );
BOOL             ServiceNotStartedHandler    ( VOID                        );
LRESULT APIENTRY UpButtonProc ( HWND, UINT, UINT, LONG      );
BOOL             ValidateAddChangeArguments  ( HWND                        );
BOOL APIENTRY    WINATDlgProc                ( HWND, UINT, UINT, LONG      );

//============================================================================
// external function prototypes
//============================================================================

#define IDC_COMPUTER            100
#define IDC_ATCOMMANDS          101
#define IDC_ADD                 102
#define IDC_CHANGE              103
#define IDC_REMOVE              105
#ifndef IDC_HELP
#define IDC_HELP                106
#endif
#define IDC_NEWCOMMAND          107
#define IDC_TODAY               108
#define IDC_TOMORROW            109
#define IDC_EVERY               110
#define IDC_NEXT                111
#define IDC_DAYS                112
#define IDC_HOUR                113
#define IDC_MIN                 114
#define IDC_AMPM                115
#define IDC_ADDCHANGEHELP       117
#define IDC_REFRESH             118
#define IDC_SELECTCOMPUTER      119
#define IDC_INTERACTIVE120
#define IDM_SAVE_TO_REGISTRY    121
#define IDM_GET_FROM_REGISTRY   122

#define IDM_HELPCONTENTS        300
#define IDM_HELPSEARCH          301
#define IDM_HELPHELP            302
#define IDM_ABOUT               303
#define IDM_SELECTCOMPUTER      304
#define IDM_EXIT                305

#define DLG_VERFIRST            400
#define DLG_VERLAST             404

#define IDD_UPBUT 500
#define IDD_DOWNBUT             501


README.TXT

Zoom In

The ZOOMIN tool can be used to visually expand bitmap images from the
Windows desktop. As such, it demonstrates techniques for manipulating
bitmaps and device contexts.


ZOOMIN.C

/****************************************************************************/
/*                                                                          */
/*                         Microsoft Confidential                           */
/*                                                                          */
/*                 Copyright (c) Microsoft Corp.  1987-1996                 */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: zoomin.c
*
* Microsoft ZoomIn utility.  This tool magnifies a portion of the screen,
* allowing you to see things at a pixel level.
*
* History:
* 01/01/88                              Created.
* 01/01/92                              Ported to NT.
* 03/06/92                              Cleanup.
*
****************************************************************************/

#include "zoomin.h"

CHAR szAppName[] = "ZoomIn";            // Aplication name.
HINSTANCE ghInst;                       // Instance handle.
HWND ghwndApp;                          // Main window handle.
HANDLE ghaccelTable;                    // Main accelerator table.
INT gnZoom = 4;                         // Zoom (magnification) factor.
HPALETTE ghpalPhysical;                 // Handle to the physical palette.
INT gcxScreenMax;                       // Width of the screen (less 1).
INT gcyScreenMax;                       // Height of the screen (less 1).
INT gcxZoomed;                          // Client width in zoomed pixels.
INT gcyZoomed;                          // Client height in zoomed pixels.
BOOL gfRefEnable = FALSE;               // TRUE if refresh is enabled.
INT gnRefInterval = 20;                 // Refresh interval in 10ths of seconds.
BOOL gfTracking = FALSE;                // TRUE if tracking is in progress.
POINT gptZoom = {100, 100};             // The center of the zoomed area.

/************************************************************************
* WinMain
*
* Main entry point for the application.
*
* Arguments:
*
* History:
*
************************************************************************/

INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, INT nCmdShow)
{
    MSG msg;

    if (!InitInstance(hInst, nCmdShow))
        return FALSE;

    /*     * Polling messages from event queue
     */

    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!TranslateAccelerator(ghwndApp, ghaccelTable, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return msg.wParam;
}

/************************************************************************
* InitInstance
*
* Instance initialization for the app.
*
* Arguments:
*
* History:
*
************************************************************************/

BOOL InitInstance(
    HINSTANCE hInst,
    INT cmdShow)
{
    WNDCLASS wc;
    INT dx;
    INT dy;
    DWORD flStyle;
    RECT rc;

    ghInst = hInst;

    /*
     * Register a class for the main application window.
     */
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon          = LoadIcon(hInst, "zoomin");
    wc.lpszMenuName   = MAKEINTRESOURCE(IDMENU_ZOOMIN);
    wc.lpszClassName  = szAppName;
    wc.hbrBackground  = GetStockObject(BLACK_BRUSH);
    wc.hInstance      = hInst;
    wc.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW;
    wc.lpfnWndProc    = (WNDPROC)AppWndProc;
    wc.cbWndExtra     = 0;
    wc.cbClsExtra     = 0;

    if (!RegisterClass(&wc))
        return FALSE;

    if (!(ghaccelTable = LoadAccelerators(hInst, MAKEINTRESOURCE(IDACCEL_ZOOMIN))))
        return FALSE;

    if (!(ghpalPhysical = CreatePhysicalPalette()))
        return FALSE;

    gcxScreenMax = GetSystemMetrics(SM_CXSCREEN) - 1;    gcyScreenMax = GetSystemMetrics(SM_CYSCREEN) - 1;

    flStyle = WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_THICKFRAME |
            WS_MINIMIZEBOX | WS_VSCROLL;
    dx = 44 * gnZoom;
    dy = 36 * gnZoom;

    SetRect(&rc, 0, 0, dx, dy);
    AdjustWindowRect(&rc, flStyle, TRUE);

    ghwndApp = CreateWindow(szAppName, szAppName, flStyle,
            CW_USEDEFAULT, 0, rc.right - rc.left, rc.bottom - rc.top,
            NULL, NULL, hInst, NULL);

    if (!ghwndApp)
        return FALSE;
    ShowWindow(ghwndApp, cmdShow);

    return TRUE;
}

/************************************************************************
* CreatePhysicalPalette
*
* Creates a palette for the app to use.  The palette references the
* physical palette, so that it can properly display images grabbed
* from palette managed apps.
*
* History:
*
************************************************************************/

HPALETTE CreatePhysicalPalette(VOID)
{
    PLOGPALETTE ppal;
    HPALETTE hpal = NULL;
    INT i;

    ppal = (PLOGPALETTE)LocalAlloc(LPTR,
            sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * NPAL);
    if (ppal) {
        ppal->palVersion = 0x300;
        ppal->palNumEntries = NPAL;

        for (i = 0; i < NPAL; i++) {
            ppal->palPalEntry[i].peFlags = (BYTE)PC_EXPLICIT;
            ppal->palPalEntry[i].peRed   = (BYTE)i;
            ppal->palPalEntry[i].peGreen = (BYTE)0;
            ppal->palPalEntry[i].peBlue  = (BYTE)0;
        }

        hpal = CreatePalette(ppal);
        LocalFree(ppal);
    }

    return hpal;
}

/************************************************************************
* AppWndProc
*
* Main window proc for the zoomin utility.
*
* Arguments:
*   Standard window proc args.
*
* History:
*
************************************************************************/

LONG APIENTRY AppWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    PAINTSTRUCT ps;
    HCURSOR hcurOld;

    switch (msg) {
        case WM_CREATE:
            SetScrollRange(hwnd, SB_VERT, MIN_ZOOM, MAX_ZOOM, FALSE);
            SetScrollPos(hwnd, SB_VERT, gnZoom, FALSE);
            break;

        case WM_TIMER:
            /*
             * Update on every timer message.  The cursor will be
             * flashed to the hourglash for some visual feedback
             * of when a snapshot is being taken.
             */
            hcurOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
            DoTheZoomIn(NULL);
            SetCursor(hcurOld);
            break;

        case WM_PAINT:
            BeginPaint(hwnd, &ps);
            DoTheZoomIn(ps.hdc);
            EndPaint(hwnd, &ps);
            return 0L;

        case WM_SIZE:
            CalcZoomedSize();
            break;

        case WM_LBUTTONDOWN:
            ((gptZoom).x = (SHORT)LOWORD(lParam), (gptZoom).y = (SHORT)HIWORD(lParam));
            ClientToScreen(hwnd, &gptZoom);
            DrawZoomRect();
            DoTheZoomIn(NULL);

            SetCapture(hwnd);
            gfTracking = TRUE;

            break;

        case WM_MOUSEMOVE:
            if (gfTracking) {
                DrawZoomRect();
                ((gptZoom).x = (SHORT)LOWORD(lParam), (gptZoom).y = (SHORT)HIWORD(lParam));
                ClientToScreen(hwnd, &gptZoom);
                DrawZoomRect();
                DoTheZoomIn(NULL);
            }

            break;

        case WM_LBUTTONUP:
            if (gfTracking) {
                DrawZoomRect();
                ReleaseCapture();
                gfTracking = FALSE;
            }

            break;

        case WM_VSCROLL:
            switch (LOWORD(wParam)) {
                case SB_LINEDOWN:
                    gnZoom++;
                    break;

                case SB_LINEUP:
                    gnZoom--;
                    break;

                case SB_PAGEUP:
                    gnZoom -= 2;
                    break;

                case SB_PAGEDOWN:
                    gnZoom += 2;
                    break;

                case SB_THUMBPOSITION:
                case SB_THUMBTRACK:
                    gnZoom = HIWORD(wParam);
                    break;
            }

            gnZoom = BOUND(gnZoom, MIN_ZOOM, MAX_ZOOM);
            SetScrollPos(hwnd, SB_VERT, gnZoom, TRUE);
            CalcZoomedSize();
            DoTheZoomIn(NULL);
            break;

        case WM_KEYDOWN:
            switch (wParam) {
                case VK_UP:
                case VK_DOWN:
                case VK_LEFT:
                case VK_RIGHT:
                    MoveView((INT)wParam, GetKeyState(VK_SHIFT) & 0x8000,
                            GetKeyState(VK_CONTROL) & 0x8000);
                    break;
            }

            break;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case MENU_EDIT_COPY:
                    CopyToClipboard();
                    break;

                case MENU_EDIT_REFRESH:
                    DoTheZoomIn(NULL);
                    break;

                case MENU_OPTIONS_REFRESHRATE:
                    DialogBox(ghInst, (LPSTR)MAKEINTRESOURCE(DID_REFRESHRATE),
                            hwnd, (WNDPROC)RefreshRateDlgProc);

                    break;

                case MENU_HELP_ABOUT:
                    DialogBox(ghInst, (LPSTR)MAKEINTRESOURCE(DID_ABOUT),
                            hwnd, (WNDPROC)AboutDlgProc);

                    break;

                default:
                     break;
            }

            break;

        case WM_CLOSE:
            if (ghpalPhysical)
                DeleteObject(ghpalPhysical);

            DestroyWindow(hwnd);

            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

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

    return 0L;
}

/************************************************************************
* CalcZoomedSize
*
* Calculates some globals.  This routine needs to be called any
* time that the size of the app or the zoom factor changes.
*
* History:
*
************************************************************************/

VOID CalcZoomedSize(VOID)
{
    RECT rc;

    GetClientRect(ghwndApp, &rc);

    gcxZoomed = (rc.right / gnZoom) + 1;
    gcyZoomed = (rc.bottom / gnZoom) + 1;
}

/************************************************************************
* DoTheZoomIn
*
* Does the actual paint of the zoomed image.
*
* Arguments:
*   HDC hdc - If not NULL, this hdc will be used to paint with.
*             If NULL, a dc for the apps window will be obtained.
*
* History:
*
************************************************************************/

VOID DoTheZoomIn(
    HDC hdc)
{
    BOOL fRelease;
    HPALETTE hpalOld = NULL;
    HDC hdcScreen;
    INT x;
    INT y;

    if (!hdc) {
        hdc = GetDC(ghwndApp);
        fRelease = TRUE;
    }
    else {
        fRelease = FALSE;
    }

    if (ghpalPhysical) {
        hpalOld = SelectPalette(hdc, ghpalPhysical, FALSE);
        RealizePalette(hdc);
    }

    /*
     * The point must not include areas outside the screen dimensions.
     */
    x = BOUND(gptZoom.x, gcxZoomed / 2, gcxScreenMax - (gcxZoomed / 2));
    y = BOUND(gptZoom.y, gcyZoomed / 2, gcyScreenMax - (gcyZoomed / 2));

    hdcScreen = GetDC(NULL);
    SetStretchBltMode(hdc, COLORONCOLOR);
    StretchBlt(hdc, 0, 0, gnZoom * gcxZoomed, gnZoom * gcyZoomed,
            hdcScreen, x - gcxZoomed / 2,
            y - gcyZoomed / 2, gcxZoomed, gcyZoomed, SRCCOPY);
    ReleaseDC(NULL, hdcScreen);

    if (hpalOld)
        SelectPalette(hdc, hpalOld, FALSE);

    if (fRelease)
        ReleaseDC(ghwndApp, hdc);
}

/************************************************************************
* MoveView
*
* This function moves the current view around.
*
* Arguments:
*   INT nDirectionCode - Direction to move.  Must be VK_UP, VK_DOWN,
*                        VK_LEFT or VK_RIGHT.
*   BOOL fFast         - TRUE if the move should jump a larger increment.
*                        If FALSE, the move is just one pixel.
*   BOOL fPeg          - If TRUE, the view will be pegged to the screen
*                        boundary in the specified direction.  This overides
*                        the fFast parameter.
*
* History:
*
************************************************************************/

VOID MoveView(
    INT nDirectionCode,
    BOOL fFast,
    BOOL fPeg)
{
    INT delta;

    if (fFast)
        delta = FASTDELTA;
    else
        delta = 1;

    switch (nDirectionCode) {
        case VK_UP:
            if (fPeg)
                gptZoom.y = gcyZoomed / 2;
            else
                gptZoom.y -= delta;

            gptZoom.y = BOUND(gptZoom.y, 0, gcyScreenMax);

            break;

        case VK_DOWN:
            if (fPeg)
                gptZoom.y = gcyScreenMax - (gcyZoomed / 2);
            else
                gptZoom.y += delta;

            gptZoom.y = BOUND(gptZoom.y, 0, gcyScreenMax);

            break;

        case VK_LEFT:
            if (fPeg)
                gptZoom.x = gcxZoomed / 2;
            else
                gptZoom.x -= delta;

            gptZoom.x = BOUND(gptZoom.x, 0, gcxScreenMax);

            break;

        case VK_RIGHT:
            if (fPeg)
                gptZoom.x = gcxScreenMax - (gcxZoomed / 2);
            else
                gptZoom.x += delta;

            gptZoom.x = BOUND(gptZoom.x, 0, gcxScreenMax);

            break;
    }

    DoTheZoomIn(NULL);
}

/************************************************************************
* DrawZoomRect
*
* This function draws the tracking rectangle.  The size and shape of
* the rectangle will be proportional to the size and shape of the
* app's client, and will be affected by the zoom factor as well.
*
* History:
*
************************************************************************/

VOID DrawZoomRect(VOID)
{
    HDC hdc;
    RECT rc;
    INT x;
    INT y;

    x = BOUND(gptZoom.x, gcxZoomed / 2, gcxScreenMax - (gcxZoomed / 2));
    y = BOUND(gptZoom.y, gcyZoomed / 2, gcyScreenMax - (gcyZoomed / 2));

    rc.left   = x - gcxZoomed / 2;
    rc.top    = y - gcyZoomed / 2;
    rc.right  = rc.left + gcxZoomed;
    rc.bottom = rc.top + gcyZoomed;

    InflateRect(&rc, 1, 1);

    hdc = GetDC(NULL);

    PatBlt(hdc, rc.left,    rc.top,     rc.right-rc.left, 1,  DSTINVERT);
    PatBlt(hdc, rc.left,    rc.bottom,  1, -(rc.bottom-rc.top),   DSTINVERT);
    PatBlt(hdc, rc.right-1, rc.top,     1,   rc.bottom-rc.top,   DSTINVERT);
    PatBlt(hdc, rc.right,   rc.bottom-1, -(rc.right-rc.left), 1, DSTINVERT);

    ReleaseDC(NULL, hdc);
}

/************************************************************************
* EnableRefresh
*
* This function turns on or off the auto-refresh feature.
*
* Arguments:
*   BOOL fEnable - TRUE to turn the refresh feature on, FALSE to
*                  turn it off.
*
* History:
*
************************************************************************/

VOID EnableRefresh(
    BOOL fEnable)
{
    if (fEnable) {
        /*
         * Already enabled.  Do nothing.
         */
        if (gfRefEnable)
            return;

        if (SetTimer(ghwndApp, IDTIMER_ZOOMIN, gnRefInterval * 100, NULL))
            gfRefEnable = TRUE;
    }
    else {
        /*
         * Not enabled yet.  Do nothing.
         */
        if (!gfRefEnable)
            return;

        KillTimer(ghwndApp, IDTIMER_ZOOMIN);
        gfRefEnable = FALSE;
    }
}

/************************************************************************
* CopyToClipboard
*
* This function copies the client area image of the app into the
* clipboard.
*
* History:
*
************************************************************************/

VOID CopyToClipboard(VOID)
{
    HDC hdcSrc;
    HDC hdcDst;
    RECT rc;
    HBITMAP hbm;

    if (OpenClipboard(ghwndApp)) {
        EmptyClipboard();

        if (hdcSrc = GetDC(ghwndApp)) {
            GetClientRect(ghwndApp, &rc);
            if (hbm = CreateCompatibleBitmap(hdcSrc,
                    rc.right - rc.left, rc.bottom - rc.top)) {
                if (hdcDst = CreateCompatibleDC(hdcSrc)) {
                    /*
                     * Calculate the dimensions of the bitmap and
                     * convert them to tenths of a millimeter for
                     * setting the size with the SetBitmapDimensionEx
                     * call.  This allows programs like WinWord to
                     * retrieve the bitmap and know what size to
                     * display it as.
                     */

SetBitmapDimensionEx(hbm, (DWORD)((INT)(((DWORD)(rc.right - rc.left) * MM10PERINCH) / (DWORD)GetDeviceCaps(hdcSrc, LOGPIXELSX))), (DWORD)((INT)(((DWORD)(rc.bottom - rc.top) * MM10PERINCH) / (DWORD)GetDeviceCaps(hdcSrc, LOGPIXELSY))), NULL);

                    SelectObject(hdcDst, hbm);
                    BitBlt(hdcDst, 0, 0,
                            rc.right - rc.left, rc.bottom - rc.top,
                            hdcSrc, rc.left, rc.top, SRCCOPY);
                    DeleteDC(hdcDst);
                    SetClipboardData(CF_BITMAP, hbm);
                }
                else {
                    DeleteObject(hbm);
                }
            }

            ReleaseDC(ghwndApp, hdcSrc);
        }

        CloseClipboard();
    }
    else {
        MessageBeep(0);
    }
}

/************************************************************************
* AboutDlgProc
*
* This is the About Box dialog procedure.
*
* History:
*
************************************************************************/

BOOL APIENTRY AboutDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg) {
        case WM_INITDIALOG:
            return TRUE;

        case WM_COMMAND:
            EndDialog(hwnd, IDOK);
            return TRUE;

        default:
            return FALSE;
    }
}

/************************************************************************
* RefreshRateDlgProc
*
* This is the Refresh Rate dialog procedure.
*
* History:
*
************************************************************************/

BOOL APIENTRY RefreshRateDlgProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    BOOL fTranslated;

    switch (msg) {
        case WM_INITDIALOG:
            SendDlgItemMessage(hwnd, DID_REFRESHRATEINTERVAL, EM_LIMITTEXT,
                    3, 0L);
            SetDlgItemInt(hwnd, DID_REFRESHRATEINTERVAL, gnRefInterval, FALSE);
            CheckDlgButton(hwnd, DID_REFRESHRATEENABLE, gfRefEnable ? 1 : 0);
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                    gnRefInterval = GetDlgItemInt(hwnd, DID_REFRESHRATEINTERVAL,
                            &fTranslated, FALSE);

                    /*
                     * Stop any existing timers then start one with the
                     * new interval if requested to.
                     */
                    EnableRefresh(FALSE);
                    EnableRefresh(
                            IsDlgButtonChecked(hwnd, DID_REFRESHRATEENABLE));

                    EndDialog(hwnd, IDOK);
                    break;

                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                    break;
            }

            break;
    }

    return FALSE;
}


ZOOMIN.H

/****************************************************************************/
/*                                                                          */
/*                         Microsoft Confidential                           */
/*                                                                          */
/*                 Copyright (c) Microsoft Corp.  1987-1996                 */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/
/****************************** Module Header *******************************
* Module Name: zoomin.h
*
* Main header file for the ZoomIn utility.
*
* History:
*
****************************************************************************/

#include <windows.h>

#define MIN_ZOOM    1
#define MAX_ZOOM    32

#define FASTDELTA   8

#define BOUND(x,min,max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))

#define MM10PERINCH 254                     // Tenths of a millimeter per inch.

#define NPAL        256                     // Number of palette entries.

#define MENU_HELP_ABOUT             100

#define MENU_EDIT_COPY              200
#define MENU_EDIT_REFRESH           201

#define MENU_OPTIONS_REFRESHRATE    300

#define DID_ABOUT                   1000

#define DID_REFRESHRATE             1100
#define DID_REFRESHRATEENABLE       1101
#define DID_REFRESHRATEINTERVAL     1102

#define IDMENU_ZOOMIN               2000

#define IDACCEL_ZOOMIN              3000

#define IDTIMER_ZOOMIN              4000

BOOL InitInstance(HINSTANCE hInst, INT cmdShow);
HPALETTE CreatePhysicalPalette(VOID);
LONG APIENTRY AppWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
VOID CalcZoomedSize(VOID);
VOID DoTheZoomIn(HDC hdc);
VOID MoveView(INT nDirectionCode, BOOL fFast, BOOL fPeg);
VOID DrawZoomRect(VOID);
VOID EnableRefresh(BOOL fEnable);
VOID CopyToClipboard(VOID);
BOOL APIENTRY AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL APIENTRY RefreshRateDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam);