
/*
 *@@sourcefile fdrview.c:
 *      shared code for all folder views.
 *
 *@@added V1.0.0 (2002-08-28) [umoeller]
 *@@header "filesys\folder.h"
 */

/*
 *      Copyright (C) 2001-2002 Ulrich M”ller.
 *      This file is part of the XWorkplace source package.
 *      XWorkplace is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published
 *      by the Free Software Foundation, in version 2 as it comes in the
 *      "COPYING" file of the XWorkplace main distribution.
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 */

#pragma strings(readonly)

/*
 *  Suggested #include order:
 *  1)  os2.h
 *  2)  C library headers
 *  3)  setup.h (code generation and debugging options)
 *  4)  headers in helpers\
 *  5)  at least one SOM implementation header (*.ih)
 *  6)  dlgids.h, headers in shared\ (as needed)
 *  7)  headers in implementation dirs (e.g. filesys\, as needed)
 *  8)  #pragma hdrstop and then more SOM headers which crash with precompiled headers
 */

#define INCL_DOSEXCEPTIONS
#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#define INCL_DOSERRORS

#define INCL_WINWINDOWMGR
#define INCL_WINMESSAGEMGR
#define INCL_WINFRAMEMGR
#define INCL_WINPOINTERS
#define INCL_WININPUT
#define INCL_WINSTDCNR
#define INCL_WINSTDDRAG
#define INCL_WINSHELLDATA
#define INCL_WINSCROLLBARS
#define INCL_WINSYS

#define INCL_GPIBITMAPS
#define INCL_GPIREGIONS
#include <os2.h>

// C library headers
#include <stdio.h>              // needed for except.h
#include <setjmp.h>             // needed for except.h
#include <assert.h>             // needed for except.h

// generic headers
#include "setup.h"                      // code generation and debugging options

// headers in /helpers
#include "helpers\dosh.h"               // Control Program helper routines
#include "helpers\cnrh.h"               // container helper routines
#include "helpers\except.h"             // exception handling
#include "helpers\gpih.h"               // GPI helper routines
#include "helpers\linklist.h"           // linked list helper routines
#include "helpers\standards.h"          // some standard macros
#include "helpers\winh.h"               // PM helper routines

// SOM headers which don't crash with prec. header files
#include "xfldr.ih"
#include "xfdisk.ih"
#include "xfdataf.ih"
#include "xfobj.ih"

// XWorkplace implementation headers
#include "shared\classtest.h"           // some cheap funcs for WPS class checks
#include "shared\common.h"              // the majestic XWorkplace include file
#include "shared\wpsh.h"                // some pseudo-SOM functions (WPS helper routines)

#include "filesys\folder.h"             // XFolder implementation
#include "filesys\fdrsplit.h"           // folder split views
#include "filesys\fdrviews.h"           // common code for folder views
#include "filesys\object.h"             // XFldObject implementation

// other SOM headers
#pragma hdrstop                         // VAC++ keeps crashing otherwise

/* ******************************************************************
 *
 *   Private declarations
 *
 ********************************************************************/

/*
 *@@ IMAGECACHEENTRY:
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 */

typedef struct _IMAGECACHEENTRY
{
    WPFileSystem    *pobjImage;     // a WPImageFile really

    HBITMAP         hbm;            // result from WPImageFile::wpQueryBitmapHandle

} IMAGECACHEENTRY, *PIMAGECACHEENTRY;

/*
 *@@ SUBCLCNR:
 *      QWL_USER data for containers that were
 *      subclassed to paint backgrounds.
 *
 *      This gets created and set by fdrMakeCnrPaint.
 *      The view settings are then manipulated
 *      every time the
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 */

typedef struct _SUBCLCNR
{
    HWND    hwndCnr;
    PFNWP   pfnwpOrig;

    BOOL    fDisabled;      // ignore CM_PAINTBACKGROUND
                            // added V1.0.11 (2016-08-12) [rwalsh]

    SIZEL   szlCnr;         // container dimensions, updated with
                            // every WM_WINDOWPOSCHANGED

    LONG    lcolBackground;

    // only if usColorOrBitmap == 0x128, we set the following:
    HBITMAP hbm;            // folder background bitmap
    SIZEL   szlBitmap;      // size of that bitmap

    PIBMFDRBKGND    pBackground;        // pointer to WPFolder instance data
                                        // with the bitmap settings

    /*
    USHORT  usTiledOrScaled;        // 0x132 == normal
                                    // 0x133 == tiled
                                    // 0x134 == scaled
                        // #define BKGND_NORMAL        0x132
                        // #define BKGND_TILED         0x133
                        // #define BKGND_SCALED        0x134
    USHORT  usScaleFactor;          // normally 1
    */

} SUBCLCNR, *PSUBCLCNR;

/* ******************************************************************
 *
 *   Global variables
 *
 ********************************************************************/

HMTX        G_hmtxImages = NULLHANDLE;
LINKLIST    G_llImages;     // linked list of IMAGECACHEENTRY structs, auto-free

/* ******************************************************************
 *
 *   Image file cache
 *
 ********************************************************************/

/*
 *@@ LockImages:
 *
 */

STATIC BOOL LockImages(VOID)
{
    if (G_hmtxImages)
        return !DosRequestMutexSem(G_hmtxImages, SEM_INDEFINITE_WAIT);

    if (!DosCreateMutexSem(NULL,
                           &G_hmtxImages,
                           0,
                           TRUE))
    {
        lstInit(&G_llImages,
                TRUE);      // auto-free
        return TRUE;
    }

    return FALSE;
}

/*
 *@@ UnlockImages:
 *
 */

STATIC VOID UnlockImages(VOID)
{
    DosReleaseMutexSem(G_hmtxImages);
}

/* ******************************************************************
 *
 *   Painting container backgrounds
 *
 ********************************************************************/

/*
 *@@ DrawBitmapClipped:
 *      draws a subrectangle of the given bitmap at
 *      the given coordinates.
 *
 *      The problem with CM_PAINTBACKGROUND is that
 *      we receive an update rectangle, and we MUST
 *      ONLY PAINT IN THAT RECTANGLE, or we'll overpaint
 *      parts of the container viewport that will not
 *      be refreshed otherwise.
 *
 *      At the same time, we must center or tile bitmaps.
 *      This function is a wrapper around WinDrawBitmap
 *      that will paint the given bitmap at the pptlOrigin
 *      position while making sure that only parts in
 *      prclClip will actually be painted.
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 *@@changed V1.0.9 (2011-10-14) [rwalsh]: commented-out unused function
 */
