Index: trunk/src/helpers/_call_filedlg.c
===================================================================
--- trunk/src/helpers/_call_filedlg.c	(revision 242)
+++ trunk/src/helpers/_call_filedlg.c	(revision 243)
@@ -15,4 +15,5 @@
 #define INCL_WINSTDCNR
 #define INCL_WINSTDFILE
+#define INCL_WINTIMER
 
 #include <os2.h>
@@ -29,5 +30,7 @@
 #include "helpers\cnrh.h"
 #include "helpers\comctl.h"
+#include "helpers\nls.h"
 #include "helpers\standards.h"
+#include "helpers\stringh.h"
 #include "helpers\winh.h"
 #include "helpers\gpih.h"
@@ -106,4 +109,132 @@
 } GROUPRECORD, *PGROUPRECORD;
 
+PFNWP G_pfnwpFrameOrig;
+
+#define RECORD_COUNT        20000
+
+PGROUPRECORD G_paRecs[RECORD_COUNT];
+
+CHAR    G_szText[] = "Longer test string with increasing length";
+
+/*
+ *@@ fnwpSubclFrame:
+ *
+ */
+
+MRESULT EXPENTRY fnwpSubclFrame(HWND hwndFrame, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+    MRESULT mrc = 0;
+
+    switch (msg)
+    {
+        case WM_COMMAND:
+            if (SHORT1FROMMP(mp1) == 1000)
+                ShowFileDlg(hwndFrame);
+        break;
+
+        case WM_CONTROL:
+            if (SHORT1FROMMP(mp1) == FID_CLIENT)
+            {
+                switch (SHORT2FROMMP(mp1))
+                {
+                    case CN_VIEWPORTCHANGED:
+                    {
+                        PCNRVIEWPORT pcvp = (PCNRVIEWPORT)mp2;
+
+                        CHAR szTemp[100],
+                             szNLS1[20],
+                             szNLS2[20],
+                             szNLS3[20],
+                             szNLS4[20],
+                             szNLS5[20],
+                             szNLS6[20];
+                        sprintf(szTemp,
+                                "Win: (%s/%s), WArea: (%s/%s), scrl: (%s/%s)",
+                                nlsThousandsULong(szNLS1,
+                                                  pcvp->szlWin.cx,
+                                                  '.'),
+                                nlsThousandsULong(szNLS2,
+                                                  pcvp->szlWin.cy,
+                                                  '.'),
+                                nlsThousandsULong(szNLS3,
+                                                  pcvp->szlWorkarea.cx,
+                                                  '.'),
+                                nlsThousandsULong(szNLS4,
+                                                  pcvp->szlWorkarea.cy,
+                                                  '.'),
+                                nlsThousandsULong(szNLS5,
+                                                  pcvp->ptlScroll.x,
+                                                  '.'),
+                                nlsThousandsULong(szNLS6,
+                                                  pcvp->ptlScroll.y,
+                                                  '.'));
+                        WinSetWindowText(WinWindowFromID(hwndFrame, FID_STATUSBAR),
+                                         szTemp);
+                    }
+                    break;
+
+                    case CN_ENTER:
+                    {
+                        CHAR sz[100];
+                        PCSZ pcsz;
+                        PNOTIFYRECORDENTER pnre = (PNOTIFYRECORDENTER)mp2;
+                        if (!pnre->pRecord)
+                            pcsz = "Whitespace";
+                        else
+                        {
+                            sprintf(sz, "group %d", ((PGROUPRECORD)pnre->pRecord)->gid);
+                            pcsz = sz;
+                        }
+                        winhDebugBox(hwndFrame,
+                                     "Enter pressed",
+                                     pcsz);
+                    }
+                    break;
+                }
+            }
+        break;
+
+        case WM_TIMER:
+            if ((ULONG)mp1 == 1)
+            {
+                static ul = 0,
+                       ul2 = 1;
+
+                HWND    hwndCnr = WinWindowFromID(hwndFrame, FID_CLIENT);
+
+                PGROUPRECORD precThis = G_paRecs[ul];
+
+                ULONG ulTimeNow;
+                DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
+                                &ulTimeNow,
+                                sizeof(ulTimeNow));
+
+                strhncpy0(precThis->szGroupName,
+                          G_szText,
+                          ul2++);
+
+                WinSendMsg(hwndCnr,
+                           CM_INVALIDATERECORD,
+                           (MPARAM)&precThis,
+                           MPFROM2SHORT(1, CMA_ERASE | CMA_TEXTCHANGED));
+
+                if (++ul >= RECORD_COUNT)
+                    ul = 0;
+                if (ul2 > strlen(G_szText))
+                    ul2 = 1;
+            }
+            else
+                mrc = G_pfnwpFrameOrig(hwndFrame, msg, mp1, mp2);
+        break;
+
+        default:
+            mrc = G_pfnwpFrameOrig(hwndFrame, msg, mp1, mp2);
+        break;
+
+    }
+
+    return mrc;
+}
+
 /*
  *@@ main:
@@ -203,14 +334,14 @@
                   | FCF_SHELLPOSITION
                   | FCF_TASKLIST,
-            0, // XFCF_TOOLBAR | XFCF_FORCETBOWNER | XFCF_STATUSBAR,
+            XFCF_TOOLBAR | XFCF_FORCETBOWNER | XFCF_STATUSBAR,
             WS_VISIBLE,                         // ulFrameStyle
             "Test File Dialog",                 // pcszFrameTitle
             0,                                  // ulResourcesID
-#if 1
+#ifdef __USE_PM_CNR__
+            WC_CONTAINER,
+#else
             WC_CCTL_CNR,
-#else
-            WC_CONTAINER,
 #endif
-            WS_VISIBLE,                         // flStyleClient
+            WS_VISIBLE | CCS_EXTENDSEL,       // flStyleClient
             0,                                  // ulID
             NULL,
@@ -262,4 +393,5 @@
     WinSendMsg(hwndFrame, WM_UPDATEFRAME, MPNULL, MPNULL);
 
+    if (G_pfnwpFrameOrig = WinSubclassWindow(hwndFrame, fnwpSubclFrame))
     {
         XFIELDINFO  xfi[4];
@@ -270,5 +402,5 @@
         // set up cnr details view
         xfi[i].ulFieldOffset = FIELDOFFSET(GROUPRECORD, gid);
-        xfi[i].pszColumnTitle = "Group ID";     // @@todo localize
+        xfi[i].pszColumnTitle = "gid";
         xfi[i].ulDataType = CFA_ULONG;
         xfi[i++].ulOrientation = CFA_RIGHT;
@@ -297,14 +429,21 @@
         } END_CNRINFO(hwndClient);
 
-        #define RECORD_COUNT        200
-
         if (preccFirst = (PGROUPRECORD)cnrhAllocRecords(hwndClient,
                                                         sizeof(GROUPRECORD),
                                                         RECORD_COUNT))
         {
-            PGROUPRECORD preccThis = preccFirst;
+            PGROUPRECORD preccThis = preccFirst,
+                         preccEmph;
             ULONG   ul = 0;
             while (preccThis)
             {
+                G_paRecs[ul] = preccThis;
+
+                if (ul & 1)
+                    preccThis->recc.flRecordAttr |= CRA_FILTERED;
+
+                if (ul == 4)
+                    preccEmph = preccThis;
+
                 preccThis->gid = ul++;
                 sprintf(preccThis->szGroupName, "group %d", preccThis->gid);
@@ -323,28 +462,16 @@
                               CRA_RECORDREADONLY,
                               RECORD_COUNT);
+
+            WinSendMsg(hwndClient,
+                       CM_SETRECORDEMPHASIS,
+                       (MPARAM)preccEmph,
+                       MPFROM2SHORT(TRUE, CRA_SELECTED));
         }
     }
 
+    WinStartTimer(hab, hwndFrame, 1, 100);
+
     while (WinGetMsg(hab, &qmsg, NULLHANDLE, 0, 0))
-    {
-        BOOL fDispatch = TRUE;
-
-        if (qmsg.hwnd == hwndFrame)
-        {
-            switch (qmsg.msg)
-            {
-                case WM_COMMAND:
-                    if (SHORT1FROMMP(qmsg.mp1) == 1000)
-                    {
-                        ShowFileDlg(hwndFrame);
-                        fDispatch = FALSE;
-                    }
-                break;
-            }
-        }
-
-        if (fDispatch)
-            WinDispatchMsg(hab, &qmsg);
-    }
+        WinDispatchMsg(hab, &qmsg);
 
     WinDestroyMsgQueue(hmq);
Index: trunk/src/helpers/cctl_checkcnr.c
===================================================================
--- trunk/src/helpers/cctl_checkcnr.c	(revision 242)
+++ trunk/src/helpers/cctl_checkcnr.c	(revision 243)
@@ -357,4 +357,5 @@
  *@@added V0.9.0 (99-11-29) [umoeller]
  *@@changed V0.9.18 (2002-03-03) [umoeller]: fixed bad orig win msg, other optimizations
+ *@@changed V1.0.1 (2003-01-22) [umoeller]: fixed wrong mouse pointers on WM_MOUSEMOVE
  */
 
@@ -457,4 +458,8 @@
                     }
                 }
+
+                // always call parent cos container changes mouse pointers
+                // V1.0.1 (2003-01-22) [umoeller]
+                mrc = pfnwpOrig(hwndCnr, msg, mp1, mp2);
             break;
 
Index: trunk/src/helpers/cctl_cnr.c
===================================================================
--- trunk/src/helpers/cctl_cnr.c	(revision 242)
+++ trunk/src/helpers/cctl_cnr.c	(revision 243)
@@ -28,4 +28,5 @@
 #define INCL_WINWINDOWMGR
 #define INCL_WINFRAMEMGR
+#define INCL_WININPUT
 #define INCL_WINSYS
 #define INCL_WINSCROLLBARS
@@ -62,9 +63,14 @@
  ********************************************************************/
 
