Index: trunk/src/helpers/_call_filedlg.c
===================================================================
--- trunk/src/helpers/_call_filedlg.c	(revision 241)
+++ trunk/src/helpers/_call_filedlg.c	(revision 242)
@@ -13,4 +13,5 @@
 #define INCL_WINBUTTONS
 #define INCL_WINPOINTERS
+#define INCL_WINSTDCNR
 #define INCL_WINSTDFILE
 
@@ -26,4 +27,5 @@
 
 #include "helpers\call_file_dlg.c"
+#include "helpers\cnrh.h"
 #include "helpers\comctl.h"
 #include "helpers\standards.h"
@@ -90,33 +92,17 @@
 
 /*
- *@@ fnwpClient:
+ *@@ GROUPRECORD:
  *
  */
 
-MRESULT EXPENTRY fnwpClient(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+typedef struct _GROUPRECORD
 {
-    MRESULT mrc = 0;
-
-    switch (msg)
-    {
-        case WM_PAINT:
-        {
-            HPS hps;
-            RECTL rcl;
-            if (hps = WinBeginPaint(hwnd, NULLHANDLE, &rcl))
-            {
-                gpihSwitchToRGB(hps);
-                WinFillRect(hps, &rcl, RGBCOL_DARKGRAY);
-                WinEndPaint(hps);
-            }
-        }
-        break;
-
-        default:
-            mrc = WinDefWindowProc(hwnd, msg, mp1, mp2);
-    }
-
-    return mrc;
-}
+    RECORDCORE      recc;
+
+    ULONG           gid;
+    CHAR            szGroupName[100];    // group name
+    PSZ             pszMembers;
+
+} GROUPRECORD, *PGROUPRECORD;
 
 /*
@@ -217,9 +203,13 @@
                   | FCF_SHELLPOSITION
                   | FCF_TASKLIST,
-            XFCF_TOOLBAR | XFCF_FORCETBOWNER | XFCF_STATUSBAR,
+            0, // XFCF_TOOLBAR | XFCF_FORCETBOWNER | XFCF_STATUSBAR,
             WS_VISIBLE,                         // ulFrameStyle
             "Test File Dialog",                 // pcszFrameTitle
             0,                                  // ulResourcesID
-            WC_CLIENT,                          // pcszClassClient
+#if 1
+            WC_CCTL_CNR,
+#else
+            WC_CONTAINER,
+#endif
             WS_VISIBLE,                         // flStyleClient
             0,                                  // ulID
@@ -248,10 +238,5 @@
     ctlRegisterToolbar(hab);
     ctlRegisterSeparatorLine(hab);
-
-    WinRegisterClass(hab,
-                     (PSZ)WC_CLIENT,
-                     fnwpClient,
-                     0,
-                     4);
+    ctlRegisterXCnr(hab);
 
     sprintf(szOpen,
@@ -275,26 +260,69 @@
     WinSetWindowText(hwndStatusBar, "Status bar");
 
-    /* hwndMenu = WinCreateMenu(hwndFrame,
-                             NULL);
-
-    hwndSubmenu = winhInsertSubmenu(hwndMenu,
-                                    MIT_END,
-                                    1,
-                                    "~File",
-                                    MIS_TEXT | MIS_SUBMENU,
-                                    1000,
-                                    "Open...",
-                                    MIS_TEXT,
-                                    0);
-
-    winhInsertMenuItem(hwndSubmenu,
-                       MIT_END,
-                       SC_CLOSE,
-                       "~Quit",
-                       MIS_SYSCOMMAND | MIS_TEXT,
-                       0);
-    */
-
     WinSendMsg(hwndFrame, WM_UPDATEFRAME, MPNULL, MPNULL);
+
+    {
+        XFIELDINFO  xfi[4];
+        PFIELDINFO      pfi = NULL;
+        int i = 0;
+        PGROUPRECORD preccFirst;
+
+        // set up cnr details view
+        xfi[i].ulFieldOffset = FIELDOFFSET(GROUPRECORD, gid);
+        xfi[i].pszColumnTitle = "Group ID";     // @@todo localize
+        xfi[i].ulDataType = CFA_ULONG;
+        xfi[i++].ulOrientation = CFA_RIGHT;
+
+        xfi[i].ulFieldOffset = FIELDOFFSET(GROUPRECORD, recc.pszIcon);
+        xfi[i].pszColumnTitle = "Group name";   // @@todo localize
+        xfi[i].ulDataType = CFA_STRING;
+        xfi[i++].ulOrientation = CFA_CENTER;
+
+        xfi[i].ulFieldOffset = FIELDOFFSET(GROUPRECORD, pszMembers);
+        xfi[i].pszColumnTitle = "Members";   // @@todo localize
+        xfi[i].ulDataType = CFA_STRING;
+        xfi[i++].ulOrientation = CFA_LEFT;
+
+        pfi = cnrhSetFieldInfos(hwndClient,
+                                xfi,
+                                i,             // array item count
+                                TRUE,          // draw lines
+                                0);            // return first column
+
+        BEGIN_CNRINFO()
+        {
+            cnrhSetView(CV_DETAIL | CA_DETAILSVIEWTITLES);
+            CnrInfo_.cyLineSpacing = 10;
+            ulSendFlags_ |= CMA_LINESPACING;
+        } END_CNRINFO(hwndClient);
+
+        #define RECORD_COUNT        200
+
+        if (preccFirst = (PGROUPRECORD)cnrhAllocRecords(hwndClient,
+                                                        sizeof(GROUPRECORD),
+                                                        RECORD_COUNT))
+        {
+            PGROUPRECORD preccThis = preccFirst;
+            ULONG   ul = 0;
+            while (preccThis)
+            {
+                preccThis->gid = ul++;
+                sprintf(preccThis->szGroupName, "group %d", preccThis->gid);
+                preccThis->recc.pszIcon = preccThis->szGroupName;
+
+                preccThis->pszMembers = "longer string than title";
+
+                preccThis = (PGROUPRECORD)preccThis->recc.preccNextRecord;
+            }
+
+            cnrhInsertRecords(hwndClient,
+                              NULL,
+                              (PRECORDCORE)preccFirst,
+                              TRUE,
+                              NULL,
+                              CRA_RECORDREADONLY,
+                              RECORD_COUNT);
+        }
+    }
 
     while (WinGetMsg(hab, &qmsg, NULLHANDLE, 0, 0))
Index: trunk/src/helpers/_test_exeh.c
===================================================================
--- trunk/src/helpers/_test_exeh.c	(revision 241)
+++ trunk/src/helpers/_test_exeh.c	(revision 242)
@@ -17,4 +17,5 @@
 
 #define INCL_KBD
+#define INCL_WINPROGRAMLIST     // needed for PROGDETAILS, wppgm.h
 #include <os2.h>
 