#if 0
VOID DrawBitmapClipped(HPS hps,             // in: presentation space
                       HBITMAP hbm,         // in: bitmap to paint
                       PPOINTL pptlOrigin,  // in: position to paint bitmap at
                       PSIZEL pszlBitmap,   // in: bitmap size
                       PRECTL prclClip)     // in: clip (update) rectangle
{
    RECTL   rclSubBitmap;
    POINTL  ptl;

    PMPF_CNRBITMAPS(("pptlOrigin->x: %d, pptlOrigin->y: %d",
            pptlOrigin->x, pptlOrigin->y));

    /*
       -- pathological case:

          cxBitmap = 200
          cyBitmap = 300

          ÚÄpresentation space (cnr)ÄÄÄÄÄÄÄÄÄÄÄÄ¿     500
          ³                                     ³
          ³                                     ³
          ³      ÚÄbitmapÄÄÄÄÄÄÄ¿   ÚrclClip¿   ³     400
          ³      ³              ³   ³       ³   ³
          ³      ³              ³   ³       ³   ³
          ³      ³              ³  350     450  ³     300
          ³      ³              ³   ³       ³   ³
          ³      ³              ³   ³       ³   ³
          ³      100            ³   ÀÄÄÄÄÄÄÄÙ   ³     200
          ³      ³              ³               ³
          ³      ³              ³               ³
          ³     pptlOriginÄ100ÄÄÙ               ³     100
          ³                                     ³
          ³                                     ³
          ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ     0

          0      100    200    300     400     500
    */

    // do nothing if the bitmap is not even in the
    // clip rectangle

    if (    (prclClip->xLeft >= pptlOrigin->x + pszlBitmap->cx)
         || (prclClip->xRight <= pptlOrigin->x)
         || (prclClip->yBottom >= pptlOrigin->y + pszlBitmap->cy)
         || (prclClip->yTop <= pptlOrigin->y)
       )
    {
        PMPF_CNRBITMAPS(("  pathological case, returning"));

        return;
    }

    if (prclClip->xLeft > pptlOrigin->x)
    {
        /*
           -- rclClip is to the right of bitmap origin:

              cxBitmap = 300
              cyBitmap = 300

              ÚÄpresentation space (cnr)ÄÄÄÄÄÄÄÄÄÄÄÄ¿     500
              ³                                     ³
              ³                                     ³
              ³      ÚÄbitmapÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿       ³     400
              ³      ³                      ³       ³
              ³      ³                      ³       ³
              ³      ³             ÚÄrclClipÄÄÄÄ¿   ³     300
              ³      100           300      ³   ³   ³
              ³      ³             ³        ³   ³   ³
              ³      ³             ÀÄÄÄÄÄ200ÅÄÄÄÙ   ³     200
              ³      ³                      ³       ³
              ³      ³                      ³       ³
              ³     pptlOriginÄÄÄ100ÄÄÄÄÄÄÄÄÙ       ³     100
              ³                                     ³
              ³                                     ³
              ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ     0

              0      100    200    300     400     500

              this gives us: rclSubBitmap xLeft   = 300 - 100 = 200
                                          yBottom = 200 - 100 = 100
                                          xRight  = 450 - 100 = 350 --> too wide, but shouldn't matter
                                          yTop    = 300 - 100 = 200

        */

        rclSubBitmap.xLeft =   prclClip->xLeft
                             - pptlOrigin->x;
        rclSubBitmap.xRight =  prclClip->xRight
                             - pptlOrigin->x;
        ptl.x = prclClip->xLeft;
    }
    else
    {
        /*
           -- rclClip is to the left of bitmap origin:

              cxBitmap = 300
              cyBitmap = 300

              ÚÄpresentation space (cnr)ÄÄÄÄÄÄÄÄÄÄÄÄ¿     500
              ³                                     ³
              ³                                     ³
              ³      ÚÄbitmapÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿       ³     400
              ³      ³                      ³       ³
              ³      ³                      ³       ³
              ³  ÚÄrclClipÄÄÄÄÄÄÄÄÄ¿        ³       ³     300
              ³  50  ³            300       ³       ³
              ³  ³   ³             ³        ³       ³
              ³  ÀÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÙ        ³       ³     200
              ³     100                     ³       ³
              ³      ³                      ³       ³
              ³     pptlOriginÄÄÄ100ÄÄÄÄÄÄÄÄÙ       ³     100
              ³                                     ³
              ³                                     ³
              ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ     0

              0      100    200    300     400     500

        */

        rclSubBitmap.xLeft  = 0;
        rclSubBitmap.xRight = prclClip->xRight - pptlOrigin->x;

        ptl.x = pptlOrigin->x;

    }

    // same thing for y
    if (prclClip->yBottom > pptlOrigin->y)
    {

        rclSubBitmap.yBottom =   prclClip->yBottom
                               - pptlOrigin->y;
        rclSubBitmap.yTop    =   prclClip->yTop
                               - pptlOrigin->y;
        ptl.y = prclClip->yBottom;
    }
    else
    {
        rclSubBitmap.yBottom  = 0;
        rclSubBitmap.yTop = prclClip->yTop - pptlOrigin->y;

        ptl.y = pptlOrigin->y;

    }

    if (    (rclSubBitmap.xLeft < rclSubBitmap.xRight)
         && (rclSubBitmap.yBottom < rclSubBitmap.yTop)
       )
        WinDrawBitmap(hps,
                      hbm,
                      &rclSubBitmap,
                      &ptl,
                      0,
                      0,
                      0);
}
#endif
/*
 *@@ ScaleSrcToDst:
 *      This provides two types of proportional scaling:
 *
 *      -- downscale-only:  the result will never be larger than
 *            the smaller of the source or destination rectangles
 *
 *      -- upscale or downscale:  when the SCALEF_EXPAND flag is
 *            set, one dimension of the result will equal the
 *            destination's while the other will be greater than
 *            or equal to the destination's corresponding dimension 
 *
 *      If the SCALEF_CENTER flag is set, the result will be centered
 *      in the destination's rectangle.  If the SCALEF_INCL flag is
 *      set, the result will be converted to an inclusive rectangle.
 *
 *@@added V1.0.9 (2011-10-14) [rwalsh]: @@fixes 277
 */

#define SCALEF_NONE     0
#define SCALEF_CENTER   1
#define SCALEF_INCL     2
#define SCALEF_EXPAND   4
#define SCALEF_TILED    8

