/***************************************************************************
 *  w_memmap.c - Memory map Widget (MW)                                     
 *                                                                          
 *      Copyright (C) 2023 Richard L Walsh, Ulrich M”ller.
 *      This file is part of the XWorkplace source package.
 *      XWorkplace is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published
 *      by the Free Software Foundation, in version 2 as it comes in the
 *      "COPYING" file of the XWorkplace main distribution.
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 ****************************************************************************/

#pragma strings(readonly)

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#define INCL_DOS
#define INCL_WIN
#define INCL_GPI
#include <os2.h>

// generic headers
#define DONT_REPLACE_MALLOC             // in case mem debug is enabled
#define DONT_REPLACE_FOR_DBCS           // do not replace strchr with DBCS version
#include "setup.h"                      // code generation and debugging options

// disable wrappers, because we're not linking statically
#ifdef DOSH_STANDARDWRAPPERS
    #undef DOSH_STANDARDWRAPPERS
#endif
#ifdef WINH_STANDARDWRAPPERS
    #undef WINH_STANDARDWRAPPERS
#endif

#include "helpers\comctl.h"             // common controls (window procs)
#include "dlgids.h"                     // all the IDs that are shared with NLS
#include "shared\center.h"              // public XCenter interfaces
#include "shared\common.h"              // the majestic XWorkplace include file
#include "shared\helppanels.h"          // all XWorkplace help panel IDs

/****************************************************************************/
/*  Local definitions                                                       */
/****************************************************************************/

#define MW_WNDCLASS         "MemMapWndClass"
#define MWM_NEWDATA         WM_USER + 27
#define MWM_NEWSETTING      WM_USER + 28
#define ERROR_TIMEOUT       640
#define HIGHSHARED_HAR      0x0005
#define LOWSHARED_HAR       0x0006

#ifdef MP
#error MP already #defined
#endif
#define MP                  MPARAM

// sleep time in ms between samples
#define MW_SAMPLERATE       2000

// this is 80px using 10.SysProportional
#define MW_DLGUNIT_WIDTH    40

// default colors
#define MWCLR_PRIV          0x00aa0000
#define MWCLR_FREE          0x00aaaaaa
#define MWCLR_UNUSED        0x0000ff00
#define MWCLR_USED          0x003f3fff

typedef struct _MWSETUP {
    BOOL        fGrid;
    BOOL        fInInit;
    LONG        cx;
    LONG        clrPriv;
    LONG        clrFree;
    LONG        clrUnused;
    LONG        clrUsed;
    LONG        clrWidgBk;
} MWSETUP;

typedef struct _MWMEM {
    ULONG       loPriv;
    ULONG       loFree;
    ULONG       loShared;
    ULONG       loUsed;
    ULONG       hiPriv;
    ULONG       hiFree;
    ULONG       hiShared;
    ULONG       hiUsed;
} MWMEM;

typedef struct _MW {
    XCENTERWIDGET *widg;
    HWND        hwnd;
    HWND        hStats;
    BOOL        fShowing;
    HMTX        hmtx;
    HEV         hev;
    MWMEM       i;
    MWMEM       o;
    MWSETUP     su;
    MWSETUP     suBak;
    char        szShowStats[64];
    char        szHideStats[64];
} MW;

typedef struct _ARENAREC
{
    ULONG   pages;
    USHORT  flags;
    USHORT  linear;
    USHORT  next;
    USHORT  prev;
    USHORT  link;
    USHORT  hash;
    union {
        struct s {
            USHORT  hob;
            USHORT  hco;
          };
        ULONG   max;
    };
    USHORT  hal;
}
ARENAREC;

typedef BOOL _Optlink CTLMAKECOLORRECT(HWND);
typedef CTLMAKECOLORRECT *PCTLMAKECOLORRECT;

/****************************************************************************/

MRESULT EXPENTRY mwWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
ULONG            mwCreate(HWND hwnd, XCENTERWIDGET * pWidg);
void             mwPaint(HWND hwnd, HPS hps, PXCENTERWIDGET pWidgt, BOOL fDrawFrame);
void             mwDrawBoxes(MW *pMW, HPS hps, LONG x, LONG y, LONG cx, LONG cy, ULONG *pVal);
void             mwToggleStats(HWND hwnd);
MRESULT EXPENTRY mwStatsProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
MRESULT EXPENTRY mwShieldProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
void    EXPENTRY mwShowSettings(WIDGETSETTINGSDLGDATA* pData);
MRESULT EXPENTRY mwSettingsProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
void             mwInitSettingsDlg(HWND hwnd, WIDGETSETTINGSDLGDATA *pData);
void             mwScanSetup(PCSZ pcszString, MWSETUP *pSU);
void             mwSaveSetup(char *pString, MWSETUP *pSU);
void             mwSetDlgColors(HWND hwnd, MWSETUP *pSU);
void             mwSetDlgText(HWND hwnd, BOOL fSettings);
void     _System mwThreadProc(ULONG arg);
BOOL             mwLinkToXWP(void);
void             mwInfo(char * msg, ...);
void             mwError(char * msg, ...);

ULONG   EXPENTRY WgtInitModule(HAB hab, HMODULE hmodSelf, HMODULE hmodXFLDR,
                               XCENTERWIDGETCLASS ** ppaClasses, PSZ pszErrorMsg);
void    EXPENTRY WgtUnInitModule(void);
void    EXPENTRY WgtQueryVersion(ULONG * major, ULONG * minor, ULONG * revision);

/****************************************************************************/
/*  Global data                                                             */
/****************************************************************************/

static XCENTERWIDGETCLASS s_WidgetClasses[] =
{
    MW_WNDCLASS,
    0,
    "MemMap",
    "Memory Map",
    WGTF_SIZEABLE | WGTF_UNIQUEPERXCENTER | WGTF_TOOLTIP,
    &mwShowSettings
};

BOOL        fFalse = FALSE;
BOOL        fVerbose = FALSE;
HMODULE     hmodXWP = 0;
LONG        dfltWidth = 80;
LONG        dfltMinWidth = 40;

