/*
 *@@sourcefile iconsize.c:
 *      XFldDesktop "Icon Size" page implementation code.
 *
 *      Function prefix for this file:
 *      --  dtp*
 *
 *@@header filesys\textsize.h
 *@@added V1.0.17 [rwalsh]
 */

/*
 *      Copyright (C) 2023 Richard L Walsh.
 *      Copyright (C) 2023 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.
 */

/*****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <setjmp.h>             // needed for except.h
#include <assert.h>             // needed for except.h

#define INCL_DOS
#define INCL_PM
#include <os2.h>

// generic headers
#include "setup.h"                      // code generation and debugging options

// SOM headers
#include "xfdesk.h"                     // access to WPDesktop & WPFilesystem stuff
#include "xcenter.h"

// headers in /helpers
#include "helpers\gpih.h"               // GPI helper routines

// 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\notebook.h"            // generic XWorkplace notebook handling
#include "filesys\textsize.h"           // this module

/*****************************************************************************/
// typedefs and macros
/*****************************************************************************/

// The headers for 'ecomedia.dll' and 'WPPngFolder' are not GPL
// and can not be #included. However, the typedefs and function
// prototypes needed to use them can be recreated here because U.S.
// copyright law deems them to be too trivial to be copyrightable.
// Symbolic constants have been recreated with new names because
// their names may be subject to copyright but their values are not.

// last arg for Gpi2ParseIconInfo & Gpi2ReadPng
#define RETAIN_ALPHA        4

// 2nd arg for wpclsSetDefFldrAttr
#define ATTRIB_BIGICONS     0x00800000

// last arg for wpclsQueryBigIconN
#define FLAG_NOSYSICON      0x00000001

typedef LHANDLE HBITMAP2;

// ecomedia.dll exports
typedef HBITMAP2 APIENTRY GPI2PARSE(PICONINFO, PULONG, PULONG, USHORT, ULONG, ULONG, ULONG, ULONG);
typedef HBITMAP2 APIENTRY GPI2SCALE(HBITMAP2, ULONG, ULONG, BOOL);
typedef BOOL     APIENTRY GPI2DELETE(HBITMAP2);
typedef BOOL     APIENTRY GPI2DRAW(HPS, HBITMAP2, LONG, PPOINTL);
typedef HBITMAP2 APIENTRY GPI2READ(PBYTE, ULONG, ULONG);

// WPPngFolder class methods
typedef void     _System PNGCLSQRYRES(SOMClass*, ULONG*, ULONG*);
typedef void     _System PNGCLSSETRES(SOMClass*, ULONG, ULONG);
typedef ULONG    _System PNGCLSQRYATTR(SOMClass*, ULONG);
typedef ULONG    _System PNGCLSSETATTR(SOMClass*, ULONG, ULONG);
typedef HBITMAP2 _System PNGCLSQRYBIGN(SOMClass*, ULONG*, ULONG*, USHORT);
typedef BOOL     _System PNGCLSQRYCUST(SOMClass*, BOOL*);
typedef void     _System PNGCLSSETCUST(SOMClass*, BOOL);

// WPPngFolder instance methods
typedef ULONG    _System PNGQRYATTR(WPFolder*, ULONG);
typedef BOOL     _System PNGSETATTR(WPFolder*, ULONG, ULONG);
typedef void     _System PNGSETRESDEF(WPFolder*, BOOL);

// structure used to avoid excessive stack usage
// when recursing into the Desktop tree
typedef struct _RESETICONS {
    WPFileSystem   *pobj;
    ULONG           rc;
    ULONG           cnt;
    ULONG           fBig;
    ULONG           attrib;
    FILEFINDBUF3    ff;
    char            path[CCHMAXPATH];
} RESETICONS;

/*****************************************************************************/

// my favorite macro
#define MP              MPARAM

// all the data that the page uses
typedef struct _ICONSIZE
{
    BOOL        fBig;           // Big on/off currently
    BOOL        fBigOrg;        // Big on/off on entry
    BOOL        fBigDflt;       // Big on/off default
    BOOL        fPng;           // Custom icons on/off currently
    BOOL        fPngOrg;        // Custom icons on/off on entry
    BOOL        fPngOk;         // Custom icons are usable
    BOOL        fFull;          // Full on/off currently
    BOOL        fFullOrg;       // Full on/off on entry
    BOOL        fFullDflt;      // Full on/off default
    BOOL        fInit;          // suppress certain updates
    LONG        icoSize;        // icon size
    LONG        bigCur;         // Big size currently
    LONG        bigOrg;         // Big size on entry
    LONG        bigDflt;        // Big size default
    LONG        bigActual;      // Big size actually being used
    LONG        clr3DDark;      // button dark highlight
    LONG        clr3DLight;     // button light highlight
    LONG        clrItem;        // button background - not default
    LONG        clrSelItem;     // button background - default
    MPARAM      armSize;        // slider arm cx/cy
    HWND        hDlg;           // Icon size dlg hwnd
    HWND        hNB;            // Notebook hwnd
    HPOINTER    icoIcon;        // Folder icon
    HBITMAP2    icoBmp;         // Folder icon converted to HBITMAP2
    HBITMAP2    icoBig;         // Folder icon scaled
    HBITMAP2    pngBmp;         // Folder PNG converted to HBITMAP2
    HBITMAP2    pngBig;         // Folder PNG scaled
} ICONSIZE;


/*****************************************************************************/
// Global data
/*****************************************************************************/

// in textsize.c
// avoid VAC info msg "expression is always false"
extern BOOL fFalse;

// IDs for controls that need NLS text
static ULONG    nlsIDs[] =
{
    ID_XSDI_ICOSIZE_FGRP,
    ID_XSDI_ICOSIZE_ITXT,
    ID_XSDI_ICOSIZE_BTXT,
    ID_XSDI_ICOSIZE_PNG,
    ID_XSDI_ICOSIZE_XGRP,
    ID_XSDI_ICOSIZE_MTXT,
    ID_XSDI_ICOSIZE_FTXT,
    ID_XSDI_ICOSIZE_MSGTXT,
    ID_XSDI_ICOSIZE_UNDO,
    ID_XSDI_ICOSIZE_DFLT,
    DID_HELP,
    0
};

// our makeshift "accelerator table" since a real one doesn't work here;
// this is handled as 3 coordinated arrays to avoid creating yet another struct
#define ACCEL_CNT   9

// dlg text used to extract a control's mnemonic
static ULONG    accelTxt[ACCEL_CNT] =
{
    ID_XSDI_ICOSIZE_DN,
    ID_XSDI_ICOSIZE_UP,
    ID_XSDI_ICOSIZE_ITXT,
    ID_XSDI_ICOSIZE_BTXT,
    ID_XSDI_ICOSIZE_MTXT,
    ID_XSDI_ICOSIZE_FTXT,
    ID_XSDI_ICOSIZE_UNDO,
    ID_XSDI_ICOSIZE_DFLT,
    DID_HELP
};

// commands associated with each mnemonic
static ULONG    accelCmds[ACCEL_CNT] =
{
    ID_XSDI_ICOSIZE_DN,
    ID_XSDI_ICOSIZE_UP,
    ID_XSDI_ICOSIZE_ICO,
    ID_XSDI_ICOSIZE_BIG,
    ID_XSDI_ICOSIZE_MINI,
    ID_XSDI_ICOSIZE_FULL,
    ID_XSDI_ICOSIZE_UNDO,
    ID_XSDI_ICOSIZE_DFLT,
    DID_HELP
};

// array of mnemonics derived from accelTxt
// plus the characters to adjust the slider
static char     accelChars[ACCEL_CNT] = "-+";

/*****************************************************************************/

// TRUE if PNG icons are usable
static BOOL     fUpdate = FALSE;
static BOOL     fPngOk = (ULONG)-1;

static BOOL     fBigInit;       // Big on/off at startup
static BOOL     fPngInit;       // Custom icons on/off at startup
static LONG     bigInit;        // Big size at startup

