/*
 * treesize.c:
 *      this is the main (and only) .C file for treesize.exe.
 *
 *      Treesize is new with XFoler V0.81.
 *
 *      This thing is multi-threaded. The second (Collect) thread
 *      (fntCollect) is started by WM_INITDLG of the main window (fnwpMain)
 *      and keeps sending messages to the main window which will then
 *      insert the directory records into the container.
 *
 *      This is mostly a quick hack done it one day and a half.
 *      There might be quite many bugs in this, besides EA collection is
 *      really slow.
 *
 *      Still, it might be interesting if you'd like to know more about
 *      the following:
 *          --  multithreading PM programs and inter-thread communication
 *          --  using a dlg template as a main window which can be resized,
 *              maximized and minimized
 *          --  container programming: tree views, context menus, drag-n-drop.
 *
 *      Copyright (C) 1997-98 Ulrich M”ller.
 *      This program 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 XFolder 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.
 */

#define  INCL_WIN
#define  INCL_WINWORKPLACE
#define  INCL_DOS
#define  INCL_DOSERRORS
#include <os2.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "treesize.h"
// #define _PMPRINTF_       // uncomment to have debugging
#include "..\helpers\pmprintf.h"
#include "..\helpers\eas.h"
#include "..\helpers\dosh.h"
#include "..\helpers\stringh.h"

/*
 * Global variables
 *
 */

// the directory to start with
CHAR        szRootDir[CCHMAXPATH];
ULONG       cbRootDir = sizeof(szRootDir)-3;

// main wnd handle
HWND        hwndMain = NULLHANDLE;

// Collect thread ID: always 0 if not running
TID         tidCollect = 0;
HAB         habCollect;
HMQ         hmqCollect;
// setting this to TRUE will stop the Collect thread
BOOL        fStopThread = FALSE;

// DIRINFO struct for the directory to start with
PDIRINFO    pdiRoot;

// and the settings read from/ stored to OS2.INI
struct {
    ULONG       ulSort;
    BOOL        CollectEAs;
    ULONG       ulSizeDisplay;
    BOOL        LowPriority;
} Settings;

/********************************************************************
 *                                                                  *
 *   Collect thread                                                 *
 *                                                                  *
 ********************************************************************/

/*
 * CollectDirectory:
 *      initially called by the Collect thread (fntCollect)
 *      and then calls itself recursively for each
 *      directory it finds.
 *      Takes a DIRINFO structure, which is also initially
 *      created by the Collect thread.
 *      This sends two messages to the main window (fnwpMain):
 *      WM_BEGINDIRECTORY, which causes the record core
 *      to be created, and WM_ENDDIRECTORY, which causes
 *      the record core to be updated with the directory size,
 *      when processing of this directory (and all the subdirs)
 *      is done with.
 */

VOID CollectDirectory(PDIRINFO pdiThis)
{
    HDIR          hdirFindHandle = HDIR_CREATE;
    FILEFINDBUF3  ffb3     = {0};      /* Returned from FindFirst/Next */
    ULONG         cbFFB3 = sizeof(FILEFINDBUF3);
    ULONG         ulFindCount    = 1;        /* Look for 1 file at a time    */
    APIRET        rc             = NO_ERROR; /* Return code                  */
    CHAR          szCurrentDir[CCHMAXPATH],
                  szSearchMask[CCHMAXPATH];

    _Pmpf(("Entering CollectDirectory"));
    _Pmpf(("  for: %s", pdiThis->szFullPath));

    // add a "\" to the directory, if we're not in root
    strcpy(szCurrentDir, pdiThis->szFullPath);
    if (szCurrentDir[strlen(szCurrentDir)-1] != '\\')
        strcat(szCurrentDir, "\\");

    // have record core inserted; since we're SENDing this
    // msg (and not posting), this also takes care of
    // thread synchronization so that the record cores don't
    // get messed up. Sending messages across threads blocks
    // the calling thread until the msg has been processed.
    // Ah yes, and this is why we may only send this if our
    // thread hasn't been requested to terminate. In that case,
    // the main window might just be being destroyed, so this
    // would block PM. Sigh.
    if (!fStopThread)
        WinSendMsg(hwndMain, WM_BEGINDIRECTORY, (MPARAM)pdiThis, NULL);

    // now go for the first directory entry in our directory (szCurrentDir):
    strcpy(szSearchMask, szCurrentDir);
    strcat(szSearchMask, "*");
    rc = DosFindFirst( szSearchMask,
                       &hdirFindHandle,
                       // find eeeeverything
                       FILE_ARCHIVED | FILE_HIDDEN | FILE_SYSTEM | FILE_READONLY | FILE_DIRECTORY,
                       &ffb3,
                       cbFFB3,
                       &ulFindCount,
                       FIL_STANDARD);

    // and start looping
    while (     (rc == NO_ERROR)
            &&  (!fStopThread)      // if our thread hasn't been stopped
          )
    {
        CHAR szFile[CCHMAXPATH];
        ULONG ulEASize;

        sprintf(szFile, "%s%s", szCurrentDir, // always has trailing "\"
                                ffb3.achName);

        // get EA size
        if (    (Settings.CollectEAs)
             && (strlen(szCurrentDir) > 3)
           )
        {
            ulEASize = eaPathQueryTotalSize(szFile);
            _Pmpf(("EAs for %s: %d", ffb3.achName, ulEASize));
            pdiThis->dTotalEASize += ulEASize;
        }

        if (ffb3.attrFile & FILE_DIRECTORY) {
            if (    (strcmp(ffb3.achName, ".") != 0)
                 && (strcmp(ffb3.achName, "..") != 0)
               )
            {
                // subdirectory found: recurse!

                // create new DIRINFO for the recursive call
                PDIRINFO pdiRecurse = malloc(sizeof(DIRINFO));
                pdiRecurse->dTotalSize = 0;
                pdiRecurse->dTotalEASize = 0;
                pdiRecurse->pParent = pdiThis;
                strcpy(pdiRecurse->szThis, ffb3.achName);
                strcpy(pdiRecurse->szFullPath, szFile);
                pdiRecurse->ulRecursionLevel = pdiThis->ulRecursionLevel + 1;
                // and go!
                CollectDirectory(pdiRecurse);
                // add the size of the subdirector(ies) to
                // our own size
                pdiThis->dTotalSize += pdiRecurse->dTotalSize;
                pdiThis->dTotalEASize += pdiRecurse->dTotalEASize;
            }
        } else {
            // regular file: add to total size
            pdiThis->dTotalSize += ffb3.cbFile;
        }

        rc = DosFindNext(hdirFindHandle,
                        &ffb3,
                        cbFFB3,
                        &ulFindCount);

    } /* endwhile */

    DosFindClose(hdirFindHandle);

    // now that we're done with this directory:
    // have record core updated with new total size
    if (!fStopThread)
        WinSendMsg(hwndMain, WM_DONEDIRECTORY, (MPARAM)pdiThis, NULL);

    // end of function: if this was a recursive call,
    // this will continue collecting files in the parent
    // directory. If this was the "root" call from fntCollect,
    // the Collect thread will then terminate.
};

/*
 * fntCollect:
 *      Collect thread, started by fnwpMain below.
 *      The main window can always determine whether
 *      the Collect thread is running by checking
 *      whether (tidCollect != 0). There is never
 *      more than one "instance" of this thread running.
 */