@@ -48,9 +49,36 @@
         if (!(arc = exehOpen(argv[1], &pExe)))
         {
+            APIRET  arc2;
+            ULONG   progt;
+
+            printf("exeh: dumping base info of \"%s\"\n", argv[1]);
+            printf("    cbDosExeHeader    %d\n", pExe->cbDosExeHeader);
+            printf("    ulExeFormat       %d (%s)\n",
+                    pExe->ulExeFormat,
+                    (pExe->ulExeFormat == EXEFORMAT_OLDDOS) ? "EXEFORMAT_OLDDOS"
+                    : (pExe->ulExeFormat == EXEFORMAT_NE) ? "EXEFORMAT_NE"
+                    : (pExe->ulExeFormat == EXEFORMAT_PE) ? "EXEFORMAT_PE"
+                    : (pExe->ulExeFormat == EXEFORMAT_LX) ? "EXEFORMAT_LX"
+                    : (pExe->ulExeFormat == EXEFORMAT_TEXT_BATCH) ? "EXEFORMAT_TEXT_BATCH"
+                    : (pExe->ulExeFormat == EXEFORMAT_TEXT_CMD) ? "EXEFORMAT_TEXT_CMD"
+                    : (pExe->ulExeFormat == EXEFORMAT_COM) ? "EXEFORMAT_COM"
+                    : "unknown"
+                  );
+            if (pExe->pLXHeader)
+                printf("    LX flags:         0x%lX\n", pExe->pLXHeader->ulFlags);
+            else if (pExe->pNEHeader)
+                printf("    NE flags:         0x%lX\n", pExe->pNEHeader->usFlags);
+            printf("    fLibrary          %d\n", pExe->fLibrary);
+            printf("    f32Bits           %d\n", pExe->f32Bits);
+            if (!(arc2 = exehQueryProgType(pExe, &progt)))
+                printf("    progtype:         %d (%s)\n", progt, exehDescribeProgType(progt));
+            else
+                printf("    exehQueryProgType returned %d\n", arc2);
+            printf("exeh: dumping bldlevel of \"%s\"\n", argv[1]);
+
             if (!(arc = exehQueryBldLevel(pExe)))
             {
                 #define STRINGORNA(p) ((pExe->p) ? (pExe->p) : "n/a")
 
-                printf("exeh: dumping bldlevel of \"%s\"\n", argv[1]);
                 printf("    Description:      \"%s\"\n", STRINGORNA(pszDescription));
                 printf("    Vendor:           \"%s\"\n", STRINGORNA(pszVendor));
Index: trunk/src/helpers/apps.c
===================================================================
--- trunk/src/helpers/apps.c	(revision 241)
+++ trunk/src/helpers/apps.c	(revision 242)
@@ -52,4 +52,5 @@
 #include "helpers\dosh.h"
 #include "helpers\except.h"             // exception handling
+#include "helpers\exeh.h"
 #include "helpers\prfh.h"
 #include "helpers\standards.h"          // some standard macros
@@ -676,86 +677,4 @@
 
 /*
- *@@ PROGTYPESTRING:
- *
- *@@added V0.9.16 (2002-01-13) [umoeller]
- */
-
-typedef struct _PROGTYPESTRING
-{
-    PROGCATEGORY    progc;
-    PCSZ            pcsz;
-} PROGTYPESTRING, *PPROGTYPESTRING;
-
-PROGTYPESTRING G_aProgTypes[] =
-    {
-        PROG_DEFAULT, "PROG_DEFAULT",
-        PROG_FULLSCREEN, "PROG_FULLSCREEN",
-        PROG_WINDOWABLEVIO, "PROG_WINDOWABLEVIO",
-        PROG_PM, "PROG_PM",
-        PROG_GROUP, "PROG_GROUP",
-        PROG_VDM, "PROG_VDM",
-            // same as PROG_REAL, "PROG_REAL",
-        PROG_WINDOWEDVDM, "PROG_WINDOWEDVDM",
-        PROG_DLL, "PROG_DLL",
-        PROG_PDD, "PROG_PDD",
-        PROG_VDD, "PROG_VDD",
-        PROG_WINDOW_REAL, "PROG_WINDOW_REAL",
-        PROG_30_STD, "PROG_30_STD",
-            // same as PROG_WINDOW_PROT, "PROG_WINDOW_PROT",
-        PROG_WINDOW_AUTO, "PROG_WINDOW_AUTO",
-        PROG_30_STDSEAMLESSVDM, "PROG_30_STDSEAMLESSVDM",
-            // same as PROG_SEAMLESSVDM, "PROG_SEAMLESSVDM",
-        PROG_30_STDSEAMLESSCOMMON, "PROG_30_STDSEAMLESSCOMMON",
-            // same as PROG_SEAMLESSCOMMON, "PROG_SEAMLESSCOMMON",
-        PROG_31_STDSEAMLESSVDM, "PROG_31_STDSEAMLESSVDM",
-        PROG_31_STDSEAMLESSCOMMON, "PROG_31_STDSEAMLESSCOMMON",
-        PROG_31_ENHSEAMLESSVDM, "PROG_31_ENHSEAMLESSVDM",
-        PROG_31_ENHSEAMLESSCOMMON, "PROG_31_ENHSEAMLESSCOMMON",
-        PROG_31_ENH, "PROG_31_ENH",
-        PROG_31_STD, "PROG_31_STD",
-
-// Warp 4 toolkit defines, whatever these were designed for...
-#ifndef PROG_DOS_GAME
-    #define PROG_DOS_GAME            (PROGCATEGORY)21
-#endif
-#ifndef PROG_WIN_GAME
-    #define PROG_WIN_GAME            (PROGCATEGORY)22
-#endif
-#ifndef PROG_DOS_MODE
-    #define PROG_DOS_MODE            (PROGCATEGORY)23
-#endif
-
-        PROG_DOS_GAME, "PROG_DOS_GAME",
-        PROG_WIN_GAME, "PROG_WIN_GAME",
-        PROG_DOS_MODE, "PROG_DOS_MODE",
-
-        // added this V0.9.16 (2001-12-08) [umoeller]
-        PROG_WIN32, "PROG_WIN32"
-    };
-
-/*
- *@@ appDescribeAppType:
- *      returns a "PROG_*" string for the given
- *      program type. Useful for WPProgram setup
- *      strings and such.
- *
- *@@added V0.9.16 (2001-10-06)
- */
-
-PCSZ appDescribeAppType(PROGCATEGORY progc)        // in: from PROGDETAILS.progc
-{
-    ULONG ul;
-    for (ul = 0;
-         ul < ARRAYITEMCOUNT(G_aProgTypes);
-         ul++)
-    {
-        if (G_aProgTypes[ul].progc == progc)
-            return G_aProgTypes[ul].pcsz;
-    }
-
-    return NULL;
-}
-
-/*
  *@@ appIsWindowsApp:
  *      checks the specified program category
Index: trunk/src/helpers/cctl_cnr.c
===================================================================
--- trunk/src/helpers/cctl_cnr.c	(revision 242)
+++ trunk/src/helpers/cctl_cnr.c	(revision 242)
@@ -0,0 +1,1488 @@
+
+/*
+ *@@sourcefile cctl_cnr.c:
+ *      implementation for the replacement container control.
+ *
+ *@@header "helpers\comctl.h"
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+/*
+ *      Copyright (C) 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 OS2EMX_PLAIN_CHAR
+    // this is needed for "os2emx.h"; if this is defined,
+    // emx will define PSZ as _signed_ char, otherwise
+    // as unsigned char
+
+#define INCL_WINWINDOWMGR
+#define INCL_WINFRAMEMGR
+#define INCL_WINSYS
+#define INCL_WINSCROLLBARS
+#define INCL_WINSTDCNR
+
+#define INCL_GPIPRIMITIVES
+#define INCL_GPILCIDS
+#include <os2.h>
+
+#include "setup.h"                      // code generation and debugging options
+
+#include "helpers\comctl.h"
+#include "helpers\linklist.h"
+#include "helpers\gpih.h"
+#include "helpers\nls.h"
+#include "helpers\standards.h"
+#include "helpers\stringh.h"
+#include "helpers\tree.h"
+#include "helpers\winh.h"
+
+#include "private\cnr.h"
+
+#pragma hdrstop
+
+/*
+ *@@category: Helpers\PM helpers\Window classes\Container control replacement
+ *      See cctl_cnr.c.
+ */
+
+/* ******************************************************************
+ *
+ *   Global variables
+ *
+ ********************************************************************/
+
+extern const SYSCOLORSET G_scsCnr =
+    {
+        TRUE,       // inherit presparams
+        SYSCLR_WINDOW,
+        SYSCLR_WINDOWTEXT
+    };
+
+/* ******************************************************************
+ *
+ *   Helper funcs
+ *
+ ********************************************************************/
+
+VOID cnrDrawString(HPS hps,
+                   PCSZ pcsz,              // in: string to test
+                   PRECTL prcl,            // in: clipping rectangle (inclusive!)
+                   ULONG fl,               // in: alignment flags
+                   PFONTMETRICS pfm)
+{
+    ULONG fl2 = 0;
+/*
+   #define CFA_LEFT            0x00000001L
+   #define CFA_RIGHT           0x00000002L
+   #define CFA_CENTER          0x00000004L
+   #define CFA_TOP             0x00000008L
+   #define CFA_VCENTER         0x00000010L
+   #define CFA_BOTTOM          0x00000020L
+
+ *      --  DT_LEFT                    0x00000000
+ *      --  DT_CENTER                  0x00000100
+ *      --  DT_RIGHT                   0x00000200
+ *      --  DT_TOP                     0x00000000
+ *      --  DT_VCENTER                 0x00000400
+ *      --  DT_BOTTOM                  0x00000800
+*/
+    if (fl & CFA_RIGHT)
+        fl2 = DT_RIGHT;
+    else if (fl & CFA_CENTER)
+        fl2 = DT_CENTER;
+
+    if (fl & CFA_BOTTOM)
+        fl2 = DT_BOTTOM;
+    else if (fl & CFA_VCENTER)
+        fl2 = DT_VCENTER;
+
+    gpihDrawString(hps,
+                   pcsz,
+                   prcl,
+                   fl2,
+                   pfm);
+}
+
+/*
+ *@@ CreateChild:
+ *      creates a container child window.
+ */
+
+HWND CreateChild(PCNRDATA pData,
+                 PCSZ pcszClass,
+                 ULONG id)
+{
+    return WinCreateWindow(pData->dwdMain.hwnd,
+                           (PSZ)pcszClass,
+                           NULL,
+                           WS_VISIBLE,
+                           0,
+                           0,
+                           0,
+                           0,
+                           pData->dwdMain.hwnd,
+                           HWND_TOP,
+                           id,
+                           pData,
+                           NULL);
+}
+
+/*
+ *@@ FindColumnFromFI:
+ *
+ */
+
+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;
+        if (pCol->pfi == pfi)
+        {
+            if (ppNode)
+                *ppNode = pNode;
+
+            return pCol;
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ *@@ FindListNodeForRecc:
+ *
+ */
+
+PLISTNODE FindListNodeForRecc(PCNRDATA pData,
+                              const RECORDCORE *precc)
+{
+    RECORDTREEITEM  *pti;
+    if (pti = (PRECORDTREEITEM)treeFind(pData->RecordsTree,
+                                        (ULONG)precc,
+                                        treeCompareKeys))
+        return pti->pListNode;
+
+    return NULL;
+}
+
+/* ******************************************************************
+ *
+ *   Standard window messages
+ *
+ ********************************************************************/
+
+/*
+ *@@ CnrCreate:
+ *      implementation for WM_CREATE in fnwpCnr.
+ */
+
+MRESULT CnrCreate(HWND hwnd, MPARAM mp1, MPARAM mp2)
+{
+    PCNRDATA    pData;
+
+    if (!(pData = NEW(CNRDATA)))
+        return (MRESULT)TRUE;       // stop window creation
+
+    WinSetWindowPtr(hwnd, QWL_USER + 1, pData);
+    ZERO(pData);
+
+    // initialize DEFWINDOWDATA
+    ctlInitDWD(hwnd,
+               mp2,
+               &pData->dwdMain,
+               WinDefWindowProc,
+               &G_scsCnr);
+
+    if (winhQueryWindowStyle(hwnd) & CCS_MINIRECORDCORE)
+        pData->fMiniRecords = TRUE;
+
+    // set up non-zero default values in cnrinfo
+    pData->CnrInfo.cb = sizeof(CNRINFO);
+    pData->CnrInfo.xVertSplitbar = -1;
+
+    lstInit(&pData->llAllocatedFIs,
+            TRUE);
+    lstInit(&pData->llColumns,
+            TRUE);
+    lstInit(&pData->llAllocatedRecs,
+            TRUE);
+    lstInit(&pData->llRootRecords,
+            TRUE);
+
+    treeInit(&pData->RecordsTree,
+             (PLONG)&pData->CnrInfo.cRecords);
+
+    nlsQueryCountrySettings(&pData->cs);
+
+    winhCreateScrollBars(hwnd,
+                         &pData->hwndVScroll,
+                         &pData->hwndHScroll);
+
+    pData->cxScrollBar = WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL);
+    pData->cyScrollBar = WinQuerySysValue(HWND_DESKTOP, SV_CYHSCROLL);
+
+    return (MRESULT)FALSE;
+}
+
+/*
+ *@@ CnrSem2:
+ *      implementation for WM_SEM2 in fnwpCnr.
+ *
+ *      The DDFL_* semaphore bits get set whenever the
+ *      view needs to recompute something. In the worst
+ *      case, we resize and/or invalidate the window.
+ */
+
+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))
+            {
+                HPS hps = WinGetPS(hwnd);
+                cdtlRecalcDetails(pData, hps, &fl);
+                WinReleasePS(hps);
+
+                if (fl & DDFL_WINDOWSIZECHANGED)
+                    WinInvalidateRect(pData->dwdMain.hwnd, NULL, TRUE);
+            }
+
+            if (fl & (DDFL_WINDOWSIZECHANGED | DDFL_WORKAREACHANGED))
+            {
+                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);
+                }
+            }
+        }
+    }
+}
+
+/*
+ *@@ 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))
+       )
+    {
+        gpihSwitchToRGB(hps);
+
+        if (    (pData->CnrInfo.flWindowAttr & (CV_DETAIL | CA_DETAILSVIEWTITLES))
+             == (CV_DETAIL | CA_DETAILSVIEWTITLES)
+           )
+        {
+            PLISTNODE pColNode;
+
+            POINTL  ptl;
+            RECTL   rcl;
+            LONG    yPadding = pData->CnrInfo.cyLineSpacing / 2;
+
+            // lowest y that we are allowed to paint at
+            LONG    yLowest = pData->dwdMain.szlWin.cy - pData->cyColTitlesBox;
+
+            if (rclPaint.yTop > yLowest)
+            {
+                // clip paint rect so we don't paint into details wnd
+                if (rclPaint.yBottom < yLowest)
+                    rclPaint.yBottom = yLowest;
+
+                WinFillRect(hps,
+                            &rclPaint,
+                            pData->dwdMain.lcolBackground);
+
+                FOR_ALL_NODES(&pData->llColumns, pColNode)
+                {
+                    RECTL       rcl2;
+                    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->dwdMain.szlWin.cy;
+
+                    rcl.yTop -= COLUMN_PADDING_Y + yPadding;
+                    rcl.yBottom =   rcl.yTop
+                                  - pData->cyColTitlesContent;
+
+                    if (pfi->flTitle & CFA_BITMAPORICON)
+                        // @@todo
+                        ;
+                    else
+                    {
+                        cnrDrawString(hps,
+                                      (PCSZ)pfi->pTitleData,
+                                      &rcl,
+                                      pfi->flTitle,
+                                      &pData->fm);
+                    }
+
+                    rcl.yBottom -= COLUMN_PADDING_Y + yPadding;
+
+                    if (pfi->flData & CFA_HORZSEPARATOR)
+                    {
+                        ptl.x = rcl.xLeft;
+                        ptl.y = rcl.yBottom;
+
+                        GpiMove(hps,
+                                &ptl);
+                        ptl.x = rcl.xRight;
+                        GpiLine(hps,
+                                &ptl);
+                    }
+
+                    rcl.yTop = rcl.yBottom;
+
+                } // FOR_ALL_NODES(&pData->llColumns, pColNode)
+            }
+        }
+
+        WinEndPaint(hps);
+    }
+}
+
+/*
+ *@@ CnrWindowPosChanged:
+ *      implementation for WM_WINDOWPOSCHANGED in fnwpCnr.
+ */
+
+MRESULT CnrWindowPosChanged(HWND hwnd,
+                            MPARAM mp1,
+                            MPARAM mp2)
+{
+    MRESULT mrc = 0;
+    PCNRDATA pData;
+    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    {
+        mrc = ctlDefWindowProc(&pData->dwdMain, WM_WINDOWPOSCHANGED, mp1, mp2);
+
+        if (((PSWP)mp1)->fl & SWP_SIZE)
+        {
+            WinPostMsg(hwnd,
+                       WM_SEM2,
+                       (MPARAM)DDFL_WINDOWSIZECHANGED,
+                       0);
+        }
+    }
+
+    return mrc;
+}
+
+/*
+ *@@ cnrPresParamChanged:
+ *      implementation for WM_PRESPARAMCHANGED for both
+ *      fnwpCnr and fnwpCnrDetails.
+ */
+
+VOID cnrPresParamChanged(HWND hwnd,
+                         ULONG ulpp)
+{
+    PCNRDATA pData;
+    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    {
+        // note, we use the dwdMain buffer here cos
+        // we share presparams with the cnr
+        ctlRefreshColors(&pData->dwdMain);
+
+        switch (ulpp)
+        {
+            case 0:     // layout palette thing dropped
+            case PP_FONTNAMESIZE:
+                if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
+                {
+                    // 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))
+                    {
+                        HWND hwndOther;
+                        DosBeep(1000, 10);
+                        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_INCALIDATERECORDS),
+                               0);
+                }
+            break;
+
+            default:
+                // just repaint everything
+                WinInvalidateRect(pData->dwdMain.hwnd, NULL, TRUE);
+        }
+    }
+}
+
+/*
+ *@@ CnrScroll:
+ *      implementation for WM_HSCROLL and WM_VSCROLL in fnwpCnr.
+ */
+
+VOID CnrScroll(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+    PCNRDATA pData;
+    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    {
+        if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
+        {
+            POINTL  ptlScroll;
+            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 (pData->CnrInfo.flWindowAttr & CA_DETAILSVIEWTITLES)
+                    {
+                        RECTL rclClip;
+                        rclClip.xLeft = 0;
+                        rclClip.xRight = pData->dwdMain.szlWin.cx;
+                        rclClip.yBottom = pData->dwdMain.szlWin.cy - pData->cyColTitlesBox;
+                        rclClip.yTop = pData->dwdMain.szlWin.cy;
+                        winhScrollWindow(hwnd,
+                                         &rclClip,
+                                         &ptlScroll);
+                    }
+                    winhScrollWindow(pData->hwndDetails,
+                                     NULL,
+                                     &ptlScroll);
+                }
+            }
+            else
+            {
+                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,
+                                     NULL,
+                                     &ptlScroll);
+            }
+        }
+    }
+}
+
+/*
+ *@@ CnrDestroy:
+ *      implementation for WM_DESTROY in fnwpCnr.
+ */
+
+VOID CnrDestroy(HWND hwnd)
+{
+    PCNRDATA pData;
+    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    {
+        // free all data that we ever allocated;
+        // all these lists are auto-free
+        lstClear(&pData->llColumns);
+        lstClear(&pData->llAllocatedFIs);
+        lstClear(&pData->llAllocatedRecs);
+        lstClear(&pData->llRootRecords);
+
+        free(pData);
+    }
+}
+
+/* ******************************************************************
+ *
+ *   General container messages
+ *
+ ********************************************************************/
+
+/*
+ *@@ CnrQueryCnrInfo:
+ *      implementation for CM_QUERYCNRINFO in fnwpCnr.
+ *
+ *      Returns no. of bytes copied.
+ */
+
+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))
+    {
+        USHORT cbCopied = max(cb, sizeof(CNRINFO));
+        memcpy(pci,
+               &pData->CnrInfo,
+               cbCopied);
+        return cbCopied;
+    }
+
+    return 0;
+}
+
+/*
+ *@@ CnrSetCnrInfo:
+ *      implementation for CM_SETCNRINFO in fnwpCnr.
+ *
+ *      Returns no. of bytes copied.
+ */
+
+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))
+    {
+        ULONG   flDirty = 0;
+
+        if (flCI & CMA_PSORTRECORD)
+            /* Pointer to the comparison function for sorting container records. If NULL,
+            which is the default condition, no sorting is performed. Sorting only occurs
+            during record insertion and when changing the value of this field. The third
+            parameter of the comparison function, pStorage, must be NULL. See
+            CM_SORTRECORD for a further description of the comparison function. */
+            pData->CnrInfo.pSortRecord = pci->pSortRecord;
+
+        if (flCI & CMA_PFIELDINFOLAST)
+            /* Pointer to the last column in the left window of the split details view. The
+            default is NULL, causing all columns to be positioned in the left window. */
+            pData->CnrInfo.pFieldInfoLast = pci->pFieldInfoLast;
+
+        if (flCI & CMA_PFIELDINFOOBJECT)
+            /* Pointer to a column that represents an object in the details view. This
+            FIELDINFO structure must contain icons or bit maps. In-use emphasis is applied
+            to this column of icons or bit maps only. The default is the leftmost column
+            in the unsplit details view, or the leftmost column in the left window of the
+            split details view. */
+            pData->CnrInfo.pFieldInfoObject = pci->pFieldInfoObject;
+
+        if (flCI & CMA_CNRTITLE)
+        {
+            // Text for the container title. The default is NULL.
+            pData->CnrInfo.pszCnrTitle = pci->pszCnrTitle;
+
+            // @@todo recalc window components, repaint
+        }
+
+        if (flCI & CMA_FLWINDOWATTR)
+        {
+            // Container window attributes.
+            if (pData->CnrInfo.flWindowAttr != pci->flWindowAttr)
+            {
+                HWND    hwndSwitchOwner = NULLHANDLE;
+
+                pData->CnrInfo.flWindowAttr = pci->flWindowAttr;
+
+                // if switching to details view, then create
+                // details subwindow
+                if (pData->CnrInfo.flWindowAttr & CV_DETAIL)
+                {
+                    if (!pData->hwndDetails)
+                    {
+                        if (pData->hwndDetails = CreateChild(pData,
+                                                             WC_CCTL_CNR_DETAILS,
+                                                             CID_LEFTDVWND))
+                        {
+                            flDirty = DDFL_ALL;
+                        }
+                    }
+                }
+                else
+                {
+                    winhDestroyWindow(&pData->hwndDetails);
+                }
+            }
+        }
+
+        if (flCI & CMA_PTLORIGIN)
+        {
+            // Lower-left origin of the container window in virtual workspace coordinates,
+            // used in the icon view. The default origin is (0,0).
+            memcpy(&pData->CnrInfo.ptlOrigin,
+                   &pci->ptlOrigin,
+                   sizeof(POINTL));
+
+            // @@todo recalc window components, repaint
+        }
+
+        if (flCI & CMA_DELTA)
+        {
+            // An application-defined threshold, or number of records, from either end of
+            // the list of available records. Used when a container needs to handle large
+            // amounts of data. The default is 0. Refer to the description of the container
+            // control in the OS/2 Programming Guide for more information about specifying
+            // deltas.
+            pData->CnrInfo.cDelta = pci->cDelta;
+        }
+
+        if (flCI & CMA_SLBITMAPORICON)
+        {
+            // The size (in pels) of icons or bit maps. The default is the system size.
+            memcpy(&pData->CnrInfo.slBitmapOrIcon,
+                   &pci->slBitmapOrIcon,
+                   sizeof(SIZEL));
+
+            // @@todo recalc window components, repaint
+        }
+
+        if (flCI & CMA_SLTREEBITMAPORICON)
+        {
+            // The size (in pels) of the expanded and collapsed icons or bit maps in the
+            // tree icon and tree text views.
+            memcpy(&pData->CnrInfo.slTreeBitmapOrIcon,
+                   &pci->slTreeBitmapOrIcon,
+                   sizeof(SIZEL));
+
+            // @@todo recalc window components, repaint
+        }
+
+        if (flCI & CMA_TREEBITMAP)
+        {
+            // Expanded and collapsed bit maps in the tree icon and tree text views.
+            pData->CnrInfo.hbmExpanded = pci->hbmExpanded;
+            pData->CnrInfo.hbmCollapsed = pci->hbmCollapsed;
+
+            // @@todo recalc window components, repaint
+        }
+
+        if (flCI & CMA_TREEICON)
+        {
+            // Expanded and collapsed icons in the tree icon and tree text views.
+            pData->CnrInfo.hptrExpanded = pci->hptrExpanded;
+            pData->CnrInfo.hptrCollapsed = pci->hptrCollapsed;
+
+            // @@todo recalc window components, repaint
+        }
+
+        if (flCI & CMA_LINESPACING)
+        {
+            // The amount of vertical space (in pels) between the records. If this value is
+            // less than 0, a default value is used.
+            pData->CnrInfo.cyLineSpacing = pci->cyLineSpacing;
+
+            // @@todo recalc window components, repaint
+        }
+
+        if (flCI & CMA_CXTREEINDENT)
+        {
+            // Horizontal distance (in pels) between levels in the tree view. If this value is
+            // less than 0, a default value is used.
+            pData->CnrInfo.cxTreeIndent = pci->cxTreeIndent;
+
+            // @@todo recalc window components, repaint
+        }
+
+        if (flCI & CMA_CXTREELINE)
+        {
+            // Width of the lines (in pels) that show the relationship between items in the
+            // tree view. If this value is less than 0, a default value is used. Also, if the
+            // CA_TREELINE container attribute of the CNRINFO data structure's
+            // flWindowAttr field is not specified, these lines are not drawn.
+            pData->CnrInfo.cxTreeLine = pci->cxTreeLine;
+        }
+
+        if (flCI & CMA_XVERTSPLITBAR)
+        {
+            // The initial position of the split bar relative to the container, used in the
+            // details view. If this value is less than 0, the split bar is not used. The default
+            // value is negative one (-1).
+            pData->CnrInfo.xVertSplitbar = pci->xVertSplitbar;
+        }
+
+        if (flDirty)
+            // post semaphore to force resize of details wnd
+            WinPostMsg(hwnd,
+                       WM_SEM2,
+                       (MPARAM)flDirty,
+                       0);
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+/* ******************************************************************
+ *
+ *   FIELDINFO-related messages
+ *
+ ********************************************************************/
+
+/*
+ *@@ CnrAllocDetailFieldInfo:
+ *      implementation for CM_ALLOCDETAILFIELDINFO in fnwpCnr.
+ *
+ *      Returns: PFIELDINFO linked list or NULL.
+ */
+
+PFIELDINFO CnrAllocDetailFieldInfo(HWND hwnd,
+                                   USHORT cFieldInfos)      // in: no. of fieldinfos to allocate (> 0)
+{
+    PFIELDINFO  pfiFirst = NULL,
+                pfiPrev = NULL;
+
+    PCNRDATA    pData;
+    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    {
+        ULONG       ul;
+        for (ul = 0;
+             ul < cFieldInfos;
+             ++ul)
+        {
+            // we must allocate each one separately or we cannot
+            // free them separately with CM_FREEDETAILFIELDINFO
+            PFIELDINFO  pfiThis;
+            if (pfiThis = NEW(FIELDINFO))
+            {
+                ZERO(pfiThis);
+
+                // link into list
+                if (!pfiPrev)
+                    // first round:
+                    pfiFirst = pfiThis;
+                else
+                    pfiPrev->pNextFieldInfo = pfiThis;
+
+                // put into private linklist
+                lstAppendItem(&pData->llAllocatedFIs,
+                              pfiThis);
+
+                pfiPrev = pfiThis;
+            }
+        }
+    }
+
+    return pfiFirst;
+}
+
+/*
+ *@@ CnrInsertDetailFieldInfo:
+ *      implementation for CM_INSERTDETAILFIELDINFO in fnwpCnr.
+ */
+
+USHORT CnrInsertDetailFieldInfo(HWND hwnd,
+                                PFIELDINFO pfiFirst,
+                                PFIELDINFOINSERT pfii)
+{
+    USHORT      usrc = 0;
+    PCNRDATA    pData;
+    if (    (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+         && (pfiFirst)
+         && (pfii)
+         && (pfii->cb = sizeof(FIELDINFOINSERT))
+       )
+    {
+        ULONG       ul;
+        PFIELDINFO  pfiThis = pfiFirst;
+        PLISTNODE   pNodeInsertAfter;
+
+        usrc = lstCountItems(&pData->llColumns);
+
+        switch ((ULONG)pfii->pFieldInfoOrder)
+        {
+            case CMA_FIRST:
+                pNodeInsertAfter = NULL;        // first
+            break;
+
+            case CMA_END:
+                pNodeInsertAfter = lstQueryLastNode(&pData->llColumns);
+                                            // can return NULL also
+            break;
+
+            default:
+                if (!FindColumnFromFI(pData,
+                                      pfii->pFieldInfoOrder,
+                                      &pNodeInsertAfter))
+                    pNodeInsertAfter = NULL;
+        }
+
+        for (ul = 0;
+             ul < pfii->cFieldInfoInsert;
+             ++ul)
+        {
+            PDETAILCOLUMN pdc;
+            if (pdc = NEW(DETAILCOLUMN))
+            {
+                ZERO(pdc);
+
+                pdc->pfi = pfiThis;
+
+                if (pNodeInsertAfter = lstInsertItemAfterNode(&pData->llColumns,
+                                                              pdc,
+                                                              pNodeInsertAfter))
+                {
+                    ++usrc;
+                    ++pData->CnrInfo.cFields;
+                    pfiThis = pfiThis->pNextFieldInfo;
+                    continue;
+                }
+            }
+
+            free(pdc);
+
+            usrc = 0;
+            break;
+
+        } // for (ul = 0; ul < pfii->cFieldInfoInsert; ...
+
+        if (    (usrc)
+             && (pfii->fInvalidateFieldInfo)
+           )
+        {
+            // post semaphore to force resize of details wnd
+            WinPostMsg(hwnd,
+                       WM_SEM2,
+                       (MPARAM)DDFL_ALL,
+                       0);
+        }
+    }
+
+    return usrc;
+}
+
+/*
+ *@@ CnrInvalidateDetailFieldInfo:
+ *      implementation for CM_INVALIDATEDETAILFIELDINFO in fnwpCnr.
+ */
+
+BOOL CnrInvalidateDetailFieldInfo(HWND hwnd)
+{
+    PCNRDATA    pData;
+    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    {
+        WinPostMsg(hwnd,
+                   WM_SEM2,
+                   (MPARAM)DDFL_INVALIDATECOLUMNS,
+                   0);
+    }
+
+    return FALSE;
+}
+
+/*
+ *@@ CnrRemoveDetailFieldInfo:
+ *      implementation for CM_REMOVEDETAILFIELDINFO in fnwpCnr.
+ */
+
+SHORT CnrRemoveDetailFieldInfo(HWND hwnd,
+                               PFIELDINFO* ppafi,
+                               USHORT cfi,
+                               USHORT fl)
+{
+    PCNRDATA    pData;
+    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    {
+        SHORT   rc = lstCountItems(&pData->llColumns);
+        ULONG   fAnythingFound = FALSE,
+                ul;
+
+        for (ul = 0;
+             ul < cfi;
+             ++ul)
+        {
+            PDETAILCOLUMN pCol;
+            PLISTNODE pNodeCol;
+            if (!(pCol = FindColumnFromFI(pData,
+                                          ppafi[ul],
+                                          &pNodeCol)))
+            {
+                rc = -1;
+                break;
+            }
+
+            // found:
+            lstRemoveNode(&pData->llColumns,
+                          pNodeCol);            // auto-free, so this frees the DETAILCOLUMN
+
+            if (fl & CMA_FREE)
+                lstRemoveItem(&pData->llAllocatedFIs,
+                              ppafi[ul]);       // auto-free, so this frees the FIELDINFO
+
+            fAnythingFound = TRUE;
+
+            --rc;
+            --pData->CnrInfo.cFields;
+        }
+
+        if (    (fAnythingFound)
+             && (fl & CMA_INVALIDATE)
+           )
+        {
+            WinPostMsg(hwnd,
+                       WM_SEM2,
+                       (MPARAM)DDFL_INVALIDATECOLUMNS,
+                       0);
+        }
+
+        return rc;
+    }
+
+    return -1;
+}
+
+/*
+ *@@ CnrFreeDetailFieldInfo:
+ *      implementation for CM_FREEDETAILFIELDINFO in fnwpCnr.
+ */
+
+BOOL CnrFreeDetailFieldInfo(HWND hwnd,
+                            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))
+    {
+        ULONG   ul;
+
+        // @@todo return FALSE if the FI is currently inserted
+
+        if (1)
+        {
+            for (ul = 0;
+                 ul < cFieldInfos;
+                 ++ul)
+            {
+                PFIELDINFO pfiThis = ppafi[ul];
+                lstRemoveItem(&pData->llAllocatedFIs,
+                              pfiThis);
+            }
+
+            brc = TRUE;
+        }
+    }
+
+    return brc;
+}
+
+/* ******************************************************************
+ *
+ *   Record insertion/removal
+ *
+ ********************************************************************/
+
+/*
+ *@@ CnrAllocRecord:
+ *      implementation for CM_ALLOCRECORD in fnwpCnr.
+ */
+
+PRECORDCORE CnrAllocRecord(HWND hwnd,
+                           ULONG cbExtra,
+                           USHORT cRecords)
+{
+    PRECORDCORE preccFirst = NULL;
+    PCNRDATA    pData;
+    if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+    {
+        ULONG   ul;
+        ULONG   cbAlloc = (   (pData->fMiniRecords)
+                            ? sizeof(MINIRECORDCORE)
+                            : sizeof(RECORDCORE)
+                          ) + cbExtra;
+
+        PRECORDCORE preccPrev = NULL;
+
+        for (ul = 0;
+             ul < cRecords;
+             ++ul)
+        {
+            PRECORDCORE preccThis;
+            if (!(preccThis = (PRECORDCORE)malloc(cbAlloc)))
+            {
+                preccFirst = NULL;
+                break;
+            }
+
+            memset(preccThis, 0, cbAlloc);
+
+            preccThis->cb = cbAlloc;
+
+            // link into list
+            if (!preccPrev)
+                // first round:
+                preccFirst = preccThis;
+            else
+                preccPrev->preccNextRecord = preccThis;
+
+            // put into private linklist
+            lstAppendItem(&pData->llAllocatedRecs,
+                          preccThis);
+
+            preccPrev = preccThis;
+        }
+    }
+
+    return preccFirst;
+}
+
+/*
+ *@@ CnrInsertRecord:
+ *      implementation for CM_INSERTRECORD in fnwpCnr.
+ */
+
+ULONG CnrInsertRecord(HWND hwnd,
+                      PRECORDCORE preccFirst,
+                      PRECORDINSERT pri)
+{
+    ULONG       cReturn = 0;
+    PCNRDATA    pData;
+    if (    (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+         && (preccFirst)
+         && (pri)
+         && (pri->cb = sizeof(RECORDINSERT))
+       )
+    {
+        PRECORDCORE preccThis = preccFirst;
+        ULONG ul;
+        PLINKLIST   pll;
+        PLISTNODE   pNodeInsertAfter;
+
+        cReturn = lstCountItems(&pData->llRootRecords);
+
+        if (pri->pRecordParent)
+        {
+            // @@todo
+        }
+        else
+            // insert at root:
+            pll = &pData->llRootRecords;
+
+        switch ((ULONG)pri->pRecordOrder)
+        {
+            case CMA_FIRST:
+                pNodeInsertAfter = NULL;        // first
+            break;
+
+            case CMA_END:
+                pNodeInsertAfter = lstQueryLastNode(pll);
+                                            // can return NULL also
+            break;
+
+            default:
+                pNodeInsertAfter = FindListNodeForRecc(pData,
+                                                       pri->pRecordOrder);
+        }
+
+        for (ul = 0;
+             ul < pri->cRecordsInsert;
+             ++ul)
+        {
+            PRECORDLISTITEM pListItem;
+
+            if (pListItem = NEW(RECORDLISTITEM))
+            {
+                ZERO(pListItem);
+
+                pListItem->precc = preccThis;
+                pListItem->preccParent = pri->pRecordParent;
+
+                if (pNodeInsertAfter = lstInsertItemAfterNode(pll,
+                                                              pListItem,
+                                                              pNodeInsertAfter))
+                {
+                    PRECORDTREEITEM pTreeItem;
+
+                    if (pTreeItem = NEW(RECORDTREEITEM))
+                    {
+                        ZERO(pTreeItem);
+
+                        pTreeItem->Tree.ulKey = (ULONG)preccThis;
+                        pTreeItem->pListNode = pNodeInsertAfter;    // newly created list node
+
+                        // the following will fail if the record
+                        // is already inserted!
+                        if (!treeInsert(&pData->RecordsTree,
+                                        (PLONG)&pData->CnrInfo.cRecords,
+                                        (TREE*)pTreeItem,
+                                        treeCompareKeys))
+                        {
+                            ++cReturn;
+                            preccThis = preccThis->preccNextRecord;
+                            continue;
+                        }
+
+                        free(pTreeItem);
+                    }
+
+                    lstRemoveNode(pll,
+                                  pNodeInsertAfter);
+                }
+
+                free(pListItem);
+            }
+
+            free(pListItem);
+
+            cReturn = 0;
+            break;      // for
+        } // for (ul = 0; ul < pri->cRecordsInsert; ...
+
+        if (    (cReturn)
+             && (pri->fInvalidateRecord)
+           )
+        {
+            WinPostMsg(hwnd,
+                       WM_SEM2,
+                       (MPARAM)DDFL_INCALIDATERECORDS,
+                       0);
+        }
+    }
+
+    return cReturn;
+}
+
+/* ******************************************************************
+ *
+ *   Container window proc
+ *
+ ********************************************************************/
+
+/*
+ *@@ fnwpCnr:
+ *
+ */
+
+MRESULT EXPENTRY fnwpCnr(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+    MRESULT     mrc = 0;
+    PCNRDATA    pData;
+
+    switch (msg)
+    {
+        /* ******************************************************************
+         *
+         *   Standard window messages
+         *
+         ********************************************************************/
+
+        case WM_CREATE:
+            mrc = CnrCreate(hwnd, mp1, mp2);
+        break;
+
+        case WM_SEM2:
+            CnrSem2(hwnd, (ULONG)mp1);
+        break;
+
+        case WM_PAINT:
+            CnrPaint(hwnd);
+        break;
+
+        case WM_WINDOWPOSCHANGED:
+            mrc = CnrWindowPosChanged(hwnd, mp1, mp2);
+        break;
+
+        case WM_HSCROLL:
+        case WM_VSCROLL:
+            CnrScroll(hwnd, msg, mp1, mp2);
+        break;
+
+        case WM_DESTROY:
+            CnrDestroy(hwnd);
+        break;
+
+        /* ******************************************************************
+         *
+         *   General container messages
+         *
+         ********************************************************************/
+
+        /*
+         * CM_QUERYCNRINFO:
+         *
+         *      Parameters:
+         *
+         *      --  PCNRINFO mp1: buffer to copy to.
+         *      --  USHORT mp2: size of buffer.
+         *
+         *      Returns no. of bytes copied or 0 on errors.
+         */
+
+        case CM_QUERYCNRINFO:
+            mrc = (MRESULT)CnrQueryCnrInfo(hwnd,
+                                           (PCNRINFO)mp1,
+                                           (USHORT)mp2);
+        break;
+
+        /*
+         * CM_SETCNRINFO:
+         *
+         *      Parameters:
+         *
+         *      --  PCNRINFO mp1: buffer to copy fields from.
+         *
+         *      --  ULONG fl: CMA_* flags for fields that changed.
+         *
+         *      Returns BOOL.
+         */
+
+        case CM_SETCNRINFO:
+            mrc = (MRESULT)CnrSetCnrInfo(hwnd,
+                                         (PCNRINFO)mp1,
+                                         (ULONG)mp2);
+        break;
+
+        /* ******************************************************************
+         *
+         *   FIELDINFO-related messages
+         *
+         ********************************************************************/
+
+        /*
+         * CM_ALLOCDETAILFIELDINFO:
+         *
+         *      Parameters:
+         *
+         *      --  USHORT mp1: no. of fieldinfos to allocate
+         *      --  mp2: reserved
+         *
+         *      Returns PFIELDINFO linked list of fieldinfos,
+         *      or NULL on errors.
+         */
+
+        case CM_ALLOCDETAILFIELDINFO:
+            mrc = (MRESULT)CnrAllocDetailFieldInfo(hwnd,
+                                                   (USHORT)mp1);
+        break;
+
+        /*
+         * CM_INSERTDETAILFIELDINFO:
+         *
+         *      Parameters:
+         *
+         *      --  PFIELDINFO mp1
+         *
+         *      --  PFIELDINFOINSERT mp2
+         *
+         *      Returns the no. of FI's in the cnr or 0 on errors.
+         */
+
+        case CM_INSERTDETAILFIELDINFO:
+            mrc = (MRESULT)CnrInsertDetailFieldInfo(hwnd,
+                                                    (PFIELDINFO)mp1,
+                                                    (PFIELDINFOINSERT)mp2);
+        break;
+
+        /*
+         * CM_INVALIDATEDETAILFIELDINFO:
+         *      No parameters.
+         *
+         *      Returns BOOL.
+         */
+
+        case CM_INVALIDATEDETAILFIELDINFO:
+            mrc = (MRESULT)CnrInvalidateDetailFieldInfo(hwnd);
+        break;
+
+        /*
+         * CM_REMOVEDETAILFIELDINFO:
+         *
+         *      Parameters:
+         *
+         *      --  PFIELDINFO* mp1: ptr to array of PFIELDINFO's
+         *
+         *      --  SHORT1FROMMP(mp1): no. of fieldinfos in array
+         *
+         *      --  SHORT2FROMMP(mp2): flRemove (CMA_FREE, CMA_INVALIDATE)
+         *
+         *      Returns the no. of FI's in the cnr or -1 on errors.
+         */
+
+        case CM_REMOVEDETAILFIELDINFO:
+            mrc = (MRESULT)CnrRemoveDetailFieldInfo(hwnd,
+                                                    (PFIELDINFO*)mp1,
+                                                    SHORT1FROMMP(mp2),
+                                                    SHORT2FROMMP(mp2));
+        break;
+
+        /*
+         * CM_FREEDETAILFIELDINFO:
+         *
+         *      Paramters:
+         *
+         *      --  PFIELDINFO* mp1: ptr to array of PFIELDINFO's
+         *      --  USHORT mp2: no. of ptrs in array
+         *
+         *      Returns BOOL.
+         */
+
+        case CM_FREEDETAILFIELDINFO:
+            mrc = (MRESULT)CnrFreeDetailFieldInfo(hwnd,
+                                                  (PFIELDINFO*)mp1,
+                                                  (USHORT)mp2);
+        break;
+
+        /* ******************************************************************
+         *
+         *   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:
+            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;
+
+        default:
+            if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+                mrc = ctlDefWindowProc(&pData->dwdMain, msg, mp1, mp2);
+        break;
+
+    }
+
+    return mrc;
+}
+
+/*
+ *@@ ctlRegisterXCnr:
+ *
+ */
+
+BOOL ctlRegisterXCnr(HAB hab)
+{
+    return (    WinRegisterClass(hab,
+                                 WC_CCTL_CNR,
+                                 fnwpCnr,
+                                 0, // CS_SYNCPAINT, // CS_CLIPSIBLINGS CS_CLIPCHILDREN
+                                 sizeof(PVOID) * 2)
+             && WinRegisterClass(hab,
+                                 WC_CCTL_CNR_DETAILS,
+                                 fnwpCnrDetails,
+                                 0, // CS_SYNCPAINT, //  | CS_PARENTCLIP, // CS_CLIPSIBLINGS CS_CLIPCHILDREN
+                                 sizeof(PVOID) * 2)
+           );
+}
+
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 242)
@@ -0,0 +1,462 @@
+
+/*
+ *@@sourcefile cctl_cnr.c:
+ *      implementation for the replacement container control.
+ *
+ *@@header "helpers\comctl.h"
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+/*
+ *      Copyright (C) 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 OS2EMX_PLAIN_CHAR
+    // this is needed for "os2emx.h"; if this is defined,
+    // emx will define PSZ as _signed_ char, otherwise
+    // as unsigned char
+
+#define INCL_WINWINDOWMGR
+#define INCL_WINFRAMEMGR
+#define INCL_WINSYS
+#define INCL_WINSTDCNR
+#define INCL_GPIPRIMITIVES
+#define INCL_GPILCIDS
+#include <os2.h>
+
+#include "setup.h"                      // code generation and debugging options
+
+#include "helpers\comctl.h"
+#include "helpers\linklist.h"
+#include "helpers\gpih.h"
+#include "helpers\nls.h"
+#include "helpers\standards.h"
+#include "helpers\stringh.h"
+#include "helpers\tree.h"
+#include "helpers\winh.h"
+
+#include "private\cnr.h"
+
+#pragma hdrstop
+
+/*
+ *@@category: Helpers\PM helpers\Window classes\Container control replacement
+ *      See cctl_cnr.c.
+ */
+
+/* ******************************************************************
+ *
+ *   Helper funcs
+ *
+ ********************************************************************/
+
+/*
+ *@@ cdtlRecalcDetails:
+ *      worker routine for refreshing all internal DETAILCOLUMN
+ *      data when important stuff has changed.
+ *
+ *      This only gets called from CnrSem2 when WM_SEM2 comes
+ *      in with sufficient update flags.
+ *
+ *      Returns the following ORed flags:
+ *
+ *      --  DDFL_WINDOWSIZECHANGED: cnr titles area changed,
+ *          so details window might need adjustment.
+ *
+ *      --  DDFL_WORKAREACHANGED: cnr workarea changed, so
+ *          scroll bars need adjustment.
+ */
+
+VOID cdtlRecalcDetails(PCNRDATA pData,
+                       HPS hps,
+                       PULONG pfl)
+{
+    LONG        xCurrent = 0;
+    PLISTNODE   pColNode;
+    ULONG       cRow = 0;
+
+    // compute total padding for one row:
+    LONG        cxPaddingRow = pData->CnrInfo.cFields * 2 * COLUMN_PADDING_X,
+                yOfsNow = 0;
+
+    LONG        cyColTitlesContent = 0,
+                cyColTitlesBox = 0;
+
+    GpiQueryFontMetrics(hps, sizeof(FONTMETRICS), &pData->fm);
+
+    // outer loop: columns (go RIGHT)
+    FOR_ALL_NODES(&pData->llColumns, pColNode)
+    {
+        PDETAILCOLUMN pCol = (PDETAILCOLUMN)pColNode->pItemData;
+        const FIELDINFO *pfi = pCol->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))
+            {
+                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);
+            }
+
+            // 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
+                    ;
+                else
+                {
+                    ULONG cLines;
+                    gpihCalcTextExtent(hps,
+                                       (PCSZ)pfi->pTitleData,
+                                       &pCol->szlTitleData.cx,
+                                       &cLines);
+                    pCol->szlTitleData.cy = cLines * (pData->fm.lMaxBaselineExt + pData->fm.lExternalLeading);
+                }
+
+                pCol->cxTotal =   max(pCol->cxWidestRecord, pCol->szlTitleData.cx);
+
+                if (pCol->szlTitleData.cy > cyColTitlesContent)
+                    cyColTitlesContent = pCol->szlTitleData.cy;
+            }
+            else
+            {
+                pCol->szlTitleData.cx
+                = 0;
+            }
+        }
+
+        // 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)
+       )
+    {
+        pData->szlWorkarea.cx = xCurrent;
+        pData->szlWorkarea.cy = yOfsNow;
+        *pfl |= DDFL_WORKAREACHANGED;
+    }
+
+    if (pData->CnrInfo.flWindowAttr & CA_DETAILSVIEWTITLES)
+        cyColTitlesBox =   cyColTitlesContent
+                         + 2 * COLUMN_PADDING_Y
+                         + pData->CnrInfo.cyLineSpacing;
+
+    if (    (cyColTitlesContent != pData->cyColTitlesContent)
+         || (cyColTitlesBox != pData->cyColTitlesBox)
+       )
+    {
+        pData->cyColTitlesContent = cyColTitlesContent;
+        pData->cyColTitlesBox = cyColTitlesBox;
+
+        *pfl |= DDFL_WINDOWSIZECHANGED | DDFL_WORKAREACHANGED;
+    }
+}
+
+/*
+ *@@ DetailsCreate:
+ *      implementation for WM_CREATE in fnwpCnrDetails.
+ *
+ *      We receive the CNRDATA as the create param.
+ */
+
+MRESULT DetailsCreate(HWND hwnd, MPARAM mp1, MPARAM mp2)
+{
+    if (!mp1)
+        return (MRESULT)TRUE;
+
+    WinSetWindowPtr(hwnd, QWL_USER + 1, mp1);
+
+    // initialize DEFWINDOWDATA
+    ctlInitDWD(hwnd,
+               mp2,
+               &((PCNRDATA)mp1)->dwdContent,
+               WinDefWindowProc,
+               &G_scsCnr);
+
+    return (MRESULT)FALSE;
+}
+
+/*
+ *@@ DetailsPaint:
+ *      implementation for WM_PAINT in fnwpCnrDetails.
+ *
+ */
+
+VOID DetailsPaint(HWND hwnd)
+{
+    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;
+        LONG    yPadding = pData->CnrInfo.cyLineSpacing / 2;
+        ULONG   cColumn = 0;
+
+        gpihSwitchToRGB(hps);
+        WinFillRect(hps,
+                    &rclPaint,
+                    pData->dwdMain.lcolBackground);
+
+        FOR_ALL_NODES(&pData->llColumns, 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)
+            {
+                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))
+                {
+                    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)
+                    cnrDrawString(hps,
+                                  pcsz,
+                                  &rcl,
+                                  pfi->flData,
+                                  &pData->fm);
+
+                ++cRow;
+
+                rcl.yTop = rcl.yBottom - yPadding;
+            }
+
+            // paint vertical separators after this column?
+            if (pfi->flData & CFA_SEPARATOR)
+            {
+                ptl.x = rcl.xRight + COLUMN_PADDING_X;
+                ptl.y = pData->dwdContent.szlWin.cy;
+                GpiMove(hps,
+                        &ptl);
+                ptl.y = 0;
+                GpiLine(hps,
+                        &ptl);
+            }
+
+            ++cColumn;
+
+        } // FOR_ALL_NODES(&pData->llColumns, pColNode)
+
+        WinEndPaint(hps);
+    }
+}
+
+/* ******************************************************************
+ *
+ *   Container details window proc
+ *
+ ********************************************************************/
+
+/*
+ *@@ fnwpCnrDetails:
+ *
+ */
+
+MRESULT EXPENTRY fnwpCnrDetails(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+    MRESULT mrc = 0;
+    PCNRDATA    pData;
+
+    switch (msg)
+    {
+        case WM_CREATE:
+            mrc = DetailsCreate(hwnd, mp1, mp2);
+        break;
+
+        case WM_PAINT:
+            DetailsPaint(hwnd);
+        break;
+
+        case WM_PRESPARAMCHANGED:
+            cnrPresParamChanged(hwnd, (ULONG)mp1);
+        break;
+
+        default:
+            if (pData = (PCNRDATA)WinQueryWindowPtr(hwnd, QWL_USER + 1))
+                mrc = ctlDefWindowProc(&pData->dwdContent, msg, mp1, mp2);
+        break;
+
+    }
+
+    return mrc;
+}
+
Index: trunk/src/helpers/cctl_toolbar.c
===================================================================
--- trunk/src/helpers/cctl_toolbar.c	(revision 241)
+++ trunk/src/helpers/cctl_toolbar.c	(revision 242)
@@ -354,7 +354,8 @@
 }
 