// ecomedia function ptrs
GPI2PARSE      *pGpi2ParseIconInfo = 0;
GPI2DRAW       *pGpi2DrawBitmap    = 0;
GPI2DELETE     *pGpi2DeleteBitmap  = 0;
GPI2SCALE      *pGpi2ScaleBitmap2  = 0;
GPI2READ       *pGpi2ReadPng       = 0;

// WPPngFolder class ptr
SOMClass       *pWPPngFolder       = 0;

// WPPngFolder method ptrs
PNGCLSQRYRES   *pwpclsQueryResolution = 0;
PNGCLSSETRES   *pwpclsSetResolution = 0;
PNGCLSQRYATTR  *pwpclsQueryDefFldrAttr = 0;
PNGCLSSETATTR  *pwpclsSetDefFldrAttr = 0;
PNGCLSQRYBIGN  *pwpclsQueryBigIconN = 0;
PNGCLSQRYCUST  *pwpclsQueryCustomIcons = 0;
PNGCLSSETCUST  *pwpclsSetCustomIcons = 0;

PNGQRYATTR     *pwpQueryFldrAttr = 0;
PNGSETATTR     *pwpSetFldrAttr = 0;
PNGSETRESDEF   *pwpSetResolutionDefault = 0;

// somIDs
PSZ             str_WPPngFolder = "WPPngFolder";
somId           id_WPPngFolder = &str_WPPngFolder;

PSZ             str_wpclsQueryResolution = "wpclsQueryResolution";
somId           id_wpclsQueryResolution = &str_wpclsQueryResolution;
PSZ             str_wpclsSetResolution = "wpclsSetResolution";
somId           id_wpclsSetResolution = &str_wpclsSetResolution;
PSZ             str_wpclsQueryDefFldrAttr = "wpclsQueryDefFldrAttr";
somId           id_wpclsQueryDefFldrAttr = &str_wpclsQueryDefFldrAttr;
PSZ             str_wpclsSetDefFldrAttr = "wpclsSetDefFldrAttr";
somId           id_wpclsSetDefFldrAttr = &str_wpclsSetDefFldrAttr;
PSZ             str_wpclsQueryBigIconN = "wpclsQueryBigIconN";
somId           id_wpclsQueryBigIconN = &str_wpclsQueryBigIconN;
PSZ             str_wpclsQueryCustomIcons = "wpclsQueryCustomIcons";
somId           id_wpclsQueryCustomIcons = &str_wpclsQueryCustomIcons;
PSZ             str_wpclsSetCustomIcons = "wpclsSetCustomIcons";
somId           id_wpclsSetCustomIcons = &str_wpclsSetCustomIcons;

PSZ             str_wpQueryFldrAttr = "wpQueryFldrAttr";
somId           id_wpQueryFldrAttr = &str_wpQueryFldrAttr;
PSZ             str_wpSetFldrAttr = "wpSetFldrAttr";
somId           id_wpSetFldrAttr = &str_wpSetFldrAttr;
PSZ             str_wpSetResolutionDefault = "wpSetResolutionDefault";
somId           id_wpSetResolutionDefault = &str_wpSetResolutionDefault;

/*****************************************************************************/
// Prototypes
/*****************************************************************************/

VOID XWPENTRY dtpIconSizeInitPage(PNOTEBOOKPAGE pnbp, ULONG flFlags);
void        InitValues(ICONSIZE *pi);
void        InitDlgItems(HWND hwnd, ICONSIZE *pi);
BOOL        InitIco(ICONSIZE *pi);
BOOL        InitPng(ICONSIZE *pi);
void        InitLayout(HWND hwnd, ICONSIZE *pi);

BOOL XWPENTRY dtpIconSizeMsgProc(PNOTEBOOKPAGE pnbp, ULONG msg,
                                 MPARAM mp1, MPARAM mp2, MRESULT *pmrc);
void        UpdateDisplay(ICONSIZE *pi, LONG size);

void        CreateBitmaps(ICONSIZE *pi);
void        ResetAll(HWND hwnd, ICONSIZE *pi, BOOL fUndo);
void        SetBtnsAndMsg(HWND hwnd, ICONSIZE *pi);
void        PaintBtns(ICONSIZE *pi, USERBUTTON *pu, USHORT id);

VOID XWPENTRY dtpUpdateIconSize(void);
void        ResetFolderIcons(RESETICONS *pr);
void        ResetDriveIcons(BOOL fBig);

/*****************************************************************************/
/*****************************************************************************/

// called by xfdesk_xwpInsertXFldDesktopTextSizePage()
// to determine whether to add the 'Icon Size' page

BOOL XWPENTRY   dtpIsPngDesktopPresent(WPObject *somSelf)
{
    BOOL        fRtn = FALSE;
    HMODULE     hmod = 0;
    HMODULE     hmod2;
    SOMClass   *pM_WPPngFolder;

    if (pWPPngFolder)
        return TRUE;

do {
    // is WPPngFolder already loaded & are we a subclass (via replacement)
    pWPPngFolder = _somClassFromId(SOMClassMgrObject, id_WPPngFolder);
    if (!pWPPngFolder || !_somIsA(somSelf, pWPPngFolder))
        break;

    // get WPPngFolder's metaclass
    pM_WPPngFolder = _somGetClass(pWPPngFolder);
    if (!pM_WPPngFolder)
        break;

    // is ecomedia.dll already loaded
    if (DosQueryModuleHandle("ECOMEDIA", &hmod2)) {
        fprintf(stderr, "DosQueryModuleHandle(ECOMEDIA) failed\n");
        break;
    }

    // we can't access the dll unless we reload
    if (DosLoadModule(0, 0, "ECOMEDIA", &hmod)) {
        fprintf(stderr, "DosLoadModuleHandle(ECOMEDIA) failed\n");
        break;
    }

    // get the exports we need
    if (DosQueryProcAddr(hmod,  7, 0, (PFN*)&pGpi2ReadPng) ||
        DosQueryProcAddr(hmod, 12, 0, (PFN*)&pGpi2ParseIconInfo) ||
        DosQueryProcAddr(hmod, 17, 0, (PFN*)&pGpi2DrawBitmap) ||
        DosQueryProcAddr(hmod, 21, 0, (PFN*)&pGpi2DeleteBitmap) ||
        DosQueryProcAddr(hmod, 25, 0, (PFN*)&pGpi2ScaleBitmap2)) {
        fprintf(stderr, "DosQueryProcAddr failed\n");
        break;
    }

    // resolve method pointers
    if (!(pwpclsQueryResolution   = (PNGCLSQRYRES*)_somFindSMethod(pM_WPPngFolder, id_wpclsQueryResolution))  ||
        !(pwpclsSetResolution     = (PNGCLSSETRES*)_somFindSMethod(pM_WPPngFolder, id_wpclsSetResolution))  ||
        !(pwpclsQueryDefFldrAttr  = (PNGCLSQRYATTR*)_somFindSMethod(pM_WPPngFolder, id_wpclsQueryDefFldrAttr)) ||
        !(pwpclsSetDefFldrAttr    = (PNGCLSSETATTR*)_somFindSMethod(pM_WPPngFolder, id_wpclsSetDefFldrAttr)) ||
        !(pwpclsQueryBigIconN     = (PNGCLSQRYBIGN*)_somFindSMethod(pM_WPPngFolder, id_wpclsQueryBigIconN)) ||
        !(pwpQueryFldrAttr        = (PNGQRYATTR*)_somFindSMethod(pWPPngFolder, id_wpQueryFldrAttr)) ||
        !(pwpSetFldrAttr          = (PNGSETATTR*)_somFindSMethod(pWPPngFolder, id_wpSetFldrAttr)) ||
        !(pwpSetResolutionDefault = (PNGSETRESDEF*)_somFindSMethod(pWPPngFolder, id_wpSetResolutionDefault))) {
        fprintf(stderr, "somFindSMethod failed\n");
        break;
    }

    // these may not be present - if not, we'll use alternate means to get this info
    if ( (pwpclsQueryCustomIcons  = (PNGCLSQRYCUST*)_somFindSMethod(pM_WPPngFolder, id_wpclsQueryCustomIcons)) &&
        !(pwpclsSetCustomIcons    = (PNGCLSSETCUST*)_somFindSMethod(pM_WPPngFolder, id_wpclsSetCustomIcons)))
        pwpclsQueryCustomIcons = 0;

    // A-OK
    fRtn = TRUE;

} while (fFalse);

    if (!fRtn)
        pWPPngFolder = 0;

    // since the dll was already loaded, this just reduces the use count
    if (hmod)
        DosFreeModule(hmod);

    return fRtn;
}

