
/*
 *@@sourcefile w_pulse.c:
 *      XCenter "Pulse" widget implementation.
 *      This is built into the XCenter and not in
 *      a plugin DLL.
 *
 *      This is an example of a more complicated
 *      widget since it now uses multithreading.
 *      A second thread is started on widget
 *      creation to monitor the CPU counters in
 *      the OS/2 kernel. The window proc (on the
 *      XCenter thread) still does the painting.
 *
 *      Function prefix for this file:
 *      --  Pwgt*
 *
 *@@added V0.9.7 (2000-11-27) [umoeller]
 *@@rewritten v1.0.13 (2019-05-04) [rwalsh]
 *@@header "shared\center.h"
 */

/*
 *      Copyright (C) 2000-2007 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)

/*
 *  Suggested #include order:
 *  1)  os2.h
 *  2)  C library headers
 *  3)  setup.h (code generation and debugging options)
 *  4)  headers in helpers\
 *  5)  at least one SOM implementation header (*.ih)
 *  6)  dlgids.h, headers in shared\ (as needed)
 *  7)  headers in implementation dirs (e.g. filesys\, as needed)
 *  8)  #pragma hdrstop and then more SOM headers which crash with precompiled headers
 */

#define INCL_DOSPROCESS
#define INCL_DOSEXCEPTIONS
#define INCL_DOSSEMAPHORES
#define INCL_DOSERRORS
#define INCL_DOSSPINLOCK        // for DosGetProcessorStatus()

#define INCL_WINWINDOWMGR
#define INCL_WINFRAMEMGR
#define INCL_WINMESSAGEMGR
#define INCL_WININPUT
#define INCL_WINSYS
#define INCL_WINTIMER
#define INCL_WINDIALOGS
#define INCL_WINSTATICS
#define INCL_WINBUTTONS
#define INCL_WINENTRYFIELDS

#define INCL_GPIBITMAPS
#define INCL_GPICONTROL
#define INCL_GPIPRIMITIVES
#define INCL_GPILOGCOLORTABLE
#define INCL_GPIREGIONS
#include <os2.h>

// C library headers
#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>             // needed for except.h
#include <assert.h>             // needed for except.h

// generic headers
#include "setup.h"                      // code generation and debugging options

// headers in /helpers
#include "helpers\comctl.h"             // common controls (window procs)
#include "helpers\dialog.h"             // dialog helpers
#include "helpers\dosh.h"               // Control Program helper routines
#include "helpers\except.h"             // exception handling
#include "helpers\gpih.h"               // GPI helper routines
#include "helpers\nls.h"                // National Language Support helpers
#include "helpers\winh.h"               // PM helper routines
#include "helpers\standards.h"          // some standard macros
#include "helpers\stringh.h"            // string helper routines
#include "helpers\threads.h"            // thread helpers
#include "helpers\timer.h"              // replacement PM timers
#include "helpers\xstring.h"            // extended string helpers

// SOM headers which don't crash with prec. header files

// XWorkplace implementation headers
#include "dlgids.h"                     // all the IDs that are shared with NLS
#include "shared\common.h"              // the majestic XWorkplace include file
#include "shared\errors.h"              // private XWorkplace error codes
#include "shared\helppanels.h"          // all XWorkplace help panel IDs

#include "shared\center.h"              // public XCenter interfaces

#pragma hdrstop                     // VAC++ keeps crashing otherwise

/* ******************************************************************
 *
 *   Private widget instance data
 *
 ********************************************************************/

#define XM_NEWDATAAVAILABLE WM_USER
#define PWGT_SZSEP          " /"

/*
 *@@ PULSESETUP:
 *      instance data to which setup strings correspond.
 *      This is also a member of WIDGETPRIVATE.
 *
 *      Putting these settings into a separate structure
 *      is no requirement, but comes in handy if you
 *      want to use the same setup string routines on
 *      both the open widget window and a settings dialog.
 */

// color indices for PULSESETUP.pColors[]

#define INDEX_BKGND             0
#define INDEX_TEXT              1
#define INDEX_TOTLOAD           2

#define INDEX_0LOAD             3
#define INDEX_25LOAD            4
#define INDEX_50LOAD            5
#define INDEX_75LOAD            6
#define INDEX_100LOAD           7

#define INDEX_IRQLOAD           8
#define INDEX_CNT               9
#define INDEX_CPULOAD           9

#define DLGID_LINE              2001
#define DLGID_BAR               2002
#define DLGID_BKGND             2003
#define DLGID_TEXT              2004
#define DLGID_CLR0              2005

#define STYLE_LINE              0x00
#define STYLE_BAR               0x01

typedef struct _PULSESETUP
{
    LONG            cx;                 // current width
    ULONG           ulStyle;            // line or bar graph
    PSZ             pszFont;            // malloc'd non-default font name
    PLONG           pColors;            // malloc'd array of colors
    ULONG           cntCores;           // used by settings dlg
    ULONG           cntClrWnds;         // used by settings dlg
} PULSESETUP, *PPULSESETUP;

/*
 *@@ WIDGETPRIVATE:
 *      more window data for the "pulse" widget.
 *
 *      An instance of this is created on WM_CREATE in
 *      fnwpPulseWidget and stored in XCENTERWIDGET.pUser.
 *
 *@@changed V1.0.8 (2007-08-05) [pr]: added fCreating @@fixes 994
 */

typedef struct _WIDGETPRIVATE
{
    PXCENTERWIDGET pWidget;
            // reverse ptr to general widget data ptr; we need
            // that all the time and don't want to pass it on
            // the stack with each function call

    PULSESETUP      Setup;
            // widget settings that correspond to a setup string

    BOOL            fCreating;      // added V1.0.8 (2007-08-05) [pr]

    PXBITMAP        pBitmap;        // bitmap for pulse graph; this contains only
                                    // the "client" (without the 3D frame)

    LONG            cxSep;          // width of the separator (PWGT_SZSEP -> " /")
    LONG            cxNbr;          // width of the percentage ("100%")
    LONG            cxStr;          // width of the entire display string
    LONG            cyStr;          // height of the entire display string
    LONG            cyBaseline;     // nbr of pixels below the font's baseline
    LONG            offsNbr[3];     // offset in pixels from the starting point to
                                    // the 1st, 2nd, & 3rd digits in the percentage 

    THREADINFO      tiCollect;      // collect thread

    HEV             hevExit;        // event semaphore posted on destroy to tell
                                    // collect thread to exit
    HEV             hevExitComplete; // posted by collect thread when it's done

    HMTX            hmtxData;       // mutex for protecting the data

    // All the following data must be protected with the mutex.
    // Rules about the data: main thread allocates it according
    // to the window size, collect thread moves data around,
    // but never reallocates.

    ULONG           cProcessors;        // CPU Count
    ULONG           cLoads;             // count of loads in each array
    PLONG           palLoads;           // ptr to arrays of LONGs containing user loads
                                        // per CPU, interrupt loads, and combined
                                        // totals for all CPUs

    ULONG           ulUpdateCnt;        // sequence nbr of most recent update
    ULONG           ulDisplayCnt;       // sequence nbr currently displayed

    BOOL            fRepaint;           // flag to force full repaint of bitmap
    APIRET          arc;                // if != NO_ERROR, an error occurred, and
                                        // the error code is displayed instead,
                                        // effectively disabling the widget

    BOOL            fTooltipShowing;    // TRUE only while tooltip is showing over
                                        // this widget
    PSZ             pszTooltipText;     // current tooltip text

    BOOL            fCrashed;           // set to TRUE if the pulse crashed somewhere.
                                        // This will disable display then to avoid
                                        // crashing again on each timer tick.

} WIDGETPRIVATE, *PWIDGETPRIVATE;

// indices for accessing the interrupt and totals arrays in palLoads
#define INT_LOAD    (pPrivate->cProcessors)
#define TOT_LOAD    (pPrivate->cProcessors + 1)
#define MAX_LOAD    (pPrivate->cProcessors + 2)

#define GETPAL(cpu) (&pPrivate->palLoads[pPrivate->cLoads * (cpu)])

MRESULT _System PulseDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
void    PulseInitDlg(HWND hwnd, PULSESETUP *pSetup);
void    SetLineColors(HWND hwnd, PULSESETUP *pSetup);
void    SetBarColors(HWND hwnd, PULSESETUP *pSetup);

/* ******************************************************************
 *
 *   Widget setup management
 *
 ********************************************************************/

/*
 *      This section contains shared code to manage the
 *      widget's settings. This can translate a widget
 *      setup string into the fields of a binary setup
 *      structure and vice versa. This code is used by
 *      both an open widget window and a settings dialog.
 */

/*
 *@@ QueryDefaultColor:
 *      returns the default color for the given index.
 */

STATIC LONG QueryDefaultColor(ULONG ulIndex)
{
    switch (ulIndex)
    {
        case INDEX_BKGND:
            return WinQuerySysColor(HWND_DESKTOP, SYSCLR_DIALOGBACKGROUND, 0);

        case INDEX_TEXT:
            return WinQuerySysColor(HWND_DESKTOP, SYSCLR_WINDOWTEXT, 0);

        case INDEX_IRQLOAD:
            return RGBCOL_DARKPINK;

        case INDEX_TOTLOAD:
            return RGBCOL_BLACK;

        case INDEX_0LOAD:
            return RGBCOL_DARKCYAN;

        case INDEX_25LOAD:
            return RGBCOL_BLUE;

        case INDEX_50LOAD:
            return RGBCOL_DARKPINK;

        case INDEX_75LOAD:
            return RGBCOL_RED;

        case INDEX_100LOAD:
            return RGBCOL_GREEN;

        case INDEX_CPULOAD + 0:
            return RGBCOL_DARKCYAN;

        case INDEX_CPULOAD + 1:
            return RGBCOL_BLUE;

        case INDEX_CPULOAD + 2:
            return RGBCOL_DARKYELLOW;

        case INDEX_CPULOAD + 3:
            return RGBCOL_DARKRED;
    }

    // any other CPU (4 or higher):
    return RGBCOL_DARKGRAY;
}