-static const SYSCOLORSET scsToolbarButton =
+static const SYSCOLORSET G_scsToolbarButton =
     {
         TRUE,       // inherit presparams
+
         SYSCLR_BUTTONMIDDLE,
         SYSCLR_MENUTEXT
@@ -444,5 +445,5 @@
                    &pData->bd.dwd,
                    WinDefWindowProc,
-                   &scsToolbarButton);
+                   &G_scsToolbarButton);
 
         if (    (pcszText)
@@ -850,5 +851,5 @@
  ********************************************************************/
 
-static const SYSCOLORSET scsToolbar =
+static const SYSCOLORSET G_scsToolbar =
     {
         TRUE,       // inherit presparams
@@ -1020,5 +1021,5 @@
                                      PP_BACKGROUNDCOLORINDEX,
                                      FALSE,
-                                     scsToolbar.lBackIndex);
+                                     G_scsToolbar.lBackIndex);
         winhStorePresParam(&ppp,
                            PP_BACKGROUNDCOLOR,
@@ -1030,5 +1031,5 @@
                                      PP_FOREGROUNDCOLORINDEX,
                                      FALSE,
-                                     scsToolbar.lForeIndex);
+                                     G_scsToolbar.lForeIndex);
         winhStorePresParam(&ppp,
                            PP_FOREGROUNDCOLOR,
@@ -1111,5 +1112,5 @@
                &pData->dwd,
                WinDefWindowProc,
-               &scsToolbar);
+               &G_scsToolbar);
 
     pData->hwndControlsOwner = ptbcd->hwndControlsOwner;