/*****************************************************************************/

// called by xfdeskM_wpclsInitData() to get the icon settings
// in effect for this session before the user can change them

VOID XWPENTRY dtpInitIconSize(void)
{
    ULONG   val;
    ULONG   cb = sizeof(val);

    // note: the WarpIn installer initializes this INI entry using a string
    // ("1") rather than a ULONG (0x00000001) - accommodate both possibilities

    if (PrfQueryProfileData(HINI_USERPROFILE, "DYNAMIC_ICONS",
                            "UseSystemIcons", &val, &cb) &&
        ((cb == 4 && val == 1) || (cb == 2 && val == 0x31)))
        fPngInit = FALSE;
    else
        fPngInit = TRUE;

    if (PrfQueryProfileData(HINI_USERPROFILE, "DYNAMIC_ICONS",
                            "DefaultFolderAttributesIconView", &val, &cb) &&
        cb == 4 && (val & ATTRIB_BIGICONS))
        fBigInit = TRUE;
    else
        fBigInit = FALSE;

    if (PrfQueryProfileData(HINI_USERPROFILE, "DYNAMIC_ICONS",
                            "IconResolutionCy", &val, &cb) &&
        cb == 4 && val >= 10 && val <= 128)
        bigInit = (LONG)val;
    else
        bigInit = WinQuerySysValue(HWND_DESKTOP, SV_CYICON);

    return;
}

/*****************************************************************************/
// Icon Size notebook page
/*****************************************************************************/

// called by XWP's notebook handler to init the page

VOID XWPENTRY dtpIconSizeInitPage(PNOTEBOOKPAGE pnbp, ULONG flFlags)
{
    ICONSIZE   *pi = 0;

    if (!(flFlags & CBI_INIT))
        return;

    // create ICONSIZE struct which contains all variable data
    pnbp->pUser = calloc(1, sizeof(ICONSIZE));
    pi = pnbp->pUser;
    pi->hDlg = pnbp->hwndDlgPage;
    pi->hNB  = pnbp->inbp.hwndNotebook;

    // init settings
    InitValues(pi);

    // init dialog window & controls
    InitDlgItems(pi->hDlg, pi);

    // get the folder icon - if this fails,
    // something is seriously wrong, so abort
    if (!InitIco(pi)) {
        fprintf(stderr, "exit dtpIconSizeInitPage - InitIco failed\n");
        return;
    }

    // get the custom folder PNG's bitmap -
    // if this fails, disable PNG
    pi->fPngOk = InitPng(pi);
    if (!pi->fPngOk) {
        WinEnableWindow(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_PNG), FALSE);
        pi->fPng = FALSE;
    }

    // resize and reposition most everything to accommodate icon size
    InitLayout(pi->hDlg, pi);

    // generate ico and png bitmaps
    CreateBitmaps(pi);

    // set the slider arm to the current value; this had to wait until
    // the slider was resized, otherwise the arm would be mispositioned
    pi->fInit = TRUE;
    WinSendDlgItemMsg(pi->hDlg, ID_XSDI_ICOSIZE_SLD, SLM_SETSLIDERINFO,
                      MPFROM2SHORT(SMA_SLIDERARMPOSITION, SMA_INCREMENTVALUE),
                      (MP)(pi->bigCur == pi->bigDflt - 8 ? 0 :
                           pi->bigCur == pi->bigDflt + 8 ? 2 : 1));
    pi->fInit = FALSE;

    // show/hide restart msg; enable/disable Undo and Default btns
    SetBtnsAndMsg(pi->hDlg, pi);

    return;
}

/*****************************************************************************/

// init key settings: XCenter full/mini; PngD normal/dynamic,
// PNG dynamic size, PngD custom/system icons

void        InitValues(ICONSIZE *pi)
{
    ULONG   ul;
    ULONG   val;
    ULONG   cy;

    cy = WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN);
    pi->icoSize = WinQuerySysValue(HWND_DESKTOP, SV_CYICON);

    // XCenter
    pi->fFullDflt = cy > 1200;
    pi->fFull = pi->fFullOrg = _xwpclsQueryIconSize(_XCenter);

    // normal / big
    pi->fBigDflt = cy > 768;
    pi->fBigOrg = fBigInit;
    if (pwpclsQueryDefFldrAttr(pWPPngFolder, OPEN_CONTENTS) & ATTRIB_BIGICONS)
        pi->fBig = TRUE;

    // custom icons
    pi->fPngOrg = fPngInit;
    if (pwpclsQueryCustomIcons)
        pi->fPng = pwpclsQueryCustomIcons(pWPPngFolder, 0);
    else {
        ul = sizeof(val);
        if (PrfQueryProfileData(HINI_USERPROFILE, "DYNAMIC_ICONS",
                                "UseSystemIcons", &val, &ul) &&
            ((ul == 4 && val == 1) || (ul == 2 && val == 0x31)))
            pi->fPng = FALSE;
        else
            pi->fPng = TRUE;
    }

    // default big size
    if (cy < 768)
        ul = (cy * pi->icoSize) / 480;
    else
        ul = (cy * pi->icoSize) / 768;
    pi->bigDflt = (ul + 4) & ~7;
    
    // current big size - if it's anything other than
    // the default +/- 8, set it to the default size
    pi->bigOrg = bigInit;
    pwpclsQueryResolution(pWPPngFolder, &ul, &cy);
    pi->bigActual = cy;

    if (cy == pi->bigDflt - 8 || cy == pi->bigDflt + 8)
        pi->bigCur = cy;
    else
        pi->bigCur = pi->bigDflt;

    return;
}

/*****************************************************************************/

// init the dlg and its controls