/*
 *@@ PwgtClearSetup:
 *      cleans up the data in the specified setup
 *      structure, but does not free the structure
 *      itself.
 */

STATIC VOID PwgtClearSetup(PPULSESETUP pSetup)
{
    if (pSetup)
    {
        FREE(pSetup->pszFont);
        FREE(pSetup->pColors);
    }
}

/*
 *@@ PwgtScanSetup:
 *      scans the given setup string and translates
 *      its data into the specified binary setup
 *      structure.
 *
 *      NOTE: It is assumed that pSetup is zeroed
 *      out. We do not clean up previous data here.
 */

STATIC VOID PwgtScanSetup(const char *pcszSetupString,
                          PPULSESETUP pSetup, ULONG cProcessors)
{
    ULONG   cnt;
    ULONG   ndx;
    char    *pStr;
    char    *pKey;
    char    *pVal;
    char    *pNext;

    // set defaults
    pSetup->cx = 130;
    pSetup->ulStyle = 0;
    pSetup->pszFont = NULL;

    // alloc the array of colors used by the widget
    // then get the default values
    cnt = INDEX_CNT + cProcessors;
    pSetup->pColors = malloc(sizeof(LONG) * cnt);
    for (ndx = 0; ndx < cnt; ndx++)
        pSetup->pColors[ndx] = QueryDefaultColor(ndx);

    // the widget may not have a setup string
    if (!pcszSetupString)
        return;

    // make a copy of the string we can modify
    pStr = strdup(pcszSetupString);
    pNext = pStr;

    // scan the string using the same logic as ctrScanSetupString()
    while (pNext) {
        pKey = pNext;
        pVal = strchr(pKey, '=');
        if (!pVal)
            break;
        *pVal++ = 0;
        pNext = strchr(pVal, ';');
        if (pNext)
            *pNext++ = 0;

        // get the key's length, minus any trailing digits
        cnt = strlen(pKey);
        while (cnt && isdigit(pKey[cnt - 1]))
            cnt--;

        // use the key's length to limit the number
        // of strings that have to be compared
        switch (cnt)
        {
            case 4:
                if (!stricmp(pKey, "FONT"))
                    pSetup->pszFont = strdup(pVal);
                break;

            case 5:
                if (!stricmp(pKey, "WIDTH"))
                    pSetup->cx = atoi(pVal);
                else
                if (!stricmp(pKey, "STYLE") && !stricmp(pVal, "BAR"))
                    pSetup->ulStyle = STYLE_BAR;
                break;

            case 6:
                if (!memicmp(pKey, "BARCOL", 6) && isdigit(pKey[6])) {
                    ndx = strtoul(&pKey[6], NULL, 10) / 25;
                    if (ndx < 5)
                        pSetup->pColors[INDEX_0LOAD + ndx] = strtol(pVal, NULL, 16);
                }
                break;

            case 7:
                if (!stricmp(pKey, "BGNDCOL"))
                    pSetup->pColors[INDEX_BKGND] = strtol(pVal, NULL, 16);
                else
                if (!stricmp(pKey, "TEXTCOL"))
                    pSetup->pColors[INDEX_TEXT] = strtol(pVal, NULL, 16);
                else
                if (!memicmp(pKey, "GRPHCOL", 7) && isdigit(pKey[7])) {
                    ndx = strtoul(&pKey[7], NULL, 10);
                    if (ndx < cProcessors)
                        pSetup->pColors[INDEX_CPULOAD + ndx] = strtol(pVal, NULL, 16);
                }
                break;

            case 10:
                if (!stricmp(pKey, "GRPHTOTCOL"))
                    pSetup->pColors[INDEX_TOTLOAD] = strtol(pVal, NULL, 16);
                break;

            case 11:
                if (!stricmp(pKey, "GRPHINTRCOL"))
                    pSetup->pColors[INDEX_IRQLOAD] = strtol(pVal, NULL, 16);
                break;
        }
    }

    free(pStr);
}

/*
 *@@ PwgtSaveSetup:
 *      composes a new setup string.
 *      The caller must invoke xstrClear on the string after use.
 */

STATIC VOID PwgtSaveSetup(PXSTRING pstrSetup, PPULSESETUP pSetup,
                          ULONG cProcessors)
{
    CHAR    szTemp[256];
    ULONG   ctr;
    ULONG   ul;

    xstrInit(pstrSetup, 256);

    // width
    sprintf(szTemp, "WIDTH=%d;", pSetup->cx);
    xstrcat(pstrSetup, szTemp, 0);

    // non-default font
    if (pSetup->pszFont)
    {
        sprintf(szTemp, "FONT=%s;", pSetup->pszFont);
        xstrcat(pstrSetup, szTemp, 0);
    }

    // style
    if (pSetup->ulStyle & STYLE_BAR)
    {
        strcpy(szTemp, "STYLE=BAR;");
        xstrcat(pstrSetup, szTemp, 0);
    }

    // background color
    if (pSetup->pColors[INDEX_BKGND] != QueryDefaultColor(INDEX_BKGND))
    {
        sprintf(szTemp, "BGNDCOL=%06lX;", pSetup->pColors[INDEX_BKGND]);
        xstrcat(pstrSetup, szTemp, 0);
    }

    // text color
    if (pSetup->pColors[INDEX_TEXT] != QueryDefaultColor(INDEX_TEXT))
    {
        sprintf(szTemp, "TEXTCOL=%06lX;", pSetup->pColors[INDEX_TEXT]);
        xstrcat(pstrSetup, szTemp, 0);
    }

    // interrupt load color
    if (pSetup->pColors[INDEX_IRQLOAD] != QueryDefaultColor(INDEX_IRQLOAD))
    {
        sprintf(szTemp, "GRPHINTRCOL=%06lX;", pSetup->pColors[INDEX_IRQLOAD]);
        xstrcat(pstrSetup, szTemp, 0);
    }

    // total load color
    if (pSetup->pColors[INDEX_TOTLOAD] != QueryDefaultColor(INDEX_TOTLOAD))
    {
        sprintf(szTemp, "GRPHTOTCOL=%06lX;", pSetup->pColors[INDEX_TOTLOAD]);
        xstrcat(pstrSetup, szTemp, 0);
    }

    // colors for each CPU
    for (ctr = 0; ctr < cProcessors; ctr++)
    {
        if (pSetup->pColors[INDEX_CPULOAD + ctr] != QueryDefaultColor(INDEX_CPULOAD + ctr))
        {
            sprintf(szTemp, "GRPHCOL%d=%06lX;", ctr, pSetup->pColors[INDEX_CPULOAD + ctr]);
            xstrcat(pstrSetup, szTemp, 0);
        }
    }

    // bargraph colors
    for (ctr = 0; ctr < 5; ctr++)
    {
        if (pSetup->pColors[INDEX_0LOAD + ctr] != QueryDefaultColor(INDEX_0LOAD + ctr))
        {
            sprintf(szTemp, "BARCOL%d=%06lX;", ctr * 25, pSetup->pColors[INDEX_0LOAD + ctr]);
            xstrcat(pstrSetup, szTemp, 0);
        }
    }
}

/* ******************************************************************
 *
 *   Helpers
 *
 ********************************************************************/

/*
 *@@ GetProcessorCount:
 *      returns the no. of processors on the system.
 *      I guess we could use DosQuerySysInfo also
 *      but this one should return the value from
 *      doshPerfGet which we should rather use.
 *
 *      Expensive call, so use with caution.
 *
 */

STATIC APIRET GetProcessorCount(PULONG pcProcessors)
{
    APIRET arc;
    PDOSHPERFSYS    pPerfData;
    if (!(arc = doshPerfOpen(&pPerfData)))
    {
        // already doshPerfOpen gets the CPU count
        // so there's no need for doshPerfGet
        *pcProcessors = pPerfData->cProcessors;

        doshPerfClose(&pPerfData);
    }

    return arc;
}

/* ******************************************************************
 *
 *   Widget settings dialog
 *
 ********************************************************************/

#define COLOR_WIDTH     60
#define COLOR_HEIGHT    16