void _System fntCollect(ULONG ulDummy)
{
    // we need a msg queue for WinSendMsg above
    if (!(habCollect = WinInitialize(0)))
        return;
    if (!(hmqCollect = WinCreateMsgQueue(habCollect, 0)))
        return;

    // low priority?
    DosSetPriority(PRTYS_THREAD,
        (Settings.LowPriority) ? PRTYC_IDLETIME : PRTYC_REGULAR,
        0, 0);

    // prepare "root" DIRINFO structure for CollectDirectory()
    pdiRoot = (PDIRINFO)malloc(sizeof(DIRINFO));
    if (pdiRoot) {
        pdiRoot->pParent = NULL;
        pdiRoot->dTotalSize = 0;
        pdiRoot->dTotalEASize = 0;
        strcpy(pdiRoot->szThis, szRootDir);
        strcpy(pdiRoot->szFullPath, szRootDir);
        pdiRoot->ulRecursionLevel = 1;
        CollectDirectory(pdiRoot);
    }

    // report that we're done completely
    if (!fStopThread)
        WinPostMsg(hwndMain, WM_DONEWITHALL, NULL, NULL);

    WinDestroyMsgQueue(hmqCollect);
    WinTerminate(habCollect);
    tidCollect = 0;
}

/********************************************************************
 *                                                                  *
 *   Main thread                                                    *
 *                                                                  *
 ********************************************************************/

/*
 * Cleanup:
 *      deallocates all memory and cleans up record cores.
 *      Also recurses; for initial call, preccParent
 *      must be NULL.
 */

VOID Cleanup(HWND hwndCnr, PSIZERECORDCORE preccParent)
{
    PSIZERECORDCORE precc2 = preccParent;

    do {
        precc2 =
            (PSIZERECORDCORE)WinSendMsg(hwndCnr,
                    CM_QUERYRECORD,
                    // the following ugly code does the following:
                    // if this is the "root" call: get first child recc.
                    // For recursive calls: get next child recc.
                    (MPARAM)((preccParent) // "root" call?
                                ? preccParent
                                : precc2), // don't care
                    MPFROM2SHORT(
                            ((preccParent) // "root" call?
                                ? CMA_FIRSTCHILD
                                : CMA_FIRST
                        ),
                        CMA_ITEMORDER)
                    );
        if ((precc2) && ((ULONG)precc2 != -1))
        {
            Cleanup(hwndCnr, precc2);
            _Pmpf(("Removing %s", precc2->pdi->szRecordText));
            if (precc2->pdi)
                free(precc2->pdi);
            WinSendMsg(hwndCnr,
                    CM_REMOVERECORD,
                    &precc2,
                    MPFROM2SHORT(1,   // remove one recc
                        CMA_FREE));
        }
    } while ((precc2) && ((ULONG)precc2 != -1));

    if (preccParent == NULL)
        WinSendMsg(hwndCnr,
                CM_INVALIDATERECORD,
                NULL,
                MPFROM2SHORT(0, CMA_REPOSITION));
}

/*
 * winhCnrAllocRecord:
 *      this is a shortcut to allocating memory for
 *      a container record. Returns the recc.
 *          ULONG cbrecc    total size of your record core
 *                          or sizeof(RECORDCORE), if you have
 *                          no additional recc data
 */

PRECORDCORE winhCnrAllocRecord(HWND hwndCnr, ULONG cbrecc)
{
    PRECORDCORE      precc;
    precc = (PRECORDCORE)WinSendMsg(hwndCnr, CM_ALLOCRECORD,
            (MPARAM)(cbrecc-sizeof(RECORDCORE)),
            (MPARAM)1);
    precc->cb = cbrecc;
    return (precc);
}

/*
 * winhCnrInsertRecord:
 *      shortcut to inserting a previously allocated recc
 *      into a container. This func returns the recordcore.
 *      Params:
 *          HWND hwndCnr    the container window
 *          PRECORDCORE preccParent
 *                          for tree views: parent record
 *          PSZ pszText     text for record
 *          ULONG flRecordAttr
 *                  recc flags, as explained in pmstddlg.h:
 *                          CRA_SELECTED       record is selected
 *                          CRA_TARGET         record has target emphasis
 *                          CRA_CURSORED       cursor is on the record
 *                          CRA_INUSE          record has in-use emphasis
 *                          CRA_FILTERED       record has been filtered
 *                          CRA_DROPONABLE     record can be dropped on
 *                          CRA_RECORDREADONLY record is read-only
 *                          CRA_EXPANDED       record is expanded
 *                          CRA_COLLAPSED      record is collapsed
 *                          CRA_PICKED         record picked (Lazy Drag)
 *                  plus the following undocumented:
 *                          CRA_IGNORE
 *                          CRA_SOURCE
 *                          CRA_DISABLED
 *                          CRA_OWNERFREE
 *                          CRA_OWNERDRAW
 */

PRECORDCORE winhCnrInsertRecord(HWND hwndCnr, PRECORDCORE preccParent, PRECORDCORE precc,
            PSZ pszText, ULONG flRecordAttr, BOOL fInvalidate)
{
    RECORDINSERT     ri;

    if (precc) {
        // RECORDCORE stuff
        precc->flRecordAttr = flRecordAttr;
        precc->preccNextRecord = NULL;
        precc->pszIcon = pszText;
        precc->pszText = pszText;
        precc->pszName = pszText;
        precc->pszTree = pszText;
        precc->hptrIcon = NULLHANDLE;

        // setup RECORDINSERT struct
        ri.cb = sizeof(RECORDINSERT);
        ri.pRecordOrder = (PRECORDCORE)CMA_END;
        ri.pRecordParent = (PRECORDCORE)preccParent;
        ri.zOrder = CMA_TOP;
        ri.fInvalidateRecord = fInvalidate;
        ri.cRecordsInsert = 1;

        // printf("  Sending CM_INSERTRECORD\n");
        WinSendMsg(hwndCnr, CM_INSERTRECORD, (MPARAM)precc, (MPARAM)&ri);
    }
    return (precc);
}

/*
 * winhCnrScrollToRecord:
 *      scrolls a given container control to make a given
 *      record visible; returns TRUE if actual scrolling was
 *      performed, FALSE if not neccessary or on errors.
 *      Parameters:
 *          HWND hwndCnr     container window
 *          PRECORDCORE precc the record to scroll to; this can also
 *                           be a PMINIRECORDCORE, we don't care
 *          ULONG fsExtent   determines the record core coordinates to
 *                           take into account (ORed):
 *                           CMA_ICON    icon rectangle
 *                           CMA_TEXT    text rectangle
 *                           CMA_TREEICON "plus" sign in tree view
 *          BOOL KeepParent  for tree views only: keep parent record of
 *                           the record visible; in case pRec is not
 *                           currently visible, we will scroll so that
 *                           the parent record is at the top of the
 *                           visible records (Win95 style)
 *      Returns:
 *          0           scrolled
 *          >0          not scrolled for the following reason:
 *                  1       record rectangle query failed (error)
 *                  2       cnr viewport query failed (error)
 *                  3       record is already visible
 *                  4       cnrinfo query failed (error)
 *                  5       parent record rectangle query failed (error)
 *      This function an improved version of the one (W)(C) Dan Libby, found at
 *      http://zebra.asta.fh-weingarten.de/os2/Snippets/Howt6364.HTML
 *      Improvements (C) 1998 Ulrich M”ller.
 */