void  ScaleSrcToDst(PSIZEL pszlSrc,
                    PSIZEL pszlDst,
                    PRECTL prclRtn,
                    ULONG  ulFlags)
{
    if (ulFlags & SCALEF_EXPAND)
    {
        // Scale the source so that one dimension equals
        // the destination and the other is GTE to it.
        prclRtn->yTop = pszlDst->cy;
        prclRtn->xRight = (pszlSrc->cx * prclRtn->yTop) / pszlSrc->cy;

        if (prclRtn->xRight < pszlDst->cx)
        {            
            prclRtn->xRight = pszlDst->cx;
            prclRtn->yTop = (pszlSrc->cy * prclRtn->xRight) / pszlSrc->cx;
        }
    }
    else
    {
        if (pszlSrc->cy <= pszlDst->cy)
        {
            // If both source dimensions are smaller than
            // the destination, use them as-is.  Otherwise,
            // use the full width of the destination and
            // scale down the source's height.
            if (pszlSrc->cx <= pszlDst->cx)
            {
                prclRtn->yTop = pszlSrc->cy;
                prclRtn->xRight = pszlSrc->cx;
            }
            else
            {
                prclRtn->xRight = pszlDst->cx;
                prclRtn->yTop = (pszlSrc->cy * prclRtn->xRight) / pszlSrc->cx;
            }
        }
        else
        {
            // Scale the source so that one dimension equals
            // the destination and the other is LTE to it.
            prclRtn->yTop = pszlDst->cy;
            prclRtn->xRight = (pszlSrc->cx * prclRtn->yTop) / pszlSrc->cy;

            if (prclRtn->xRight > pszlDst->cx)
            {            
                prclRtn->xRight = pszlDst->cx;
                prclRtn->yTop = (pszlSrc->cy * prclRtn->xRight) / pszlSrc->cx;
            }
        }
    }

    PMPF_CNRBITMAPS(("src= %d.%d  dst= %d.%d  rtn= %d.%d  adj= %d.%d  flags= %s%s%s\n",
                    pszlSrc->cx, pszlSrc->cy,
                    pszlDst->cx, pszlDst->cy,
                    prclRtn->xRight, prclRtn->yTop,
                    pszlDst->cx - prclRtn->xRight, pszlDst->cy - prclRtn->yTop,
                    (ulFlags & SCALEF_CENTER) ? "C" : "",
                    (ulFlags & SCALEF_INCL)   ? "I" : "",
                    (ulFlags & SCALEF_EXPAND) ? "X" : ""));

    if (ulFlags & SCALEF_CENTER)
    {
        LONG  lAdj;

        // deal with the differences being an odd nbr of pixels
        lAdj = pszlDst->cy - prclRtn->yTop;
        prclRtn->yBottom = lAdj / 2;
        prclRtn->yTop += lAdj - prclRtn->yBottom;

        lAdj = pszlDst->cx - prclRtn->xRight;
        prclRtn->xLeft = lAdj / 2;
        prclRtn->xRight += lAdj - prclRtn->xLeft;
    }

    if (ulFlags & SCALEF_INCL)
    {
        prclRtn->yTop--;
        prclRtn->xRight--;
    }

    return;
}

/*
 *@@ BlitClippedBitmap:
 *      Sets a clipping rectangle then blits a bitmap.
 *      If the source and destination rectangles aren't
 *      equal, the output will be expanded or compressed.
 *
 *@@added V1.0.9 (2011-10-14) [rwalsh]: @@fixes 277
 */

void  BlitClippedBitmap(HPS hps,
                        HBITMAP hbm,
                        PPOINTL pptl,
                        PRECTL prclClip)
{
    HRGN    hrgnOldClip = NULLHANDLE;
    RECTL   rclAdj;

    // reset clip region to "all" to quickly get the
    // old clip region; we must save and restore that,
    // or the cnr will stop painting quickly
    GpiSetClipRegion(hps, NULLHANDLE, &hrgnOldClip);

    // adjust the rectl to be inclusive,
    // then set it as the new clip region
    rclAdj = *prclClip;
    rclAdj.xRight--;
    rclAdj.yTop--;
    GpiIntersectClipRectangle(hps, &rclAdj);

    // blit
    GpiWCBitBlt(hps,
                hbm,
                4L,             // must always be 4
                pptl,           // points array
                ROP_SRCCOPY,
                BBO_IGNORE);

    // restore the old clip region
    GpiSetClipRegion(hps, hrgnOldClip, &hrgnOldClip);

    // hrgnOldClip now has the clip region that was
    // created by GpiIntersectClipRectangle, so delete that
    if (hrgnOldClip)
        GpiDestroyRegion(hps, hrgnOldClip);

    return;
}

/*
 *@@ PaintCnrBackground:
 *      implementation of CM_PAINTBACKGROUND in fnwpSubclCnr.
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 *@@changed V1.0.9 (2011-10-14) [rwalsh]: rewritten to implement proportional scaling and tiling @@fixes 277
 */