Index: trunk/src/helpers/cctl_xframe.c
===================================================================
--- trunk/src/helpers/cctl_xframe.c	(revision 241)
+++ trunk/src/helpers/cctl_xframe.c	(revision 242)
@@ -82,5 +82,5 @@
 
 MRESULT ctlFormatExtFrame(HWND hwndFrame,
-                          PXFRAMECONTROLS pxfc,
+                          const XFRAMECONTROLS *pxfc,
                           MPARAM mp1,
                           MPARAM mp2)
@@ -209,9 +209,12 @@
  */
 
-VOID ctlCalcExtFrameRect(MPARAM mp1,
-                         MPARAM mp2,
-                         LONG lStatusBarHeight)
+MRESULT ctlCalcExtFrameRect(HWND hwndFrame,
+                            const XFRAMECONTROLS *pxfc,
+                            MPARAM mp1,
+                            MPARAM mp2)
 {
     PRECTL prclPassed = (PRECTL)mp1;
+
+    MRESULT mrc = pxfc->pfnwpOrig(hwndFrame, WM_CALCFRAMERECT, mp1, mp2);
 
     if (mp2)
@@ -220,18 +223,10 @@
         //  call default window procedure to subtract child frame
         //  controls from the rectangle's height
-        LONG lClientHeight;
-
-        //  position the static text frame extension below the client
-        lClientHeight = prclPassed->yTop - prclPassed->yBottom;
-        if (lStatusBarHeight > lClientHeight)
-            // extension is taller than client, so set client height to 0
-            prclPassed->yTop = prclPassed->yBottom;
-        else
-        {
-            //  set the origin of the client and shrink it based upon the
-            //  static text control's height
-            prclPassed->yBottom += lStatusBarHeight;
-            prclPassed->yTop -= lStatusBarHeight;
-        }
+
+        if (pxfc->hwndToolBar)
+            prclPassed->yTop -= pxfc->lToolBarHeight;
+
+        if (pxfc->hwndStatusBar)
+            prclPassed->yBottom += pxfc->lStatusBarHeight;
     }
     else
@@ -242,7 +237,13 @@
         //  set the origin of the frame and increase it based upon the
         //  static text control's height
-        prclPassed->yBottom -= lStatusBarHeight;
-        prclPassed->yTop += lStatusBarHeight;
+
+        if (pxfc->hwndToolBar)
+            prclPassed->yTop += pxfc->lToolBarHeight;
+
+        if (pxfc->hwndStatusBar)
+            prclPassed->yBottom -= pxfc->lStatusBarHeight;
     }
+
+    return mrc;
 }
 
@@ -289,11 +290,15 @@
 
         case WM_CALCFRAMERECT:
-            mrc = pData->xfc.pfnwpOrig(hwndFrame, msg, mp1, mp2);
-
             // we have a status bar: calculate its rectangle
-            ctlCalcExtFrameRect(mp1,
-                                mp2,
-                                STATUS_BAR_HEIGHT);
+            mrc = ctlCalcExtFrameRect(hwndFrame,
+                                      &pData->xfc,
+                                      mp1,
+                                      mp2);
         break;
+
+        /*
+        case WM_WINDOWPOSCHANGED:
+            WinSendMsg(hwndFrame, WM_UPDATEFRAME, 0, 0);
+        break; */
 
         case WM_SYSCOMMAND:
Index: trunk/src/helpers/cnrh.c
===================================================================
--- trunk/src/helpers/cnrh.c	(revision 241)
+++ trunk/src/helpers/cnrh.c	(revision 242)
@@ -168,7 +168,7 @@
                       BOOL fDrawLines)           // in: if TRUE, we'll draw lines around the columns
 {
-    PFIELDINFO pInfo;
+    PFIELDINFO pfi;
     if (    (ppFieldInfo2)
-         && (pInfo = *ppFieldInfo2)
+         && (pfi = *ppFieldInfo2)
        )
     {
@@ -177,12 +177,12 @@
             flData |= CFA_HORZSEPARATOR | CFA_SEPARATOR;
 
-        pInfo->cb = sizeof(FIELDINFO);
-        pInfo->flData = flData;
-        pInfo->flTitle = CFA_FITITLEREADONLY | ulOrientation;
-        pInfo->offStruct = ulFieldOffset;
-        pInfo->pTitleData = pszColumnTitle;   // strdup removed, V0.9.1 (99-12-18) [umoeller]
-        pInfo->pUserData   = NULL;
-        pInfo->cxWidth = 0;
-        *ppFieldInfo2 = pInfo->pNextFieldInfo;
+        pfi->cb = sizeof(FIELDINFO);
+        pfi->flData = flData;
+        pfi->flTitle = CFA_FITITLEREADONLY | ulOrientation;
+        pfi->offStruct = ulFieldOffset;
+        pfi->pTitleData = pszColumnTitle;   // strdup removed, V0.9.1 (99-12-18) [umoeller]
+        pfi->pUserData   = NULL;
+        pfi->cxWidth = 0;
+        *ppFieldInfo2 = pfi->pNextFieldInfo;
     }
 }
@@ -342,7 +342,7 @@
 
         // insert field infos
-        if (cnrhInsertFieldInfos(hwndCnr,
-                                 pFieldInfoFirst,
-                                 ulFieldCount) == 0)
+        if (!cnrhInsertFieldInfos(hwndCnr,
+                                  pFieldInfoFirst,
+                                  ulFieldCount))
             pFieldInfoReturn = NULL;
     }
Index: trunk/src/helpers/comctl.c
===================================================================
--- trunk/src/helpers/comctl.c	(revision 241)
+++ trunk/src/helpers/comctl.c	(revision 242)
@@ -248,4 +248,12 @@
         case WM_ENABLE:
             WinInvalidateRect(pdwd->hwnd, NULL, TRUE);
+        break;
+
+        case WM_WINDOWPOSCHANGED:
+            if (((PSWP)mp1)->fl & SWP_SIZE)
+            {
+                pdwd->szlWin.cx = ((PSWP)mp1)->cx;
+                pdwd->szlWin.cy = ((PSWP)mp1)->cy;
+            }
         break;
 
Index: trunk/src/helpers/exeh.c
===================================================================
--- trunk/src/helpers/exeh.c	(revision 241)
+++ trunk/src/helpers/exeh.c	(revision 242)
@@ -45,4 +45,6 @@
 #define INCL_DOSDEVIOCTL
 #define INCL_DOSERRORS
+
+#define INCL_WINPROGRAMLIST
 #include <os2.h>
 
@@ -835,90 +837,317 @@
 {
     APIRET      arc = NO_ERROR;
+    PXFILE      pFile;
+    ULONG       ulNRNTOfs = 0;
 
     if (!pExec)
-        arc = ERROR_INVALID_PARAMETER;
+        return ERROR_INVALID_PARAMETER;
+
+    pFile = pExec->pFile;
+    if (pExec->ulExeFormat == EXEFORMAT_LX)
+    {
+        // OK, LX format:
+        // check if we have a non-resident name table
+        if (pExec->pLXHeader == NULL)
+            arc = ERROR_INVALID_DATA;
+        else if (pExec->pLXHeader->ulNonResdNameTblOfs == 0)
+            arc = ERROR_INVALID_DATA;
+        else
+            ulNRNTOfs = pExec->pLXHeader->ulNonResdNameTblOfs;
+    }
+    else if (pExec->ulExeFormat == EXEFORMAT_NE)
+    {
+        // OK, NE format:
+        // check if we have a non-resident name table
+        if (pExec->pNEHeader == NULL)
+            arc = ERROR_INVALID_DATA;
+        else if (pExec->pNEHeader->ulNonResdTblOfs == 0)
+            arc = ERROR_INVALID_DATA;
+        else
+            ulNRNTOfs = pExec->pNEHeader->ulNonResdTblOfs;
+    }
     else
+        // neither LX nor NE: stop
+        arc = ERROR_INVALID_EXE_SIGNATURE;
+
+    if (    (!arc)
+         && (ulNRNTOfs)
+       )
     {
-        PXFILE      pFile = pExec->pFile;
-
-        ULONG       ulNRNTOfs = 0;
-
-        if (pExec->ulExeFormat == EXEFORMAT_LX)
+        ULONG       cb = 2000;
+
+        PSZ         pszNameTable;
+
+        if (!(pszNameTable = (PSZ)malloc(2001)))
+            arc = ERROR_NOT_ENOUGH_MEMORY;
+        else
         {
-            // OK, LX format:
-            // check if we have a non-resident name table
-            if (pExec->pLXHeader == NULL)
-                arc = ERROR_INVALID_DATA;
-            else if (pExec->pLXHeader->ulNonResdNameTblOfs == 0)
-                arc = ERROR_INVALID_DATA;
-            else
-                ulNRNTOfs = pExec->pLXHeader->ulNonResdNameTblOfs;
-        }
-        else if (pExec->ulExeFormat == EXEFORMAT_NE)
-        {
-            // OK, NE format:
-            // check if we have a non-resident name table
-            if (pExec->pNEHeader == NULL)
-                arc = ERROR_INVALID_DATA;
-            else if (pExec->pNEHeader->ulNonResdTblOfs == 0)
-                arc = ERROR_INVALID_DATA;
-            else
-                ulNRNTOfs = pExec->pNEHeader->ulNonResdTblOfs;
-        }
-        else
-            // neither LX nor NE: stop
-            arc = ERROR_INVALID_EXE_SIGNATURE;
-
-        if (    (!arc)
-             && (ulNRNTOfs)
-           )
-        {
-            ULONG       cb = 2000;
-
-            PSZ         pszNameTable;
-
-            if (!(pszNameTable = (PSZ)malloc(2001)))
-                arc = ERROR_NOT_ENOUGH_MEMORY;
-            else
+            // V0.9.16 (2002-01-05) [umoeller]: rewrote the following
+
+            // read from offset of non-resident name table
+            if (!(arc = doshReadAt(pFile,           // file is still open
+                                   ulNRNTOfs,       // ofs determined above
+                                   &cb,             // 2000
+                                   pszNameTable,
+                                   0)))
             {
-                // V0.9.16 (2002-01-05) [umoeller]: rewrote the following
-
-                // read from offset of non-resident name table
-                if (!(arc = doshReadAt(pFile,           // file is still open
-                                       ulNRNTOfs,       // ofs determined above
-                                       &cb,             // 2000
-                                       pszNameTable,
-                                       0)))
-                {
-                    // the string is in Pascal format, so the
-                    // first byte has the length
-                    BYTE bLen;
-                    if (!(bLen = *pszNameTable))
-                        // length byte is null:
-                        arc = ERROR_INVALID_DATA;
+                // the string is in Pascal format, so the
+                // first byte has the length
+                BYTE bLen;
+                if (!(bLen = *pszNameTable))
+                    // length byte is null:
+                    arc = ERROR_INVALID_DATA;
+                else
+                {
+                    // now copy the string
+                    if (!(pExec->pszDescription = (PSZ)malloc(bLen + 1)))
+                        arc = ERROR_NOT_ENOUGH_MEMORY;
                     else
                     {
-                        // now copy the string
-                        if (!(pExec->pszDescription = (PSZ)malloc(bLen + 1)))
-                            arc = ERROR_NOT_ENOUGH_MEMORY;
-                        else
-                        {
-                            memcpy(pExec->pszDescription,
-                                   pszNameTable + 1,    // skip length byte
-                                   bLen);               // length byte
-                            // terminate string
-                            pExec->pszDescription[bLen] = 0;
-
-                            ParseBldLevel(pExec);
-                        }
+                        memcpy(pExec->pszDescription,
+                               pszNameTable + 1,    // skip length byte
+                               bLen);               // length byte
+                        // terminate string
+                        pExec->pszDescription[bLen] = 0;
+
+                        ParseBldLevel(pExec);
                     }
                 }
-
-                free(pszNameTable);
             }
+
+            free(pszNameTable);
         }
-    } // end if (!pExec)
+    }
 
     return arc;
+}
+
+/*
+ *@@ exehQueryProgType:
+ *      attempts to sets *pulProgType to a PROGTYPE constant.
+ *
+ *      Returns:
+ *
+ *      --  NO_ERROR
+ *
+ *      --  ERROR_INVALID_PARAMETER;
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ *@@changed V1.0.1 (2003-01-17) [umoeller]: now correctly returning PROG_PDD/VDD for NE and LX @@fixes 343
+ */
+
+APIRET exehQueryProgType(const EXECUTABLE *pExec,
+                         PROGCATEGORY *pulProgType)
+{
+    APIRET      arc = NO_ERROR;
+
+    if (!pExec)
+        return ERROR_INVALID_PARAMETER;
+
+    // now we have the PEXECUTABLE:
+    // check what we found
+    switch (pExec->ulOS)
+    {
+        case EXEOS_DOS3:
+        case EXEOS_DOS4:
+            *pulProgType = PROG_WINDOWEDVDM;
+        break;
+
+        case EXEOS_OS2:
+            switch (pExec->ulExeFormat)
+            {
+                case EXEFORMAT_LX:
+                    switch (pExec->pLXHeader->ulFlags & E32MODMASK)
+                    {
+                        case E32MODPDEV:
+                            *pulProgType = PROG_PDD;
+                        break;
+
+                        case E32MODVDEV:
+                            *pulProgType = PROG_VDD;
+                        break;
+
+                        case E32MODDLL:
+                        case E32MODPROTDLL:
+                            *pulProgType = PROG_DLL;
+                        break;
+
+                        default:
+                            // all bits clear: --> real executable
+                            switch (pExec->pLXHeader->ulFlags & E32APPMASK)
+                            {
+                                case E32PMAPI:
+                                    // _Pmpf(("  LX OS2 PM"));
+                                    *pulProgType = PROG_PM;
+                                break;
+
+                                case E32PMW:
+                                    // _Pmpf(("  LX OS2 VIO"));
+                                    *pulProgType = PROG_WINDOWABLEVIO;
+                                break;
+
+                                case E32NOPMW:
+                                    // _Pmpf(("  LX OS2 FULLSCREEN"));
+                                    *pulProgType = PROG_FULLSCREEN;
+                                break;
+
+                                default:
+                                    // _Pmpf(("  LX OS2 FULLSCREEN"));
+                                    *pulProgType = PROG_FULLSCREEN;
+                                break;
+                            }
+                        break;      // executable
+                    }
+                break;
+
+                case EXEFORMAT_NE:
+                    if (pExec->fLibrary)
+                    {
+                        // there is no flag in the NE header for whether
+                        // this is a device driver, so rely on extension
+                        // V1.0.1 (2003-01-17) [umoeller]
+                        PSZ p;
+                        if (    (p = doshGetExtension(pExec->pFile->pszFilename))
+                             && (    (!stricmp(p, "ADD"))
+                                  || (!stricmp(p, "DMD"))
+                                  || (!stricmp(p, "FLT"))
+                                  || (!stricmp(p, "IFS"))
+                                  || (!stricmp(p, "SNP"))
+                                  || (!stricmp(p, "SYS"))
+                                )
+                           )
+                            *pulProgType = PROG_PDD;
+                                    // there can be no 16-bit VDDs, so this must be a PDD
+                        else
+                            *pulProgType = PROG_DLL;
+                    }
+                    else switch (pExec->pNEHeader->usFlags & NEAPPTYP)
+                    {
+                        case NEWINCOMPAT:
+                            // _Pmpf(("  NE OS2 VIO"));
+                            *pulProgType = PROG_WINDOWABLEVIO;
+                        break;
+
+                        case NEWINAPI:
+                            // _Pmpf(("  NE OS2 PM"));
+                            *pulProgType = PROG_PM;
+                        break;
+
+                        case NENOTWINCOMPAT:
+                        default:
+                            // _Pmpf(("  NE OS2 FULLSCREEN"));
+                            *pulProgType = PROG_FULLSCREEN;
+                        break;
+                    }
+                break;
+
+                case EXEFORMAT_COM:
+                    *pulProgType = PROG_WINDOWABLEVIO;
+                break;
+
+                default:
+                    arc = ERROR_INVALID_EXE_SIGNATURE;
+            }
+        break;
+
+        case EXEOS_WIN16:
+        case EXEOS_WIN386:
+            // _Pmpf(("  WIN16"));
+            *pulProgType = PROG_31_ENHSEAMLESSCOMMON;
+        break;
+
+        case EXEOS_WIN32_GUI:
+        case EXEOS_WIN32_CLI:
+            // _Pmpf(("  WIN32"));
+            *pulProgType = PROG_WIN32;
+        break;
+
+        default:
+            arc = ERROR_INVALID_EXE_SIGNATURE;
+    }
+
+    return arc;
+}
+
+/*
+ *@@ PROGTYPESTRING:
+ *
+ *@@added V0.9.16 (2002-01-13) [umoeller]
+ */
+
+typedef struct _PROGTYPESTRING
+{
+    PROGCATEGORY    progc;
+    PCSZ            pcsz;
+} PROGTYPESTRING, *PPROGTYPESTRING;
+
+PROGTYPESTRING G_aProgTypes[] =
+    {
+        PROG_DEFAULT, "PROG_DEFAULT",
+        PROG_FULLSCREEN, "PROG_FULLSCREEN",
+        PROG_WINDOWABLEVIO, "PROG_WINDOWABLEVIO",
+        PROG_PM, "PROG_PM",
+        PROG_GROUP, "PROG_GROUP",
+        PROG_VDM, "PROG_VDM",
+            // same as PROG_REAL, "PROG_REAL",
+        PROG_WINDOWEDVDM, "PROG_WINDOWEDVDM",
+        PROG_DLL, "PROG_DLL",
+        PROG_PDD, "PROG_PDD",
+        PROG_VDD, "PROG_VDD",
+        PROG_WINDOW_REAL, "PROG_WINDOW_REAL",
+        PROG_30_STD, "PROG_30_STD",
+            // same as PROG_WINDOW_PROT, "PROG_WINDOW_PROT",
+        PROG_WINDOW_AUTO, "PROG_WINDOW_AUTO",
+        PROG_30_STDSEAMLESSVDM, "PROG_30_STDSEAMLESSVDM",
+            // same as PROG_SEAMLESSVDM, "PROG_SEAMLESSVDM",
+        PROG_30_STDSEAMLESSCOMMON, "PROG_30_STDSEAMLESSCOMMON",
+            // same as PROG_SEAMLESSCOMMON, "PROG_SEAMLESSCOMMON",
+        PROG_31_STDSEAMLESSVDM, "PROG_31_STDSEAMLESSVDM",
+        PROG_31_STDSEAMLESSCOMMON, "PROG_31_STDSEAMLESSCOMMON",
+        PROG_31_ENHSEAMLESSVDM, "PROG_31_ENHSEAMLESSVDM",
+        PROG_31_ENHSEAMLESSCOMMON, "PROG_31_ENHSEAMLESSCOMMON",
+        PROG_31_ENH, "PROG_31_ENH",
+        PROG_31_STD, "PROG_31_STD",
+
+// Warp 4 toolkit defines, whatever these were designed for...
+#ifndef PROG_DOS_GAME
+    #define PROG_DOS_GAME            (PROGCATEGORY)21
+#endif
+#ifndef PROG_WIN_GAME
+    #define PROG_WIN_GAME            (PROGCATEGORY)22
+#endif
+#ifndef PROG_DOS_MODE
+    #define PROG_DOS_MODE            (PROGCATEGORY)23
+#endif
+
+        PROG_DOS_GAME, "PROG_DOS_GAME",
+        PROG_WIN_GAME, "PROG_WIN_GAME",
+        PROG_DOS_MODE, "PROG_DOS_MODE",
+
+        // added this V0.9.16 (2001-12-08) [umoeller]
+        PROG_WIN32, "PROG_WIN32"
+    };
+
+/*
+ *@@ exehDescribeProgType:
+ *      returns a "PROG_*" string for the given
+ *      program type. Useful for WPProgram setup
+ *      strings and such.
+ *
+ *@@added V0.9.16 (2001-10-06)
+ *@@changed V1.0.1 (2003-01-17) [umoeller]: moved this here from apps.c
+ */
+
+PCSZ exehDescribeProgType(PROGCATEGORY progc)        // in: from PROGDETAILS.progc
+{
+    ULONG ul;
+    for (ul = 0;
+         ul < ARRAYITEMCOUNT(G_aProgTypes);
+         ul++)
+    {
+        if (G_aProgTypes[ul].progc == progc)
+            return G_aProgTypes[ul].pcsz;
+    }
+
+    return NULL;
 }
 