ULONG winhCnrScrollToRecord(HWND hwndCnr, PRECORDCORE pRec, ULONG fsExtent, BOOL KeepParent)
{
    QUERYRECORDRECT qRect, qRect2;
    RECTL           rclRecord, rclParentRecord, rclCnr, rclCnr2;
    POINTL          ptlRecord, ptlParentRecord;
    CNRINFO         CnrInfo;
    HAB             hab = WinQueryAnchorBlock(hwndCnr);
    BOOL            KeepParent2;
    LONG            lYOfs;

    qRect.cb = sizeof(qRect);
    qRect.pRecord = (PRECORDCORE)pRec;
    qRect.fsExtent = fsExtent;

    // query record location and size of container
    if (!WinSendMsg(hwndCnr, CM_QUERYRECORDRECT, &rclRecord, &qRect))
        return 1;
    if (!WinSendMsg(hwndCnr, CM_QUERYVIEWPORTRECT, &rclCnr,
                    MPFROM2SHORT(CMA_WINDOW, FALSE)) )
        return 2;

    // check if left bottom point of pRec is currently visible in container
    ptlRecord.x = (rclRecord.xLeft);
    ptlRecord.y = (rclRecord.yBottom);
    // ptlRecord.x = (rclRecord.xLeft + rclRecord.xRight) / 2;
    // ptlRecord.y = (rclRecord.yBottom + rclRecord.yTop) / 2;
    if (WinPtInRect(hab, &rclCnr, &ptlRecord))
         return 3;

    if (KeepParent)
        if (!WinSendMsg(hwndCnr, CM_QUERYCNRINFO, (MPARAM)&CnrInfo, (MPARAM)sizeof(CnrInfo)))
            return 4;
        else KeepParent2 = (CnrInfo.flWindowAttr & CV_TREE);
    else KeepParent2 = FALSE;

    // calculate offset to scroll to make pRec visible
    lYOfs = (rclCnr.yBottom - rclRecord.yBottom)    // this would suffice
          + (rclRecord.yTop - rclRecord.yBottom);  // but we make the next rcl visible too

    if (KeepParent2) {
        qRect2.cb = sizeof(qRect2);
        qRect2.pRecord = (PRECORDCORE)WinSendMsg(hwndCnr, CM_QUERYRECORD,
                (MPARAM)pRec, MPFROM2SHORT(CMA_PARENT, CMA_ITEMORDER));
        qRect2.fsExtent = fsExtent;

        // now query PARENT record location and size of container
        if (!WinSendMsg(hwndCnr, CM_QUERYRECORDRECT, &rclParentRecord, &qRect2))
            return 5;

        ptlParentRecord.x = (rclParentRecord.xLeft);
        ptlParentRecord.y = (rclParentRecord.yTop);
        // ptlParentRecord.x = (rclParentRecord.xLeft + rclParentRecord.xRight) / 2;
        // ptlParentRecord.y = (rclParentRecord.yBottom + rclParentRecord.yTop) / 2;
        rclCnr2 = rclCnr;
        WinOffsetRect(hab, &rclCnr2, 0, -lYOfs);
        // if ( (rclParentRecord.yBottom - rclRecord.yBottom) > (rclCnr.yTop - rclCnr.yBottom) )
        if (!(WinPtInRect(hab, &rclCnr2, &ptlParentRecord)))
        {
            lYOfs = (rclCnr.yTop - rclParentRecord.yTop) // this would suffice
                  - (rclRecord.yTop - rclRecord.yBottom);  // but we make the previous rcl visible too
        }
    }

    if (!KeepParent2)
        WinPostMsg(hwndCnr, CM_SCROLLWINDOW,
                (MPARAM)CMA_HORIZONTAL,
                (MPARAM)(rclRecord.xLeft - rclCnr.xLeft));
    WinPostMsg(hwndCnr, CM_SCROLLWINDOW,
            (MPARAM)CMA_VERTICAL,
            (MPARAM)lYOfs);

    return 0;
}

/*
 * fnCompareName:
 *      comparison func for container sort by name
 */

SHORT EXPENTRY fnCompareName(PRECORDCORE pmrc1, PRECORDCORE pmrc2, PVOID pStorage)
{
    HAB habDesktop = WinQueryAnchorBlock(HWND_DESKTOP);
    pStorage = pStorage; // to keep the compiler happy
    if ((pmrc1) && (pmrc2))
        if ((pmrc1->pszIcon) && (pmrc2->pszIcon))
            switch (WinCompareStrings(habDesktop, 0, 0,
                pmrc1->pszIcon, pmrc2->pszIcon, 0))
            {
                case WCS_LT: return (-1);
                case WCS_GT: return (1);
            }

    return (0);
}

/*
 * fnCompareSize:
 *      comparison func for container sort by file size
 */

SHORT EXPENTRY fnCompareSize(PSIZERECORDCORE pmrc1, PSIZERECORDCORE pmrc2, PVOID pStorage)
{
    pStorage = pStorage; // to keep the compiler happy
    if ((pmrc1) && (pmrc2))
        if ((pmrc1->pdi) && (pmrc2->pdi))
            if (pmrc1->pdi->dTotalSize > pmrc2->pdi->dTotalSize)
                return (-1);
            else if (pmrc1->pdi->dTotalSize < pmrc2->pdi->dTotalSize)
                return (1);

    return (0);
}

/*
 * fnCompareEASize:
 *      comparison func for container sort by ea size
 */

SHORT EXPENTRY fnCompareEASize(PSIZERECORDCORE pmrc1, PSIZERECORDCORE pmrc2, PVOID pStorage)
{
    pStorage = pStorage; // to keep the compiler happy
    if ((pmrc1) && (pmrc2))
        if ((pmrc1->pdi) && (pmrc2->pdi))
            if (pmrc1->pdi->dTotalEASize > pmrc2->pdi->dTotalEASize)
                return (-1);
            else if (pmrc1->pdi->dTotalEASize < pmrc2->pdi->dTotalEASize)
                return (1);

    return (0);
}

/*
 * winhCenterWindow:
 *      centers a window within its parent window.
 *      The window should not be visible to avoid flickering.
 */

void winhCenterWindow(HWND hwnd)
{
   RECTL rclParent;
   RECTL rclWindow;

   WinQueryWindowRect(hwnd, &rclWindow);
   WinQueryWindowRect(WinQueryWindow(hwnd, QW_PARENT), &rclParent);

   rclWindow.xLeft   = (rclParent.xRight - rclWindow.xRight) / 2;
   rclWindow.yBottom = (rclParent.yTop   - rclWindow.yTop  ) / 2;

   WinSetWindowPos(hwnd, NULLHANDLE, rclWindow.xLeft, rclWindow.yBottom,
                    0, 0, SWP_MOVE);
}

/*
 * fnwpMain:
 *      dlg func for main window. This does all the
 *      processing after main() has called WinProcessDlg.
 *      This mainly does the following:
 *      --  update the container by reacting to msgs from
 *          the Collect thread
 *      --  handle cnr context menus
 *      --  handle cnr drag'n'drop
 *      --  handle dialog resizing/minimizing etc.
 */