MRESULT PaintCnrBackground(HWND hwndCnr,
                           POWNERBACKGROUND pob)
{
    PSUBCLCNR     pSubCnr;
    PIBMFDRBKGNDSTORE pBkgndStore;
    POINTL        aptl[4];
    SIZEL         szl,
                  szlAdj;
    LONG          lStart,
                  lAdj;
    ULONG         ulFlags = 0;

    if (    !(pSubCnr = (PSUBCLCNR)WinQueryWindowPtr(hwndCnr, QWL_USER))
         ||  (pSubCnr->fDisabled)
         || !(pSubCnr->pBackground)
         || !(pBkgndStore = &pSubCnr->pBackground->BkgndStore)
       )
        return (MRESULT)FALSE;

    // we need not paint the background if we have a bitmap
    // AND it should fill the entire folder (avoid flicker)
    if (    (!pSubCnr->hbm)
         || (BKGND_NORMAL == pBkgndStore->usTiledOrScaled)
       )
    {
        gpihSwitchToRGB(pob->hps);
        WinFillRect(pob->hps,
                    &pob->rclBackground,
                    pSubCnr->lcolBackground);
    }

    // if there's no bitmap or any of the dimensions are zero, exit
    if (    (!pSubCnr->hbm)
         || (!pSubCnr->szlBitmap.cx)
         || (!pSubCnr->szlBitmap.cy)
         || (!pSubCnr->szlCnr.cx)
         || (!pSubCnr->szlCnr.cy)
       )
        return (MRESULT)TRUE;

    switch (pBkgndStore->usTiledOrScaled)
    {
        /*
         * BKGND_NORMAL:
         *      scale the bitmap down if needed (but never up),
         *      and center it in the container
         */
        case BKGND_NORMAL:
            ulFlags = SCALEF_CENTER | SCALEF_INCL;
        break;

        /*
         * BKGND_SCALED:
         *      for 1x1, scale the bitmap to match the container
         *      size while maintaining its aspect ration.  One
         *      dimension will equal the container's while the
         *      other will be GTE to it.  The oversized dimension
         *      will be clipped by centering the scaled image.
         *
         *      For 2x2 to 20x20, scale the bitmap so that the
         *      specified number of copies will be tiled horizontally
         *      and vertically.
         */
        case BKGND_SCALED:
        {
            // 1x1
            if (pBkgndStore->usScaleFactor < 2)
            {
                ulFlags = SCALEF_EXPAND | SCALEF_CENTER | SCALEF_INCL;
                break;
            }

            // more than 20 suggests corrupted data
            if (pBkgndStore->usScaleFactor > 20)
                return (MRESULT)FALSE;

            // 2x2 to 20x20
            ulFlags = SCALEF_TILED;

            // calc the size of the destination rectl
            szl.cx = pSubCnr->szlCnr.cx / pBkgndStore->usScaleFactor;
            szl.cy = pSubCnr->szlCnr.cy / pBkgndStore->usScaleFactor;

            // calc the number of leftover pixels that have to be
            // dealt with - it could be anywhere from 0 to ScaleFactor-1
            szlAdj.cx = pSubCnr->szlCnr.cx % pBkgndStore->usScaleFactor;
            szlAdj.cy = pSubCnr->szlCnr.cy % pBkgndStore->usScaleFactor;
        }
        break;

        /*
         * BKGND_TILED:
         *      draw the bitmap as many times as it fits in the
         *      container.  If the bitmap is larger than the
         *      window, it will be scaled down proportionally.
         */
        case BKGND_TILED:
        {
            RECTL rcl;

            ulFlags = SCALEF_TILED;

            // scale the bitmap, but don't center it
            ScaleSrcToDst(&pSubCnr->szlBitmap,
                          &pSubCnr->szlCnr,
                          &rcl,
                          0);

            // save the scaled bitmap's dimensions
            szl.cx = rcl.xRight;
            szl.cy = rcl.yTop;

            // these are used below for tiling BKGND_SCALED bitmaps
            // but not for BKGND_TILED, so set them to zero
            szlAdj.cx = 0;
            szlAdj.cy = 0;
        }
        break;

        default:
            return (MRESULT)FALSE;
    }

    /* the array of POINTLs is used like this:
     *    aptl[0]: target bottom-left
     *    aptl[1]: target top-right (inclusive)
     *    aptl[2]: source bottom-left
     *    aptl[3]: source top-right (exclusive)
     *
     * start them out as all zeros, then
     * set the source rectl to the entire bitmap
     */
    memset(aptl, 0, sizeof(aptl));
    aptl[3].x = pSubCnr->szlBitmap.cx;
    aptl[3].y = pSubCnr->szlBitmap.cy;

    /*
     * Paint routine for BKGND_NORMAL and BKGND_SCALED (1x1)
     */
    if (!(ulFlags & SCALEF_TILED))
    {
        // scale the destination rectl
        ScaleSrcToDst(&pSubCnr->szlBitmap,
                      &pSubCnr->szlCnr,
                      (PRECTL)&aptl[0],
                      ulFlags);

        // display the portion which lies in the update rectl
        BlitClippedBitmap(pob->hps,
                          pSubCnr->hbm,
                          aptl,
                          &pob->rclBackground);
        
        return (MRESULT)TRUE;
    }

    /*
     * Paint routine for BKGND_TILED and BKGND_SCALED (2x2 to 20x20)
     *
     * To avoid needlessly painting areas that will be clipped out,
     * calculate the first row (from the top) and the first column
     * that intersect the update rectl - we'll start there.
     *
     * This process is complicated by the possibility that BKGND_SCALED
     * may have had extra pixels that have to be used up by increasing
     * the size of the first so-many rows and/or columns by 1 pixel each,
     * then reverting to the original size for the remainder.  This is
     * not a problem for BKGND_TILED - szlAdj is always zero.
     */

    // calc the first row - if it gets an extra pixel,
    // increase the bitmap height now
    lStart = (pSubCnr->szlCnr.cy - pob->rclBackground.yTop) / szl.cy;
    lAdj = min(lStart, szlAdj.cy);
    if (lStart * szl.cy + lAdj > pSubCnr->szlCnr.cy - pob->rclBackground.yTop)
    {
        lStart--;
        lAdj = min(lStart, szlAdj.cy);
    }
    aptl[1].y = pSubCnr->szlCnr.cy - (lStart * szl.cy + lAdj) - 1;
    szlAdj.cy -= lAdj;
    if (szlAdj.cy)
        szl.cy++;
    aptl[0].y = aptl[1].y + 1 - szl.cy;

    // calc the first column - if it gets an extra pixel,
    // the bitmap width will be increased in the loop below
    lStart = pob->rclBackground.xLeft / szl.cx;
    lAdj = min(lStart, szlAdj.cx);
    if (lStart * szl.cx + lAdj > pob->rclBackground.xLeft)
    {
        lStart--;
        lAdj = min(lStart, szlAdj.cx);
    }
    lStart = lStart * szl.cx + lAdj;
    szlAdj.cx -= lAdj;

    // outer loop: the "rows"
    while (aptl[1].y >= pob->rclBackground.yBottom)
    {
        lAdj = szlAdj.cx;

        // init X values to the first column that has to be painted
        if (lAdj)
            szl.cx++;
        aptl[0].x = lStart;
        aptl[1].x = lStart + szl.cx - 1;

        // inner loop: the "columns"
        while (aptl[0].x < pob->rclBackground.xRight)
        {
            BlitClippedBitmap(pob->hps,
                              pSubCnr->hbm,
                              aptl,
                              &pob->rclBackground);

            // advance rightward - if we've run out of extra pixels,
            // restore the original width for the remaining columns;
            aptl[0].x += szl.cx;
            if (lAdj && !(--lAdj))
                szl.cx--;
            aptl[1].x += szl.cx;
        }

        // if the last column drawn used an extra pixel,
        // reset the column width to its original size
        if (lAdj)
            szl.cx--;

        // advance downward - if we've run out of extra pixels,
        // restore the original height for the remaining rows
        aptl[1].y -= szl.cy;
        if (szlAdj.cy && !(--szlAdj.cy))
            szl.cy--;
        aptl[0].y -= szl.cy;
    }

    return (MRESULT)TRUE;
}

/*
 *@@ fnwpSubclCnr:
 *      window proc that our containers are subclassed with
 *      to implement container background painting. See
 *      PaintCnrBackground.
 *
 *      We have a SUBCLCNR struct in QWL_USER, which was
 *      put there by fdrMakeCnrPaint.
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 *@@changed V1.0.9 (2011-10-06) [rwalsh]:  added WM_BUTTON2DOWN intercept
 */

MRESULT EXPENTRY fnwpSubclCnr(HWND hwndCnr, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT     mrc = 0;
    PSUBCLCNR   pSubCnr;

    switch (msg)
    {
        case CM_PAINTBACKGROUND:
            mrc = PaintCnrBackground(hwndCnr, (POWNERBACKGROUND)mp1);
        break;

        case WM_WINDOWPOSCHANGED:
            // when we resize and have a bitmap, repaint the
            // entire container to get everything right
            if (pSubCnr = (PSUBCLCNR)WinQueryWindowPtr(hwndCnr, QWL_USER))
            {
                if (((PSWP)mp1)->fl & SWP_SIZE)
                {
                    pSubCnr->szlCnr.cx = ((PSWP)mp1)->cx;
                    pSubCnr->szlCnr.cy = ((PSWP)mp1)->cy;

                    if (pSubCnr->hbm)
                        WinInvalidateRect(hwndCnr, NULL, TRUE);
                }

                mrc = pSubCnr->pfnwpOrig(hwndCnr, msg, mp1, mp2);
            }
        break;

        case WM_VSCROLL:
        case WM_HSCROLL:
            if (pSubCnr = (PSUBCLCNR)WinQueryWindowPtr(hwndCnr, QWL_USER))
            {
                switch (SHORT2FROMMP(mp2))
                {
                    // case SB_LINEUP:
                    // case SB_LINEDOWN:
                    // case SB_PAGEUP:
                    // case SB_PAGEDOWN:
                    case SB_ENDSCROLL:
                    case SB_SLIDERPOSITION:
                        if (pSubCnr->hbm)
                            WinInvalidateRect(hwndCnr, NULL, TRUE);

                }

                mrc = pSubCnr->pfnwpOrig(hwndCnr, msg, mp1, mp2);
            }
        break;

        case WM_DESTROY:
            if (pSubCnr = (PSUBCLCNR)WinQueryWindowPtr(hwndCnr, QWL_USER))
            {
                mrc = pSubCnr->pfnwpOrig(hwndCnr, msg, mp1, mp2);

                free(pSubCnr);
            }
        break;

        // Suppress activation on MB2-down to emulate standard
        // folder behavior.  Putting this here is less of a
        // kludge than it seems:  this is how the WPS does it.
        //@@added V1.0.9 (2011-10-06) [rwalsh]
        case WM_BUTTON2DOWN:
            mrc = (MRESULT)TRUE;
        break;

        default:
            pSubCnr = (PSUBCLCNR)WinQueryWindowPtr(hwndCnr, QWL_USER);
            mrc = pSubCnr->pfnwpOrig(hwndCnr, msg, mp1, mp2);
        break;
    }

    return mrc;
}