static CONTROLDEF
    PulseOKButton = CONTROLDEF_DEFPUSHBUTTON(NULL, DID_OK, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT),
    PulseCancelButton = CONTROLDEF_PUSHBUTTON(NULL, DID_CANCEL, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT),

    ProcessorsGroup = CONTROLDEF_GROUP(
                            NULL,
                            -1,
                            -1,
                            -1),

    ColorBox    = CONTROLDEF_TEXT(NULL,
                                  DLGID_CLR0,
                                  COLOR_WIDTH,
                                  COLOR_HEIGHT),

    *paColorBoxes = NULL,   // kso: Pointer to an array of colors. Inited on first call.

    GraphTypeGroup = CONTROLDEF_GROUP(NULL,
                                      -1,
                                      -1,
                                      -1),

    LineGraph   = CONTROLDEF_FIRST_AUTORADIO(NULL,
                                             DLGID_LINE,
                                             SZL_AUTOSIZE,
                                             SZL_AUTOSIZE),

    BarGraph    = CONTROLDEF_NEXT_AUTORADIO(NULL,
                                            DLGID_BAR,
                                            SZL_AUTOSIZE,
                                            SZL_AUTOSIZE),

    PulseOthersGroup
                = CONTROLDEF_GROUP(NULL,
                                   -1,
                                   -1,
                                   -1),

    PulseBkgndCol
                = CONTROLDEF_TEXT(NULL,
                                  DLGID_BKGND,
                                  COLOR_WIDTH,
                                  COLOR_HEIGHT),
    PulseTextCol
                = CONTROLDEF_TEXT(NULL,
                                  DLGID_TEXT,
                                  COLOR_WIDTH,
                                  COLOR_HEIGHT);

static const DLGHITEM
    dlgPulseFront[] =
    {
        START_TABLE,
            START_ROW(ROW_VALIGN_TOP),
                START_GROUP_TABLE(&ProcessorsGroup),
    },
    dlgColorBox[] =
    {
                START_ROW(0),
                    CONTROL_DEF(&ColorBox),
    },
    dlgPulseTail[] =
    {
                END_TABLE,
            START_TABLE,
                START_ROW(0),
                    START_GROUP_TABLE_EXT(&PulseOthersGroup, TABLE_INHERIT_SIZE),
                        START_ROW(0),
                                CONTROL_DEF(&PulseBkgndCol),
                        START_ROW(0),
                            CONTROL_DEF(&PulseTextCol),
                    END_TABLE,
                START_ROW(0),
                    START_GROUP_TABLE_EXT(&GraphTypeGroup, TABLE_INHERIT_SIZE),
                        START_ROW(0),
                            CONTROL_DEF(&LineGraph),
                        START_ROW(0),
                            CONTROL_DEF(&BarGraph),
                    END_TABLE,
            END_TABLE,
            START_ROW(0),
                CONTROL_DEF(&PulseOKButton),
                CONTROL_DEF(&PulseCancelButton),
        END_TABLE
    };

static PDLGHITEM    padlgColorBoxes = NULL;  /* kso: Array of processor controls. */


/*
 *@@ PwgtShowSettingsDlg:
 *      shows the pulse widget settings dialog for
 *      setting up the colors.
 *
 *@@changed V1.0.8 (2007-08-05) [pr]: now setting Presparams @@fixes 994
 */

VOID _System PwgtShowSettingsDlg(PWIDGETSETTINGSDLGDATA pData)
{
    APIRET      arc;
    HWND        hwndDlg = 0;
    ULONG       cntCpu;
    ULONG       cntBox;
    ULONG       ul;
    PDLGARRAY   pArray = NULL;
    PULSESETUP  Setup;

do {
    if (GetProcessorCount(&cntCpu))
        break;

    cntBox = (cntCpu < 5) ? 5 : cntCpu + 1;

    ProcessorsGroup.pcszText = cmnGetString(ID_CRSI_PWGT_CPUGRAPHCOLORS);
    GraphTypeGroup.pcszText = cmnGetString(ID_CRSI_PWGT_GRAPHTYPE);
    LineGraph.pcszText = cmnGetString(ID_CRSI_PWGT_GRAPHPERCPU);
    BarGraph.pcszText = cmnGetString(ID_CRSI_PWGT_GRAPHAVERAGE);
    PulseOthersGroup.pcszText = cmnGetString(ID_CRSI_PWGT_OTHERCOLORS);
    PulseBkgndCol.pcszText = cmnGetString(ID_CRSI_PWGT_BACKGROUNDCOLOR);
    PulseTextCol.pcszText = cmnGetString(ID_CRSI_PWGT_TEXTCOLOR);
    PulseOKButton.pcszText = cmnGetString(DID_OK);
    PulseCancelButton.pcszText = cmnGetString(DID_CANCEL);

    // create an array of DLGHITEMs for the color windows
    if (!paColorBoxes)
    {
        paColorBoxes = malloc(sizeof(ColorBox) * cntBox);
        padlgColorBoxes = malloc(sizeof(dlgColorBox) * cntBox);
        if (!paColorBoxes || !padlgColorBoxes)
            break;

        for (ul = 0; ul < cntBox; ul++)
        {
            paColorBoxes[ul] = ColorBox;
            paColorBoxes[ul].usID += ul;

            // this copies two array entries, then modifies the second
            memcpy(&padlgColorBoxes[2 * ul], dlgColorBox, sizeof(dlgColorBox));
            padlgColorBoxes[2 * ul + 1].ul1 = (ULONG)&paColorBoxes[ul];
        }
    }

    if (dlghCreateArray(ARRAYITEMCOUNT(dlgPulseFront) +
                        ARRAYITEMCOUNT(dlgPulseTail) +
                        ARRAYITEMCOUNT(dlgColorBox) * cntBox,
                        &pArray))
        break;

    if (dlghAppendToArray(pArray, dlgPulseFront, ARRAYITEMCOUNT(dlgPulseFront)) ||
        dlghAppendToArray(pArray, padlgColorBoxes,
                          ARRAYITEMCOUNT(dlgColorBox) * cntBox) ||
        dlghAppendToArray(pArray, dlgPulseTail, ARRAYITEMCOUNT(dlgPulseTail)))
        break;

    // scan the setup string
    PwgtScanSetup(pData->pcszSetupString, &Setup, cntCpu);
    Setup.cntCores = cntCpu;
    Setup.cntClrWnds = cntBox;

    if (dlghCreateDlg(&hwndDlg,
                      pData->hwndOwner,
                      FCF_FIXED_DLG,
                      PulseDlgProc,
                      cmnGetString(ID_CRSI_PWGT_TITLE),
                      pArray->paDlgItems,
                      pArray->cDlgItemsNow,
                      &Setup,
                      cmnQueryDefaultFont()))
        break;

    winhCenterWindow(hwndDlg);

    if (WinProcessDlg(hwndDlg) == DID_OK) {
        XSTRING strSetup;

        WinSetPresParam(pData->pView->hwndWidget, PP_FOREGROUNDCOLOR,
                        sizeof(LONG), &(Setup.pColors[INDEX_TEXT]));
        WinSetPresParam(pData->pView->hwndWidget, PP_BACKGROUNDCOLOR,
                        sizeof(LONG), &(Setup.pColors[INDEX_BKGND]));

        PwgtSaveSetup(&strSetup, &Setup, cntCpu);
        pData->pctrSetSetupString(pData->hSettings, strSetup.psz);
        xstrClear(&strSetup);
    }

    WinDestroyWindow(hwndDlg);

} while(0);

    if (pArray)
        dlghFreeArray(&pArray);
}

/*
 *@@ PulseDlgProc:
 *
 */

MRESULT _System PulseDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    PULSESETUP *pSetup;

    switch (msg)
    {
        case WM_INITDLG:
            WinSetWindowULong(hwnd, QWL_USER, (ULONG)mp2);
            PulseInitDlg(hwnd, (PULSESETUP*)mp2);
            return 0;

        case WM_CONTROL:
        {
            pSetup = (PULSESETUP*)WinQueryWindowULong(hwnd, QWL_USER);
            if (!pSetup)
                break;

            if (mp1 == MPFROM2SHORT(DLGID_LINE, BN_CLICKED)) {
                pSetup->ulStyle = STYLE_LINE;
                SetLineColors(hwnd, pSetup);
                return 0;
            }

            if (mp1 == MPFROM2SHORT(DLGID_BAR, BN_CLICKED)) {
                pSetup->ulStyle = STYLE_BAR;
                SetBarColors(hwnd, pSetup);
                return 0;
            }

            // all of the color windows are subclassed by ctlMakeColorRect()
            // to have them send an EN_CHANGE when their preparams change
            if (SHORT1FROMMP(mp1) >= DLGID_BKGND &&
                SHORT1FROMMP(mp1) <  DLGID_CLR0 + pSetup->cntClrWnds &&
                SHORT2FROMMP(mp1) == EN_CHANGE) {

                ULONG   ndx;

                if (SHORT1FROMMP(mp1) == DLGID_BKGND)
                    ndx = INDEX_BKGND;
                else
                if (SHORT1FROMMP(mp1) == DLGID_TEXT)
                    ndx = INDEX_TEXT;
                else
                if (pSetup->ulStyle == STYLE_BAR)
                    ndx = INDEX_0LOAD + (SHORT1FROMMP(mp1) - DLGID_CLR0);
                else
                if (SHORT1FROMMP(mp1) == DLGID_CLR0 + pSetup->cntCores)
                    ndx = INDEX_IRQLOAD;
                else
                    ndx = INDEX_CPULOAD + (SHORT1FROMMP(mp1) - DLGID_CLR0);

                WinQueryPresParam((HWND)mp2, PP_BACKGROUNDCOLOR, 0, 0, sizeof(LONG), 
                                  &(pSetup->pColors[ndx]), QPF_NOINHERIT);
                return 0;
            }

            break;
        }
    }

    return WinDefDlgProc(hwnd, msg, mp1, mp2);
}

