home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: WPS_PM / WPS_PM.zip / xfld085s.zip / treesize / treesize.c next >
C/C++ Source or Header  |  1998-12-28  |  66KB  |  1,611 lines

  1. /*
  2.  * treesize.c:
  3.  *      this is the main (and only) .C file for treesize.exe.
  4.  *
  5.  *      Treesize is new with XFoler V0.81.
  6.  *
  7.  *      This thing is multi-threaded. The second (Collect) thread
  8.  *      (fntCollect) is started by WM_INITDLG of the main window (fnwpMain)
  9.  *      and keeps sending messages to the main window which will then
  10.  *      insert the directory records into the container.
  11.  *
  12.  *      This is mostly a quick hack done it one day and a half.
  13.  *      There might be quite many bugs in this, besides EA collection is
  14.  *      really slow.
  15.  *
  16.  *      Still, it might be interesting if you'd like to know more about
  17.  *      the following:
  18.  *          --  multithreading PM programs and inter-thread communication
  19.  *          --  using a dlg template as a main window which can be resized,
  20.  *              maximized and minimized
  21.  *          --  container programming: tree views, context menus, drag-n-drop.
  22.  *
  23.  *      Copyright (C) 1997-98 Ulrich Möller.
  24.  *      This program is free software; you can redistribute it and/or modify
  25.  *      it under the terms of the GNU General Public License as published by
  26.  *      the Free Software Foundation, in version 2 as it comes in the COPYING
  27.  *      file of the XFolder main distribution.
  28.  *      This program is distributed in the hope that it will be useful,
  29.  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
  30.  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  31.  *      GNU General Public License for more details.
  32.  */
  33.  
  34. #define  INCL_WIN
  35. #define  INCL_WINWORKPLACE
  36. #define  INCL_DOS
  37. #define  INCL_DOSERRORS
  38. #include <os2.h>
  39. #include <stdlib.h>
  40. #include <stdio.h>
  41. #include <string.h>
  42. #include "treesize.h"
  43. // #define _PMPRINTF_       // uncomment to have debugging
  44. #include "..\helpers\pmprintf.h"
  45. #include "..\helpers\eas.h"
  46. #include "..\helpers\dosh.h"
  47. #include "..\helpers\stringh.h"
  48.  
  49. /*
  50.  * Global variables
  51.  *
  52.  */
  53.  
  54. // the directory to start with
  55. CHAR        szRootDir[CCHMAXPATH];
  56. ULONG       cbRootDir = sizeof(szRootDir)-3;
  57.  
  58. // main wnd handle
  59. HWND        hwndMain = NULLHANDLE;
  60.  
  61. // Collect thread ID: always 0 if not running
  62. TID         tidCollect = 0;
  63. HAB         habCollect;
  64. HMQ         hmqCollect;
  65. // setting this to TRUE will stop the Collect thread
  66. BOOL        fStopThread = FALSE;
  67.  
  68. // DIRINFO struct for the directory to start with
  69. PDIRINFO    pdiRoot;
  70.  
  71. // and the settings read from/ stored to OS2.INI
  72. struct {
  73.     ULONG       ulSort;
  74.     BOOL        CollectEAs;
  75.     ULONG       ulSizeDisplay;
  76.     BOOL        LowPriority;
  77. } Settings;
  78.  
  79. /********************************************************************
  80.  *                                                                  *
  81.  *   Collect thread                                                 *
  82.  *                                                                  *
  83.  ********************************************************************/
  84.  
  85. /*
  86.  * CollectDirectory:
  87.  *      initially called by the Collect thread (fntCollect)
  88.  *      and then calls itself recursively for each
  89.  *      directory it finds.
  90.  *      Takes a DIRINFO structure, which is also initially
  91.  *      created by the Collect thread.
  92.  *      This sends two messages to the main window (fnwpMain):
  93.  *      WM_BEGINDIRECTORY, which causes the record core
  94.  *      to be created, and WM_ENDDIRECTORY, which causes
  95.  *      the record core to be updated with the directory size,
  96.  *      when processing of this directory (and all the subdirs)
  97.  *      is done with.
  98.  */
  99.  
  100. VOID CollectDirectory(PDIRINFO pdiThis)
  101. {
  102.     HDIR          hdirFindHandle = HDIR_CREATE;
  103.     FILEFINDBUF3  ffb3     = {0};      /* Returned from FindFirst/Next */
  104.     ULONG         cbFFB3 = sizeof(FILEFINDBUF3);
  105.     ULONG         ulFindCount    = 1;        /* Look for 1 file at a time    */
  106.     APIRET        rc             = NO_ERROR; /* Return code                  */
  107.     CHAR          szCurrentDir[CCHMAXPATH],
  108.                   szSearchMask[CCHMAXPATH];
  109.  
  110.     _Pmpf(("Entering CollectDirectory"));
  111.     _Pmpf(("  for: %s", pdiThis->szFullPath));
  112.  
  113.     // add a "\" to the directory, if we're not in root
  114.     strcpy(szCurrentDir, pdiThis->szFullPath);
  115.     if (szCurrentDir[strlen(szCurrentDir)-1] != '\\')
  116.         strcat(szCurrentDir, "\\");
  117.  
  118.     // have record core inserted; since we're SENDing this
  119.     // msg (and not posting), this also takes care of
  120.     // thread synchronization so that the record cores don't
  121.     // get messed up. Sending messages across threads blocks
  122.     // the calling thread until the msg has been processed.
  123.     // Ah yes, and this is why we may only send this if our
  124.     // thread hasn't been requested to terminate. In that case,
  125.     // the main window might just be being destroyed, so this
  126.     // would block PM. Sigh.
  127.     if (!fStopThread)
  128.         WinSendMsg(hwndMain, WM_BEGINDIRECTORY, (MPARAM)pdiThis, NULL);
  129.  
  130.     // now go for the first directory entry in our directory (szCurrentDir):
  131.     strcpy(szSearchMask, szCurrentDir);
  132.     strcat(szSearchMask, "*");
  133.     rc = DosFindFirst( szSearchMask,
  134.                        &hdirFindHandle,
  135.                        // find eeeeverything
  136.                        FILE_ARCHIVED | FILE_HIDDEN | FILE_SYSTEM | FILE_READONLY | FILE_DIRECTORY,
  137.                        &ffb3,
  138.                        cbFFB3,
  139.                        &ulFindCount,
  140.                        FIL_STANDARD);
  141.  
  142.     // and start looping
  143.     while (     (rc == NO_ERROR)
  144.             &&  (!fStopThread)      // if our thread hasn't been stopped
  145.           )
  146.     {
  147.         CHAR szFile[CCHMAXPATH];
  148.         ULONG ulEASize;
  149.  
  150.         sprintf(szFile, "%s%s", szCurrentDir, // always has trailing "\"
  151.                                 ffb3.achName);
  152.  
  153.         // get EA size
  154.         if (    (Settings.CollectEAs)
  155.              && (strlen(szCurrentDir) > 3)
  156.            )
  157.         {
  158.             ulEASize = eaPathQueryTotalSize(szFile);
  159.             _Pmpf(("EAs for %s: %d", ffb3.achName, ulEASize));
  160.             pdiThis->dTotalEASize += ulEASize;
  161.         }
  162.  
  163.         if (ffb3.attrFile & FILE_DIRECTORY) {
  164.             if (    (strcmp(ffb3.achName, ".") != 0)
  165.                  && (strcmp(ffb3.achName, "..") != 0)
  166.                )
  167.             {
  168.                 // subdirectory found: recurse!
  169.  
  170.                 // create new DIRINFO for the recursive call
  171.                 PDIRINFO pdiRecurse = malloc(sizeof(DIRINFO));
  172.                 pdiRecurse->dTotalSize = 0;
  173.                 pdiRecurse->dTotalEASize = 0;
  174.                 pdiRecurse->pParent = pdiThis;
  175.                 strcpy(pdiRecurse->szThis, ffb3.achName);
  176.                 strcpy(pdiRecurse->szFullPath, szFile);
  177.                 pdiRecurse->ulRecursionLevel = pdiThis->ulRecursionLevel + 1;
  178.                 // and go!
  179.                 CollectDirectory(pdiRecurse);
  180.                 // add the size of the subdirector(ies) to
  181.                 // our own size
  182.                 pdiThis->dTotalSize += pdiRecurse->dTotalSize;
  183.                 pdiThis->dTotalEASize += pdiRecurse->dTotalEASize;
  184.             }
  185.         } else {
  186.             // regular file: add to total size
  187.             pdiThis->dTotalSize += ffb3.cbFile;
  188.         }
  189.  
  190.         rc = DosFindNext(hdirFindHandle,
  191.                         &ffb3,
  192.                         cbFFB3,
  193.                         &ulFindCount);
  194.  
  195.     } /* endwhile */
  196.  
  197.     DosFindClose(hdirFindHandle);
  198.  
  199.     // now that we're done with this directory:
  200.     // have record core updated with new total size
  201.     if (!fStopThread)
  202.         WinSendMsg(hwndMain, WM_DONEDIRECTORY, (MPARAM)pdiThis, NULL);
  203.  
  204.     // end of function: if this was a recursive call,
  205.     // this will continue collecting files in the parent
  206.     // directory. If this was the "root" call from fntCollect,
  207.     // the Collect thread will then terminate.
  208. };
  209.  
  210. /*
  211.  * fntCollect:
  212.  *      Collect thread, started by fnwpMain below.
  213.  *      The main window can always determine whether
  214.  *      the Collect thread is running by checking
  215.  *      whether (tidCollect != 0). There is never
  216.  *      more than one "instance" of this thread running.
  217.  */
  218.  
  219. void _System fntCollect(ULONG ulDummy)
  220. {
  221.     // we need a msg queue for WinSendMsg above
  222.     if (!(habCollect = WinInitialize(0)))
  223.         return;
  224.     if (!(hmqCollect = WinCreateMsgQueue(habCollect, 0)))
  225.         return;
  226.  
  227.     // low priority?
  228.     DosSetPriority(PRTYS_THREAD,
  229.         (Settings.LowPriority) ? PRTYC_IDLETIME : PRTYC_REGULAR,
  230.         0, 0);
  231.  
  232.     // prepare "root" DIRINFO structure for CollectDirectory()
  233.     pdiRoot = (PDIRINFO)malloc(sizeof(DIRINFO));
  234.     if (pdiRoot) {
  235.         pdiRoot->pParent = NULL;
  236.         pdiRoot->dTotalSize = 0;
  237.         pdiRoot->dTotalEASize = 0;
  238.         strcpy(pdiRoot->szThis, szRootDir);
  239.         strcpy(pdiRoot->szFullPath, szRootDir);
  240.         pdiRoot->ulRecursionLevel = 1;
  241.         CollectDirectory(pdiRoot);
  242.     }
  243.  
  244.     // report that we're done completely
  245.     if (!fStopThread)
  246.         WinPostMsg(hwndMain, WM_DONEWITHALL, NULL, NULL);
  247.  
  248.     WinDestroyMsgQueue(hmqCollect);
  249.     WinTerminate(habCollect);
  250.     tidCollect = 0;
  251. }
  252.  
  253. /********************************************************************
  254.  *                                                                  *
  255.  *   Main thread                                                    *
  256.  *                                                                  *
  257.  ********************************************************************/
  258.  
  259. /*
  260.  * Cleanup:
  261.  *      deallocates all memory and cleans up record cores.
  262.  *      Also recurses; for initial call, preccParent
  263.  *      must be NULL.
  264.  */
  265.  
  266. VOID Cleanup(HWND hwndCnr, PSIZERECORDCORE preccParent)
  267. {
  268.     PSIZERECORDCORE precc2 = preccParent;
  269.  
  270.     do {
  271.         precc2 =
  272.             (PSIZERECORDCORE)WinSendMsg(hwndCnr,
  273.                     CM_QUERYRECORD,
  274.                     // the following ugly code does the following:
  275.                     // if this is the "root" call: get first child recc.
  276.                     // For recursive calls: get next child recc.
  277.                     (MPARAM)((preccParent) // "root" call?
  278.                                 ? preccParent
  279.                                 : precc2), // don't care
  280.                     MPFROM2SHORT(
  281.                             ((preccParent) // "root" call?
  282.                                 ? CMA_FIRSTCHILD
  283.                                 : CMA_FIRST
  284.                         ),
  285.                         CMA_ITEMORDER)
  286.                     );
  287.         if ((precc2) && ((ULONG)precc2 != -1))
  288.         {
  289.             Cleanup(hwndCnr, precc2);
  290.             _Pmpf(("Removing %s", precc2->pdi->szRecordText));
  291.             if (precc2->pdi)
  292.                 free(precc2->pdi);
  293.             WinSendMsg(hwndCnr,
  294.                     CM_REMOVERECORD,
  295.                     &precc2,
  296.                     MPFROM2SHORT(1,   // remove one recc
  297.                         CMA_FREE));
  298.         }
  299.     } while ((precc2) && ((ULONG)precc2 != -1));
  300.  
  301.     if (preccParent == NULL)
  302.         WinSendMsg(hwndCnr,
  303.                 CM_INVALIDATERECORD,
  304.                 NULL,
  305.                 MPFROM2SHORT(0, CMA_REPOSITION));
  306. }
  307.  
  308. /*
  309.  * winhCnrAllocRecord:
  310.  *      this is a shortcut to allocating memory for
  311.  *      a container record. Returns the recc.
  312.  *          ULONG cbrecc    total size of your record core
  313.  *                          or sizeof(RECORDCORE), if you have
  314.  *                          no additional recc data
  315.  */
  316.  
  317. PRECORDCORE winhCnrAllocRecord(HWND hwndCnr, ULONG cbrecc)
  318. {
  319.     PRECORDCORE      precc;
  320.     precc = (PRECORDCORE)WinSendMsg(hwndCnr, CM_ALLOCRECORD,
  321.             (MPARAM)(cbrecc-sizeof(RECORDCORE)),
  322.             (MPARAM)1);
  323.     precc->cb = cbrecc;
  324.     return (precc);
  325. }
  326.  
  327. /*
  328.  * winhCnrInsertRecord:
  329.  *      shortcut to inserting a previously allocated recc
  330.  *      into a container. This func returns the recordcore.
  331.  *      Params:
  332.  *          HWND hwndCnr    the container window
  333.  *          PRECORDCORE preccParent
  334.  *                          for tree views: parent record
  335.  *          PSZ pszText     text for record
  336.  *          ULONG flRecordAttr
  337.  *                  recc flags, as explained in pmstddlg.h:
  338.  *                          CRA_SELECTED       record is selected
  339.  *                          CRA_TARGET         record has target emphasis
  340.  *                          CRA_CURSORED       cursor is on the record
  341.  *                          CRA_INUSE          record has in-use emphasis
  342.  *                          CRA_FILTERED       record has been filtered
  343.  *                          CRA_DROPONABLE     record can be dropped on
  344.  *                          CRA_RECORDREADONLY record is read-only
  345.  *                          CRA_EXPANDED       record is expanded
  346.  *                          CRA_COLLAPSED      record is collapsed
  347.  *                          CRA_PICKED         record picked (Lazy Drag)
  348.  *                  plus the following undocumented:
  349.  *                          CRA_IGNORE
  350.  *                          CRA_SOURCE
  351.  *                          CRA_DISABLED
  352.  *                          CRA_OWNERFREE
  353.  *                          CRA_OWNERDRAW
  354.  */
  355.  
  356. PRECORDCORE winhCnrInsertRecord(HWND hwndCnr, PRECORDCORE preccParent, PRECORDCORE precc,
  357.             PSZ pszText, ULONG flRecordAttr, BOOL fInvalidate)
  358. {
  359.     RECORDINSERT     ri;
  360.  
  361.     if (precc) {
  362.         // RECORDCORE stuff
  363.         precc->flRecordAttr = flRecordAttr;
  364.         precc->preccNextRecord = NULL;
  365.         precc->pszIcon = pszText;
  366.         precc->pszText = pszText;
  367.         precc->pszName = pszText;
  368.         precc->pszTree = pszText;
  369.         precc->hptrIcon = NULLHANDLE;
  370.  
  371.         // setup RECORDINSERT struct
  372.         ri.cb = sizeof(RECORDINSERT);
  373.         ri.pRecordOrder = (PRECORDCORE)CMA_END;
  374.         ri.pRecordParent = (PRECORDCORE)preccParent;
  375.         ri.zOrder = CMA_TOP;
  376.         ri.fInvalidateRecord = fInvalidate;
  377.         ri.cRecordsInsert = 1;
  378.  
  379.         // printf("  Sending CM_INSERTRECORD\n");
  380.         WinSendMsg(hwndCnr, CM_INSERTRECORD, (MPARAM)precc, (MPARAM)&ri);
  381.     }
  382.     return (precc);
  383. }
  384.  
  385. /*
  386.  * winhCnrScrollToRecord:
  387.  *      scrolls a given container control to make a given
  388.  *      record visible; returns TRUE if actual scrolling was
  389.  *      performed, FALSE if not neccessary or on errors.
  390.  *      Parameters:
  391.  *          HWND hwndCnr     container window
  392.  *          PRECORDCORE precc the record to scroll to; this can also
  393.  *                           be a PMINIRECORDCORE, we don't care
  394.  *          ULONG fsExtent   determines the record core coordinates to
  395.  *                           take into account (ORed):
  396.  *                           CMA_ICON    icon rectangle
  397.  *                           CMA_TEXT    text rectangle
  398.  *                           CMA_TREEICON "plus" sign in tree view
  399.  *          BOOL KeepParent  for tree views only: keep parent record of
  400.  *                           the record visible; in case pRec is not
  401.  *                           currently visible, we will scroll so that
  402.  *                           the parent record is at the top of the
  403.  *                           visible records (Win95 style)
  404.  *      Returns:
  405.  *          0           scrolled
  406.  *          >0          not scrolled for the following reason:
  407.  *                  1       record rectangle query failed (error)
  408.  *                  2       cnr viewport query failed (error)
  409.  *                  3       record is already visible
  410.  *                  4       cnrinfo query failed (error)
  411.  *                  5       parent record rectangle query failed (error)
  412.  *      This function an improved version of the one (W)(C) Dan Libby, found at
  413.  *      http://zebra.asta.fh-weingarten.de/os2/Snippets/Howt6364.HTML
  414.  *      Improvements (C) 1998 Ulrich Möller.
  415.  */
  416.  
  417. ULONG winhCnrScrollToRecord(HWND hwndCnr, PRECORDCORE pRec, ULONG fsExtent, BOOL KeepParent)
  418. {
  419.     QUERYRECORDRECT qRect, qRect2;
  420.     RECTL           rclRecord, rclParentRecord, rclCnr, rclCnr2;
  421.     POINTL          ptlRecord, ptlParentRecord;
  422.     CNRINFO         CnrInfo;
  423.     HAB             hab = WinQueryAnchorBlock(hwndCnr);
  424.     BOOL            KeepParent2;
  425.     LONG            lYOfs;
  426.  
  427.     qRect.cb = sizeof(qRect);
  428.     qRect.pRecord = (PRECORDCORE)pRec;
  429.     qRect.fsExtent = fsExtent;
  430.  
  431.     // query record location and size of container
  432.     if (!WinSendMsg(hwndCnr, CM_QUERYRECORDRECT, &rclRecord, &qRect))
  433.         return 1;
  434.     if (!WinSendMsg(hwndCnr, CM_QUERYVIEWPORTRECT, &rclCnr,
  435.                     MPFROM2SHORT(CMA_WINDOW, FALSE)) )
  436.         return 2;
  437.  
  438.     // check if left bottom point of pRec is currently visible in container
  439.     ptlRecord.x = (rclRecord.xLeft);
  440.     ptlRecord.y = (rclRecord.yBottom);
  441.     // ptlRecord.x = (rclRecord.xLeft + rclRecord.xRight) / 2;
  442.     // ptlRecord.y = (rclRecord.yBottom + rclRecord.yTop) / 2;
  443.     if (WinPtInRect(hab, &rclCnr, &ptlRecord))
  444.          return 3;
  445.  
  446.     if (KeepParent)
  447.         if (!WinSendMsg(hwndCnr, CM_QUERYCNRINFO, (MPARAM)&CnrInfo, (MPARAM)sizeof(CnrInfo)))
  448.             return 4;
  449.         else KeepParent2 = (CnrInfo.flWindowAttr & CV_TREE);
  450.     else KeepParent2 = FALSE;
  451.  
  452.     // calculate offset to scroll to make pRec visible
  453.     lYOfs = (rclCnr.yBottom - rclRecord.yBottom)    // this would suffice
  454.           + (rclRecord.yTop - rclRecord.yBottom);  // but we make the next rcl visible too
  455.  
  456.     if (KeepParent2) {
  457.         qRect2.cb = sizeof(qRect2);
  458.         qRect2.pRecord = (PRECORDCORE)WinSendMsg(hwndCnr, CM_QUERYRECORD,
  459.                 (MPARAM)pRec, MPFROM2SHORT(CMA_PARENT, CMA_ITEMORDER));
  460.         qRect2.fsExtent = fsExtent;
  461.  
  462.         // now query PARENT record location and size of container
  463.         if (!WinSendMsg(hwndCnr, CM_QUERYRECORDRECT, &rclParentRecord, &qRect2))
  464.             return 5;
  465.  
  466.         ptlParentRecord.x = (rclParentRecord.xLeft);
  467.         ptlParentRecord.y = (rclParentRecord.yTop);
  468.         // ptlParentRecord.x = (rclParentRecord.xLeft + rclParentRecord.xRight) / 2;
  469.         // ptlParentRecord.y = (rclParentRecord.yBottom + rclParentRecord.yTop) / 2;
  470.         rclCnr2 = rclCnr;
  471.         WinOffsetRect(hab, &rclCnr2, 0, -lYOfs);
  472.         // if ( (rclParentRecord.yBottom - rclRecord.yBottom) > (rclCnr.yTop - rclCnr.yBottom) )
  473.         if (!(WinPtInRect(hab, &rclCnr2, &ptlParentRecord)))
  474.         {
  475.             lYOfs = (rclCnr.yTop - rclParentRecord.yTop) // this would suffice
  476.                   - (rclRecord.yTop - rclRecord.yBottom);  // but we make the previous rcl visible too
  477.         }
  478.     }
  479.  
  480.     if (!KeepParent2)
  481.         WinPostMsg(hwndCnr, CM_SCROLLWINDOW,
  482.                 (MPARAM)CMA_HORIZONTAL,
  483.                 (MPARAM)(rclRecord.xLeft - rclCnr.xLeft));
  484.     WinPostMsg(hwndCnr, CM_SCROLLWINDOW,
  485.             (MPARAM)CMA_VERTICAL,
  486.             (MPARAM)lYOfs);
  487.  
  488.     return 0;
  489. }
  490.  
  491. /*
  492.  * fnCompareName:
  493.  *      comparison func for container sort by name
  494.  */
  495.  
  496. SHORT EXPENTRY fnCompareName(PRECORDCORE pmrc1, PRECORDCORE pmrc2, PVOID pStorage)
  497. {
  498.     HAB habDesktop = WinQueryAnchorBlock(HWND_DESKTOP);
  499.     pStorage = pStorage; // to keep the compiler happy
  500.     if ((pmrc1) && (pmrc2))
  501.         if ((pmrc1->pszIcon) && (pmrc2->pszIcon))
  502.             switch (WinCompareStrings(habDesktop, 0, 0,
  503.                 pmrc1->pszIcon, pmrc2->pszIcon, 0))
  504.             {
  505.                 case WCS_LT: return (-1);
  506.                 case WCS_GT: return (1);
  507.             }
  508.  
  509.     return (0);
  510. }
  511.  
  512. /*
  513.  * fnCompareSize:
  514.  *      comparison func for container sort by file size
  515.  */
  516.  
  517. SHORT EXPENTRY fnCompareSize(PSIZERECORDCORE pmrc1, PSIZERECORDCORE pmrc2, PVOID pStorage)
  518. {
  519.     pStorage = pStorage; // to keep the compiler happy
  520.     if ((pmrc1) && (pmrc2))
  521.         if ((pmrc1->pdi) && (pmrc2->pdi))
  522.             if (pmrc1->pdi->dTotalSize > pmrc2->pdi->dTotalSize)
  523.                 return (-1);
  524.             else if (pmrc1->pdi->dTotalSize < pmrc2->pdi->dTotalSize)
  525.                 return (1);
  526.  
  527.     return (0);
  528. }
  529.  
  530. /*
  531.  * fnCompareEASize:
  532.  *      comparison func for container sort by ea size
  533.  */
  534.  
  535. SHORT EXPENTRY fnCompareEASize(PSIZERECORDCORE pmrc1, PSIZERECORDCORE pmrc2, PVOID pStorage)
  536. {
  537.     pStorage = pStorage; // to keep the compiler happy
  538.     if ((pmrc1) && (pmrc2))
  539.         if ((pmrc1->pdi) && (pmrc2->pdi))
  540.             if (pmrc1->pdi->dTotalEASize > pmrc2->pdi->dTotalEASize)
  541.                 return (-1);
  542.             else if (pmrc1->pdi->dTotalEASize < pmrc2->pdi->dTotalEASize)
  543.                 return (1);
  544.  
  545.     return (0);
  546. }
  547.  
  548. /*
  549.  * winhCenterWindow:
  550.  *      centers a window within its parent window.
  551.  *      The window should not be visible to avoid flickering.
  552.  */
  553.  
  554. void winhCenterWindow(HWND hwnd)
  555. {
  556.    RECTL rclParent;
  557.    RECTL rclWindow;
  558.  
  559.    WinQueryWindowRect(hwnd, &rclWindow);
  560.    WinQueryWindowRect(WinQueryWindow(hwnd, QW_PARENT), &rclParent);
  561.  
  562.    rclWindow.xLeft   = (rclParent.xRight - rclWindow.xRight) / 2;
  563.    rclWindow.yBottom = (rclParent.yTop   - rclWindow.yTop  ) / 2;
  564.  
  565.    WinSetWindowPos(hwnd, NULLHANDLE, rclWindow.xLeft, rclWindow.yBottom,
  566.                     0, 0, SWP_MOVE);
  567. }
  568.  
  569. /*
  570.  * fnwpMain:
  571.  *      dlg func for main window. This does all the
  572.  *      processing after main() has called WinProcessDlg.
  573.  *      This mainly does the following:
  574.  *      --  update the container by reacting to msgs from
  575.  *          the Collect thread
  576.  *      --  handle cnr context menus
  577.  *      --  handle cnr drag'n'drop
  578.  *      --  handle dialog resizing/minimizing etc.
  579.  */
  580.  
  581. // shortcuts to dlg controls
  582. HWND        hwndCnr, hwndText, hwndIcon, hwndClose, hwndClear;
  583.  
  584. // thousands separator from "Country" object
  585. CHAR        szThousand[10];
  586.  
  587. // for container scrolling
  588. PRECORDCORE preccScrollTo = NULL;
  589.  
  590. // some storage for between dragging and dropping
  591. BOOL        fDnDValid = FALSE;
  592. CHAR        szDir[CCHMAXPATH];
  593. CHAR        szFile[CCHMAXPATH];
  594.  
  595. // is window minimized?
  596. BOOL        fMinimized = FALSE;
  597.  
  598. MRESULT EXPENTRY fnwpMain(HWND hwndDlg, ULONG msg, MPARAM mp1, MPARAM mp2)
  599. {
  600.     MRESULT mrc = 0;
  601.  
  602.     switch (msg) {
  603.  
  604.         /*
  605.          * WM_INITDLG:
  606.          *      Dialog wnds don't get WM_CREATE, they get
  607.          *      WM_INITDLG instead.
  608.          *      setup all the controls, mainly the cnr.
  609.          */
  610.  
  611.         case WM_INITDLG: {
  612.             CNRINFO CnrInfo;
  613.  
  614.             // remember wnd handles for later
  615.             hwndIcon = WinWindowFromID(hwndDlg, ID_TSDI_ICON);
  616.             hwndText = WinWindowFromID(hwndDlg, ID_TSDI_TEXT1);
  617.             hwndCnr = WinWindowFromID(hwndDlg, ID_TSDI_CNR);
  618.             hwndClear = WinWindowFromID(hwndDlg, DID_CLEAR);
  619.             hwndClose = WinWindowFromID(hwndDlg, DID_OK);
  620.  
  621.             // setup the container
  622.             WinSendMsg(hwndCnr, CM_QUERYCNRINFO, &CnrInfo, (MPARAM)sizeof(CnrInfo));
  623.             // don't sort initially, because sort-by-size doesn't
  624.             // work until we have all the directory sizes. Instead,
  625.             // we add all the containers to the end of the list
  626.             // initially, which also reduces cnr repainting. The
  627.             // list is only sorted after processing is done completely.
  628.             CnrInfo.pSortRecord = NULL;
  629.             // switch to tree view
  630.             CnrInfo.flWindowAttr = CV_TREE | CV_TEXT | CA_TREELINE;
  631.             CnrInfo.cxTreeIndent = 25;
  632.             WinSendMsg(hwndCnr, CM_SETCNRINFO,
  633.                     &CnrInfo,
  634.                     (MPARAM)( /* CMA_PSORTRECORD | */ CMA_FLWINDOWATTR | CMA_CXTREEINDENT));
  635.  
  636.             mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
  637.  
  638.             // get the thousands separator for later; this
  639.             // is what is specified in the "Country" object
  640.             PrfQueryProfileString(HINI_USER, "PM_Default_National", "sThousand",
  641.                     ",", szThousand, sizeof(szThousand)-1);
  642.  
  643.             // initiate processing (below)
  644.             WinPostMsg(hwndDlg, WM_START, 0, 0);
  645.         break; }
  646.  
  647.         /*
  648.          * WM_START:
  649.          *      start collect thread. This is posted
  650.          *      both by WM_INITDLG above and after folders have
  651.          *      been dropped on the container (CN_DROP below)
  652.          */
  653.  
  654.         case WM_START:
  655.             WinSetWindowText(hwndText, szRootDir);
  656.             WinEnableWindow(hwndClear, FALSE);
  657.             fStopThread = FALSE;
  658.             DosCreateThread(&tidCollect, fntCollect, 0,
  659.                     CREATE_READY, 4*65536);
  660.             WinSetActiveWindow(HWND_DESKTOP, hwndDlg);
  661.                 // needed when something has been dropped
  662.         break;
  663.  
  664.         /*
  665.          * WM_BEGINDIRECTORY:
  666.          *      posted by CollectDirectory (in the Collect
  667.          *      thread) when it's starting to work on a directory.
  668.          *      We will insert the record core then, which
  669.          *      initially displays "Working..."
  670.          */
  671.  
  672.         case WM_BEGINDIRECTORY: {
  673.  
  674.             // the DIRINFO struct is passed in mp1 by
  675.             // the Collect thread
  676.             PDIRINFO pdi = (PDIRINFO)mp1;
  677.  
  678.             // allocate memory for record core
  679.             pdi->precc = winhCnrAllocRecord(hwndCnr, sizeof(SIZERECORDCORE));
  680.  
  681.             // recc text
  682.             sprintf(pdi->szRecordText, "%s: working...", pdi->szThis);
  683.  
  684.              _Pmpf(("  WM_BEGINDIRECTORY %s", pdi->szRecordText));
  685.              _Pmpf(("    Level: %d", pdi->ulRecursionLevel));
  686.  
  687.             if (pdi->precc) {
  688.                 PSIZERECORDCORE preccParent = NULL,
  689.                                 preccNew;
  690.  
  691.                 // store reverse pointer; DIRINFO points
  692.                 // to the record core, the recc points
  693.                 // to DIRINFO (check treesize.h)
  694.                 ((PSIZERECORDCORE)(pdi->precc))->pdi = pdi;
  695.                 ((PSIZERECORDCORE)(pdi->precc))->fDisplayValid = FALSE;
  696.  
  697.                 // store parent directory
  698.                 if (pdi->pParent)
  699.                     preccParent = (PSIZERECORDCORE)pdi->pParent->precc;
  700.  
  701.                 // insert recc into container
  702.                 preccNew = (PSIZERECORDCORE)winhCnrInsertRecord(hwndCnr,
  703.                         // parent for tree view
  704.                         (PRECORDCORE)preccParent,   // NULL for root level
  705.                         // recc to insert
  706.                         (PRECORDCORE)pdi->precc,
  707.                         pdi->szRecordText,
  708.                         // recc attributes:
  709.                         CRA_RECORDREADONLY          // don't make editable
  710.                             // initially, expand tree two levels deep
  711.                             | ((pdi->ulRecursionLevel < 3)
  712.                                 ? CRA_EXPANDED
  713.                                 : CRA_COLLAPSED),
  714.                             // invalidate (repaint) only if parent is expanded;
  715.                             // this avoids excessive cnr repaint
  716.                             (preccParent)
  717.                                 ? (preccParent->recc.flRecordAttr & CRA_EXPANDED)
  718.                                 : TRUE  // invalidate for root level
  719.                         );
  720.  
  721.                 // select recc if it's the root recc;
  722.                 // in Tree view, this automatically deselects
  723.                 // the previous selection
  724.                 if (pdi->ulRecursionLevel == 1)
  725.                     WinSendMsg(hwndCnr,
  726.                         CM_SETRECORDEMPHASIS,
  727.                         preccNew,
  728.                         MPFROM2SHORT(
  729.                             // select or deselect flag
  730.                             TRUE,
  731.                             CRA_SELECTED
  732.                         ));
  733.             }
  734.         break; }
  735.  
  736.         /*
  737.          * WM_DONEDIRECTORY:
  738.          *      posted by CollectDirectory when processing
  739.          *      of a directory and all of its subdirectories
  740.          *      is done. We will update the recc to show
  741.          *      the dir's size.
  742.          *      When a recc text changes, the record core
  743.          *      _always_ needs to be invalidated, or the
  744.          *      cnr displays garbage. To avoid excessive
  745.          *      cnr repainting, we try to do this economically.
  746.          */
  747.  
  748.         case WM_DONEDIRECTORY: {
  749.             CHAR szSize[30];
  750.             CHAR szEASize[30];
  751.  
  752.             // the DIRINFO struct is passed in mp1 by
  753.             // the Collect thread
  754.             PDIRINFO pdi = (PDIRINFO)mp1;
  755.  
  756.             // recc's parent record
  757.             PRECORDCORE preccParent =
  758.                             (pdi->pParent)
  759.                                 ? pdi->pParent->precc
  760.                                 : NULL;
  761.  
  762.             // invalidate (repaint) record only if the
  763.             // parent recc is expanded; for collapsed trees,
  764.             // this repaint is done only when the tree is
  765.             // expanded manually (CN_EXPAND below) to avoid
  766.             // excessive cnr repaint
  767.             BOOL fInvalidate = (preccParent)
  768.                                 ? (preccParent->flRecordAttr & CRA_EXPANDED)
  769.                                 : TRUE;
  770.  
  771.             // new recc title:
  772.             sprintf(pdi->szRecordText, "%s (%s %s",
  773.                                 // title
  774.                                     pdi->szThis,
  775.                                 // value
  776.                                     strhThousandsDouble(szSize,
  777.                                         (Settings.ulSizeDisplay == SD_BYTES)
  778.                                                 ? pdi->dTotalSize
  779.                                             : (Settings.ulSizeDisplay == SD_KBYTES)
  780.                                                 ? ((pdi->dTotalSize + 512) / 1024)
  781.                                             : ((pdi->dTotalSize + (512*1024)) / 1024 / 1024),
  782.                                         szThousand[0]),
  783.                                 // "bytes" string
  784.                                     (Settings.ulSizeDisplay == SD_BYTES)
  785.                                             ? "bytes"
  786.                                         : (Settings.ulSizeDisplay == SD_KBYTES)
  787.                                             ? "KBytes"
  788.                                         : "MBytes");
  789.             if (Settings.CollectEAs)
  790.                 sprintf(pdi->szRecordText + strlen(pdi->szRecordText),
  791.                                 ", %s %s EAs",
  792.                                 strhThousandsDouble(szSize,
  793.                                         (Settings.ulSizeDisplay == SD_BYTES)
  794.                                                 ? pdi->dTotalEASize
  795.                                             : (Settings.ulSizeDisplay == SD_KBYTES)
  796.                                                 ? ((pdi->dTotalEASize + 512) / 1024)
  797.                                             : ((pdi->dTotalSize + (512*1024)) / 1024 / 1024),
  798.                                         szThousand[0]),
  799.                                     (Settings.ulSizeDisplay == SD_BYTES)
  800.                                             ? "bytes"
  801.                                         : (Settings.ulSizeDisplay == SD_KBYTES)
  802.                                             ? "KBytes"
  803.                                         : "MBytes");
  804.  
  805.             strcat(pdi->szRecordText, ")");
  806.              _Pmpf(("  WM_DONEDIRECTORY %s", pdi->szRecordText));
  807.  
  808.             if (fInvalidate) {
  809.                 WinSendMsg(hwndCnr,
  810.                         CM_INVALIDATERECORD,
  811.                         (MPARAM)&(pdi->precc),
  812.                         MPFROM2SHORT(1,     // one record only
  813.                                 CMA_TEXTCHANGED));
  814.                 ((PSIZERECORDCORE)pdi->precc)->fDisplayValid = TRUE;
  815.             }
  816.         break; }
  817.  
  818.         /*
  819.          * WM_DONEWITHALL:
  820.          *      posted by the Collect thread right before
  821.          *      it is ending. We will now sort the whole cnr.
  822.          */
  823.  
  824.         case WM_DONEWITHALL: {
  825.             WinEnableWindow(hwndClear, TRUE);
  826.  
  827.             // sort cnr; ulSort is a global var containing
  828.             // either SV_SIZE or SV_EASIZE or SV_NAME
  829.             WinSendMsg(hwndCnr,
  830.                         CM_SORTRECORD,
  831.                         ((Settings.ulSort == SV_SIZE) ? (MPARAM)fnCompareSize
  832.                         : (Settings.ulSort == SV_EASIZE) ? (MPARAM)fnCompareEASize
  833.                         : (MPARAM)fnCompareName),
  834.                         NULL);
  835.  
  836.             // scroll to make root record visible
  837.             preccScrollTo = pdiRoot->precc;
  838.             WinStartTimer(WinQueryAnchorBlock(hwndDlg),
  839.                     hwndDlg,
  840.                     2,
  841.                     100);
  842.         break; }
  843.  
  844.         /*
  845.          * WM_CONTROL:
  846.          *
  847.          */
  848.  
  849.         case WM_CONTROL: {
  850.             switch (SHORT2FROMMP(mp1)) { // notify code
  851.  
  852.                 /*
  853.                  * CN_ENTER:
  854.                  *      double-click or "Enter" on
  855.                  *      record core: open WPS folder
  856.                  */
  857.  
  858.                 case CN_ENTER: {
  859.                     PNOTIFYRECORDENTER pnre = (PVOID)mp2;
  860.                     if (pnre) {
  861.                         PSIZERECORDCORE precc = (PVOID)pnre->pRecord;
  862.                         if (precc)
  863.                             WinOpenObject(WinQueryObject(precc->pdi->szFullPath),
  864.                                     0,          // == OPEN_DEFAULT
  865.                                     TRUE);
  866.                         // else double click on whitespace, which we'll ignore
  867.                     }
  868.                 break; }
  869.  
  870.                 /*
  871.                  * CN_EXPANDTREE:
  872.                  *      tree view has been expanded:
  873.                  *      do cnr auto-scroll if we're
  874.                  *      not initially filling the cnr,
  875.                  *      and invalidate all the sub-records,
  876.                  *      if they have not been invalidated
  877.                  *      before
  878.                  */
  879.  
  880.                 case CN_EXPANDTREE: {
  881.                     PRECORDCORE precc2 = NULL;
  882.                     BOOL fFirstRun = TRUE,
  883.                          fWindowDisabled = FALSE;
  884.                     preccScrollTo = mp2;
  885.  
  886.                     // loop thru all the child records
  887.                     // and invalidate them
  888.                     do {
  889.                         precc2 =
  890.                             (PRECORDCORE)WinSendMsg(hwndCnr,
  891.                                     CM_QUERYRECORD,
  892.                                     // the following gets either the
  893.                                     // first child recc of the recc
  894.                                     // that has been expanded or the
  895.                                     // next child for consecutive loops
  896.                                     (MPARAM)((fFirstRun)
  897.                                             ? preccScrollTo   // first loop
  898.                                             : precc2),
  899.                                     MPFROM2SHORT(
  900.                                             ((fFirstRun)
  901.                                             ? CMA_FIRSTCHILD  // first loop
  902.                                             : CMA_NEXT
  903.                                         ),
  904.                                         CMA_ITEMORDER)
  905.                                     );
  906.                         fFirstRun = FALSE;
  907.                         if ((precc2) && ((ULONG)precc2 != -1))
  908.                             if (!((PSIZERECORDCORE)precc2)->fDisplayValid)
  909.                             {
  910.                                 if (!fWindowDisabled) {
  911.                                     WinEnableWindowUpdate(hwndCnr, FALSE);
  912.                                     fWindowDisabled = TRUE;
  913.                                 }
  914.                                 WinSendMsg(hwndCnr,
  915.                                         CM_INVALIDATERECORD,
  916.                                         (MPARAM)&precc2,
  917.                                         MPFROM2SHORT(1,     // one record only
  918.                                                 CMA_TEXTCHANGED));
  919.  
  920.                                 ((PSIZERECORDCORE)precc2)->fDisplayValid = TRUE;
  921.                             }
  922.                     } while ((precc2) && ((ULONG)precc2 != -1));
  923.  
  924.                     if (fWindowDisabled)
  925.                         WinEnableWindowUpdate(hwndCnr, TRUE);
  926.  
  927.                     // do cnr auto-scroll
  928.                     WinStartTimer(WinQueryAnchorBlock(hwndDlg),
  929.                             hwndDlg,
  930.                             1,
  931.                             100);
  932.                 break; }
  933.  
  934.                 /*
  935.                  * CN_EMPHASIS:
  936.                  *      selection changed: update title
  937.                  */
  938.  
  939.                 case CN_EMPHASIS: {
  940.                     // get cnr notification struct
  941.                     PNOTIFYRECORDEMPHASIS pnre = (PNOTIFYRECORDEMPHASIS)mp2;
  942.  
  943.                     if (pnre)
  944.                         if (    (pnre->fEmphasisMask & CRA_SELECTED)
  945.                              && (pnre->pRecord)
  946.                            )
  947.                         {
  948.                             WinSetWindowText(hwndText,
  949.                                     ((PSIZERECORDCORE)(pnre->pRecord))
  950.                                         ->pdi->szFullPath);
  951.                         }
  952.                 break; }
  953.  
  954.                 /*
  955.                  * CN_CONTEXTMENU:
  956.                  *      context menu requested
  957.                  */
  958.  
  959.                 case CN_CONTEXTMENU: {
  960.                     POINTL ptl;
  961.                     HWND   hPopupMenu;
  962.                     ULONG ulFlagsSelected = 0;
  963.                     CHAR szDummy[2000];
  964.  
  965.                     // mp2 contains the recc upon which the context
  966.                     // menu was requested or NULL for cnr whitespace
  967.                     PSIZERECORDCORE preccSelected = (PSIZERECORDCORE)mp2;
  968.  
  969.                     // open context menu only on whitespace
  970.                     // of container
  971.                     if (!preccSelected) {
  972.                         // whitespace:
  973.                         hPopupMenu = WinLoadMenu(hwndDlg, NULLHANDLE,
  974.                                 ID_TSM_CONTEXT);
  975.  
  976.                         WinQueryPointerPos(HWND_DESKTOP, &ptl);
  977.                         WinPopupMenu(HWND_DESKTOP, hwndDlg,
  978.                                 hPopupMenu,
  979.                                 (SHORT)ptl.x,
  980.                                 (SHORT)ptl.y,
  981.                                 0,
  982.                                 PU_NONE | PU_MOUSEBUTTON1 | PU_KEYBOARD
  983.                                 | PU_HCONSTRAIN | PU_VCONSTRAIN);
  984.  
  985.                         // check current sort item
  986.                         WinSendMsg(hPopupMenu, MM_SETITEMATTR,
  987.                                 MPFROM2SHORT(
  988.                                         (Settings.ulSort == SV_NAME) ? ID_TSMI_SORTBYNAME
  989.                                         : (Settings.ulSort == SV_EASIZE) ? ID_TSMI_SORTBYEASIZE
  990.                                         : ID_TSMI_SORTBYSIZE
  991.                                     , TRUE),
  992.                                 MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));
  993.  
  994.                         // check size display
  995.                         WinSendMsg(hPopupMenu, MM_SETITEMATTR,
  996.                                 MPFROM2SHORT(
  997.                                         (Settings.ulSizeDisplay == SD_BYTES) ? ID_TSMI_SIZE_BYTES
  998.                                         : (Settings.ulSizeDisplay == SD_KBYTES) ? ID_TSMI_SIZE_KBYTES
  999.                                         : ID_TSMI_SIZE_MBYTES
  1000.                                     , TRUE),
  1001.                                 MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));
  1002.  
  1003.                         // check "Collect EAs"?
  1004.                         if (Settings.CollectEAs)
  1005.                             WinSendMsg(hPopupMenu, MM_SETITEMATTR,
  1006.                                 MPFROM2SHORT(ID_TSMI_COLLECTEAS,
  1007.                                     TRUE),
  1008.                                 MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));
  1009.                         // check "Low priority"?
  1010.                         if (Settings.LowPriority)
  1011.                             WinSendMsg(hPopupMenu, MM_SETITEMATTR,
  1012.                                 MPFROM2SHORT(ID_TSMI_LOWPRTY,
  1013.                                     TRUE),
  1014.                                 MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));
  1015.                     }
  1016.                 break; }
  1017.  
  1018.                 /**********************************************
  1019.                 *                                             *
  1020.                 *  Drag'n'drop                                *
  1021.                 *                                             *
  1022.                 **********************************************/
  1023.  
  1024.                 /*
  1025.                     The container control translates the
  1026.                     PM drag'n'drop messages into container
  1027.                     notifications. This is barely documented,
  1028.                     but still helpful, because the cnr already
  1029.                     gives us container-specific structures
  1030.                     with these notification codes and draws
  1031.                     target emphasis automatically.
  1032.  
  1033.                     For us, two nofifications are important:
  1034.  
  1035.                     --  CN_DRAGOVER is sent while the user
  1036.                         is dragging stuff over the container;
  1037.                         the return value must indicate whether
  1038.                         this is a valid object that may be
  1039.                         dropped;
  1040.                     --  CN_DROP is sent after the user has
  1041.                         released the dragged object (i.e.
  1042.                         when it's dropped). We must then
  1043.                         react to read in the new directory.
  1044.                  */
  1045.  
  1046.                 /*
  1047.                  * CN_DRAGOVER:
  1048.                  *      something's being dragged over the container.
  1049.                  *      Depending on the return value of this message,
  1050.                  *      PM will show the "not allowed" pointer or not.
  1051.                  *      Also, the cnr can automatically give itself
  1052.                  *      target emphasis.
  1053.                  *      Let's check if we're getting a WPS folder and,
  1054.                  *      if so, allow this.
  1055.                  */
  1056.  
  1057.                 case CN_DRAGOVER: {
  1058.                     // container drag info structures
  1059.                     PCNRDRAGINFO pcdi = (PCNRDRAGINFO)mp2;
  1060.                     PDRAGITEM pdi;
  1061.  
  1062.                     // reset global variable for CN_DROP later
  1063.                     fDnDValid = FALSE;
  1064.  
  1065.                     // default return value: do _not_ allow dropping.
  1066.                     // This will result in the "forbidden" pointer
  1067.                     // being drawn by PM. NEVERDROP means that we
  1068.                     // will _never_ accept this object, and PM will
  1069.                     // stop sending messages for this object then.
  1070.                     mrc = MRFROM2SHORT(DOR_NEVERDROP,
  1071.                             DO_DEFAULT);
  1072.  
  1073.                     if (tidCollect)
  1074.                         // accept nothing while Collect thread is working
  1075.                         break;
  1076.                     if (pcdi->pDragInfo->cditem != 1)
  1077.                         // accept no more than one single item at a time;
  1078.                         // we cannot handle more than one directory
  1079.                         break;
  1080.                     if (pcdi->pDragInfo->usOperation != DO_DEFAULT) {
  1081.                         // we don't accept modifier keys (Shift, Ctrl, ...).
  1082.                         // But instead of NEVERDROP, return NODROP,
  1083.                         // which will keep sending msgs instead
  1084.                         // of stopping that
  1085.                         mrc = MRFROM2SHORT(DOR_NODROP, DO_DEFAULT);
  1086.                         break;
  1087.                     }
  1088.                     if (pcdi->pRecord) {
  1089.                         // mouse is over a record core: no,
  1090.                         // we only accept drag on whitespace.
  1091.                         mrc = MRFROM2SHORT(DOR_NODROP, DO_DEFAULT);
  1092.                         break;
  1093.                     }
  1094.  
  1095.                     // Now that we've ruled out the most apparent
  1096.                     // errors, we need to check further:
  1097.  
  1098.                     // get access to the drag'n'drop structures
  1099.                     DrgAccessDraginfo(pcdi->pDragInfo);
  1100.                     // get the item being dragged (PDRAGITEM)
  1101.                     pdi = DrgQueryDragitemPtr(pcdi->pDragInfo, 0);
  1102.  
  1103.                     /*  From PMREF:
  1104.  
  1105.                         typedef struct _DRAGITEM {
  1106.                             HWND     hwndItem;
  1107.                               Window handle of the source of the drag operation.
  1108.                             ULONG    ulItemID;
  1109.                               Information used by the source to identify the object being dragged.
  1110.                             HSTR     hstrType;
  1111.                               String handle of the object type.
  1112.                             HSTR     hstrRMF;
  1113.                               String handle of the rendering mechanism and format.
  1114.                             HSTR     hstrContainerName;
  1115.                               String handle of the name of the container holding the source object.
  1116.                             HSTR     hstrSourceName;
  1117.                               String handle of the name of the source object.
  1118.                             HSTR     hstrTargetName;
  1119.                               String handle of the suggested name of the object at the target.
  1120.                             SHORT    cxOffset;
  1121.                               X-offset from the pointer hot spot to the origin of the image that represents this object.
  1122.                             SHORT    cyOffset;
  1123.                               Y-offset from the pointer hot spot to the origin of the image that represents this object.
  1124.                             USHORT   fsControl;
  1125.                               Source-object control flags.
  1126.                             USHORT   fsSupportedOps;
  1127.                               Direct manipulation operations supported by the source object.
  1128.                         } DRAGITEM;        */
  1129.  
  1130.                     if (pdi) {
  1131.                         // now check render mechanism and format.
  1132.                         // The WPS gives us DRM_OS2FILE for "real" files only,
  1133.                         // but DRM_OBJECT for abstract objects.
  1134.                         // Moreover, the format is DRF_TEXT for all files,
  1135.                         // but folders have DRF_UNKNOWN instead.
  1136.                         // So by checking for DRM_OS2FILE and DRF_UNKNOWN,
  1137.                         // we should get folders only.
  1138.                         // (Unfortunately, this will _not_ work for root folders
  1139.                         // (drive objects), because these are rendered as
  1140.                         // DRM_OBJECT, and since we're not running in the WPS
  1141.                         // process, we cannot get the corresponding SOM object)
  1142.                         if (DrgVerifyRMF(pdi, "DRM_OS2FILE", "DRF_UNKNOWN"))
  1143.                         {
  1144.                             // it's a folder (directory):
  1145.                             // the following gives us the full path of where the
  1146.                             // dragged folder resides in. The PM d'n'd functions always
  1147.                             // use these strange "string handles", which we
  1148.                             // need to translate now.
  1149.                             DrgQueryStrName(pdi->hstrContainerName,
  1150.                                         sizeof(szDir), szDir);
  1151.                                 // e.g. for "F:\OS2\APPS" this would be "F:\OS2\"
  1152.  
  1153.                             // this gives us the filename of the dragged folder
  1154.                             DrgQueryStrName(pdi->hstrSourceName,
  1155.                                         sizeof(szFile), szFile);
  1156.                                 // e.g. for "F:\OS2\APPS" this would be "APPS"
  1157.  
  1158.                             // store flag for CN_DROP below
  1159.                             fDnDValid = TRUE;
  1160.                             // return "allow drop" code
  1161.                             mrc = MRFROM2SHORT(DOR_DROP,
  1162.                                     DO_UNKNOWN);  // means application-specific;
  1163.                                         // that's cool for us, because we don't
  1164.                                         // really want the WPS to start copying
  1165.                                         // or moving anything
  1166.                         }
  1167.                     }
  1168.  
  1169.                     // clean up
  1170.                     DrgFreeDraginfo(pcdi->pDragInfo);
  1171.                 break; }
  1172.  
  1173.                 /*
  1174.                  * CN_DROP:
  1175.                  *      something _has_ now been dropped on the cnr.
  1176.                  */
  1177.  
  1178.                 case CN_DROP: {
  1179.                     // check the global variable which has been set
  1180.                     // by CN_DRAGOVER above:
  1181.                     if (fDnDValid) {
  1182.                         // CN_DRAGOVER above has considered this valid:
  1183.                         // restart the whole gathering process
  1184.                         sprintf(szRootDir, "%s%s", szDir, szFile);
  1185.                         WinPostMsg(hwndDlg, WM_START, NULL, NULL);
  1186.                         fDnDValid = FALSE;
  1187.                     }
  1188.                 break; }
  1189.             }
  1190.  
  1191.  
  1192.         break; }
  1193.  
  1194.         /*
  1195.          * WM_TIMER:
  1196.          *      timer for tree view auto-scroll
  1197.          */
  1198.  
  1199.         case WM_TIMER: {
  1200.             WinStopTimer(WinQueryAnchorBlock(hwndDlg),
  1201.                     hwndDlg,
  1202.                     (ULONG)mp1);
  1203.             if ( ( preccScrollTo->flRecordAttr & CRA_EXPANDED) != 0 ) {
  1204.                 PRECORDCORE     preccLastChild;
  1205.                 // scroll the tree view properly
  1206.                 preccLastChild = WinSendMsg(hwndCnr,
  1207.                         CM_QUERYRECORD,
  1208.                         preccScrollTo,   // expanded PRECORDCORE from CN_EXPANDTREE
  1209.                         MPFROM2SHORT(
  1210.                              ( ((ULONG)mp1 == 1)
  1211.                                 ? CMA_LASTCHILD : CMA_FIRSTCHILD),
  1212.                              CMA_ITEMORDER));
  1213.                 if (preccLastChild) {
  1214.                     // ULONG ulrc;
  1215.                     winhCnrScrollToRecord(hwndCnr,
  1216.                             (PRECORDCORE)preccLastChild,
  1217.                             CMA_TEXT,   // record text rectangle only
  1218.                             TRUE);      // keep parent visible
  1219.                 }
  1220.             }
  1221.         break; }
  1222.  
  1223.         /*
  1224.          * WM_ADJUSTWINDOWPOS:
  1225.          *      sent _while_ a window is being moved
  1226.          *      or resized.
  1227.          *      set minimum wnd size when resizing
  1228.          */
  1229.  
  1230.         case WM_ADJUSTWINDOWPOS: {
  1231.             PSWP pswp = PVOIDFROMMP(mp1);
  1232.             mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
  1233.  
  1234.             // only if window is not minimized, or this
  1235.             // will produce really funny minimized windows
  1236.             if (    ((pswp->fl & (SWP_SIZE | SWP_MINIMIZE)) == SWP_SIZE)
  1237.                  && (!fMinimized)
  1238.                )
  1239.             {
  1240.                 // resizing and not minimize: assert minimum cx/cy
  1241.                 if (pswp->cx < 300)
  1242.                     pswp->cx = 300;
  1243.                 if (pswp->cy < 300)
  1244.                     pswp->cy = 300;
  1245.             }
  1246.         break; }
  1247.  
  1248.         /*
  1249.          * WM_WINDOWPOSCHANGED:
  1250.          *      posted _after_ the window has been moved
  1251.          *      or resized.
  1252.          *      since we have a sizeable dlg, we need to
  1253.          *      update the controls' sizes also, or PM
  1254.          *      will display garbage. This is the trick
  1255.          *      how to use sizeable dlgs, because the do
  1256.          *      _not_ get sent WM_SIZE messages.
  1257.          */
  1258.  
  1259.         case WM_WINDOWPOSCHANGED: {
  1260.             // this msg is passed two SWP structs:
  1261.             // one for the old, one for the new data
  1262.             // (from PM docs)
  1263.             PSWP pswpNew = PVOIDFROMMP(mp1);
  1264.             PSWP pswpOld = pswpNew + 1;
  1265.             BOOL fWasMinimized = fMinimized;
  1266.  
  1267.             if (pswpNew->fl & SWP_MINIMIZE) {
  1268.                 // minimizing: pmref says, we should
  1269.                 // hide all controls, because otherwise
  1270.                 // these get painted upon the minimized icon
  1271.                 fMinimized = TRUE;
  1272.                 WinShowWindow(hwndCnr, FALSE);
  1273.                 WinShowWindow(hwndClose, FALSE);
  1274.                 WinShowWindow(hwndIcon, FALSE);
  1275.                 WinShowWindow(hwndClear, FALSE);
  1276.                 WinShowWindow(hwndText, FALSE);
  1277.             }
  1278.             else if (pswpNew->fl & SWP_RESTORE) {
  1279.                 // un-minimize: do the reverse
  1280.                 fMinimized = FALSE;
  1281.                 WinShowWindow(hwndCnr, TRUE);
  1282.                 WinShowWindow(hwndClose, TRUE);
  1283.                 WinShowWindow(hwndIcon, TRUE);
  1284.                 WinShowWindow(hwndClear, TRUE);
  1285.                 WinShowWindow(hwndText, TRUE);
  1286.             }
  1287.  
  1288.             // resizing and not minimize?
  1289.             if (    (pswpNew->fl & SWP_SIZE)
  1290.                 &&  (!fMinimized)
  1291.                 &&  (!fWasMinimized)
  1292.                )
  1293.             {
  1294.                 SWP swpControl;
  1295.  
  1296.                 // for each control in the dlg, we now adjust
  1297.                 // size and/or position by determining the
  1298.                 // difference between the old and new SWP structs.
  1299.                 LONG        ldcx = (pswpNew->cx - pswpOld->cx),
  1300.                                     // width delta
  1301.                             ldcy = (pswpNew->cy - pswpOld->cy);
  1302.                                     // height delta
  1303.  
  1304.                 // container: adjust width and height
  1305.                 WinQueryWindowPos(hwndCnr,
  1306.                         &swpControl);
  1307.                 WinSetWindowPos(hwndCnr,
  1308.                         NULLHANDLE, 0, 0, // don't care
  1309.                         swpControl.cx + ldcx,
  1310.                         swpControl.cy + ldcy,
  1311.                         SWP_SIZE);
  1312.  
  1313.                 // top text: adjust width and ypos
  1314.                 WinQueryWindowPos(hwndText,
  1315.                         &swpControl);
  1316.                 WinSetWindowPos(hwndText,
  1317.                         NULLHANDLE,
  1318.                         swpControl.x,
  1319.                         swpControl.y + ldcy,
  1320.                         swpControl.cx + ldcx,
  1321.                         swpControl.cy,
  1322.                         SWP_MOVE | SWP_SIZE);
  1323.  
  1324.                 // icon: adjust ypos only
  1325.                 WinQueryWindowPos(hwndIcon,
  1326.                         &swpControl);
  1327.                 WinSetWindowPos(hwndIcon,
  1328.                         NULLHANDLE,
  1329.                         swpControl.x,
  1330.                         swpControl.y + ldcy,
  1331.                         0, 0,
  1332.                         SWP_MOVE);
  1333.  
  1334.                 // "Clear" button:
  1335.                 // adjust xpos only
  1336.                 WinQueryWindowPos(hwndClear,
  1337.                         &swpControl);
  1338.                 WinSetWindowPos(hwndClear,
  1339.                         NULLHANDLE,
  1340.                         swpControl.x + ldcx,
  1341.                         swpControl.y,
  1342.                         0, 0,
  1343.                         SWP_MOVE);
  1344.             }
  1345.             mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
  1346.         break; }
  1347.  
  1348.         /*
  1349.          * WM_COMMAND:
  1350.          *      button or menu commands.
  1351.          */
  1352.  
  1353.         case WM_COMMAND: {
  1354.             switch ((ULONG)mp1) {
  1355.  
  1356.                 case DID_CLEAR:
  1357.                     Cleanup(hwndCnr, NULL);
  1358.                 break;
  1359.  
  1360.                 case ID_TSMI_SORTBYNAME: {
  1361.                     WinSendMsg(hwndCnr,
  1362.                                 CM_SORTRECORD,
  1363.                                 (MPARAM)fnCompareName,
  1364.                                 NULL);
  1365.                     Settings.ulSort = SV_NAME;
  1366.                     PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
  1367.                             &Settings,
  1368.                             sizeof(Settings));
  1369.                 break; }
  1370.  
  1371.                 case ID_TSMI_SORTBYSIZE: {
  1372.                     WinSendMsg(hwndCnr,
  1373.                                 CM_SORTRECORD,
  1374.                                 (MPARAM)fnCompareSize,
  1375.                                 NULL);
  1376.                     Settings.ulSort = SV_SIZE;
  1377.                     PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
  1378.                             &Settings,
  1379.                             sizeof(Settings));
  1380.                 break; }
  1381.  
  1382.                 case ID_TSMI_SORTBYEASIZE: {
  1383.                     WinSendMsg(hwndCnr,
  1384.                                 CM_SORTRECORD,
  1385.                                 (MPARAM)fnCompareEASize,
  1386.                                 NULL);
  1387.                     Settings.ulSort = SV_EASIZE;
  1388.                     PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
  1389.                             &Settings,
  1390.                             sizeof(Settings));
  1391.                 break; }
  1392.  
  1393.                 case ID_TSMI_SIZE_BYTES: {
  1394.                     Settings.ulSizeDisplay = SD_BYTES;
  1395.                     PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
  1396.                             &Settings,
  1397.                             sizeof(Settings));
  1398.                 break; }
  1399.  
  1400.                 case ID_TSMI_SIZE_KBYTES: {
  1401.                     Settings.ulSizeDisplay = SD_KBYTES;
  1402.                     PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
  1403.                             &Settings,
  1404.                             sizeof(Settings));
  1405.                 break; }
  1406.  
  1407.                 case ID_TSMI_SIZE_MBYTES: {
  1408.                     Settings.ulSizeDisplay = SD_MBYTES;
  1409.                     PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
  1410.                             &Settings,
  1411.                             sizeof(Settings));
  1412.                 break; }
  1413.  
  1414.                 case ID_TSMI_LOWPRTY: {
  1415.                     Settings.LowPriority = !(Settings.LowPriority);
  1416.                     PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
  1417.                             &Settings,
  1418.                             sizeof(Settings));
  1419.  
  1420.                     if (tidCollect)
  1421.                         // collect thread running:
  1422.                         DosSetPriority(PRTYS_THREAD,
  1423.                             (Settings.LowPriority) ? PRTYC_IDLETIME : PRTYC_REGULAR,
  1424.                             0, tidCollect);
  1425.                 break; }
  1426.  
  1427.                 case ID_TSMI_COLLECTEAS: {
  1428.                     Settings.CollectEAs = !(Settings.CollectEAs);
  1429.                     PrfWriteProfileData(HINI_USER, "XFldTreesize", "Settings",
  1430.                             &Settings,
  1431.                             sizeof(Settings));
  1432.                 break; }
  1433.  
  1434.                 case ID_TSMI_PRODINFO: {
  1435.                     // advertise for myself
  1436.                     CHAR szGPLInfo[2000];
  1437.                     HWND hwndInfo = WinLoadDlg(HWND_DESKTOP, hwndDlg,
  1438.                                       WinDefDlgProc,
  1439.                                       NULLHANDLE,
  1440.                                       ID_TSD_PRODINFO,
  1441.                                       NULL);
  1442.  
  1443.                     // load GPL info message into prodinfo MLE
  1444.                     strcpy(szGPLInfo,
  1445.                         "Treesize is part of the XFolder package. XFolder is free software. "
  1446.                        "The full source code is now available. You are welcome to "
  1447.                         "redistribute and/or modify XFolder under the conditions of the "
  1448.                         "GNU General Public License (GPL). XFolder comes with absolutely "
  1449.                         "NO WARRANTY. For details, refer to the \"Notices\" section of "
  1450.                         "the XFolder Online Reference.\n\n"
  1451.                         "Treesize will display either the contents of the directory you "
  1452.                         "have specified on the command line (e.g. \"TREESIZE F:\\OS2\") "
  1453.                         "or, if no dir has been specified, the current directory. "
  1454.                         "\nAfter Treesize has finished collecting the directories, "
  1455.                         "you may drop any additional WPS folder "
  1456.                         "on the existing tree view to have a new tree displayed. "
  1457.                         "\nPress the \"Clear\" button before that to have an empty window. "
  1458.                         "\nDouble-click on any item in the tree view to have the corresponding "
  1459.                         "WPS folder opened in its default view. "
  1460.                         "\nTreesize saves all window parameters (position, colors, fonts) "
  1461.                         "and the sort options you have last specified."
  1462.                         );
  1463.                     WinSetDlgItemText(hwndInfo, ID_TSDI_TEXT1, szGPLInfo);
  1464.                     WinSendDlgItemMsg(hwndInfo, ID_TSDI_TEXT1,
  1465.                             MLM_SETFIRSTCHAR,       // scroll MLE to top
  1466.                             (MPARAM)0,
  1467.                             NULL);
  1468.  
  1469.                     winhCenterWindow(hwndInfo);
  1470.                     WinProcessDlg(hwndInfo);
  1471.                     WinDestroyWindow(hwndInfo);
  1472.                 break; }
  1473.  
  1474.                 default: {
  1475.                     // this includes the "OK" button and
  1476.                     // the "Close" menu item:
  1477.                     // store window params (size, presparams, ...)
  1478.                     // Note that this function only works while the
  1479.                     // window is still visible, so we do this here.
  1480.                     fStopThread = TRUE;
  1481.                     WinStoreWindowPos("XFldTreesize", "WindowPos", hwndDlg);
  1482.                     Cleanup(hwndCnr, NULL);
  1483.                     mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
  1484.                 break; }
  1485.  
  1486.             }
  1487.         break; }
  1488.  
  1489.         default:
  1490.             mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
  1491.     }
  1492.     return (mrc);
  1493. }
  1494.  
  1495. /*
  1496.  * main:
  1497.  *      Program entry point. This loads the main wnd
  1498.  +      as a dlg resource. All the processing is done
  1499.  *      in fnwpMain above, which is running after this
  1500.  *      func has called WinProcessDlg.
  1501.  */
  1502.  
  1503. int main(int argc, char *argv[])
  1504. {
  1505.     HAB         hab;
  1506.     HMQ         hmq;
  1507.     FRAMECDATA  fcd;
  1508.     QMSG        qmsg;
  1509.     BOOL        Proceed = TRUE;
  1510.     ULONG       ul;
  1511.     CHAR        szTitle[400];
  1512.  
  1513.     if (!(hab = WinInitialize(0)))
  1514.         return FALSE;
  1515.  
  1516.     if (!(hmq = WinCreateMsgQueue(hab, 0)))
  1517.         return FALSE;
  1518.  
  1519.     // check command line parameters: in C,
  1520.     // the first parameter is always the full
  1521.     // path of the TREESIZE executable.
  1522.     if (argc == 1) {
  1523.         // one param only: no directory given,
  1524.         // use current dir then.
  1525.         ULONG ulCurrentDisk = 0, ulDriveMap = 0;
  1526.         DosQueryCurrentDisk(&ulCurrentDisk, &ulDriveMap);
  1527.         szRootDir[0] = 'A'+ulCurrentDisk-1;
  1528.         szRootDir[1] = ':';
  1529.         szRootDir[2] = '\\';
  1530.         DosQueryCurrentDir(0, szRootDir+3, &cbRootDir);
  1531.     } else
  1532.         // param given: use this
  1533.         strcpy(szRootDir, argv[1]);
  1534.  
  1535.     if (Proceed) {
  1536.         ULONG       cbSettings = sizeof(Settings);
  1537.         SWCNTRL     swctl;
  1538.         PID         pid;
  1539.         HSWITCH     hswitch;
  1540.         HPOINTER    hIcon = WinLoadPointer(HWND_DESKTOP,
  1541.                                     NULLHANDLE,
  1542.                                     ID_ICON);
  1543.  
  1544.         // this loads the main window. All the processing
  1545.         // is done in fnwpMain.
  1546.         hwndMain = WinLoadDlg(HWND_DESKTOP, HWND_DESKTOP,
  1547.                 fnwpMain,
  1548.                 0, ID_TSD_MAIN,
  1549.                 szRootDir);
  1550.  
  1551.         // append path to title
  1552.         WinQueryWindowText(hwndMain, sizeof(szTitle), szTitle);
  1553.         sprintf(szTitle+strlen(szTitle), " - %s", szRootDir);
  1554.         WinSetWindowText(hwndMain, szTitle);
  1555.  
  1556.         // give the dlg an icon
  1557.         WinSendMsg(hwndMain,
  1558.                    WM_SETICON,
  1559.                    (MPARAM)hIcon,
  1560.                     NULL);
  1561.  
  1562.         // add ourselves to the tasklist
  1563.         swctl.hwnd = hwndMain;                 // window handle
  1564.         swctl.hwndIcon = hIcon;                // icon handle
  1565.         swctl.hprog = NULLHANDLE;              // program handle
  1566.         WinQueryWindowProcess(hwndMain, &(swctl.idProcess), NULL);
  1567.                                                // process identifier
  1568.         swctl.idSession = 0;                   // session identifier ?
  1569.         swctl.uchVisibility = SWL_VISIBLE;     // visibility
  1570.         swctl.fbJump = SWL_JUMPABLE;           // jump indicator
  1571.         WinQueryWindowText(hwndMain, sizeof(swctl.szSwtitle), (PSZ)&swctl.szSwtitle);
  1572.         swctl.bProgType = PROG_DEFAULT;        // program type
  1573.  
  1574.         hswitch = WinAddSwitchEntry(&swctl);
  1575.  
  1576.         // get settings in OS2.INI
  1577.         WinRestoreWindowPos("XFldTreesize", "WindowPos", hwndMain);
  1578.         Settings.ulSort = SV_SIZE;
  1579.         Settings.CollectEAs = TRUE;
  1580.         Settings.ulSizeDisplay = SD_KBYTES;
  1581.         Settings.LowPriority = TRUE;
  1582.         PrfQueryProfileData(HINI_USER, "XFldTreesize", "Settings",
  1583.                     &Settings,
  1584.                     &cbSettings);
  1585.  
  1586.         // go!
  1587.         WinShowWindow(hwndMain, TRUE);
  1588.         WinProcessDlg(hwndMain);
  1589.         WinDestroyWindow(hwndMain);
  1590.     } // end if (proceed)
  1591.  
  1592.     if (tidCollect) {
  1593.         // give the Collect thread 5 seconds to exit;
  1594.         // normally, this shouldn't be a problem
  1595.         fStopThread = TRUE;
  1596.         for (ul = 0; ul < 50; ul++)
  1597.             if (tidCollect == 0)
  1598.                 break;
  1599.             else
  1600.                 DosSleep(100);  // 50*100 ms = 5 seconds
  1601.     }
  1602.  
  1603.     // clean up on the way out
  1604.     WinDestroyMsgQueue(hmq);
  1605.     WinTerminate(hab);
  1606.  
  1607.     return TRUE;
  1608. }
  1609.  
  1610.  
  1611.