/*
 *@@ fdrvMakeCnrPaint:
 *      subclasses the given cnr with fnwpSubclCnr
 *      to make it process CM_PAINTBACKGROUND.
 *
 *      This creates a SUBCLCNR struct and puts it
 *      into the cnr's QWL_USER.
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 */

BOOL fdrvMakeCnrPaint(HWND hwndCnr)
{
    PSUBCLCNR   pSubCnr;
    CNRINFO     CnrInfo;

    if (cnrhQueryCnrInfo(hwndCnr, &CnrInfo))
    {
        CnrInfo.flWindowAttr |= CA_OWNERPAINTBACKGROUND;
        WinSendMsg(hwndCnr,
                   CM_SETCNRINFO,
                   (MPARAM)&CnrInfo,
                   (MPARAM)CMA_FLWINDOWATTR);

        if (pSubCnr = NEW(SUBCLCNR))
        {
            ZERO(pSubCnr);

            pSubCnr->hwndCnr = hwndCnr;
            WinSetWindowPtr(hwndCnr, QWL_USER, pSubCnr);
            if (pSubCnr->pfnwpOrig = WinSubclassWindow(hwndCnr,
                                                       fnwpSubclCnr))
                return TRUE;
        }
    }

    return FALSE;
}

/*
 *@@ fdrvDisableBkgndPainting:
 *      If TRUE, causes the cnr subproc to ignore
 *      CM_PAINTBACKGROUND so the cnr does the painting
 *
 *      Used by Xview when the cnr has been subclassed
 *      but the user has turned off SPLIT_FDRSTYLES.
 *
 *@@added V1.0.11 (2016-08-12) [rwalsh]
 */

BOOL fdrvDisableBkgndPainting(HWND hwndCnr, BOOL fDisable)
{
    PSUBCLCNR     pSubCnr;

    if (pSubCnr = (PSUBCLCNR)WinQueryWindowPtr(hwndCnr, QWL_USER))
    {
        pSubCnr->fDisabled = fDisable;
        return TRUE;
    }
    return FALSE;
}

/*
 *@@ ResolveColor:
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 */

LONG ResolveColor(LONG lcol)         // in: explicit color or SYSCLR_* index
{
    // 0x40000000 is a special color value that I have
    // seen in use only by the Desktop itself; I assume
    // this is for getting the desktop icon text color
    if (lcol == 0x40000000)
    {
        CHAR    szTemp[20];
        LONG    lRed, lGreen, lBlue;
        PrfQueryProfileString(HINI_USER,
                              "PM_Colors",
                              "DesktopIconText",
                              "0 0 0",
                              szTemp,
                              sizeof(szTemp));
        sscanf(szTemp, "%d %d %d", &lRed, &lGreen, &lBlue);
        lcol = MAKE_RGB(lRed, lGreen, lBlue);
    }
    // otherwise, if the highest byte is set in the
    // color value, it is really a negative SYSCLR_*
    // index; for backgrounds, this is normally
    // SYSCLR_WINDOW, so check this
    else if (lcol & 0xFF000000)
    {
#ifdef __DEBUG__
        #define DUMPCOL(i) case i: pcsz = # i; break
        PCSZ pcsz = "unknown index";
        switch (lcol)
        {
            DUMPCOL(SYSCLR_SHADOWHILITEBGND);
            DUMPCOL(SYSCLR_SHADOWHILITEFGND);
            DUMPCOL(SYSCLR_SHADOWTEXT);
            DUMPCOL(SYSCLR_ENTRYFIELD);
            DUMPCOL(SYSCLR_MENUDISABLEDTEXT);
            DUMPCOL(SYSCLR_MENUHILITE);
            DUMPCOL(SYSCLR_MENUHILITEBGND);
            DUMPCOL(SYSCLR_PAGEBACKGROUND);
            DUMPCOL(SYSCLR_FIELDBACKGROUND);
            DUMPCOL(SYSCLR_BUTTONLIGHT);
            DUMPCOL(SYSCLR_BUTTONMIDDLE);
            DUMPCOL(SYSCLR_BUTTONDARK);
            DUMPCOL(SYSCLR_BUTTONDEFAULT);
            DUMPCOL(SYSCLR_TITLEBOTTOM);
            DUMPCOL(SYSCLR_SHADOW);
            DUMPCOL(SYSCLR_ICONTEXT);
            DUMPCOL(SYSCLR_DIALOGBACKGROUND);
            DUMPCOL(SYSCLR_HILITEFOREGROUND);
            DUMPCOL(SYSCLR_HILITEBACKGROUND);
            DUMPCOL(SYSCLR_INACTIVETITLETEXTBGND);
            DUMPCOL(SYSCLR_ACTIVETITLETEXTBGND);
            DUMPCOL(SYSCLR_INACTIVETITLETEXT);
            DUMPCOL(SYSCLR_ACTIVETITLETEXT);
            DUMPCOL(SYSCLR_OUTPUTTEXT);
            DUMPCOL(SYSCLR_WINDOWSTATICTEXT);
            DUMPCOL(SYSCLR_SCROLLBAR);
            DUMPCOL(SYSCLR_BACKGROUND);
            DUMPCOL(SYSCLR_ACTIVETITLE);
            DUMPCOL(SYSCLR_INACTIVETITLE);
            DUMPCOL(SYSCLR_MENU);
            DUMPCOL(SYSCLR_WINDOW);
            DUMPCOL(SYSCLR_WINDOWFRAME);
            DUMPCOL(SYSCLR_MENUTEXT);
            DUMPCOL(SYSCLR_WINDOWTEXT);
            DUMPCOL(SYSCLR_TITLETEXT);
            DUMPCOL(SYSCLR_ACTIVEBORDER);
            DUMPCOL(SYSCLR_INACTIVEBORDER);
            DUMPCOL(SYSCLR_APPWORKSPACE);
            DUMPCOL(SYSCLR_HELPBACKGROUND);
            DUMPCOL(SYSCLR_HELPTEXT);
            DUMPCOL(SYSCLR_HELPHILITE);
        }

        PMPF_CNRBITMAPS(("  --> %d (%s)", lcol, pcsz));

#endif

        lcol = WinQuerySysColor(HWND_DESKTOP,
                                lcol,
                                0);
    }

    return lcol;
}