void    PulseInitDlg(HWND hwnd, PULSESETUP *pSetup)
{
    LONG    ctr;
    HWND    hCtl;

    hCtl = WinWindowFromID(hwnd, DLGID_BKGND);
    WinSetPresParam(hCtl, PP_BACKGROUNDCOLOR, sizeof(LONG),
                    &(pSetup->pColors[INDEX_BKGND]));

    hCtl = WinWindowFromID(hwnd, DLGID_TEXT);
    WinSetPresParam(hCtl, PP_BACKGROUNDCOLOR, sizeof(LONG),
                    &(pSetup->pColors[INDEX_TEXT]));
    WinSetPresParam(hCtl, PP_FOREGROUNDCOLOR, sizeof(LONG),
                    &(pSetup->pColors[INDEX_BKGND]));

    // subclass all of the color windows to have them
    // send a notification when their presparams change
    for (ctr = -2; ctr < (LONG)pSetup->cntClrWnds; ctr++)
        ctlMakeColorRect(WinWindowFromID(hwnd, DLGID_CLR0 + ctr));

    if (pSetup->ulStyle == STYLE_BAR) {
        WinCheckButton(hwnd, DLGID_BAR, TRUE);
        SetBarColors(hwnd, pSetup);
    }
    else {
        WinCheckButton(hwnd, DLGID_LINE, TRUE);
        SetLineColors(hwnd, pSetup);
    }
}

void    SetLineColors(HWND hwnd, PULSESETUP *pSetup)
{
    ULONG   ctr;
    HWND    hCtl;
    PCSZ    pcszCPUXUserColor = cmnGetString(ID_CRSI_PWGT_CPUXUSERCOLOR);
    char    szText[32];

    // CPU loads
    for (ctr = 0; ctr < pSetup->cntCores; ctr++)
    {
        hCtl = WinWindowFromID(hwnd, DLGID_CLR0 + ctr);

        sprintf(szText, pcszCPUXUserColor, ctr);
        WinSetWindowText(hCtl, szText);

        WinSetPresParam(hCtl, PP_BACKGROUNDCOLOR, sizeof(LONG),
                        &(pSetup->pColors[INDEX_CPULOAD + ctr]));
        WinShowWindow(hCtl, TRUE);
    }

    // IRQ load
    hCtl = WinWindowFromID(hwnd, DLGID_CLR0 + ctr);
    WinSetWindowText(hCtl, cmnGetString(ID_CRSI_PWGT_IRQLOADCOLOR));
    WinSetPresParam(hCtl, PP_BACKGROUNDCOLOR, sizeof(LONG),
                    &(pSetup->pColors[INDEX_IRQLOAD]));
    WinShowWindow(hCtl, TRUE);
    ctr++;

    // hide unneeded color windows
    while (ctr < pSetup->cntClrWnds)
    {
        hCtl = WinWindowFromID(hwnd, DLGID_CLR0 + ctr);
        WinShowWindow(hCtl, FALSE);
        ctr++;
    }
}

void    SetBarColors(HWND hwnd, PULSESETUP *pSetup)
{
    ULONG   ctr;
    HWND    hCtl;

    static char *apszLoad[5] = {"0 - 25%",
                                "25 - 50%",
                                "50 - 75%",
                                "75 - 99%",
                                0 };

    // reload string each time - changing NLS DLLs will invalidate it
    apszLoad[4] = cmnGetString(ID_CRSI_PWGT_GRAPHPEGGED);

    for (ctr = 0; ctr < 5; ctr++)
    {
        hCtl = WinWindowFromID(hwnd, DLGID_CLR0 + ctr);
        WinSetWindowText(hCtl, apszLoad[ctr]);
        WinSetPresParam(hCtl, PP_BACKGROUNDCOLOR, sizeof(LONG),
                        &(pSetup->pColors[INDEX_0LOAD + ctr]));
        WinShowWindow(hCtl, TRUE);
    }

    // hide unneeded color windows
    while (ctr < pSetup->cntClrWnds)
    {
        hCtl = WinWindowFromID(hwnd, DLGID_CLR0 + ctr);
        WinShowWindow(hCtl, FALSE);
        ctr++;
    }
}

/* ******************************************************************
 *
 *   Thread synchronization
 *
 ********************************************************************/

/*
 *@@ LockData:
 *      locks the private data. Note that we only
 *      wait half a second for the mutex to be available
 *      and return FALSE otherwise.
 */

STATIC BOOL LockData(PWIDGETPRIVATE pPrivate)
{
    return !DosRequestMutexSem(pPrivate->hmtxData, 500);
}

/*
 *@@ UnlockData:
 *      unlocks the private data.
 */

STATIC VOID UnlockData(PWIDGETPRIVATE pPrivate)
{
    DosReleaseMutexSem(pPrivate->hmtxData);
}

/* ******************************************************************
 *
 *   "Collect" thread
 *
 ********************************************************************/

/*
 *@@ fntCollect:
 *      extra thread to collect the performance data from
 *      the OS/2 kernel.
 *
 *      This thread does NOT have a PM message queue. It
 *      is started from PwgtCreate and will simply collect
 *      the performance data in a loop, sleeping for half
 *      a second after each query. Routines to display
 *      this data run on the XCenter's main thread.
 *
 *      The thread data is a pointer to WIDGETPRIVATE.
 *
 *      Note: This thread does not allocate the data fields
 *      in WIDGETPRIVATE. It only updates the fields, while
 *      the allocations (and the resizes) are still done
 *      on the main thread. To synchronize this, we have
 *      a mutex in WIDGETPRIVATE (see LockData()).
 *
 */

STATIC VOID _Optlink fntCollect(PTHREADINFO ptiMyself)
{
    BOOL            fLocked = FALSE;
    ULONG           iCPU;
    PDOSHPERFSYS    pPerfData = 0;
    PWIDGETPRIVATE  pPrivate = (PWIDGETPRIVATE)ptiMyself->ulData;

    if (!pPrivate)
        return;

    if (doshPerfOpen(&pPerfData)) {
        DosPostEventSem(pPrivate->hevExitComplete);
        return;
    }

    // give this thread a higher-than-regular priority
    // so that the "loads" array is always up-to-date
    DosSetPriority(PRTYS_THREAD, PRTYC_TIMECRITICAL, 0, 0);

    TRY_LOUD(excpt1)
    {
        // start collecting
        while (TRUE)
        {
            // get new loads from OS/2 kernel
            // LOCK the data while we're moving around here
            if (!(pPrivate->arc = doshPerfGet(pPerfData)) &&
                (fLocked = LockData(pPrivate)))
            {
                LONG    tot = 0;
                LONG    intr = 0;
                LONG    intrCnt = 0;
                LONG    *pal;

                pPrivate->ulUpdateCnt++;

                // get the loads for each CPU
                for (iCPU = 0; iCPU < pPrivate->cProcessors; iCPU++)
                {
                    pal = GETPAL(iCPU);

                    // shift each entry in the array leftwards,
                    memmove(pal, pal + 1,
                            sizeof(LONG) * (pPrivate->cLoads - 1));

                    // update the last entry with the current value
                    pal[pPrivate->cLoads - 1] = pPerfData->palLoads[iCPU];

                    tot += pPerfData->palLoads[iCPU];
                    intr += pPerfData->palIntrs[iCPU];
                    intrCnt += (pPerfData->palIntrs[iCPU] > 0);

                } // for iCPU

                // update the interrupt array
                pal = GETPAL(INT_LOAD);
                memmove(pal, pal + 1, sizeof(LONG) * (pPrivate->cLoads - 1));
                pal[pPrivate->cLoads - 1] = intrCnt ? intr / intrCnt : 0;

                // update the totals array (including interrupts)
                pal = GETPAL(TOT_LOAD);
                memmove(pal, pal + 1, sizeof(LONG) * (pPrivate->cLoads - 1));
                pal[pPrivate->cLoads - 1] = (intr + tot) / pPrivate->cProcessors;

                UnlockData(pPrivate);
                fLocked = FALSE;

                // have main thread update the display
                if (!ptiMyself->fExit)
                    WinPostMsg(pPrivate->pWidget->hwndWidget,
                               XM_NEWDATAAVAILABLE, (MPARAM)1, 0);
            }

            // exit if we have problems
            if (pPrivate->arc || ptiMyself->fExit || pPrivate->fCrashed)
                break;

            // We need to sleep for half a second but we should not use
            // DosSleep because we MUST exit quickly when the widget gets
            // destroyed. Instead wait for an event semaphore with a
            // timeout of half a second. This will return as soon as
            // PwgtDestroy() posts the semaphore

            // anything other than a timeout is a reason to exit
            if (DosWaitEventSem(pPrivate->hevExit, 500) != ERROR_TIMEOUT)
                break;

        } // end while (TRUE);
    }
    CATCH(excpt1)
    {
        pPrivate->fCrashed = TRUE;
    }
    END_CATCH();

    if (fLocked)
        UnlockData(pPrivate);

    if (pPerfData)
        doshPerfClose(&pPerfData);

    // PwgtDestroy is blocking on hevExitComplete,
    // so tell main thread we're done
    DosPostEventSem(pPrivate->hevExitComplete);
}

/* ******************************************************************
 *
 *   PM window class implementation
 *
 ********************************************************************/

/*
 *@@ PwgtMeasureString:
 *      calculate the dimensions of the text display and its
 *      components, along with the position of each digit
 *      within the percentage figures
 */