void        InitDlgItems(HWND hwnd, ICONSIZE *pi)
{
    ULONG   ul;
    ULONG  *pul;
    HWND    hCtl;
    char    buf[128];

    // get colors
    pi->clr3DDark = WinQuerySysColor(HWND_DESKTOP, SYSCLR_BUTTONDARK, 0);
    pi->clr3DLight = WinQuerySysColor(HWND_DESKTOP, SYSCLR_BUTTONLIGHT, 0);
    pi->clrItem = pi->clrSelItem = WinQuerySysColor(HWND_DESKTOP,
                                                    SYSCLR_DIALOGBACKGROUND, 0);
    gpihManipulateRGB(&pi->clrItem, .92);
    gpihManipulateRGB(&pi->clrSelItem, 1.06);

    // set fonts
    if ((ul = PrfQueryProfileString(HINI_USER, "PM_SystemFonts", "WindowText",
                               0, buf, sizeof(buf))) != 0)
        WinSetPresParam(hwnd, PP_FONTNAMESIZE, ul, buf);

    if ((ul = PrfQueryProfileString(HINI_USER, "PM_SystemFonts", "DefaultFont",
                               0, buf, sizeof(buf))) != 0) {
        WinSetPresParam(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_FGRP), PP_FONTNAMESIZE, ul, buf);
        WinSetPresParam(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_XGRP), PP_FONTNAMESIZE, ul, buf);
    }

    // ensure the "Normal" label gets resized when its text gets changed
    WinSetWindowBits(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_ITXT), QWL_STYLE,
                     SS_AUTOSIZE, SS_AUTOSIZE);

    // set NLS text
    pul = nlsIDs;
    for (pul; *pul; pul++)
        WinSetDlgItemText(hwnd, *pul, cmnGetString(*pul));

    // create ad hoc "accelerator table"
    for (ul = 2; ul < ACCEL_CNT; ul++)
    {
        char *ptr = strchr(cmnGetString(accelTxt[ul]), '~');
        if (!ptr)
            accelChars[ul] = 0;
        else
            accelChars[ul] = (char)WinUpperChar(0, 0, 0, (ULONG)ptr[1]);
    }

    // set "Custom icons" chkbox
    WinSendDlgItemMsg(hwnd, ID_XSDI_ICOSIZE_PNG, BM_SETCHECK, (MP)pi->fPng, 0);

    // set up slider: make shaft and arm smaller
    hCtl = WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_SLD);
    ul = (ULONG)WinSendMsg(hCtl, SLM_QUERYSLIDERINFO, (MP)SMA_SHAFTDIMENSIONS, 0);
    WinSendMsg(hCtl, SLM_SETSLIDERINFO, (MP)SMA_SHAFTDIMENSIONS, (MP)(HIUSHORT(ul) / 2));
    ul = (ULONG)WinSendMsg(hCtl, SLM_QUERYSLIDERINFO, (MP)SMA_SLIDERARMDIMENSIONS, 0);
    pi->armSize = MPFROM2SHORT((LOUSHORT(ul) / 2), (HIUSHORT(ul) * 3 / 5));
    WinSendMsg(hCtl, SLM_SETSLIDERINFO, (MP)SMA_SLIDERARMDIMENSIONS, pi->armSize);

    // set the slider text
    WinSendMsg(hCtl, SLM_SETSCALETEXT, (MP)0, (MP)"-");
    WinSendMsg(hCtl, SLM_SETSCALETEXT, (MP)1, (MP)_ltoa(pi->bigDflt, buf, 10));
    WinSendMsg(hCtl, SLM_SETSCALETEXT, (MP)2, (MP)"+");

    return;
}

/*****************************************************************************/

// After the normal and big icon windows are resized & repositioned,
// all remaining controls are centered under one or the other.

void        InitLayout(HWND hwnd, ICONSIZE *pi)
{
    ULONG   ctr;
    LONG    cxTxt;
    LONG    cyDelta;
    LONG    bigMax = (pi->bigDflt + 8) + 8; // expand by 8 pixels to accommodate
    LONG    icoMax = (pi->icoSize + 8) + 8; // a 4 pixel margin on each side
    POINTL  ptl = {4, 2};
    SWP    *ps;
    SWP     swp[11];

#define psIco   (&swp[0])
#define psBig   (&swp[1])

    // convert dlg units into pixels
    WinMapDlgPoints(hwnd, &ptl, 1, TRUE);

    // normal icon 
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_ICO), psIco);
    psIco->x += (3 * (psIco->cx - bigMax)) / 4;
    psIco->cx = bigMax;
    psIco->cy = bigMax;

    // big icon
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_BIG), psBig);
    psBig->x += (psBig->cx - bigMax) / 4;
    psBig->cx = bigMax;
    psBig->cy = bigMax;

    // calc width of text fields so they almost-but-not-quite overlap
    cxTxt = bigMax + psBig->x - psIco->x - psIco->cx - ptl.x;

    ctr = 2;

    // Custom icons checkbox
    ps = &swp[ctr++];
    WinSendMsg(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_PNG), BM_AUTOSIZE, 0, 0);
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_PNG), ps);
    ps->x = psIco->x + (bigMax - ps->cx) / 2;

    // size slider
    ps = &swp[ctr++];
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_SLD), ps);
    ps->cx = bigMax + 12;
    ps->x = psBig->x + (bigMax - ps->cx) / 2;

    // Ico text ("Normal")
    ps = &swp[ctr++];
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_ITXT), ps);
    ps->cx = cxTxt;
    ps->x = psIco->x + (bigMax - ps->cx) / 2;
    ps->y = psIco->y + bigMax + ptl.y / 2;

    // Big text ("Dynamic")
    ps = &swp[ctr++];
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_BTXT), ps);
    ps->cx = cxTxt;
    ps->x = psBig->x + (bigMax - ps->cx) / 2;
    ps->y = psBig->y + bigMax + ptl.y / 2;

    // Folder groupbox - adjust height to be 8 dlg units above the text
    ps = &swp[ctr++];
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_FGRP), ps);
    cyDelta = ps->cy;
    ps->cy = (ps[-1].y + ps[-1].cy) + (ptl.y * 4) - ps->y;
    cyDelta -= ps->cy;

    // Mini icon
    ps = &swp[ctr++];
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_MINI), ps);
    ps->x = psIco->x + (bigMax - icoMax) / 2;
    ps->cx = icoMax;
    ps->y += ps->cy - icoMax;
    ps->cy = icoMax;

    // Full size icon
    ps = &swp[ctr++];
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_FULL), ps);
    ps->x = psBig->x + (bigMax - icoMax) / 2;
    ps->cx = icoMax;
    ps->y += ps->cy - icoMax;
    ps->cy = icoMax;

    // Mini text ("Small")
    ps = &swp[ctr++];
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_MTXT), ps);
    ps->cx = cxTxt;
    ps->x = psIco->x + (bigMax - ps->cx) / 2;

    // Full text ("Large")
    ps = &swp[ctr++];
    WinQueryWindowPos(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_FTXT), ps);
    ps->cx = cxTxt;
    ps->x = psBig->x + (bigMax - ps->cx) / 2;

    WinSetMultWindowPos(WinQueryAnchorBlock(hwnd), swp, 11);

    return;
}

/*****************************************************************************/

// fetch WPFolder's default icon for use on 3 of the buttons,
// then get its iconinfo so 'ecomedia' can convert it to an HBITMAP2

BOOL        InitIco(ICONSIZE *pi)
{
    BOOL        fRtn = FALSE;
    ULONG       ul;
    ULONG       cx;
    ULONG       cy;
    ICONINFO   *pii = 0;

do {
    // get the handle of WPFolder's icon
    if (!(pi->icoIcon = _wpclsQueryIcon(pWPPngFolder))) {
        fprintf(stderr, "InitIco: _wpclsQueryIcon\n");
        break;
    }

    // get iconinfo for WPFolder's icon
    if (!(ul = _wpclsQueryIconData(pWPPngFolder, 0))) {
        fprintf(stderr, "InitIco: _wpclsQueryIconData for size\n");
        break;
    }
    if (!(pii = calloc(1, ul))) {
        fprintf(stderr, "InitIco: calloc\n");
        break;
    }
    if (!(ul = _wpclsQueryIconData(pWPPngFolder, pii))) {
        fprintf(stderr, "InitIco: _wpclsQueryIconData for size\n");
        break;
    }

    // call ecomedia to covert the iconinfo to an HBITMAP2 struct
    if (!(pi->icoBmp = pGpi2ParseIconInfo(pii, &cx, &cy, 8, 0, 0, 0, RETAIN_ALPHA))) {
        fprintf(stderr, "InitIco: Gpi2ParseIconInfo\n");
        break;
    }

    fRtn = TRUE;

} while (fFalse);

    if (pii)
        free(pii);

    return fRtn;
}

/*****************************************************************************/

// try to get a custom PNG folder icon from various sources:
// an HBITMAP2 from the class, a PNG file from the BIGICONS directory,
// and finally, the fallback PNG stored as a resource