/*
 *@@ fdrvRemoveFromImageCache:
 *      removes the given object from the image cache.
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 *@@changed V1.0.1 (2003-01-31) [umoeller]: fixed dangling linklist ptr
 */

BOOL fdrvRemoveFromImageCache(WPObject *pobjImage)
{
    BOOL    brc = FALSE,
            fLocked = FALSE;

    TRY_LOUD(excpt1)
    {
        if (fLocked = LockImages())
        {
            PLISTNODE pNode = lstQueryFirstNode(&G_llImages);
            while (pNode)
            {
                PLISTNODE pNext = pNode->pNext;     // fixed V1.0.1 (2003-01-29) [umoeller]
                PIMAGECACHEENTRY pice = (PIMAGECACHEENTRY)pNode->pItemData;

                if (pice->pobjImage == pobjImage)
                {
                    // found:

                    // delete the bitmap
                    GpiDeleteBitmap(pice->hbm);

                    lstRemoveNode(&G_llImages,
                                  pNode);       // auto-free
                    brc = TRUE;
                    break;
                }

                pNode = pNext;      // fixed V1.0.1 (2003-01-29) [umoeller]
            }
        }
    }
    CATCH(excpt1)
    {
    }
    END_CATCH();

    if (fLocked)
        UnlockImages();

    return brc;
}

/*
 *@@ GetBitmap:
 *      returns the bitmap handle for the given folder
 *      background structure. This will either be
 *      a bitmap from our image cache or a newly
 *      created one, if the image was not in the
 *      cache.
 *
 *      Returns NULLHANDLE if
 *
 *      --  the folder has no background bitmap,
 *
 *      --  the specified bitmap file does not exist or
 *          is not understood or
 *
 *      --  we're running on Warp 3. We require
 *          the help of the WPImageFile class here.
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 */

HBITMAP GetBitmap(PIBMFDRBKGND pBkgnd)
{
    HBITMAP hbm = NULLHANDLE;
    BOOL    fLocked = FALSE;

    TRY_LOUD(excpt1)
    {
        if (    (pBkgnd->BkgndStore.usColorOrBitmap == BKGND_BITMAP)
             && (pBkgnd->BkgndStore.pszBitmapFile)
           )
        {
            WPObject    *pobjImage;

            // check if WPFolder has found the WPImageFile
            // for us already (that is, if the folder had
            // a legacy open view already)
            if (pobjImage = pBkgnd->pobjImage)
            {
                // yes: check that it's valid
                CHAR szFilename[CCHMAXPATH] = "unknown";
                _wpQueryFilename(pBkgnd->pobjImage, szFilename, TRUE);
                if (stricmp(pBkgnd->BkgndStore.pszBitmapFile, szFilename))
                    cmnLog(__FILE__, __LINE__, __FUNCTION__,
                           "Background bitmap mismatch:\n"
                           "Folder has \"%s\", but image file is from \"%s\"",
                           pBkgnd->BkgndStore.pszBitmapFile,
                           szFilename);
            }
            else
            {
                // find the WPImageFile from the path, but DO NOT
                // set the WPImageFile* pointer in the IBMFDRBKGND
                // struct, or the WPS will go boom
                CHAR    szTemp[CCHMAXPATH];
                if (strchr(pBkgnd->BkgndStore.pszBitmapFile, '\\'))
                    strcpy(szTemp, pBkgnd->BkgndStore.pszBitmapFile);
                else
                {
                    // not fully qualified: assume ?:\os2\bitmap then
                    sprintf(szTemp,
                            "?:\\os2\\bitmap\\%s",
                            pBkgnd->BkgndStore.pszBitmapFile);
                }

                if (szTemp[0] == '?')
                    szTemp[0] = doshQueryBootDrive();

                pobjImage = _wpclsQueryObjectFromPath(_WPFileSystem,
                                                      szTemp);
            }

            if (pobjImage)
            {
                // now we have the WPImageFile...
                // in any case, LOCK it so it won't go dormant
                // behind our butt
                _wpLockObject(pobjImage);

                // now we need to return a HBITMAP... unfortunately
                // WPImageFile does NO reference counting whatsover
                // when doing _wpQueryBitmapHandle, but will create
                // a new HBITMAP for every call, which is just plain
                // stupid. So that's what we need the image cache
                // for: create only one HBITMAP for every image file,
                // no matter how many times it is used...

                // so check if we can find it in the cache
                if (fLocked = LockImages())
                {
                    PLISTNODE pNode = lstQueryFirstNode(&G_llImages);
                    while (pNode)
                    {
                        PIMAGECACHEENTRY pice = (PIMAGECACHEENTRY)pNode->pItemData;
                        if (pice->pobjImage == pobjImage)
                        {
                            // found:
                            hbm = pice->hbm;

                            // then we've locked it before and can unlock
                            // it once
                            // _wpUnlockObject(pobjImage);

                            break;
                        }

                        pNode = pNode->pNext;
                    }

                    if (!hbm)
                    {
                        // image file was not in cache:
                        // then create a HBITMAP and add a cache entry
                        if (hbm = _wpQueryHandleFromContents(pobjImage))
                        {
                            PIMAGECACHEENTRY pice;
                            if (pice = NEW(IMAGECACHEENTRY))
                            {
                                ZERO(pice);
                                pice->pobjImage = pobjImage;
                                pice->hbm = hbm;

                                lstAppendItem(&G_llImages,
                                              pice);

                                // remove from cache when it goes dormant
                                _xwpModifyFlags(pobjImage,
                                                OBJLIST_IMAGECACHE,
                                                OBJLIST_IMAGECACHE);
                            }
                        }
                    }
                } // end if (fLocked = LockImages())
            }
        }
    }
    CATCH(excpt1)
    {
    }
    END_CATCH();

    if (fLocked)
        UnlockImages();

    return hbm;
}

/*
 *@@ SetCnrLayout:
 *      one-shot function for setting the view settings
 *      of the given container according to the instance
 *      settings of the given folder.
 *
 *      This sets the container's background color and
 *      bitmap, if applicable, foreground color and font.
 *
 *@@added V1.0.0 (2002-08-24) [umoeller]
 */