STATIC void PwgtMeasureString(HWND hwnd, PWIDGETPRIVATE pPrivate)
{
    HPS     hps;
    POINTL  ptlStart = {0,0};
    POINTL  ptlPos[5];
    char    szSep[] = PWGT_SZSEP;
    char    szNbr[] = "100%";

    hps = WinGetPS(hwnd);

    // separator width (" /")
    GpiQueryTextBox(hps, sizeof(szSep)-1, szSep, TXTBOX_COUNT, ptlPos);
    pPrivate->cxSep = ptlPos[TXTBOX_CONCAT].x;

    // percentage width ("100%")
    GpiQueryTextBox(hps, sizeof(szNbr)-1, szNbr, TXTBOX_COUNT, ptlPos);
    pPrivate->cxNbr = ptlPos[TXTBOX_CONCAT].x;

    // total width and height of the full string
    pPrivate->cxStr = (pPrivate->cProcessors * pPrivate->cxNbr) +
                      ((pPrivate->cProcessors - 1) * pPrivate->cxSep);
    pPrivate->cyStr = ptlPos[TXTBOX_TOPRIGHT].y - ptlPos[TXTBOX_BOTTOMLEFT].y;

    // needed to center text properly
    pPrivate->cyBaseline = -ptlPos[TXTBOX_BOTTOMLEFT].y;

    // offsets of each digit from the starting point
    GpiQueryCharStringPosAt(hps, &ptlStart, 0, sizeof(szNbr)-1, szNbr, 0, ptlPos);
    pPrivate->offsNbr[0] = ptlPos[0].x;
    pPrivate->offsNbr[1] = ptlPos[1].x;
    pPrivate->offsNbr[2] = ptlPos[2].x;

    WinReleasePS(hps);
}

/*
 *@@ PwgtCreate:
 *      implementation for WM_CREATE.
 *
 *@@changed V1.0.2 (2003-08-10) [bvl]: Calculating the size of the tooltip based on the string resource size @@fixes 490
 *@@changed V1.0.8 (2007-08-05) [pr]: now setting Presparams @@fixes 994
 */

STATIC MRESULT PwgtCreate(HWND hwnd, MPARAM mp1)
{
    MRESULT mrc = 0;        // continue window creation
    ULONG   cb;

    PXCENTERWIDGET pWidget = (PXCENTERWIDGET)mp1;
    PWIDGETPRIVATE pPrivate = malloc(sizeof(WIDGETPRIVATE));

    memset(pPrivate, 0, sizeof(WIDGETPRIVATE));

    // link the two together
    pWidget->pUser = pPrivate;
    pPrivate->pWidget = pWidget;

    // V1.0.8 (2007-08-05) [pr]
    pPrivate->fCreating = TRUE;

    // test open performance subsystem, get CPU count
    if (GetProcessorCount(&pPrivate->cProcessors)) {
        fprintf(stderr, "PwgtCreate: GetProcessorCount() failed\n");
        return (MRESULT)1;
    }

    PwgtScanSetup(pWidget->pcszSetupString,
                  &pPrivate->Setup,
                  pPrivate->cProcessors);

    // set window font (this affects all the cached presentation
    // spaces we use)
    winhSetWindowFont(hwnd,
                      (pPrivate->Setup.pszFont)
                       ? pPrivate->Setup.pszFont
                       // default font: use the same as in the rest of XWorkplace:
                       : cmnQueryDefaultFont());

    // V1.0.8 (2007-08-05) [pr]
    winhSetPresColor(hwnd,
                     PP_BACKGROUNDCOLOR,
                     pPrivate->Setup.pColors[INDEX_BKGND]);
    winhSetPresColor(hwnd,
                     PP_FOREGROUNDCOLOR,
                     pPrivate->Setup.pColors[INDEX_TEXT]);

    PwgtMeasureString(hwnd, pPrivate);

    // enable context menu help
    pWidget->pcszHelpLibrary = cmnQueryHelpLibrary();
    pWidget->ulHelpPanelID = ID_XSH_WIDGET_PULSE_MAIN;

    // create data arrays
    pPrivate->cLoads = pPrivate->Setup.cx > 3 ? pPrivate->Setup.cx - 2 : 2;
    cb = pPrivate->cLoads * sizeof(LONG);
    pPrivate->palLoads = (PLONG)calloc(MAX_LOAD, cb);

    // create mutex for data protection
    DosCreateMutexSem(NULL, &pPrivate->hmtxData, 0, FALSE);

    // create event sems for collect thread
    DosCreateEventSem(NULL, &pPrivate->hevExit, 0, 0);
    DosCreateEventSem(NULL, &pPrivate->hevExitComplete, 0, 0);

    // start the collect thread
    thrCreate(&pPrivate->tiCollect,
              fntCollect,
              NULL,
              "PulseWidgetCollect",
              THRF_WAIT,
              (ULONG)pPrivate);        // thread data

    // alloc buffer for tooltips (including 20 extra bytes for expansion)
    cb = (strlen(cmnGetString(ID_CRSI_PWGT_TOOLTIP2)) * pPrivate->cProcessors) +
         strlen(cmnGetString(ID_CRSI_PWGT_TOOLTIP1)) + 20;
    pPrivate->pszTooltipText = malloc(cb);

    pPrivate->fCreating = FALSE;  // V1.0.8 (2007-08-05) [pr]

    return mrc;
}

/*
 *@@ PwgtControl:
 *      implementation for WM_CONTROL.
 *
 *@@changed V1.0.8 (2007-08-05) [pr]: now setting Presparams on XN_SETUPCHANGED @@fixes 994
 */

STATIC BOOL PwgtControl(HWND hwnd, MPARAM mp1, MPARAM mp2)
{
    BOOL brc = FALSE;

    PXCENTERWIDGET pWidget;
    PWIDGETPRIVATE pPrivate;

    if (    (pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER))
         && (pPrivate = (PWIDGETPRIVATE)pWidget->pUser)
       )
    {
        USHORT  usID = SHORT1FROMMP(mp1),
                usNotifyCode = SHORT2FROMMP(mp1);

        switch (usID)
        {
            case ID_XCENTER_CLIENT:
            {
                switch (usNotifyCode)
                {
                    /*
                     * XN_QUERYSIZE:
                     *      XCenter wants to know our size.
                     */

                    case XN_QUERYSIZE:
                    {
                        PSIZEL pszl = (PSIZEL)mp2;
                        pszl->cx = pPrivate->Setup.cx;
                        pszl->cy = 10;

                        brc = TRUE;
                    }
                    break;

                    /*
                     * XN_SETUPCHANGED:
                     *      XCenter has a new setup string for
                     *      us in mp2.
                     */

                    case XN_SETUPCHANGED:
                    {
                        const char *pcszNewSetupString = (const char*)mp2;

                        // reinitialize the setup data
                        PwgtClearSetup(&pPrivate->Setup);
                        PwgtScanSetup(pcszNewSetupString,
                                      &pPrivate->Setup,
                                      pPrivate->cProcessors);

                        // V1.0.8 (2007-08-05) [pr]
                        winhSetWindowFont(pWidget->hwndWidget,
                                          (pPrivate->Setup.pszFont)
                                           ? pPrivate->Setup.pszFont
                                           // default font: use the same as in the rest of XWorkplace:
                                           : cmnQueryDefaultFont());
                        winhSetPresColor(pWidget->hwndWidget,
                                         PP_BACKGROUNDCOLOR,
                                         pPrivate->Setup.pColors[INDEX_BKGND]);
                        winhSetPresColor(pWidget->hwndWidget,
                                         PP_FOREGROUNDCOLOR,
                                         pPrivate->Setup.pColors[INDEX_TEXT]);

                        pPrivate->fRepaint = TRUE;
                        WinInvalidateRect(pWidget->hwndWidget, NULL, FALSE);
                    }
                    break;
                }
            }
            break;

            case ID_XCENTER_TOOLTIP:
                switch (usNotifyCode)
                {
                    case TTN_NEEDTEXT:
                    {
                        PTOOLTIPTEXT pttt = (PTOOLTIPTEXT)mp2;
                        //pttt->pszText = pPrivate->szTooltipText;
                        pttt->pszText = pPrivate->pszTooltipText;
                        pttt->ulFormat = TTFMT_PSZ;
                    }
                    break;

                    case TTN_SHOW:
                        pPrivate->fTooltipShowing = TRUE;
                    break;

                    case TTN_POP:
                        pPrivate->fTooltipShowing = FALSE;
                    break;
                }
            break;
        }
    } // end if (pWidget)

    return brc;
}


/*
 *@@ ReallocLoadArrays:
 *      recreates the load arrays when the graph's width changes
 */

STATIC BOOL ReallocLoadArrays(PWIDGETPRIVATE pPrivate, LONG new)
{
    LONG    old;
    ULONG   cDelta;
    ULONG   iCPU;
    PLONG   palNew;
    PLONG   palOld;
    PLONG   palNewXX;

    // reallocate load arrays while preserving existing data
    if (!LockData(pPrivate))
        return FALSE;

    old = pPrivate->cLoads;
    pPrivate->cLoads = new;
    cDelta = abs(old - new);

    // User
    palNewXX = (PLONG)malloc(sizeof(LONG) * new * MAX_LOAD);

    for (iCPU = 0; iCPU < MAX_LOAD; iCPU++)
    {
        palNew = &palNewXX[iCPU * new];
        palOld = &pPrivate->palLoads[iCPU * old];

        // window has become wider: fill the front
        // with zeroes and copy old values after that
        if (new > old)
        {
            memset(palNew, 0, cDelta * sizeof(LONG));
            memcpy(palNew + cDelta, palOld,
                   old * sizeof(LONG));
        }
        // window has become smaller: drop the first items
        else
            memcpy(palNew, palOld + cDelta,
                   new * sizeof(LONG));
    }

    free(pPrivate->palLoads);
    pPrivate->palLoads = palNewXX;

    UnlockData(pPrivate);

    return TRUE;
}