// shortcuts to dlg controls
HWND        hwndCnr, hwndText, hwndIcon, hwndClose, hwndClear;

// thousands separator from "Country" object
CHAR        szThousand[10];

// for container scrolling
PRECORDCORE preccScrollTo = NULL;

// some storage for between dragging and dropping
BOOL        fDnDValid = FALSE;
CHAR        szDir[CCHMAXPATH];
CHAR        szFile[CCHMAXPATH];

// is window minimized?
BOOL        fMinimized = FALSE;

MRESULT EXPENTRY fnwpMain(HWND hwndDlg, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc = 0;

    switch (msg) {

        /*
         * WM_INITDLG:
         *      Dialog wnds don't get WM_CREATE, they get
         *      WM_INITDLG instead.
         *      setup all the controls, mainly the cnr.
         */

        case WM_INITDLG: {
            CNRINFO CnrInfo;

            // remember wnd handles for later
            hwndIcon = WinWindowFromID(hwndDlg, ID_TSDI_ICON);
            hwndText = WinWindowFromID(hwndDlg, ID_TSDI_TEXT1);
            hwndCnr = WinWindowFromID(hwndDlg, ID_TSDI_CNR);
            hwndClear = WinWindowFromID(hwndDlg, DID_CLEAR);
            hwndClose = WinWindowFromID(hwndDlg, DID_OK);

            // setup the container
            WinSendMsg(hwndCnr, CM_QUERYCNRINFO, &CnrInfo, (MPARAM)sizeof(CnrInfo));
            // don't sort initially, because sort-by-size doesn't
            // work until we have all the directory sizes. Instead,
            // we add all the containers to the end of the list
            // initially, which also reduces cnr repainting. The
            // list is only sorted after processing is done completely.
            CnrInfo.pSortRecord = NULL;
            // switch to tree view
            CnrInfo.flWindowAttr = CV_TREE | CV_TEXT | CA_TREELINE;
            CnrInfo.cxTreeIndent = 25;
            WinSendMsg(hwndCnr, CM_SETCNRINFO,
                    &CnrInfo,
                    (MPARAM)( /* CMA_PSORTRECORD | */ CMA_FLWINDOWATTR | CMA_CXTREEINDENT));

            mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);

            // get the thousands separator for later; this
            // is what is specified in the "Country" object
            PrfQueryProfileString(HINI_USER, "PM_Default_National", "sThousand",
                    ",", szThousand, sizeof(szThousand)-1);

            // initiate processing (below)
            WinPostMsg(hwndDlg, WM_START, 0, 0);
        break; }

        /*
         * WM_START:
         *      start collect thread. This is posted
         *      both by WM_INITDLG above and after folders have
         *      been dropped on the container (CN_DROP below)
         */

        case WM_START:
            WinSetWindowText(hwndText, szRootDir);
            WinEnableWindow(hwndClear, FALSE);
            fStopThread = FALSE;
            DosCreateThread(&tidCollect, fntCollect, 0,
                    CREATE_READY, 4*65536);
            WinSetActiveWindow(HWND_DESKTOP, hwndDlg);
                // needed when something has been dropped
        break;

        /*
         * WM_BEGINDIRECTORY:
         *      posted by CollectDirectory (in the Collect
         *      thread) when it's starting to work on a directory.
         *      We will insert the record core then, which
         *      initially displays "Working..."
         */

        case WM_BEGINDIRECTORY: {

            // the DIRINFO struct is passed in mp1 by
            // the Collect thread
            PDIRINFO pdi = (PDIRINFO)mp1;

            // allocate memory for record core
            pdi->precc = winhCnrAllocRecord(hwndCnr, sizeof(SIZERECORDCORE));

            // recc text
            sprintf(pdi->szRecordText, "%s: working...", pdi->szThis);

             _Pmpf(("  WM_BEGINDIRECTORY %s", pdi->szRecordText));
             _Pmpf(("    Level: %d", pdi->ulRecursionLevel));

            if (pdi->precc) {
                PSIZERECORDCORE preccParent = NULL,
                                preccNew;

                // store reverse pointer; DIRINFO points
                // to the record core, the recc points
                // to DIRINFO (check treesize.h)
                ((PSIZERECORDCORE)(pdi->precc))->pdi = pdi;
                ((PSIZERECORDCORE)(pdi->precc))->fDisplayValid = FALSE;

                // store parent directory
                if (pdi->pParent)
                    preccParent = (PSIZERECORDCORE)pdi->pParent->precc;

                // insert recc into container
                preccNew = (PSIZERECORDCORE)winhCnrInsertRecord(hwndCnr,
                        // parent for tree view
                        (PRECORDCORE)preccParent,   // NULL for root level
                        // recc to insert
                        (PRECORDCORE)pdi->precc,
                        pdi->szRecordText,
                        // recc attributes:
                        CRA_RECORDREADONLY          // don't make editable
                            // initially, expand tree two levels deep
                            | ((pdi->ulRecursionLevel < 3)
                                ? CRA_EXPANDED
                                : CRA_COLLAPSED),
                            // invalidate (repaint) only if parent is expanded;
                            // this avoids excessive cnr repaint
                            (preccParent)
                                ? (preccParent->recc.flRecordAttr & CRA_EXPANDED)
                                : TRUE  // invalidate for root level
                        );

                // select recc if it's the root recc;
                // in Tree view, this automatically deselects
                // the previous selection
                if (pdi->ulRecursionLevel == 1)
                    WinSendMsg(hwndCnr,
                        CM_SETRECORDEMPHASIS,
                        preccNew,
                        MPFROM2SHORT(
                            // select or deselect flag
                            TRUE,
                            CRA_SELECTED
                        ));
            }
        break; }

        /*
         * WM_DONEDIRECTORY:
         *      posted by CollectDirectory when processing
         *      of a directory and all of its subdirectories
         *      is done. We will update the recc to show
         *      the dir's size.
         *      When a recc text changes, the record core
         *      _always_ needs to be invalidated, or the
         *      cnr displays garbage. To avoid excessive
         *      cnr repainting, we try to do this economically.
         */

        case WM_DONEDIRECTORY: {
            CHAR szSize[30];
            CHAR szEASize[30];

            // the DIRINFO struct is passed in mp1 by
            // the Collect thread
            PDIRINFO pdi = (PDIRINFO)mp1;

            // recc's parent record
            PRECORDCORE preccParent =
                            (pdi->pParent)
                                ? pdi->pParent->precc
                                : NULL;

            // invalidate (repaint) record only if the
            // parent recc is expanded; for collapsed trees,
            // this repaint is done only when the tree is
            // expanded manually (CN_EXPAND below) to avoid
            // excessive cnr repaint
            BOOL fInvalidate = (preccParent)
                                ? (preccParent->flRecordAttr & CRA_EXPANDED)
                                : TRUE;

            // new recc title:
            sprintf(pdi->szRecordText, "%s (%s %s",
                                // title
                                    pdi->szThis,
                                // value
                                    strhThousandsDouble(szSize,
                                        (Settings.ulSizeDisplay == SD_BYTES)
                                                ? pdi->dTotalSize
                                            : (Settings.ulSizeDisplay == SD_KBYTES)
                                                ? ((pdi->dTotalSize + 512) / 1024)
                                            : ((pdi->dTotalSize + (512*1024)) / 1024 / 1024),
                                        szThousand[0]),
                                // "bytes" string
                                    (Settings.ulSizeDisplay == SD_BYTES)
                                            ? "bytes"
                                        : (Settings.ulSizeDisplay == SD_KBYTES)
                                            ? "KBytes"
                                        : "MBytes");
            if (Settings.CollectEAs)
                sprintf(pdi->szRecordText + strlen(pdi->szRecordText),
                                ", %s %s EAs",
                                strhThousandsDouble(szSize,
                                        (Settings.ulSizeDisplay == SD_BYTES)
                                                ? pdi->dTotalEASize
                                            : (Settings.ulSizeDisplay == SD_KBYTES)
                                                ? ((pdi->dTotalEASize + 512) / 1024)
                                            : ((pdi->dTotalSize + (512*1024)) / 1024 / 1024),
                                        szThousand[0]),
                                    (Settings.ulSizeDisplay == SD_BYTES)
                                            ? "bytes"
                                        : (Settings.ulSizeDisplay == SD_KBYTES)
                                            ? "KBytes"
                                        : "MBytes");

            strcat(pdi->szRecordText, ")");
             _Pmpf(("  WM_DONEDIRECTORY %s", pdi->szRecordText));

            if (fInvalidate) {
                WinSendMsg(hwndCnr,
                        CM_INVALIDATERECORD,
                        (MPARAM)&(pdi->precc),
                        MPFROM2SHORT(1,     // one record only
                                CMA_TEXTCHANGED));
                ((PSIZERECORDCORE)pdi->precc)->fDisplayValid = TRUE;
            }
        break; }

        /*
         * WM_DONEWITHALL:
         *      posted by the Collect thread right before
         *      it is ending. We will now sort the whole cnr.
         */

        case WM_DONEWITHALL: {
            WinEnableWindow(hwndClear, TRUE);

            // sort cnr; ulSort is a global var containing
            // either SV_SIZE or SV_EASIZE or SV_NAME
            WinSendMsg(hwndCnr,
                        CM_SORTRECORD,
                        ((Settings.ulSort == SV_SIZE) ? (MPARAM)fnCompareSize
                        : (Settings.ulSort == SV_EASIZE) ? (MPARAM)fnCompareEASize
                        : (MPARAM)fnCompareName),
                        NULL);

            // scroll to make root record visible
            preccScrollTo = pdiRoot->precc;
            WinStartTimer(WinQueryAnchorBlock(hwndDlg),
                    hwndDlg,
                    2,
                    100);
        break; }

        /*
         * WM_CONTROL:
         *
         */

        case WM_CONTROL: {
            switch (SHORT2FROMMP(mp1)) { // notify code

                /*
                 * CN_ENTER:
                 *      double-click or "Enter" on
                 *      record core: open WPS folder
                 */

                case CN_ENTER: {
                    PNOTIFYRECORDENTER pnre = (PVOID)mp2;
                    if (pnre) {
                        PSIZERECORDCORE precc = (PVOID)pnre->pRecord;
                        if (precc)
                            WinOpenObject(WinQueryObject(precc->pdi->szFullPath),
                                    0,          // == OPEN_DEFAULT
                                    TRUE);
                        // else double click on whitespace, which we'll ignore
                    }
                break; }

                /*
                 * CN_EXPANDTREE:
                 *      tree view has been expanded:
                 *      do cnr auto-scroll if we're
                 *      not initially filling the cnr,
                 *      and invalidate all the sub-records,
                 *      if they have not been invalidated
                 *      before
                 */

                case CN_EXPANDTREE: {
                    PRECORDCORE precc2 = NULL;
                    BOOL fFirstRun = TRUE,
                         fWindowDisabled = FALSE;
                    preccScrollTo = mp2;

                    // loop thru all the child records
                    // and invalidate them
                    do {
                        precc2 =
                            (PRECORDCORE)WinSendMsg(hwndCnr,
                                    CM_QUERYRECORD,
                                    // the following gets either the
                                    // first child recc of the recc
                                    // that has been expanded or the
                                    // next child for consecutive loops
                                    (MPARAM)((fFirstRun)
                                            ? preccScrollTo   // first loop
                                            : precc2),
                                    MPFROM2SHORT(
                                            ((fFirstRun)
                                            ? CMA_FIRSTCHILD  // first loop
                                            : CMA_NEXT
                                        ),
                                        CMA_ITEMORDER)
                                    );
                        fFirstRun = FALSE;
                        if ((precc2) && ((ULONG)precc2 != -1))
                            if (!((PSIZERECORDCORE)precc2)->fDisplayValid)
                            {
                                if (!fWindowDisabled) {
                                    WinEnableWindowUpdate(hwndCnr, FALSE);
                                    fWindowDisabled = TRUE;
                                }
                                WinSendMsg(hwndCnr,
                                        CM_INVALIDATERECORD,
                                        (MPARAM)&precc2,
                                        MPFROM2SHORT(1,     // one record only
                                                CMA_TEXTCHANGED));

                                ((PSIZERECORDCORE)precc2)->fDisplayValid = TRUE;
                            }
                    } while ((precc2) && ((ULONG)precc2 != -1));

                    if (fWindowDisabled)
                        WinEnableWindowUpdate(hwndCnr, TRUE);

                    // do cnr auto-scroll
                    WinStartTimer(WinQueryAnchorBlock(hwndDlg),
                            hwndDlg,
                            1,
                            100);
                break; }

                /*
                 * CN_EMPHASIS:
                 *      selection changed: update title
                 */

                case CN_EMPHASIS: {
                    // get cnr notification struct
                    PNOTIFYRECORDEMPHASIS pnre = (PNOTIFYRECORDEMPHASIS)mp2;

                    if (pnre)
                        if (    (pnre->fEmphasisMask & CRA_SELECTED)
                             && (pnre->pRecord)
                           )
                        {
                            WinSetWindowText(hwndText,
                                    ((PSIZERECORDCORE)(pnre->pRecord))
                                        ->pdi->szFullPath);
                        }
                break; }

                /*
                 * CN_CONTEXTMENU:
                 *      context menu requested
                 */

                case CN_CONTEXTMENU: {
                    POINTL ptl;
                    HWND   hPopupMenu;
                    ULONG ulFlagsSelected = 0;
                    CHAR szDummy[2000];

                    // mp2 contains the recc upon which the context
                    // menu was requested or NULL for cnr whitespace
                    PSIZERECORDCORE preccSelected = (PSIZERECORDCORE)mp2;

                    // open context menu only on whitespace
                    // of container
                    if (!preccSelected) {
                        // whitespace:
                        hPopupMenu = WinLoadMenu(hwndDlg, NULLHANDLE,
                                ID_TSM_CONTEXT);

                        WinQueryPointerPos(HWND_DESKTOP, &ptl);
                        WinPopupMenu(HWND_DESKTOP, hwndDlg,
                                hPopupMenu,
                                (SHORT)ptl.x,
                                (SHORT)ptl.y,
                                0,
                                PU_NONE | PU_MOUSEBUTTON1 | PU_KEYBOARD
                                | PU_HCONSTRAIN | PU_VCONSTRAIN);

                        // check current sort item
                        WinSendMsg(hPopupMenu, MM_SETITEMATTR,
                                MPFROM2SHORT(
                                        (Settings.ulSort == SV_NAME) ? ID_TSMI_SORTBYNAME
                                        : (Settings.ulSort == SV_EASIZE) ? ID_TSMI_SORTBYEASIZE
                                        : ID_TSMI_SORTBYSIZE
                                    , TRUE),
                                MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));

                        // check size display
                        WinSendMsg(hPopupMenu, MM_SETITEMATTR,
                                MPFROM2SHORT(
                                        (Settings.ulSizeDisplay == SD_BYTES) ? ID_TSMI_SIZE_BYTES
                                        : (Settings.ulSizeDisplay == SD_KBYTES) ? ID_TSMI_SIZE_KBYTES
                                        : ID_TSMI_SIZE_MBYTES
                                    , TRUE),
                                MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));

                        // check "Collect EAs"?
                        if (Settings.CollectEAs)
                            WinSendMsg(hPopupMenu, MM_SETITEMATTR,
                                MPFROM2SHORT(ID_TSMI_COLLECTEAS,
                                    TRUE),
                                MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));
                        // check "Low priority"?
                        if (Settings.LowPriority)
                            WinSendMsg(hPopupMenu, MM_SETITEMATTR,
                                MPFROM2SHORT(ID_TSMI_LOWPRTY,
                                    TRUE),
                                MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));
                    }
                break; }

                /**********************************************
                *                                             *
                *  Drag'n'drop                                *
                *                                             *
                **********************************************/

                /*
                    The container control translates the
                    PM drag'n'drop messages into container
                    notifications. This is barely documented,
                    but still helpful, because the cnr already
                    gives us container-specific structures
                    with these notification codes and draws
                    target emphasis automatically.

                    For us, two nofifications are important:

                    --  CN_DRAGOVER is sent while the user
                        is dragging stuff over the container;
                        the return value must indicate whether
                        this is a valid object that may be
                        dropped;
                    --  CN_DROP is sent after the user has
                        released the dragged object (i.e.
                        when it's dropped). We must then
                        react to read in the new directory.
                 */

                /*
                 * CN_DRAGOVER:
                 *      something's being dragged over the container.
                 *      Depending on the return value of this message,
                 *      PM will show the "not allowed" pointer or not.
                 *      Also, the cnr can automatically give itself
                 *      target emphasis.
                 *      Let's check if we're getting a WPS folder and,
                 *      if so, allow this.
                 */

                case CN_DRAGOVER: {
                    // container drag info structures
                    PCNRDRAGINFO pcdi = (PCNRDRAGINFO)mp2;
                    PDRAGITEM pdi;

                    // reset global variable for CN_DROP later
                    fDnDValid = FALSE;

                    // default return value: do _not_ allow dropping.
                    // This will result in the "forbidden" pointer
                    // being drawn by PM. NEVERDROP means that we
                    // will _never_ accept this object, and PM will
                    // stop sending messages for this object then.
                    mrc = MRFROM2SHORT(DOR_NEVERDROP,
                            DO_DEFAULT);

                    if (tidCollect)
                        // accept nothing while Collect thread is working
                        break;
                    if (pcdi->pDragInfo->cditem != 1)
                        // accept no more than one single item at a time;
                        // we cannot handle more than one directory
                        break;
                    if (pcdi->pDragInfo->usOperation != DO_DEFAULT) {
                        // we don't accept modifier keys (Shift, Ctrl, ...).
                        // But instead of NEVERDROP, return NODROP,
                        // which will keep sending msgs instead
                        // of stopping that
                        mrc = MRFROM2SHORT(DOR_NODROP, DO_DEFAULT);
                        break;
                    }
                    if (pcdi->pRecord) {
                        // mouse is over a record core: no,
                        // we only accept drag on whitespace.
                        mrc = MRFROM2SHORT(DOR_NODROP, DO_DEFAULT);
                        break;
                    }

                    // Now that we've ruled out the most apparent
                    // errors, we need to check further:

                    // get access to the drag'n'drop structures
                    DrgAccessDraginfo(pcdi->pDragInfo);
                    // get the item being dragged (PDRAGITEM)
                    pdi = DrgQueryDragitemPtr(pcdi->pDragInfo, 0);

                    /*  From PMREF:

                        typedef struct _DRAGITEM {
                            HWND     hwndItem;
                              Window handle of the source of the drag operation.
                            ULONG    ulItemID;
                              Information used by the source to identify the object being dragged.
                            HSTR     hstrType;
                              String handle of the object type.
                            HSTR     hstrRMF;
                              String handle of the rendering mechanism and format.
                            HSTR     hstrContainerName;
                              String handle of the name of the container holding the source object.
                            HSTR     hstrSourceName;
                              String handle of the name of the source object.
                            HSTR     hstrTargetName;
                              String handle of the suggested name of the object at the target.
                            SHORT    cxOffset;
                              X-offset from the pointer hot spot to the origin of the image that represents this object.
                            SHORT    cyOffset;
                              Y-offset from the pointer hot spot to the origin of the image that represents this object.
                            USHORT   fsControl;
                              Source-object control flags.
                            USHORT   fsSupportedOps;
                              Direct manipulation operations supported by the source object.
                        } DRAGITEM;        */

                    if (pdi) {
                        // now check render mechanism and format.
                        // The WPS gives us DRM_OS2FILE for "real" files only,
                        // but DRM_OBJECT for abstract objects.
                        // Moreover, the format is DRF_TEXT for all files,
                        // but folders have DRF_UNKNOWN instead.
                        // So by checking for DRM_OS2FILE and DRF_UNKNOWN,
                        // we should get folders only.
                        // (Unfortunately, this will _not_ work for root folders
                        // (drive objects), because these are rendered as
                        // DRM_OBJECT, and since we're not running in the WPS
                        // process, we cannot get the corresponding SOM object)
                        if (DrgVerifyRMF(pdi, "DRM_OS2FILE", "DRF_UNKNOWN"))
                        {
                            // it's a folder (directory):
                            // the following gives us the full path of where the
                            // dragged folder resides in. The PM d'n'd functions always
                            // use these strange "string handles", which we
                            // need to translate now.
                            DrgQueryStrName(pdi->hstrContainerName,
                                        sizeof(szDir), szDir);
                                // e.g. for "F:\OS2\APPS" this would be "F:\OS2\"

                            // this gives us the filename of the dragged folder
                            DrgQueryStrName(pdi->hstrSourceName,
                                        sizeof(szFile), szFile);
                                // e.g. for "F:\OS2\APPS" this would be "APPS"

                            // store flag for CN_DROP below
                            fDnDValid = TRUE;
                            // return "allow drop" code
                            mrc = MRFROM2SHORT(DOR_DROP,
                                    DO_UNKNOWN);  // means application-specific;
                                        // that's cool for us, because we don't
                                        // really want the WPS to start copying
                                        // or moving anything
                        }
                    }

                    // clean up
                    DrgFreeDraginfo(pcdi->pDragInfo);
                break; }

                /*
                 * CN_DROP:
                 *      something _has_ now been dropped on the cnr.
                 */

                case CN_DROP: {
                    // check the global variable which has been set
                    // by CN_DRAGOVER above:
                    if (fDnDValid) {
                        // CN_DRAGOVER above has considered this valid:
                        // restart the whole gathering process
                        sprintf(szRootDir, "%s%s", szDir, szFile);
                        WinPostMsg(hwndDlg, WM_START, NULL, NULL);
                        fDnDValid = FALSE;
                    }
                break; }
            }


        break; }

        /*
         * WM_TIMER:
         *      timer for tree view auto-scroll
         */

        case WM_TIMER: {
            WinStopTimer(WinQueryAnchorBlock(hwndDlg),
                    hwndDlg,
                    (ULONG)mp1);
            if ( ( preccScrollTo->flRecordAttr & CRA_EXPANDED) != 0 ) {
                PRECORDCORE     preccLastChild;
                // scroll the tree view properly
                preccLastChild = WinSendMsg(hwndCnr,
                        CM_QUERYRECORD,
                        preccScrollTo,   // expanded PRECORDCORE from CN_EXPANDTREE
                        MPFROM2SHORT(
                             ( ((ULONG)mp1 == 1)
                                ? CMA_LASTCHILD : CMA_FIRSTCHILD),
                             CMA_ITEMORDER));
                if (preccLastChild) {
                    // ULONG ulrc;
                    winhCnrScrollToRecord(hwndCnr,
                            (PRECORDCORE)preccLastChild,
                            CMA_TEXT,   // record text rectangle only
                            TRUE);      // keep parent visible
                }
            }
        break; }

        /*
         * WM_ADJUSTWINDOWPOS:
         *      sent _while_ a window is being moved
         *      or resized.
         *      set minimum wnd size when resizing
         */

        case WM_ADJUSTWINDOWPOS: {
            PSWP pswp = PVOIDFROMMP(mp1);
            mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);

            // only if window is not minimized, or this
            // will produce really funny minimized windows
            if (    ((pswp->fl & (SWP_SIZE | SWP_MINIMIZE)) == SWP_SIZE)
                 && (!fMinimized)
               )
            {
                // resizing and not minimize: assert minimum cx/cy
                if (pswp->cx < 300)
                    pswp->cx = 300;
                if (pswp->cy < 300)
                    pswp->cy = 300;
            }
        break; }

        /*
         * WM_WINDOWPOSCHANGED:
         *      posted _after_ the window has been moved
         *      or resized.
         *      since we have a sizeable dlg, we need to
         *      update the controls' sizes also, or PM
         *      will display garbage. This is the trick
         *      how to use sizeable dlgs, because the do
         *      _not_ get sent WM_SIZE messages.
         */

        case WM_WINDOWPOSCHANGED: {
            // this msg is passed two SWP structs:
            // one for the old, one for the new data
            // (from PM docs)
            PSWP pswpNew = PVOIDFROMMP(mp1);
            PSWP pswpOld = pswpNew + 1;
            BOOL fWasMinimized = fMinimized;

            if (pswpNew->fl & SWP_MINIMIZE) {
                // minimizing: pmref says, we should
                // hide all controls, because otherwise
                // these get painted upon the minimized icon
                fMinimized = TRUE;
                WinShowWindow(hwndCnr, FALSE);
                WinShowWindow(hwndClose, FALSE);
                WinShowWindow(hwndIcon, FALSE);
                WinShowWindow(hwndClear, FALSE);
                WinShowWindow(hwndText, FALSE);
            }
            else if (pswpNew->fl & SWP_RESTORE) {
                // un-minimize: do the reverse
                fMinimized = FALSE;
                WinShowWindow(hwndCnr, TRUE);
                WinShowWindow(hwndClose, TRUE);
                WinShowWindow(hwndIcon, TRUE);
                WinShowWindow(hwndClear, TRUE);
                WinShowWindow(hwndText, TRUE);
            }

            // resizing and not minimize?
            if (    (pswpNew->fl & SWP_SIZE)
                &&  (!fMinimized)
                &&  (!fWasMinimized)
               )
            {
                SWP swpControl;

                // for each control in the dlg, we now adjust
                // size and/or position by determining the
                // difference between the old and new SWP structs.
                LONG        ldcx = (pswpNew->cx - pswpOld->cx),
                                    // width delta
                            ldcy = (pswpNew->cy - pswpOld->cy);
                                    // height delta

                // container: adjust width and height
                WinQueryWindowPos(hwndCnr,
                        &swpControl);
                WinSetWindowPos(hwndCnr,
                        NULLHANDLE, 0, 0, // don't care
                        swpControl.cx + ldcx,
                        swpControl.cy + ldcy,
                        SWP_SIZE);

                // top text: adjust width and ypos
                WinQueryWindowPos(hwndText,
                        &swpControl);
                WinSetWindowPos(hwndText,
                        NULLHANDLE,
                        swpControl.x,
                        swpControl.y + ldcy,
                        swpControl.cx + ldcx,
                        swpControl.cy,
                        SWP_MOVE | SWP_SIZE);

                // icon: adjust ypos only
                WinQueryWindowPos(hwndIcon,
                        &swpControl);
                WinSetWindowPos(hwndIcon,
                        NULLHANDLE,
                        swpControl.x,
                        swpControl.y + ldcy,
                        0, 0,
                        SWP_MOVE);

                // "Clear" button:
                // adjust xpos only
                WinQueryWindowPos(hwndClear,
                        &swpControl);
                WinSetWindowPos(hwndClear,
                        NULLHANDLE,
                        swpControl.x + ldcx,
                        swpControl.y,
                        0, 0,
                        SWP_MOVE);
            }
            mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
        break; }

        /*
         * WM_COMMAND:
         *      button or menu commands.
         */

        case WM_COMMAND: {
            switch ((ULONG)mp1) {

                case DID_CLEAR:
                    Cleanup(hwndCnr, NULL);
                break;

                case ID_TSMI_SORTBYNAME: {
                    WinSendMsg(hwndCnr,
                                CM_SORTRECORD,
                                (MPARAM)fnCompareName,
                                NULL);
                    Settings.ulSort = SV_NAME;
                    PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
                            &Settings,
                            sizeof(Settings));
                break; }

                case ID_TSMI_SORTBYSIZE: {
                    WinSendMsg(hwndCnr,
                                CM_SORTRECORD,
                                (MPARAM)fnCompareSize,
                                NULL);
                    Settings.ulSort = SV_SIZE;
                    PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
                            &Settings,
                            sizeof(Settings));
                break; }

                case ID_TSMI_SORTBYEASIZE: {
                    WinSendMsg(hwndCnr,
                                CM_SORTRECORD,
                                (MPARAM)fnCompareEASize,
                                NULL);
                    Settings.ulSort = SV_EASIZE;
                    PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
                            &Settings,
                            sizeof(Settings));
                break; }

                case ID_TSMI_SIZE_BYTES: {
                    Settings.ulSizeDisplay = SD_BYTES;
                    PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
                            &Settings,
                            sizeof(Settings));
                break; }

                case ID_TSMI_SIZE_KBYTES: {
                    Settings.ulSizeDisplay = SD_KBYTES;
                    PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
                            &Settings,
                            sizeof(Settings));
                break; }

                case ID_TSMI_SIZE_MBYTES: {
                    Settings.ulSizeDisplay = SD_MBYTES;
                    PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
                            &Settings,
                            sizeof(Settings));
                break; }

                case ID_TSMI_LOWPRTY: {
                    Settings.LowPriority = !(Settings.LowPriority);
                    PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
                            &Settings,
                            sizeof(Settings));

                    if (tidCollect)
                        // collect thread running:
                        DosSetPriority(PRTYS_THREAD,
                            (Settings.LowPriority) ? PRTYC_IDLETIME : PRTYC_REGULAR,
                            0, tidCollect);
                break; }

                case ID_TSMI_COLLECTEAS: {
                    Settings.CollectEAs = !(Settings.CollectEAs);
                    PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
                            &Settings,
                            sizeof(Settings));
                break; }

                case ID_TSMI_PRODINFO: {
                    // advertise for myself
                    CHAR szGPLInfo[2000];
                    HWND hwndInfo = WinLoadDlg(HWND_DESKTOP, hwndDlg,
                                      WinDefDlgProc,
                                      NULLHANDLE,
                                      ID_TSD_PRODINFO,
                                      NULL);

                    // load GPL info message into prodinfo MLE
                    strcpy(szGPLInfo,
                        "Treesize is part of the XFolder package. XFolder is free software. "
                       "The full source code is now available. You are welcome to "
                        "redistribute and/or modify XFolder under the conditions of the "
                        "GNU General Public License (GPL). XFolder comes with absolutely "
                        "NO WARRANTY. For details, refer to the \"Notices\" section of "
                        "the XFolder Online Reference.\n\n"
                        "Treesize will display either the contents of the directory you "
                        "have specified on the command line (e.g. \"TREESIZE F:\\OS2\") "
                        "or, if no dir has been specified, the current directory. "
                        "\nAfter Treesize has finished collecting the directories, "
                        "you may drop any additional WPS folder "
                        "on the existing tree view to have a new tree displayed. "
                        "\nPress the \"Clear\" button before that to have an empty window. "
                        "\nDouble-click on any item in the tree view to have the corresponding "
                        "WPS folder opened in its default view. "
                        "\nTreesize saves all window parameters (position, colors, fonts) "
                        "and the sort options you have last specified."
                        );
                    WinSetDlgItemText(hwndInfo, ID_TSDI_TEXT1, szGPLInfo);
                    WinSendDlgItemMsg(hwndInfo, ID_TSDI_TEXT1,
                            MLM_SETFIRSTCHAR,       // scroll MLE to top
                            (MPARAM)0,
                            NULL);

                    winhCenterWindow(hwndInfo);
                    WinProcessDlg(hwndInfo);
                    WinDestroyWindow(hwndInfo);
                break; }

                default: {
                    // this includes the "OK" button and
                    // the "Close" menu item:
                    // store window params (size, presparams, ...)
                    // Note that this function only works while the
                    // window is still visible, so we do this here.
                    fStopThread = TRUE;
                    WinStoreWindowPos("XFldTreesize", "WindowPos", hwndDlg);
                    Cleanup(hwndCnr, NULL);
                    mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
                break; }

            }
        break; }

        default:
            mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
    }
    return (mrc);
}