PCTRSCANSETUPSTRING      pctrScanSetupString  = 0;
PCTRFREESETUPVALUE       pctrFreeSetupValue   = 0;
PCTRPARSECOLORSTRING     pctrParseColorString = 0;
PCMNQUERYDEFAULTFONT     pcmnQueryDefaultFont = 0;
PCMNQUERYHELPLIBRARY     pcmnQueryHelpLibrary = 0;
PCTLMAKECOLORRECT        pctlMakeColorRect    = 0;
PCMNGETSTRING            pcmnGetString        = 0;
PCMNQUERYNLSMODULEHANDLE pcmnQueryNLSModuleHandle = 0;

/****************************************************************************/
/****************************************************************************/

MRESULT EXPENTRY mwWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT         ret = 0;
    HPS             hps;
    XCENTERWIDGET  *pWidg;
    MW             *pMW;

    switch (msg)
    {
        case MWM_NEWDATA:
        {
            ULONG   rc;

            pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);
            pMW = (MW*)pWidg->pUser;

            // make a copy of the mutex-protected data
            rc = DosRequestMutexSem(pMW->hmtx, 250);
            if (rc) {
                memset(&pMW->o, 0, sizeof(MWMEM));
                mwError("mwWndProc: DosRequestMutexSem - rc= %d\n", rc);
            }
            else {
                memcpy(&pMW->o, &pMW->i, sizeof(MWMEM));
                rc = DosReleaseMutexSem(pMW->hmtx);
                if (rc)
                    mwError("mwWndProc: DosReleaseMutexSem - rc= %d\n", rc);
            }

            if (pMW->fShowing)
                WinPostMsg(pMW->hStats, MWM_NEWDATA, 0, 0);

            hps = WinGetPS(hwnd);
            mwPaint(hwnd, hps, pWidg, FALSE);
            WinReleasePS(hps);
            break;
        }

        case MWM_NEWSETTING:
            pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);
            pMW = (MW*)pWidg->pUser;

            switch ((ULONG)mp1)
            {
                case ID_CRDI_MMAP_PRIVCLR:
                    pMW->su.clrPriv = (LONG)mp2;
                    break;

                case ID_CRDI_MMAP_FREECLR:
                    pMW->su.clrFree = (LONG)mp2;
                    break;

                case ID_CRDI_MMAP_UNUSEDCLR:
                    pMW->su.clrUnused = (LONG)mp2;
                    break;

                case ID_CRDI_MMAP_USEDCLR:
                    pMW->su.clrUsed = (LONG)mp2;
                    break;

                case ID_CRDI_MMAP_GRID:
                    pMW->su.fGrid = (BOOL)mp2;
                    break;

                case ID_CRDI_MMAP_UNDO:
                    memcpy(&pMW->su, &pMW->suBak, sizeof(MWSETUP));
                    break;

                case ID_CRDI_MMAP_DEFAULT:
                    mwScanSetup(0, &pMW->su);
                    break;
            }
            
            hps = WinGetPS(hwnd);
            mwPaint(hwnd, hps, pWidg, FALSE);
            WinReleasePS(hps);

            if (pMW->hStats && (ULONG)mp1 != ID_CRDI_MMAP_GRID)
                WinPostMsg(pMW->hStats, msg, mp1, mp2);
            
            break;

        case WM_PAINT:
            pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);

            hps = WinBeginPaint(hwnd, 0, 0);
            mwPaint(hwnd, hps, pWidg, TRUE);
            WinEndPaint(hps);
            break;

        case WM_BUTTON1DOWN:
            mwToggleStats(hwnd);
            break;

        case WM_CONTROL:
            if (mp1 == MPFROM2SHORT(ID_XCENTER_CLIENT, XN_QUERYSIZE) && mp2) {
                SIZEL *szl = (SIZEL*)mp2;

                pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);
                pMW = (MW*)pWidg->pUser;

                szl->cx = pMW->su.cx < dfltMinWidth ? dfltMinWidth : pMW->su.cx;
                szl->cy = WinQuerySysValue(HWND_DESKTOP, SV_CXICON) / 2;
                ret = (MRESULT)TRUE;
                break;
            }

            if (mp1 == MPFROM2SHORT(ID_XCENTER_TOOLTIP, TTN_NEEDTEXT) && mp2) {
                TOOLTIPTEXT *ptt = (TOOLTIPTEXT*)mp2;

                pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);
                pMW = (MW*)pWidg->pUser;

                ptt->ulFormat = TTFMT_PSZ;
                ptt->pszText = pMW->fShowing ? pMW->szHideStats : pMW->szShowStats;
                ptt->pszText = pcmnGetString(pMW->fShowing ?
                                             ID_CRDI_MMAP_HIDESTATS :
                                             ID_CRDI_MMAP_SHOWSTATS);
                ret = (MRESULT)TRUE;
                break;
            }
            break;

        case WM_WINDOWPOSCHANGED:
        {
            SWP    *pNew = (SWP*)mp1;
            char    szSetup[256];

            if (!(pNew->fl & SWP_SIZE) || pNew[0].cx == pNew[1].cx)
                break;

            pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);
            pMW = (MW*)pWidg->pUser;

            pMW->su.cx = pNew->cx;
            pMW->suBak.cx = pNew->cx;

            if (pNew->cx < dfltMinWidth)
                WinPostMsg(WinQueryWindow(hwnd, QW_PARENT), XCM_SETWIDGETSIZE,
                           (MP)hwnd, (MP)dfltMinWidth);
            else {
                mwSaveSetup(szSetup, &pMW->su);
                WinSendMsg(pWidg->pGlobals->hwndClient, XCM_SAVESETUP,
                           (MP)hwnd, (MP)szSetup);
            }

            break;
        }

        case WM_CREATE:
        {
            if (!mp1) {
                ret = (MRESULT)1;
                mwError("WM_CREATE: mp1 is null\n");
                break;
            }

            // link to xfldr.dll exports
            if (!mwLinkToXWP()) {
                ret = (MRESULT)1;
                break;
            }

            // DefWidgetProc() requires this
            WinSetWindowULong(hwnd, QWL_USER, (ULONG)mp1);

            // perform instance-specific init
            ret = (MRESULT)mwCreate(hwnd, (XCENTERWIDGET*)mp1);
            break;
        }

        case WM_DESTROY:
            pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);
            pMW = (MW*)pWidg->pUser;

            if (pMW) {
                if (pMW->hStats)
                    WinDestroyWindow(pMW->hStats);

                // the thread proc will free pMW & close the 2 sems
                DosPostEventSem(pMW->hev);
                DosSleep(250);
            }

            ret = pWidg->pfnwpDefWidgetProc(hwnd, msg, mp1, mp2);
            break;

        default:
            pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);
            if (pWidg)
                ret = pWidg->pfnwpDefWidgetProc(hwnd, msg, mp1, mp2);
            else {
                mwInfo("mwWndProc: pWidg is null - msg= %x\n", msg);
                ret = WinDefWindowProc(hwnd, msg, mp1, mp2);
            }
            break;
    }

    return ret;
}