/*
 *@@ PaintBkgnd:
 *      fills part or all of the bitmap with the background color
 */

STATIC void PaintBkgnd(PWIDGETPRIVATE pPrivate, LONG lStart)
{
    HPS     hpsMem = pPrivate->pBitmap->hpsMem;
    RECTL   rcl;

    rcl.xLeft   = lStart;
    rcl.yBottom = 0;
    rcl.xRight  = pPrivate->pBitmap->szl.cx - 1;
    rcl.yTop    = pPrivate->pBitmap->szl.cy - 1;

    GpiSetColor(hpsMem, pPrivate->Setup.pColors[INDEX_BKGND]);
    gpihBox(hpsMem, DRO_FILL, &rcl);
}

/*
 *@@ PlotBarGraph:
 *      draws a colored bar for each entry in the designated
 *      load array starting at ndx; the color of the bar varies as
 *      the value of the entry exceeds thresholds of 25, 50, and 75%
 */

STATIC void PlotBarGraph(PWIDGETPRIVATE pPrivate, LONG iCPU, LONG ndx, LONG scale)
{
    LONG    lValue;
    LONG    first;
    LONG    clr;
    HPS     hpsMem = pPrivate->pBitmap->hpsMem;
    SIZEL   *pSzl = &pPrivate->pBitmap->szl;
    POINTL  ptl;

    first = pPrivate->cLoads - pPrivate->cLoads / scale;
    if (ndx < first)
        ndx = first;

    ptl.x = 2 * (ndx - first);

    // Total Loads
    for (; ndx < pPrivate->cLoads; ndx++, ptl.x += scale)
    {
        LONG    lPegged = 0;
        ULONG   cpu;

        // count the processors that were running at over 98%
        if (iCPU == TOT_LOAD) {
            for (cpu = 0; cpu < pPrivate->cProcessors; cpu++)
                lPegged += GETPAL(cpu)[ndx] > 980;
        }

        lValue = GETPAL(iCPU)[ndx];
    
        ptl.y = 0;
        GpiMove(hpsMem, &ptl);

        if (lValue < 250)
            clr = INDEX_0LOAD;
        else
        if (lValue < 500)
            clr = INDEX_25LOAD;
        else
        if (lValue < 750)
            clr = INDEX_50LOAD;
        else
            clr = INDEX_75LOAD;

        // if any processor was pegged, make that
        // part of the bar a different color
        if (lPegged) {
            GpiSetColor(hpsMem, pPrivate->Setup.pColors[INDEX_100LOAD]);
            lPegged = (lPegged * 250 > lValue) ? lValue : lPegged * 250;
            ptl.y = (pSzl->cy * lPegged) / 1000;
            GpiLine(hpsMem, &ptl);
        }

        GpiSetColor(hpsMem, pPrivate->Setup.pColors[clr]);
        ptl.y = (pSzl->cy * lValue) / 1000;
        GpiLine(hpsMem, &ptl);
    }
}

/*
 *@@ PlotLineGraph:
 *      plots a single-colored line graph for each entry
 *      in the designated load array starting at ndx
 */

STATIC void PlotLineGraph(PWIDGETPRIVATE pPrivate, LONG iCPU, LONG ndx, LONG scale)
{
    LONG    *pal;
    LONG    clr;
    LONG    first;
    HPS     hpsMem = pPrivate->pBitmap->hpsMem;
    SIZEL   *pSzl = &pPrivate->pBitmap->szl;
    POINTL  ptl;
    ULONG   cpu;

    clr = iCPU < pPrivate->cProcessors ? pPrivate->Setup.pColors[INDEX_CPULOAD + iCPU] :
          (iCPU == INT_LOAD ? pPrivate->Setup.pColors[INDEX_IRQLOAD] :
           pPrivate->Setup.pColors[INDEX_TOTLOAD]);
    GpiSetColor(hpsMem, clr);

    pal = GETPAL(iCPU);

    first = pPrivate->cLoads - pPrivate->cLoads / scale;

    if (ndx <= first) {
        ndx = first;

        ptl.x = 0;
        ptl.y = (pSzl->cy * pal[ndx]) * scale / 1000;
        ndx++;
    }
    else {
        ptl.x = ndx - first - 1;
        ptl.y = (pSzl->cy * pal[ndx-1]) * scale / 1000;
    }

    GpiMove(hpsMem, &ptl);
    ptl.x += scale;

    for (; ndx < pPrivate->cLoads; ndx++, ptl.x += scale)
    {
        ptl.y = (pSzl->cy * pal[ndx]) * scale / 1000;
        GpiLine(hpsMem, &ptl);
    }
}

/*
 *@@ UpdateTooltip:
 *      this updates the text that will be shown
 *      by the tooltip when it is next displayed
 */

STATIC void UpdateTooltip(PWIDGETPRIVATE pPrivate)
{
    PXCENTERWIDGET pWidget = pPrivate->pWidget;
    ULONG   iCPU;
    LONG    load;
    LONG    avg;
    PSZ     pTip;
    char    chDecimal;

    // update the tooltip text
    chDecimal = ((PCOUNTRYSETTINGS)pWidget->pGlobals->pCountrySettings)->cDecimal;

    // "CPU count: %d"
    // "\nCPU 0 IRQ: %lu%c%lu%c",
    pTip = pPrivate->pszTooltipText;

    load = GETPAL(INT_LOAD + 1)[-1];
    avg  = GETPAL(TOT_LOAD + 1)[-1];

    pTip += sprintf(pPrivate->pszTooltipText,
                    cmnGetString(ID_CRSI_PWGT_TOOLTIP1),
                    pPrivate->cProcessors,
                    avg / 10,
                    chDecimal,
                    avg % 10,
                    '%',
                    load / 10,
                    chDecimal,
                    load % 10,
                    '%');

    for (iCPU = 0; iCPU < pPrivate->cProcessors; iCPU++)
    {
        load = GETPAL(iCPU + 1)[-1];

        // "\nCPU %d User: %lu%c%lu%c",
        pTip += sprintf(pTip,
                        cmnGetString(ID_CRSI_PWGT_TOOLTIP2),
                        iCPU,
                        load / 10,
                        chDecimal,
                        load % 10,
                        '%');
    }

    // if tooltip is currently showing, refresh its display
    if (pPrivate->fTooltipShowing)
        WinSendMsg(pWidget->pGlobals->hwndTooltip, TTM_UPDATETIPTEXT,
                   (MPARAM)pPrivate->pszTooltipText, 0);
}

/*
 *@@ PwgtUpdateGraph:
 *      creates, validates and updates the graph bitmap.
 *
 *      This starts by confirming that the sizes of the load arrays
 *      and the bitmap match the size of the window. If not, it
 *      replaces them and forces a full repaint of the bitmap.
 *
 *      It then determines the number of load-array updates since
 *      the last repaint. If a full repaint hasn't been forced,
 *      it only draws the rightmost out-of-date columns.
 */

STATIC VOID PwgtUpdateGraph(PWIDGETPRIVATE pPrivate, LONG cx, LONG cy)
{
    PXCENTERWIDGET pWidget = pPrivate->pWidget;
    ULONG   updateCnt = 0;
    LONG    scaledLoads;
    LONG    scale = 1;

    // if the size of the load arrays doesn't match the width of the
    // bitmap, reallocate them; this must be the first validation
    // since everything below relies on pPrivate->cLoads to be accurate
    if (cx != pPrivate->cLoads) {
        if (!ReallocLoadArrays(pPrivate, cx))
            return;

        updateCnt = pPrivate->cLoads;
    }

    // if requested, force a complete repaint
    if (pPrivate->fRepaint) {
        pPrivate->fRepaint = FALSE;
        updateCnt = pPrivate->cLoads;
    }

    // if the existing bitmap is the wrong size, destroy it
    if (pPrivate->pBitmap &&
        (cx != pPrivate->pBitmap->szl.cx ||
         cy != pPrivate->pBitmap->szl.cy)) {
            gpihDestroyXBitmap(&pPrivate->pBitmap);
            pPrivate->pBitmap = 0;
    }

    // if no bitmap exists, create one of the proper size
    if (!pPrivate->pBitmap) {
        pPrivate->pBitmap = gpihCreateXBitmap(pWidget->habWidget, cx, cy);
        if (!pPrivate->pBitmap)
            return;

        updateCnt = pPrivate->cLoads;
    }

    // lock out the collect thread
    if (!LockData(pPrivate))
        return;

    // get the nbr of updates since the last bmp refresh
    if (!updateCnt)
        updateCnt = pPrivate->ulUpdateCnt - pPrivate->ulDisplayCnt;
    pPrivate->ulDisplayCnt = pPrivate->ulUpdateCnt;
    UnlockData(pPrivate);

    // if there's nothing to update, exit
    if (!updateCnt)
        return;

    if (pPrivate->Setup.ulStyle & STYLE_BAR)
        scale = 2;

    scaledLoads = pPrivate->cLoads / scale;
    if (updateCnt > scaledLoads)
        updateCnt = scaledLoads;

    // if this is a partial update, scroll the bitmap
    if (updateCnt < scaledLoads) {
        HPS     hpsMem = pPrivate->pBitmap->hpsMem;
        POINTL  aptl[3] = { {0,0}, {0,0}, {0,0} };

        aptl[1].x = cx - (updateCnt * scale);
        aptl[1].y = cy;
        aptl[2].x = updateCnt * scale;
        GpiBitBlt(hpsMem, hpsMem, 3, aptl, ROP_SRCCOPY, BBO_IGNORE);
    }

    PaintBkgnd(pPrivate, cx - (updateCnt * scale));

    // update each component starting at column 'ndx'
    if (LockData(pPrivate)) {
        LONG    iCPU;
        ULONG   ndx = pPrivate->cLoads - updateCnt;

        // bar graph for averages
        if (pPrivate->Setup.ulStyle & STYLE_BAR) {
            PlotBarGraph(pPrivate, TOT_LOAD, ndx, scale);
        }
        else {
            // line graph per CPU
            for (iCPU = 0; iCPU < pPrivate->cProcessors; iCPU++)
                PlotLineGraph(pPrivate, iCPU, ndx, scale);

            // line graph for INTs
            PlotLineGraph(pPrivate, INT_LOAD, ndx, scale);
        }

        UpdateTooltip(pPrivate);
        UnlockData(pPrivate);
    }
}