-extern const SYSCOLORSET G_scsCnr =
-    {
-        TRUE,       // inherit presparams
-        SYSCLR_WINDOW,
-        SYSCLR_WINDOWTEXT
+extern const CCTLCOLOR G_scsCnr[] =
+    {
+            TRUE, PP_BACKGROUNDCOLOR, SYSCLR_WINDOW,
+            TRUE, PP_FOREGROUNDCOLOR, SYSCLR_WINDOWTEXT,
+            TRUE, PP_HILITEBACKGROUNDCOLOR, SYSCLR_HILITEBACKGROUND,
+            TRUE, PP_HILITEFOREGROUNDCOLOR, SYSCLR_HILITEFOREGROUND,
+            TRUE, PP_BORDERCOLOR, SYSCLR_WINDOWFRAME,
+            TRUE, PP_PAGEBACKGROUNDCOLOR, RGBCOL_WHITE,
+            TRUE, PP_PAGEFOREGROUNDCOLOR, RGBCOL_BLACK,
+            TRUE, PP_FIELDBACKGROUNDCOLOR, SYSCLR_SCROLLBAR
     };
 
@@ -75,5 +81,31 @@
  ********************************************************************/
 
-VOID cnrDrawString(HPS hps,
+/*
+ *@@ ctnrInit:
+ *
+ */
+
+VOID ctnrInit(HWND hwnd,
+              MPARAM mp2,
+              ULONG flMainCnrStyle,     // in: window style bits of main cnr
+              PDEFWINDATA pdwd)
+{
+    ctlInitDWD(hwnd,
+               mp2,
+               pdwd,
+               WinDefWindowProc,
+               (flMainCnrStyle & CCS_NOCONTROLPTR)
+                    ? CCS_NOSENDCTLPTR
+                    : 0,
+               G_scsCnr,
+               ARRAYITEMCOUNT(G_scsCnr));
+}
+
+/*
+ *@@ ctnrDrawString:
+ *
+ */
+
+VOID ctnrDrawString(HPS hps,
                    PCSZ pcsz,              // in: string to test
                    PRECTL prcl,            // in: clipping rectangle (inclusive!)
@@ -115,11 +147,142 @@
 
 /*
+ *@@ ctnrGetRecordRect:
+ *      returns the rectangle of the given record
+ *      converted to window coordinates. Works for
+ *      all views.
+ */
+
+VOID ctnrGetRecordRect(PCNRDATA pData,
+                       PRECTL prcl,
+                       const RECORDLISTITEM *prli)
+{
+    LONG    deltaWorkspace =   (   pData->scrw.szlWorkarea.cy
+                                 - pData->dwdContent.szlWin.cy)
+                             - pData->scrw.ptlScrollOfs.y;
+    prcl->xLeft = 0;
+    prcl->xRight = pData->dwdContent.szlWin.cx;
+    prcl->yBottom = prli->ptl.y - deltaWorkspace;
+    prcl->yTop = prcl->yBottom + prli->szlBox.cy;
+}
+
+/*
+ *@@ ctnrRepaintRecord:
+ *
+ */
+
+BOOL ctnrRepaintRecord(PCNRDATA pData,
+                         const RECORDLISTITEM *prli)
+{
+    RECTL   rcl;
+    ctnrGetRecordRect(pData, &rcl, prli);
+    ++rcl.xRight;       // for separators, if any
+    return WinInvalidateRect(pData->dwdContent.hwnd, &rcl, FALSE);
+}
+
+/*
+ *@@ ctnrQuerySelMode:
+ *      returns one of CCS_EXTENDSEL, CCS_MULTIPLESEL, or
+ *      CCS_SINGLESEL, depending on the cnr's current view
+ *      and window style bits.
+ */
+
+ULONG ctnrQuerySelMode(PCNRDATA pData)
+{
+    // in tree view, there is always only single-sel mode
+    if (!(pData->CnrInfo.flWindowAttr & CV_TREE))
+    {
+        // not tree view: then check window style bits
+        ULONG   flStyle = winhQueryWindowStyle(pData->dwdMain.hwnd);
+        if (flStyle & CCS_EXTENDSEL)
+            return CCS_EXTENDSEL;
+        else if (flStyle & CCS_MULTIPLESEL)
+            return CCS_MULTIPLESEL;
+
+        // this appears to be what the pm cnr does...
+        // the CCS_SINGLESEL is totally superfluous cos
+        // if none of the "selection style" bits are
+        // set, the cnr operates in "single sel" mode
+    }
+
+    return CCS_SINGLESEL;
+}
+
+/*
+ *@@ ctnrChangeEmphasis:
+ *
+ */
+
+BOOL ctnrChangeEmphasis(PCNRDATA pData,
+                        PRECORDLISTITEM prliSet,
+                        BOOL fTurnOn,
+                        ULONG fsEmphasis)
+{
+    PRECORDLISTITEM prliOld;
+    ULONG   flRecordAttrOld = prliSet->flRecordAttr;
+
+/* #define CCS_EXTENDSEL             0x00000001L
+#define CCS_MULTIPLESEL           0x00000002L
+#define CCS_SINGLESEL             0x00000004L */
+
+    ULONG ulSel = ctnrQuerySelMode(pData);
+
+    if (fTurnOn)
+    {
+        ULONG   flMask;
+        if (ulSel == CCS_SINGLESEL)
+            flMask = CRA_CURSORED | CRA_SELECTED;
+        else
+            flMask = CRA_CURSORED;
+
+        if (fsEmphasis & flMask)
+        {
+            if (prliOld = pData->prliCursored)
+            {
+                prliOld->flRecordAttr &= ~flMask;
+                ctnrRepaintRecord(pData, prliOld);
+            }
+
+            pData->prliCursored = prliSet;
+        }
+
+        prliSet->flRecordAttr |= fsEmphasis;
+    }
+    else
+        prliSet->flRecordAttr &= ~fsEmphasis;
+
+    if (flRecordAttrOld != prliSet->flRecordAttr)
+        ctnrRepaintRecord(pData, prliSet);
+
+    return TRUE;
+}
+
+/*
+ *@@ ctnrRecordEnter:
+ *      helper function when a record gets double-clicked
+ *      upon or when "Enter" key gets pressed.
+ */
+
+VOID ctnrRecordEnter(PCNRDATA pData,
+                     const RECORDLISTITEM *prli,
+                     BOOL fKeyboard)
+{
+    NOTIFYRECORDENTER nre;
+    nre.hwndCnr = pData->dwdMain.hwnd;
+    nre.fKey = fKeyboard;
+    nre.pRecord = (prli) ? (PRECORDCORE)prli->precc : NULL;
+
+    ctlSendWmControl(nre.hwndCnr,
+                     CN_ENTER,
+                     &nre);
+}
+
+/*
  *@@ CreateChild:
  *      creates a container child window.
  */
 
-HWND CreateChild(PCNRDATA pData,
-                 PCSZ pcszClass,
-                 ULONG id)
+STATIC HWND CreateChild(PCNRDATA pData,
+                        PCSZ pcszClass,
+                        ULONG id)
 {
     return WinCreateWindow(pData->dwdMain.hwnd,
@@ -143,12 +306,12 @@
  */
 
-PDETAILCOLUMN FindColumnFromFI(PCNRDATA pData,
-                               const FIELDINFO *pfi,
-                               PLISTNODE *ppNode)       // out: listnode of column
+STATIC PDETAILCOLUMN FindColumnFromFI(PCNRDATA pData,
+                                      const FIELDINFO *pfi,
+                                      PLISTNODE *ppNode)       // out: listnode of column
 {
     PLISTNODE pNode;
     FOR_ALL_NODES(&pData->llColumns, pNode)
     {
-        PDETAILCOLUMN pCol = pNode->pItemData;
+        PDETAILCOLUMN pCol = (PDETAILCOLUMN)pNode->pItemData;
         if (pCol->pfi == pfi)
         {
@@ -164,10 +327,10 @@
 
 /*
- *@@ FindListNodeForRecc:
- *
- */
-
-PLISTNODE FindListNodeForRecc(PCNRDATA pData,
-                              const RECORDCORE *precc)
+ *@@ ctnrFindListNodeForRecc:
+ *
+ */
+
+PLISTNODE ctnrFindListNodeForRecc(PCNRDATA pData,
+                                  const RECORDCORE *precc)
 {
     RECORDTREEITEM  *pti;
@@ -180,4 +343,52 @@
 }
 
+VOID SendViewportChanged(PCNRDATA pData)
+{
+    CNRVIEWPORT cvp;
+    cvp.hwndCnr = pData->dwdMain.hwnd;
+    cvp.szlWorkarea = pData->scrw.szlWorkarea;
+    cvp.szlWin = pData->dwdContent.szlWin;
+    cvp.ptlScroll = pData->scrw.ptlScrollOfs;
+
+    ctlSendWmControl(pData->dwdMain.hwnd,
+                     CN_VIEWPORTCHANGED,
+                     &cvp);
+}
+
+/*
+ *@@ ctnrUpdateScrollbars:
+ *
+ */
+
+VOID ctnrUpdateScrollbars(PCNRDATA pData,
+                          LONG cx,
+                          LONG cy)
+{
+    BOOL    fNotify = FALSE;
+
+    if (cx && pData->scrw.hwndHScroll)
+    {
+        winhUpdateScrollBar(pData->scrw.hwndHScroll,
+                            cx,
+                            pData->scrw.szlWorkarea.cx,
+                            pData->scrw.ptlScrollOfs.x,
+                            FALSE);
+        fNotify = TRUE;
+    }
+
+    if (cy && pData->scrw.hwndVScroll)
+    {
+        winhUpdateScrollBar(pData->scrw.hwndVScroll,
+                            cy,
+                            pData->scrw.szlWorkarea.cy,
+                            pData->scrw.ptlScrollOfs.y,
+                            FALSE);
+        fNotify = TRUE;
+    }
+
+    if (fNotify)
+        SendViewportChanged(pData);
+}
+
 /* ******************************************************************
  *
@@ -191,5 +402,7 @@
  */
 
-MRESULT CnrCreate(HWND hwnd, MPARAM mp1, MPARAM mp2)
+STATIC MRESULT CnrCreate(HWND hwnd,
+                         MPARAM mp1,
+                         MPARAM mp2)
 {
     PCNRDATA    pData;
@@ -202,11 +415,10 @@
 
     // initialize DEFWINDOWDATA
-    ctlInitDWD(hwnd,
-               mp2,
-               &pData->dwdMain,
-               WinDefWindowProc,
-               &G_scsCnr);
-
-    if (winhQueryWindowStyle(hwnd) & CCS_MINIRECORDCORE)
+    ctnrInit(hwnd,
+             mp2,
+             ((PCREATESTRUCT)mp2)->flStyle,
+             &pData->dwdMain);
+
+    if (((PCREATESTRUCT)mp2)->flStyle & CCS_MINIRECORDCORE)
         pData->fMiniRecords = TRUE;
 
@@ -229,10 +441,8 @@
     nlsQueryCountrySettings(&pData->cs);
 
-    winhCreateScrollBars(hwnd,
-                         &pData->hwndVScroll,
-                         &pData->hwndHScroll);
-
-    pData->cxScrollBar = WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL);
-    pData->cyScrollBar = WinQuerySysValue(HWND_DESKTOP, SV_CYHSCROLL);
+    winhCreateScroller(hwnd,
+                       &pData->scrw,
+                       CID_VSCROLL,
+                       CID_HSCROLL);
 
     return (MRESULT)FALSE;
@@ -248,90 +458,78 @@
  */
 
-VOID CnrSem2(HWND hwnd, ULONG fl)
-{
-    PCNRDATA    pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
-    {
-        if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
-        {
-            if (fl & (DDFL_INVALIDATECOLUMNS | DDFL_INCALIDATERECORDS))
+STATIC VOID CnrSem2(PCNRDATA pData,
+                    ULONG fl)
+{
+    HWND    hwnd = pData->dwdMain.hwnd;
+
+    if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
+    {
+        if (fl & (DDFL_INVALIDATECOLUMNS | DDFL_INVALIDATERECORDS | DDFL_INVALIDATESOME))
+        {
+            HPS hps = WinGetPS(hwnd);
+            cdtlRecalcDetails(pData, hps, &fl);
+            WinReleasePS(hps);
+        }
+
+        if (fl & (DDFL_WINDOWSIZECHANGED | DDFL_WORKAREACHANGED))
+        {
+            LONG    y = 0,
+                    cx = pData->dwdMain.szlWin.cx,
+                    cy = pData->dwdMain.szlWin.cy - pData->cyColTitlesBox;
+
+            if (pData->scrw.hwndVScroll)
+                cx -= pData->scrw.cxScrollBar;
+            if (pData->scrw.hwndHScroll)
             {
-                HPS hps = WinGetPS(hwnd);
-                cdtlRecalcDetails(pData, hps, &fl);
-                WinReleasePS(hps);
-
-                if (fl & DDFL_WINDOWSIZECHANGED)
-                    WinInvalidateRect(pData->dwdMain.hwnd, NULL, TRUE);
+                y += pData->scrw.cyScrollBar;
+                cy -= pData->scrw.cyScrollBar;
             }
 
-            if (fl & (DDFL_WINDOWSIZECHANGED | DDFL_WORKAREACHANGED))
+            _PmpfF(("cyColTitlesBox %d, new cy: %d", pData->cyColTitlesBox, cy));
+
+            if (fl & DDFL_WINDOWSIZECHANGED)
+                WinSetWindowPos(pData->dwdContent.hwnd,
+                                HWND_TOP,
+                                0,
+                                y,
+                                cx,
+                                cy,
+                                SWP_MOVE | SWP_SIZE);
+                                        // SWP_MOVE is required or PM will move our
+                                        // subwindow to some adjustment position
+
+            if (pData->scrw.hwndVScroll)
             {
-                LONG    y = 0,
-                        cx = pData->dwdMain.szlWin.cx,
-                        cy = pData->dwdMain.szlWin.cy - pData->cyColTitlesBox;
-
-                if (pData->hwndVScroll)
-                    cx -= pData->cxScrollBar;
-                if (pData->hwndHScroll)
-                {
-                    y += pData->cyScrollBar;
-                    cy -= pData->cyScrollBar;
-                }
-
-                _PmpfF(("cyColTitlesBox %d, new cy: %d", pData->cyColTitlesBox, cy));
-
-                if (fl & DDFL_WINDOWSIZECHANGED)
-                    WinSetWindowPos(pData->hwndDetails,
-                                    HWND_TOP,
-                                    0,
-                                    y,
-                                    cx,
-                                    cy,
-                                    SWP_MOVE | SWP_SIZE);
-                                            // SWP_MOVE is required or PM will move our
-                                            // subwindow to some adjustment position
-
-                if (pData->hwndVScroll)
-                {
-                    WinSetWindowPos(pData->hwndVScroll,
-                                    HWND_TOP,
-                                    cx,
-                                    y,
-                                    pData->cxScrollBar,
-                                    cy,
-                                    SWP_MOVE | SWP_SIZE);
-
-                    _PmpfF(("updating VScroll, cy: %d, szlWorkarea.cy: %d",
-                            cy,
-                            pData->szlWorkarea.cy));
-
-                    winhUpdateScrollBar(pData->hwndVScroll,
-                                        cy,
-                                        pData->szlWorkarea.cy,
-                                        pData->ptlScrollOfs.y,
-                                        FALSE);
-                }
-
-                if (pData->hwndHScroll)
-                {
-                    WinSetWindowPos(pData->hwndHScroll,
-                                    HWND_TOP,
-                                    0,
-                                    0,
-                                    cx,
-                                    pData->cyScrollBar,
-                                    SWP_MOVE | SWP_SIZE);
-
-                    _PmpfF(("updating HScroll, cx: %d, szlWorkarea.cx: %d",
-                            cx,
-                            pData->szlWorkarea.cx));
-
-                    winhUpdateScrollBar(pData->hwndHScroll,
-                                        cx,
-                                        pData->szlWorkarea.cx,
-                                        pData->ptlScrollOfs.x,
-                                        FALSE);
-                }
+                WinSetWindowPos(pData->scrw.hwndVScroll,
+                                HWND_TOP,
+                                cx,
+                                y,
+                                pData->scrw.cxScrollBar,
+                                cy,
+                                SWP_MOVE | SWP_SIZE);
+
+                _PmpfF(("updating VScroll, cy: %d, scrw.szlWorkarea.cy: %d",
+                        cy,
+                        pData->scrw.szlWorkarea.cy));
             }
+
+            if (pData->scrw.hwndHScroll)
+            {
+                WinSetWindowPos(pData->scrw.hwndHScroll,
+                                HWND_TOP,
+                                0,
+                                0,
+                                cx,
+                                pData->scrw.cyScrollBar,
+                                SWP_MOVE | SWP_SIZE);
+
+                _PmpfF(("updating HScroll, cx: %d, scrw.szlWorkarea.cx: %d",
+                        cx,
+                        pData->scrw.szlWorkarea.cx));
+            }
+
+            ctnrUpdateScrollbars(pData,
+                                 cx,
+                                 cy);
         }
     }
@@ -341,14 +539,17 @@
  *@@ CnrPaint:
  *      implementation for WM_PAINT in fnwpCnr.
- */
-
-VOID CnrPaint(HWND hwnd)
-{
-    HPS         hps;
-    PCNRDATA    pData;
-    RECTL       rclPaint;
-
-    if (    (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
-         && (hps = WinBeginPaint(hwnd, NULLHANDLE, &rclPaint))
+ *
+ *      This paints only the part of the container that belongs
+ *      to the main container window, which is the title area
+ *      and/or the details column headings.
+ */
+
+STATIC VOID CnrPaint(PCNRDATA pData)
+{
+    HPS     hps;
+    RECTL   rclPaint;
+
+    if (    (pData)
+         && (hps = WinBeginPaint(pData->dwdMain.hwnd, NULLHANDLE, &rclPaint))
        )
     {
@@ -376,5 +577,5 @@
                 WinFillRect(hps,
                             &rclPaint,
-                            pData->dwdMain.lcolBackground);
+                            ctlQueryColor(&pData->dwdMain, CTLCOL_BGND));
 
                 FOR_ALL_NODES(&pData->llColumns, pColNode)
@@ -386,6 +587,6 @@
                     ULONG       cRow;
 
-                    rcl.xLeft = pCol->xLeft + COLUMN_PADDING_X - pData->ptlScrollOfs.x;
-                    rcl.xRight = rcl.xLeft + pCol->cxTotal;
+                    rcl.xLeft = pCol->xLeft + COLUMN_PADDING_X - pData->scrw.ptlScrollOfs.x;
+                    rcl.xRight = rcl.xLeft + pCol->cxContent;
 
                     // we start out at the top and work our way down,
@@ -402,5 +603,5 @@
                     else
                     {
-                        cnrDrawString(hps,
+                        ctnrDrawString(hps,
                                       (PCSZ)pfi->pTitleData,
                                       &rcl,
@@ -438,11 +639,11 @@
  */
 
-MRESULT CnrWindowPosChanged(HWND hwnd,
-                            MPARAM mp1,
-                            MPARAM mp2)
+STATIC MRESULT CnrWindowPosChanged(PCNRDATA pData,
+                                   MPARAM mp1,
+                                   MPARAM mp2)
 {
     MRESULT mrc = 0;
-    PCNRDATA pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+
+    if (pData)
     {
         mrc = ctlDefWindowProc(&pData->dwdMain, WM_WINDOWPOSCHANGED, mp1, mp2);
@@ -450,5 +651,5 @@
         if (((PSWP)mp1)->fl & SWP_SIZE)
         {
-            WinPostMsg(hwnd,
+            WinPostMsg(pData->dwdMain.hwnd,
                        WM_SEM2,
                        (MPARAM)DDFL_WINDOWSIZECHANGED,
@@ -461,17 +662,18 @@
 
 /*
- *@@ cnrPresParamChanged:
+ *@@ ctnrPresParamChanged:
  *      implementation for WM_PRESPARAMCHANGED for both
  *      fnwpCnr and fnwpCnrDetails.
  */
 
-VOID cnrPresParamChanged(HWND hwnd,
+VOID ctnrPresParamChanged(HWND hwnd,         // in: either main cnr or content subwindow
                          ULONG ulpp)
 {
-    PCNRDATA pData;
+    PCNRDATA    pData;
     if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
     {
-        // note, we use the dwdMain buffer here cos
-        // we share presparams with the cnr
+        // note, no matter what hwnd is passed in,
+        // we use the dwdMain buffer here cos we share presparams
+        // between all cnr child windows
         ctlRefreshColors(&pData->dwdMain);
 
@@ -480,27 +682,33 @@
             case 0:     // layout palette thing dropped
             case PP_FONTNAMESIZE:
-                if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
+                if (!pData->fSettingPP)
                 {
-                    // if we got this on the details window,
-                    // set it on the main cnr as well, and
-                    // vice versa
-                    PSZ pszFont;
-                    if (pszFont = winhQueryWindowFont(hwnd))
+                    pData->fSettingPP = TRUE;
+
+                    if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
                     {
-                        HWND hwndOther;
-                        DosBeep(1000, 10);
-                        if (hwnd == pData->dwdMain.hwnd)
-                            hwndOther = pData->dwdContent.hwnd;
-                        else
-                            hwndOther = pData->dwdMain.hwnd;
-
-                        winhSetWindowFont(hwndOther, pszFont);
-                        free(pszFont);
+                        // if we got this on the contents window,
+                        // set it on the main cnr as well, and
+                        // vice versa
+                        PSZ pszFont;
+                        if (pszFont = winhQueryWindowFont(hwnd))
+                        {
+                            HWND hwndOther;
+                            if (hwnd == pData->dwdMain.hwnd)
+                                hwndOther = pData->dwdContent.hwnd;
+                            else
+                                hwndOther = pData->dwdMain.hwnd;
+
+                            winhSetWindowFont(hwndOther, pszFont);
+                            free(pszFont);
+                        }
+
+                        WinPostMsg(pData->dwdMain.hwnd,
+                                   WM_SEM2,
+                                   (MPARAM)(DDFL_INVALIDATECOLUMNS | DDFL_INVALIDATERECORDS),
+                                   0);
                     }
 
-                    WinPostMsg(pData->dwdMain.hwnd,
-                               WM_SEM2,
-                               (MPARAM)(DDFL_INVALIDATECOLUMNS | DDFL_INCALIDATERECORDS),
-                               0);
+                    pData->fSettingPP = FALSE;
                 }
             break;
@@ -518,23 +726,35 @@
  */
 
-VOID CnrScroll(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
-{
-    PCNRDATA pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
-    {
+STATIC VOID CnrScroll(PCNRDATA pData,
+                      ULONG msg,
+                      MPARAM mp1,
+                      MPARAM mp2)
+{
+    if (pData)
+    {
+        BOOL    fNotify = FALSE;
+
         if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
         {
-            POINTL  ptlScroll;
+            POINTL          ptlScroll;
+            NOTIFYSCROLL    ns;
+            ns.hwndCnr = pData->dwdMain.hwnd;
+
             if (msg == WM_HSCROLL)
             {
                 ptlScroll.y = 0;
-                if (ptlScroll.x = winhHandleScrollMsg2(pData->hwndHScroll,
-                                                       &pData->ptlScrollOfs.x,
-                                                       pData->dwdContent.szlWin.cx,
-                                                       pData->szlWorkarea.cx,
-                                                       5,
-                                                       msg,
-                                                       mp2))
+                if (ptlScroll.x = winhHandleScrollMsg(pData->scrw.hwndHScroll,
+                                                      &pData->scrw.ptlScrollOfs.x,
+                                                      pData->dwdContent.szlWin.cx,
+                                                      pData->scrw.szlWorkarea.cx,
+                                                      5,
+                                                      msg,
+                                                      mp2))
                 {
+                    winhScrollWindow(pData->dwdContent.hwnd,
+                                     NULL,
+                                     &ptlScroll);
+
+                    // scroll main cnr with detail titles too
                     if (pData->CnrInfo.flWindowAttr & CA_DETAILSVIEWTITLES)
                     {
@@ -544,11 +764,16 @@
                         rclClip.yBottom = pData->dwdMain.szlWin.cy - pData->cyColTitlesBox;
                         rclClip.yTop = pData->dwdMain.szlWin.cy;
-                        winhScrollWindow(hwnd,
+                        winhScrollWindow(pData->dwdMain.hwnd,
                                          &rclClip,
                                          &ptlScroll);
                     }
-                    winhScrollWindow(pData->hwndDetails,
-                                     NULL,
-                                     &ptlScroll);
+
+                    ns.lScrollInc = ptlScroll.y;
+                    ns.fScroll = CMA_HORIZONTAL;        // @@todo details flags
+                    ctlSendWmControl(pData->dwdMain.hwnd,
+                                     CN_SCROLL,
+                                     &ns);
+
+                    fNotify = TRUE;
                 }
             }
@@ -556,16 +781,29 @@
             {
                 ptlScroll.x = 0;
-                if (ptlScroll.y = winhHandleScrollMsg2(pData->hwndVScroll,
-                                                       &pData->ptlScrollOfs.y,
-                                                       pData->dwdContent.szlWin.cy,
-                                                       pData->szlWorkarea.cy,
-                                                       5,
-                                                       msg,
-                                                       mp2))
-                    winhScrollWindow(pData->hwndDetails,
+                if (ptlScroll.y = winhHandleScrollMsg(pData->scrw.hwndVScroll,
+                                                      &pData->scrw.ptlScrollOfs.y,
+                                                      pData->dwdContent.szlWin.cy,
+                                                      pData->scrw.szlWorkarea.cy,
+                                                      5,
+                                                      msg,
+                                                      mp2))
+                {
+                    winhScrollWindow(pData->dwdContent.hwnd,
                                      NULL,
                                      &ptlScroll);
+
+                    ns.lScrollInc = ptlScroll.x;
+                    ns.fScroll = CMA_VERTICAL;        // @@todo details flags
+                    ctlSendWmControl(pData->dwdMain.hwnd,
+                                     CN_SCROLL,
+                                     &ns);
+
+                    fNotify = TRUE;
+                }
             }
         }
+
+        if (fNotify)
+            SendViewportChanged(pData);
     }
 }
@@ -576,8 +814,7 @@
  */
 
-VOID CnrDestroy(HWND hwnd)
-{
-    PCNRDATA pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+STATIC VOID CnrDestroy(PCNRDATA pData)
+{
+    if (pData)
     {
         // free all data that we ever allocated;
@@ -605,10 +842,9 @@
  */
 
-USHORT CnrQueryCnrInfo(HWND hwnd,
-                       PCNRINFO pci,        // in: mp1 of CM_QUERYCNRINFO
-                       USHORT cb)           // in: mp2 of CM_QUERYCNRINFO
-{
-    PCNRDATA pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+STATIC USHORT CnrQueryCnrInfo(PCNRDATA pData,
+                              PCNRINFO pci,        // out: target buffer (mp1 of CM_QUERYCNRINFO)
+                              USHORT cb)           // in: mp2 of CM_QUERYCNRINFO
+{
+    if (pData)
     {
         USHORT cbCopied = max(cb, sizeof(CNRINFO));
@@ -629,10 +865,9 @@
  */
 
-BOOL CnrSetCnrInfo(HWND hwnd,
-                   PCNRINFO pci,        // in: mp1 of CM_SETCNRINFO
-                   ULONG flCI)          // in: CMA_* flags in mp2 of CM_SETCNRINFO
-{
-    PCNRDATA pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+STATIC BOOL CnrSetCnrInfo(PCNRDATA pData,
+                          PCNRINFO pci,        // in: mp1 of CM_SETCNRINFO
+                          ULONG flCI)          // in: CMA_* flags in mp2 of CM_SETCNRINFO
+{
+    if (pData)
     {
         ULONG   flDirty = 0;
@@ -680,9 +915,9 @@
                 if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
                 {
-                    if (!pData->hwndDetails)
+                    if (!pData->dwdContent.hwnd)
                     {
-                        if (pData->hwndDetails = CreateChild(pData,
-                                                             WC_CCTL_CNR_DETAILS,
-                                                             CID_LEFTDVWND))
+                        if (pData->dwdContent.hwnd = CreateChild(pData,
+                                                                 WC_CCTL_CNR_DETAILS,
+                                                                 CID_LEFTDVWND))
                         {
                             flDirty = DDFL_ALL;
@@ -692,5 +927,5 @@
                 else
                 {
-                    winhDestroyWindow(&pData->hwndDetails);
+                    winhDestroyWindow(&pData->dwdContent.hwnd);
                 }
             }
@@ -782,4 +1017,6 @@
             // flWindowAttr field is not specified, these lines are not drawn.
             pData->CnrInfo.cxTreeLine = pci->cxTreeLine;
+
+            // @@todo recalc window components, repaint
         }
 
@@ -790,9 +1027,11 @@
             // value is negative one (-1).
             pData->CnrInfo.xVertSplitbar = pci->xVertSplitbar;
+
+            // @@todo recalc window components, repaint
         }
 
         if (flDirty)
             // post semaphore to force resize of details wnd
-            WinPostMsg(hwnd,
+            WinPostMsg(pData->dwdMain.hwnd,
                        WM_SEM2,
                        (MPARAM)flDirty,
@@ -805,4 +1044,56 @@
 }
 
+/*
+ *@@ CnrQueryViewportRect:
+ *      implementation for CM_QUERYVIEWPORTRECT in fnwpCnr.
+ *
+ *      From my testing, this simply returns the extensions
+ *      of the container content window, which the cnr docs
+ *      dub the "viewport". This never returns the workarea
+ *      size, that is, the total size of the container's
+ *      viewable content.
+ */
+
+STATIC BOOL CnrQueryViewportRect(PCNRDATA pData,
+                                 PRECTL prcl,
+                                 USHORT usIndicator,
+                                 BOOL fRightSplitView)      // @@todo
+{
+    if (pData && prcl)
+    {
+        prcl->xLeft = 0;
+        prcl->yBottom = 0;
+        prcl->xRight = pData->dwdContent.szlWin.cx;
+        prcl->yTop = pData->dwdContent.szlWin.cy;
+
+        switch (usIndicator)
+        {
+            case CMA_WORKSPACE:
+                // for CMA_WORKSPACE, the PM cnr returns a 0 yBottom when
+                // the cnr is scrolled to the very top and negative y
+                // values when it has been scrolled down to some extent;
+                // wonder what the use for this would be
+                prcl->xLeft += pData->scrw.ptlScrollOfs.x;
+                prcl->xRight += pData->scrw.ptlScrollOfs.x;
+                prcl->yBottom -= pData->scrw.ptlScrollOfs.y;
+                prcl->yTop -= pData->scrw.ptlScrollOfs.y;
+            break;
+
+            case CMA_WINDOW:
+                // for CMA_WINDOW, the PM cnr returns a constant
+                // rectangle without scrolling taken into account
+                WinMapWindowPoints(pData->dwdContent.hwnd,
+                                   pData->dwdMain.hwnd,
+                                   (PPOINTL)prcl,
+                                   2);
+            break;
+        }
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
 /* ******************************************************************
  *
@@ -818,12 +1109,11 @@
  */
 
-PFIELDINFO CnrAllocDetailFieldInfo(HWND hwnd,
-                                   USHORT cFieldInfos)      // in: no. of fieldinfos to allocate (> 0)
+STATIC PFIELDINFO CnrAllocDetailFieldInfo(PCNRDATA pData,
+                                          USHORT cFieldInfos)      // in: no. of fieldinfos to allocate (> 0)
 {
     PFIELDINFO  pfiFirst = NULL,
                 pfiPrev = NULL;
 
-    PCNRDATA    pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    if (pData)
     {
         ULONG       ul;
@@ -863,11 +1153,11 @@
  */
 
-USHORT CnrInsertDetailFieldInfo(HWND hwnd,
-                                PFIELDINFO pfiFirst,
-                                PFIELDINFOINSERT pfii)
+STATIC USHORT CnrInsertDetailFieldInfo(PCNRDATA pData,
+                                       PFIELDINFO pfiFirst,
+                                       PFIELDINFOINSERT pfii)
 {
     USHORT      usrc = 0;
-    PCNRDATA    pData;
-    if (    (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+
+    if (    (pData)
          && (pfiFirst)
          && (pfii)
@@ -933,5 +1223,5 @@
         {
             // post semaphore to force resize of details wnd
-            WinPostMsg(hwnd,
+            WinPostMsg(pData->dwdMain.hwnd,
                        WM_SEM2,
                        (MPARAM)DDFL_ALL,
@@ -948,16 +1238,57 @@
  */
 
-BOOL CnrInvalidateDetailFieldInfo(HWND hwnd)
-{
-    PCNRDATA    pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
-    {
-        WinPostMsg(hwnd,
-                   WM_SEM2,
-                   (MPARAM)DDFL_INVALIDATECOLUMNS,
-                   0);
-    }
+STATIC BOOL CnrInvalidateDetailFieldInfo(PCNRDATA pData)
+{
+    if (pData)
+        return WinPostMsg(pData->dwdMain.hwnd,
+                          WM_SEM2,
+                          (MPARAM)DDFL_INVALIDATECOLUMNS,
+                          0);
 
     return FALSE;
+}
+
+/*
+ *@@ CnrQueryDetailFieldInfo:
+ *      implementation for CM_QUERYDETAILFIELDINFO in fnwpCnr.
+ */
+
+STATIC const FIELDINFO* CnrQueryDetailFieldInfo(PCNRDATA pData,
+                                                PFIELDINFO pfiIn,
+                                                USHORT cmd)        // in: mp2 (CMA_FIRST, CMA_LAST, CMA_NEXT, CMA_PREV)
+{
+    const FIELDINFO *pfiReturn = NULL;
+
+    if (pData)
+    {
+        PLISTNODE pNode;
+
+        switch (cmd)
+        {
+            case CMA_FIRST:
+                pNode = lstQueryFirstNode(&pData->llColumns);
+            break;
+
+            case CMA_LAST:
+                pNode = lstQueryLastNode(&pData->llColumns);
+            break;
+
+            case CMA_NEXT:
+            case CMA_PREV:
+                if (FindColumnFromFI(pData,
+                                     pfiIn,
+                                     &pNode))
+                    if (cmd == CMA_NEXT)
+                        pNode = pNode->pNext;
+                    else
+                        pNode = pNode->pPrevious;
+            break;
+        }
+
+        if (pNode)
+            pfiReturn = ((PDETAILCOLUMN)pNode->pItemData)->pfi;
+    }
+
+    return pfiReturn;
 }
 
@@ -967,11 +1298,10 @@
  */
 
-SHORT CnrRemoveDetailFieldInfo(HWND hwnd,
-                               PFIELDINFO* ppafi,
-                               USHORT cfi,
-                               USHORT fl)
-{
-    PCNRDATA    pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+STATIC SHORT CnrRemoveDetailFieldInfo(PCNRDATA pData,
+                                      PFIELDINFO* ppafi,
+                                      USHORT cfi,
+                                      USHORT fl)
+{
+    if (pData)
     {
         SHORT   rc = lstCountItems(&pData->llColumns);
@@ -1011,5 +1341,5 @@
            )
         {
-            WinPostMsg(hwnd,
+            WinPostMsg(pData->dwdMain.hwnd,
                        WM_SEM2,
                        (MPARAM)DDFL_INVALIDATECOLUMNS,
@@ -1028,12 +1358,11 @@
  */
 
-BOOL CnrFreeDetailFieldInfo(HWND hwnd,
-                            PFIELDINFO *ppafi,      // in: mp1 of CM_FREEDETAILFIELDINFO
-                            USHORT cFieldInfos)     // in: no. of items in array
+STATIC BOOL CnrFreeDetailFieldInfo(PCNRDATA pData,
+                                   PFIELDINFO *ppafi,      // in: mp1 of CM_FREEDETAILFIELDINFO
+                                   USHORT cFieldInfos)     // in: no. of items in array
 {
     BOOL        brc = FALSE;
 
-    PCNRDATA    pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    if (pData)
     {
         ULONG   ul;
@@ -1070,11 +1399,11 @@
  */
 
-PRECORDCORE CnrAllocRecord(HWND hwnd,
-                           ULONG cbExtra,
-                           USHORT cRecords)
+STATIC PRECORDCORE CnrAllocRecord(PCNRDATA pData,
+                                  ULONG cbExtra,
+                                  USHORT cRecords)
 {
     PRECORDCORE preccFirst = NULL;
-    PCNRDATA    pData;
-    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+
+    if (pData)
     {
         ULONG   ul;
@@ -1124,11 +1453,11 @@
  */
 
-ULONG CnrInsertRecord(HWND hwnd,
-                      PRECORDCORE preccFirst,
-                      PRECORDINSERT pri)
+STATIC ULONG CnrInsertRecord(PCNRDATA pData,
+                             PRECORDCORE preccFirst,
+                             PRECORDINSERT pri)
 {
     ULONG       cReturn = 0;
-    PCNRDATA    pData;
-    if (    (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+
+    if (    (pData)
          && (preccFirst)
          && (pri)
@@ -1163,6 +1492,6 @@
 
             default:
-                pNodeInsertAfter = FindListNodeForRecc(pData,
-                                                       pri->pRecordOrder);
+                pNodeInsertAfter = ctnrFindListNodeForRecc(pData,
+                                                           pri->pRecordOrder);
         }
 
@@ -1171,15 +1500,30 @@
              ++ul)
         {
-            PRECORDLISTITEM pListItem;
-
-            if (pListItem = NEW(RECORDLISTITEM))
+            PRECORDLISTITEM prli;
+
+            if (prli = NEW(RECORDLISTITEM))
             {
-                ZERO(pListItem);
-
-                pListItem->precc = preccThis;
-                pListItem->preccParent = pri->pRecordParent;
+                ZERO(prli);
+
+                prli->precc = preccThis;
+                prli->preccParent = pri->pRecordParent;
+
+                // make private copy of record attributes
+                prli->flRecordAttr = preccThis->flRecordAttr;
+
+                // PM container gives the first record in the cnr
+                // "cursored" and "selected" emphasis, so that's
+                // what we'll do too
+                if (    (!cReturn)       // @@todo filtered records
+                     && (!(prli->flRecordAttr & CRA_FILTERED))
+                   )
+                {
+                    prli->flRecordAttr |= CRA_CURSORED | CRA_SELECTED;
+
+                    pData->prliCursored = prli;
+                }
 
                 if (pNodeInsertAfter = lstInsertItemAfterNode(pll,
-                                                              pListItem,
+                                                              prli,
                                                               pNodeInsertAfter))
                 {
@@ -1212,8 +1556,8 @@
                 }
 
-                free(pListItem);
+                free(prli);
             }
 
-            free(pListItem);
+            free(prli);
 
             cReturn = 0;
@@ -1225,7 +1569,7 @@
            )
         {
-            WinPostMsg(hwnd,
+            WinPostMsg(pData->dwdMain.hwnd,
                        WM_SEM2,
-                       (MPARAM)DDFL_INCALIDATERECORDS,
+                       (MPARAM)DDFL_INVALIDATERECORDS,
                        0);
         }
@@ -1235,4 +1579,167 @@
 }
 
+/*
+ *@@ CnrInsertRecordArray:
+ *      implementation for CM_INSERTRECORDARRAY in fnwpCnr.
+ */
+
+STATIC ULONG CnrInsertRecordArray(PCNRDATA pData,
+                                  PRECORDCORE *papRecords,
+                                  PRECORDINSERT pri)
+{
+    ULONG       cReturn = 0;
+
+    if (    (pData)
+         && (papRecords)
+         && (pri)
+         && (pri->cb = sizeof(RECORDINSERT))
+       )
+    {
+        // produce a linked list off the array and call
+        // the CM_INSERTRECORD implementation
+        ULONG   ul;
+        for (ul = 0;
+             ul < (pri->cRecordsInsert - 1);
+             ++ul)
+        {
+            papRecords[ul]->preccNextRecord = papRecords[ul + 1];
+        }
+
+        papRecords[pri->cRecordsInsert - 1]->preccNextRecord = (PRECORDCORE)NULL;
+
+        cReturn = CnrInsertRecord(pData,
+                                  papRecords[0],
+                                  pri);
+    }
+
+    return cReturn;
+}
+
+/*
+ *@@ CnrSetRecordEmphasis:
+ *      implementation for CM_SETRECORDEMPHASIS in fnwpCnr.
+ */
+
+STATIC BOOL CnrSetRecordEmphasis(PCNRDATA pData,
+                                 PRECORDCORE precc,
+                                 BOOL fTurnOn,
+                                 USHORT fsEmphasis)
+{
+    BOOL        brc = FALSE;
+
+    if (pData)
+    {
+        if (precc)
+        {
+            PLISTNODE   pNode;
+            if (pNode = ctnrFindListNodeForRecc(pData,
+                                                precc))
+            {
+                PRECORDLISTITEM prli = (PRECORDLISTITEM)pNode->pItemData;
+
+                ctnrChangeEmphasis(pData,
+                                   prli,
+                                   fTurnOn,
+                                   fsEmphasis);
+
+                // update caller's buffer too
+                precc->flRecordAttr = prli->flRecordAttr;
+
+                brc = TRUE;
+            }
+        } // if (precc)
+        // @@todo else set emphasis on entire cnr
+    }
+
+    return brc;
+}
+
+/*
+ *@@ CnrQueryRecordEmphasis:
+ *      implementation for CM_QUERYRECORDEMPHASIS in fnwpCnr.
+ *
+ *      Note, if several flags are set in fsEmphasis, all
+ *      of them must be set in the record to match.
+ */
+
+PRECORDCORE CnrQueryRecordEmphasis(PCNRDATA pData,
+                                   PRECORDCORE preccSearchAfter,
+                                   USHORT fsEmphasis)
+{
+    if (pData)
+    {
+        PLISTNODE pNode = NULL;
+        if (preccSearchAfter == (PRECORDCORE)CMA_FIRST)
+            pNode = lstQueryFirstNode(&pData->llRootRecords);
+        else
+            if (pNode = ctnrFindListNodeForRecc(pData, preccSearchAfter))
+                pNode = pNode->pNext;
+            else
+                return (PRECORDCORE)-1;
+        // @@todo how does this search tree subrecords?
+
+        while (pNode)
+        {
+            PRECORDLISTITEM prli = (PRECORDLISTITEM)pNode->pItemData;
+            if ((prli->flRecordAttr & fsEmphasis) == fsEmphasis)
+                return (PRECORDCORE)prli->precc;
+
+            pNode = pNode->pNext;
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ *@@ CnrInvalidateRecord:
+ *      implementation for CM_INVALIDATERECORD in fnwpCnr.
+ *
+ */
+
+STATIC BOOL CnrInvalidateRecord(PCNRDATA pData,
+                                PRECORDCORE *papRecs,
+                                USHORT cRecs,
+                                USHORT fsInvalidate)
+{
+    BOOL    brc = TRUE;
+
+    if (pData)
+    {
+        if (    (!papRecs)
+             || (!cRecs)
+           )
+            // invalidate all:
+            CnrSem2(pData, DDFL_INVALIDATERECORDS);
+        else
+        {
+            ULONG   ul;
+            for (ul = 0;
+                 ul < cRecs;
+                 ++cRecs)
+            {
+                PRECORDCORE precc = papRecs[ul];
+                PLISTNODE pRecNode;
+                if (!(pRecNode = ctnrFindListNodeForRecc(pData,
+                                                         precc)))
+                {
+                    brc = FALSE;
+                    break;
+                }
+
+                // set special flag for recompute
+                ((PRECORDLISTITEM)pRecNode->pItemData)->flInvalidate = fsInvalidate;
+            }
+
+            if (brc)
+                CnrSem2(pData, DDFL_INVALIDATESOME);
+                // @@todo optimize: post sem only if a column size has
+                // actually changed
+        }
+    }
+
+    return brc;
+}
+
 /* ******************************************************************
  *
@@ -1249,5 +1756,6 @@
 {
     MRESULT     mrc = 0;
-    PCNRDATA    pData;
+
+    PCNRDATA    pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1);
 
     switch (msg)
@@ -1264,22 +1772,50 @@
 
         case WM_SEM2:
-            CnrSem2(hwnd, (ULONG)mp1);
+            CnrSem2(pData, (ULONG)mp1);
         break;
 
         case WM_PAINT:
-            CnrPaint(hwnd);
+            CnrPaint(pData);
         break;
 
         case WM_WINDOWPOSCHANGED:
-            mrc = CnrWindowPosChanged(hwnd, mp1, mp2);
+            mrc = CnrWindowPosChanged(pData, mp1, mp2);
+        break;
+
+        case WM_SYSCOLORCHANGE:
+            ctnrPresParamChanged(hwnd, 0);
+        break;
+
+        case WM_PRESPARAMCHANGED:
+            ctnrPresParamChanged(hwnd, (ULONG)mp1);
         break;
 
         case WM_HSCROLL:
         case WM_VSCROLL:
-            CnrScroll(hwnd, msg, mp1, mp2);
+            CnrScroll(pData, msg, mp1, mp2);
         break;
 
         case WM_DESTROY:
-            CnrDestroy(hwnd);
+            CnrDestroy(pData);
+        break;
+
+        /* ******************************************************************
+         *
+         *   Mouse and keyboard input
+         *
+         ********************************************************************/
+
+        /*
+         * WM_OPEN:
+         *      when the user double-clicks on the _main_ cnr
+         *      (i.e. details titles), we notify the owner
+         *      of a "whitespace" enter event.
+         */
+
+        case WM_OPEN:
+            ctnrRecordEnter(pData,
+                            NULL,
+                            FALSE);     // mouse, not keyboard
+            mrc = (MRESULT)TRUE;
         break;
 
@@ -1301,8 +1837,8 @@
          */
 
-        case CM_QUERYCNRINFO:
-            mrc = (MRESULT)CnrQueryCnrInfo(hwnd,
+        case CM_QUERYCNRINFO:       // done
+            mrc = (MRESULT)CnrQueryCnrInfo(pData,
                                            (PCNRINFO)mp1,
-                                           (USHORT)mp2);
+                                           SHORT1FROMMP(mp2));
         break;
 
@@ -1319,13 +1855,216 @@
          */
 
-        case CM_SETCNRINFO:
-            mrc = (MRESULT)CnrSetCnrInfo(hwnd,
+        case CM_SETCNRINFO:         // parlty done
+            mrc = (MRESULT)CnrSetCnrInfo(pData,
                                          (PCNRINFO)mp1,
                                          (ULONG)mp2);
         break;
 
+        case CM_PAINTBACKGROUND:        // @@todo
+        break;
+
+        case CM_SCROLLWINDOW:           // @@todo
+        break;
+
+        /*
+         * CM_QUERYVIEWPORTRECT:
+         *
+         *      Parameters:
+         *
+         *      --  PRECTL mp1
+         *
+         *      --  SHORT1FROMMP(mp2): -- CMA_WINDOW: return window coordinates
+         *                             -- CMA_WORKSPACE: return workspace coordinates
+         *
+         *      --  BOOL SHORT2FROMMP(mp2): if TRUE, return right split details view
+         */
+
+        case CM_QUERYVIEWPORTRECT:          // done
+            mrc = (MRESULT)CnrQueryViewportRect(pData,
+                                                (PRECTL)mp1,
+                                                SHORT1FROMMP(mp2),
+                                                SHORT2FROMMP(mp2));
+        break;
+
+        case CM_SETTEXTVISIBILITY:      // @@todo
+        break;
+
         /* ******************************************************************
          *
-         *   FIELDINFO-related messages
+         *   Record allocation/insertion/removal
+         *
+         ********************************************************************/
+
+        /*
+         * CM_ALLOCRECORD:
+         *
+         *      Parameters:
+         *
+         *      --  ULONG mp1: record size in addition to (MINI)RECORDCORE size.
+         *
+         *      --  USHORT mp2: no. of records to allocate.
+         *
+         *      Returns linked list of RECORDCORE's or NULL on errors.
+         */
+
+        case CM_ALLOCRECORD:          // done
+            mrc = (MRESULT)CnrAllocRecord(pData,
+                                          (ULONG)mp1,
+                                          SHORT1FROMMP(mp2));
+        break;
+
+        /*
+         * CM_INSERTRECORD:
+         *      inserts one or more records. If there's more
+         *      than one record, we assume it's a linked list.
+         *
+         *      Parameters:
+         *
+         *      --  PRECORDCORE mp1: first record
+         *
+         *      --  PRECORDINSERT pri
+         *
+         *      Returns the no. of records in the container or 0 on errors.
+         */
+
+        case CM_INSERTRECORD:           // done
+            mrc = (MRESULT)CnrInsertRecord(pData,
+                                           (PRECORDCORE)mp1,
+                                           (PRECORDINSERT)mp2);
+        break;
+
+        /*
+         * CM_INSERTRECORDARRAY:
+         *      inserts one or more records. As opposed to with
+         *      CM_INSERTRECORD, mp1 points to an array of
+         *      record pointers instead of to a linked list
+         *      of records.
+         *
+         *      Parameters:
+         *
+         *      --  PRECORDCORE mp1: first record
+         *
+         *      --  PRECORDINSERT pri
+         *
+         *      Returns the no. of records in the container or 0 on errors.
+         */
+
+        case CM_INSERTRECORDARRAY:      // done
+            mrc = (MRESULT)CnrInsertRecordArray(pData,
+                                               (PRECORDCORE*)mp1,
+                                               (PRECORDINSERT)mp2);
+        break;
+
+        /*
+         * CM_QUERYRECORD:
+         *
+         *      Parameters:
+         *
+         *      --  PRECORDCORE mp1: preccSearch
+         *
+         *      --  SHORT1FROMMP(mp1): CMA_FIRST, CMA_LAST, CMA_NEXT, CMA_PREV
+         *
+         *          or for tree views: CMA_FIRSTCHILD, CMA_LASTCHILD, CMA_PARENT
+         *
+         *      --  SHORT2FROMMP(mp1): CMA_ITEMORDER or CMA_ZORDER
+         */
+
+        case CM_QUERYRECORD:            // @@todo
+        break;
+
+        /*
+         * CM_SETRECORDEMPHASIS:
+         *
+         *      Parameters:
+         *
+         *      --  PRECORDCORE mp1: record to change emphasis for.
+         *
+         *      --  SHORT1FROMMP(mp2): TRUE == turn flags on, FALSE == turn flags off.
+         *
+         *      --  SHORT2FROMMP(mp2): any combination of CRA_CURSORED, CRA_DISABLED,
+         *          CRA_INUSE, CRA_PICKED, CRA_SELECTED, CRA_SOURCE
+         *
+         *      Returns BOOL.
+         */
+
+        case CM_SETRECORDEMPHASIS:
+            mrc = (MRESULT)CnrSetRecordEmphasis(pData,
+                                                (PRECORDCORE)mp1,
+                                                SHORT1FROMMP(mp2),
+                                                SHORT2FROMMP(mp2));
+        break;
+
+        /*
+         * CM_QUERYRECORDEMPHASIS:
+         *
+         *      Parameters:
+         *
+         *      --  PRECORDCORE mp1: record after which to start search
+         *          or NULL to start search from beginning.
+         *
+         *      --  USHORT mp2: any combination of CRA_COLLAPSED, CRA_CURSORED,
+         *          CRA_DISABLED, CRA_DROPONABLE, CRA_EXPANDED, CRA_FILTERED,
+         *          CRA_INUSE, CRA_PICKED, CRA_SELECTED, CRA_SOURCE
+         */
+
+        case CM_QUERYRECORDEMPHASIS:    // done
+            mrc = (MRESULT)CnrQueryRecordEmphasis(pData,
+                                                  (PRECORDCORE)mp1,
+                                                  SHORT1FROMMP(mp2));
+        break;
+
+        case CM_QUERYRECORDFROMRECT:    // @@todo
+        break;
+
+        case CM_QUERYRECORDINFO:        // @@todo
+        break;
+
+        case CM_QUERYRECORDRECT:        // @@todo
+        break;
+
+        /*
+         * CM_INVALIDATERECORD:
+         *
+         *      Parameters:
+         *
+         *      --  PRECORDCORE* mp1: ptr to array of record pointers
+         *
+         *      --  SHORT1FROMMP(mp2): no. of records in array
+         *
+         *      --  SHORT2FROMMP(mp2): CMA_ERASE, CMA_REPOSITION,
+         *          CMA_NOREPOSITION, CMA_TEXTCHANGED
+         *
+         *      Returns BOOL.
+         */
+
+        case CM_INVALIDATERECORD:       // done
+            mrc = (MRESULT)CnrInvalidateRecord(pData,
+                                               (PRECORDCORE*)mp1,
+                                               SHORT1FROMMP(mp2),
+                                               SHORT2FROMMP(mp2));
+        break;
+
+        case CM_REMOVERECORD:   // @@todo
+        case CM_FREERECORD:     // @@todo
+        break;
+
+        case CM_ERASERECORD:    // @@todo
+        break;
+
+        case CM_ARRANGE:        // @@todo
+        break;
+
+        case CM_FILTER:         // @@todo
+        break;
+
+        case CM_QUERYDRAGIMAGE: // @@todo
+
+        case CM_SEARCHSTRING:
+
+        case CM_SORTRECORD:     // @@todo
+
+        /* ******************************************************************
+         *
+         *   Details view
          *
          ********************************************************************/
@@ -1343,7 +2082,7 @@
          */
 
-        case CM_ALLOCDETAILFIELDINFO:
-            mrc = (MRESULT)CnrAllocDetailFieldInfo(hwnd,
-                                                   (USHORT)mp1);
+        case CM_ALLOCDETAILFIELDINFO: // done
+            mrc = (MRESULT)CnrAllocDetailFieldInfo(pData,
+                                                   SHORT1FROMMP(mp1));
         break;
 
@@ -1360,6 +2099,6 @@
          */
 
-        case CM_INSERTDETAILFIELDINFO:
-            mrc = (MRESULT)CnrInsertDetailFieldInfo(hwnd,
+        case CM_INSERTDETAILFIELDINFO:      // done
+            mrc = (MRESULT)CnrInsertDetailFieldInfo(pData,
                                                     (PFIELDINFO)mp1,
                                                     (PFIELDINFOINSERT)mp2);
@@ -1373,6 +2112,22 @@
          */
 
-        case CM_INVALIDATEDETAILFIELDINFO:
-            mrc = (MRESULT)CnrInvalidateDetailFieldInfo(hwnd);
+        case CM_INVALIDATEDETAILFIELDINFO:  // done
+            mrc = (MRESULT)CnrInvalidateDetailFieldInfo(pData);
+        break;
+
+        /*
+         * CM_QUERYDETAILFIELDINFO:
+         *
+         *      Parameters:
+         *
+         *      --  PFIELDINFO mp1
+         *
+         *      --  USHORT mp2: CMA_FIRST, CMA_LAST, CMA_NEXT, CMA_PREV
+         */
+
+        case CM_QUERYDETAILFIELDINFO:       // done
+            mrc = (MRESULT)CnrQueryDetailFieldInfo(pData,
+                                                   (PFIELDINFO)mp1,
+                                                   SHORT1FROMMP(mp2));
         break;
 
@@ -1391,6 +2146,6 @@
          */
 
-        case CM_REMOVEDETAILFIELDINFO:
-            mrc = (MRESULT)CnrRemoveDetailFieldInfo(hwnd,
+        case CM_REMOVEDETAILFIELDINFO:      // done
+            mrc = (MRESULT)CnrRemoveDetailFieldInfo(pData,
                                                     (PFIELDINFO*)mp1,
                                                     SHORT1FROMMP(mp2),
@@ -1409,54 +2164,49 @@
          */
 
-        case CM_FREEDETAILFIELDINFO:
-            mrc = (MRESULT)CnrFreeDetailFieldInfo(hwnd,
+        case CM_FREEDETAILFIELDINFO:        // done
+            mrc = (MRESULT)CnrFreeDetailFieldInfo(pData,
                                                   (PFIELDINFO*)mp1,
-                                                  (USHORT)mp2);
+                                                  SHORT1FROMMP(mp2));
+        break;
+
+        case CM_HORZSCROLLSPLITWINDOW:
         break;
 
         /* ******************************************************************
          *
-         *   Record allocation/insertion/removal
+         *   Icon view
          *
          ********************************************************************/
 
-        /*
-         * CM_ALLOCRECORD:
-         *
-         *      Parameters:
-         *
-         *      --  ULONG mp1: record size in addition to (MINI)RECORDCORE size.
-         *
-         *      --  USHORT mp2: no. of records to allocate.
-         *
-         *      Returns linked list of RECORDCORE's or NULL on errors.
-         */
-
-        case CM_ALLOCRECORD:
-            mrc = (MRESULT)CnrAllocRecord(hwnd,
-                                          (ULONG)mp1,
-                                          (USHORT)mp2);
-        break;
-
-        /*
-         * CM_INSERTRECORD:
-         *
-         *      Parameters:
-         *
-         *      --  PRECORDCORE mp1: first record
-         *
-         *      --  PRECORDINSERT pri
-         *
-         *      Returns the no. of records in the container or 0 on errors.
-         */
-
-        case CM_INSERTRECORD:
-            mrc = (MRESULT)CnrInsertRecord(hwnd,
-                                           (PRECORDCORE)mp1,
-                                           (PRECORDINSERT)mp2);
-        break;
+        case CM_SETGRIDINFO:    // @@todo
+        case CM_QUERYGRIDINFO:  // @@todo
+        case CM_SNAPTOGRID:     // @@todo
+        break;
+
+        /* ******************************************************************
+         *
+         *   Tree management
+         *
+         ********************************************************************/
+
+        case CM_COLLAPSETREE:   // @@todo
+        case CM_EXPANDTREE:
+        case CM_MOVETREE:       // @@todo
+        break;
+
+        /* ******************************************************************
+         *
+         *   Direct editing
+         *
+         ********************************************************************/
+
+        case CM_OPENEDIT:       // @@todo
+
+        case CM_CLOSEEDIT:      // @@todo
+        break;
+
 
         default:
-            if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+            if (pData)
                 mrc = ctlDefWindowProc(&pData->dwdMain, msg, mp1, mp2);
         break;
Index: trunk/src/helpers/cctl_cnr_dtls.c
===================================================================
--- trunk/src/helpers/cctl_cnr_dtls.c	(revision 242)
+++ trunk/src/helpers/cctl_cnr_dtls.c	(revision 243)
@@ -26,7 +26,10 @@
     // as unsigned char
 
+#define INCL_DOSMISC
+
 #define INCL_WINWINDOWMGR
 #define INCL_WINFRAMEMGR
 #define INCL_WINSYS
+#define INCL_WININPUT
 #define INCL_WINSTDCNR
 #define INCL_GPIPRIMITIVES
@@ -61,4 +64,25 @@
 
 /*
+ *@@ InvalidateColumn:
+ *
+ */
+
+VOID InvalidateColumn(PCNRDATA pData,
+                      const DETAILCOLUMN *pColumn)
+{
+    RECTL   rcl;
+    rcl.xLeft = pColumn->xLeft - pData->scrw.ptlScrollOfs.x;
+    rcl.xRight = rcl.xLeft + pColumn->cxContent + 2 * COLUMN_PADDING_X + 1;
+    rcl.yBottom = 0;
+    rcl.yTop = pData->dwdContent.szlWin.cy;
+
+    WinInvalidateRect(pData->dwdContent.hwnd, &rcl, FALSE);
+
+    // and titles too
+    rcl.yTop = pData->dwdMain.szlWin.cy;
+    WinInvalidateRect(pData->dwdMain.hwnd, &rcl, FALSE);
+}
+
+/*
  *@@ cdtlRecalcDetails:
  *      worker routine for refreshing all internal DETAILCOLUMN
@@ -68,5 +92,9 @@
  *      in with sufficient update flags.
  *
- *      Returns the following ORed flags:
+ *      This sets the following flags in *pfl:
+ *
+ *      --  DDFL_INVALIDATECOLUMNS: columns have to be
+ *          repositioned, so details window needs a full
+ *          repaint.
  *
  *      --  DDFL_WINDOWSIZECHANGED: cnr titles area changed,
@@ -79,17 +107,25 @@
 VOID cdtlRecalcDetails(PCNRDATA pData,
                        HPS hps,
-                       PULONG pfl)
+                       PULONG pfl)      // in/out: DDFL_* flags
 {
     LONG        xCurrent = 0;
     PLISTNODE   pColNode;
-    ULONG       cRow = 0;
+    ULONG       cColumn = 0;
 
     // compute total padding for one row:
-    LONG        cxPaddingRow = pData->CnrInfo.cFields * 2 * COLUMN_PADDING_X,
-                yOfsNow = 0;
-
+    LONG        cxPaddingRow = pData->CnrInfo.cFields * 2 * COLUMN_PADDING_X;
     LONG        cyColTitlesContent = 0,
                 cyColTitlesBox = 0;
 
+    BOOL        fRefreshAll = (*pfl & (DDFL_INVALIDATECOLUMNS | DDFL_INVALIDATERECORDS));
+
+    LONG        yNow = 0;
+
+    // list of records to invalidate while we're running;
+    // this only gets appended to if we're not invalidating
+    // all records anyway
+    LINKLIST    llInvalidateRecords;
+    lstInit(&llInvalidateRecords, FALSE);       // receives RECORDLISTITEM's
+
     GpiQueryFontMetrics(hps, sizeof(FONTMETRICS), &pData->fm);
 
@@ -97,158 +133,221 @@
     FOR_ALL_NODES(&pData->llColumns, pColNode)
     {
-        PDETAILCOLUMN pCol = (PDETAILCOLUMN)pColNode->pItemData;
-        const FIELDINFO *pfi = pCol->pfi;
+        PDETAILCOLUMN pColumn = (PDETAILCOLUMN)pColNode->pItemData;
+        const FIELDINFO *pfi = pColumn->pfi;
         PLISTNODE   pRecNode;
 
-        pCol->xLeft = xCurrent;
-
-        pCol->cxTotal
-        = pCol->cxWidestRecord
-        = 0;
-
-        // inner loop: records (go DOWN)
-        FOR_ALL_NODES(&pData->llRootRecords, pRecNode)
-        {
-            PRECORDLISTITEM prliThis = (PRECORDLISTITEM)pRecNode->pItemData;
-            const   RECORDCORE *preccThis = prliThis->precc;
-            PVOID   pColumnData = (PVOID)((PBYTE)preccThis + pfi->offStruct);
-
-            SIZEL   szlThis = {10, 10};
-            ULONG   cLines;
-            PCSZ    pcsz = NULL;
-            CHAR    szTemp[30];
-
-            if (!cRow)
-                prliThis->szlContent.cy = 0;
-
-            switch (pfi->flData & (   CFA_BITMAPORICON
-                                    | CFA_DATE
-                                    | CFA_STRING
-                                    | CFA_TIME
-                                    | CFA_ULONG))
+        LONG        cxContent;
+
+        if (fRefreshAll)
+            pColumn->cxWidestRecord = 0;
+
+        // skip invisible columns
+        if (!(pfi->flData & CFA_INVISIBLE))
+        {
+            yNow = 0;
+
+            // inner loop: records (go UP)
+            // (we start with the LAST record so we can calculate
+            // the y coordinates correctly)
+            pRecNode = lstQueryLastNode(&pData->llRootRecords);
+            while (pRecNode)
             {
-                case CFA_BITMAPORICON:
-                    // @@todo
-                break;
-
-                case CFA_STRING:
-                    pcsz = *(PSZ*)pColumnData;
-                break;
-
-                case CFA_DATE:
-                    nlsDate(&pData->cs,
-                            szTemp,
-                            ((PCDATE)pColumnData)->year,
-                            ((PCDATE)pColumnData)->month,
-                            ((PCDATE)pColumnData)->day);
-                    pcsz = szTemp;
-                break;
-
-                case CFA_TIME:
-                    nlsTime(&pData->cs,
-                            szTemp,
-                            ((PCTIME)pColumnData)->hours,
-                            ((PCTIME)pColumnData)->minutes,
-                            ((PCTIME)pColumnData)->seconds);
-                    pcsz = szTemp;
-                break;
-
-                case CFA_ULONG:
-                    nlsThousandsULong(szTemp,
-                                      *((PULONG)pColumnData),
-                                      pData->cs.cs.cThousands);
-                    pcsz = szTemp;
-                break;
-            }
-
-            if (pcsz)
+                PRECORDLISTITEM prliThis = (PRECORDLISTITEM)pRecNode->pItemData;
+                const   RECORDCORE *preccThis = prliThis->precc;
+                PVOID   pColumnData = (PVOID)((PBYTE)preccThis + pfi->offStruct);
+
+                if (!(prliThis->flRecordAttr & CRA_FILTERED))
+                {
+                    // skip filtered records
+                    if (    (*pfl & DDFL_INVALIDATERECORDS)
+                         || (prliThis->flInvalidate)
+                       )
+                    {
+                        ULONG   cLines;
+                        SIZEL   szlThis = {10, 10};
+                        PCSZ    pcsz = NULL;
+                        CHAR    szTemp[30];
+
+                        if (!cColumn)
+                        {
+                            // we're in the leftmost column:
+                            prliThis->szlContent.cx
+                            = prliThis->szlContent.cy
+                            = prliThis->szlBox.cx
+                            = prliThis->szlBox.cy
+                            = 0;
+                        }
+
+                        switch (pfi->flData & (   CFA_BITMAPORICON
+                                                | CFA_DATE
+                                                | CFA_STRING
+                                                | CFA_TIME
+                                                | CFA_ULONG))
+                        {
+                            case CFA_BITMAPORICON:
+                                // @@todo
+                            break;
+
+                            case CFA_STRING:
+                                pcsz = *(PSZ*)pColumnData;
+                            break;
+
+                            case CFA_DATE:
+                                nlsDate(&pData->cs,
+                                        szTemp,
+                                        ((PCDATE)pColumnData)->year,
+                                        ((PCDATE)pColumnData)->month,
+                                        ((PCDATE)pColumnData)->day);
+                                pcsz = szTemp;
+                            break;
+
+                            case CFA_TIME:
+                                nlsTime(&pData->cs,
+                                        szTemp,
+                                        ((PCTIME)pColumnData)->hours,
+                                        ((PCTIME)pColumnData)->minutes,
+                                        ((PCTIME)pColumnData)->seconds);
+                                pcsz = szTemp;
+                            break;
+
+                            case CFA_ULONG:
+                                nlsThousandsULong(szTemp,
+                                                  *((PULONG)pColumnData),
+                                                  pData->cs.cs.cThousands);
+                                pcsz = szTemp;
+                            break;
+                        }
+
+                        if (pcsz)
+                        {
+                            gpihCalcTextExtent(hps,
+                                               pcsz,
+                                               &szlThis.cx,
+                                               &cLines);
+                            szlThis.cy = cLines * (pData->fm.lMaxBaselineExt + pData->fm.lExternalLeading);
+                        }
+
+                        // increment record's total width
+                        prliThis->szlContent.cx += szlThis.cx;
+                        prliThis->szlBox.cx +=   szlThis.cx
+                                               + cxPaddingRow;       // computed at top
+
+                        // record's content height = height of tallest column of that record
+                        if (szlThis.cy > prliThis->szlContent.cy)
+                            prliThis->szlContent.cy = szlThis.cy;
+
+                        // remember max width of this record's column
+                        // for the entire column
+                        if (szlThis.cx > pColumn->cxWidestRecord)
+                            pColumn->cxWidestRecord = szlThis.cx;
+
+                        if (!pColNode->pNext)
+                        {
+                            // last column of outer loop:
+
+                            // clear refresh flags, if any
+                            prliThis->flInvalidate = 0;
+
+                            // compute box cy from content
+                            prliThis->szlBox.cy =   prliThis->szlContent.cy
+                                                  + pData->CnrInfo.cyLineSpacing;
+
+                            // store record's position in workarea coords
+                            prliThis->ptl.x = 0;        // we're in Details view
+                            prliThis->ptl.y = yNow;
+
+                            if (!fRefreshAll)
+                                // invalidate this record's rectangle
+                                lstAppendItem(&llInvalidateRecords,
+                                              prliThis);
+                        }
+                    }
+
+                    // go down for next record
+                    yNow += prliThis->szlBox.cy;
+
+                } // if (!(prliThis->flRecordAttr & CRA_FILTERED))
+
+                pRecNode = pRecNode->pPrevious;
+
+            } // while (pRecNode)
+
+            if (!(cxContent = pfi->cxWidth))
             {
-                gpihCalcTextExtent(hps,
-                                   pcsz,
-                                   &szlThis.cx,
-                                   &cLines);
-                szlThis.cy = cLines * (pData->fm.lMaxBaselineExt + pData->fm.lExternalLeading);
-            }
-
-            // remember max width of this record's column
-            // for the entire column
-            if (szlThis.cx > pCol->cxWidestRecord)
-                pCol->cxWidestRecord = szlThis.cx;
-
-            // record's content height = height of tallest column of that record
-            if (szlThis.cy > prliThis->szlContent.cy)
-                prliThis->szlContent.cy = szlThis.cy;
-
-            if (!pColNode->pNext)
-            {
-                // last column: compute box from content
-                prliThis->szlBox.cx =   prliThis->szlContent.cx
-                                      + cxPaddingRow;       // computed at top
-                prliThis->szlBox.cy =   prliThis->szlContent.cy
-                                      + pData->CnrInfo.cyLineSpacing;
-
-                prliThis->yOfs = yOfsNow;
-
-                yOfsNow += prliThis->szlBox.cy;
-            }
-
-            ++cRow;
-        }
-
-        if (!(pCol->cxTotal = pfi->cxWidth))
-        {
-            // this is an auto-size column:
-
-            if (pData->CnrInfo.flWindowAttr & CA_DETAILSVIEWTITLES)
-            {
-                // compute space needed for title
-
-                pCol->szlTitleData.cx
-                = pCol->szlTitleData.cy
-                = 0;
-
-                if (pfi->flTitle & CFA_BITMAPORICON)
-                    // @@todo
-                    ;
+                // this is an auto-size column:
+
+                if (pData->CnrInfo.flWindowAttr & CA_DETAILSVIEWTITLES)
+                {
+                    if (*pfl & DDFL_INVALIDATECOLUMNS)
+                    {
+                        // compute space needed for title
+
+                        pColumn->szlTitleData.cx
+                        = pColumn->szlTitleData.cy
+                        = 0;
+
+                        if (pfi->flTitle & CFA_BITMAPORICON)
+                            // @@todo
+                            ;
+                        else
+                        {
+                            ULONG cLines;
+                            gpihCalcTextExtent(hps,
+                                               (PCSZ)pfi->pTitleData,
+                                               &pColumn->szlTitleData.cx,
+                                               &cLines);
+                            pColumn->szlTitleData.cy = cLines * (pData->fm.lMaxBaselineExt + pData->fm.lExternalLeading);
+                        }
+                    }
+
+                    cxContent = max(pColumn->cxWidestRecord, pColumn->szlTitleData.cx);
+
+                    if (pColumn->szlTitleData.cy > cyColTitlesContent)
+                        cyColTitlesContent = pColumn->szlTitleData.cy;
+                }
                 else
                 {
-                    ULONG cLines;
-                    gpihCalcTextExtent(hps,
-                                       (PCSZ)pfi->pTitleData,
-                                       &pCol->szlTitleData.cx,
-                                       &cLines);
-                    pCol->szlTitleData.cy = cLines * (pData->fm.lMaxBaselineExt + pData->fm.lExternalLeading);
+                    pColumn->szlTitleData.cx
+                    = 0;
                 }
-
-                pCol->cxTotal =   max(pCol->cxWidestRecord, pCol->szlTitleData.cx);
-
-                if (pCol->szlTitleData.cy > cyColTitlesContent)
-                    cyColTitlesContent = pCol->szlTitleData.cy;
             }
-            else
+
+            // check if column needs invalidating
+            if (    (fRefreshAll)
+                 || (pColumn->xLeft != xCurrent)
+                 || (pColumn->cxContent != cxContent)
+               )
             {
-                pCol->szlTitleData.cx
-                = 0;
+                pColumn->xLeft = xCurrent;
+                pColumn->cxContent = cxContent;
+
+                if (!fRefreshAll)
+                    InvalidateColumn(pData,
+                                     pColumn);
             }
-        }
-
-        // go one column to the right
-        xCurrent += pCol->cxTotal + 2 * COLUMN_PADDING_X;
-
-        if (    (pfi->flData & CFA_SEPARATOR)
-             && (pColNode->pNext)
-           )
-            xCurrent += DEFAULT_BORDER_WIDTH;
-    }
-
-    if (    (pData->szlWorkarea.cx != xCurrent)
-         || (pData->szlWorkarea.cy != yOfsNow)
+
+            // go one column to the right
+            xCurrent += cxContent + 2 * COLUMN_PADDING_X;
+
+            if (    (pfi->flData & CFA_SEPARATOR)
+                 && (pColNode->pNext)
+               )
+                xCurrent += DEFAULT_BORDER_WIDTH;
+
+            ++cColumn;
+        }
+    }
+
+    // has workarea changed?
+    if (    (pData->scrw.szlWorkarea.cx != xCurrent)
+         || (pData->scrw.szlWorkarea.cy != yNow)
        )
     {
-        pData->szlWorkarea.cx = xCurrent;
-        pData->szlWorkarea.cy = yOfsNow;
+        pData->scrw.szlWorkarea.cx = xCurrent;
+        pData->scrw.szlWorkarea.cy = yNow;
         *pfl |= DDFL_WORKAREACHANGED;
     }
 
+    // has details title box changed?
     if (pData->CnrInfo.flWindowAttr & CA_DETAILSVIEWTITLES)
         cyColTitlesBox =   cyColTitlesContent
@@ -265,4 +364,19 @@
         *pfl |= DDFL_WINDOWSIZECHANGED | DDFL_WORKAREACHANGED;
     }
+
+    // now invalidate records
+    if (fRefreshAll)
+        WinInvalidateRect(pData->dwdMain.hwnd, NULL, TRUE);
+    else
+    {
+        PLISTNODE pNode = lstQueryFirstNode(&llInvalidateRecords);
+        while (pNode)
+        {
+            ctnrRepaintRecord(pData, (PRECORDLISTITEM)pNode->pItemData);
+            pNode = pNode->pNext;
+        }
+    }
+
+    lstClear(&llInvalidateRecords);
 }
 
@@ -276,5 +390,10 @@
 MRESULT DetailsCreate(HWND hwnd, MPARAM mp1, MPARAM mp2)
 {
-    if (!mp1)
+    HWND    hwndParent;
+
+    if (    (!mp1)
+         || (!mp2)
+         || (!(hwndParent = (((PCREATESTRUCT)mp2)->hwndParent)))
+       )
         return (MRESULT)TRUE;
 
@@ -282,9 +401,11 @@
 
     // initialize DEFWINDOWDATA
-    ctlInitDWD(hwnd,
-               mp2,
-               &((PCNRDATA)mp1)->dwdContent,
-               WinDefWindowProc,
-               &G_scsCnr);
+    ctnrInit(hwnd,
+             mp2,
+             winhQueryWindowStyle(hwndParent),      // main container
+             &((PCNRDATA)mp1)->dwdContent);
+
+    // the cnr appears to always grab the focus on creation
+    WinSetFocus(HWND_DESKTOP, hwnd);    // @@todo how do we do this on creation only?
 
     return (MRESULT)FALSE;
@@ -297,126 +418,715 @@
  */
 
-VOID DetailsPaint(HWND hwnd)
+VOID DetailsPaint(PCNRDATA pData)
 {
     HPS         hps;
-    PCNRDATA    pData;
-    RECTL       rclPaint;
-
-    if (    (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
-         && (hps = WinBeginPaint(hwnd, NULLHANDLE, &rclPaint))
-       )
-    {
-        PLISTNODE pColNode;
-
-        POINTL  ptl;
-        RECTL   rcl;
+    RECTL       rclClip;
+
+    if (hps = WinBeginPaint(pData->dwdContent.hwnd, NULLHANDLE, &rclClip))
+    {
+        PLISTNODE pColNode,
+                  pRecNodeFirst2Paint = NULL;        // first one to paint
+
         LONG    yPadding = pData->CnrInfo.cyLineSpacing / 2;
         ULONG   cColumn = 0;
+        BOOL    fFirstCol = TRUE;
+
+        BOOL    fHasFocus;
+        HWND    hwndFocus;
+
+        if (    (hwndFocus = WinQueryFocus(HWND_DESKTOP))
+             && (hwndFocus == pData->dwdContent.hwnd)
+           )
+            fHasFocus = TRUE;
+        else
+            fHasFocus = FALSE;
 
         gpihSwitchToRGB(hps);
+
+#if 0
+        {
+            ULONG ulTimeNow;
+            DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
+                            &ulTimeNow,
+                            sizeof(ulTimeNow));
+            WinFillRect(hps,
+                        &rclClip,
+                        ulTimeNow & 0xFFFFFF);
+        }
+#else
         WinFillRect(hps,
-                    &rclPaint,
-                    pData->dwdMain.lcolBackground);
-
-        FOR_ALL_NODES(&pData->llColumns, pColNode)
+                    &rclClip,
+                    ctlQueryColor(&pData->dwdMain, CTLCOL_BGND));
+#endif
+
+        // skip columns that are not in paint rectangle
+        pColNode = lstQueryFirstNode(&pData->llColumns);
+        while (pColNode)
+        {
+            PDETAILCOLUMN pCol = (PDETAILCOLUMN)pColNode->pItemData;
+            const FIELDINFO *pfi = pCol->pfi;
+            if (    (!(pfi->flData & CFA_INVISIBLE))
+                 && (pCol->xLeft + pCol->cxContent - pData->scrw.ptlScrollOfs.x >= rclClip.xLeft)
+               )
+                break;
+
+            pColNode = pColNode->pNext;
+        }
+
+        // now loop for the remaining columns (go RIGHT)
+        while (pColNode)
         {
             PDETAILCOLUMN pCol = (PDETAILCOLUMN)pColNode->pItemData;
             const FIELDINFO *pfi = pCol->pfi;
             PLISTNODE   pRecNode;
-            ULONG       cRow;
-
-            rcl.xLeft = pCol->xLeft + COLUMN_PADDING_X - pData->ptlScrollOfs.x;
-            rcl.xRight = rcl.xLeft + pCol->cxTotal;
-
-            // we start out at the top and work our way down,
-            // decrementing rcl.yTop with every item we painted
-            rcl.yTop = pData->dwdContent.szlWin.cy + pData->ptlScrollOfs.y;
-
-            PMPF_RECT("rcl: ", &rcl);
-
-            // now paint this column's data for all records!
-            cRow = 0;
-            FOR_ALL_NODES(&pData->llRootRecords, pRecNode)
+
+            if (!(pfi->flData & CFA_INVISIBLE))
             {
-                PRECORDLISTITEM prliThis = (PRECORDLISTITEM)pRecNode->pItemData;
-                const   RECORDCORE *preccThis = prliThis->precc;
-                PVOID   pColumnData = (PVOID)((PBYTE)preccThis + pfi->offStruct);
-                CHAR    szTemp[30];
-                PCSZ    pcsz = NULL;
-
-                rcl.yTop -= yPadding;
-                rcl.yBottom =   rcl.yTop
-                              - prliThis->szlContent.cy;
-
-                switch (pfi->flData & (   CFA_BITMAPORICON
-                                        | CFA_DATE
-                                        | CFA_STRING
-                                        | CFA_TIME
-                                        | CFA_ULONG))
+                RECTL rclRecc,
+                      rcl;
+
+                rcl.xLeft = pCol->xLeft + COLUMN_PADDING_X - pData->scrw.ptlScrollOfs.x;
+                rcl.xRight = rcl.xLeft + pCol->cxContent;
+
+                if (fFirstCol)
                 {
-                    case CFA_BITMAPORICON:
-                        // @@todo
-                    break;
-
-                    case CFA_STRING:
-                        pcsz = *(PSZ*)pColumnData;
-                    break;
-
-                    case CFA_DATE:
-                        nlsDate(&pData->cs,
-                                szTemp,
-                                ((PCDATE)pColumnData)->year,
-                                ((PCDATE)pColumnData)->month,
-                                ((PCDATE)pColumnData)->day);
-                        pcsz = szTemp;
-                    break;
-
-                    case CFA_TIME:
-                        nlsTime(&pData->cs,
-                                szTemp,
-                                ((PCTIME)pColumnData)->hours,
-                                ((PCTIME)pColumnData)->minutes,
-                                ((PCTIME)pColumnData)->seconds);
-                        pcsz = szTemp;
-                    break;
-
-                    case CFA_ULONG:
-                        nlsThousandsULong(szTemp,
-                                          *((PULONG)pColumnData),
-                                          pData->cs.cs.cThousands);
-                        pcsz = szTemp;
-                    break;
+                    // first time we get here: skip all records that
+                    // are outside the paint rectangle
+
+                    pRecNode = lstQueryFirstNode(&pData->llRootRecords);
+                    while (pRecNode)
+                    {
+                        PRECORDLISTITEM prliThis = (PRECORDLISTITEM)pRecNode->pItemData;
+
+                        if (!(prliThis->flRecordAttr & CRA_FILTERED))
+                        {
+                            ctnrGetRecordRect(pData, &rclRecc, prliThis);
+                            if (rclRecc.yBottom <= rclClip.yTop)
+                            {
+                                pRecNodeFirst2Paint = pRecNode;
+
+                                break;
+                            }
+                        }
+
+                        pRecNode = pRecNode->pNext;
+                    }
                 }
 
-                if (pcsz)
-                    cnrDrawString(hps,
-                                  pcsz,
-                                  &rcl,
-                                  pfi->flData,
-                                  &pData->fm);
-
-                ++cRow;
-
-                rcl.yTop = rcl.yBottom - yPadding;
+                // now inner loop for the remaining records (go DOWN)
+                pRecNode = pRecNodeFirst2Paint;
+                while (pRecNode)
+                {
+                    PRECORDLISTITEM prliThis = (PRECORDLISTITEM)pRecNode->pItemData;
+
+                    if (!(prliThis->flRecordAttr & CRA_FILTERED))
+                    {
+                        const   RECORDCORE *preccThis = prliThis->precc;
+                        PVOID   pColumnData = (PVOID)((PBYTE)preccThis + pfi->offStruct);
+                        CHAR    szTemp[100];
+                        PCSZ    pcsz = NULL;
+                        LONG    lcolBackground,
+                                lcolForeground;
+
+                        ctnrGetRecordRect(pData, &rclRecc, prliThis);
+
+                        PMPF_RECT("rclRecc: ", &rclRecc);
+
+                        if (prliThis->flRecordAttr & CRA_SELECTED)
+                        {
+                            lcolBackground = ctlQueryColor(&pData->dwdMain, CNRCOL_HILITEBGND);
+                            lcolForeground = ctlQueryColor(&pData->dwdMain, CNRCOL_HILITEFGND);
+
+                            if (fFirstCol)
+                                WinFillRect(hps,
+                                            &rclRecc,
+                                            lcolBackground);
+                        }
+                        else
+                        {
+                            lcolBackground = ctlQueryColor(&pData->dwdMain, CTLCOL_BGND);
+                            lcolForeground = ctlQueryColor(&pData->dwdMain, CTLCOL_FGND);
+                        }
+
+                        GpiSetColor(hps, lcolForeground);
+                        GpiSetBackColor(hps, lcolBackground);
+
+                        rcl.yTop = rclRecc.yTop - yPadding;
+                        rcl.yBottom = rcl.yTop - prliThis->szlContent.cy;
+
+                        if (    (fFirstCol)
+                             && (prliThis->flRecordAttr & CRA_CURSORED)
+                           )
+                        {
+                            RECTL rcl2;
+                            rcl2.xLeft = rclRecc.xLeft + 1;
+                            rcl2.xRight = rclRecc.xRight - 2;
+                            rcl2.yBottom = rclRecc.yBottom + 1;
+                            rcl2.yTop = rclRecc.yTop - 2;
+                            GpiSetLineType(hps, LINETYPE_ALTERNATE);
+                            gpihBox(hps,
+                                    DRO_OUTLINE,
+                                    &rcl2);
+                            GpiSetLineType(hps, LINETYPE_DEFAULT);
+                        }
+
+                        switch (pfi->flData & (   CFA_BITMAPORICON
+                                                | CFA_DATE
+                                                | CFA_STRING
+                                                | CFA_TIME
+                                                | CFA_ULONG))
+                        {
+                            case CFA_BITMAPORICON:
+                                // @@todo
+                            break;
+
+                            case CFA_STRING:
+                                pcsz = *(PSZ*)pColumnData;
+                            break;
+
+                            case CFA_DATE:
+                                nlsDate(&pData->cs,
+                                        szTemp,
+                                        ((PCDATE)pColumnData)->year,
+                                        ((PCDATE)pColumnData)->month,
+                                        ((PCDATE)pColumnData)->day);
+                                pcsz = szTemp;
+                            break;
+
+                            case CFA_TIME:
+                                nlsTime(&pData->cs,
+                                        szTemp,
+                                        ((PCTIME)pColumnData)->hours,
+                                        ((PCTIME)pColumnData)->minutes,
+                                        ((PCTIME)pColumnData)->seconds);
+                                pcsz = szTemp;
+                            break;
+
+                            case CFA_ULONG:
+                                nlsThousandsULong(szTemp,
+                                                  *((PULONG)pColumnData),
+                                                  pData->cs.cs.cThousands);
+                                pcsz = szTemp;
+                            break;
+                        }
+
+                        if (pcsz)
+                            ctnrDrawString(hps,
+                                          pcsz,
+                                          &rcl,
+                                          pfi->flData,
+                                          &pData->fm);
+
+                        // if we're outside the paint rect now,
+                        // we can quit
+                        // _Pmpf(("rcl.yBottom: %d, rclClip.yBottom: %d",
+                        //         rcl.yBottom,
+                        //         rclClip.yBottom));
+                        if (rclRecc.yBottom <= rclClip.yBottom)
+                            break;
+
+                    } // if (!(prliThis->flRecordAttr & CRA_FILTERED))
+
+                    pRecNode = pRecNode->pNext;
+
+                } // while (pRecNode)
+
+                // paint vertical separators after this column?
+                if (pfi->flData & CFA_SEPARATOR)
+                {
+                    POINTL  ptl;
+                    GpiSetColor(hps, ctlQueryColor(&pData->dwdMain, CNRCOL_BORDER));
+                    ptl.x = rcl.xRight + COLUMN_PADDING_X;
+                    ptl.y = pData->dwdContent.szlWin.cy;
+                    GpiMove(hps,
+                            &ptl);
+                    ptl.y = 0;
+                    GpiLine(hps,
+                            &ptl);
+                }
+
+                fFirstCol = FALSE;
+
+                ++cColumn;
+
+                // we're done if this column is outside the
+                // paint rectangle
+                if (    (!(pCol->pfi->flData & CFA_INVISIBLE))
+                     && (pCol->xLeft + pCol->cxContent - pData->scrw.ptlScrollOfs.x >= rclClip.xRight)
+                   )
+                    break; // while (pColNode)
+
+            } // if (!(pfi->flData & CFA_INVISIBLE))
+
+            pColNode = pColNode->pNext;
+
+        } // while (pColNode)
+
+        WinEndPaint(hps);
+    }
+}
+
+/*
+ *@@ FindRecordFromMouseY:
+ *
+ */
+
+PRECORDLISTITEM FindRecordFromMouseY(PCNRDATA pData,
+                                     SHORT y)
+{
+    // convert y clickpos from window to workspace coordinates
+    LONG    deltaWorkspace =   (   pData->scrw.szlWorkarea.cy
+                                 - pData->dwdContent.szlWin.cy)
+                             - pData->scrw.ptlScrollOfs.y;
+    LONG    yWorkspace =  (LONG)y + deltaWorkspace;
+
+    PLISTNODE pRecNode = lstQueryFirstNode(&pData->llRootRecords);
+    while (pRecNode)
+    {
+        PRECORDLISTITEM prliThis = (PRECORDLISTITEM)pRecNode->pItemData;
+
+        if (!(prliThis->flRecordAttr & CRA_FILTERED))
+        {
+            if (prliThis->ptl.y < yWorkspace)
+                return prliThis;
+        }
+
+        pRecNode = pRecNode->pNext;
+    }
+
+    return NULL;
+}
+
+/*
+ *@@ DeselectExcept:
+ *
+ */
+
+VOID DeselectExcept(PCNRDATA pData,
+                    PRECORDLISTITEM prliMouse)
+{
+    // deselect all selected
+    PLISTNODE pRecNode;
+    pRecNode = lstQueryFirstNode(&pData->llRootRecords);
+    while (pRecNode)
+    {
+        PRECORDLISTITEM prliThis = (PRECORDLISTITEM)pRecNode->pItemData;
+
+        if (    (prliThis != prliMouse)
+             && (prliThis->flRecordAttr & CRA_SELECTED)
+           )
+        {
+            ctnrChangeEmphasis(pData,
+                               prliThis,
+                               FALSE,
+                               CRA_SELECTED);
+        }
+
+        pRecNode = pRecNode->pNext;
+    }
+}
+
+/*
+ *@@ DetailsSingleSelect:
+ *      implementation for WM_SINGLESELECT in fnwpCnrDetails.
+ */
+
+MRESULT DetailsSingleSelect(PCNRDATA pData,
+                            SHORT y)
+{
+    if (pData)
+    {
+        BOOL    fCtrl = WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000;
+        ULONG   ulSel = ctnrQuerySelMode(pData);
+
+        // find record under mouse
+        PRECORDLISTITEM prliMouse = FindRecordFromMouseY(pData, y);
+
+        if (    (ulSel == CCS_SINGLESEL)
+             || (!fCtrl)
+           )
+        {
+            DeselectExcept(pData, prliMouse);
+        }
+
+        if (prliMouse)      // can be null with click on whitespace
+        {
+            // when ctrl is pressed, toggle the state of
+            // the record under the mouse; otherwise select it
+            ctnrChangeEmphasis(pData,
+                               prliMouse,
+                               (!fCtrl) || (!(prliMouse->flRecordAttr & CRA_SELECTED)),
+                               CRA_SELECTED | CRA_CURSORED);
+        }
+
+        return (MRESULT)TRUE;
+    }
+
+    return (MRESULT)FALSE;
+}
+
+/*
+ *@@ DetailsBeginSelect:
+ *      implementation for WM_BEGINSELECT in fnwpCnrDetails.
+ *
+ */
+
+MRESULT DetailsBeginSelect(PCNRDATA pData,
+                           SHORT y)
+{
+    if (pData)
+    {
+        BOOL    fCtrl = WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000;
+        ULONG   ulSel = ctnrQuerySelMode(pData);
+        PRECORDLISTITEM prliMouse;
+
+        // find record under mouse
+        prliMouse
+        = pData->prliSwipingFirst
+        = FindRecordFromMouseY(pData, y);
+
+        if (    (ulSel == CCS_SINGLESEL)
+             || (!fCtrl)
+           )
+        {
+            DeselectExcept(pData, prliMouse );
+        }
+
+        if (prliMouse )
+        {
+            // when ctrl is pressed, toggle the state of
+            // the record under the mouse; otherwise select it;
+            // perform this operation on all the records that
+            // user swipes over (seems to be how pm cnr does it)
+            pData->fSwipeTurnOn = (!fCtrl) || (!(prliMouse->flRecordAttr & CRA_SELECTED));
+
+            ctnrChangeEmphasis(pData,
+                               prliMouse ,
+                               pData->fSwipeTurnOn,
+                               CRA_SELECTED | CRA_CURSORED);
+            WinSetCapture(HWND_DESKTOP, pData->dwdContent.hwnd);
+        }
+
+        return (MRESULT)TRUE;
+    }
+
+    return (MRESULT)FALSE;
+}
+
+/*
+ *@@ DetailsMouseMove:
+ *      implementation for WM_MOUSEMOVE in fnwpCnrDetails.
+ */
+
+MRESULT DetailsMouseMove(PCNRDATA pData,
+                         MPARAM mp1,
+                         MPARAM mp2)
+{
+    if (pData)
+    {
+        if (pData->prliSwipingFirst)
+        {
+            // we're swiping:
+            PRECORDLISTITEM prliThis;
+            if (prliThis = FindRecordFromMouseY(pData,
+                                                SHORT2FROMMP(mp1)))
+                ctnrChangeEmphasis(pData,
+                                   prliThis,
+                                   pData->fSwipeTurnOn,
+                                   CRA_SELECTED | CRA_CURSORED);
+        }
+
+        return ctlDefWindowProc(&pData->dwdContent, WM_MOUSEMOVE, mp1, mp2);
+    }
+
+    return (MRESULT)FALSE;
+}
+
+/*
+ *@@ DetailsEndSelect:
+ *      implementation for WM_ENDSELECT in fnwpCnrDetails.
+ */
+
+MRESULT DetailsEndSelect(PCNRDATA pData)
+{
+    if (pData)
+    {
+        if (pData->prliSwipingFirst)
+        {
+            WinSetCapture(HWND_DESKTOP, NULLHANDLE);
+            pData->prliSwipingFirst = NULL;
+        }
+
+        return (MRESULT)TRUE;
+    }
+
+    return (MRESULT)FALSE;
+}
+
+/*
+ *@@ DetailsOpen:
+ *      implementation for WM_OPEN in fnwpCnrDetails.
+ */
+
+MRESULT DetailsOpen(PCNRDATA pData)
+{
+    if (pData)
+    {
+        ctnrRecordEnter(pData,
+                        pData->prliCursored,
+                        FALSE);     // mouse, not keyboard
+
+        return (MRESULT)TRUE;
+    }
+
+    return (MRESULT)FALSE;
+}
+
+/*
+ *@@ ScrollToRecord:
+ *      scrolls the content area so that the given record becomes
+ *      visible.
+ *
+ +      If fMakeTop == TRUE, we scroll so that the top of the
+ *      record's box is scrolled to the top of the window.
+ *      Otherwise we scroll so that the bottom of the record's
+ *      box is at the bottom of the rectangle.
+ */
+
+VOID ScrollToRecord(PCNRDATA pData,
+                    PRECORDLISTITEM prliThis,
+                    BOOL fMakeTop,
+                    BOOL fForce)        // in: if TRUE, we scroll even if the record is already visible
+{
+    RECTL   rcl;
+    LONG    lNewY = -100;
+
+    ctnrGetRecordRect(pData, &rcl, prliThis);
+    if (fMakeTop)
+    {
+        if (    (rcl.yTop > pData->dwdContent.szlWin.cy)
+             || (fForce)
+           )
+        {
+/*
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿   Ä¿
+³                             ³    ³ pData->scrw.ptlScrollOfs.y
++-----------------------------+   ÄÙ
++-----------------------------+
+º                             º
+º                             º
+ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍŒ
+³                             ³
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+*/
+            lNewY =   pData->scrw.szlWorkarea.cy
+                    - (prliThis->ptl.y + prliThis->szlBox.cy);
+        }
+    }
+    else if (    (rcl.yBottom < 0)
+              || (fForce)
+            )
+    {
+/*
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿   Ä¿
+³                             ³    ³ pData->scrw.ptlScrollOfs.y
+ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»    ³
+º                             º    ³
++-----------------------------+    ³
++-----------------------------+   ÄÙ
+³                             ³
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+*/
+        lNewY =   pData->scrw.szlWorkarea.cy
+                - pData->dwdContent.szlWin.cy
+                - prliThis->ptl.y;
+    }
+
+    if (lNewY != -100)
+    {
+        POINTL  ptlScroll;
+
+        if (lNewY < 0)
+            lNewY = 0;
+        else if (lNewY >= pData->scrw.szlWorkarea.cy)
+            lNewY = pData->scrw.szlWorkarea.cy - 1;
+
+        ptlScroll.x = 0;
+        ptlScroll.y = lNewY - pData->scrw.ptlScrollOfs.y;
+
+        winhScrollWindow(pData->dwdContent.hwnd,
+                         NULL,
+                         &ptlScroll);
+
+        pData->scrw.ptlScrollOfs.y = lNewY;
+
+        WinPostMsg(pData->dwdMain.hwnd,
+                   WM_SEM2,
+                   (MPARAM)DDFL_WORKAREACHANGED,
+                   0);
+    }
+}
+
+/*
+ *@@ DetailsChar:
+ *      implementation for WM_CHAR in fnwpCnrDetails.
+ */
+
+MRESULT DetailsChar(PCNRDATA pData,
+                    MPARAM mp1,
+                    MPARAM mp2)
+{
+    if (pData)
+    {
+        USHORT      usFlags = SHORT1FROMMP(mp1);
+        USHORT      usch    = SHORT1FROMMP(mp2);
+        USHORT      usvk    = SHORT2FROMMP(mp2);
+
+        PRECORDLISTITEM prliThis;
+        PLISTNODE   pNode;
+        PRECORDLISTITEM prliScrollTo = NULL;
+        BOOL        fMakeTop = FALSE,
+                    fForce = FALSE;
+
+        if (usFlags & KC_VIRTUALKEY)
+        {
+            switch (usvk)
+            {
+                case VK_ENTER:
+                case VK_NEWLINE:
+                    if (!(usFlags & KC_KEYUP))
+                        if (pData->prliCursored)
+                            ctnrRecordEnter(pData,
+                                            pData->prliCursored,
+                                            TRUE);      // keyboard
+
+                    return (MRESULT)TRUE;
+
+                case VK_SPACE:
+                    if (!(usFlags & KC_KEYUP))
+                    {
+                        if (    (CCS_SINGLESEL != ctnrQuerySelMode(pData))
+                             && (prliThis = pData->prliCursored)
+                           )
+                        {
+                            if (prliThis->flRecordAttr & CRA_SELECTED)
+                                DeselectExcept(pData, NULL);
+                            else
+                                ctnrChangeEmphasis(pData,
+                                                   prliThis,
+                                                   TRUE,
+                                                   CRA_SELECTED);
+                        }
+                    }
+
+                    return (MRESULT)TRUE;
+
+                case VK_DOWN:
+                case VK_UP:
+                    if (!(usFlags & KC_KEYUP))
+                    {
+                        if (    (prliThis = pData->prliCursored)
+                             && (pNode = ctnrFindListNodeForRecc(pData, prliThis->precc))
+                           )
+                        {
+                            while (TRUE)
+                            {
+                                if (usvk == VK_UP)
+                                {
+                                    if (!(pNode = pNode->pPrevious))
+                                        break;
+                                }
+                                else
+                                    if (!(pNode = pNode->pNext))
+                                        break;
+
+                                prliThis = (PRECORDLISTITEM)pNode->pItemData;
+                                if (!(prliThis->flRecordAttr & CRA_FILTERED))
+                                {
+                                    prliScrollTo = prliThis;
+                                    fMakeTop = (usvk == VK_UP);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                break;
+
+                case VK_HOME:
+                case VK_END:
+                    if (!(usFlags & KC_KEYUP))
+                    {
+                        if (usvk == VK_HOME)
+                            pNode = lstQueryFirstNode(&pData->llRootRecords);
+                        else
+                            pNode = lstQueryLastNode(&pData->llRootRecords);
+
+                        while (pNode)
+                        {
+                            prliThis = (PRECORDLISTITEM)pNode->pItemData;
+                            if (!(prliThis->flRecordAttr & CRA_FILTERED))
+                            {
+                                prliScrollTo = prliThis;
+                                if (usvk == VK_HOME)
+                                    fMakeTop = TRUE;
+                                fForce = TRUE;
+                                break;
+                            }
+
+                            if (usvk == VK_HOME)
+                                pNode = pNode->pNext;
+                            else
+                                pNode = pNode->pPrevious;
+                        }
+                    }
+                break;
+
+                case VK_PAGEDOWN:
+                    if (!(usFlags & KC_KEYUP))
+                    {
+                        // find the bottommost record currently visible in the
+                        // viewport and make that one the topmost
+                        prliScrollTo = FindRecordFromMouseY(pData,
+                                                            0);     // window y
+                        fMakeTop = TRUE;
+                        fForce = TRUE;
+                    }
+                break;
+
+                case VK_PAGEUP:
+                    if (!(usFlags & KC_KEYUP))
+                    {
+                        // find the topmost record currently visible in the
+                        // viewport
+                        prliScrollTo = FindRecordFromMouseY(pData,
+                                                            pData->dwdContent.szlWin.cy);     // window y
+                            // @@todo this is slightly different from the pm cnr behavior
+                        fForce = TRUE;
+                    }
+                break;
             }
 
-            // paint vertical separators after this column?
-            if (pfi->flData & CFA_SEPARATOR)
+            if (prliScrollTo)
             {
-                ptl.x = rcl.xRight + COLUMN_PADDING_X;
-                ptl.y = pData->dwdContent.szlWin.cy;
-                GpiMove(hps,
-                        &ptl);
-                ptl.y = 0;
-                GpiLine(hps,
-                        &ptl);
+                DeselectExcept(pData, prliScrollTo);
+
+                ctnrChangeEmphasis(pData,
+                                   prliScrollTo,
+                                   TRUE,
+                                   CRA_SELECTED | CRA_CURSORED);
+
+                // now make sure the new record is visible
+                // in the viewport
+                ScrollToRecord(pData,
+                               prliScrollTo,
+                               fMakeTop,
+                               fForce);
+
+                return (MRESULT)TRUE;
             }
-
-            ++cColumn;
-
-        } // FOR_ALL_NODES(&pData->llColumns, pColNode)
-
-        WinEndPaint(hps);
-    }
+        }
+    }
+
+    return (MRESULT)FALSE;
 }
 
@@ -434,9 +1144,15 @@
 MRESULT EXPENTRY fnwpCnrDetails(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
 {
-    MRESULT mrc = 0;
-    PCNRDATA    pData;
+    MRESULT     mrc = 0;
+    PCNRDATA    pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1);
 
     switch (msg)
     {
+        /* ******************************************************************
+         *
+         *   Standard window messages
+         *
+         ********************************************************************/
+
         case WM_CREATE:
             mrc = DetailsCreate(hwnd, mp1, mp2);
@@ -444,16 +1160,72 @@
 
         case WM_PAINT:
-            DetailsPaint(hwnd);
+            DetailsPaint(pData);
         break;
 
+        case WM_SYSCOLORCHANGE:
+            ctnrPresParamChanged(hwnd, 0);
+        break;
+
         case WM_PRESPARAMCHANGED:
-            cnrPresParamChanged(hwnd, (ULONG)mp1);
+            ctnrPresParamChanged(hwnd, (ULONG)mp1);
         break;
 
+        /* ******************************************************************
+         *
+         *   Mouse and keyboard input
+         *
+         ********************************************************************/
+
+        case WM_MOUSEMOVE:
+            mrc = DetailsMouseMove(pData,
+                                   mp1,
+                                   mp2);
+        break;
+
+        case WM_BUTTON1DOWN:
+        case WM_BUTTON2DOWN:
+        case WM_BUTTON3DOWN:
+            WinSetFocus(HWND_DESKTOP, pData->dwdContent.hwnd);
+            mrc = (MPARAM)TRUE;
+        break;
+
+        case WM_SINGLESELECT:
+            mrc = DetailsSingleSelect(pData,
+                                      SHORT2FROMMP(mp1));       // we only need y in details view
+        break;
+
+        case WM_BEGINSELECT:
+            mrc = DetailsBeginSelect(pData,
+                                     SHORT2FROMMP(mp1));
+        break;
+
+        case WM_ENDSELECT:
+            mrc = DetailsEndSelect(pData);
+        break;
+
+        case WM_OPEN:
+            mrc = DetailsOpen(pData);
+        break;
+
+        case WM_CHAR:
+            mrc = DetailsChar(pData, mp1, mp2);
+        break;
+
+        /* ******************************************************************
+         *
+         *   Direct manipulation
+         *
+         ********************************************************************/
+
+        case DM_DRAGOVER:
+        case DM_DRAGLEAVE:
+        case DM_DROPNOTIFY:
+        case DM_DROP:
+        case DM_DROPHELP:
+
         default:
-            if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+            if (pData)
                 mrc = ctlDefWindowProc(&pData->dwdContent, msg, mp1, mp2);
         break;
-
     }
 
Index: trunk/src/helpers/cctl_toolbar.c
===================================================================
--- trunk/src/helpers/cctl_toolbar.c	(revision 242)
+++ trunk/src/helpers/cctl_toolbar.c	(revision 243)
@@ -186,5 +186,5 @@
     LONG    lLeft,
             lRight,
-            lColorMiddle = pbd->dwd.lcolBackground;
+            lColorMiddle = ctlQueryColor(&pbd->dwd, CTLCOL_BGND); // pbd->dwd.lcolBackground;
     RECTL   rclWin,
             rclTemp;
@@ -344,5 +344,6 @@
        )
     {
-        GpiSetColor(hps, pbd->dwd.lcolForeground);
+        GpiSetColor(hps,
+                    ctlQueryColor(&pbd->dwd, CTLCOL_FGND)); // pbd->dwd.lcolForeground);
         rclTemp.yTop -= 2 * TBB_TEXTSPACING + lOfs;
         rclTemp.xRight += 2 * lOfs;     // twice the offset because we center horizontally
@@ -354,5 +355,5 @@
 }
 
-static const SYSCOLORSET G_scsToolbarButton =
+/* static const SYSCOLORSET G_scsToolbarButton =
     {
         TRUE,       // inherit presparams
@@ -360,4 +361,11 @@
         SYSCLR_BUTTONMIDDLE,
         SYSCLR_MENUTEXT
+    };
+*/
+
+static const CCTLCOLOR G_scsToolbarButton[] =
+    {
+        TRUE, PP_BACKGROUNDCOLOR, SYSCLR_BUTTONMIDDLE,
+        TRUE, PP_FOREGROUNDCOLOR, SYSCLR_MENUTEXT,
     };
 
@@ -445,5 +453,7 @@
                    &pData->bd.dwd,
                    WinDefWindowProc,
-                   &G_scsToolbarButton);
+                   0,
+                   G_scsToolbarButton,
+                   ARRAYITEMCOUNT(G_scsToolbarButton));
 
         if (    (pcszText)
@@ -851,9 +861,8 @@
  ********************************************************************/
 
-static const SYSCOLORSET G_scsToolbar =
-    {
-        TRUE,       // inherit presparams
-        SYSCLR_MENU,
-        SYSCLR_MENUTEXT
+static const CCTLCOLOR G_scsToolbar[] =
+    {
+        TRUE, PP_BACKGROUNDCOLOR, SYSCLR_MENU,
+        TRUE, PP_FOREGROUNDCOLOR, SYSCLR_MENUTEXT
     };
 
@@ -1017,9 +1026,5 @@
                            (PVOID)pcszFont);
 
-        lColor = winhQueryPresColor2(pData->dwd.hwnd,
-                                     PP_BACKGROUNDCOLOR,
-                                     PP_BACKGROUNDCOLORINDEX,
-                                     FALSE,
-                                     G_scsToolbar.lBackIndex);
+        lColor = ctlQueryColor(&pData->dwd, CTLCOL_BGND);
         winhStorePresParam(&ppp,
                            PP_BACKGROUNDCOLOR,
@@ -1027,9 +1032,5 @@
                            &lColor);
 
-        lColor = winhQueryPresColor2(pData->dwd.hwnd,
-                                     PP_FOREGROUNDCOLOR,
-                                     PP_FOREGROUNDCOLORINDEX,
-                                     FALSE,
-                                     G_scsToolbar.lForeIndex);
+        lColor = ctlQueryColor(&pData->dwd, CTLCOL_FGND);
         winhStorePresParam(&ppp,
                            PP_FOREGROUNDCOLOR,
@@ -1112,5 +1113,7 @@
                &pData->dwd,
                WinDefWindowProc,
-               &G_scsToolbar);
+               0,
+               G_scsToolbar,
+               ARRAYITEMCOUNT(G_scsToolbar));
 
     pData->hwndControlsOwner = ptbcd->hwndControlsOwner;
Index: trunk/src/helpers/comctl.c
===================================================================
--- trunk/src/helpers/comctl.c	(revision 242)
+++ trunk/src/helpers/comctl.c	(revision 243)
@@ -178,5 +178,35 @@
  *      given window. This must be called in WM_CREATE
  *      of a window proc if it intends to use
- *      ctlDefWindowProc as its default window procedure.
+ *      ctlDefWindowProc as its default window procedure
+ *      for generic message handling.
+ *
+ *      Parameter remarks:
+ *
+ *      --  For hwnd and mp2, pass in what you get in WM_CREATE.
+ *
+ *      --  pdwd must point to a static DEFWINDATA which will
+ *          maintain instance data for this control. It is
+ *          recommended to have a DEFWINDATA struct in your
+ *          window's instance data, which you must allocate
+ *          in WM_CREATE anyway. So just pass the address of
+ *          that struct within your instance buffer.
+ *
+ *      --  pDefWindowProc is called by ctlDefWindowProc
+ *          after its own message processing. In most cases,
+ *          pass in WinDefWindowProc, unless you're superclassing
+ *          a standard PM control.
+ *
+ *      --  flCtl fine-tunes the behavior of ctlDefWindowProc:
+ *
+ *          --  If CCS_NOSENDCTLPTR, we do not send WM_CONTROLPOINTER
+ *              on every mouse move, but force the system "arrow"
+ *              pointer over the control.
+ *
+ *      --  paCtlColors must point to a CCTLCOLOR array. This
+ *          enables automatic presparams and syscolor caching
+ *          within ctlDefWindowProc so your window gets invalidated
+ *          automatically. If you always use ctlQueryColor() in your
+ *          WM_PAINT handler to determine the colors to be used for
+ *          painting, you have full presparams support.
  *
  *@@added V1.0.1 (2002-11-30) [umoeller]
@@ -184,15 +214,21 @@
 
 VOID ctlInitDWD(HWND hwnd,
-                MPARAM mp2,
+                MPARAM mp2,                     // in: mp2 of WM_CREATE
                 PDEFWINDATA pdwd,
-                PFNWP pDefWindowProc,
-                const SYSCOLORSET *pSysColorSet)
+                PFNWP pDefWindowProc,           // in: parent window proc
+                ULONG flCtl,                    // in: CTL_* flags
+                const CCTLCOLOR *paCtlColors,
+                ULONG cCtlColors)
 {
     pdwd->hwnd = hwnd;
+    pdwd->hab = WinQueryAnchorBlock(hwnd);
+    pdwd->pDefWindowProc = pDefWindowProc;
+    pdwd->flCtl = flCtl;
     pdwd->szlWin.cx = ((PCREATESTRUCT)mp2)->cx;
     pdwd->szlWin.cy = ((PCREATESTRUCT)mp2)->cy;
-    pdwd->hab = WinQueryAnchorBlock(hwnd);
-    pdwd->pDefWindowProc = pDefWindowProc;
-    pdwd->pSysColorSet = pSysColorSet;
+    pdwd->paCtlColors = paCtlColors;
+    pdwd->cCtlColors = cCtlColors;
+
+    pdwd->palColorValues = (PLONG)malloc(sizeof(LONG) * cCtlColors);
 
     ctlRefreshColors(pdwd);
@@ -207,14 +243,34 @@
 VOID ctlRefreshColors(PDEFWINDATA pdwd)
 {
-    pdwd->lcolBackground = winhQueryPresColor2(pdwd->hwnd,
-                                               PP_BACKGROUNDCOLOR,
-                                               PP_BACKGROUNDCOLORINDEX,
-                                               pdwd->pSysColorSet->fInheritPP,
-                                               pdwd->pSysColorSet->lBackIndex);
-    pdwd->lcolForeground = winhQueryPresColor2(pdwd->hwnd,
-                                               PP_FOREGROUNDCOLOR,
-                                               PP_FOREGROUNDCOLORINDEX,
-                                               pdwd->pSysColorSet->fInheritPP,
-                                               pdwd->pSysColorSet->lForeIndex);
+    ULONG   ul;
+    for (ul = 0;
+         ul < pdwd->cCtlColors;
+         ++ul)
+    {
+        pdwd->palColorValues[ul] = winhQueryPresColor2(pdwd->hwnd,
+                                                       pdwd->paCtlColors[ul].ulPP,
+                                                       0,
+                                                       pdwd->paCtlColors[ul].fInheritPP,
+                                                       pdwd->paCtlColors[ul].ulSysColor);
+    }
+}
+
+/*
+ *@@ ctlQueryColor:
+ *      returns the color value corresponding to the given
+ *      color index. The color index is interpreted according
+ *      to the CCTLCOLOR array that was given to ctlInitDWD
+ *      and must not be larger than the number of colors in
+ *      that array minus 1.
+ *
+ *@@added V1.0.1 (2003-01-22) [umoeller]
+ */
+
+LONG ctlQueryColor(PDEFWINDATA pdwd, ULONG ulIndex)
+{
+    if (ulIndex < pdwd->cCtlColors)
+        return pdwd->palColorValues[ulIndex];
+
+    return 0;
 }
 
@@ -238,7 +294,24 @@
 {
     MRESULT mrc = 0;
+    BOOL    fCallDefault = TRUE;
+    HWND    hwndOwner;
 
     switch (msg)
     {
+        case WM_MOUSEMOVE:
+            if (    (!(pdwd->flCtl & CCS_NOSENDCTLPTR))
+                 && (hwndOwner = WinQueryWindow(pdwd->hwnd, QW_OWNER))
+               )
+            {
+                HPOINTER hptrArrow = WinQuerySysPointer(HWND_DESKTOP, SPTR_ARROW, FALSE);
+                WinSetPointer(HWND_DESKTOP,
+                              (HPOINTER)WinSendMsg(hwndOwner,
+                                                   WM_CONTROLPOINTER,
+                                                   (MPARAM)WinQueryWindowUShort(pdwd->hwnd, QWS_ID),
+                                                   (MPARAM)hptrArrow));
+                fCallDefault = FALSE;
+            }
+        break;
+
         case WM_SYSCOLORCHANGE:
         case WM_PRESPARAMCHANGED:
@@ -258,7 +331,11 @@
         break;
 
-        default:
-            mrc = pdwd->pDefWindowProc(pdwd->hwnd, msg, mp1, mp2);
+        case WM_DESTROY:
+            FREE(pdwd->palColorValues);
+        break;
     }
+
+    if (fCallDefault)
+        mrc = pdwd->pDefWindowProc(pdwd->hwnd, msg, mp1, mp2);
 
     return mrc;
Index: trunk/src/helpers/dosh.c
===================================================================
--- trunk/src/helpers/dosh.c	(revision 242)
+++ trunk/src/helpers/dosh.c	(revision 243)
@@ -3270,4 +3270,39 @@
 
 /*
+ *@@ doshCreateLogFilename:
+ *      produces a log filename in pszBuf.
+ *      If $(LOGFILES) is set, that directory
+ *      is used; otherwise we use the root
+ *      directory of the boot drive.
+ *
+ *@@added V1.0.1 (2003-01-25) [umoeller]
+ */
+
+BOOL doshCreateLogFilename(PSZ pszBuf,             // out: fully qualified filename
+                           PCSZ pcszFilename,      // in: short log filename
+                           BOOL fAllowBootDrive)   // in: allow creating log files on boot drive?
+{
+    CHAR    szBoot[] = "?:";
+    PSZ     pszLogDir;
+    if (DosScanEnv("LOGFILES",      // new eCS 1.1 setting
+                   &pszLogDir))
+    {
+        // variable not set:
+        if (!fAllowBootDrive)
+            return FALSE;
+
+        szBoot[0] = doshQueryBootDrive();
+        pszLogDir = szBoot;
+    }
+
+    sprintf(pszBuf,
+            "%s\\%s",
+            pszLogDir,
+            pcszFilename);
+
+    return TRUE;
+}
+
+/*
  *@@ doshCreateBackupFileName:
  *      creates a valid backup filename of pszExisting
Index: trunk/src/helpers/gpih.c
===================================================================
--- trunk/src/helpers/gpih.c	(revision 242)
+++ trunk/src/helpers/gpih.c	(revision 243)
@@ -737,10 +737,10 @@
     {
         ULONG cLines = strhCount(pcsz, '\n') + 1;
-        cyString = cLines * (pfm->lMaxBaselineExt + pfm->lExternalLeading);
+        cyString = cLines * (pfm->lMaxBaselineExt + pfm->lMaxDescender + pfm->lExternalLeading);
 
         if (fl & DT_VCENTER)
-            ptlRun.y += (cyRect - cyString) / 2;
+            ptlRun.y = (cyRect - cyString) / 2;
         else
-            ptlRun.y += cyRect - cyString;
+            ptlRun.y = cyString;
     }
 
@@ -789,7 +789,10 @@
         ptlRun.y -= pfm->lExternalLeading;
 
+        if (!*pNext)
+            break;
+
         if (*pNext == '\r')
             pNext++;
-        pThis = pNext;
+        pThis = ++pNext;
     }
 }
@@ -2553,6 +2556,6 @@
 LONG gpihStretchBitmap(HPS hpsTarget,       // in: memory PS to copy bitmap to
                        HBITMAP hbmSource,   // in: bitmap to be copied into hpsTarget (must be free)
-                       PRECTL prclSource,   // in: source rectangle -- if NULL, use size of bitmap
-                       PRECTL prclTarget,   // in: target rectangle (req.)
+                       PRECTL prclSource,   // in: source rectangle -- if NULL, use size of source bmp
+                       PRECTL prclTarget,   // in: target rectangle (required)
                        BOOL fProportional)  // in: preserve proportions when stretching?
 {
Index: trunk/src/helpers/helpers_post.in
===================================================================
--- trunk/src/helpers/helpers_post.in	(revision 242)
+++ trunk/src/helpers/helpers_post.in	(revision 243)
@@ -1,37 +1,8 @@
 #
-#   Post-Include file for the main helpers makefile.
-#   This contains inference rules for the files.
+#   Post-include file for the main helpers makefile.
 #
-
-# Now define inference rules for what to do with certain file
-# types, based on their file extension.
-# The syntax we need here is ".fromext.toext".
-# So whenever NMAKE encounters a .toext file, it
-# executes what we specify here.
-# The ugly {} brackets are some awkward syntax for specifying
-# files in other directories.
-
-# Special macros used here: $(@B) is the current target w/out ext.
-
-# -- compile C files to .OBJ files, using the CC_HELPPERS macro
-#    given to us.
-#    The output will be placed in the directory specified by
-#    the OUTPUTDIR variable (set above).
-
-.c.{$(OUTPUTDIR)}.obj:
-    @echo $(MAKEDIR)\makefile: Compiling $(@B).c
-!ifdef EMX
-    $(CC_HELPERS) -o $(OUTPUTDIR)\$(@B).obj $(@B).c
-!else
-!ifndef PRECH
-    $(CC_HELPERS) /Fo$(OUTPUTDIR)\$(@B).obj $(@B).c
-!else
-    $(CC_HELPERS) /fi"$(PRECH)\$(@B).pch" /si"$(PRECH)\$(@B).pch" /Fo$(OUTPUTDIR)\$(@B).obj $(@B).c
-!endif
-!endif
-
-$(OUTPUTDIR)\interlock.obj: $(@B).asm
-    @echo $(MAKEDIR)\makefile: Assembling $(@B).asm
-    alp -Sv:ALP -Fdo:$(OUTPUTDIR) $(@B).asm
+#   This is shared between makefile and makefile_dll
+#   and contains inference rules for the files.
+#
 
 # The .OBJ-from-sources dependencies are now automatically
@@ -43,4 +14,4 @@
 !endif
 
+$(OUTPUTDIR)\interlock.obj: $(@B).asm
 
-
Index: trunk/src/helpers/helpers_pre.in
===================================================================
--- trunk/src/helpers/helpers_pre.in	(revision 242)
+++ trunk/src/helpers/helpers_pre.in	(revision 243)
@@ -1,38 +1,8 @@
 #
 #   Pre-include file for the main helpers makefile.
-#   This contains shared definitions and all the objects
-#   that have to be built.
 #
-
-!ifndef PROJECT_BASE_DIR
-!error in $(MAKEDIR)\makefile: PROJECT_BASE_DIR must be set before calling the HELPERS makefile. Terminating.
-!endif
-
-!if [@echo     $(MAKEDIR)\makefile: PROJECT_BASE_DIR is: $(PROJECT_BASE_DIR)]
-!endif
-
-# include setup (compiler options etc.)
-!include $(PROJECT_BASE_DIR)\config.in
-!include $(PROJECT_BASE_DIR)\make\setup.in
-
-!ifndef HELPERS_OUTPUT_DIR
-!error in $(MAKEDIR)\makefile: HELPERS_OUTPUT_DIR must be set before calling the HELPERS makefile. Terminating.
-!endif
-
-# Define the suffixes for files which NMAKE will work on.
-# .SUFFIXES is a reserved NMAKE keyword ("pseudotarget") for
-# defining file extensions that NMAKE will recognize in inference
-# rules.
-.SUFFIXES: .c .h .ih .obj .lib .dll
-
-# OUTPUTDIR specifies the directory where we will put the
-# files created by this makefile.
-OUTPUTDIR = $(HELPERS_OUTPUT_DIR)
-
-!if [@echo     $(MAKEDIR)\makefile: helpers OBJs will be written to $(OUTPUTDIR)]
-!endif
-
-!if [@md $(OUTPUTDIR) 2> NUL]
-!endif
+#   This is shared between makefile and makefile_dll
+#   and defines all the objects that have to be built.
+#
 
 # The OBJS macro contains all the .OBJ files which need to be
@@ -101,4 +71,5 @@
 $(OUTPUTDIR)\memdebug.obj\
 $(OUTPUTDIR)\memdebug_win.obj\
+$(OUTPUTDIR)\mmpmh.obj\
 $(OUTPUTDIR)\shapewin.obj\
 $(OUTPUTDIR)\threads.obj\
@@ -108,7 +79,2 @@
 $(OUTPUTDIR)\winh.obj
 
-# helpers include path
-INC = ..\..\include
-HLPINC = $(INC)\helpers
-
-PROJECTINC = $(PROJECT_BASE_DIR)\include
Index: trunk/src/helpers/interlock.asm
===================================================================
--- trunk/src/helpers/interlock.asm	(revision 242)
+++ trunk/src/helpers/interlock.asm	(revision 243)
@@ -19,5 +19,5 @@
 CODE32         SEGMENT DWORD PUBLIC USE32 'CODE'
 
-;* LONG DosInterlockedIncrement(PLONG);
+;* LONG lockIncrement(PLONG);
 ;*
 ;* InterlockedIncrement adds 1 to a long variable and returns
@@ -28,6 +28,6 @@
 ;* The returned number need not be equal to the result!!!!
 
-public  DosInterlockedIncrement
-DosInterlockedIncrement proc near
+public  lockIncrement
+lockIncrement proc near
         ;we are allowed to trash edx
         mov     edx, dword ptr [esp+4] ; LPLONG lpAddend
@@ -36,7 +36,7 @@
         inc     eax
         ret     4
-DosInterlockedIncrement endp
+lockIncrement endp
 
-;* LONG DosInterlockedDecrement(PLONG);
+;* LONG lockDecrement(PLONG);
 ;*
 ;* InterlockedIncrement adds 1 to a long variable and returns
@@ -47,6 +47,6 @@
 ;* The returned number need not be equal to the result!!!!
 
-public  DosInterlockedDecrement
-DosInterlockedDecrement proc near
+public  lockDecrement
+lockDecrement proc near
         ;we are allowed to trash edx
         mov     edx, dword ptr [esp+4] ; LPLONG lpAddend
@@ -55,8 +55,8 @@
         dec     eax
         ret     4
-DosInterlockedDecrement endp
+lockDecrement endp
 
 
-; * LONG DosInterlockedExchange(PLONG, LONG);
+; * LONG lockExchange(PLONG, LONG);
 ; *
 ; * Atomically exchanges a pair of values.
@@ -65,6 +65,6 @@
 ; * Prior value of value pointed to by Target
 
-public  DosInterlockedExchange
-DosInterlockedExchange proc near
+public  lockExchange
+lockExchange proc near
         push    edx
         mov     eax, [esp+12]           ; LONG value
@@ -73,9 +73,9 @@
         pop     edx
         ret     8
-DosInterlockedExchange endp
+lockExchange endp
 
 
 ;/************************************************************************
-; * LONG DosInterlockedCompareExchange(PLONG dest, LONG xchg, LONG compare)
+; * LONG lockCompareExchange(PLONG dest, LONG xchg, LONG compare)
 ; *
 ; * Atomically compares Destination and Comperand, and if found equal exchanges
@@ -84,6 +84,6 @@
 ; */
 
-public DosInterlockedCompareExchange
-DosInterlockedCompareExchange proc near
+public lockCompareExchange
+lockCompareExchange proc near
         push    ebp
         mov     ebp, esp
@@ -100,7 +100,7 @@
         pop     ebp
         ret     12
-DosInterlockedCompareExchange endp
+lockCompareExchange endp
 
-; * LONG DosInterlockedExchangeAdd(PLONG dest, LONG incr);
+; * LONG lockExchangeAdd(PLONG dest, LONG incr);
 ; *
 ; * Atomically adds Increment to Addend and returns the previous value of
@@ -111,6 +111,6 @@
 ; */
 
-public DosInterlockedExchangeAdd
-DosInterlockedExchangeAdd proc near
+public lockExchangeAdd
+lockExchangeAdd proc near
         push    edx
         mov     eax, dword ptr [esp+12] ;LONG Increment /* Value to add */
@@ -119,5 +119,5 @@
         pop     edx
         ret     8
-DosInterlockedExchangeAdd endp
+lockExchangeAdd endp
 
 CODE32          ENDS
Index: trunk/src/helpers/makefile
===================================================================
--- trunk/src/helpers/makefile	(revision 242)
+++ trunk/src/helpers/makefile	(revision 243)
@@ -1,4 +1,2 @@
-# $Id$
-
 #
 # makefile:
@@ -9,9 +7,10 @@
 #       This makefile is even more complicated than the other makefiles
 #       because the helpers code has been designed to be independent
-#       of any single project. For example, the helpers code is used
-#       by XWorkplace also. As a result, I had to design a way so that
+#       of any single project. Presently the helpers code is used in
+#       XWorkplace and WarpIN. As a result, I had to design a way so that
 #       this makefile can produce output in a variable directory, with
 #       variable compiler flags, and so on. This is done via environment
-#       variables (see below).
+#       variables passed in from the "parent" makefile of a specific
+#       project.
 #
 #       Even worse, with V0.9.5, I chose to create a shared runtime DLL
@@ -85,10 +84,7 @@
 #
 
-# Say hello to yourself.
-!if [@echo +++++ Entering $(MAKEDIR)\makefile]
-!endif
-
-# helpers_pre.in: sets up more environment variables
-# and defines $(OBJ), which contains all object targets
+# set up shared environment variables
+!include ..\..\make\helpers.in
+# define $(OBJ), which contains all object targets
 !include helpers_pre.in
 
@@ -194,10 +190,12 @@
 TESTCASE_DIR = testcase
 
-DEBUGDIALOG =
+!undef DEBUGDIALOG
 !ifdef DBGDLG
 DEBUGDIALOG = /DDEBUG_DIALOG_WINDOWS=1
 !endif
 
-TESTCASE_CC = icc /c /ti+ /w2 /ss /se /D__DEBUG__=1 $(DEBUGDIALOG) /i$(HELPERS_BASE)\include /Fo$(TESTCASE_DIR)\$(@B).obj $(@B).c
+TESTCASE_CC_BASE = icc /c /ti+ /w2 /ss /se /D__DEBUG__=1 $(DEBUGDIALOG) /i..\..\include
+
+TESTCASE_CC = $(TESTCASE_CC_BASE) /Fo$(TESTCASE_DIR)\$(@B).obj  $(@B).c
 
 .c.{$(TESTCASE_DIR)}.obj:
@@ -212,4 +210,5 @@
     exeh.exe \
     fdlg.exe \
+    fdlg_pm.exe \
     xmap.exe \
     vcard.exe
@@ -265,5 +264,5 @@
 
 # fdlg.exe
-FDLG_TEST_OBJS = \
+FDLG_TEST_OBJS_SHARED = \
     $(TESTCASE_DIR)\comctl.obj \
     $(TESTCASE_DIR)\cctl_cnr.obj \
@@ -282,9 +281,26 @@
     $(TESTCASE_DIR)\tree.obj \
     $(TESTCASE_DIR)\nls.obj \
-    $(TESTCASE_DIR)\nlscache.obj \
+    $(TESTCASE_DIR)\nlscache.obj
+
+FDLG_TEST_OBJS = \
+    $(FDLG_TEST_OBJS_SHARED) \
     $(TESTCASE_DIR)\_call_filedlg.obj
 
-fdlg.exe: $(FDLG_TEST_OBJS)
-    ilink /debug /pmtype:pm $(FDLG_TEST_OBJS) pmprintf.lib /o:$@
+$(TESTCASE_DIR)\_call_filedlg_pm.obj: _call_filedlg.c
+    @echo $(MAKEDIR)\makefile: Compiling $(@B).c (PM version)
+    @echo INCLUDE is $(INCLUDE)
+    $(TESTCASE_CC_BASE) /D__USE_PM_CNR__=1 /Fo$(TESTCASE_DIR)\_call_filedlg_pm.obj _call_filedlg.c
+
+FDLG_TEST_OBJS_PM = \
+    $(FDLG_TEST_OBJS_SHARED) \
+    $(TESTCASE_DIR)\_call_filedlg_pm.obj
+
+fdlg.exe: $(FDLG_TEST_OBJS) makefile
+    @echo $(MAKEDIR)\makefile: Linking $@
+    ilink /nologo /debug /pmtype:pm $(FDLG_TEST_OBJS) pmprintf.lib /o:$@
+
+fdlg_pm.exe: $(FDLG_TEST_OBJS_PM) makefile
+    @echo $(MAKEDIR)\makefile: Linking $@ (PM version)
+    ilink /nologo /debug /pmtype:pm $(FDLG_TEST_OBJS_PM) pmprintf.lib /o:$@
 
 # xmap.exe
Index: trunk/src/helpers/makefile_dll
===================================================================
--- trunk/src/helpers/makefile_dll	(revision 242)
+++ trunk/src/helpers/makefile_dll	(revision 243)
@@ -21,4 +21,7 @@
 !endif
 
+# set up shared environment variables
+!include ..\..\make\helpers.in
+# define $(OBJ), which contains all object targets
 !include helpers_pre.in
 
Index: trunk/src/helpers/mmpmh.c
===================================================================
--- trunk/src/helpers/mmpmh.c	(revision 243)
+++ trunk/src/helpers/mmpmh.c	(revision 243)
@@ -0,0 +1,781 @@
+
+/*
+ *@@sourcefile mmpmh.c:
+ *
+ *      Usage: OS/2 PM programs.
+ *
+ *      Function prefixes (new with V0.81):
+ *      --  mmh*
+ *
+ *      This file is new with V1.0.1 and contains some
+ *      code formerly in XWorkplace multimedia.
+ *
+ *      Note: Version numbering in this file relates to XWorkplace version
+ *            numbering.
+ *
+ *@@header "helpers\mmpmh.h"
+ *@@added V1.0.1 (2003-01-25) [umoeller]
+ */
+
+/*
+ *      Copyright (C) 1997-2003 Ulrich Mller.
+ *      This file is part of the "XWorkplace helpers" source package.
+ *      This 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.
+ */
+
+#define INCL_DOSEXCEPTIONS
+#define INCL_DOSPROCESS
+#define INCL_DOSERRORS
+
+#define INCL_GPI                // required for INCL_MMIO_CODEC
+#define INCL_GPIBITMAPS         // required for INCL_MMIO_CODEC
+#include <os2.h>
+
+#define INCL_MCIOS2
+#define INCL_MMIOOS2
+#define INCL_MMIO_CODEC
+#include <os2me.h>
+
+#include <stdio.h>
+#include <setjmp.h>
+
+#include "setup.h"                      // code generation and debugging options
+
+#include "helpers\dosh.h"
+#include "helpers\except.h"
+#include "helpers\gpih.h"
+#include "helpers\mmpmh.h"
+#include "helpers\standards.h"
+
+#pragma hdrstop
+
+/*
+ *@@category: Helpers\Multimedia helpers
+ *      See nls.c.
+ */
+
+/* ******************************************************************
+ *
+ *   Global variables
+ *
+ ********************************************************************/
+
+extern FNTD_MCISENDCOMMAND              *G_mciSendCommand = NULL;
+extern FNTD_MCIGETERRORSTRING           *G_mciGetErrorString = NULL;
+
+extern FNTD_MMIOINIFILECODEC            *G_mmioIniFileCODEC = NULL;
+extern FNTD_MMIOQUERYCODECNAMELENGTH    *G_mmioQueryCODECNameLength = NULL;
+extern FNTD_MMIOQUERYCODECNAME          *G_mmioQueryCODECName = NULL;
+extern FNTD_MMIOINIFILEHANDLER          *G_mmioIniFileHandler = NULL;
+extern FNTD_MMIOQUERYFORMATCOUNT        *G_mmioQueryFormatCount = NULL;
+extern FNTD_MMIOGETFORMATS              *G_mmioGetFormats = NULL;
+extern FNTD_MMIOGETFORMATNAME           *G_mmioGetFormatName = NULL;
+
+extern FNTD_MMIOGETHEADER               *G_mmioGetHeader = NULL;
+extern FNTD_MMIOREAD                    *G_mmioRead = NULL;
+extern FNTD_MMIOIDENTIFYFILE            *G_mmioIdentifyFile = NULL;
+extern FNTD_MMIOOPEN                    *G_mmioOpen = NULL;
+extern FNTD_MMIOQUERYHEADERLENGTH       *G_mmioQueryHeaderLength = NULL;
+extern FNTD_MMIOCLOSE                   *G_mmioClose = NULL;
+
+BOOL G_fFuncsResolved = FALSE;
+
+/*
+ * G_aResolveFromMDM:
+ *      functions imported from MDM.DLL.
+ *      Used with doshResolveImports.
+ */
+
+static const RESOLVEFUNCTION G_aResolveFromMDM[] =
+    {
+        "mciSendCommand", (PFN*)&G_mciSendCommand,
+        "mciGetErrorString", (PFN*)&G_mciGetErrorString
+    };
+
+/*
+ * G_aResolveFromMMIO:
+ *      functions resolved from MMIO.DLL.
+ *      Used with doshResolveImports.
+ */
+
+static const RESOLVEFUNCTION G_aResolveFromMMIO[] =
+    {
+        "mmioIniFileCODEC", (PFN*)&G_mmioIniFileCODEC,
+        "mmioQueryCODECNameLength", (PFN*)&G_mmioQueryCODECNameLength,
+        "mmioQueryCODECName", (PFN*)&G_mmioQueryCODECName,
+        "mmioIniFileHandler", (PFN*)&G_mmioIniFileHandler,
+        "mmioQueryFormatCount", (PFN*)&G_mmioQueryFormatCount,
+        "mmioGetFormats", (PFN*)&G_mmioGetFormats,
+        "mmioGetFormatName", (PFN*)&G_mmioGetFormatName,
+        "mmioGetHeader", (PFN*)&G_mmioGetHeader,
+        "mmioRead", (PFN*)&G_mmioRead,
+        "mmioIdentifyFile", (PFN*)&G_mmioIdentifyFile,
+        "mmioOpen", (PFN*)&G_mmioOpen,
+        "mmioQueryHeaderLength", (PFN*)&G_mmioQueryHeaderLength,
+        "mmioClose", (PFN*)&G_mmioClose,
+    };
+
+/* ******************************************************************
+ *
+ *   Initialization
+ *
+ ********************************************************************/
+
+/*
+ *@@ mmhInit:
+ *      initializes the MMPM helpers by resolving
+ *      some MMPM/2 functions dynamically from the
+ *      MDM.DLL and MMIO.DLL libraries.
+ */
+
+APIRET mmhInit(VOID)
+{
+    APIRET arc;
+
+    HMODULE hmodMDM = NULLHANDLE,
+            hmodMMIO = NULLHANDLE;
+
+    if (!(arc = doshResolveImports("MDM.DLL",
+                                   &hmodMDM,
+                                   G_aResolveFromMDM,
+                                   ARRAYITEMCOUNT(G_aResolveFromMDM))))
+        if (!(arc = doshResolveImports("MMIO.DLL",
+                                       &hmodMMIO,
+                                       G_aResolveFromMMIO,
+                                       ARRAYITEMCOUNT(G_aResolveFromMMIO))))
+            G_fFuncsResolved = TRUE;
+
+    return arc;
+}
+
+/* ******************************************************************
+ *
+ *   IOProcs info
+ *
+ ********************************************************************/
+
+/*
+ *@@ mmhGetIOProcs:
+ *      allocates and fills an array of MMFORMATINFO
+ *      structures describing the IOProcs that are
+ *      installed on the system.
+ *
+ *      On input, specify any combination of these
+ *      flags to filter the type of IOProcs returned:
+ *
+ *      --  MMIO_MEDIATYPE_AUDIO            (0x00000002)
+ *
+ *      --  MMIO_MEDIATYPE_IMAGE            (0x00000001)
+ *
+ *      --  MMIO_MEDIATYPE_DIGITALVIDEO     (0x00000040)
+ *
+ *      --  MMIO_MEDIATYPE_MIDI             (0x00000004)
+ *
+ *      --  MMIO_MEDIATYPE_MOVIE            (0x00000100)
+ *
+ *      --  MMIO_MEDIATYPE_ANIMATION        (0x00000080)
+ *
+ *      --  MMIO_MEDIATYPE_COMPOUND         (0x00000008)
+ *
+ *      --  MMIO_MEDIATYPE_OTHER            (0x00000010)
+ *
+ *      --  MMIO_MEDIATYPE_UNKNOWN          (0x00000020)
+ *
+ *      If none of these are specified, all known formats
+ *      are returned.
+ *
+ *      If NO_ERROR is returned, *ppaInfos has been
+ *      set to a newly allocated array, which the caller
+ *      must free().
+ *
+ *      Otherwise this returns one of the following
+ *      error codes:
+ *
+ *      --  ERROR_NOT_ENOUGH_MEMORY
+ *
+ *      --  MMIOERR_INVALID_PARAMETER
+ *
+ *      --  MMIOERR_INTERNAL_SYSTEM
+ *
+ *      --  MMIO_ERROR: something else went wrong.
+ */
+
+APIRET mmhGetIOProcs(ULONG flMediaType,         // in: MMIO_MEDIATYPE_* flags
+                     PMMFORMATINFO *ppaInfos,   // out: new array of formats
+                     PLONG pcFormats)           // out: no. of items in array (not array size!)
+{
+    APIRET          arc;
+    MMFORMATINFO    mmfi;
+    LONG            cFormats;
+
+    if (!G_fFuncsResolved)
+        return MMHERR_MMPM_NOT_INITIALIZED;
+
+    memset(&mmfi, 0, sizeof(mmfi));     // zeroed struct means get all
+    mmfi.ulMediaType = flMediaType;
+    mmfi.ulStructLen = sizeof(mmfi);
+
+    if (!(arc = G_mmioQueryFormatCount(&mmfi,
+                                       &cFormats,
+                                       0,     // "reserved"
+                                       0)))    // "reserved"
+    {
+        PMMFORMATINFO   pammfi;
+        if (!(pammfi = (PMMFORMATINFO)malloc(cFormats * sizeof(MMFORMATINFO))))
+            arc = ERROR_NOT_ENOUGH_MEMORY;
+        else
+        {
+            LONG    lFormatsRead = 0;
+            if (!(arc = G_mmioGetFormats(&mmfi,
+                                         cFormats,
+                                         pammfi,
+                                         pcFormats,
+                                         0,     // reserved
+                                         0)))     // reserved
+                *ppaInfos = pammfi;
+            else
+                free(pammfi);
+        }
+    }
+
+    return arc;
+}
+
+/* ******************************************************************
+ *
+ *   File identification
+ *
+ ********************************************************************/
+
+/*
+ *@@ mmhIdentifyFile:
+ *      attempts to find an IOProc that can read the specified
+ *      file.
+ *
+ *      If NO_ERROR is returned, this fills the given MMFORMATINFO
+ *      with data about the given file.
+ *
+ *      Otherwise this returns:
+ *
+ *      --  MMHERR_MMPM_NOT_INITIALIZED: mmhInit not called or
+ *          it failed.
+ *
+ *      --  MMHERR_NO_IOPROC: no IOProc is installed
+ *          to handle this format.
+ */
+
+APIRET mmhIdentifyFile(PCSZ pcszFilename,       // in: file to check
+                       PMMFORMATINFO pmmfi)     // out: file format info
+{
+    APIRET  arc;
+    FOURCC  fccStorageSystem;
+
+    if (!G_fFuncsResolved)
+        return MMHERR_MMPM_NOT_INITIALIZED;
+
+    if (!(arc = G_mmioIdentifyFile(pcszFilename,
+                                   NULL,    // needed for RIFF only
+                                   pmmfi,   // out: format info (FOURCC)
+                                   &fccStorageSystem, // out: FOURCC of storage IOProc
+                                   0L,         // reserved
+                                   0L)))       // can be:
+                                    /*  MMIO_FORCE_IDENTIFY_SS
+                                            Forces the identification of a storage
+                                            system by ignoring the file
+                                            name and actually checking the MMIO Manager's
+                                            I/O procedure list.
+                                        MMIO_FORCE_IDENTIFY_FF
+                                            Forces the identification  of a file
+                                            format by ignoring the file name
+                                            and actually checking the MMIO Manager's
+                                            I/O procedure list. */
+    {
+        // if mmioIdentifyFile did not find a custom-written IO proc which
+        // can understand the image file, then it will return the DOS IO Proc
+        // info because the image file IS a DOS file.
+        if (    (pmmfi->fccIOProc == FOURCC_DOS)
+             || (!(pmmfi->ulFlags & MMIO_CANREADTRANSLATED))
+           )
+            arc = MMHERR_NO_IOPROC;
+    }
+
+    return arc;
+}
+
+/* ******************************************************************
+ *
+ *   Image loading
+ *
+ ********************************************************************/
+
+/*
+ *@@ mmhOpenImage:
+ *
+ */
+
+APIRET mmhOpenImage(PCSZ pcszFilename,  // in: filename to load
+                    FOURCC fccIOProc,   // in: FOURCC of ioproc to use; if 0, we call mmhIdentifyFile here
+                    HMMIO *phmmio)      // out: mmio handle
+{
+    APIRET  arc = NO_ERROR;
+
+    if (    (!pcszFilename)
+         || (!phmmio)
+       )
+        return ERROR_INVALID_PARAMETER;
+
+    if (!G_fFuncsResolved)
+        return MMHERR_MMPM_NOT_INITIALIZED;
+
+#ifdef __DEBUG__
+    TRY_LOUD(excpt1)
+#else
+    TRY_QUIET(excpt1)
+#endif
+    {
+        if (!fccIOProc)
+        {
+            MMFORMATINFO mmfi;
+            if (!(arc = mmhIdentifyFile(pcszFilename, &mmfi)))
+                // ensure this is an IMAGE IOproc
+                if (mmfi.ulMediaType != MMIO_MEDIATYPE_IMAGE)
+                    arc = MMHERR_NOT_IMAGE_FILE;
+                else
+                    fccIOProc = mmfi.fccIOProc;
+        }
+
+        if (!arc)
+        {
+            MMIOINFO      mmioinfo;
+
+            memset(&mmioinfo, 0, sizeof(MMIOINFO));
+            mmioinfo.fccIOProc = fccIOProc;
+            mmioinfo.ulTranslate = MMIO_TRANSLATEHEADER | MMIO_TRANSLATEDATA;
+
+            if (!(*phmmio = G_mmioOpen(pcszFilename,
+                                       &mmioinfo,
+                                       MMIO_READ | MMIO_DENYWRITE | MMIO_NOIDENTIFY)))
+                arc = MMHERR_OPEN_FAILED;
+        }
+    }
+    CATCH(excpt1)
+    {
+        arc = ERROR_PROTECTION_VIOLATION;
+    } END_CATCH();
+
+    return arc;
+}
+
+/*
+ *@@ mmhLoadImageHeader:
+ *
+ */
+
+APIRET mmhLoadImageHeader(HMMIO hmmio,              // in: mmio handle from mmhOpenImage
+                          PMMIMAGEHEADER pmmih,     // out: mmio image header
+                          PULONG pcbBytesPerRow)    // out: padded bytes for each bitmap scan line
+{
+    APIRET  arc = NO_ERROR;
+
+    if (    (!hmmio)
+         || (!pmmih)
+         || (!pcbBytesPerRow)
+       )
+        return ERROR_INVALID_PARAMETER;
+
+    if (!G_fFuncsResolved)
+        return MMHERR_MMPM_NOT_INITIALIZED;
+
+#ifdef __DEBUG__
+    TRY_LOUD(excpt1)
+#else
+    TRY_QUIET(excpt1)
+#endif
+    {
+        ULONG           cbHeader;
+        LONG            lBytesRead;
+        if (    (G_mmioQueryHeaderLength(hmmio,
+                                         (PLONG)&cbHeader,
+                                         0L,
+                                         0L))
+             || (cbHeader != sizeof (MMIMAGEHEADER))
+             || (G_mmioGetHeader(hmmio,
+                                 pmmih,
+                                 sizeof(MMIMAGEHEADER),
+                                 &lBytesRead,
+                                 0,
+                                 0))
+           )
+            arc = MMHERR_INCOMPATIBLE_HEADER;
+        else
+        {
+            /*
+             *  Determine the number of bytes required, per row.
+             *      PLANES MUST ALWAYS BE = 1
+             */
+
+            ULONG   cRowBits;
+            // ULONG   cbBytesPerRow;
+
+            ULONG   cbPadding;
+
+            // szlSourceBmp.cx = mmih.mmXDIBHeader.BMPInfoHeader2.cx;
+            // szlSourceBmp.cy = mmih.mmXDIBHeader.BMPInfoHeader2.cy;
+
+            // sBitCount = mmih.mmXDIBHeader.BMPInfoHeader2.cBitCount;
+
+            /*
+             *  Account for odd bits used in 1bpp or 4bpp images that are
+             *  NOT on byte boundaries.
+             */
+
+            cRowBits =   pmmih->mmXDIBHeader.BMPInfoHeader2.cx
+                       * pmmih->mmXDIBHeader.BMPInfoHeader2.cBitCount;
+            *pcbBytesPerRow = cRowBits >> 3;
+
+            if (cRowBits % 8)
+                ++(*pcbBytesPerRow);
+
+            /*
+             *  Ensure the row length in bytes accounts for byte padding.
+             *  All bitmap data rows must are aligned on LONG/4-BYTE boundaries.
+             *  The data FROM an IOProc should always appear in this form.
+             */
+
+            if (cbPadding = ((*pcbBytesPerRow) % 4))
+                *pcbBytesPerRow += 4 - cbPadding;
+        }
+    }
+    CATCH(excpt1)
+    {
+        arc = ERROR_PROTECTION_VIOLATION;
+    } END_CATCH();
+
+    return arc;
+}
+
+/*
+ *@@ mmhLoadImageBits:
+ *
+ */
+
+APIRET mmhLoadImageBits(HMMIO hmmio,            // in: mmio handle from mmhOpenImage
+                        PMMIMAGEHEADER pmmih,   // in: image header from mmhLoadImageHeader
+                        ULONG cbBytesPerRow,    // in: padded bytes per scanline from mmhLoadImageHeader
+                        PBYTE *ppbBitmapBits)   // out: bitmap data allocated via DosAllocMem
+{
+    APIRET  arc = NO_ERROR;
+
+    if (    (!ppbBitmapBits)
+         || (!cbBytesPerRow)
+         || (!pmmih)
+         || (!hmmio)
+       )
+        return ERROR_INVALID_PARAMETER;
+
+    if (!G_fFuncsResolved)
+        return MMHERR_MMPM_NOT_INITIALIZED;
+
+    *ppbBitmapBits = NULL;
+
+#ifdef __DEBUG__
+    TRY_LOUD(excpt1)
+#else
+    TRY_QUIET(excpt1)
+#endif
+    {
+        ULONG cScanLines = pmmih->mmXDIBHeader.BMPInfoHeader2.cy;
+
+        if (!(arc = DosAllocMem((PPVOID)ppbBitmapBits,
+                                cbBytesPerRow * cScanLines,
+                                PAG_COMMIT | PAG_READ | PAG_WRITE)))
+        {
+            ULONG   ulRowThis;
+            PBYTE   pbCurrent = *ppbBitmapBits;
+
+            // load the bitmap from the file,
+            // one line at a time, starting from the BOTTOM
+            for (ulRowThis = 0;
+                 ulRowThis < cScanLines;
+                 ulRowThis++)
+            {
+                ULONG   cbRead;
+
+                if (!(cbRead = G_mmioRead(hmmio,
+                                          pbCurrent,
+                                          cbBytesPerRow)))
+                    // 0 means "done" here:
+                    break;
+                else if (cbRead == MMIO_ERROR)
+                {
+                    arc = MMHERR_MMIOREAD_FAILED;
+                    break;
+                }
+
+                pbCurrent += cbBytesPerRow;
+            }
+        } // end if (DosAllocMem((PPVOID)&pRowBuffer,
+    }
+    CATCH(excpt1)
+    {
+        arc = ERROR_PROTECTION_VIOLATION;
+    } END_CATCH();
+
+    if (    (arc)
+         && (*ppbBitmapBits)
+       )
+    {
+        DosFreeMem(*ppbBitmapBits);
+        *ppbBitmapBits = NULL;
+    }
+
+    return arc;
+}
+
+/*
+ *@@ mmhCreateBitmapFromBits:
+ *
+ *@@added V1.0.1 (2003-01-29) [umoeller]
+ */
+
+APIRET mmhCreateBitmapFromBits(PSIZEL pszlTarget,   // in: desired size of new bitmap or NULL for no resize
+                               PBITMAPINFOHEADER2 pbmihSource,  // in: source bitmap format
+                               PBYTE pbBitmapBits,  // in: source bitmap data
+                               HBITMAP *phbmOut)   // out: newly created bitmap
+{
+    APIRET  arc = NO_ERROR;
+
+    HDC     hdcMem = NULLHANDLE;
+    HPS     hpsMem = NULLHANDLE;
+
+    if (!G_fFuncsResolved)
+        return MMHERR_MMPM_NOT_INITIALIZED;
+
+    if (    (!pbmihSource)
+         || (!pbBitmapBits)
+         || (!phbmOut)
+       )
+        return ERROR_INVALID_PARAMETER;
+
+    *phbmOut = NULLHANDLE;
+
+#ifdef __DEBUG__
+    TRY_LOUD(excpt1)
+#else
+    TRY_QUIET(excpt1)
+#endif
+    {
+        SIZEL   szlPS;
+
+        if (pszlTarget)
+        {
+            // caller wants to scale:
+            szlPS.cx = pszlTarget->cx;
+            szlPS.cy = pszlTarget->cy;
+        }
+        else
+        {
+            szlPS.cx = pbmihSource->cx;
+            szlPS.cy = pbmihSource->cy;
+        }
+
+        if (!gpihCreateMemPS(WinQueryAnchorBlock(HWND_DESKTOP),
+                             &szlPS,
+                             &hdcMem,
+                             &hpsMem))
+            arc = MMHERR_GPIGREATEPS_FAILED;
+        else
+        {
+            BITMAPINFOHEADER2 bmihTarget;
+            memcpy(&bmihTarget,
+                   pbmihSource,
+                   sizeof(bmihTarget));
+
+            bmihTarget.cx = szlPS.cx;
+            bmihTarget.cy = szlPS.cy;
+
+            // I'd love to pass the bits directly to
+            // GpiCreateBitmap, but scaling doesn't
+            // work with CBI_INIT... so we have to do
+            // GpiDrawBits separately.
+            if (!(*phbmOut = GpiCreateBitmap(hpsMem,
+                                             &bmihTarget,
+                                             0,
+                                             0,
+                                             0)))
+                arc = MMHERR_GPICREATEBITMAP_FAILED;
+            else
+            {
+                // bitmap created:
+
+                if (HBM_ERROR == GpiSetBitmap(hpsMem,
+                                              *phbmOut))
+                    arc = MMHERR_GPISETBITMAP_FAILED;
+                else
+                {
+                    POINTL aptl[4];
+                    // target bottom left (world coordinates)
+                    aptl[0].x = 0;
+                    aptl[0].y = 0;
+                    // target top right (world coordinates)
+                    aptl[1].x = bmihTarget.cx - 1;
+                    aptl[1].y = bmihTarget.cy - 1;
+                    // source bottom left (device coordinates)
+                    aptl[2].x = 0;
+                    aptl[2].y = 0;
+                    // source top right (device coordinates)
+                    aptl[3].x = pbmihSource->cx;
+                    aptl[3].y = pbmihSource->cy;
+
+                    if (GPI_ERROR == GpiDrawBits(hpsMem,
+                                                 pbBitmapBits,
+                                                 (PBITMAPINFO2)pbmihSource,
+                                                 4,
+                                                 aptl,
+                                                 ROP_SRCCOPY,
+                                                 BBO_IGNORE))
+                        arc = MMHERR_GPISETBITMAPBITS_FAILED;
+
+                    /*
+                    if (GPI_ALTERROR == GpiSetBitmapBits(hpsMem,
+                                                         (LONG)0,
+                                                         (LONG)szlSourceBmp.cy,
+                                                         (PBYTE)pRowBuffer,
+                                                         (PBITMAPINFO2)&bmihSource))
+                        arc = MMHERR_GPISETBITMAPBITS_FAILED;
+                    */
+                } // end if (GpiSetBitmap(hpsMem
+            } // if (!(*phbmOut = GpiCreateBitmap(hpsMem,
+        }
+    }
+    CATCH(excpt1)
+    {
+        arc = ERROR_PROTECTION_VIOLATION;
+    } END_CATCH();
+
+    if (hpsMem)
+    {
+        // unset bitmap from mem PS
+        GpiSetBitmap(hpsMem,
+                     NULLHANDLE);
+
+        if (arc && *phbmOut)
+        {
+            // error:
+            GpiDeleteBitmap(*phbmOut);
+            *phbmOut = NULLHANDLE;
+        }
+
+        GpiDestroyPS(hpsMem);
+    }
+
+    if (hdcMem)
+        DevCloseDC(hdcMem);
+
+    return arc;
+}
+
+/*
+ *@@ mmhLoadImage:
+ *      one-shot function for loading an image from a file.
+ *      Understands any image file format supported by MMPM/2.
+ *
+ *      If fccIOProc is specified, it _must_ represent an
+ *      IOProc for an image format. We do not verify this
+ *      here.
+ *
+ *      Otherwise, if fccIOProc is NULLHANDLE, we automatically
+ *      run mmhIdentifyFile and may return MMHERR_NOT_IMAGE_FILE.
+ *
+ *      Returns either NO_ERROR or one of the following
+ *      error codes:
+ *
+ *      --  MMHERR_MMPM_NOT_INITIALIZED: mmhInit not called or
+ *          it failed.
+ *
+ *      --  MMHERR_NO_IOPROC: file format not understood
+ *          by MMPM/2.
+ *
+ *      --  MMHERR_NOT_IMAGE_FILE: file format understood, but
+ *          is not image file or cannot be translated.
+ *
+ *      --  MMHERR_OPEN_FAILED: mmioOpen failed.
+ *
+ *      --  MMHERR_INCOMPATIBLE_HEADER: ioproc returned invalid
+ *          header.
+ *
+ *      --  ERROR_PROTECTION_VIOLATION: probably crash in IOProc.
+ *
+ *      plus the error codes from CreateBitmapFromFile().
+ */
+
+APIRET mmhLoadImage(PCSZ pcszFilename,  // in: filename to load
+                    FOURCC fccIOProc,   // in: FOURCC of ioproc to use; if 0, we call mmhIdentifyFile here
+                    PSIZEL pszlBmp,     // in: desired size of new bitmap or NULL for no resize
+                    HBITMAP *phbmOut)   // out: newly created bitmap
+{
+    APIRET  arc = NO_ERROR;
+
+    PBYTE   pbBitmapBits = NULL;
+    HMMIO   hmmio = NULLHANDLE;
+
+    if (    (!pcszFilename)
+         || (!phbmOut)
+       )
+        return ERROR_INVALID_PARAMETER;
+
+    if (!G_fFuncsResolved)
+        return MMHERR_MMPM_NOT_INITIALIZED;
+
+#ifdef __DEBUG__
+    TRY_LOUD(excpt1)
+#else
+    TRY_QUIET(excpt1)
+#endif
+    {
+        if (!(arc = mmhOpenImage(pcszFilename,
+                                 fccIOProc,
+                                 &hmmio)))
+        {
+            MMIMAGEHEADER   mmihSource;
+            ULONG           cbBytesPerRow;
+
+            if (!(arc = mmhLoadImageHeader(hmmio,
+                                           &mmihSource,
+                                           &cbBytesPerRow)))
+            {
+                if (!(arc = mmhLoadImageBits(hmmio,
+                                             &mmihSource,
+                                             cbBytesPerRow,
+                                             &pbBitmapBits)))
+                {
+                    arc = mmhCreateBitmapFromBits(pszlBmp,
+                                                  &mmihSource.mmXDIBHeader.BMPInfoHeader2,
+                                                  pbBitmapBits,
+                                                  phbmOut);
+                } // if (!(arc = mmhLoadImageBits(pcszFilename,
+            } // if (!(arc = mmhLoadImageHeader(pcszFilename,
+        }
+    }
+    CATCH(excpt1)
+    {
+        arc = ERROR_PROTECTION_VIOLATION;
+    } END_CATCH();
+
+    if (pbBitmapBits)
+        DosFreeMem(pbBitmapBits);
+
+    if (hmmio)
+        G_mmioClose(hmmio, 0);
+
+    return arc;
+}
+
+
Index: trunk/src/helpers/nls.c
===================================================================
--- trunk/src/helpers/nls.c	(revision 242)
+++ trunk/src/helpers/nls.c	(revision 243)
@@ -657,5 +657,5 @@
         break;
 
-        case 2: // yyyy.mm.dd  (Japanese)
+        case 2: // yyyy.mm.dd  (ISO and Japanese)
             sprintf(pszDate, "%04d%c%02d%c%02d",
                     year,
@@ -849,5 +849,5 @@
 
     for (ul = 0;
-         ul < sizeof(G_szUpperMap);
+         ul < 256;
          ++ul)
     {
@@ -901,12 +901,11 @@
 {
     ULONG   ul = 0;
+    PSZ     p;
 
     if (!G_fUpperMapInited)
         InitUpperMap();
 
-    if (psz)
-    {
-        PSZ     p = psz;
-
+    if (p = psz)
+    {
         while (*p++ = G_szUpperMap[*p])
             ++ul;
Index: trunk/src/helpers/nlscache.c
===================================================================
--- trunk/src/helpers/nlscache.c	(revision 242)
+++ trunk/src/helpers/nlscache.c	(revision 243)
@@ -6,5 +6,5 @@
  *
  *      Function prefixes (new with V0.81):
- *      --  stc*
+ *      --  nls*
  *
  *      This file is new with V0.9.19, but contains functions
Index: trunk/src/helpers/sem.c
===================================================================
--- trunk/src/helpers/sem.c	(revision 242)
+++ trunk/src/helpers/sem.c	(revision 243)
@@ -2,10 +2,8 @@
 /*
  *@@sourcefile sem.c:
- *      implements fast mutex semaphores a la Win32
- *      critical sections.
- *
- *      This is an OS/2 implementation of what Win32 calls as
- *      "critical sections"
- *      It is an implementation that is highly optimized for
+ *      implements fast mutex semaphores.
+ *
+ *      This is an OS/2 implementation of what Win32 calls
+ *      "critical sections". This is highly optimized for
  *      the case where there is only one thread trying to
  *      access the critical section, i.e. it is available most
@@ -194,5 +192,5 @@
     // do an atomic increase of the lockcounter and see if it is > 0
     // (i.e. it is already posessed)
-    if (DosInterlockedIncrement(&pmtx->LockCount))
+    if (lockIncrement(&pmtx->LockCount))
     {
         // semaphore was already requested:
@@ -206,9 +204,9 @@
         }
 
-        // current owner is different thread thread:
+        // current owner is different thread:
 
         // do an atomic operation where we compare the owning thread id with 0
         // and if this is true, exchange it with the id of the current thread
-        if (DosInterlockedCompareExchange((PLONG)&pmtx->OwningThread, threadid, 0))
+        if (lockCompareExchange((PLONG)&pmtx->OwningThread, threadid, 0))
         {
             // the compare did not return equal, i.e. the pmtx sect is in use
@@ -257,5 +255,5 @@
 BOOL semTry(PFASTMTX pmtx)
 {
-    if (DosInterlockedIncrement(&pmtx->LockCount))
+    if (lockIncrement(&pmtx->LockCount))
     {
         if (pmtx->OwningThread == GetTID())
@@ -265,5 +263,5 @@
         }
 
-        DosInterlockedDecrement(&pmtx->LockCount);
+        lockDecrement(&pmtx->LockCount);
 
         return FALSE;
@@ -291,5 +289,5 @@
     if (--pmtx->RecursionCount)
     {
-        DosInterlockedDecrement(&pmtx->LockCount );
+        lockDecrement(&pmtx->LockCount );
         return NO_ERROR;
     }
@@ -297,5 +295,5 @@
     pmtx->OwningThread = 0;
 
-    if (DosInterlockedDecrement(&pmtx->LockCount) >= 0)
+    if (lockDecrement(&pmtx->LockCount) >= 0)
         // someone is waiting
         DosPostEventSem(pmtx->hmtxLock);
Index: trunk/src/helpers/textview.c
===================================================================
--- trunk/src/helpers/textview.c	(revision 242)
+++ trunk/src/helpers/textview.c	(revision 243)
@@ -1522,5 +1522,5 @@
                                        // can be NULL to paint all
                   LONG lViewXOfs,      // in: x offset to paint; 0 means rightmost
-                  PULONG pulViewYOfs,  // in: y offset to paint; 0 means _top_most;
+                  PLONG plViewYOfs,    // in: y offset to paint; 0 means _top_most;
                                        // out: y offset which should be passed to next call
                                        // (if TRUE is returned and fPaintHalfLines == FALSE)
@@ -1534,5 +1534,5 @@
             fAnyLinesPainted = FALSE;
     ULONG   ulCurrentLineIndex = *pulLineIndex;
-    // LONG    lViewYOfsSaved = *pulViewYOfs;
+    // LONG    lViewYOfsSaved = *plViewYOfs;
     PLISTNODE pRectNode = lstNodeFromIndex(&pxfd->llRectangles,
                                            ulCurrentLineIndex);
@@ -1550,6 +1550,6 @@
         rclLine.xLeft = pLineRcl->rcl.xLeft - lViewXOfs;
         rclLine.xRight = pLineRcl->rcl.xRight - lViewXOfs;
-        rclLine.yBottom = pLineRcl->rcl.yBottom + *pulViewYOfs;
-        rclLine.yTop = pLineRcl->rcl.yTop + *pulViewYOfs;
+        rclLine.yBottom = pLineRcl->rcl.yBottom + *plViewYOfs;
+        rclLine.yTop = pLineRcl->rcl.yTop + *plViewYOfs;
 
         /* if (pmpf)
@@ -1711,8 +1711,8 @@
                     // return TRUE
                     brc = TRUE;
-                    // and set *pulViewYOfs to the top of
+                    // and set *plViewYOfs to the top of
                     // the next line, which wasn't visible
                     // on the page any more
-                    *pulViewYOfs = pLineRcl2->rcl.yTop + *pulViewYOfs;
+                    *plViewYOfs = pLineRcl2->rcl.yTop + *plViewYOfs;
                 }
                 break;
@@ -1867,6 +1867,8 @@
     XFORMATDATA xfd;
 
-    HWND    hwndVScroll,        // vertical scroll bar
-            hwndHScroll;        // horizontal scroll bar
+    SCROLLABLEWINDOW scrw;      // V1.0.1 (2003-01-25) [umoeller]
+
+//     HWND    hwndVScroll,        // vertical scroll bar
+//             hwndHScroll;        // horizontal scroll bar
 
     BOOL    fVScrollVisible,    // TRUE if vscroll is currently used
@@ -1878,6 +1880,6 @@
             rclViewText;        // same as rclViewPaint, but excluding cdata borders
 
-    ULONG   ulViewXOfs,         // pixels that we have scrolled to the RIGHT; 0 means very left
-            ulViewYOfs;         // pixels that we have scrolled to the BOTTOM; 0 means very top
+//     LONG    lViewXOfs,          // pixels that we have scrolled to the RIGHT; 0 means very left
+//             lViewYOfs;          // pixels that we have scrolled to the BOTTOM; 0 means very top
 
     BOOL    fAcceptsPresParamsNow; // TRUE after first WM_PAINT
@@ -1993,5 +1995,5 @@
         if (ptxvd->fHScrollVisible)
             ulOfs = ulScrollCX;
-        WinSetWindowPos(ptxvd->hwndVScroll,
+        WinSetWindowPos(ptxvd->scrw.hwndVScroll,
                         HWND_TOP,
                         ptxvd->rclViewReal.xRight - ulScrollCX,
@@ -2007,5 +2009,5 @@
         if (ptxvd->fVScrollVisible)
             ulOfs = ulScrollCX;
-        WinSetWindowPos(ptxvd->hwndHScroll,
+        WinSetWindowPos(ptxvd->scrw.hwndHScroll,
                         HWND_TOP,
                         0,
@@ -2043,16 +2045,16 @@
     ulWinCY = (ptxvd->rclViewText.yTop - ptxvd->rclViewText.yBottom);
 
-    if (ptxvd->ulViewYOfs < 0)
-        ptxvd->ulViewYOfs = 0;
-    if (ptxvd->ulViewYOfs > ((LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY))
-        ptxvd->ulViewYOfs = (LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY;
+    if (ptxvd->scrw.ptlScrollOfs.y < 0)
+        ptxvd->scrw.ptlScrollOfs.y = 0;
+    if (ptxvd->scrw.ptlScrollOfs.y > ((LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY))
+        ptxvd->scrw.ptlScrollOfs.y = (LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY;
 
     // vertical scroll bar enabled at all?
     if (ptxvd->flStyle & XS_VSCROLL)
     {
-        BOOL fEnabled = winhUpdateScrollBar(ptxvd->hwndVScroll,
+        BOOL fEnabled = winhUpdateScrollBar(ptxvd->scrw.hwndVScroll,
                                             ulWinCY,
                                             ptxvd->xfd.szlWorkspace.cy,
-                                            ptxvd->ulViewYOfs,
+                                            ptxvd->scrw.ptlScrollOfs.y,
                                             (ptxvd->flStyle & XS_AUTOVHIDE));
         // is auto-hide on?
@@ -2082,8 +2084,8 @@
     if (ptxvd->flStyle & XS_HSCROLL)
     {
-        BOOL fEnabled = winhUpdateScrollBar(ptxvd->hwndHScroll,
+        BOOL fEnabled = winhUpdateScrollBar(ptxvd->scrw.hwndHScroll,
                                             ulWinCX,
                                             ptxvd->xfd.szlWorkspace.cx,
-                                            ptxvd->ulViewXOfs,
+                                            ptxvd->scrw.ptlScrollOfs.x,
                                             (ptxvd->flStyle & XS_AUTOHHIDE));
         // is auto-hide on?
@@ -2165,6 +2167,6 @@
             xstrcatc(pstr, '\n');
 
-        ptxvd->ulViewXOfs = 0;
-        ptxvd->ulViewYOfs = 0;
+        ptxvd->scrw.ptlScrollOfs.x = 0;
+        ptxvd->scrw.ptlScrollOfs.y = 0;
         AdjustViewRects(hwndTextView,
                         ptxvd);
@@ -2187,11 +2189,11 @@
 {
     ULONG   ulLineIndex = 0;
-    ULONG   ulYOfs = ptxvd->ulViewYOfs;
+    LONG    lYOfs = ptxvd->scrw.ptlScrollOfs.y;
     txvPaintText(ptxvd->hab,
                  ptxvd->hps,        // paint PS: screen
                  &ptxvd->xfd,       // formatting data
                  prcl2Paint,        // update rectangle given to us
-                 ptxvd->ulViewXOfs,  // current X scrolling offset
-                 &ulYOfs,            // current Y scrolling offset
+                 ptxvd->scrw.ptlScrollOfs.x,  // current X scrolling offset
+                 &lYOfs,            // current Y scrolling offset
                  TRUE,              // draw even partly visible lines
                  &ulLineIndex);
@@ -2243,8 +2245,8 @@
 
     RECTL           rclLine;
-    rclLine.xLeft = pLineRcl->rcl.xLeft - ptxvd->ulViewXOfs;
-    rclLine.xRight = pLineRcl->rcl.xRight - ptxvd->ulViewXOfs;
-    rclLine.yBottom = pLineRcl->rcl.yBottom + ptxvd->ulViewYOfs;
-    rclLine.yTop = pLineRcl->rcl.yTop + ptxvd->ulViewYOfs;
+    rclLine.xLeft = pLineRcl->rcl.xLeft - ptxvd->scrw.ptlScrollOfs.x;
+    rclLine.xRight = pLineRcl->rcl.xRight - ptxvd->scrw.ptlScrollOfs.x;
+    rclLine.yBottom = pLineRcl->rcl.yBottom + ptxvd->scrw.ptlScrollOfs.y;
+    rclLine.yTop = pLineRcl->rcl.yTop + ptxvd->scrw.ptlScrollOfs.y;
 
     if (pWordThis->pcszLinkTarget)
@@ -2252,5 +2254,5 @@
 
     // x start: this word's X coordinate
-    ptlStart.x = pWordThis->lX - ptxvd->ulViewXOfs;
+    ptlStart.x = pWordThis->lX - ptxvd->scrw.ptlScrollOfs.x;
     // y start: bottom line of rectangle plus highest
     // base line offset found in all words (format step 2)
@@ -2280,5 +2282,5 @@
                        &rclLine,
                        pWordThis,
-                       ptxvd->ulViewXOfs);
+                       ptxvd->scrw.ptlScrollOfs.x);
 }
 
@@ -2379,14 +2381,15 @@
         ptxvd->rclViewReal.yTop = pcs->cy;
 
-        winhCreateScrollBars(hwndTextView,
-                             &ptxvd->hwndVScroll,
-                             &ptxvd->hwndHScroll);
+        winhCreateScroller(hwndTextView,
+                           &ptxvd->scrw,
+                           ID_VSCROLL,
+                           ID_HSCROLL);
 
         fShow = ((ptxvd->flStyle & XS_VSCROLL) != 0);
-        WinShowWindow(ptxvd->hwndVScroll, fShow);
+        WinShowWindow(ptxvd->scrw.hwndVScroll, fShow);
         ptxvd->fVScrollVisible = fShow;
 
         fShow = ((ptxvd->flStyle & XS_HSCROLL) != 0);
-        WinShowWindow(ptxvd->hwndHScroll, fShow);
+        WinShowWindow(ptxvd->scrw.hwndHScroll, fShow);
         ptxvd->fHScrollVisible = fShow;
 
@@ -2552,4 +2555,50 @@
                               FALSE,
                               TRUE);    // full reformat
+    }
+}
+
+/*
+ *@@ ProcessScroll:
+ *
+ *@@added V1.0.1 (2003-01-25) [umoeller]
+ */
+
+STATIC VOID ProcessScroll(HWND hwndTextView,
+                          ULONG msg,
+                          MPARAM mp2)
+{
+    PTEXTVIEWWINDATA ptxvd;
+    if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE))
+    {
+        POINTL  ptlScroll = {0, 0};
+        if (    (msg == WM_VSCROLL)
+             && (ptxvd->fVScrollVisible)
+           )
+        {
+            LONG cy = ptxvd->rclViewText.yTop - ptxvd->rclViewText.yBottom;
+            ptlScroll.y = winhHandleScrollMsg(ptxvd->scrw.hwndVScroll,
+                                              &ptxvd->scrw.ptlScrollOfs.y,
+                                              cy,
+                                              ptxvd->xfd.szlWorkspace.cy,
+                                              ptxvd->cdata.ulVScrollLineUnit,
+                                              msg,
+                                              mp2);
+        }
+        else if (ptxvd->fHScrollVisible)
+        {
+            LONG cx = ptxvd->rclViewText.xRight - ptxvd->rclViewText.xLeft;
+            ptlScroll.x = winhHandleScrollMsg(ptxvd->scrw.hwndHScroll,
+                                              &ptxvd->scrw.ptlScrollOfs.x,
+                                              cx,
+                                              ptxvd->xfd.szlWorkspace.cx,
+                                              ptxvd->cdata.ulHScrollLineUnit,
+                                              msg,
+                                              mp2);
+        }
+
+        if (ptlScroll.x || ptlScroll.y)
+            winhScrollWindow(hwndTextView,
+                             &ptxvd->rclViewText,
+                             &ptlScroll);
     }
 }
@@ -2635,6 +2684,6 @@
         PLISTNODE pWordNodeClicked;
 
-        ptlPos.x = SHORT1FROMMP(mp1) + ptxvd->ulViewXOfs;
-        ptlPos.y = SHORT2FROMMP(mp1) - ptxvd->ulViewYOfs;
+        ptlPos.x = SHORT1FROMMP(mp1) + ptxvd->scrw.ptlScrollOfs.x;
+        ptlPos.y = SHORT2FROMMP(mp1) - ptxvd->scrw.ptlScrollOfs.y;
 
         if (    (!(ptxvd->flStyle & XS_STATIC))
@@ -2707,6 +2756,6 @@
         HWND        hwndOwner = NULLHANDLE;
 
-        ptlPos.x = SHORT1FROMMP(mp1) + ptxvd->ulViewXOfs;
-        ptlPos.y = SHORT2FROMMP(mp1) - ptxvd->ulViewYOfs;
+        ptlPos.x = SHORT1FROMMP(mp1) + ptxvd->scrw.ptlScrollOfs.x;
+        ptlPos.y = SHORT2FROMMP(mp1) - ptxvd->scrw.ptlScrollOfs.y;
         WinSetCapture(HWND_DESKTOP, NULLHANDLE);
 
@@ -2886,18 +2935,18 @@
             // and then have lower y coordinates down to way in the negatives,
             // to get the y offset, we must...
-            ptxvd->ulViewYOfs = (-pRect->rcl.yTop) - ulWinCY;
-
-            if (ptxvd->ulViewYOfs < 0)
-                ptxvd->ulViewYOfs = 0;
-            if (ptxvd->ulViewYOfs > ((LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY))
-                ptxvd->ulViewYOfs = (LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY;
+            ptxvd->scrw.ptlScrollOfs.y = (-pRect->rcl.yTop) - ulWinCY;
+
+            if (ptxvd->scrw.ptlScrollOfs.y < 0)
+                ptxvd->scrw.ptlScrollOfs.y = 0;
+            if (ptxvd->scrw.ptlScrollOfs.y > ((LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY))
+                ptxvd->scrw.ptlScrollOfs.y = (LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY;
 
             // vertical scroll bar enabled at all?
             if (ptxvd->flStyle & XS_VSCROLL)
             {
-                /* BOOL fEnabled = */ winhUpdateScrollBar(ptxvd->hwndVScroll,
+                /* BOOL fEnabled = */ winhUpdateScrollBar(ptxvd->scrw.hwndVScroll,
                                                     ulWinCY,
                                                     ptxvd->xfd.szlWorkspace.cy,
-                                                    ptxvd->ulViewYOfs,
+                                                    ptxvd->scrw.ptlScrollOfs.y,
                                                     (ptxvd->flStyle & XS_AUTOVHIDE));
                 WinInvalidateRect(hwndTextView, NULL, FALSE);
@@ -2990,4 +3039,5 @@
  *@@changed V0.9.20 (2002-08-10) [umoeller]: added support for formatting HTML and plain text automatically
  *@@changed V1.0.0 (2002-08-12) [umoeller]: optimized locality by moving big chunks into subfuncs
+ *@@changed V1.0.1 (2003-01-25) [umoeller]: adjusted scroll msgs for new handler code
  */
 
@@ -3081,38 +3131,7 @@
 
         case WM_VSCROLL:
-            if (    (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE))
-                 && (ptxvd->fVScrollVisible)
-               )
-            {
-                winhHandleScrollMsg(hwndTextView,
-                                    ptxvd->hwndVScroll,
-                                    &ptxvd->ulViewYOfs,
-                                    &ptxvd->rclViewText,
-                                    ptxvd->xfd.szlWorkspace.cy,
-                                    ptxvd->cdata.ulVScrollLineUnit,
-                                    msg,
-                                    mp2);
-            }
-        break;
-
-        /*
-         * WM_HSCROLL:
-         *
-         */
-
         case WM_HSCROLL:
-            if (    (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE))
-                 && (ptxvd->fHScrollVisible)
-               )
-            {
-                winhHandleScrollMsg(hwndTextView,
-                                    ptxvd->hwndHScroll,
-                                    &ptxvd->ulViewXOfs,
-                                    &ptxvd->rclViewText,
-                                    ptxvd->xfd.szlWorkspace.cx,
-                                    ptxvd->cdata.ulHScrollLineUnit,
-                                    msg,
-                                    mp2);
-            }
+            ProcessScroll(hwndTextView, msg, mp2);
+                    // V1.0.1 (2003-01-22) [umoeller]
         break;
 
@@ -3777,5 +3796,5 @@
     ULONG       ulCurrentLineIndex = 0,
                 ulCurrentPage = 1;
-    ULONG       ulCurrentYOfs = 0;
+    LONG        lCurrentYOfs = 0;
 
     /* MATRIXLF    matlf;
@@ -3847,5 +3866,5 @@
                                     &rclPageWorld,
                                     0,
-                                    &ulCurrentYOfs,
+                                    &lCurrentYOfs,
                                     FALSE,      // draw only fully visible lines
                                     &ulCurrentLineIndex); // in/out: line to start with
Index: trunk/src/helpers/winh.c
===================================================================
--- trunk/src/helpers/winh.c	(revision 242)
+++ trunk/src/helpers/winh.c	(revision 243)
@@ -1896,50 +1896,4 @@
 
 /*
- *@@ winhCreateScrollBars:
- *      creates two scroll bars with an arbitrary
- *      position for later use with winhUpdateScrollBar.
- *
- *@@added V1.0.1 (2003-01-17) [umoeller]
- */
-
-BOOL winhCreateScrollBars(HWND hwndParent,
-                          HWND *phwndV,     // out: vertical scroll bar
-                          HWND *phwndH)     // out: horizontal scroll bar
-{
-    SBCDATA     sbcd;
-    sbcd.cb = sizeof(SBCDATA);
-    sbcd.sHilite = 0;
-    sbcd.posFirst = 0;
-    sbcd.posLast = 100;
-    sbcd.posThumb = 30;
-    sbcd.cVisible = 50;
-    sbcd.cTotal = 50;
-
-    return (    (*phwndV = WinCreateWindow(hwndParent,
-                                           WC_SCROLLBAR,
-                                           "",
-                                           SBS_VERT | SBS_THUMBSIZE | WS_VISIBLE,
-                                           10, 10,
-                                           20, 100,
-                                           hwndParent,     // owner
-                                           HWND_TOP,
-                                           ID_VSCROLL,
-                                           &sbcd,
-                                           0))
-             && (*phwndH = WinCreateWindow(hwndParent,
-                                           WC_SCROLLBAR,
-                                           "",
-                                           SBS_THUMBSIZE | WS_VISIBLE,
-                                           10, 10,
-                                           20, 100,
-                                           hwndParent,     // owner
-                                           HWND_TOP,
-                                           ID_HSCROLL,
-                                           &sbcd,
-                                           0))
-           );
-}
-
-/*
  *@@ winhUpdateScrollBar:
  *      updates the given scroll bar according to the given
@@ -1958,32 +1912,36 @@
  *      Terminology:
  *
- *      -- "window": the actual window with scroll bars which displays
- *         a subrectangle of the available data. With a typical PM
- *         application, this will be your client window.
- *
- *         The width or height of this must be passed in ulWinPels.
- *
- *      -- "workarea": the entire data to be displayed, of which the
- *         "window" can only display a subrectangle, if the workarea
- *         is larger than the window.
- *
- *         The width or height of this must be passed in ulWorkareaPels.
- *         This can be smaller than ulWinPels (if the window is larger
- *         than the data) or the same or larger than ulWinPels
- *         (if the window is too small to show all the data).
- *
- *      -- "window offset": the offset of the current window within
- *         the workarea.
- *
- *         For horizontal scroll bars, this is the X coordinate,
- *         counting from the left of the window (0 means leftmost).
- *
- *         For vertical scroll bars, this is counted from the _top_
- *         of the workarea (0 means topmost, as opposed to OS/2
- *         window coordinates!). This is because for vertical scroll
- *         bars controls, higher values move the thumb _down_. Yes
- *         indeed, this conflicts with PM's coordinate system.
- *
- *         The window offset is therefore always positive.
+ *      --  "window": the actual window with scroll bars which displays
+ *          a subrectangle of the available data. With a typical PM
+ *          application, this will be your client window.
+ *
+ *          The width or height of this must be passed in ulWinPels.
+ *
+ *      --  "workarea": the entire data to be displayed, of which the
+ *          "window" can only display a subrectangle, if the workarea
+ *          is larger than the window.
+ *
+ *          The width or height of this must be passed in ulWorkareaPels.
+ *          This can be smaller than ulWinPels (if the window is larger
+ *          than the data) or the same or larger than ulWinPels
+ *          (if the window is too small to show all the data).
+ *
+ *          This value is exclusive in the sense that the maximum
+ *          window offset (below) can be the workarea minus one.
+ *
+ *      --  "window offset": the zero-based offset of the current
+ *          window within the workarea, whose maximum value is
+ *          the workarea minus one.
+ *
+ *          For horizontal scroll bars, this is the X coordinate,
+ *          counting from the left of the window (0 means leftmost).
+ *
+ *          For vertical scroll bars, this is counted from the _top_
+ *          of the workarea (0 means topmost, as opposed to OS/2
+ *          window coordinates!). This is because for vertical scroll
+ *          bars controls, higher values move the thumb _down_. Yes
+ *          indeed, this conflicts with PM's coordinate system.
+ *
+ *          The window offset is therefore always positive.
  *
  *      The scroll bar gets disabled if the entire workarea is visible,
@@ -2009,4 +1967,6 @@
  *@@changed V0.9.3 (2000-04-30) [umoeller]: fixed pels/unit confusion
  *@@changed V0.9.3 (2000-05-08) [umoeller]: now handling scroll units automatically
+ *@@changed V1.0.1 (2003-01-25) [umoeller]: fixed max value which caused right/bottommost scroll button to never be disabled
+ *@@changed V1.0.1 (2003-01-25) [umoeller]: fixed bad thumb position for large offsets
  */
 
@@ -2026,23 +1986,14 @@
     BOOL brc = FALSE;
 
-    // _Pmpf(("Entering winhUpdateScrollBar"));
-
-    // for large workareas, adjust scroll bar units
-    USHORT  usScrollUnitPels = 1;
-    if (ulWorkareaPels > 10000)
-        usScrollUnitPels = 100;
-
-    if (ulWorkareaPels > ulWinPels)
-    {
+    if (ulWorkareaPels >= ulWinPels)
+    {
+        // for large workareas, adjust scroll bar units
+        USHORT  usDivisor = 1;
+        USHORT  lMaxAllowedUnitOfs;
+
+        if (ulWorkareaPels > 10000)
+            usDivisor = 100;
+
         // scrollbar needed:
-        USHORT  usThumbDivisorUnits = usScrollUnitPels;
-        USHORT  lMaxAllowedUnitOfs;
-        // _Pmpf(("winhUpdateScrollBar: ulWorkareaPels > ulWinPels, enabling scroller"));
-        // divisor for thumb size (below)
-        if (ulWorkareaPels > 10000)
-            // for very large workareas, we need to
-            // raise the divisor, because we only
-            // have a USHORT
-            usThumbDivisorUnits = usScrollUnitPels * 100;
 
         // workarea is larger than window:
@@ -2052,15 +2003,11 @@
 
         // calculate limit
-        lMaxAllowedUnitOfs = ((ulWorkareaPels - ulWinPels + usScrollUnitPels)
-                               // scroll unit is 10
-                               / usScrollUnitPels);
-
-        // _Pmpf(("    usCurUnitOfs: %d", ulCurUnitOfs));
-        // _Pmpf(("    usMaxUnits: %d", lMaxAllowedUnitOfs));
+        lMaxAllowedUnitOfs =   (ulWorkareaPels - ulWinPels)
+                             / usDivisor;
 
         // set thumb position and limit
         WinSendMsg(hwndScrollBar,
                    SBM_SETSCROLLBAR,
-                   (MPARAM)(ulCurPelsOfs), //  / usThumbDivisorUnits),   // position: 0 means top
+                   (MPARAM)(ulCurPelsOfs / usDivisor),   // position: 0 means top
                    MPFROM2SHORT(0,  // minimum
                                 lMaxAllowedUnitOfs));    // maximum
@@ -2070,6 +2017,6 @@
         WinSendMsg(hwndScrollBar,
                    SBM_SETTHUMBSIZE,
-                   MPFROM2SHORT(    ulWinPels / usThumbDivisorUnits,       // visible
-                                    ulWorkareaPels / usThumbDivisorUnits), // total
+                   MPFROM2SHORT(    ulWinPels / usDivisor,       // visible
+                                    ulWorkareaPels / usDivisor), // total
                    0);
         brc = TRUE;
@@ -2077,5 +2024,4 @@
     else
     {
-        // _Pmpf(("winhUpdateScrollBar: ulWorkareaPels <= ulWinPels"));
         // entire workarea is visible:
         WinEnableWindow(hwndScrollBar, FALSE);
@@ -2091,35 +2037,37 @@
 /*
  *@@ winhHandleScrollMsg:
- *      this helper handles a WM_VSCROLL or WM_HSCROLL
- *      message posted to a client window when the user
- *      has worked on a client scroll bar. Calling this
- *      function is ALL you need to do to handle those
- *      two messages.
+ *      this helper handles a WM_VSCROLL or WM_HSCROLL message
+ *      posted to a client window when the user has worked on a
+ *      client scroll bar. Calling this function is all you need to
+ *      do to handle those two messages.
  *
  *      This is most useful in conjunction with winhUpdateScrollBar.
  *      See that function for the terminology also.
  *
- *      This function calculates the new scrollbar position
- *      (from the mp2 value, which can be line up/down,
- *      page up/down, or slider track) and calls WinScrollWindow
- *      accordingly. The window part which became invalid
- *      because of the scrolling is automatically invalidated
- *      (using WinInvalidateRect), so expect a WM_PAINT after
- *      calling this function.
+ *      This function calculates the new scrollbar position (from
+ *      the mp2 value, which can be line up/down, page up/down, or
+ *      slider track) and calculates the scrolling offset
+ *      accordingly, which is returned as a LONG.
  *
  *      This function assumes that the scrollbar operates
  *      on values starting from zero. The maximum value
- *      of the scroll bar is:
- *
- +          ulWorkareaPels - (prcl2Scroll->yTop - prcl2Scroll->yBottom)
- *
- *      This function also automatically changes the scroll bar
+ *      of the scroll bar is therefore
+ *
+ +          lWorkareaPels - (prcl2Scroll->yTop - prcl2Scroll->yBottom)
+ *
+ *      This function also automatically shrinks the scroll bar
  *      units, should you have a workarea size which doesn't fit
  *      into the SHORT's that the scroll bar uses internally. As
  *      a result, this function handles a the complete range of
- *      a ULONG for the workarea.
+ *      a LONG for the workarea. (The PM scroll bar implementation
+ *      is really brain-dead in this respect... the workarea can
+ *      easily be larger than 32768 pixels. That's what scroll bars
+ *      are for in the first place, dammit.)
  *
  *      Replace "bottom" and "top" with "right" and "left" for
  *      horizontal scrollbars in the above formula.
+ *
+ *      Returns the amount of pixels to be passed to winhScrollWindow
+ *      afterwards.
  *
  *@@added V0.9.1 (2000-02-13) [umoeller]
@@ -2127,16 +2075,12 @@
  *@@changed V0.9.3 (2000-05-08) [umoeller]: now handling scroll units automatically
  *@@changed V0.9.7 (2001-01-17) [umoeller]: changed PLONG to PULONG
- */
-
-BOOL winhHandleScrollMsg(HWND hwnd2Scroll,          // in: client window to scroll
-                         HWND hwndScrollBar,        // in: vertical or horizontal scroll bar window
-                         PULONG pulCurPelsOfs,      // in/out: current workarea offset;
-                                                    // this is updated with the proper scroll units
-                         PRECTL prcl2Scroll,        // in: hwnd2Scroll rectangle to scroll
-                                                    // (in window coordinates);
-                                                    // this is passed to WinScrollWindow,
-                                                    // which considers this inclusive!
-                         LONG ulWorkareaPels,       // in: total workarea dimension,
-                                                    // into which *pulCurPelsOfs is an offset
+ *@@changed V1.0.1 (2003-01-25) [umoeller]: changed prototype, no longer calling WinScrollWindow, fixed offset bugs
+ */
+
+LONG winhHandleScrollMsg(HWND hwndScrollBar,        // in: vertical or horizontal scroll bar window
+                         PLONG plCurPelsOfs,        // in/out: current workarea offset (in window coordinates)
+                         LONG lWindowPels,          // in: window cx or cy (in window coordinates)
+                         LONG lWorkareaPels,        // in: total workarea dimension,
+                                                    // into which *plCurPelsOfs is an offset
                          USHORT usLineStepPels,     // in: pixels to scroll line-wise
                                                     // (scroll bar buttons pressed)
@@ -2146,207 +2090,61 @@
                                                     // see PMREF for details
 {
-    ULONG   ulOldPelsOfs = *pulCurPelsOfs;
+    LONG    lOldPelsOfs = *plCurPelsOfs;
     USHORT  usPosUnits = SHORT1FROMMP(mp2), // in scroll units
             usCmd = SHORT2FROMMP(mp2);
-    LONG    lMaxAllowedUnitOfs;
-    ULONG   ulWinPels;
+    LONG    lLimitPels;
 
     // for large workareas, adjust scroll bar units
-    USHORT  usScrollUnitPels = 1;
-    if (ulWorkareaPels > 10000)
-        usScrollUnitPels = 100;
-
-    // calculate window size (vertical or horizontal)
-    if (msg == WM_VSCROLL)
-        ulWinPels = (prcl2Scroll->yTop - prcl2Scroll->yBottom);
-    else
-        ulWinPels = (prcl2Scroll->xRight - prcl2Scroll->xLeft);
-
-    lMaxAllowedUnitOfs = ((LONG)ulWorkareaPels - ulWinPels) / usScrollUnitPels;
-
-    // _Pmpf(("Entering winhHandleScrollMsg"));
+    LONG    lScrollUnitPels = 1;
+    if (lWorkareaPels > 10000)
+        lScrollUnitPels = 100;
 
     switch (usCmd)
     {
         case SB_LINEUP:
-            if (*pulCurPelsOfs > usLineStepPels)
-                *pulCurPelsOfs -= usLineStepPels;  //  * usScrollUnitPels);
-            else
-                *pulCurPelsOfs = 0;
+            *plCurPelsOfs -= usLineStepPels;
         break;
 
         case SB_LINEDOWN:
-            *pulCurPelsOfs += usLineStepPels;  //  * usScrollUnitPels);
+            *plCurPelsOfs += usLineStepPels;
         break;
 
         case SB_PAGEUP:
-            if (*pulCurPelsOfs > ulWinPels)
-                *pulCurPelsOfs -= ulWinPels; // convert to units
-            else
-                *pulCurPelsOfs = 0;
+            *plCurPelsOfs -= lWindowPels;
         break;
 
         case SB_PAGEDOWN:
-            *pulCurPelsOfs += ulWinPels; // convert to units
+            *plCurPelsOfs += lWindowPels;
         break;
 
         case SB_SLIDERTRACK:
-            *pulCurPelsOfs = (usPosUnits * usScrollUnitPels);
-            // _Pmpf(("    SB_SLIDERTRACK: usUnits = %d", usPosUnits));
+            *plCurPelsOfs = (LONG)usPosUnits * lScrollUnitPels;
         break;
 
         case SB_SLIDERPOSITION:
-            *pulCurPelsOfs = (usPosUnits * usScrollUnitPels);
+            *plCurPelsOfs = (LONG)usPosUnits * lScrollUnitPels;
         break;
     }
 
-    // are we close to the lower limit?
-    /* if (*plCurUnitOfs < usLineStepUnits) // usScrollUnit)
-        *plCurUnitOfs = 0;
-    // are we close to the upper limit?
-    else if (*plCurUnitOfs + usLineStepUnits > lMaxUnitOfs)
-    {
-        _Pmpf(("        !!! limiting: %d to %d", *plCurUnitOfs, lMaxUnitOfs));
-        *plCurUnitOfs = lMaxUnitOfs;
-    } */
-
-    /* if (*plCurPelsOfs < 0)
-        *plCurPelsOfs = 0; */       // checked above
-    if (*pulCurPelsOfs > (lMaxAllowedUnitOfs * usScrollUnitPels))
-    {
-        *pulCurPelsOfs = (lMaxAllowedUnitOfs * usScrollUnitPels);
-    }
-    if (    (*pulCurPelsOfs != ulOldPelsOfs)
-         || (*pulCurPelsOfs == 0)
-         || (*pulCurPelsOfs == (lMaxAllowedUnitOfs * usScrollUnitPels))
+    // calc max scroll offset:
+    // if we have a viewport of 200 and the window size is 199,
+    // we can have scroll values of 0 or 1
+    lLimitPels = lWorkareaPels - lWindowPels;
+
+    // now delimit
+    if (*plCurPelsOfs < 0)
+        *plCurPelsOfs = 0;
+    else if (*plCurPelsOfs > lLimitPels)
+        *plCurPelsOfs = lLimitPels;
+
+    if (    (*plCurPelsOfs != lOldPelsOfs)
+         || (*plCurPelsOfs == 0)
+         || (*plCurPelsOfs == lLimitPels)
        )
     {
-        RECTL   rcl2Scroll,
-                rcl2Update;
-
         // changed:
         WinSendMsg(hwndScrollBar,
                    SBM_SETPOS,
-                   (MPARAM)(*pulCurPelsOfs / usScrollUnitPels), //  / usScrollUnit),
-                   0);
-        // scroll window rectangle:
-        rcl2Scroll.xLeft =  prcl2Scroll->xLeft;
-        rcl2Scroll.xRight =  prcl2Scroll->xRight;
-        rcl2Scroll.yBottom =  prcl2Scroll->yBottom;
-        rcl2Scroll.yTop =  prcl2Scroll->yTop;
-
-        if (msg == WM_VSCROLL)
-            WinScrollWindow(hwnd2Scroll,
-                            0,
-                            (*pulCurPelsOfs - ulOldPelsOfs)  // scroll units changed
-                            ,    // * usScrollUnitPels,     // convert to pels
-                            &rcl2Scroll,  // rcl to scroll
-                            prcl2Scroll, // clipping rect
-                            NULLHANDLE, // no region
-                            &rcl2Update,
-                            0);
-        else
-            WinScrollWindow(hwnd2Scroll,
-                            -(LONG)(*pulCurPelsOfs - ulOldPelsOfs) // scroll units changed
-                            ,    // * usScrollUnitPels,
-                            0,
-                            &rcl2Scroll,  // rcl to scroll
-                            prcl2Scroll, // clipping rect
-                            NULLHANDLE, // no region
-                            &rcl2Update,
-                            0);
-
-        // WinScrollWindow has stored the invalid window
-        // rectangle which needs to be repainted in rcl2Update:
-        WinInvalidateRect(hwnd2Scroll, &rcl2Update, FALSE);
-    }
-
-    // _Pmpf(("End of winhHandleScrollMsg"));
-
-    return TRUE;
-}
-
-/*
- *@@ winhHandleScrollMsg2:
- *
- *      Returns the amount of pixels to be passed to
- *      WinScrollWindow.
- *
- *@@added V1.0.1 (2003-01-17) [umoeller]
- */
-
-LONG winhHandleScrollMsg2(HWND hwndScrollBar,        // in: vertical or horizontal scroll bar window
-                          PLONG plCurPelsOfs,        // in/out: current workarea offset;
-                                                     // this is updated with the proper scroll units
-                          LONG lWindowPels,          // in: window cx or cy (in window coordinates);
-                          LONG lWorkareaPels,        // in: total workarea dimension,
-                                                     // into which *plCurPelsOfs is an offset
-                          USHORT usLineStepPels,     // in: pixels to scroll line-wise
-                                                     // (scroll bar buttons pressed)
-                          ULONG msg,                 // in: either WM_VSCROLL or WM_HSCROLL
-                          MPARAM mp2)                // in: complete mp2 of WM_VSCROLL/WM_HSCROLL;
-                                                     // this has two SHORT's (usPos and usCmd),
-                                                     // see PMREF for details
-{
-    LONG    lOldPelsOfs = *plCurPelsOfs;
-    USHORT  usPosUnits = SHORT1FROMMP(mp2), // in scroll units
-            usCmd = SHORT2FROMMP(mp2);
-    LONG    lMaxAllowedUnitOfs;
-
-    // for large workareas, adjust scroll bar units
-    USHORT  usScrollUnitPels = 1;
-    if (lWorkareaPels > 10000)
-        usScrollUnitPels = 100;
-
-    lMaxAllowedUnitOfs = (lWorkareaPels - lWindowPels) / usScrollUnitPels;
-
-    // _Pmpf(("Entering winhHandleScrollMsg"));
-
-    switch (usCmd)
-    {
-        case SB_LINEUP:
-            if (*plCurPelsOfs > usLineStepPels)
-                *plCurPelsOfs -= usLineStepPels;  //  * usScrollUnitPels);
-            else
-                *plCurPelsOfs = 0;
-        break;
-
-        case SB_LINEDOWN:
-            *plCurPelsOfs += usLineStepPels;  //  * usScrollUnitPels);
-        break;
-
-        case SB_PAGEUP:
-            if (*plCurPelsOfs > lWindowPels)
-                *plCurPelsOfs -= lWindowPels; // convert to units
-            else
-                *plCurPelsOfs = 0;
-        break;
-
-        case SB_PAGEDOWN:
-            *plCurPelsOfs += lWindowPels; // convert to units
-        break;
-
-        case SB_SLIDERTRACK:
-            *plCurPelsOfs = (usPosUnits * usScrollUnitPels);
-            // _Pmpf(("    SB_SLIDERTRACK: usUnits = %d", usPosUnits));
-        break;
-
-        case SB_SLIDERPOSITION:
-            *plCurPelsOfs = (usPosUnits * usScrollUnitPels);
-        break;
-    }
-
-    if (*plCurPelsOfs > (lMaxAllowedUnitOfs * usScrollUnitPels))
-        *plCurPelsOfs = (lMaxAllowedUnitOfs * usScrollUnitPels);
-
-    if (    (*plCurPelsOfs != lOldPelsOfs)
-         || (*plCurPelsOfs == 0)
-         || (*plCurPelsOfs == (lMaxAllowedUnitOfs * usScrollUnitPels))
-       )
-    {
-        // changed:
-        WinSendMsg(hwndScrollBar,
-                   SBM_SETPOS,
-                   (MPARAM)(*plCurPelsOfs / usScrollUnitPels), //  / usScrollUnit),
+                   (MPARAM)(*plCurPelsOfs / lScrollUnitPels),
                    0);
 
@@ -2381,5 +2179,5 @@
 
 /*
- *@@ winhProcessScrollChars:
+ *@@ winhProcessScrollChars2:
  *      helper for processing WM_CHAR messages for
  *      client windows with scroll bars.
@@ -2400,17 +2198,16 @@
  *      The following keystrokes are processed here:
  *
- *      -- "cursor up, down, right, left": scroll one
- *         line in the proper direction.
- *      -- "page up, down": scroll one page up or down.
- *      -- "Home": scroll leftmost.
- *      -- "Ctrl+ Home": scroll topmost.
- *      -- "End": scroll rightmost.
- *      -- "Ctrl+ End": scroll bottommost.
- *      -- "Ctrl + page up, down": scroll one screen left or right.
- *
- *      This is CUA behavior.
- *
- *      Returns TRUE if the message has been
- *      processed.
+ *      --  "cursor up, down, right, left": scroll one
+ *          line in the proper direction.
+ *      --  "page up, down": scroll one page up or down.
+ *      --  "Home": scroll leftmost.
+ *      --  "Ctrl+ Home": scroll topmost.
+ *      --  "End": scroll rightmost.
+ *      --  "Ctrl+ End": scroll bottommost.
+ *      --  "Ctrl + page up, down": scroll one screen left or right.
+ *
+ *      This is CUA behavior, if anyone still cares about that.
+ *
+ *      Returns TRUE if the message has been processed.
  *
  *@@added V0.9.3 (2000-04-29) [umoeller]
@@ -2418,18 +2215,12 @@
  */
 
-BOOL winhProcessScrollChars(HWND hwndClient,    // in: client window
-                            HWND hwndVScroll,   // in: vertical scroll bar
-                            HWND hwndHScroll,   // in: horizontal scroll bar
-                            MPARAM mp1,         // in: WM_CHAR mp1
-                            MPARAM mp2,         // in: WM_CHAR mp2
-                            ULONG ulVertMax,    // in: maximum workarea cy
-                            ULONG ulHorzMax)    // in: maximum workarea cx
+BOOL winhProcessScrollChars2(HWND hwndClient,    // in: client window
+                             const SCROLLABLEWINDOW *pscrw,   // in: scroller data
+                             MPARAM mp1,         // in: WM_CHAR mp1
+                             MPARAM mp2)         // in: WM_CHAR mp2
 {
     BOOL    fProcessed = FALSE;
     USHORT usFlags    = SHORT1FROMMP(mp1);
-    // USHORT usch       = SHORT1FROMMP(mp2);
     USHORT usvk       = SHORT2FROMMP(mp2);
-
-    // _Pmpf(("Entering winhProcessScrollChars"));
 
     if (usFlags & KC_VIRTUALKEY)
@@ -2494,10 +2285,10 @@
                     // vertical:
                     ulMsg = WM_VSCROLL;
-                    sPos = ulVertMax;
+                    sPos = pscrw->szlWorkarea.cy;
                 }
                 else
                 {
                     ulMsg = WM_HSCROLL;
-                    sPos = ulHorzMax;
+                    sPos = pscrw->szlWorkarea.cx;
                 }
 
@@ -2515,6 +2306,6 @@
         {
             HWND   hwndScrollBar = ((ulMsg == WM_VSCROLL)
-                                        ? hwndVScroll
-                                        : hwndHScroll);
+                                        ? pscrw->hwndVScroll
+                                        : pscrw->hwndHScroll);
             if (WinIsWindowEnabled(hwndScrollBar))
             {
@@ -2530,7 +2321,179 @@
     }
 
-    // _Pmpf(("End of  winhProcessScrollChars"));
-
     return fProcessed;
+}
+
+/*
+ *@@ winhProcessScrollChars:
+ *      wrapper around winhProcessScrollChars2 for prototype
+ *      compatibility.
+ *
+ *@@added V1.0.1 (2003-01-25) [umoeller]
+ */
+
+BOOL winhProcessScrollChars(HWND hwndClient,    // in: client window
+                            HWND hwndVScroll,   // in: vertical scroll bar
+                            HWND hwndHScroll,   // in: horizontal scroll bar
+                            MPARAM mp1,         // in: WM_CHAR mp1
+                            MPARAM mp2,         // in: WM_CHAR mp2
+                            ULONG ulVertMax,    // in: maximum workarea cy
+                            ULONG ulHorzMax)    // in: maximum workarea cx
+{
+    SCROLLABLEWINDOW scrw;
+
+    scrw.hwndVScroll = hwndVScroll;
+    scrw.hwndHScroll = hwndHScroll;
+    scrw.szlWorkarea.cx = ulHorzMax;
+    scrw.szlWorkarea.cy = ulVertMax;
+
+    return winhProcessScrollChars2(hwndClient,
+                                   &scrw,
+                                   mp1,
+                                   mp2);
+}
+
+/*
+ *@@ winhCreateScrollBars:
+ *      creates two scroll bars with an arbitrary
+ *      position for later use with winhUpdateScrollBar.
+ *
+ *@@added V1.0.1 (2003-01-25) [umoeller]
+ */
+
+BOOL XWPENTRY winhCreateScroller(HWND hwndParent,           // in: parent and owner of scroll bars
+                                 PSCROLLABLEWINDOW pscrw,   // out: scroller data
+                                 ULONG idVScroll,           // in: window ID of vertical scroll bar
+                                 ULONG idHScroll)           // in: window ID of horizontal scroll bar
+{
+    SBCDATA     sbcd;
+    sbcd.cb = sizeof(SBCDATA);
+    sbcd.sHilite = 0;
+    sbcd.posFirst = 0;
+    sbcd.posLast = 100;
+    sbcd.posThumb = 30;
+    sbcd.cVisible = 50;
+    sbcd.cTotal = 50;
+
+    pscrw->cxScrollBar = WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL);
+    pscrw->cyScrollBar = WinQuerySysValue(HWND_DESKTOP, SV_CYHSCROLL);
+
+    pscrw->idVScroll = idVScroll;
+    pscrw->idHScroll = idHScroll;
+
+    pscrw->szlWorkarea.cx
+    = pscrw->szlWorkarea.cy
+    = pscrw->ptlScrollOfs.x
+    = pscrw->ptlScrollOfs.y
+    = 0;
+
+    return (    (pscrw->hwndVScroll = WinCreateWindow(hwndParent,
+                                                      WC_SCROLLBAR,
+                                                      "",
+                                                      SBS_VERT | SBS_THUMBSIZE | WS_VISIBLE,
+                                                      10, 10,
+                                                      20, 100,
+                                                      hwndParent,     // owner
+                                                      HWND_TOP,
+                                                      idVScroll,
+                                                      &sbcd,
+                                                      0))
+             && (pscrw->hwndHScroll = WinCreateWindow(hwndParent,
+                                                      WC_SCROLLBAR,
+                                                      "",
+                                                      SBS_THUMBSIZE | WS_VISIBLE,
+                                                      10, 10,
+                                                      20, 100,
+                                                      hwndParent,     // owner
+                                                      HWND_TOP,
+                                                      idHScroll,
+                                                      &sbcd,
+                                                      0))
+           );
+}
+
+/*
+ *@@ winhHandleScrollerMsgs:
+ *      unified function that you can simply call for all of
+ *      the WM_HSCROLL, WM_VSCROLL, and WM_CHAR messages to
+ *      get full automatic scrolling support for window
+ *      messages.
+ *
+ *      This calls winhHandleScrollMsg, winhScrollWindow,
+ *      and winhProcessScrollChars in turn as appropriate.
+ *
+ *      However, you still need to call winhUpdateScrollBar
+ *      for each scroll bar whenever either the workarea or
+ *      window size changes.
+ *
+ *      See winhUpdateScrollBar for the terminology.
+ *
+ *      The size of the workarea must be passed in with
+ *      pscrw, as well as the window and scrollbar handles.
+ *      By contrast, the current window size must be passed
+ *      in via the pszlWin parameter. This allows for cooperation
+ *      with the ctlDefWindowProc funcs, which have their own
+ *      fields for this in DEFWINDOWDATA so we won't duplicate.
+ *
+ *      Preconditions:
+ *
+ *      --  hwnd2Scroll (the "client") and the scroll bar windows
+ *          must be siblings presently. In other words, scrolling
+ *          won't work correctly if the scroll bars are children
+ *          of the client because we do not presently handle
+ *          passing a clipping rectangle to WinScrollWindow here.
+ *
+ *@@added V1.0.1 (2003-01-25) [umoeller]
+ */
+
+MRESULT winhHandleScrollerMsgs(HWND hwnd2Scroll,        // in: "client" window
+                               PSCROLLABLEWINDOW pscrw, // in: scroller data
+                               PSIZEL pszlWin,
+                               ULONG msg,
+                               MPARAM mp1,
+                               MPARAM mp2)
+{
+    MRESULT mrc = 0;
+    POINTL  ptlScroll = {0, 0};
+
+    switch (msg)
+    {
+        case WM_VSCROLL:
+            if (ptlScroll.y = winhHandleScrollMsg(pscrw->hwndVScroll,
+                                                  &pscrw->ptlScrollOfs.y,
+                                                  pszlWin->cy,
+                                                  pscrw->szlWorkarea.cy,
+                                                  10,
+                                                  msg,
+                                                  mp2))
+                winhScrollWindow(hwnd2Scroll,
+                                 NULL,
+                                 &ptlScroll);
+        break;
+
+        case WM_HSCROLL:
+            if (ptlScroll.x = winhHandleScrollMsg(pscrw->hwndHScroll,
+                                                  &pscrw->ptlScrollOfs.x,
+                                                  pszlWin->cx,
+                                                  pscrw->szlWorkarea.cx,
+                                                  10,
+                                                  msg,
+                                                  mp2))
+                winhScrollWindow(hwnd2Scroll,
+                                 NULL,
+                                 &ptlScroll);
+        break;
+
+        case WM_CHAR:
+            mrc = (MRESULT)winhProcessScrollChars(hwnd2Scroll,
+                                                  pscrw->hwndVScroll,
+                                                  pscrw->hwndHScroll,
+                                                  mp1,
+                                                  mp2,
+                                                  pscrw->szlWorkarea.cy,
+                                                  pscrw->szlWorkarea.cx);
+        break;
+    }
+
+    return mrc;
 }
 
@@ -3763,4 +3726,29 @@
 
     return hswitch;
+}
+
+/*
+ *@@ winhUpdateTasklist:
+ *      refreshes the task list entry for the given
+ *      window with a new title text.
+ *
+ *@@added V1.0.1 (2003-01-25) [umoeller]
+ */
+
+BOOL winhUpdateTasklist(HWND hwnd,
+                        PCSZ pcszNewTitle)
+{
+    HSWITCH hsw;
+    if (hsw = WinQuerySwitchHandle(hwnd, 0))
+    {
+        SWCNTRL swc;
+        WinQuerySwitchEntry(hsw, &swc);
+        strhncpy0(swc.szSwtitle,
+                  pcszNewTitle,
+                  sizeof(swc.szSwtitle));
+        return !WinChangeSwitchEntry(hsw, &swc);
+    }
+
+    return FALSE;
 }
 