/****************************************************************************/

ULONG       mwCreate(HWND hwnd, XCENTERWIDGET * pWidg)
{
    ULONG   rc;
    TID     tid;
    MW     *pMW;
    POINTL  ptl = {MW_DLGUNIT_WIDTH, 0};

do {
    pWidg->pcszHelpLibrary = pcmnQueryHelpLibrary();
    pWidg->ulHelpPanelID = ID_XSH_WIDGET_MEMORY_MAP;

    pMW = (MW*)calloc(1, sizeof(MW));
    if (!pMW) {
        mwError("mwCreate: calloc() failed\n");
        return 1;
    }
    pWidg->pUser = (void*)pMW;
    pMW->widg = pWidg;
    pMW->hwnd = hwnd;
    pMW->su.clrWidgBk = pWidg->pGlobals->lcolClientBackground;

    // convert the dflt width in dialog units to pixels;  this will
    // keep the relative width consistent at all Text Size settings
    WinMapDlgPoints(hwnd, &ptl, 1, TRUE);
    dfltWidth = ptl.x;
    dfltMinWidth = ptl.x / 2;

    // this must be called to set the defaults
    // even if the setup string is null
    pMW->su.cx = dfltWidth;
    mwScanSetup(pWidg->pcszSetupString, &pMW->su);
    memcpy(&pMW->suBak, &pMW->su, sizeof(MWSETUP));

    // access to data
    rc = DosCreateMutexSem(0, &pMW->hmtx, 0, FALSE);
    if (rc) {
        mwError("mwCreate: DosCreateMutexSem - rc= %d\n", rc);
        break;
    }

    // terminate data thread when posted
    rc = DosCreateEventSem(0, &pMW->hev, 0, FALSE);
    if (rc) {
        mwError("mwCreate: DosCreateEventSem - rc= %d\n", rc);
        break;
    }

    // start data thread
    rc = DosCreateThread(&tid, &mwThreadProc, (ULONG)pMW, 0, 0xf000);
    if (rc) {
        mwError("mwCreate: DosCreateThread - rc= %d\n", rc);
        break;
    }

    return 0;

} while (fFalse);

    if (pMW->hev)
        DosCloseEventSem(pMW->hev);

    if (pMW->hmtx)
        DosCloseMutexSem(pMW->hmtx);

    free(pMW);
    pWidg->pUser = 0;

    return 1;
}

/****************************************************************************/

void        mwPaint(HWND hwnd, HPS hps, PXCENTERWIDGET pWidg, BOOL fDrawFrame)
{
    ULONG   rc;
    ULONG   ulBorder = 1;
    LONG    width;
    LONG    height;
    LONG    incr;
    LONG    left;
    POINTL  ptl;
    POINTL  ptl2;
    RECTL   rcl;
    MW     *pMW = (MW*)pWidg->pUser;

    GpiCreateLogColorTable(hps, 0, LCOLF_RGB, 0, 0, 0);

    // get window rectl
    WinQueryWindowRect(hwnd, &rcl);
    rcl.xRight -= ulBorder;
    rcl.yTop   -= ulBorder;

    if (fDrawFrame) {

        // fill with bkgnd color
        ptl.x = rcl.xLeft;
        ptl.y = rcl.yBottom;
        GpiMove(hps, &ptl);

        ptl.x = rcl.xRight;
        ptl.y = rcl.yTop;
        GpiSetColor(hps, pMW->su.clrWidgBk);
        GpiBox(hps, DRO_OUTLINEFILL, &ptl, 0, 0);

        // frame-around-statics option
        if (pWidg->pGlobals->flDisplayStyle & XCS_SUNKBORDERS)
        {
            LONG    clrLeft = pWidg->pGlobals->lcol3DDark;
            LONG    clrRight = pWidg->pGlobals->lcol3DLight;

            ptl.x = rcl.xLeft;
            ptl.y = rcl.yBottom;
            GpiMove(hps, &ptl);

            // left & top
            GpiSetColor(hps, clrLeft);
            ptl.y = rcl.yTop;
            GpiLine(hps, &ptl);
            ptl.x = rcl.xRight;
            GpiLine(hps, &ptl);

            // right & bottom
            GpiSetColor(hps, clrRight);
            ptl.y = rcl.yBottom;
            GpiLine(hps, &ptl);
            ptl.x = rcl.xLeft;
            GpiLine(hps, &ptl);
        }
    }

    // possible at startup
    if (!pMW)
        return;

    // set RECTL to size & position of the graph
    rcl.xLeft   += ulBorder;
    rcl.yBottom += ulBorder;
    rcl.xRight -= ulBorder;
    rcl.yTop   -= ulBorder;

    // calc height, ensure there's at least one
    // pixel available to separate the two grapshs
    height = rcl.yTop - rcl.yBottom;
    if (!(height & 1))
        height--;
    height = (height / 2) - 1;

    // make the graph a multiple of 8 so the legend lines up correctly
    incr = (rcl.xRight - rcl.xLeft) / 8;
    width = incr * 8;

    // center the graph
    left = rcl.xLeft + (rcl.xRight - rcl.xLeft - width) / 2;

    // draw the lomem and himem graphs
    mwDrawBoxes(pMW, hps, left, rcl.yBottom,              width, height, &pMW->o.loPriv);
    mwDrawBoxes(pMW, hps, left, rcl.yBottom + height + 2, width, height, &pMW->o.hiPriv);

    // because GpiBox() uses an inclusive rect, the boxes overlap except
    // for the last one which ends up 1px too wide - this erases the excess
    ptl.x = left + width;
    ptl.y = rcl.yBottom;
    ptl2.x = ptl.x;
    ptl2.y = rcl.yTop;
    GpiMove(hps, &ptl);
    GpiSetColor(hps, pMW->su.clrWidgBk);
    GpiLine(hps, &ptl2);

    // draw the legend
    if (pMW->su.fGrid) {
        ptl.x = left;
        ptl2.x = ptl.x;

        for (rc = 0; rc < 8; rc++) {
            GpiMove(hps, &ptl);
            GpiLine(hps, &ptl2);
            ptl.x += incr;
            ptl2.x += incr;
        }
    }

    return;
}