VOID fdrvSetCnrLayout(HWND hwndCnr,         // in: cnr whose colors and fonts are to be set
                      XFolder *pFolder,     // in: folder that cnr gets filled with
                      ULONG ulView)         // in: one of OPEN_CONTENTS, OPEN_DETAILS, OPEN_TREE
{
    TRY_LOUD(excpt1)
    {
        // try to figure out the background color
        XFolderData     *somThis = XFolderGetData(pFolder);
        PIBMFOLDERDATA  pFdrData;
        PIBMFDRBKGND    pBkgnd;
        if (    (pFdrData = (PIBMFOLDERDATA)_pvWPFolderData)
             && (pBkgnd = pFdrData->pCurrentBackground)
           )
        {
            LONG        lcolBack,
                        lcolFore;
            PSUBCLCNR   pSubCnr;

            // avoid flicker; the cnr repaints on
            // every presparam change
            WinEnableWindowUpdate(hwndCnr, FALSE);

            // 1) background color and bitmap

            lcolBack = ResolveColor(pBkgnd->BkgndStore.lcolBackground);

            if (pSubCnr = (PSUBCLCNR)WinQueryWindowPtr(hwndCnr, QWL_USER))
            {
                // container is subclassed:

                pSubCnr->lcolBackground = lcolBack;

                // set the bitmap handle to what the
                // folder wants; this creates a bitmap
                // if necessary and puts it in the
                // image cache
                if (pSubCnr->hbm = GetBitmap(pBkgnd))
                {
                    // folder has a bitmap:
                    BITMAPINFOHEADER2 bih;
                    bih.cbFix = sizeof(bih);
                    GpiQueryBitmapInfoHeader(pSubCnr->hbm,
                                             &bih);
                    pSubCnr->szlBitmap.cx = bih.cx;
                    pSubCnr->szlBitmap.cy = bih.cy;
                }

                pSubCnr->pBackground = pBkgnd;

                /*
                pSubCnr->usTiledOrScaled = pBkgnd->BkgndStore.usTiledOrScaled;
                pSubCnr->usScaleFactor = pBkgnd->BkgndStore.usScaleFactor;
                */
            }
            else
                // container is not subclassed:
                winhSetPresColor(hwndCnr,
                                 PP_BACKGROUNDCOLOR,
                                 lcolBack);

            // 2) foreground color

            switch (ulView)
            {
                case OPEN_CONTENTS:
                    lcolFore = pFdrData->LongArray.rgbIconViewTextColColumns;
                break;

                case OPEN_DETAILS:
                    lcolFore = pFdrData->LongArray.rgbDetlViewTextCol;
                break;

                case OPEN_TREE:
                    lcolFore = pFdrData->LongArray.rgbTreeViewTextColIcons;
                break;
            }

            lcolFore = ResolveColor(lcolFore);

            winhSetPresColor(hwndCnr,
                             PP_FOREGROUNDCOLOR,
                             lcolFore);

            // 3) set the font according to the view flag;
            // _wpQueryFldrFont returns a default font
            // properly if there's no instance setting
            // for this view
            winhSetWindowFont(hwndCnr,
                              _wpQueryFldrFont(pFolder, ulView));

            WinShowWindow(hwndCnr, TRUE);
        }
    }
    CATCH(excpt1)
    {
    }
    END_CATCH();
}

/* ******************************************************************
 *
 *   Populate management
 *
 ********************************************************************/

/*
 *@@ fdrGetFSFromRecord:
 *      returns the WPFileSystem* which is represented
 *      by the specified record.
 *      This resolves shadows and returns root folders
 *      for WPDisk objects cleanly.
 *
 *      Returns NULL
 *
 *      -- if (fFoldersOnly) and precc does not represent
 *         a folder;
 *
 *      -- if (!fFoldersOnly) and precc does not represent
 *         a file-system object;
 *
 *      -- if the WPDisk or WPShadow cannot be resolved.
 *
 *      If fFoldersOnly and the return value is not NULL,
 *      it is guaranteed to be some kind of folder.
 *
 *@@added V0.9.9 (2001-03-11) [umoeller]
 *@@changed V1.0.9 (2011-09-17) [rwalsh]: restructured to improve efficiency
 */

WPFileSystem* fdrvGetFSFromRecord(PMINIRECORDCORE precc,
                                  BOOL fFoldersOnly)
{
    WPObject *pobj = NULL;
    if (    (precc)
         && (pobj = OBJECT_FROM_PREC(precc))
         && (pobj = objResolveIfShadow(pobj))
       )
    {
        if (!_somIsA(pobj, ((fFoldersOnly) ? _WPFolder : _WPFileSystem)))
        {
            if (!_somIsA(pobj, _WPDisk))
                pobj = NULL;
            else
                pobj = ((_somIsA(pobj, _XFldDisk))
                       ? _xwpSafeQueryRootFolder(pobj, FALSE, NULL)
                       : _wpQueryRootFolder(pobj));
        }
    }

    return pobj;
}

/*
 *@@ fdrIsInsertable:
 *      checks if pObject can be inserted in a container.
 *
 *      The return value depends on ulInsert:
 *
 *      --  INSERT_ALL (0): we allow all objects to be inserted,
 *          even broken shadows. This is used by the split
 *          view for the files container.
 *
 *          pcszFileMask is ignored in this case.
 *
 *      --  INSERT_FILESYSTEMS (1): this inserts all WPFileSystem and
 *          WPDisk objects plus shadows pointing to them. This
 *          is for the files container in the file dialog,
 *          obviously, because opening abstracts with a file
 *          dialog is impossible (unless the abstract is a
 *          shadow pointing to a file-system object).
 *
 *          For file-system objects, if (pcszFileMask != NULL), the
 *          object's real name is checked against that file mask also.
 *          For example, if (pcszFileMask == *.TXT), this will
 *          return TRUE only if pObject's real name matches
 *          *.TXT.
 *
 *          This is for the right (files) view.
 *
 *      --  INSERT_FOLDERSONLY (2): only real folders are inserted.
 *          We will not even insert disk objects or shadows,
 *          even if they point to shadows. We will also
 *          not insert folder templates.
 *
 *          pcszFileMask is ignored in this case.
 *
 *          This is for the left (drives) view when items
 *          are expanded.
 *
 *          This will NOT resolve shadows because if we insert
 *          shadows of folders into a container, their contents
 *          cannot be inserted a second time. The WPS shares
 *          records so each object can only be inserted once.
 *
 *      --  INSERT_FOLDERSANDDISKS (3): in addition to folders
 *          (as with INSERT_FOLDERSONLY), we allow insertion
 *          of drive objects too.
 *
 *@@changed V1.0.9 (2011-09-24) [rwalsh]: restructured to improve efficiency
 *@@changed V1.0.11 (2016-11-24) [rwalsh]: rewritten & simplified yet again
 */

BOOL fdrvIsInsertable(WPObject *pObject,
                      ULONG ulFoldersOnly,
                      PCSZ pcszFileMask)     // in: upper-case file mask or NULL
{
    if (!pObject)
        return FALSE;

    if (ulFoldersOnly == INSERT_ALL)
        return TRUE;

    // different rules for INSERT_FILESYSTEMS which
    // is only used by XWP's file open/save dialog
    if (ulFoldersOnly == INSERT_FILESYSTEMS)
    {
        // resolve shadows - disallow ones that are broken
        if (!(pObject = objResolveIfShadow(pObject)))
            return FALSE;

        // exclude non-file systems except disks
        if (!_somIsA(pObject, _WPFileSystem))
        {
            if (_somIsA(pObject, _WPDisk))
                return TRUE;

            return FALSE;
        }

        // include folders (including templates)
        if (_somIsA(pObject, _WPFolder))
            return TRUE;

        // all that's left are files
        if ((pcszFileMask) && (*pcszFileMask))
        {
            // file mask specified:
            CHAR szRealName[CCHMAXPATH];
            if (_wpQueryFilename(pObject, szRealName, FALSE))   // not q'fied
                return doshMatch(pcszFileMask, szRealName);
        }

        // no file mask:
        return TRUE;
    }

    // INSERT_FOLDERSONLY or INSERT_FOLDERSANDDISKS

    // no templates
    if (_wpQueryStyle(pObject) & OBJSTYLE_TEMPLATE)
        return FALSE;

    if (_somIsA(pObject, _WPFolder))
        return TRUE;

    // see if this is a disk or network share
    if (ulFoldersOnly == INSERT_FOLDERSANDDISKS)
    {
        WPObject *pobj2;

        if (_somIsA(pObject, _WPDisk))
            return TRUE;

        if ((pobj2 = objResolveIfShadow(pObject)))
            return ctsIsSharedDir(pobj2);
    }

    return FALSE;
}