/*
 * PwgtPaintText:
 *      this composes the string that shows each processor's
 *      usage figure. The string is drawn character-by-character
 *      at fixed positions to ensure it doesn't shift left or right
 *      when the number of digits in a percentage changes. If the
 *      processor is offline, the percentage is replaced by a dash.
 */

STATIC VOID PwgtPaintText(HWND hwnd, HPS hps, PWIDGETPRIVATE pPrivate)
{
    LONG        xpos;
    LONG        cnt;
    ULONG       iCPU;
    PULONG      paulLoad;
    POINTL      ptl;
    RECTL       rcl;
    char        szSep[] = PWGT_SZSEP;
    char        szText[16];

    // lock out the collect thread, we're reading the loads array
    if (!LockData(pPrivate))
        return;

    // allocate array for strings
    paulLoad = _alloca(sizeof(ULONG) * pPrivate->cProcessors);

    for (iCPU = 0; iCPU < pPrivate->cProcessors; iCPU++)
        paulLoad[iCPU] = GETPAL(iCPU + 1)[-1];

    paulLoad[0] += GETPAL(INT_LOAD + 1)[-1];

    // unlock
    UnlockData(pPrivate);

    // center the string horizontally; if there's enough of a left margin,
    // shift it left one character to make it look more nearly centered
    WinQueryWindowRect(hwnd, &rcl);
    if (rcl.xRight > pPrivate->cxStr) {
        xpos = (rcl.xRight - pPrivate->cxStr) / 2;
        if (xpos > pPrivate->offsNbr[1])
            xpos -= pPrivate->offsNbr[1];
    }
    else
        xpos = 2;

    // center the string vertically in a way that
    // centers the text lying above the baseline
    ptl.y = (rcl.yTop - pPrivate->cyStr) / 2 + pPrivate->cyBaseline;

    // ensure the text is the proper color
    GpiSetColor(hps, pPrivate->Setup.pColors[INDEX_TEXT]);

    // show the first processor's percentage
    cnt = sprintf(szText, "%lu%%", (paulLoad[0] + 5) / 10);
    ptl.x = xpos + pPrivate->offsNbr[4-cnt];
    GpiCharStringAt(hps, &ptl, cnt, szText);
    xpos += pPrivate->cxNbr;

    // for each additional processor, display a separator,
    // then that processor's percentage
    for (iCPU = 1; iCPU < pPrivate->cProcessors && xpos < rcl.xRight; iCPU++)
    {
        ULONG   status = 1;

        ptl.x = xpos;
        GpiCharStringAt(hps, &ptl, sizeof(szSep)-1, szSep);
        xpos += pPrivate->cxSep;

        // if the load is zero, see if the processor is offline
        if (!paulLoad[iCPU])
            DosGetProcessorStatus(iCPU + 1, &status);

        // if online, display the percentage; otherwise, display a dash
        if (status) {
            cnt = sprintf(szText, "%lu%%", (paulLoad[iCPU] + 5) / 10);
            ptl.x = xpos + pPrivate->offsNbr[4-cnt];
        }
        else {
            cnt = 1;
            szText[0] = '-';
            ptl.x = xpos + pPrivate->offsNbr[2];
        }
        GpiCharStringAt(hps, &ptl, cnt, szText);
        xpos += pPrivate->cxNbr;
    }
}

/*
 * PwgtPaint2:
 *      this does the actual painting of the frame (if
 *      fDrawFrame==TRUE) and the pulse bitmap.
 *
 *      Gets called by PwgtPaint.
 *
 *      The specified HPS is switched to RGB mode before
 *      painting.
 *
 *      If DosPerfSysCall succeeds, this displays the pulse.
 *      Otherwise an error msg is displayed.
 */

STATIC VOID PwgtPaint2(HWND hwnd, HPS hps, PXCENTERWIDGET pWidget, BOOL fDrawFrame)
{

    PWIDGETPRIVATE pPrivate = (PWIDGETPRIVATE)pWidget->pUser;

    if (!pPrivate)
        return;

    TRY_LOUD(excpt1)
    {
        RECTL       rclWin;
        ULONG       ulBorder = 1;

        gpihSwitchToRGB(hps);

        // get window rectl
        WinQueryWindowRect(hwnd, &rclWin);
        rclWin.xRight -= ulBorder;
        rclWin.yTop   -= ulBorder;

        if (fDrawFrame)
        {
            LONG lDark, lLight;

            if (pPrivate->pWidget->pGlobals->flDisplayStyle & XCS_SUNKBORDERS)
            {
                lDark = pWidget->pGlobals->lcol3DDark;
                lLight = pWidget->pGlobals->lcol3DLight;
            }
            else
                lDark = lLight = pPrivate->Setup.pColors[INDEX_BKGND];

            // rcl is inclusive
            gpihDraw3DFrame(hps, &rclWin, ulBorder, lDark, lLight);
        }

        // set RECTL to size & position of the bitmap
        rclWin.xLeft   += ulBorder;
        rclWin.yBottom += ulBorder;

        // update the graph
        if (!pPrivate->fCrashed && !pPrivate->arc)
            PwgtUpdateGraph(pPrivate,
                            rclWin.xRight - rclWin.xLeft,
                            rclWin.yTop -   rclWin.yBottom);

        // if any error conditions exist, display an error message;
        // otherwise, paint the window
        if (!pPrivate->pBitmap || pPrivate->fCrashed || pPrivate->arc)
        {
            CHAR    szPaint[16] = "";
            ULONG   ulPaintLen;

            // rcl is exclusive
            WinFillRect(hps, &rclWin, pPrivate->Setup.pColors[INDEX_BKGND]);

            GpiSetColor(hps, pPrivate->Setup.pColors[INDEX_TEXT]);
            ulPaintLen = sprintf(szPaint, "E %lu", pPrivate->arc);
            WinDrawText(hps, ulPaintLen, szPaint, &rclWin, 0, 0,
                        DT_CENTER | DT_VCENTER | DT_TEXTATTRS);
        }
        else
        {
            WinDrawBitmap(hps, pPrivate->pBitmap->hbm, NULL,
                          (PPOINTL)&rclWin, 0, 0, DBM_NORMAL);
            PwgtPaintText(hwnd, hps, pPrivate);
        }
    }
    CATCH(excpt1)
    {
        pPrivate->fCrashed = TRUE;
        UnlockData(pPrivate);
    } END_CATCH();
}

/*
 *@@ PwgtPaint:
 *      implementation for WM_PAINT.
 */

STATIC VOID PwgtPaint(HWND hwnd)
{
    PXCENTERWIDGET pWidget;

    if (pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER))
    {
        HPS hps = WinBeginPaint(hwnd, NULLHANDLE, NULL);
        PwgtPaint2(hwnd, hps, pWidget, TRUE);        // draw frame
        WinEndPaint(hps);
    }
}

/*
 *@@ PwgtNewDataAvailable:
 *      handles asynchronous updates to the widget window
 */

STATIC VOID PwgtNewDataAvailable(HWND hwnd)
{
    PXCENTERWIDGET  pWidget;
    PWIDGETPRIVATE  pPrivate;

    if ((pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER)) &&
        (pPrivate = (PWIDGETPRIVATE)pWidget->pUser))
    {
        HPS hps = WinGetPS(hwnd);
        PwgtPaint2(hwnd, hps, pWidget, FALSE);        // no draw frame
        WinReleasePS(hps);
    }
}

/*
 *@@ PwgtWindowPosChanged:
 *      implementation for WM_WINDOWPOSCHANGED.
 */