BOOL        InitPng(ICONSIZE *pi)
{
    BOOL    fRtn = FALSE;
    ULONG   ul;
    ULONG   cb;
    ULONG   cx;
    ULONG   cy;
    HFILE   hf = 0;
    char   *ptr;
    char   *pBuf;
    FILESTATUS3 fs3;
    char    path[CCHMAXPATH] = "";

do {
    // if we can get the bmp from PngD, we're done here
    pi->pngBmp = pwpclsQueryBigIconN(pWPPngFolder, &cx, &cy, FLAG_NOSYSICON);
    if (pi->pngBmp) {
        fprintf(stderr, "got bmp from PngD - pngBmp= %x\n", pi->pngBmp);
        return TRUE;
    }

    // if not, confirm that custom icons is functional
    // by locating PngD's 'bigicons.txt' file

    // use the BIGICONSPATH env variable if present
    if (!DosScanEnv("BIGICONSPATH", &ptr) ||
        !DosScanEnv("BIGICONS", &ptr)) {
        strcpy(path, ptr);
        ptr = strchr(path, 0);
        if (ptr[-1] == '\\')
            ptr--;
        strcpy(ptr, "\\bigicons.txt");
    }

    // if the custom path is missing or defective, try the default path;
    if (!path[0] ||
        DosQueryPathInfo(path, FIL_STANDARD, &fs3, sizeof(fs3))) {

        DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &ul, sizeof(ul));
        path[0] = (char)(ul + 0x40);
        strcpy(&path[1], ":\\sys\\icons\\BIGICONS\\bigicons.txt");

        if (DosQueryPathInfo(path, FIL_STANDARD, &fs3, sizeof(fs3))) {
            fprintf(stderr, "InitPng: Unable to locate 'bigicons' folder\n");
            break;
        }
    }

    // now that we have the right folder, look for the folder png
    ptr = strrchr(path, '\\');
    strcpy(ptr, "\\folder.png");
    if (DosOpen(path, &hf, &ul, 0, 0,
                OPEN_ACTION_FAIL_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS,
                OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY, 0)) {

        strcpy(ptr, "\\folder_closed.png");
        if (DosOpen(path, &hf, &ul, 0, 0,
                    OPEN_ACTION_FAIL_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS,
                    OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY, 0)) {

            strcpy(ptr, "\\folder-closed.png");
            if (DosOpen(path, &hf, &ul, 0, 0,
                        OPEN_ACTION_FAIL_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS,
                        OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY, 0))
                hf = 0;
        }
    }

    // if an installed 'folder*.png' can't be found,
    // fall back to an embedded PNG file
    if (!hf) {
        if (DosGetResource(cmnQueryMainResModuleHandle(), RT_PNG,
                           ID_XSDI_ICOSIZE_PNGICON, (void**)&pBuf)) {
            fprintf(stderr, "InitPng: DosGetResource failed\n");
            break;
        }

        if (!(pi->pngBmp = pGpi2ReadPng(pBuf, 0, RETAIN_ALPHA)))
            fprintf(stderr, "InitPng: Gpi2ReadPng from resource failed\n");
        else
            fRtn = TRUE;

        DosFreeResource(pBuf);
        break;
    }

    // get the file size & alloc a buffer
    if (DosSetFilePtr(hf, 0, FILE_END, &cb) ||
        DosSetFilePtr(hf, 0, FILE_BEGIN, &ul)) {
        fprintf(stderr, "InitPng: DosSetFilePtr failed\n");
        break;
    }

    if (!(pBuf = calloc(1, cb))) {
        fprintf(stderr, "InitPng: calloc failed\n");
        break;
    }

    // read the file
    if (DosRead(hf, pBuf, cb, &cb))
        fprintf(stderr, "InitPng: DosRead failed\n");
    else {
        pi->pngBmp = pGpi2ReadPng(pBuf, 0, RETAIN_ALPHA);
        if (!pi->pngBmp)
            fprintf(stderr, "InitPng: Gpi2ReadPng from file failed\n");
        else
            fRtn = TRUE;
    }

    free(pBuf);

} while (fFalse);

    if (hf)
        DosClose(hf);

    return fRtn;
}

/*****************************************************************************/
/*****************************************************************************/
// our XWP "window procedure"

BOOL XWPENTRY dtpIconSizeMsgProc(PNOTEBOOKPAGE pnbp, ULONG msg,
                                 MPARAM mp1, MPARAM mp2, MRESULT *pmrc)
{
    ICONSIZE   *pi = (ICONSIZE*)pnbp->pUser;

    *pmrc = 0;

    switch (msg)
    {
        case WM_COMMAND:
            switch (SHORT1FROMMP(mp1))
            {
                // normal or dynamic buttons selected
                case ID_XSDI_ICOSIZE_ICO:
                case ID_XSDI_ICOSIZE_BIG:
                    pi->fBig = (mp1 == (MP)ID_XSDI_ICOSIZE_BIG);
                    fUpdate = TRUE;
                    WinInvalidateRect(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_ICO), 0, FALSE);
                    WinInvalidateRect(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_BIG), 0, FALSE);
                    UpdateDisplay(pi, 0);
                    SetBtnsAndMsg(pi->hDlg, pi); // this depends on UpdateDisplay()
                    break;

                // small or large buttons selected
                case ID_XSDI_ICOSIZE_MINI:
                case ID_XSDI_ICOSIZE_FULL:
                    pi->fFull = (mp1 == (MP)ID_XSDI_ICOSIZE_FULL);
                    WinInvalidateRect(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_MINI), 0, FALSE);
                    WinInvalidateRect(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_FULL), 0, FALSE);
                    _xwpclsSetIconSize(_XCenter, pi->fFull ? 1 : 0);
                    SetBtnsAndMsg(pi->hDlg, pi);
                    break;

                // minus or plus keys pressed - move the slider position
                case ID_XSDI_ICOSIZE_DN:
                case ID_XSDI_ICOSIZE_UP:
                {
                    USHORT  us;

                    us = (SHORT)WinSendMsg(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_SLD),
                                           SLM_QUERYSLIDERINFO,
                                           MPFROM2SHORT(SMA_SLIDERARMPOSITION,
                                           SMA_INCREMENTVALUE), 0);
                    us += (mp1 == (MP)ID_XSDI_ICOSIZE_DN) ? -1 : 1;
                    if (us <= 2)
                        WinSendMsg(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_SLD),
                                   SLM_SETSLIDERINFO,
                                   MPFROM2SHORT(SMA_SLIDERARMPOSITION,
                                   SMA_INCREMENTVALUE), (MP)us);
                    break;
                }

                case ID_XSDI_ICOSIZE_UNDO:
                case ID_XSDI_ICOSIZE_DFLT:
                    ResetAll(pi->hDlg, pi, SHORT1FROMMP(mp1) == ID_XSDI_ICOSIZE_UNDO);
                    break;

                case DID_HELP:
                    return FALSE;

            }
            return TRUE;

        case WM_CHAR:
            // this is a substitute for an accelerator table
            if ((SHORT1FROMMP(mp1) & (KC_CHAR | KC_KEYUP)) == KC_CHAR) {
                ULONG   ctr;
                ULONG   ulChar = WinUpperChar(0, 0, 0, (ULONG)SHORT1FROMMP(mp2));

                for (ctr = 0; ctr < ACCEL_CNT; ctr++)
                {
                    if (accelChars[ctr] == (char)ulChar) {
                        WinPostMsg(pi->hDlg, WM_COMMAND, (MP)accelCmds[ctr], 0);
                        *pmrc = (MRESULT)TRUE;
                        return TRUE;
                    }
                }
            }

            // we didn't handle this char, so pass it on
            break;

        case WM_CONTROL:
            switch ((ULONG)mp1)
            {
                // "use custom icons" toggled - display appropriate
                // big icon and show the restart msg if appropriate
                case MAKEULONG(ID_XSDI_ICOSIZE_PNG, BN_CLICKED):
                {
                    ULONG   val;

                    pi->fPng = (BOOL)WinSendMsg((HWND)mp2, BM_QUERYCHECK, 0, 0);
                    if (pwpclsSetCustomIcons)
                        pwpclsSetCustomIcons(pWPPngFolder, pi->fPng);
                    else {
                        val = !pi->fPng;
                        PrfWriteProfileData(HINI_USERPROFILE, "DYNAMIC_ICONS",
                                            "UseSystemIcons",
                                            (val ? &val : 0), sizeof(val));
                    }

                    WinInvalidateRect(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_BIG), 0, 0);
                    SetBtnsAndMsg(pi->hDlg, pi);
                    break;
                }
          
                // the slider changed - update the bmps and redisplay
                case MAKEULONG(ID_XSDI_ICOSIZE_SLD, SLN_CHANGE):
                {
                    USHORT  us;
                    ULONG   ul;
          
                    // don't react - other code is doing an init of the slider
                    if (pi->fInit)
                        break;
          
                    us = (USHORT)WinSendMsg(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_SLD),
                            SLM_QUERYSLIDERINFO,
                            MPFROM2SHORT(SMA_SLIDERARMPOSITION, SMA_INCREMENTVALUE), 0);
          
                    pi->bigCur = pi->bigDflt + (us == 0 ? -8 : us == 2 ? +8 : 0);
                    CreateBitmaps(pi);
                    WinInvalidateRect(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_BIG), 0, FALSE);

                    // don't change anything if BigIcons is off
                    if (pi->fBig)
                        UpdateDisplay(pi, 0);
                    SetBtnsAndMsg(pi->hDlg, pi); // this depends on UpdateDisplay()
                    break;
                }
          
                case MAKEULONG(ID_XSDI_ICOSIZE_ICO, BN_PAINT):
                case MAKEULONG(ID_XSDI_ICOSIZE_BIG, BN_PAINT):
                case MAKEULONG(ID_XSDI_ICOSIZE_MINI, BN_PAINT):
                case MAKEULONG(ID_XSDI_ICOSIZE_FULL, BN_PAINT):
                    PaintBtns(pi, (USERBUTTON*)mp2, SHORT1FROMMP(mp1));
                    break;

            }
            return TRUE;

        case WM_DESTROY:
            if (pi->pngBig)
                pGpi2DeleteBitmap(pi->pngBig);
            if (pi->pngBmp)
                pGpi2DeleteBitmap(pi->pngBmp);
            if (pi->icoBig)
                pGpi2DeleteBitmap(pi->icoBig);
            if (pi->icoBmp)
                pGpi2DeleteBitmap(pi->icoBmp);
            return FALSE;
    }

    return FALSE;
}