/****************************************************************************/

void        mwDrawBoxes(MW *pMW, HPS hps, LONG x, LONG y, LONG cx, LONG cy, ULONG *pVal)
{
    LONG    total;
    LONG    cxPriv;
    LONG    cxFree;
    LONG    cxUnused;
    LONG    cxUsed;
    POINTL  ptl;
    POINTL  ptl2;

    // exit if nothing to draw
    total = pVal[0] + pVal[1] + pVal[2];
    if (!total)
        return;

    // if there's any private, shared used, or shared unused,
    // ensure at least 1 line gets drawn;
    // put accumulated rounding errors into cxFree

    cxPriv = pVal[0] * cx / total;
    if (!cxPriv && pVal[0])
        cxPriv++;

    cxUnused = (pVal[2] - pVal[3]) * cx / total;
    if (!cxUnused && (pVal[2] - pVal[3]))
        cxUnused++;

    cxUsed = pVal[3] * cx / total;
    if (!cxUsed && pVal[3])
        cxUsed++;

    cxFree = cx - (cxPriv + cxUnused + cxUsed);

    // private
    ptl.x = x;
    ptl.y = y;
    GpiMove(hps, &ptl);
    ptl2.x = ptl.x + cxPriv;
    ptl2.y = ptl.y + cy;
    GpiSetColor(hps, pMW->su.clrPriv);
    GpiBox(hps, DRO_OUTLINEFILL, &ptl2, 0, 0);

    // free
    ptl.x = ptl2.x;
    GpiMove(hps, &ptl);
    ptl2.x += cxFree;
    GpiSetColor(hps, pMW->su.clrFree);
    GpiBox(hps, DRO_OUTLINEFILL, &ptl2, 0, 0);

    // shared unused
    ptl.x = ptl2.x;
    GpiMove(hps, &ptl);
    ptl2.x += cxUnused;
    GpiSetColor(hps, pMW->su.clrUnused);
    GpiBox(hps, DRO_OUTLINEFILL, &ptl2, 0, 0);

    // shared used
    ptl.x = ptl2.x;
    GpiMove(hps, &ptl);
    ptl2.x += cxUsed;
    GpiSetColor(hps, pMW->su.clrUsed);
    GpiBox(hps, DRO_OUTLINEFILL, &ptl2, 0, 0);

    return;
}

/****************************************************************************/
/****************************************************************************/

void        mwToggleStats(HWND hwnd)
{
    XCENTERWIDGET  *pWidg;
    MW             *pMW;
    RECTL           rcl;
    SWP             swp;

    pWidg = (XCENTERWIDGET*)WinQueryWindowULong(hwnd, QWL_USER);
    pMW = (MW*)pWidg->pUser;

    if (pMW->fShowing) {
        WinSendMsg(pMW->hStats, WM_CLOSE, 0, 0);
        return;
    }
    pMW->fShowing = TRUE;

    if (!pMW->hStats) {
        pMW->hStats = WinLoadDlg(HWND_OBJECT, hwnd, &mwStatsProc,
                                 pcmnQueryNLSModuleHandle(FALSE),
                                 ID_CRDI_MMAP_STATS, pMW);
        if (!pMW->hStats) {
            mwError("mwToggleStats: WinLoadDlg() failed\n");
            return;
        }
    }

    WinSendMsg(pMW->hStats, MWM_NEWDATA, 0, 0);

    // center stats over widget
    WinQueryWindowRect(pMW->hStats, &rcl);
    WinQueryWindowPos(hwnd, &swp);
    rcl.xLeft = swp.x + (swp.cx - rcl.xRight) / 2;

    // position above/below XCenter
    if (pMW->widg->pGlobals->ulPosition == XCENTER_TOP)
        rcl.yBottom = swp.y - rcl.yTop - 4;
    else
        rcl.yBottom = swp.y + swp.cy + 4;

    // map XCenter coordinates to Desktop
    WinMapWindowPoints(WinQueryWindow(hwnd, QW_PARENT), HWND_DESKTOP, (POINTL*)&rcl, 1);

    // adjust the pos if the window is partially off-screen
    if (rcl.xLeft < 0)
        rcl.xLeft = 0;
    else {
        LONG cx = WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN) - (rcl.xLeft + rcl.xRight);
        if (cx < 0)
            rcl.xLeft += cx;
    }

    WinSetWindowPos(pMW->hStats, HWND_TOP, rcl.xLeft, rcl.yBottom, 0, 0,
                    SWP_MOVE | SWP_ZORDER | SWP_NOREDRAW |
                    SWP_NOADJUST | SWP_DEACTIVATE);

    WinSetParent(pMW->hStats, HWND_DESKTOP, TRUE);

    return;
}

/****************************************************************************/