STATIC VOID PwgtWindowPosChanged(HWND hwnd, MPARAM mp1, MPARAM mp2)
{
    PXCENTERWIDGET pWidget;
    PWIDGETPRIVATE pPrivate;

    if (!(((PSWP)mp1)->fl & SWP_SIZE) ||
        !(pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER)) ||
        !(pPrivate = (PWIDGETPRIVATE)pWidget->pUser))
        return;

    TRY_LOUD(excpt1)
    {
        PSWP pswpNew = (PSWP)mp1;
        PSWP pswpOld = pswpNew + 1;

        // if the width has changed...
        if (pswpNew->cx != pswpOld->cx)
        {
            XSTRING strSetup;

            // update setup string
            pPrivate->Setup.cx = pswpNew->cx;
            PwgtSaveSetup(&strSetup, &pPrivate->Setup, pPrivate->cProcessors);
            if (strSetup.ulLength)
                WinSendMsg(WinQueryWindow(hwnd, QW_PARENT), XCM_SAVESETUP,
                           (MPARAM)hwnd, (MPARAM)strSetup.psz);
            xstrClear(&strSetup);
        }

        // force bitmap update
        pPrivate->fRepaint = TRUE;
        WinInvalidateRect(hwnd, NULL, FALSE);
    }
    CATCH(excpt1)
    {
        pPrivate->fCrashed = TRUE;
    } END_CATCH();
}

/*
 *@@ PwgtPresParamChanged:
 *      implementation for WM_PRESPARAMCHANGED.
 *
 *@@changed V1.0.8 (2007-08-05) [pr]: rewrote this mess @@fixes 994
 */

STATIC VOID PwgtPresParamChanged(HWND hwnd,
                                 ULONG ulAttrChanged)
{
    PXCENTERWIDGET pWidget;
    PWIDGETPRIVATE pPrivate;

    if (    (pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER))
         && (pPrivate = (PWIDGETPRIVATE)pWidget->pUser)
       )
    {
        BOOL fInvalidate = TRUE;

        switch (ulAttrChanged)
        {
            case 0:     // layout palette thing dropped
                pPrivate->Setup.pColors[INDEX_BKGND]
                    = winhQueryPresColor(hwnd,
                                         PP_BACKGROUNDCOLOR,
                                         FALSE,
                                         SYSCLR_DIALOGBACKGROUND);
                pPrivate->Setup.pColors[INDEX_TEXT]
                    = winhQueryPresColor(hwnd,
                                         PP_FOREGROUNDCOLOR,
                                         FALSE,
                                         SYSCLR_WINDOWTEXT);
            break;

            case PP_BACKGROUNDCOLOR:
                pPrivate->Setup.pColors[INDEX_BKGND]
                    = winhQueryPresColor(hwnd,
                                         PP_BACKGROUNDCOLOR,
                                         FALSE,
                                         SYSCLR_DIALOGBACKGROUND);
            break;

            case PP_FOREGROUNDCOLOR:
                pPrivate->Setup.pColors[INDEX_TEXT]
                    = winhQueryPresColor(hwnd,
                                         PP_FOREGROUNDCOLOR,
                                         FALSE,
                                         SYSCLR_WINDOWTEXT);
            break;

            case PP_FONTNAMESIZE:
            {
                PSZ pszFont;
                if (pPrivate->Setup.pszFont)
                {
                    free(pPrivate->Setup.pszFont);
                    pPrivate->Setup.pszFont = NULL;
                }

                if (pszFont = winhQueryWindowFont(hwnd))
                {
                    // we must use local malloc() for the font
                    pPrivate->Setup.pszFont = strdup(pszFont);
                    winhFree(pszFont);
                }

                // do not do this during WM_CREATE
                if (!pPrivate->fCreating)
                {
                    PwgtMeasureString(hwnd, pPrivate);
                    WinPostMsg(pWidget->pGlobals->hwndClient,
                               XCM_REFORMAT,
                               (MPARAM)XFMF_GETWIDGETSIZES,
                               0);
                }
            }
            break;

            default:
                fInvalidate = FALSE;
        }

        if (fInvalidate)
        {
            // force a repaint
            pPrivate->fRepaint = TRUE;
            WinInvalidateRect(hwnd, NULL, FALSE);

            if (!pPrivate->fCreating)
            {
                XSTRING strSetup;

                PwgtSaveSetup(&strSetup,
                              &pPrivate->Setup,
                              pPrivate->cProcessors);
                if (strSetup.ulLength)
                    WinSendMsg(WinQueryWindow(hwnd, QW_PARENT),
                               XCM_SAVESETUP,
                               (MPARAM)hwnd,
                               (MPARAM)strSetup.psz);

                xstrClear(&strSetup);
            }
        }
    } // end if (pWidget && pPrivate)
}

/*
 *@@ PwgtButton1DblClick:
 *      open the OS/2 Kernel object on double-click.
 */

STATIC VOID PwgtButton1DblClick(HWND hwnd)
{
    PXCENTERWIDGET pWidget;
    PWIDGETPRIVATE pPrivate;

    if (    (pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER))
         && (pPrivate = (PWIDGETPRIVATE)pWidget->pUser)
       )
    {
        HOBJECT hobj;
        if (hobj = WinQueryObject("<XWP_KERNEL>"))
        {
            WinOpenObject(hobj,
                          2, // OPEN_SETTINGS,
                          TRUE);
        }
    }
}

/*
 *@@ PwgtDestroy:
 *      implementation for WM_DESTROY.
 */

STATIC VOID PwgtDestroy(HWND hwnd)
{
    PXCENTERWIDGET pWidget;
    PWIDGETPRIVATE pPrivate;

    if (    (pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER))
         && (pPrivate = (PWIDGETPRIVATE)pWidget->pUser)
       )
    {
        // FIRST thing to do is stop the collect thread
        // a) set fExit flag so that collect thread won't
        //    collect any more
        pPrivate->tiCollect.fExit = TRUE;
        // b) post exit event, in case the collect thread
        //    is currently sleeping
        DosPostEventSem(pPrivate->hevExit);
        // c) now wait for collect thread to post "exit done"
        DosWaitEventSem(pPrivate->hevExitComplete, 2000);

        if (pPrivate->pBitmap)
            gpihDestroyXBitmap(&pPrivate->pBitmap);

        DosCloseMutexSem(pPrivate->hmtxData);
        DosCloseEventSem(pPrivate->hevExit);
        DosCloseEventSem(pPrivate->hevExitComplete);

        if (pPrivate->palLoads)
            free(pPrivate->palLoads);

        PwgtClearSetup(&pPrivate->Setup);

        // do not destroy pPrivate->hdcWin, it is
        // destroyed automatically
        if (pPrivate->pszTooltipText)
            free(pPrivate->pszTooltipText);

        free(pPrivate);
    } // end if (pWidget && pPrivate);
}

/*
 *@@ fnwpPulseWidget:
 *      window procedure for the "Pulse" widget class.
 *
 *      Supported setup strings:
 *
 *      --  "TEXTCOL=rrggbb": color for the percentage text (if displayed).
 *
 *      --  "BGNDCOL=rrggbb": background color.
 *
 *      --  "GRPHCOL=rrggbb": graph color.
 *
 *      --  "FONT=point.face": presentation font.
 *
 *      --  "WIDTH=cx": widget display width.
 */

MRESULT EXPENTRY fnwpPulseWidget(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc = 0;

    switch (msg)
    {
        /*
         * WM_CREATE:
         *      as with all widgets, we receive a pointer to the
         *      XCENTERWIDGET in mp1, which was created for us.
         *
         *      The first thing the widget MUST do on WM_CREATE
         *      is to store the XCENTERWIDGET pointer (from mp1)
         *      in the QWL_USER window word by calling:
         *
         *          WinSetWindowPtr(hwnd, QWL_USER, mp1);
         *
         *      We use XCENTERWIDGET.pUser for allocating
         *      WIDGETPRIVATE for our own stuff.
         *
         *      Each widget must write its desired width into
         *      XCENTERWIDGET.cx and cy.
         */

        case WM_CREATE:
            WinSetWindowPtr(hwnd, QWL_USER, mp1);
            mrc = PwgtCreate(hwnd, mp1);
        break;

        /*
         * WM_CONTROL:
         *      process notifications/queries from the XCenter.
         */

        case WM_CONTROL:
            mrc = (MPARAM)PwgtControl(hwnd, mp1, mp2);
        break;

        /*
         * WM_PAINT:
         *
         */

        case WM_PAINT:
            PwgtPaint(hwnd);
        break;

        /*
         * XM_NEWDATAAVAILABLE:
         *      collect thread has new data --> repaint.
         *
         */

        case XM_NEWDATAAVAILABLE:
            PwgtNewDataAvailable(hwnd);
                // repaints!
        break;

        /*
         * WM_WINDOWPOSCHANGED:
         *      on window resize, allocate new bitmap.
         */

        case WM_WINDOWPOSCHANGED:
            PwgtWindowPosChanged(hwnd, mp1, mp2);
        break;

        /*
         * WM_PRESPARAMCHANGED:
         *
         */

        case WM_PRESPARAMCHANGED:
            PwgtPresParamChanged(hwnd, (ULONG)mp1);
        break;

        /*
         *@@ WM_BUTTON1DBLCLK:
         *      on double-click on clock, open
         *      system clock settings.
         */

        case WM_BUTTON1DBLCLK:
            PwgtButton1DblClick(hwnd);
            mrc = (MPARAM)TRUE;     // message processed
        break;

        /*
         * WM_DESTROY:
         *
         */

        case WM_DESTROY:
            PwgtDestroy(hwnd);
            mrc = ctrDefWidgetProc(hwnd, msg, mp1, mp2);
        break;

        default:
            mrc = ctrDefWidgetProc(hwnd, msg, mp1, mp2);
    } // end switch(msg)

    return mrc;
}