/*****************************************************************************/

// This updates the class defaults for BigIcons on/off and for icon size
// if they've changed, then updates the Desktop regardless of change.

void        UpdateDisplay(ICONSIZE *pi, LONG size)
{
    ULONG       attrib;
    HWND        hDesk;
    WPDesktop  *pDesk = 0;
    CNRINFO     info;

    if (!size)
        size = pi->bigCur;

    // if BigIcons is on, set the default resolution (cx,cy) if it has changed
    if (pi->fBig && size != pi->bigActual) {
        pi->bigActual = size;
        pwpclsSetResolution(pWPPngFolder, size, size);
    }

    // if the class-default BigIcon state doesn't match fBig, update it
    attrib = pwpclsQueryDefFldrAttr(pWPPngFolder, OPEN_CONTENTS);
    if (((attrib & ATTRIB_BIGICONS) == ATTRIB_BIGICONS) != pi->fBig) {

        // when enabling, also remove incompatible attribs
        if (pi->fBig) {
            attrib |= ATTRIB_BIGICONS;
            attrib &= ~CV_MINI;
        }
        else
            attrib &= ~ATTRIB_BIGICONS;

        // set the class default Normal/BigIcons setting
        pwpclsSetDefFldrAttr(pWPPngFolder, attrib, OPEN_CONTENTS);
    }

    // update the Desktop
    if (!(pDesk = _wpclsQueryActiveDesktop(_WPDesktop)) ||
        !(hDesk = _wpclsQueryActiveDesktopHWND(_WPDesktop)) ||
        !(hDesk = WinWindowFromID(hDesk, FID_CLIENT))) {
        fprintf(stderr, "unable to get active Desktop - pDesk= %x  hDesk= %x\n",
                pDesk, hDesk);
        return;
    }

    // disable updates to avoid multiple repaints
    WinEnableWindowUpdate(hDesk, FALSE);

    // have the folder use the class-default cx,cy
    pwpSetResolutionDefault(pDesk, TRUE);

    // using the class attrib as-is would change the layout,
    // so just duplicate changes made to the class default
    attrib = pwpQueryFldrAttr(pDesk, OPEN_CONTENTS);
    if (pi->fBig) {
        attrib &= ~CV_MINI;
        attrib |= ATTRIB_BIGICONS;
    }
    else
        attrib &= ~ATTRIB_BIGICONS;

    // this should only be needed if we changed an attrib,
    // but the Desktop won't respond to a size change without it
    pwpSetFldrAttr(pDesk, attrib, OPEN_CONTENTS);

    // this guarantees an update by toggling an undefined attrib bit
    WinSendMsg(hDesk, CM_QUERYCNRINFO, (MP)&info, (MP)sizeof(info));
    info.flWindowAttr ^= 0x80000000;
    WinSendMsg(hDesk, CM_SETCNRINFO, (MP)&info, (MP)CMA_FLWINDOWATTR);

    // for plain-ole icon view (i.e. not CV_NAME or CV_GRID)
    // reposition each icon to accommodate any change in size
    if ((info.flWindowAttr & (CV_ICON | CV_GRID)) == CV_ICON)
        WinSendMsg(hDesk, CM_INVALIDATERECORD, 0, 0);

    // reenable updates
    WinShowWindow(hDesk, TRUE);

    return;
}

/*****************************************************************************/

// generate new ico and png bitmaps at the currently-selected size;

void        CreateBitmaps(ICONSIZE *pi)
{
    // clean up old icon bmp
    if (pi->icoBig) {
        pGpi2DeleteBitmap(pi->icoBig);
        pi->icoBig = 0;
    }

    // create a new icon bmp at the specified size
    pi->icoBig = pGpi2ScaleBitmap2(pi->icoBmp, pi->bigCur, pi->bigCur, TRUE);
    if (!pi->icoBig)
        fprintf(stderr, "CreateBitmaps: Gpi2ScaleBitmap2(ico) failed\n");

    // if PNG icons could be loaded
    if (pi->fPngOk) {

        // clean up old PNG bmp
        if (pi->pngBig) {
            pGpi2DeleteBitmap(pi->pngBig);
            pi->pngBig = 0;
        }

        // create a new PNG bmp at the specified size
        pi->pngBig = pGpi2ScaleBitmap2(pi->pngBmp, pi->bigCur, pi->bigCur, TRUE);
        if (!pi->pngBig)
            fprintf(stderr, "CreateBitmaps: Gpi2ScaleBitmap2(png) failed\n");
    }

    return;
}

/*****************************************************************************/

// react to the Undo and Default buttons