MRESULT EXPENTRY mwStatsProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT     ret = 0;
    ULONG       ul;
    MW         *pMW;

    switch (msg)
    {
        case WM_INITDLG:
        {
            // save pMW
            WinSetWindowULong(hwnd, QWL_USER, (ULONG)mp2);
            pMW  = (MW*)mp2;

            // subclass the shield window which overlays all of the statics;
            // throw away its wndproc which we will never use
            WinSubclassWindow(WinWindowFromID(hwnd, ID_CRDI_MMAP_SHIELD), mwShieldProc);

            // set dlg colors and NLS text
            mwSetDlgText(hwnd, FALSE);
            mwSetDlgColors(hwnd, &pMW->su);

            // set total hi & lo memory fields
            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_LOTOTAL, (USHORT)512, FALSE);
            if (DosQuerySysInfo(QSV_VIRTUALADDRESSLIMIT, QSV_VIRTUALADDRESSLIMIT,
                                &ul, sizeof(ul)))
                ul = 512;
            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_HITOTAL, (USHORT)(ul - 512), FALSE);

            ret = (MRESULT)1;
            break;
        }

        case MWM_NEWDATA:
            pMW = (MW*)WinQueryWindowULong(hwnd, QWL_USER);

            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_LOPRIV,   pMW->i.loPriv, FALSE);
            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_LOFREE,   pMW->i.loFree, FALSE);
            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_LOUNUSED, pMW->i.loShared - pMW->i.loUsed, FALSE);
            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_LOUSED,   pMW->i.loUsed, FALSE);

            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_HIPRIV,   pMW->i.hiPriv, FALSE);
            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_HIFREE,   pMW->i.hiFree, FALSE);
            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_HIUNUSED, pMW->i.hiShared - pMW->i.hiUsed, FALSE);
            WinSetDlgItemShort(hwnd, ID_CRDI_MMAP_HIUSED,   pMW->i.hiUsed, FALSE);
            break;

        case MWM_NEWSETTING:
            pMW = (MW*)WinQueryWindowULong(hwnd, QWL_USER);
            mwSetDlgColors(hwnd, &pMW->su);
            break;

        case WM_CLOSE:
        case WM_COMMAND:
        case WM_SYSCOMMAND:
        {
            HWND    hFocus = WinQueryFocus(HWND_DESKTOP);

            pMW = (MW*)WinQueryWindowULong(hwnd, QWL_USER);

            pMW->fShowing = FALSE;
            WinSetParent(hwnd, HWND_OBJECT, TRUE);
            WinShowWindow(WinWindowFromID(hwnd, ID_CRDI_MMAP_XBTN), FALSE);
            if (hFocus != hwnd)
                WinFocusChange(HWND_DESKTOP, hFocus, 0);
            ret = (MRESULT)TRUE;
            break;
        }

        // sent by the shield window
        case WM_BUTTON1DOWN:
        case WM_BUTTON2DOWN:
        case WM_BUTTON3DOWN:
            WinSetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
                            SWP_ZORDER | SWP_DEACTIVATE | SWP_NOADJUST);
            ret = (MRESULT)1;
            break;

        case WM_BUTTON1MOTIONSTART:
        case WM_BEGINDRAG:
        {
            HWND    hFocus = WinQueryFocus(HWND_DESKTOP);
            WinSendMsg(hwnd,WM_TRACKFRAME, (MPARAM)(TF_MOVE | TF_STANDARD), 0);
            WinShowWindow(WinWindowFromID(hwnd, ID_CRDI_MMAP_XBTN), TRUE);
            WinFocusChange(HWND_DESKTOP, hFocus, 0);
            WinSetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
                            SWP_ZORDER | SWP_DEACTIVATE | SWP_NOADJUST);
            ret = (MRESULT)1;
            break;
        }

        default:
            ret = WinDefDlgProc(hwnd, msg, mp1, mp2);
            break;
    }

    return ret;
}

/****************************************************************************/

// This window is at the top of the z-order and covers the entire
// stats dialog. It allows the user to drag the window elsewhere
// regardless of where the mouse pointer is in the dialog.

MRESULT EXPENTRY mwShieldProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT     ret = 0;

    switch (msg)
    {
        case WM_BUTTON1DOWN:
        case WM_BUTTON2DOWN:
        case WM_BUTTON3DOWN:
            WinSendMsg(WinQueryWindow(hwnd, QW_OWNER), msg, mp1, mp2);
            ret = (MRESULT)1;
            break;

        case WM_BUTTON1MOTIONSTART:
        case WM_BEGINDRAG:
            WinSendMsg(WinQueryWindow(hwnd, QW_OWNER), msg, mp1, mp2);
            ret = (MRESULT)1;
            break;

        default:
            ret = WinDefWindowProc(hwnd, msg, mp1, mp2);
            break;
    }

    return ret;
}

/****************************************************************************/
/****************************************************************************/

void EXPENTRY mwShowSettings(WIDGETSETTINGSDLGDATA* pData)
{
    MWSETUP *pSU;
    RECTL   rcl;
    char    szSetup[256];

    // if the widget isn't open, we aren't linked to xfldr.dll yet
    if (!mwLinkToXWP())
        return;

    pSU = (MWSETUP*)calloc(2, sizeof(MWSETUP));
    if (!pSU) {
        mwError("mwShowSettings: calloc() failed\n");
        return;
    }
    pData->pUser = pSU;

    mwScanSetup(pData->pcszSetupString, pSU);
    if (pData->pView && pData->pView->hwndWidget) {
        WinQueryWindowRect(pData->pView->hwndWidget, &rcl);
        pSU->cx = rcl.xRight;
    }
    memcpy(&pSU[1], &pSU[0], sizeof(MWSETUP));

    WinDlgBox(HWND_DESKTOP, pData->hwndOwner, mwSettingsProc,
              pcmnQueryNLSModuleHandle(FALSE), ID_CRDI_MMAP_SETTINGS, pData);

    mwSaveSetup(szSetup, pSU);
    pData->pctrSetSetupString(pData->hSettings, szSetup);

    free(pSU);
}

/****************************************************************************/