/*
 * main:
 *      Program entry point. This loads the main wnd
 +      as a dlg resource. All the processing is done
 *      in fnwpMain above, which is running after this
 *      func has called WinProcessDlg.
 */

int main(int argc, char *argv[])
{
    HAB         hab;
    HMQ         hmq;
    FRAMECDATA  fcd;
    QMSG        qmsg;
    BOOL        Proceed = TRUE;
    ULONG       ul;
    CHAR        szTitle[400];

    if (!(hab = WinInitialize(0)))
        return FALSE;

    if (!(hmq = WinCreateMsgQueue(hab, 0)))
        return FALSE;

    // check command line parameters: in C,
    // the first parameter is always the full
    // path of the TREESIZE executable.
    if (argc == 1) {
        // one param only: no directory given,
        // use current dir then.
        ULONG ulCurrentDisk = 0, ulDriveMap = 0;
        DosQueryCurrentDisk(&ulCurrentDisk, &ulDriveMap);
        szRootDir[0] = 'A'+ulCurrentDisk-1;
        szRootDir[1] = ':';
        szRootDir[2] = '\\';
        DosQueryCurrentDir(0, szRootDir+3, &cbRootDir);
    } else
        // param given: use this
        strcpy(szRootDir, argv[1]);

    if (Proceed) {
        ULONG       cbSettings = sizeof(Settings);
        SWCNTRL     swctl;
        PID         pid;
        HSWITCH     hswitch;
        HPOINTER    hIcon = WinLoadPointer(HWND_DESKTOP,
                                    NULLHANDLE,
                                    ID_ICON);

        // this loads the main window. All the processing
        // is done in fnwpMain.
        hwndMain = WinLoadDlg(HWND_DESKTOP, HWND_DESKTOP,
                fnwpMain,
                0, ID_TSD_MAIN,
                szRootDir);

        // append path to title
        WinQueryWindowText(hwndMain, sizeof(szTitle), szTitle);
        sprintf(szTitle+strlen(szTitle), " - %s", szRootDir);
        WinSetWindowText(hwndMain, szTitle);

        // give the dlg an icon
        WinSendMsg(hwndMain,
                   WM_SETICON,
                   (MPARAM)hIcon,
                    NULL);

        // add ourselves to the tasklist
        swctl.hwnd = hwndMain;                 // window handle
        swctl.hwndIcon = hIcon;                // icon handle
        swctl.hprog = NULLHANDLE;              // program handle
        WinQueryWindowProcess(hwndMain, &(swctl.idProcess), NULL);
                                               // process identifier
        swctl.idSession = 0;                   // session identifier ?
        swctl.uchVisibility = SWL_VISIBLE;     // visibility
        swctl.fbJump = SWL_JUMPABLE;           // jump indicator
        WinQueryWindowText(hwndMain, sizeof(swctl.szSwtitle), (PSZ)&swctl.szSwtitle);
        swctl.bProgType = PROG_DEFAULT;        // program type

        hswitch = WinAddSwitchEntry(&swctl);

        // get settings in OS2.INI
        WinRestoreWindowPos("XFldTreesize", "WindowPos", hwndMain);
        Settings.ulSort = SV_SIZE;
        Settings.CollectEAs = TRUE;
        Settings.ulSizeDisplay = SD_KBYTES;
        Settings.LowPriority = TRUE;
        PrfQueryProfileData(HINI_USER, "XFldTreesize", "Settings",
                    &Settings,
                    &cbSettings);

        // go!
        WinShowWindow(hwndMain, TRUE);
        WinProcessDlg(hwndMain);
        WinDestroyWindow(hwndMain);
    } // end if (proceed)

    if (tidCollect) {
        // give the Collect thread 5 seconds to exit;
        // normally, this shouldn't be a problem
        fStopThread = TRUE;
        for (ul = 0; ul < 50; ul++)
            if (tidCollect == 0)
                break;
            else
                DosSleep(100);  // 50*100 ms = 5 seconds
    }

    // clean up on the way out
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);

    return TRUE;
}