/*
 *@@ IsObjectInCnr:
 *      returns TRUE if pObject has already been
 *      inserted into hwndCnr.
 *
 */

BOOL fdrvIsObjectInCnr(WPObject *pObject,
                       HWND hwndCnr)
{
    BOOL    brc = FALSE;
    BOOL    fLocked = FALSE;

    TRY_LOUD(excpt1)
    {
        if (fLocked = !_wpRequestObjectMutexSem(pObject, SEM_INDEFINITE_WAIT))
        {
            PUSEITEM    pUseItem = NULL;
            for (pUseItem = _wpFindUseItem(pObject, USAGE_RECORD, NULL);
                 pUseItem;
                 pUseItem = _wpFindUseItem(pObject, USAGE_RECORD, pUseItem))
            {
                // USAGE_RECORD specifies where this object is
                // currently inserted
                PRECORDITEM pRecordItem = (PRECORDITEM)(pUseItem + 1);

                if (hwndCnr == pRecordItem->hwndCnr)
                {
                    brc = TRUE;
                    break;
                }
            }
        }
    }
    CATCH(excpt1)
    {
    } END_CATCH();

    if (fLocked)
        _wpReleaseObjectMutexSem(pObject);

    return brc;
}

/*
 *@@ CLEARCNRDATA:
 *
 *@@added V1.0.1 (2002-11-30) [umoeller]
 */

typedef struct _CLEARCNRDATA
{
    BOOL        fUnlock;
    LINKLIST    llRecords;                 // linked list of PMINIRECORDCORE structs
} CLEARCNRDATA, *PCLEARCNRDATA;

/*
 *@@ fncbClearCnr:
 *      callback for cnrhForAllRecords, specified
 *      by fdrvClearContainer.
 *
 *@@added V1.0.0 (2002-09-09) [umoeller]
 *@@changed V1.0.1 (2002-11-30) [umoeller]: changed callback param for linklist (speedup)
 */

STATIC ULONG XWPENTRY fncbClearCnr(HWND hwndCnr,
                                   PRECORDCORE precc,
                                   ULONG ulData)      // callback parameter
{
    PCLEARCNRDATA pData = (PCLEARCNRDATA)ulData;

    // WPObject *pobj = OBJECT_FROM_PREC(precc);

    /* _wpCnrRemoveObject(pobj,
                       hwndCnr); */
            // this is _very_ inefficient. Removing 1,000 records
            // can take up to a second this way. Use wpclsRemoveObjects
            // instead.

    // add to list for wpclsRemoveObjects
    lstAppendItem(&pData->llRecords, precc);    // V1.0.1 (2002-11-30) [umoeller]

    if (pData->fUnlock)
        // this is the "unlock" that corresponds to the "lock"
        // that was issued on every object by wpPopulate
        _wpUnlockObject(OBJECT_FROM_PREC(precc));

    return 0;
}

/*
 *@@ fdrvClearContainer:
 *      removes all objects that were inserted into the
 *      specified container and updates the USEITEM's
 *      correctly.
 *
 *      Returns the no. of records that were removed.
 *
 *      flClear can be set to any combination of the
 *      following:
 *
 *      --  Only if CLEARFL_TREEVIEW is set, we will
 *          recurse into subrecords and remove these
 *          also. This makes cleanup a lot faster for
 *          non-tree views.
 *
 *      --  If CLEARFL_UNLOCKOBJECTS is set, we will
 *          unlock every object that we remove once.
 *
 *@@changed V1.0.0 (2002-09-13) [umoeller]: added flClear
 *@@changed V1.0.0 (2002-11-23) [umoeller]: fixed broken lazy drag in progress @@fixes 225
 *@@changed V1.0.1 (2002-11-30) [umoeller]: major speedup @@fixes 270
 */

ULONG fdrvClearContainer(HWND hwndCnr,      // in: cnr to clear
                         ULONG flClear)     // in: CLEARFL_* flags
{
    ULONG       ulrc;
    PDRAGINFO   pdrgInfo;
    CLEARCNRDATA data;
    ULONG       cRecords;

    HPOINTER    hptrOld = winhSetWaitPointer();     // V1.0.1 (2002-11-30) [umoeller]

    // disable window updates
    // for the cnr or this takes forever
    WinEnableWindowUpdate(hwndCnr, FALSE);

    // if this container is the current source of a
    // lazy drag operation, cancel the lazy drag
    // before clearing the container
    if (    (DrgQueryDragStatus() == DGS_LAZYDRAGINPROGRESS)
         && (pdrgInfo = DrgQueryDraginfoPtr(NULL))
         && (pdrgInfo->hwndSource == hwndCnr)
       )
        DrgCancelLazyDrag();

    data.fUnlock = (flClear & CLEARFL_UNLOCKOBJECTS);
    lstInit(&data.llRecords, FALSE);

    // recurse through all records to build a list of
    // records to remove and shoot at them all at once
    // V1.0.1 (2002-11-30) [umoeller]
    ulrc = cnrhForAllRecords(hwndCnr,
                             // recurse only for tree view
                             (flClear & CLEARFL_TREEVIEW)
                                ? NULL
                                : (PRECORDCORE)-1,
                             fncbClearCnr,
                             // callback parameter:
                             (ULONG)&data);        // V1.0.1 (2002-11-30) [umoeller]

    if (cRecords = lstCountItems(&data.llRecords))
    {
        PMINIRECORDCORE *paRecords;
        if (paRecords = (PMINIRECORDCORE*)malloc(cRecords * sizeof(PMINIRECORDCORE)))
        {
            PMINIRECORDCORE *pThis = paRecords;
            PLISTNODE pNode;

            FOR_ALL_NODES(&data.llRecords, pNode)
            {
                *pThis++ = (PMINIRECORDCORE)pNode->pItemData;
            }

            _wpclsRemoveObjects(_WPObject,
                                hwndCnr,
                                (PVOID*)paRecords,
                                cRecords,
                                FALSE);

            free(paRecords);
        }
    }


    lstClear(&data.llRecords);

    WinSetPointer(HWND_DESKTOP, hptrOld);

    WinShowWindow(hwndCnr, TRUE);

    return ulrc;
}