MRESULT EXPENTRY mwSettingsProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    WIDGETSETTINGSDLGDATA *pData;
    MRESULT     ret = 0;
    MWSETUP    *pSU;

    switch (msg)
    {
        case WM_INITDLG:
        {
            // save pData
            WinSetWindowULong(hwnd, QWL_USER, (ULONG)mp2);
            mwInitSettingsDlg(hwnd, (WIDGETSETTINGSDLGDATA*)mp2);

            // this keeps PM from giving the focus to this window
            ret = (MRESULT)1;
            break;
        }

        case WM_CONTROL:
        {
            LONG    value = -1;

            pData = (WIDGETSETTINGSDLGDATA*)WinQueryWindowULong(hwnd, QWL_USER);
            pSU = (MWSETUP*)pData->pUser;

            if (pSU->fInInit)
                break;

            if (mp1 == MPFROM2SHORT(ID_CRDI_MMAP_GRID, BN_CLICKED)) {
                value = (BOOL)WinSendMsg((HWND)mp2, BM_QUERYCHECK, 0, 0);
                pSU->fGrid = (BOOL)value;
            }
            else
            if (SHORT2FROMMP(mp1) == EN_CHANGE) {
                if (!WinQueryPresParam((HWND)mp2, PP_BACKGROUNDCOLOR, 0, 0,
                                       sizeof(value), &value, QPF_NOINHERIT))
                    value = -1;
                else
                if (SHORT1FROMMP(mp1) == ID_CRDI_MMAP_PRIVCLR)
                    pSU->clrPriv = value;
                else
                if (SHORT1FROMMP(mp1) == ID_CRDI_MMAP_FREECLR)
                    pSU->clrFree = value;
                else
                if (SHORT1FROMMP(mp1) == ID_CRDI_MMAP_UNUSEDCLR)
                    pSU->clrUnused = value;
                else
                if (SHORT1FROMMP(mp1) == ID_CRDI_MMAP_USEDCLR)
                    pSU->clrUsed = value;
                else
                    value = -1;
            }

            if (value != -1 && pData->pView && pData->pView->hwndWidget)
                WinPostMsg(pData->pView->hwndWidget, MWM_NEWSETTING,
                           (MP)SHORT1FROMMP(mp1), (MP)value);

            break;
        }

        case WM_COMMAND:
            pData = (WIDGETSETTINGSDLGDATA*)WinQueryWindowULong(hwnd, QWL_USER);
            pSU = (MWSETUP*)pData->pUser;
            ret = (MRESULT)1;

            if (mp1 == (MP)ID_CRDI_MMAP_UNDO) {
                memcpy(&pSU[0], &pSU[1], sizeof(MWSETUP));
                mwSetDlgColors(hwnd, pSU);
                WinCheckButton(hwnd, ID_CRDI_MMAP_GRID, pSU->fGrid);

                if (pData->pView && pData->pView->hwndWidget)
                    WinPostMsg(pData->pView->hwndWidget, MWM_NEWSETTING,
                               (MP)ID_CRDI_MMAP_UNDO, 0);
                break;
            }

            if (mp1 == (MP)ID_CRDI_MMAP_DEFAULT) {
                mwScanSetup(0, pSU);
                mwSetDlgColors(hwnd, pSU);
                WinCheckButton(hwnd, ID_CRDI_MMAP_GRID, pSU->fGrid);

                if (pData->pView && pData->pView->hwndWidget)
                    WinPostMsg(pData->pView->hwndWidget, MWM_NEWSETTING,
                               (MP)ID_CRDI_MMAP_DEFAULT, 0);
                break;
            }

            if (mp1 == (MP)DID_CANCEL)
                ret = WinDefDlgProc(hwnd, msg, mp1, mp2);

            break;

        default:
            ret = WinDefDlgProc(hwnd, msg, mp1, mp2);
            break;
    }

    return ret;
}

/****************************************************************************/

void        mwInitSettingsDlg(HWND hwnd, WIDGETSETTINGSDLGDATA *pData)
{
    LONG        adj = 0;
    MWSETUP    *pSU = (MWSETUP*)pData->pUser;
    RECTL       rclDlg;
    RECTL       rclWidg;

    // set dlg colors, NLS text, and Grid checkbox
    mwSetDlgText(hwnd, TRUE);
    mwSetDlgColors(hwnd, pSU);
    WinCheckButton(hwnd, ID_CRDI_MMAP_GRID, pSU->fGrid);

    // convert the static text fields to 'color rectangles'
    // which notify their owner when the PP_BACKGROUNDCOLOR changes
    pctlMakeColorRect(WinWindowFromID(hwnd, ID_CRDI_MMAP_PRIVCLR));
    pctlMakeColorRect(WinWindowFromID(hwnd, ID_CRDI_MMAP_FREECLR));
    pctlMakeColorRect(WinWindowFromID(hwnd, ID_CRDI_MMAP_UNUSEDCLR));
    pctlMakeColorRect(WinWindowFromID(hwnd, ID_CRDI_MMAP_USEDCLR));

    // exit if the widget isn't open - XCenter Properties NB will center it
    if (!pData->pView || !pData->pView->hwndWidget) {
        WinSetActiveWindow(HWND_DESKTOP, hwnd);
        return;
    }

    // if the Stats window is showing, get its height
    if (pData->pView->pUser) {
        MW  *pMW = (MW*)pData->pView->pUser;

        if (pMW->hStats && pMW->fShowing) {
            WinQueryWindowRect(pMW->hStats, &rclDlg);
            adj = rclDlg.yTop;
        }
    }

    // get the size of the 2 windows
    WinQueryWindowRect(hwnd, &rclDlg);
    WinQueryWindowRect(pData->pView->hwndWidget, &rclWidg);

    // center the dlg directly over/under the widget,
    // offset by the height of the stats window if showing
    rclDlg.xLeft = (rclWidg.xRight - rclDlg.xRight) / 2;
    if (pData->pGlobals->ulPosition == XCENTER_TOP)
        rclDlg.yBottom -= rclDlg.yTop + adj + 8;
    else
        rclDlg.yBottom += rclWidg.yTop + adj + 8;

    WinMapWindowPoints(pData->pView->hwndWidget, HWND_DESKTOP, (POINTL*)&rclDlg, 1);

    if (rclDlg.xLeft < 0)
        rclDlg.xLeft = 0;
    else {
        adj = WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN) - (rclDlg.xLeft + rclDlg.xRight);
        if (adj < 0)
            rclDlg.xLeft += adj;
    }
    WinSetWindowPos(hwnd, 0, rclDlg.xLeft, rclDlg.yBottom, 0, 0,
                    SWP_MOVE | SWP_ACTIVATE);

    return;
}

/****************************************************************************/
/****************************************************************************/

void        mwSetDlgColors(HWND hwnd, MWSETUP *pSU)
{
    pSU->fInInit = TRUE;

    // set PPs for the colored dividers
    WinSetPresParam(WinWindowFromID(hwnd, ID_CRDI_MMAP_PRIVCLR), PP_BACKGROUNDCOLOR,
                    sizeof(LONG), &pSU->clrPriv);
    WinSetPresParam(WinWindowFromID(hwnd, ID_CRDI_MMAP_FREECLR), PP_BACKGROUNDCOLOR,
                    sizeof(LONG), &pSU->clrFree);
    WinSetPresParam(WinWindowFromID(hwnd, ID_CRDI_MMAP_UNUSEDCLR), PP_BACKGROUNDCOLOR,
                    sizeof(LONG), &pSU->clrUnused);
    WinSetPresParam(WinWindowFromID(hwnd, ID_CRDI_MMAP_USEDCLR), PP_BACKGROUNDCOLOR,
                    sizeof(LONG), &pSU->clrUsed);

    pSU->fInInit = FALSE;

    return;
}