Index: trunk/src/helpers/gpih.c
===================================================================
--- trunk/src/helpers/gpih.c	(revision 241)
+++ trunk/src/helpers/gpih.c	(revision 242)
@@ -61,6 +61,7 @@
 #endif
 #include "helpers\dosh.h"
+#include "helpers\gpih.h"
 #include "helpers\winh.h"
-#include "helpers\gpih.h"
+#include "helpers\stringh.h"
 
 #pragma hdrstop
@@ -459,7 +460,7 @@
  */
 
-VOID gpihDrawThickFrame(HPS hps,              // in: presentation space for output
-                        PRECTL prcl,          // in: rectangle to draw (inclusive)
-                        ULONG ulWidth)       // in: line width (>= 1)
+VOID gpihDrawThickFrame(HPS hps,            // in: presentation space for output
+                        PRECTL prcl,        // in: rectangle to draw (inclusive)
+                        ULONG ulWidth)      // in: line width (>= 1)
 {
     ULONG ul = 0;
@@ -506,9 +507,9 @@
  */
 
-VOID gpihDraw3DFrame2(HPS hps,
-                      PRECTL prcl,       // in: rectangle (inclusive)
-                      USHORT usWidth,    // in: line width (>= 1)
-                      LONG lColorLeft,   // in: color to use for left and top; e.g. SYSCLR_BUTTONLIGHT
-                      LONG lColorRight)  // in: color to use for right and bottom; e.g. SYSCLR_BUTTONDARK
+VOID gpihDraw3DFrame2(HPS hps,              // in: presentation space for output
+                      PRECTL prcl,          // in: rectangle (inclusive)
+                      USHORT usWidth,       // in: line width (>= 1)
+                      LONG lColorLeft,      // in: color to use for left and top; e.g. SYSCLR_BUTTONLIGHT
+                      LONG lColorRight)     // in: color to use for right and bottom; e.g. SYSCLR_BUTTONDARK
 {
     USHORT us;
@@ -553,5 +554,5 @@
  */
 
-VOID gpihDraw3DFrame(HPS hps,
+VOID gpihDraw3DFrame(HPS hps,           // in: presentation space for output
                      PRECTL prcl,       // in: rectangle (inclusive)
                      USHORT usWidth,    // in: line width (>= 1)
@@ -576,5 +577,5 @@
  */
 
-LONG gpihCharStringPosAt(HPS hps,
+LONG gpihCharStringPosAt(HPS hps,               // in: presentation space for output
                          PPOINTL pptlStart,
                          PRECTL prclRect,
@@ -628,4 +629,171 @@
 
 /*
+ *@@ gpihCalcTextExtent:
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+VOID gpihCalcTextExtent(HPS hps,            // in: presentation space for output
+                        PCSZ pcsz,          // in: string to test
+                        PLONG pcx,          // out: max width occupied by a line in the string
+                        PULONG pcLines)     // out: no. of lines
+{
+    LONG    cxLineThis;
+    PCSZ    pThis = pcsz;
+    *pcx = 0;
+
+    *pcLines = 0;
+
+    if (!pThis)
+        return;
+
+    while (*pThis)
+    {
+        ULONG lenThis;
+        PCSZ pNext = strhFindEOL(pThis, &lenThis);
+
+        ++(*pcLines);
+
+        if (lenThis)
+        {
+            POINTL aptl[TXTBOX_COUNT];
+            GpiQueryTextBox(hps,
+                            lenThis,
+                            (PCH)pThis,
+                            TXTBOX_COUNT,
+                            aptl);
+
+            cxLineThis = aptl[TXTBOX_TOPRIGHT].x - aptl[TXTBOX_BOTTOMLEFT].x;
+
+            if (cxLineThis > *pcx)
+                *pcx = cxLineThis;
+        }
+
+        if (*pNext == '\r')
+            pNext++;
+        pThis = pNext;
+    }
+}
+
+/*
+ *@@ gpihDrawString:
+ *      replacement for WinDrawText that can still align
+ *      properly with multi-line strings.
+ *
+ *      fl works as with WinDrawText, that is:
+ *
+ *      --  specify one of DT_LEFT, DT_CENTER, DT_RIGHT;
+ *
+ *      --  specifiy one of DT_TOP, DT_VCENTER, DT_BOTTOM.
+ *
+ *      The alignment definitions are:
+ *
+ *      --  DT_LEFT                    0x00000000
+ *      --  DT_CENTER                  0x00000100
+ *      --  DT_RIGHT                   0x00000200
+ *      --  DT_TOP                     0x00000000
+ *      --  DT_VCENTER                 0x00000400
+ *      --  DT_BOTTOM                  0x00000800
+ *
+ *      Other flags:
+ *
+ *      --  DT_QUERYEXTENT             0x00000002   (not supported)
+ *      --  DT_UNDERSCORE              0x00000010   (not supported)
+ *      --  DT_STRIKEOUT               0x00000020   (not supported)
+ *      --  DT_TEXTATTRS               0x00000040   (always enabled)
+ *      --  DT_EXTERNALLEADING         0x00000080   (not supported)
+ *      --  DT_HALFTONE                0x00001000   (not supported)
+ *      --  DT_MNEMONIC                0x00002000   (not supported)
+ *      --  DT_WORDBREAK               0x00004000   (always enabled)
+ *      --  DT_ERASERECT               0x00008000   (not supported)
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+VOID gpihDrawString(HPS hps,                // in: presentation space for output
+                    PCSZ pcsz,              // in: string to test
+                    PRECTL prcl,            // in: clipping rectangle (inclusive!)
+                    ULONG fl,               // in: alignment flags
+                    PFONTMETRICS pfm)
+{
+    PCSZ    pThis = pcsz;
+    POINTL  ptlRun,
+            ptlUse;
+    LONG    cxRect,
+            cyRect,
+            cyString;
+
+    if (!pThis || !prcl || !pfm)
+        return;
+
+    ptlRun.x = prcl->xLeft;
+    ptlRun.y = prcl->yTop;
+
+    cxRect = prcl->xRight - prcl->xLeft + 1;
+    cyRect = prcl->yTop - prcl->yBottom + 1;
+
+    // vertical alignment:
+    if (fl & (DT_VCENTER | DT_BOTTOM))
+    {
+        ULONG cLines = strhCount(pcsz, '\n') + 1;
+        cyString = cLines * (pfm->lMaxBaselineExt + pfm->lExternalLeading);
+
+        if (fl & DT_VCENTER)
+            ptlRun.y += (cyRect - cyString) / 2;
+        else
+            ptlRun.y += cyRect - cyString;
+    }
+
+    while (*pThis)
+    {
+        ULONG lenThis;
+        PCSZ pNext = strhFindEOL(pThis, &lenThis);
+
+        ptlRun.y -= pfm->lMaxBaselineExt;
+
+        if (lenThis)
+        {
+            // horizontal alignment:
+            if (!(fl & (DT_CENTER | DT_RIGHT)))
+                ptlUse.x = ptlRun.x;
+            else
+            {
+                POINTL  aptl[TXTBOX_COUNT];
+                LONG    cxString;
+                GpiQueryTextBox(hps,
+                                lenThis,
+                                (PCH)pThis,
+                                TXTBOX_COUNT,
+                                aptl);
+
+                cxString = aptl[TXTBOX_TOPRIGHT].x - aptl[TXTBOX_BOTTOMLEFT].x;
+
+                if (fl & DT_CENTER)
+                    ptlUse.x = ptlRun.x + (cxRect - cxString) / 2;
+                else
+                    // right
+                    ptlUse.x = ptlRun.x + cxRect - cxString;
+            }
+
+            ptlUse.y = ptlRun.y + pfm->lMaxDescender;
+
+            GpiCharStringPosAt(hps,
+                               &ptlUse,
+                               prcl,
+                               CHS_CLIP,
+                               lenThis,
+                               (PCH)pThis,
+                               NULL);
+        }
+
+        ptlRun.y -= pfm->lExternalLeading;
+
+        if (*pNext == '\r')
+            pNext++;
+        pThis = pNext;
+    }
+}
+
+/*
  *@@ gpihFillBackground:
  *      fills the specified rectangle in the way
@@ -647,5 +815,5 @@
  */
 
-VOID gpihFillBackground(HPS hps,            // in: PS to paint into
+VOID gpihFillBackground(HPS hps,            // in: presentation space for output
                         PRECTL prcl,        // in: rectangle (inclusive!)
                         PBKGNDINFO pInfo)   // in: background into
@@ -818,16 +986,16 @@
  */
 
-BOOL gpihMatchFont(HPS hps,
-                   LONG lSize,            // in: font point size
-                   BOOL fFamily,          // in: if TRUE, pszName specifies font family;
-                                          //     if FALSE, pszName specifies font face
-                   const char *pcszName,  // in: font family or face name (without point size)
-                   USHORT usFormat,       // in: none, one or several of:
-                                          // -- FATTR_SEL_ITALIC
-                                          // -- FATTR_SEL_UNDERSCORE (underline)
-                                          // -- FATTR_SEL_BOLD
-                                          // -- FATTR_SEL_STRIKEOUT
-                                          // -- FATTR_SEL_OUTLINE (hollow)
-                   FATTRS *pfa,           // out: font attributes if found
+BOOL gpihMatchFont(HPS hps,                 // in: presentation space for output
+                   LONG lSize,              // in: font point size
+                   BOOL fFamily,            // in: if TRUE, pszName specifies font family;
+                                            //     if FALSE, pszName specifies font face
+                   const char *pcszName,    // in: font family or face name (without point size)
+                   USHORT usFormat,         // in: none, one or several of:
+                                            // -- FATTR_SEL_ITALIC
+                                            // -- FATTR_SEL_UNDERSCORE (underline)
+                                            // -- FATTR_SEL_BOLD
+                                            // -- FATTR_SEL_STRIKEOUT
+                                            // -- FATTR_SEL_OUTLINE (hollow)
+                   FATTRS *pfa,             // out: font attributes if found
                    PFONTMETRICS pFontMetrics) // out: font metrics of created font (optional)
 {
@@ -1130,5 +1298,5 @@
  */
 
-LONG gpihQueryNextFontID(HPS hps)
+LONG gpihQueryNextFontID(HPS hps)       // in: presentation space for output
 {
     LONG    lcidNext = -1;
@@ -1228,5 +1396,5 @@
  */
 
-LONG gpihCreateFont(HPS hps,
+LONG gpihCreateFont(HPS hps,            // in: presentation space for output
                     FATTRS *pfa)
 {
@@ -1404,5 +1572,5 @@
  */
 
-LONG gpihFindFont(HPS hps,               // in: HPS for font selection
+LONG gpihFindFont(HPS hps,               // in: presentation space for output
                   LONG lSize,            // in: font point size
                   BOOL fFamily,          // in: if TRUE, pszName specifies font family;
@@ -1595,5 +1763,5 @@
  */
 
-LONG gpihQueryLineSpacing(HPS hps)
+LONG gpihQueryLineSpacing(HPS hps)      // in: presentation space for output
 {
     FONTMETRICS fm;
Index: trunk/src/helpers/helpers_pre.in
===================================================================
--- trunk/src/helpers/helpers_pre.in	(revision 241)
+++ trunk/src/helpers/helpers_pre.in	(revision 242)
@@ -86,4 +86,6 @@
 $(OUTPUTDIR)\cctl_chart.obj\
 $(OUTPUTDIR)\cctl_checkcnr.obj\
+$(OUTPUTDIR)\cctl_cnr.obj\
+$(OUTPUTDIR)\cctl_cnr_dtls.obj\
 $(OUTPUTDIR)\cctl_combo.obj\
 $(OUTPUTDIR)\cctl_progbar.obj\
Index: trunk/src/helpers/linklist.c
===================================================================
--- trunk/src/helpers/linklist.c	(revision 241)
+++ trunk/src/helpers/linklist.c	(revision 242)
@@ -675,4 +675,58 @@
 
 /*
+ *@@ InsertFront:
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+VOID InsertFront(PLINKLIST pList,
+                 PLISTNODE pNewNode)
+{
+    if (pList->pFirst)
+        pList->pFirst->pPrevious = pNewNode;
+
+    pNewNode->pNext = pList->pFirst;
+    pNewNode->pPrevious = NULL;
+
+    pList->pFirst = pNewNode;
+
+    if (!pList->pLast)
+        // the list was empty:
+        pList->pLast = pNewNode;        // V0.9.14 (2001-07-14) [umoeller]
+
+    (pList->ulCount)++;
+}
+
+/*
+ *@@ InsertAfterNode:
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+VOID InsertAfterNode(PLINKLIST pList,
+                     PLISTNODE pNewNode,
+                     PLISTNODE pNodeInsertAfter)
+{
+    // 1) set pointers for new node
+    pNewNode->pPrevious = pNodeInsertAfter;
+    pNewNode->pNext = pNodeInsertAfter->pNext;
+
+    // 2) adjust next item
+    // so that it points to the new node
+    if (pNodeInsertAfter->pNext)
+        pNodeInsertAfter->pNext->pPrevious = pNewNode;
+
+    // 3) adjust previous item
+    // so that it points to the new node
+    pNodeInsertAfter->pNext = pNewNode;
+
+    // 4) adjust last item, if necessary
+    if (pList->pLast == pNodeInsertAfter)
+        pList->pLast = pNewNode;
+
+    (pList->ulCount)++;
+}
+
+/*
  *@@ lstInsertItemBefore:
  *      this inserts a new node to the list. As opposed to
@@ -699,6 +753,6 @@
 
 PLISTNODE lstInsertItemBefore(PLINKLIST pList,
-                              void* pNewItemData,     // data to store in list node
-                              unsigned long ulIndex)
+                              void* pNewItemData,       // in: data to store in list node
+                              unsigned long ulIndex)    // in: index before which to insert
 {
     PLISTNODE pNewNode = NULL;
@@ -715,17 +769,6 @@
         {
             // insert at beginning:
-            if (pList->pFirst)
-                pList->pFirst->pPrevious = pNewNode;
-
-            pNewNode->pNext = pList->pFirst;
-            pNewNode->pPrevious = NULL;
-
-            pList->pFirst = pNewNode;
-
-            if (!pList->pLast)
-                // the list was empty:
-                pList->pLast = pNewNode;        // V0.9.14 (2001-07-14) [umoeller]
-
-            (pList->ulCount)++;
+            InsertFront(pList,
+                        pNewNode);
         }
         else
@@ -735,24 +778,9 @@
 
             if (pNodeInsertAfter = lstNodeFromIndex(pList,
-                                                    (ulIndex - 1)))
+                                                    ulIndex - 1))
             {
-                // 1) set pointers for new node
-                pNewNode->pPrevious = pNodeInsertAfter;
-                pNewNode->pNext = pNodeInsertAfter->pNext;
-
-                // 2) adjust next item
-                // so that it points to the new node
-                if (pNodeInsertAfter->pNext)
-                    pNodeInsertAfter->pNext->pPrevious = pNewNode;
-
-                // 3) adjust previous item
-                // so that it points to the new node
-                pNodeInsertAfter->pNext = pNewNode;
-
-                // 4) adjust last item, if necessary
-                if (pList->pLast == pNodeInsertAfter)
-                    pList->pLast = pNewNode;
-
-                (pList->ulCount)++;
+                InsertAfterNode(pList,
+                                pNewNode,
+                                pNodeInsertAfter);
             }
             else
@@ -763,4 +791,50 @@
             }
         }
+    }
+
+    return pNewNode;
+}
+
+/*
+ *@@ lstInsertItemAfterNode:
+ *      this inserts a new node to the list. As opposed to
+ *      lstAppendItem, the new node can be appended anywhere
+ *      in the list, that is, it will be appended AFTER
+ *      the given existing list node.
+ *
+ *      If pNodeAfter is NULL, the new item will be made the
+ *      first item.
+ *
+ *      As opposed to lstInsertItemBefore, this does not
+ *      need to traverse the list, so it is very quick.
+ *
+ *      This returns the LISTNODE of the new list item,
+ *      or NULL upon errors.
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+PLISTNODE lstInsertItemAfterNode(PLINKLIST pList,
+                                 void* pNewItemData,            // in: data to store in list node
+                                 PLISTNODE pNodeInsertAfter)    // in: node to insert after or NULL to make new node the first
+{
+    PLISTNODE pNewNode = NULL;
+
+    if (    (pList)
+         && (pList->ulMagic == LINKLISTMAGIC)
+         && (pNewNode = (PLISTNODE)malloc(sizeof(LISTNODE)))
+       )
+    {
+        memset(pNewNode, 0, sizeof(LISTNODE));
+        pNewNode->pItemData = pNewItemData;
+
+        if (!pNodeInsertAfter)
+            // insert at beginning:
+            InsertFront(pList,
+                        pNewNode);
+        else
+            InsertAfterNode(pList,
+                            pNewNode,
+                            pNodeInsertAfter);
     }
 
Index: trunk/src/helpers/makefile
===================================================================
--- trunk/src/helpers/makefile	(revision 241)
+++ trunk/src/helpers/makefile	(revision 242)
@@ -234,5 +234,8 @@
     $(TESTCASE_DIR)\datetime.obj \
     $(TESTCASE_DIR)\tree.obj \
-    $(TESTCASE_DIR)\gpih.obj
+    $(TESTCASE_DIR)\gpih.obj \
+    $(TESTCASE_DIR)\prfh.obj \
+    $(TESTCASE_DIR)\nls.obj \
+    $(TESTCASE_DIR)\nlscache.obj
 
 $(TESTCASE_DIR)\dialog.obj: ..\..\include\helpers\dialog.h
@@ -264,7 +267,10 @@
 FDLG_TEST_OBJS = \
     $(TESTCASE_DIR)\comctl.obj \
+    $(TESTCASE_DIR)\cctl_cnr.obj \
+    $(TESTCASE_DIR)\cctl_cnr_dtls.obj \
     $(TESTCASE_DIR)\cctl_toolbar.obj \
     $(TESTCASE_DIR)\cctl_tooltip.obj \
     $(TESTCASE_DIR)\cctl_xframe.obj \
+    $(TESTCASE_DIR)\cnrh.obj \
     $(TESTCASE_DIR)\dosh.obj \
     $(TESTCASE_DIR)\gpih.obj \
@@ -273,4 +279,8 @@
     $(TESTCASE_DIR)\stringh.obj \
     $(TESTCASE_DIR)\xstring.obj \
+    $(TESTCASE_DIR)\prfh.obj \
+    $(TESTCASE_DIR)\tree.obj \
+    $(TESTCASE_DIR)\nls.obj \
+    $(TESTCASE_DIR)\nlscache.obj \
     $(TESTCASE_DIR)\_call_filedlg.obj
 
Index: trunk/src/helpers/nls.c
===================================================================
--- trunk/src/helpers/nls.c	(revision 241)
+++ trunk/src/helpers/nls.c	(revision 242)
@@ -349,4 +349,27 @@
 
 /*
+ *@@ nlsGetAMPM:
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+VOID nlsGetAMPM(PCOUNTRYAMPM pampm)
+{
+    PrfQueryProfileString(HINI_USER,
+                          "PM_National",
+                          "s2359",        // key
+                          "PM",           // default
+                          pampm->sz2359,
+                          sizeof(pampm->sz2359));
+
+    PrfQueryProfileString(HINI_USER,
+                          "PM_National",
+                          "s1159",        // key
+                          "AM",           // default
+                          pampm->sz1159,
+                          sizeof(pampm->sz1159));
+}
+
+/*
  *@@ nlsQueryCountrySettings:
  *      this returns the most frequently used country settings
@@ -369,8 +392,9 @@
  */
 
-VOID nlsQueryCountrySettings(PCOUNTRYSETTINGS pcs)
-{
-    if (pcs)
-    {
+VOID nlsQueryCountrySettings(PCOUNTRYSETTINGS2 pcs2)
+{
+    if (pcs2)
+    {
+        PCOUNTRYSETTINGS pcs = &pcs2->cs;
         pcs->ulDateFormat = PrfQueryProfileInt(HINI_USER,
                                                (PSZ)PMINIAPP_NATIONAL,
@@ -397,4 +421,6 @@
                                                "sThousand",
                                                ',');
+
+        nlsGetAMPM(&pcs2->ampm);
     }
 }
@@ -424,21 +450,23 @@
                       CHAR cThousands)     // in: separator char (e.g. '.')
 {
-    USHORT ust, uss, usc;
-    CHAR   szTemp[40];
-    usc = sprintf(szTemp, "%lu", ul);  // V0.9.20 (2002-07-03) [umoeller]
-
-    ust = 0;
-    // usc = strlen(szTemp);
-    for (uss = 0; uss < usc; uss++)
+    CHAR    szTemp[30];
+    USHORT  ust = 0,
+            uss,
+            usLen = sprintf(szTemp, "%lu", ul);  // V0.9.20 (2002-07-03) [umoeller]
+
+    for (uss = 0;
+         uss < usLen;
+         ++uss)
     {
         if (uss)
-            if (((usc - uss) % 3) == 0)
+            if (0 == ((usLen - uss) % 3))
             {
                 pszTarget[ust] = cThousands;
                 ust++;
             }
-        pszTarget[ust] = szTemp[uss];
-        ust++;
-    }
+
+        pszTarget[ust++] = szTemp[uss];
+    }
+
     pszTarget[ust] = '\0';
 
@@ -473,14 +501,13 @@
                        CHAR cThousands)
 {
-    USHORT ust, uss, usc;
+    USHORT ust, uss, usLen;
     CHAR   szTemp[40];
-    usc = sprintf(szTemp, "%.0f", floor(dbl)); // V0.9.20 (2002-07-03) [umoeller]
+    usLen = sprintf(szTemp, "%.0f", floor(dbl)); // V0.9.20 (2002-07-03) [umoeller]
 
     ust = 0;
-    // usc = strlen(szTemp);
-    for (uss = 0; uss < usc; uss++)
+    for (uss = 0; uss < usLen; uss++)
     {
         if (uss)
-            if (((usc - uss) % 3) == 0)
+            if (((usLen - uss) % 3) == 0)
             {
                 pszTarget[ust] = cThousands;
@@ -547,22 +574,16 @@
  *
  *@@changed V0.9.0 (99-11-07) [umoeller]: now calling nlsDateTime
- */
-
-VOID nlsFileDate(PSZ pszBuf,           // out: string returned
-                 FDATE *pfDate,        // in: date information
-                 ULONG ulDateFormat,   // in: date format (0-3)
-                 CHAR cDateSep)        // in: date separator (e.g. '.')
-{
-    DATETIME dt;
-    dt.day = pfDate->day;
-    dt.month = pfDate->month;
-    dt.year = pfDate->year + 1980;
-
-    nlsDateTime(pszBuf,
-                NULL,          // no time
-                &dt,
-                ulDateFormat,
-                cDateSep,
-                0, 0);         // no time
+ *@@changed V1.0.1 (2003-01-17) [umoeller]: prototype changed for optimization, this was not exported
+ */
+
+VOID nlsFileDate(PSZ pszBuf,            // out: string returned
+                 const FDATE *pfDate,   // in: date information
+                 const COUNTRYSETTINGS2 *pcs)
+{
+    nlsDate(pcs,
+            pszBuf,
+            pfDate->year + 1980,
+            pfDate->month,
+            pfDate->day);
 }
 
@@ -590,22 +611,123 @@
  *@@changed V0.8.5 (99-03-15) [umoeller]: fixed 12-hour crash
  *@@changed V0.9.0 (99-11-07) [umoeller]: now calling nlsDateTime
+ *@@changed V1.0.1 (2003-01-17) [umoeller]: prototype changed for optimization, this was not exported
  */
 
 VOID nlsFileTime(PSZ pszBuf,           // out: string returned
-                 FTIME *pfTime,        // in: time information
-                 ULONG ulTimeFormat,   // in: 24-hour time format (0 or 1)
-                 CHAR cTimeSep)        // in: time separator (e.g. ':')
-{
-    DATETIME dt;
-    dt.hours = pfTime->hours;
-    dt.minutes = pfTime->minutes;
-    dt.seconds = pfTime->twosecs * 2;
-
-    nlsDateTime(NULL,          // no date
-                 pszBuf,
-                 &dt,
-                 0, 0,          // no date
-                 ulTimeFormat,
-                 cTimeSep);
+                 const FTIME *pfTime,        // in: time information
+                 const COUNTRYSETTINGS2 *pcs)
+{
+    nlsTime(pcs,
+            pszBuf,
+            pfTime->hours,
+            pfTime->minutes,
+            pfTime->twosecs * 2);
+}
+
+/*
+ *@@ nlsDate:
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+VOID nlsDate(const COUNTRYSETTINGS2 *pcs2,
+             PSZ pszDate,                   // out: date string returned
+             USHORT year,
+             BYTE month,
+             BYTE day)
+{
+    switch (pcs2->cs.ulDateFormat)
+    {
+        case 0:  // mm.dd.yyyy  (English)
+            sprintf(pszDate, "%02d%c%02d%c%04d",
+                    month,
+                        pcs2->cs.cDateSep,
+                    day,
+                        pcs2->cs.cDateSep,
+                    year);
+        break;
+
+        case 1:  // dd.mm.yyyy  (e.g. German)
+            sprintf(pszDate, "%02d%c%02d%c%04d",
+                    day,
+                        pcs2->cs.cDateSep,
+                    month,
+                        pcs2->cs.cDateSep,
+                    year);
+        break;
+
+        case 2: // yyyy.mm.dd  (Japanese)
+            sprintf(pszDate, "%04d%c%02d%c%02d",
+                    year,
+                        pcs2->cs.cDateSep,
+                    month,
+                        pcs2->cs.cDateSep,
+                    day);
+        break;
+
+        default: // yyyy.dd.mm
+            sprintf(pszDate, "%04d%c%02d%c%02d",
+                    year,
+                        pcs2->cs.cDateSep,
+                    day,
+                        pcs2->cs.cDateSep,
+                    month);
+        break;
+    }
+}
+
+/*
+ *@@ nlsTime:
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+VOID nlsTime(const COUNTRYSETTINGS2 *pcs2,
+             PSZ pszTime,                   // out: time string returned
+             BYTE hours,
+             BYTE minutes,
+             BYTE seconds)
+{
+    if (!pcs2->cs.ulTimeFormat)
+    {
+        // for 12-hour clock, we need additional INI data
+        if (hours >= 12)  // V0.9.16 (2001-12-05) [pr] if (hours > 12)
+        {
+            // yeah cool Paul, now we get 00:20 PM if it's 20 past noon
+            // V0.9.18 (2002-02-13) [umoeller]
+            ULONG ulHours;
+            if (!(ulHours = hours % 12))
+                ulHours = 12;
+
+            // >= 12h: PM.
+            sprintf(pszTime, "%02d%c%02d%c%02d %s",
+                    // leave 12 == 12 (not 0)
+                    ulHours,
+                        pcs2->cs.cTimeSep,
+                    minutes,
+                        pcs2->cs.cTimeSep,
+                    seconds,
+                    pcs2->ampm.sz2359);
+        }
+        else
+        {
+            // < 12h: AM
+            sprintf(pszTime, "%02d%c%02d%c%02d %s",
+                    hours,
+                        pcs2->cs.cTimeSep,
+                    minutes,
+                        pcs2->cs.cTimeSep,
+                    seconds,
+                    pcs2->ampm.sz1159);
+        }
+    }
+    else
+        // 24-hour clock
+        sprintf(pszTime, "%02d%c%02d%c%02d",
+                hours,
+                    pcs2->cs.cTimeSep,
+                minutes,
+                    pcs2->cs.cTimeSep,
+                seconds);
 }
 
@@ -616,12 +738,16 @@
  *      for more detailed parameter descriptions.
  *
+ *      Use of this function is deprecated. Use the
+ *      speedier nlsDateTime2 instead.
+ *
  *@@added V0.9.0 (99-11-07) [umoeller]
  *@@changed V0.9.16 (2001-12-05) [pr]: fixed AM/PM hour bug
  *@@changed V0.9.18 (2002-02-13) [umoeller]: fixed AM/PM hour bug fix
+ *@@changed V1.0.1 (2003-01-17) [umoeller]: extracted nlsDate, nlsTime
  */
 
 VOID nlsDateTime(PSZ pszDate,          // out: date string returned (can be NULL)
                  PSZ pszTime,          // out: time string returned (can be NULL)
-                 DATETIME *pDateTime,  // in: date/time information
+                 const DATETIME *pDateTime,  // in: date/time information
                  ULONG ulDateFormat,   // in: date format (0-3); see nlsFileDate
                  CHAR cDateSep,        // in: date separator (e.g. '.')
@@ -629,101 +755,26 @@
                  CHAR cTimeSep)        // in: time separator (e.g. ':')
 {
+    COUNTRYSETTINGS2 cs2;
     if (pszDate)
     {
-        switch (ulDateFormat)
-        {
-            case 0:  // mm.dd.yyyy  (English)
-                sprintf(pszDate, "%02d%c%02d%c%04d",
-                        pDateTime->month,
-                            cDateSep,
-                        pDateTime->day,
-                            cDateSep,
-                        pDateTime->year);
-            break;
-
-            case 1:  // dd.mm.yyyy  (e.g. German)
-                sprintf(pszDate, "%02d%c%02d%c%04d",
-                        pDateTime->day,
-                            cDateSep,
-                        pDateTime->month,
-                            cDateSep,
-                        pDateTime->year);
-            break;
-
-            case 2: // yyyy.mm.dd  (Japanese)
-                sprintf(pszDate, "%04d%c%02d%c%02d",
-                        pDateTime->year,
-                            cDateSep,
-                        pDateTime->month,
-                            cDateSep,
-                        pDateTime->day);
-            break;
-
-            default: // yyyy.dd.mm
-                sprintf(pszDate, "%04d%c%02d%c%02d",
-                        pDateTime->year,
-                            cDateSep,
-                        pDateTime->day,
-                            cDateSep,
-                        pDateTime->month);
-            break;
-        }
+        cs2.cs.ulDateFormat = ulDateFormat;
+        cs2.cs.cDateSep = cDateSep;
+        nlsDate(&cs2,
+                pszDate,                   // out: date string returned
+                pDateTime->year,
+                pDateTime->month,
+                pDateTime->day);
     }
 
     if (pszTime)
     {
-        if (ulTimeFormat == 0)
-        {
-            // for 12-hour clock, we need additional INI data
-            CHAR szAMPM[10] = "err";
-
-            if (pDateTime->hours >= 12)  // V0.9.16 (2001-12-05) [pr] if (pDateTime->hours > 12)
-            {
-                // yeah cool Paul, now we get 00:20 PM if it's 20 past noon
-                // V0.9.18 (2002-02-13) [umoeller]
-                ULONG ulHours;
-                if (!(ulHours = pDateTime->hours % 12))
-                    ulHours = 12;
-
-                // >= 12h: PM.
-                PrfQueryProfileString(HINI_USER,
-                                      "PM_National",
-                                      "s2359",        // key
-                                      "PM",           // default
-                                      szAMPM, sizeof(szAMPM)-1);
-                sprintf(pszTime, "%02d%c%02d%c%02d %s",
-                        // leave 12 == 12 (not 0)
-                        ulHours,
-                            cTimeSep,
-                        pDateTime->minutes,
-                            cTimeSep,
-                        pDateTime->seconds,
-                        szAMPM);
-            }
-            else
-            {
-                // < 12h: AM
-                PrfQueryProfileString(HINI_USER,
-                                      "PM_National",
-                                      "s1159",        // key
-                                      "AM",           // default
-                                      szAMPM, sizeof(szAMPM)-1);
-                sprintf(pszTime, "%02d%c%02d%c%02d %s",
-                        pDateTime->hours,
-                            cTimeSep,
-                        pDateTime->minutes,
-                            cTimeSep,
-                        pDateTime->seconds,
-                        szAMPM);
-            }
-        }
-        else
-            // 24-hour clock
-            sprintf(pszTime, "%02d%c%02d%c%02d",
-                    pDateTime->hours,
-                        cTimeSep,
-                    pDateTime->minutes,
-                        cTimeSep,
-                    pDateTime->seconds);
+        cs2.cs.ulTimeFormat = ulTimeFormat;
+        cs2.cs.cTimeSep = cTimeSep;
+        nlsGetAMPM(&cs2.ampm);
+        nlsTime(&cs2,
+                pszTime,
+                pDateTime->hours,
+                pDateTime->minutes,
+                pDateTime->seconds);
     }
 }
@@ -752,4 +803,33 @@
 }
 
+/*
+ *@@ nlsDateTime2:
+ *      speedier version of nlsDateTime that caches
+ *      all information and needs _no_ Prf* call
+ *      any more.
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+VOID nlsDateTime2(PSZ pszDate,
+                  PSZ pszTime,
+                  const DATETIME *pDateTime,
+                  const COUNTRYSETTINGS2 *pcs2)
+{
+    if (pszDate)
+        nlsDate(pcs2,
+                pszDate,
+                pDateTime->year,
+                pDateTime->month,
+                pDateTime->day);
+
+    if (pszTime)
+        nlsTime(pcs2,
+                pszTime,
+                pDateTime->hours,
+                pDateTime->minutes,
+                pDateTime->seconds);
+}
+
 CHAR G_szUpperMap[257];
 BOOL G_fUpperMapInited = FALSE;
Index: trunk/src/helpers/stringh.c
===================================================================
--- trunk/src/helpers/stringh.c	(revision 241)
+++ trunk/src/helpers/stringh.c	(revision 242)
@@ -456,18 +456,14 @@
                 CHAR c)
 {
-    PSZ         p = (PSZ)pszSearch;
-    ULONG       ulCount = 0;
+    PCSZ    p = pszSearch;
+    ULONG   ulCount = 0;
     while (TRUE)
     {
-        p = strchr(p, c);
-        if (p)
-        {
-            ulCount++;
-            p++;
-        }
-        else
-            break;
-    }
-    return ulCount;
+        if (!(p = strchr(p, c)))
+            return ulCount;
+
+        ulCount++;
+        p++;
+    }
 }
 
Index: trunk/src/helpers/textview.c
===================================================================
--- trunk/src/helpers/textview.c	(revision 241)
+++ trunk/src/helpers/textview.c	(revision 242)
@@ -1890,7 +1890,4 @@
 
 } TEXTVIEWWINDATA, *PTEXTVIEWWINDATA;
-
-#define ID_VSCROLL      100
-#define ID_HSCROLL      101
 
 /*
@@ -2327,5 +2324,4 @@
                 // can be NULL
     PCREATESTRUCT       pcs = (PCREATESTRUCT)mp2;
-    SBCDATA             sbcd;
 
     MRESULT             mrc = (MRESULT)TRUE;     // error
@@ -2383,38 +2379,12 @@
         ptxvd->rclViewReal.yTop = pcs->cy;
 
-        sbcd.cb = sizeof(SBCDATA);
-        sbcd.sHilite = 0;
-        sbcd.posFirst = 0;
-        sbcd.posLast = 100;
-        sbcd.posThumb = 30;
-        sbcd.cVisible = 50;
-        sbcd.cTotal = 50;
-
-        ptxvd->hwndVScroll = WinCreateWindow(hwndTextView,
-                                             WC_SCROLLBAR,
-                                             "",
-                                             SBS_VERT | SBS_THUMBSIZE | WS_VISIBLE,
-                                             10, 10,
-                                             20, 100,
-                                             hwndTextView,     // owner
-                                             HWND_TOP,
-                                             ID_VSCROLL,
-                                             &sbcd,
-                                             0);
+        winhCreateScrollBars(hwndTextView,
+                             &ptxvd->hwndVScroll,
+                             &ptxvd->hwndHScroll);
+
         fShow = ((ptxvd->flStyle & XS_VSCROLL) != 0);
         WinShowWindow(ptxvd->hwndVScroll, fShow);
         ptxvd->fVScrollVisible = fShow;
 
-        ptxvd->hwndHScroll = WinCreateWindow(hwndTextView,
-                                             WC_SCROLLBAR,
-                                             "",
-                                             SBS_THUMBSIZE | WS_VISIBLE,
-                                             10, 10,
-                                             20, 100,
-                                             hwndTextView,     // owner
-                                             HWND_TOP,
-                                             ID_HSCROLL,
-                                             &sbcd,
-                                             0);
         fShow = ((ptxvd->flStyle & XS_HSCROLL) != 0);
         WinShowWindow(ptxvd->hwndHScroll, fShow);
Index: trunk/src/helpers/winh.c
===================================================================
--- trunk/src/helpers/winh.c	(revision 241)
+++ trunk/src/helpers/winh.c	(revision 242)
@@ -1896,4 +1896,50 @@
 
 /*
+ *@@ 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
@@ -1918,9 +1964,9 @@
  *         The width or height of this must be passed in ulWinPels.
  *
- *      -- "viewport": the entire data to be displayed, of which the
- *         "window" can only display a subrectangle, if the viewport
+ *      -- "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 ulViewportPels.
+ *         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
@@ -1928,5 +1974,5 @@
  *
  *      -- "window offset": the offset of the current window within
- *         the viewport.
+ *         the workarea.
  *
  *         For horizontal scroll bars, this is the X coordinate,
@@ -1934,5 +1980,5 @@
  *
  *         For vertical scroll bars, this is counted from the _top_
- *         of the viewport (0 means topmost, as opposed to OS/2
+ *         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
@@ -1941,13 +1987,13 @@
  *         The window offset is therefore always positive.
  *
- *      The scroll bar gets disabled if the entire viewport is visible,
- *      that is, if ulViewportPels <= ulWinPels. In that case
+ *      The scroll bar gets disabled if the entire workarea is visible,
+ *      that is, if ulWorkareaPels <= ulWinPels. In that case
  *      FALSE is returned. If (fAutoHide == TRUE), the scroll
  *      bar is not only disabled, but also hidden from the display.
  *      In that case, you will need to reformat your output because
- *      your viewport becomes larger without the scroll bar.
+ *      your workarea becomes larger without the scroll bar.
  *
  *      This function will set the range of the scroll bar to 0 up
- *      to a value depending on the viewport size. For vertical scroll
+ *      to a value depending on the workarea size. For vertical scroll
  *      bars, 0 means topmost (which is kinda sick with the OS/2
  *      coordinate system), for horizontal scroll bars, 0 means leftmost.
@@ -1955,8 +2001,8 @@
  *      The maximum value of the scroll bar will be
  *
- +          (ulViewportPels - ulWinPels) / usScrollUnitPels
+ +          (ulWorkareaPels - ulWinPels) / usScrollUnitPels
  *
  *      The thumb size of the scroll bar will also be adjusted
- *      based on the viewport and window size, as it should be.
+ *      based on the workarea and window size, as it should be.
  *
  *@@added V0.9.1 (2000-02-14) [umoeller]
@@ -1969,5 +2015,5 @@
                                                 // visible window part (in pixels),
                                                 // excluding the scroll bar!
-                         ULONG ulViewportPels,  // in: dimension of total data part, of
+                         ULONG ulWorkareaPels,  // in: dimension of total data part, of
                                                 // which ulWinPels is a sub-dimension
                                                 // (in pixels);
@@ -1982,23 +2028,23 @@
     // _Pmpf(("Entering winhUpdateScrollBar"));
 
-    // for large viewports, adjust scroll bar units
+    // for large workareas, adjust scroll bar units
     USHORT  usScrollUnitPels = 1;
-    if (ulViewportPels > 10000)
+    if (ulWorkareaPels > 10000)
         usScrollUnitPels = 100;
 
-    if (ulViewportPels > ulWinPels)
+    if (ulWorkareaPels > ulWinPels)
     {
         // scrollbar needed:
         USHORT  usThumbDivisorUnits = usScrollUnitPels;
         USHORT  lMaxAllowedUnitOfs;
-        // _Pmpf(("winhUpdateScrollBar: ulViewportPels > ulWinPels, enabling scroller"));
+        // _Pmpf(("winhUpdateScrollBar: ulWorkareaPels > ulWinPels, enabling scroller"));
         // divisor for thumb size (below)
-        if (ulViewportPels > 10000)
-            // for very large viewports, we need to
+        if (ulWorkareaPels > 10000)
+            // for very large workareas, we need to
             // raise the divisor, because we only
             // have a USHORT
             usThumbDivisorUnits = usScrollUnitPels * 100;
 
-        // viewport is larger than window:
+        // workarea is larger than window:
         WinEnableWindow(hwndScrollBar, TRUE);
         if (fAutoHide)
@@ -2006,5 +2052,5 @@
 
         // calculate limit
-        lMaxAllowedUnitOfs = ((ulViewportPels - ulWinPels + usScrollUnitPels)
+        lMaxAllowedUnitOfs = ((ulWorkareaPels - ulWinPels + usScrollUnitPels)
                                // scroll unit is 10
                                / usScrollUnitPels);
@@ -2021,9 +2067,9 @@
 
         // set thumb size based on ulWinPels and
-        // ulViewportPels
+        // ulWorkareaPels
         WinSendMsg(hwndScrollBar,
                    SBM_SETTHUMBSIZE,
                    MPFROM2SHORT(    ulWinPels / usThumbDivisorUnits,       // visible
-                                    ulViewportPels / usThumbDivisorUnits), // total
+                                    ulWorkareaPels / usThumbDivisorUnits), // total
                    0);
         brc = TRUE;
@@ -2031,6 +2077,6 @@
     else
     {
-        // _Pmpf(("winhUpdateScrollBar: ulViewportPels <= ulWinPels"));
-        // entire viewport is visible:
+        // _Pmpf(("winhUpdateScrollBar: ulWorkareaPels <= ulWinPels"));
+        // entire workarea is visible:
         WinEnableWindow(hwndScrollBar, FALSE);
         if (fAutoHide)
@@ -2066,11 +2112,11 @@
  *      of the scroll bar is:
  *
- +          ulViewportPels - (prcl2Scroll->yTop - prcl2Scroll->yBottom)
+ +          ulWorkareaPels - (prcl2Scroll->yTop - prcl2Scroll->yBottom)
  *
  *      This function also automatically changes the scroll bar
- *      units, should you have a viewport size which doesn't fit
+ *      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 viewport.
+ *      a ULONG for the workarea.
  *
  *      Replace "bottom" and "top" with "right" and "left" for
@@ -2085,5 +2131,5 @@
 BOOL winhHandleScrollMsg(HWND hwnd2Scroll,          // in: client window to scroll
                          HWND hwndScrollBar,        // in: vertical or horizontal scroll bar window
-                         PULONG pulCurPelsOfs,      // in/out: current viewport offset;
+                         PULONG pulCurPelsOfs,      // in/out: current workarea offset;
                                                     // this is updated with the proper scroll units
                          PRECTL prcl2Scroll,        // in: hwnd2Scroll rectangle to scroll
@@ -2091,5 +2137,5 @@
                                                     // this is passed to WinScrollWindow,
                                                     // which considers this inclusive!
-                         LONG ulViewportPels,       // in: total viewport dimension,
+                         LONG ulWorkareaPels,       // in: total workarea dimension,
                                                     // into which *pulCurPelsOfs is an offset
                          USHORT usLineStepPels,     // in: pixels to scroll line-wise
@@ -2106,7 +2152,7 @@
     ULONG   ulWinPels;
 
-    // for large viewports, adjust scroll bar units
+    // for large workareas, adjust scroll bar units
     USHORT  usScrollUnitPels = 1;
-    if (ulViewportPels > 10000)
+    if (ulWorkareaPels > 10000)
         usScrollUnitPels = 100;
 
@@ -2117,5 +2163,5 @@
         ulWinPels = (prcl2Scroll->xRight - prcl2Scroll->xLeft);
 
-    lMaxAllowedUnitOfs = ((LONG)ulViewportPels - ulWinPels) / usScrollUnitPels;
+    lMaxAllowedUnitOfs = ((LONG)ulWorkareaPels - ulWinPels) / usScrollUnitPels;
 
     // _Pmpf(("Entering winhHandleScrollMsg"));
@@ -2222,4 +2268,117 @@
 
 /*
+ *@@ 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),
+                   0);
+
+        if (msg == WM_VSCROLL)
+            return (*plCurPelsOfs - lOldPelsOfs);
+
+        return -(*plCurPelsOfs - lOldPelsOfs);
+    }
+
+    return 0;
+}
+
+/*
+ *@@ winhScrollWindow:
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+BOOL winhScrollWindow(HWND hwnd2Scroll,
+                      PRECTL prclClip,          // clipping rectangle or NULL
+                      PPOINTL pptlScroll)
+{
+    return !!WinScrollWindow(hwnd2Scroll,
+                             pptlScroll->x,
+                             pptlScroll->y,
+                             prclClip,
+                             prclClip,
+                             NULLHANDLE,     // no region
+                             NULL,           // no rect
+                             SW_INVALIDATERGN);
+}
+
+/*
  *@@ winhProcessScrollChars:
  *      helper for processing WM_CHAR messages for
@@ -2264,6 +2423,6 @@
                             MPARAM mp1,         // in: WM_CHAR mp1
                             MPARAM mp2,         // in: WM_CHAR mp2
-                            ULONG ulVertMax,    // in: maximum viewport cy
-                            ULONG ulHorzMax)    // in: maximum viewport cx
+                            ULONG ulVertMax,    // in: maximum workarea cy
+                            ULONG ulHorzMax)    // in: maximum workarea cx
 {
     BOOL    fProcessed = FALSE;
@@ -3305,11 +3464,11 @@
             fl |= QPF_ID2COLORINDEX;            // convert indexed color 2 to RGB V0.9.20 (2002-08-04) [umoeller]
 
-        if ((ul = WinQueryPresParam(hwnd,
-                                    ulppRGB,
-                                    ulppIndex,
-                                    &attrFound,
-                                    sizeof(lColorFound),
-                                    &lColorFound,
-                                    fl)))
+        if (ul = WinQueryPresParam(hwnd,
+                                   ulppRGB,
+                                   ulppIndex,
+                                   &attrFound,
+                                   sizeof(lColorFound),
+                                   &lColorFound,
+                                   fl))
             return lColorFound;
     }
@@ -4270,4 +4429,20 @@
                            NULL,
                            NULL);
+}
+
+/*
+ *@@ winhSetParentAndOwner:
+ *      switches owner _and_ parent of the given window.
+ *
+ *@@added V1.0.1 (2003-01-17) [umoeller]
+ */
+
+BOOL winhSetParentAndOwner(HWND hwnd,           // in: window whose parent and owner to change
+                           HWND hwndNewParent,  // in: new parent and owner
+                           BOOL fRedraw)
+{
+    return (    WinSetParent(hwnd, hwndNewParent, fRedraw)
+             && WinSetOwner(hwnd, hwndNewParent)
+           );
 }
 