void        ResetAll(HWND hwnd, ICONSIZE *pi, BOOL fUndo)
{
    ULONG   ndx;
    ULONG   val;
    ULONG   bigNew;
    ULONG   bigCurSav = pi->bigCur;
    ULONG   fBigSav   = pi->fBig;
    BOOL    fPngSav = pi->fPng;

    if (fUndo) {
        pi->fBig  = pi->fBigOrg;
        pi->fFull = pi->fFullOrg;
        pi->fPng  = pi->fPngOrg;
        pi->bigCur = pi->bigOrg;
        fUpdate = FALSE;
    }
    else {
        pi->fBig  = pi->fBigDflt;
        pi->fFull = pi->fFullDflt;
        pi->fPng  = FALSE;
        pi->bigCur = pi->bigDflt;
    }

    // when undoing, bigOrg may be out of range;
    // apply that value to PngD but reset bigCur to the default
    bigNew = pi->bigCur;
    if (pi->bigCur == pi->bigDflt - 8)
        ndx = 0;
    else
    if (pi->bigCur == pi->bigDflt + 8)
        ndx = 2;
    else {
        ndx = 1;
        pi->bigCur = pi->bigDflt;
    }

    // update custom icons setting
    if (pi->fPng != fPngSav) {
        if (pwpclsSetCustomIcons)
            pwpclsSetCustomIcons(pWPPngFolder, pi->fPng);
        else {
            val = !pi->fPng;
            PrfWriteProfileData(HINI_USERPROFILE, "DYNAMIC_ICONS", "UseSystemIcons",
                                (val ? &val : 0), sizeof(val));
        }
    }

    // update the dialog after all settings are in place
    WinSendDlgItemMsg(hwnd, ID_XSDI_ICOSIZE_PNG, BM_SETCHECK, (MP)pi->fPng, 0);

    pi->fInit = TRUE;
    WinSendMsg(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_SLD), SLM_SETSLIDERINFO,
               MPFROM2SHORT(SMA_SLIDERARMPOSITION, SMA_INCREMENTVALUE), (MP)ndx);
    pi->fInit = FALSE;

    // if the size changed, we need new bitmaps
    if (bigCurSav != pi->bigCur)
        CreateBitmaps(pi);

    // this depends on CreateBitmaps()
    WinInvalidateRect(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_ICO), 0, FALSE);
    WinInvalidateRect(WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_BIG), 0, FALSE);

    // update folders
    UpdateDisplay(pi, bigNew);

    // update XCenter
    if (_xwpclsSetIconSize(_XCenter, pi->fFull ? 1 : 0)) {
        WinInvalidateRect(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_MINI), 0, FALSE);
        WinInvalidateRect(WinWindowFromID(pi->hDlg, ID_XSDI_ICOSIZE_FULL), 0, FALSE);
    }

    // this depends on UpdateDisplay()
    SetBtnsAndMsg(hwnd, pi);

    return;
}

/*****************************************************************************/

// set the Undo and Default buttons based on current settings;
// since these are notebook buttons, they're children of the dlg
// during init but children of the notebook thereafter

void        SetBtnsAndMsg(HWND hwnd, ICONSIZE *pi)
{
    BOOL    fFldr;
    BOOL    fXCtr;
    LONG    clr;
    ULONG   msgID;
    HWND    hCtl;
    char    buf[16];

    hCtl = WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_SLD);
    WinSendMsg(hCtl, SLM_SETSCALETEXT,
               (MP)1, (MP)_ltoa(pi->bigCur, buf, 10));
    WinSendMsg(hCtl, SLM_SETSLIDERINFO, (MP)SMA_SLIDERARMDIMENSIONS, pi->armSize);

    hCtl = WinWindowFromID(pi->hNB, ID_XSDI_ICOSIZE_UNDO);
    if (!hCtl)
        hCtl = WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_UNDO);

    fFldr = pi->fBig != pi->fBigOrg || pi->fPng != pi->fPngOrg ||
            (pi->fBig && pi->bigActual != pi->bigOrg);
    fXCtr = pi->fFull  != pi->fFullOrg;
    WinEnableWindow(hCtl, fXCtr || fFldr || fUpdate);

    hCtl = WinWindowFromID(pi->hNB, ID_XSDI_ICOSIZE_DFLT);
    if (!hCtl)
        hCtl = WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_DFLT);

    WinEnableWindow(hCtl,
            (pi->fBig != pi->fBigDflt || pi->fFull != pi->fFullDflt ||
             pi->fPng != FALSE || pi->bigCur != pi->bigDflt ||
             (pi->fBig && pi->bigActual != pi->bigDflt)));


    hCtl = WinWindowFromID(hwnd, ID_XSDI_ICOSIZE_MSGTXT);
    msgID = fFldr ? ID_XSDI_ICOSIZE_MSG2 :
            fUpdate ? ID_XSDI_ICOSIZE_MSG3 : ID_XSDI_ICOSIZE_MSGTXT;
    WinSetWindowText(hCtl, cmnGetString(msgID));
    clr = (fFldr || fUpdate) ? 0x00BF0000 : 0;
    WinSetPresParam(hCtl, PP_FOREGROUNDCOLOR, sizeof(clr), &clr);

    return;
}

/*****************************************************************************/

// Since buttons don't permit setting an ico/bmp at runtime, we have
// to paint them ourselves. Most of the work involves drawing the same
// 3D highlighting that a button would.

void        PaintBtns(ICONSIZE *pi, USERBUTTON *pu, USHORT id)
{
    BOOL    fSel;
    BOOL    mini = FALSE;
    LONG    siz;
    LONG    clrB;
    LONG    clrL;
    LONG    clrR;
    ULONG   state = pu->fsState;
    HWND    hwnd = pu->hwnd;
    HPS     hps = pu->hps;
    HBITMAP2 hbm = 0;
    RECTL   rcl;
    POINTL  aptl[3];

    // set up the current window's details 
    switch (id)
    {
        // folder Dynamic
        case ID_XSDI_ICOSIZE_BIG:
            hbm = (pi->fPng && pi->pngBig) ? pi->pngBig : pi->icoBig;
            siz = pi->bigCur;
            fSel = pi->fBig;
            break;

        // folder Normal
        case ID_XSDI_ICOSIZE_ICO:
            siz = pi->icoSize;
            fSel = !pi->fBig;
            break;

        // XCenter Small
        case ID_XSDI_ICOSIZE_MINI:
            mini = TRUE;
            siz = pi->icoSize / 2;
            fSel = !pi->fFull;
            break;

        // XCenter Large
        case ID_XSDI_ICOSIZE_FULL:
            siz = pi->icoSize;
            fSel = pi->fFull;
            break;

        default:
            fprintf(stderr, "BmpBtnProc: invalid ID= %d\n", id);
            return;
    }

    // set up colors depending on selection state;
    // if pressing this btn would change its selection
    // state, use the selection color to indicate that
    if (!fSel && (state & BDS_HILITED))
        fSel = TRUE;
    clrB = fSel ? pi->clrSelItem : pi->clrItem;
    clrL = (state & BDS_HILITED) ? pi->clr3DDark  : pi->clr3DLight;
    clrR = (state & BDS_HILITED) ? pi->clr3DLight : pi->clr3DDark;

    // switch to RGB & get the window's dimensions
    GpiCreateLogColorTable(hps, 0, LCOLF_RGB, 0, 0, 0);
    WinQueryWindowRect(hwnd, &rcl);

    // leave a 1px margin - the btn's wndproc will use it
    // to draw a focus rectangle after we're done painting
    rcl.xLeft++;
    rcl.yBottom++;
    rcl.xRight -= 2;
    rcl.yTop -= 2;

    // paint the background
    GpiMove(hps, (POINTL*)&rcl);
    GpiSetColor(hps, clrB);
    GpiBox(hps, DRO_FILL, (POINTL*)&rcl.xRight, 0, 0);

    // draw left & top in black
    GpiMove(hps, (POINTL*)&rcl);
    memcpy(&aptl[0], &rcl, sizeof(rcl));
    aptl[0].y = rcl.yTop;
    GpiSetColor(hps, 0);
    GpiPolyLine(hps, 2, aptl);

    // draw right & bottom in the "left" color -
    // this avoids having 3 lines of white when the btn is pushed
    GpiMove(hps, &aptl[1]);
    aptl[0].x = rcl.xRight;
    aptl[0].y = rcl.yBottom;
    aptl[1].x = rcl.xLeft + 1;
    aptl[1].y = rcl.yBottom;
    GpiSetColor(hps, clrL);
    GpiPolyLine(hps, 2, aptl);

    // make the inner rectls 2px wide
    GpiSetLineWidth(hps, 0x20000);

    // draw left & top in the "left" color
    aptl[1].x++;
    GpiMove(hps, &aptl[1]);
    aptl[0].x = rcl.xLeft + 2;
    aptl[0].y = rcl.yTop - 1;
    aptl[1].x = rcl.xRight - 2;
    aptl[1].y = rcl.yTop - 1;
    GpiSetColor(hps, clrL);
    GpiPolyLine(hps, 2, aptl);

    // draw right & bottom in the "right" color -
    aptl[1].x++;
    GpiMove(hps, &aptl[1]);
    aptl[0].x = rcl.xRight - 1;
    aptl[0].y = rcl.yBottom + 2;
    aptl[1].x = rcl.xLeft + 2;
    aptl[1].y = rcl.yBottom + 2;
    GpiSetColor(hps, clrR);
    GpiPolyLine(hps, 2, aptl);

    // center the bmp in the window
    aptl[0].x = (rcl.xRight + 2 - siz) / 2;
    aptl[0].y = (rcl.yTop + 2 - siz) / 2;

    // if the btn is pressed, shift the image 2px rightward & downward
    if (state & BDS_HILITED) {
        aptl[0].x += 2;
        aptl[0].y -= 2;
    }

    // draw the png
    if (hbm) {
        // set the target's top-right corner & the source's origin
        aptl[1].x = aptl[0].x + siz;
        aptl[1].y = aptl[0].y + siz;
        aptl[2].x = 0;
        aptl[2].y = 0;

        // if the image is at max size, it will overpaint the
        // highlight, so clip it by moving the source's origin
        // up 2px and reducing the target rectl's width by 2px
        if ((state & BDS_HILITED) && siz == pi->bigDflt + 8) {
            aptl[0].y += 2;
            aptl[1].x -= 2;
            aptl[2].y += 2;
        }

        if (!pGpi2DrawBitmap(hps, hbm, 3, aptl))
            fprintf(stderr, "Gpi2DrawBitmap failed for ID= %d\n", id);
    }
    else {
        // draw the icon
        if (!WinDrawPointer(hps, aptl[0].x, aptl[0].y, pi->icoIcon,
                            mini ? DP_MINI : 0))
            fprintf(stderr, "WinDrawPointer failed for ID= %d\n", id);
    }

    return;
}