/****************************************************************************/

void        mwSetDlgText(HWND hwnd, BOOL fSettings)
{
    ULONG   id;
    ULONG   ul;
    char   *ptr;
    char    szText[128];

    ptr = (char*)pcmnQueryDefaultFont();
    WinSetPresParam(hwnd, PP_FONTNAMESIZE, strlen(ptr) + 1, (void*)ptr);

    WinSetWindowText(hwnd, pcmnGetString(ID_CRDI_MMAP_TITLE));

    ul = fSettings ? ID_CRDI_MMAP_LASTSETTINGS : ID_CRDI_MMAP_LASTCOMMON;
    for (id = ID_CRDI_MMAP_FIRSTCOMMON; id <= ul; id++)
        WinSetDlgItemText(hwnd, id, pcmnGetString(id));

    if (!fSettings)
        return;

    if (DosQuerySysInfo(QSV_VIRTUALADDRESSLIMIT, QSV_VIRTUALADDRESSLIMIT,
                        &ul, sizeof(ul)))
        ul = 512;
    sprintf(szText, pcmnGetString(ID_CRDI_MMAP_GRID), (ul - 512) / 8);
    WinSetDlgItemText(hwnd, ID_CRDI_MMAP_GRID, szText);

    return;
}

/****************************************************************************/
/****************************************************************************/

void        mwScanSetup(PCSZ pcszString, MWSETUP *pSU)
{
    PSZ  p;

    pSU->fGrid     = TRUE;
    pSU->clrPriv   = MWCLR_PRIV;
    pSU->clrFree   = MWCLR_FREE;
    pSU->clrUnused = MWCLR_UNUSED;
    pSU->clrUsed   = MWCLR_USED;

    if (!pcszString || !*pcszString)
        return;

    if ((p = pctrScanSetupString(pcszString, "WIDTH")) != 0 ) {
        pSU->cx = atoi(p);
        if (pSU->cx < dfltMinWidth ||
            pSU->cx > WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN) / 2)
            pSU->cx = dfltWidth;
        pctrFreeSetupValue(p);
    }
    if ((p = pctrScanSetupString(pcszString, "GRID")) != 0 ) {
        ULONG   ul;
        COUNTRYCODE cc = {0, 0};
        char    szText[8];

        ul = strlen(p);
        if (ul <= 5) {
            strcpy(szText, p);
            DosMapCase(ul, &cc, szText);
            if (!strcmp(p, "OFF") || !strcmp(p, "FALSE"))
                pSU->fGrid = FALSE;
        }
        pctrFreeSetupValue(p);
    }
    if ((p = pctrScanSetupString(pcszString, "PRIVATE")) != 0 ) {
        pSU->clrPriv = pctrParseColorString(p);
        pctrFreeSetupValue(p);
    }
    if ((p = pctrScanSetupString(pcszString, "FREE")) != 0 ) {
        pSU->clrFree = pctrParseColorString(p);
        pctrFreeSetupValue(p);
    }
    if ((p = pctrScanSetupString(pcszString, "UNUSED")) != 0 ) {
        pSU->clrUnused = pctrParseColorString(p);
        pctrFreeSetupValue(p);
    }
    if ((p = pctrScanSetupString(pcszString, "USED")) != 0 ) {
        pSU->clrUsed = pctrParseColorString(p);
        pctrFreeSetupValue(p);
    }

    return;
}

/****************************************************************************/

void        mwSaveSetup(char *pString, MWSETUP *pSU)
{
    char   *ptr = pString;

    *ptr = 0;

    if (pSU->cx != dfltWidth)
        ptr += sprintf(ptr, "WIDTH=%d;", pSU->cx);

    if (!pSU->fGrid)
        ptr += sprintf(ptr, "GRID=OFF;");

    if (pSU->clrPriv != MWCLR_PRIV)
        ptr += sprintf(ptr, "PRIVATE=%06lX;", pSU->clrPriv);

    if (pSU->clrFree != MWCLR_FREE)
        ptr += sprintf(ptr, "FREE=%06lX;", pSU->clrFree);

    if (pSU->clrUnused != MWCLR_UNUSED)
        ptr += sprintf(ptr, "UNUSED=%06lX;", pSU->clrUnused);

    if (pSU->clrUsed != MWCLR_USED)
        ptr += sprintf(ptr, "USED=%06lX;", pSU->clrUsed);

    return;
}

/****************************************************************************/
/****************************************************************************/

void    _System mwThreadProc(ULONG arg)
{
    BOOL        fPost;
    ULONG       rc;
    ULONG       loPages;
    ULONG       hiPages;
    MW         *pMW = (MW*)arg;
    ARENAREC    loBnd;
    ARENAREC    hiBnd;
    ARENAREC    lo1st;
    ARENAREC    hi1st;
    ARENAREC    arTmp;

    do {
        // get arena rec for HAR 0x0006 (shared arena boundary sentinel)
        rc = DosPerfSysCall(0xCD, (ULONG)&loBnd, sizeof(ARENAREC), LOWSHARED_HAR);
        if (rc || (loBnd.flags & 0xFFF) != 7) {
            mwError("mwThreadProc: failed to get low shared sentinel: rc= %d flg= %hx\n",
                    rc, loBnd.flags);
            break;
        }

        // get rec for 1st object
        rc = DosPerfSysCall(0xCD, (ULONG)&lo1st, sizeof(ARENAREC), loBnd.next);
        if (rc) {
            mwError("mwThreadProc: failed to get first low shared object: rc= %d\n", rc);
            break;
        }

        loPages = lo1st.pages;
        arTmp.next = lo1st.next;
        while (arTmp.next != LOWSHARED_HAR)
        {
            rc = DosPerfSysCall(0xCD, (ULONG)&arTmp, sizeof(ARENAREC), arTmp.next);
            if (rc) {
                mwError("mwThreadProc: failed to get next low shared object: rc= %d\n", rc);
                break;
            }
            loPages += arTmp.pages;
        }
        if (rc)
            break;

        // get arena rec for HAR 0x0005 (high shared arena boundary sentinel)
        rc = DosPerfSysCall(0xCD, (ULONG)&hiBnd, sizeof(ARENAREC), HIGHSHARED_HAR);
        if (rc || (hiBnd.flags & 0xFFF) != 7) {
            mwError("mwThreadProc: failed to get high shared sentinel: rc= %d flg= %hx\n",
                    rc, hiBnd.flags);
            break;
        }

        // get rec for 1st object
        rc = DosPerfSysCall(0xCD, (ULONG)&hi1st, sizeof(ARENAREC), hiBnd.next);
        if (rc) {
            mwError("mwThreadProc: failed to get first high shared object: rc= %d\n", rc);
            break;
        }

        hiPages = hi1st.pages;
        arTmp.next = hi1st.next;
        while (arTmp.next != HIGHSHARED_HAR)
        {
            rc = DosPerfSysCall(0xCD, (ULONG)&arTmp, sizeof(ARENAREC), arTmp.next);
            if (rc) {
                mwError("mwThreadProc: failed to get next high shared object: rc= %d\n", rc);
                break;
            }
            hiPages += arTmp.pages;
        }
        if (rc)
            break;

        rc = DosRequestMutexSem(pMW->hmtx, 2500);
        if (rc)
            continue;

        pMW->i.loPriv = (loBnd.linear + 8) / 16;
        pMW->i.loFree = (lo1st.linear - loBnd.linear + 8) / 16;
        pMW->i.loShared = ((loBnd.max >> 4) - lo1st.linear + 8) / 16;
        pMW->i.loUsed = (loPages + 128) / 256;

        pMW->i.hiPriv = (hiBnd.linear - 0x2000 + 8) / 16; // start of arena - start of high mem
        pMW->i.hiFree = (hi1st.linear - hiBnd.linear + 8) / 16;
        pMW->i.hiShared = ((hiBnd.max >> 4) - hi1st.linear + 8) / 16;
        pMW->i.hiUsed = (hiPages + 128) / 256;

        // has the data changed?
        fPost = (BOOL)memcmp(&pMW->i, &pMW->o, sizeof(MWMEM));

        rc = DosReleaseMutexSem(pMW->hmtx);
        if (rc)
            break;

        // don't cause a redisplay if no new data
        if (fPost)
            WinPostMsg(pMW->hwnd, MWM_NEWDATA, 0, 0);

        rc = DosWaitEventSem(pMW->hev, MW_SAMPLERATE);

    } while (rc == ERROR_TIMEOUT);

    mwInfo("mwThreadProc: terminating - rc= %d\n", rc);

    pMW->widg->pUser = 0;
    DosCloseEventSem(pMW->hev);
    DosCloseMutexSem(pMW->hmtx);
    free(pMW);

    return;
}

/****************************************************************************/
/****************************************************************************/

BOOL        mwLinkToXWP(void)
{
    if (!pctlMakeColorRect &&
        (DosQueryProcAddr(hmodXWP, 0, "ctrScanSetupString",  (PFN*)&pctrScanSetupString)  ||
         DosQueryProcAddr(hmodXWP, 0, "ctrFreeSetupValue",   (PFN*)&pctrFreeSetupValue)   ||
         DosQueryProcAddr(hmodXWP, 0, "ctrParseColorString", (PFN*)&pctrParseColorString) ||
         DosQueryProcAddr(hmodXWP, 0, "cmnQueryDefaultFont", (PFN*)&pcmnQueryDefaultFont) ||
         DosQueryProcAddr(hmodXWP, 0, "cmnQueryHelpLibrary", (PFN*)&pcmnQueryHelpLibrary) ||
         DosQueryProcAddr(hmodXWP, 0, "ctlMakeColorRect",    (PFN*)&pctlMakeColorRect)    ||
         DosQueryProcAddr(hmodXWP, 0, "cmnQueryNLSModuleHandle", (PFN*)&pcmnQueryNLSModuleHandle) ||
         DosQueryProcAddr(hmodXWP, 0, "cmnGetString",        (PFN*)&pcmnGetString))) {
        mwError("LinkToXWP: DosQueryProcAddr() failed\n");
        return FALSE;
    }

    return TRUE;
}

/****************************************************************************/
/*  Debugging functions                                                     */
/****************************************************************************/

void    mwInfo(char * msg, ...)
{
    ULONG   ul;
    va_list arg_ptr;
    char    buf[256];

    if (!fVerbose)
        return;

    va_start(arg_ptr, msg);
    vsprintf(buf, msg, arg_ptr);
    va_end(arg_ptr);

    DosWrite(2, buf, strlen(buf), &ul);
    return;
}

/****************************************************************************/

void    mwError(char * msg, ...)
{
    ULONG   ul;
    va_list arg_ptr;
    char    buf[256];

    va_start(arg_ptr, msg);
    vsprintf(buf, msg, arg_ptr);
    va_end(arg_ptr);

    DosWrite(2, buf, strlen(buf), &ul);
    return;
}

/****************************************************************************/
/*  Exported functions                                                      */
/****************************************************************************/

ULONG EXPENTRY WgtInitModule(HAB hab, HMODULE hmodSelf, HMODULE hmodXFLDR,
                             XCENTERWIDGETCLASS ** ppaClasses, PSZ pszErrorMsg)
{
    char    *ptr;

    hmodXWP = hmodXFLDR;

    if (!WinRegisterClass(hab, MW_WNDCLASS, mwWndProc,
                          CS_PARENTCLIP | CS_SIZEREDRAW | CS_SYNCPAINT,
                          sizeof(ULONG))) {
        strcpy(pszErrorMsg, "WgtInitModule: WinRegisterClass failed for MW_WNDCLASS");
        mwError(pszErrorMsg);
        return 0;
    }

    fVerbose = !(DosScanEnv("MWVERBOSE", &ptr));
    *ppaClasses = s_WidgetClasses;

    return 1;
}

/****************************************************************************/

void EXPENTRY WgtUnInitModule(void)
{
}

/****************************************************************************/

void EXPENTRY WgtQueryVersion(ULONG * major, ULONG * minor, ULONG * revision)
{
    if (major)
        *major = 1;
    if (minor)
        *minor = 0;
    if (revision)
        *revision = 16;
}

/****************************************************************************/