/*****************************************************************************/
/*****************************************************************************/

// To minimize stack growth during recursion, this creates a structure that
// contains the scratch space that the recursive function would normally
// allocate from the stack. Consequently, each recursion requires 16 bytes
// of stack space rather than ~580 bytes

VOID XWPENTRY dtpUpdateIconSize(void)
{
    BOOL    fBig;
    BOOL    fPng;
    ULONG   big;
    ULONG   val;
    ULONG   ul;
    RESETICONS   r;

    if (!pWPPngFolder)
        return;

    fBig = (pwpclsQueryDefFldrAttr(pWPPngFolder, OPEN_CONTENTS)
                                   & ATTRIB_BIGICONS) == ATTRIB_BIGICONS;

    pwpclsQueryResolution(pWPPngFolder, &ul, &big);

    if (pwpclsQueryCustomIcons)
        fPng = pwpclsQueryCustomIcons(pWPPngFolder, 0);
    else {
        fPng = (PrfQueryProfileData(HINI_USERPROFILE, "DYNAMIC_ICONS",
                                    "UseSystemIcons", &val, &ul) &&
                ((ul == 4 && val == 1) || (ul == 2 && val == 0x31))) ? FALSE : TRUE;
    }

    if (!fUpdate && fBig == fBigInit && big == bigInit && fPng == fPngInit)
        return;

    fprintf(stderr, "dtpUpdateIconSize: updating\n");

    // start out with a clean structure
    memset(&r, 0, sizeof(RESETICONS));
    r.fBig = fBig;

    // get the active Desktop object
    r.pobj = _wpclsQueryActiveDesktop(_WPDesktop);
    if (!r.pobj) {
        fprintf(stderr, "dtpUpdateIconSize: wpclsQueryActiveDesktop() failed\n");
        return;
    }

    // put the Desktop's path into the reset structure
    if (!_wpQueryFilename(r.pobj, r.path, TRUE)) {
        fprintf(stderr, "dtpUpdateIconSize: wpQueryFilename() for Desktop failed\n");
        return;
    }

    // recursively reset the icons for the Desktop and all its sub-folders
    ResetFolderIcons(&r);

    // iteratively reset the icons in the root directory of each drive
    ResetDriveIcons(r.fBig);

    return;
}

/*****************************************************************************/

// On entry, pr->path contains the f/q path of the folder to reset.
// The function saves a ptr to the null at the end of the string,
// then appends search criteria and subdirectory names to it as needed.
// Just before exit, it restores the null, returning the string to
// the value it had on entry.

void        ResetFolderIcons(RESETICONS *pr)
{
    HDIR    hdir;
    char   *ptr;

    // reset icons for the current folder
    pr->pobj = _wpclsQueryObjectFromPath(_WPFileSystem, pr->path);
    if (!pr->pobj)
        fprintf(stderr, "ResetFolderFonts: wpclsQueryObjectFromPath(%s) failed",
               pr->path);
    else {
        // have the folder use the class-default cx,cy
        pwpSetResolutionDefault(pr->pobj, TRUE);

        // using the class attrib as-is would change the layout,
        // so just duplicate changes made to the class default
        pr->attrib = pwpQueryFldrAttr(pr->pobj, OPEN_CONTENTS);
        if (pr->fBig) {
            pr->attrib &= ~CV_MINI;
            pr->attrib |= ATTRIB_BIGICONS;
        }
        else
            pr->attrib &= ~ATTRIB_BIGICONS;

        // this should only be needed if we changed an attrib,
        // but the Desktop won't respond to a size change without it
        pwpSetFldrAttr(pr->pobj, pr->attrib, OPEN_CONTENTS);

        _wpSaveDeferred(pr->pobj);
    }

    // set up to find sub-folders
    ptr = strchr(pr->path, 0);
    strcpy(ptr, "\\*");
    hdir = HDIR_CREATE;

    // look for sub-folders, one at a time
    pr->cnt = 1;
    pr->rc = DosFindFirst(pr->path, &hdir, MUST_HAVE_DIRECTORY | FILE_DIRECTORY,
                          &pr->ff, sizeof(FILEFINDBUF3), &pr->cnt, FIL_STANDARD);

    while (!pr->rc) {
        if (strcmp(pr->ff.achName, ".") &&
            strcmp(pr->ff.achName, "..")) {

            // append the found folder to the current folder's path
            strcpy(&ptr[1], pr->ff.achName);

            // reset found folder's fonts
            ResetFolderIcons(pr);
        }

        // look for the next sub-folder
        pr->cnt = 1;
        pr->rc = DosFindNext(hdir, &pr->ff, sizeof(FILEFINDBUF3), &pr->cnt);
    }

    if (hdir != HDIR_CREATE)
        DosFindClose(hdir);

    // restore the null
    *ptr = 0;

    return;
}

/*****************************************************************************/

// Reset fonts for the root directory of each drive present

void        ResetDriveIcons(BOOL fBig)
{
    ULONG   map;
    ULONG   mask;
    ULONG   ctr;
    ULONG   attrib;
    WPFileSystem   *pobj;
    char    drv[4] = "@:\\";

    DosQueryCurrentDisk(&ctr, &map);

    for (ctr = 1, mask = 1; ctr < 26; ctr++, mask <<= 1)
    {
        if (!(mask & map))
            continue;

        // attempts to get an obj ptr for inaccessible drives such as
        // empty diskette or optical drives will fail, so ignore errors
        drv[0] = (char)(ctr + 0x40);
        pobj = _wpclsQueryObjectFromPath(_WPFileSystem, drv);
        if (pobj) {
            // have the folder use the class-default cx,cy
            pwpSetResolutionDefault(pobj, TRUE);

            // using the class attrib as-is would change the layout,
            // so just duplicate changes made to the class default
            attrib = pwpQueryFldrAttr(pobj, OPEN_CONTENTS);
            if (fBig) {
                attrib &= ~CV_MINI;
                attrib |= ATTRIB_BIGICONS;
            }
            else
                attrib &= ~ATTRIB_BIGICONS;

            // this should only be needed if we changed an attrib,
            // but the Desktop won't respond to a size change without it
            pwpSetFldrAttr(pobj, attrib, OPEN_CONTENTS);

            _wpSaveDeferred(pobj);
        }
    }

    return;
}

/*****************************************************************************/

