Index: trunk/src/helpers/comctl.c
===================================================================
--- trunk/src/helpers/comctl.c	(revision 52)
+++ trunk/src/helpers/comctl.c	(revision 53)
@@ -584,5 +584,6 @@
              */
 
-            case WM_TIMER: {
+            case WM_TIMER:
+            {
                 pa->usAniCurrent++;
                 if (pa->usAniCurrent >= pa->usAniCount)
@@ -590,7 +591,7 @@
 
                 WinSendMsg(hwndStatic,
-                        SM_SETHANDLE,
-                        (MPARAM)pa->ahptrAniIcons[pa->usAniCurrent],
-                        (MPARAM)NULL);
+                           SM_SETHANDLE,
+                           (MPARAM)pa->ahptrAniIcons[pa->usAniCurrent],
+                           (MPARAM)NULL);
             break; }
 
@@ -606,9 +607,9 @@
                 SIZEL szlPage;
 
-                LONG lBkgndColor = winhQueryPresColor(
-                                WinQueryWindow(hwndStatic, QW_PARENT),
-                                PP_BACKGROUNDCOLOR,
-                                FALSE,
-                                SYSCLR_DIALOGBACKGROUND);
+                LONG lBkgndColor
+                    = winhQueryPresColor(WinQueryWindow(hwndStatic, QW_PARENT),
+                                         PP_BACKGROUNDCOLOR,
+                                         FALSE,
+                                         SYSCLR_DIALOGBACKGROUND);
 
                 HPS hps = WinGetPS(hwndStatic);
@@ -632,5 +633,8 @@
                 szlPage.cx = pa->rclIcon.xRight - pa->rclIcon.xLeft;
                 szlPage.cy = pa->rclIcon.yTop - pa->rclIcon.yBottom;
-                if (gpihCreateMemPS(pa->hab, &szlPage, &hdcMem, &hpsMem))
+                if (gpihCreateMemPS(pa->hab,
+                                    &szlPage,
+                                    &hdcMem,
+                                    &hpsMem))
                 {
                     // switch the memory PS to RGB mode too
@@ -653,9 +657,9 @@
                             ptl.y = pa->rclIcon.yTop;
                             GpiSetColor(hpsMem,
-                                    lBkgndColor);
+                                        lBkgndColor);
                             GpiBox(hpsMem,
-                                    DRO_FILL, // interior only
-                                    &ptl,
-                                    0, 0);    // no corner rounding
+                                   DRO_FILL, // interior only
+                                   &ptl,
+                                   0, 0);    // no corner rounding
 
                             /*
@@ -689,8 +693,8 @@
 
                                 if (hbmSource)
-                                    gpihStretchBitmap(hpsMem,
-                                                      hbmSource,
+                                    gpihStretchBitmap(hpsMem,       // target
+                                                      hbmSource,    // source
                                                       NULL,     // use size of bitmap
-                                                      &(pa->rclIcon),
+                                                      &pa->rclIcon,
                                                       ((pa->ulFlags & ANF_PROPORTIONAL)
                                                             != 0));
@@ -720,5 +724,6 @@
              */
 
-            case WM_PAINT: {
+            case WM_PAINT:
+            {
                 RECTL rcl;
                 HPS hps = WinBeginPaint(hwndStatic, NULLHANDLE, &rcl);
Index: trunk/src/helpers/dialog.c
===================================================================
--- trunk/src/helpers/dialog.c	(revision 53)
+++ trunk/src/helpers/dialog.c	(revision 53)
@@ -0,0 +1,1422 @@
+
+/*
+ *@@sourcefile dialog.c:
+ *      contains PM helper functions to create and
+ *      auto-format dialogs from control arrays in memory.
+ *
+ *      See dlghCreateDlg for details.
+ *
+ *      Usage: All PM programs.
+ *
+ *      Function prefixes (new with V0.81):
+ *      --  dlg*   Dialog functions
+ *
+ *      Note: Version numbering in this file relates to XWorkplace version
+ *            numbering.
+ *
+ *@@header "helpers\dialog.h"
+ */
+
+/*
+ *      Copyright (C) 2001 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_DOSERRORS
+
+#define INCL_WINWINDOWMGR
+#define INCL_WINFRAMEMGR
+#define INCL_WINDIALOGS
+#define INCL_WINBUTTONS
+#define INCL_WINSTATICS
+#define INCL_WINSYS
+
+#define INCL_GPIPRIMITIVES
+#define INCL_GPIBITMAPS
+#define INCL_GPILCIDS
+#include <os2.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "setup.h"                      // code generation and debugging options
+
+#include "helpers\comctl.h"
+#include "helpers\dialog.h"
+#include "helpers\gpih.h"
+#include "helpers\linklist.h"
+#include "helpers\standards.h"
+#include "helpers\stringh.h"
+#include "helpers\winh.h"
+
+/*
+ *@@category: Helpers\PM helpers\Dialog templates
+ */
+
+/* ******************************************************************
+ *
+ *   Private declarations
+ *
+ ********************************************************************/
+
+/*
+ *@@ DLGPRIVATE:
+ *      private data to the dlg manager, allocated
+ *      by dlghCreateDlg. This is what is really
+ *      used, even though the prototype only
+ *      declares DIALOGDATA.
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+typedef struct _DLGPRIVATE
+{
+    // public data
+    HWND        hwndDlg;
+
+    // definition data (private)
+    LINKLIST    llTables;
+
+    HWND        hwndFirstFocus;
+
+    POINTL      ptlTotalOfs;
+
+    LINKLIST    llControls;     // linked list of all PCOLUMNDEF structs,
+                                 // in the order in which windows were
+                                 // created
+
+    const char  *pcszControlsFont;  // from dlghCreateDlg
+
+    HPS         hps;
+    const char  *pcszFontLast;
+    LONG        lcidLast;
+} DLGPRIVATE, *PDLGPRIVATE;
+
+typedef struct _COLUMNDEF *PCOLUMNDEF;
+typedef struct _ROWDEF *PROWDEF;
+typedef struct _TABLEDEF *PTABLEDEF;
+
+/*
+ *@@ CONTROLPOS:
+ *      control position. We don't want to use SWP.
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+typedef struct _CONTROLPOS
+{
+    LONG        x,
+                y,
+                cx,
+                cy;
+} CONTROLPOS, *PCONTROLPOS;
+
+/*
+ *@@ COLUMNDEF:
+ *      representation of a table column.
+ *      This is stored in a linked list in ROWDEF.
+ *
+ *      A table column represents either a PM control
+ *      window or another table, which may therefore
+ *      be nested.
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+typedef struct _COLUMNDEF
+{
+    PROWDEF     pOwningRow;         // row whose linked list this column belongs to
+
+    BOOL        fIsNestedTable;     // if TRUE, pvDefinition points to a nested TABLEDEF;
+                                    // if FALSE, pvDefinition points to a CONTROLDEF as
+                                    // specified by the user
+
+    PVOID       pvDefinition;       // either a PTABLEDEF or a PCONTROLDEF
+
+    CONTROLPOS  cpControl,          // real pos and size of control
+                cpColumn;           // pos and size of column; can be wider, spacings applied
+
+    HWND        hwndControl;        // created control; NULLHANDLE for tables always
+
+} COLUMNDEF;
+
+/*
+ *@@ ROWDEF:
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+typedef struct _ROWDEF
+{
+    PTABLEDEF   pOwningTable;   // table whose linked list this row belongs to
+
+    LINKLIST    llColumns;      // contains COLUMNDEF structs, no auto-free
+
+    CONTROLPOS  cpRow;
+
+} ROWDEF;
+
+/*
+ *@@ TABLEDEF:
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+typedef struct _TABLEDEF
+{
+    LINKLIST    llRows;             // contains ROWDEF structs, no auto-free
+
+    PCONTROLDEF pCtlDef;            // if != NULL, we create a PM control around the table
+
+    CONTROLPOS  cpTable;
+
+} TABLEDEF;
+
+/*
+ *@@ PROCESSMODE:
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+typedef enum _PROCESSMODE
+{
+    PROCESS_CALC_SIZES,             // step 1
+    PROCESS_CALC_POSITIONS,         // step 2
+    PROCESS_CREATE_CONTROLS         // step 3
+} PROCESSMODE;
+
+/* ******************************************************************
+ *
+ *   Worker routines
+ *
+ ********************************************************************/
+
+#define PM_GROUP_SPACING_X          20
+#define PM_GROUP_SPACING_TOP        30
+
+VOID ProcessTable(PTABLEDEF pTableDef,
+                  const CONTROLPOS *pcpTable,
+                  PROCESSMODE ProcessMode,
+                  PDLGPRIVATE pDlgData);
+
+/*
+ *@@ CalcAutoSizeText:
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+VOID CalcAutoSizeText(PCONTROLDEF pControlDef,
+                      PSIZEL pszlAuto,          // out: computed size
+                      PDLGPRIVATE pDlgData)
+{
+    BOOL        fFind = TRUE;
+    RECTL       rclText;
+
+    const char *pcszFontThis = pControlDef->pcszFont;
+                    // can be NULL,
+                    // or CTL_COMMON_FONT
+
+    if (pcszFontThis == CTL_COMMON_FONT)
+        pcszFontThis = pDlgData->pcszControlsFont;
+
+    if (!pDlgData->hps)
+    {
+        // first call: get a PS
+        pDlgData->hps = WinGetPS(pDlgData->hwndDlg);
+        fFind = TRUE;
+    }
+    else
+        if (strhcmp(pcszFontThis, pDlgData->pcszFontLast))
+            fFind = TRUE;
+
+    if (fFind)
+    {
+        FONTMETRICS fm;
+        LONG lcid;
+        LONG lPointSize = 0;
+        if (pDlgData->lcidLast)
+        {
+            // delete old font
+            GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);
+            GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast);
+        }
+
+        // create new font
+        pDlgData->lcidLast = gpihFindPresFont(NULLHANDLE,        // no window yet
+                                              FALSE,
+                                              pDlgData->hps,
+                                              pcszFontThis,
+                                              &fm,
+                                              &lPointSize);
+        GpiSetCharSet(pDlgData->hps, pDlgData->lcidLast);
+        if (fm.fsDefn & FM_DEFN_OUTLINE)
+            gpihSetPointSize(pDlgData->hps, lPointSize);
+
+        pszlAuto->cy = fm.lMaxBaselineExt + fm.lExternalLeading;
+    }
+
+    // ok, we FINALLY have a font now...
+    // get the control string and see how much space it needs
+    if (pControlDef->pcszText)
+    {
+        POINTL aptl[TXTBOX_COUNT];
+        GpiQueryTextBox(pDlgData->hps,
+                        strlen(pControlDef->pcszText),
+                        (PCH)pControlDef->pcszText,
+                        TXTBOX_COUNT,
+                        aptl);
+        pszlAuto->cx = aptl[TXTBOX_TOPRIGHT].x - aptl[TXTBOX_BOTTOMLEFT].x;
+    }
+}
+
+/*
+ *@@ CalcAutoSize:
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+VOID CalcAutoSize(PCONTROLDEF pControlDef,
+                  PSIZEL pszlAuto,          // out: computed size
+                  PDLGPRIVATE pDlgData)
+{
+    // dumb defaults
+    pszlAuto->cx = 100;
+    pszlAuto->cy = 30;
+
+    switch ((ULONG)pControlDef->pcszClass)
+    {
+        case 0xffff0003L: // WC_BUTTON:
+            CalcAutoSizeText(pControlDef,
+                             pszlAuto,
+                             pDlgData);
+            if (pControlDef->flStyle & (  BS_AUTOCHECKBOX
+                                        | BS_AUTORADIOBUTTON
+                                        | BS_AUTO3STATE
+                                        | BS_3STATE
+                                        | BS_CHECKBOX
+                                        | BS_RADIOBUTTON))
+                pszlAuto->cx += 20;     // @@@
+            else if (pControlDef->flStyle & BS_BITMAP)
+                ;
+            else if (pControlDef->flStyle & (BS_ICON | BS_MINIICON))
+                ;
+            // we can't test for BS_PUSHBUTTON because that's 0x0000
+            else if (!(pControlDef->flStyle & BS_USERBUTTON))
+            {
+                pszlAuto->cx += (2 * WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER) + 15);
+                pszlAuto->cy += (2 * WinQuerySysValue(HWND_DESKTOP, SV_CYBORDER) + 15);
+            }
+        break;
+
+        case 0xffff0005L: // WC_STATIC:
+            if (pControlDef->flStyle & SS_TEXT)
+                CalcAutoSizeText(pControlDef,
+                                 pszlAuto,
+                                 pDlgData);
+            else if (pControlDef->flStyle & SS_BITMAP)
+            {
+                HBITMAP hbm = (HBITMAP)pControlDef->pcszText;
+                if (hbm)
+                {
+                    BITMAPINFOHEADER2 bmih2;
+                    ZERO(&bmih2);
+                    bmih2.cbFix = sizeof(bmih2);
+                    if (GpiQueryBitmapInfoHeader(hbm,
+                                                 &bmih2))
+                    {
+                        pszlAuto->cx = bmih2.cx;
+                        pszlAuto->cy = bmih2.cy;
+                    }
+                }
+            }
+        break;
+    }
+}
+
+/*
+ *@@ ProcessColumn:
+ *      processes a column, which per definition is either
+ *      a control or a nested subtable.
+ *
+ *      A column is part of a row, which in turn is part
+ *      of a table. There can be several columns in a row,
+ *      and several rows in a table.
+ *
+ *      Since tables may be specified as columns, it is
+ *      possible to produce complex dialog layouts by
+ *      nesting tables.
+ *
+ *      This does the following:
+ *
+ *      -- PROCESS_CALC_SIZES: size is taken from control def,
+ *         or for tables, this produces a recursive ProcessTable
+ *         call.
+ *         Preconditions: none.
+ *
+ *      -- PROCESS_CALC_POSITIONS: position of each column
+ *         is taken from *plX, which is increased by the
+ *         column width by this call.
+ *
+ *         Preconditions: Owning row must already have its
+ *         y position properly set, or we can't compute
+ *         ours. Besides, plX must point to the current X
+ *         in the row and will be incremented by the columns
+ *         size here.
+ *
+ *      -- PROCESS_CREATE_CONTROLS: well, creates the controls.
+ *
+ *         For tables, this recurses again. If the table has
+ *         a string assigned, this also produces a group box
+ *         after the recursion.
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+VOID ProcessColumn(PCOLUMNDEF pColumnDef,
+                   PROWDEF pOwningRow,
+                   PROCESSMODE ProcessMode,
+                   PLONG plX,                   // in/out: PROCESS_CALC_POSITIONS only
+                   PDLGPRIVATE pDlgData)
+{
+    pColumnDef->pOwningRow = pOwningRow;
+
+    // for PROCESS_CALC_SIZES: have control return its size
+    //      plus spacings into szlControl
+    // for PROCESS_CALC_POSITIONS: have control compute its
+    //      position from the column position (there may be
+    //      spacings)
+    // for PROCESS_CREATE_CONTROLS: well, create the control
+    switch (ProcessMode)
+    {
+        case PROCESS_CALC_SIZES:
+        {
+            ULONG       ulXSpacing = 0,
+                        ulYSpacing = 0;
+            if (pColumnDef->fIsNestedTable)
+            {
+                // nested table: recurse!!
+                PTABLEDEF pTableDef = (PTABLEDEF)pColumnDef->pvDefinition;
+                ProcessTable(pTableDef,
+                             NULL,
+                             ProcessMode,
+                             pDlgData);
+
+                // store the size of the sub-table
+                pColumnDef->cpControl.cx = pTableDef->cpTable.cx;
+                pColumnDef->cpControl.cy = pTableDef->cpTable.cy;
+
+                // should we create a PM control around the table?
+                if (pTableDef->pCtlDef)
+                {
+                    // yes: make this wider
+                    ulXSpacing = (2 * PM_GROUP_SPACING_X);
+                    ulYSpacing = (PM_GROUP_SPACING_X + PM_GROUP_SPACING_TOP);
+                }
+            }
+            else
+            {
+                // no nested table, but control:
+                PCONTROLDEF pControlDef = (PCONTROLDEF)pColumnDef->pvDefinition;
+                PSIZEL      pszl = &pControlDef->szlControlProposed;
+                SIZEL       szlAuto;
+
+                if (    (pszl->cx == -1)
+                     || (pszl->cy == -1)
+                   )
+                {
+                    CalcAutoSize(pControlDef,
+                                 &szlAuto,
+                                 pDlgData);
+                }
+
+                if (pszl->cx == -1)
+                    pColumnDef->cpControl.cx = szlAuto.cx;
+                else
+                    pColumnDef->cpControl.cx = pszl->cx;
+
+                if (pszl->cy == -1)
+                    pColumnDef->cpControl.cy = szlAuto.cy;
+                else
+                    pColumnDef->cpControl.cy = pszl->cy;
+
+                // @@@hack sizes
+
+                ulXSpacing = ulYSpacing = (2 * pControlDef->ulSpacing);
+            }
+
+            pColumnDef->cpColumn.cx =   pColumnDef->cpControl.cx
+                                       + ulXSpacing;
+            pColumnDef->cpColumn.cy =   pColumnDef->cpControl.cy
+                                       + ulYSpacing;
+        break; }
+
+        case PROCESS_CALC_POSITIONS:
+        {
+            // calculate column position: this includes spacing
+            ULONG ulSpacing = 0;
+
+            // column position = *plX
+            pColumnDef->cpColumn.x = *plX;
+            pColumnDef->cpColumn.y = pOwningRow->cpRow.y;
+
+            if (pColumnDef->fIsNestedTable)
+            {
+                PTABLEDEF pTableDef = (PTABLEDEF)pColumnDef->pvDefinition;
+                // should we create a PM control around the table?
+                if (pTableDef->pCtlDef)
+                    // yes:
+                    ulSpacing = PM_GROUP_SPACING_X;
+            }
+            else
+            {
+                // no nested table, but control:
+                PCONTROLDEF pControlDef = (PCONTROLDEF)pColumnDef->pvDefinition;
+                ulSpacing = pControlDef->ulSpacing;
+            }
+
+            // increase plX by column width
+            *plX += pColumnDef->cpColumn.cx;
+
+            // calculate control pos by applying spacing
+            pColumnDef->cpControl.x =   pColumnDef->cpColumn.x
+                                      + ulSpacing;
+            pColumnDef->cpControl.y =   pColumnDef->cpColumn.y
+                                      + ulSpacing;
+
+            if (pColumnDef->fIsNestedTable)
+            {
+                // nested table:
+                PTABLEDEF pTableDef = (PTABLEDEF)pColumnDef->pvDefinition;
+
+                // recurse!! to create windows for the sub-table
+                ProcessTable(pTableDef,
+                             &pColumnDef->cpControl,   // start pos for new table
+                             ProcessMode,
+                             pDlgData);
+            }
+        break; }
+
+        case PROCESS_CREATE_CONTROLS:
+        {
+            PCONTROLPOS pcp = NULL;
+            PCONTROLDEF pControlDef = NULL;
+            const char  *pcszTitle = NULL;
+            ULONG       flStyle = 0;
+            LHANDLE     lHandleSet = NULLHANDLE;
+
+            if (pColumnDef->fIsNestedTable)
+            {
+                // nested table:
+                PTABLEDEF pTableDef = (PTABLEDEF)pColumnDef->pvDefinition;
+
+                // recurse!!
+                ProcessTable(pTableDef,
+                             NULL,
+                             ProcessMode,
+                             pDlgData);
+
+                // should we create a PM control around the table?
+                // (do this AFTER the other controls from recursing,
+                // otherwise the stupid container doesn't show up)
+                if (pTableDef->pCtlDef)
+                {
+                    // yes:
+                    pcp  = &pColumnDef->cpColumn;  // !! not control
+                    pControlDef = pTableDef->pCtlDef;
+                    pcszTitle = pControlDef->pcszText;
+                    flStyle = pControlDef->flStyle;
+                }
+            }
+            else
+            {
+                // no nested table, but control:
+                pControlDef = (PCONTROLDEF)pColumnDef->pvDefinition;
+                pcp = &pColumnDef->cpControl;
+                pcszTitle = pControlDef->pcszText;
+                flStyle = pControlDef->flStyle;
+
+                // change the title if this is a static with SS_BITMAP;
+                // we have used a HBITMAP in there!
+                if (    ((ULONG)pControlDef->pcszClass == 0xffff0005L) // WC_STATIC:
+                     && (flStyle & SS_BITMAP)
+                   )
+                {
+                    // change style flag to not use SS_BITMAP;
+                    // control creation fails otherwise (stupid, stupid PM)
+                    flStyle = ((flStyle & ~SS_BITMAP) | SS_FGNDFRAME);
+                    pcszTitle = "";
+                    lHandleSet = (LHANDLE)pControlDef->pcszText;
+                }
+            }
+
+            if (pcp && pControlDef)
+            {
+                // create something:
+                PPRESPARAMS ppp = NULL;
+
+                const char  *pcszFont = pControlDef->pcszFont;
+                                // can be NULL, or CTL_COMMON_FONT
+                if (pcszFont == CTL_COMMON_FONT)
+                    pcszFont = pDlgData->pcszControlsFont;
+
+                if (pcszFont)
+                    winhStorePresParam(&ppp,
+                                       PP_FONTNAMESIZE,
+                                       strlen(pcszFont),
+                                       (PVOID)pcszFont);
+
+                pColumnDef->hwndControl
+                    = WinCreateWindow(pDlgData->hwndDlg,   // parent
+                                      (PSZ)pControlDef->pcszClass,
+                                      (pcszTitle)   // hacked
+                                            ? (PSZ)pcszTitle
+                                            : "",
+                                      flStyle,      // hacked
+                                      pcp->x + pDlgData->ptlTotalOfs.x,
+                                      pcp->y + pDlgData->ptlTotalOfs.y,
+                                      pcp->cx,
+                                      pcp->cy,
+                                      pDlgData->hwndDlg,   // owner
+                                      HWND_BOTTOM,
+                                      pControlDef->usID,
+                                      NULL,             // control data @@@
+                                      ppp);
+
+                if (pColumnDef->hwndControl && lHandleSet)
+                {
+                    // subclass the damn static
+                    ctlPrepareStretchedBitmap(pColumnDef->hwndControl,
+                                              TRUE);
+
+                    WinSendMsg(pColumnDef->hwndControl,
+                               SM_SETHANDLE,
+                               (MPARAM)lHandleSet,
+                               0);
+                }
+
+                if (ppp)
+                    free(ppp);
+
+                if (pColumnDef->hwndControl)
+                {
+                    lstAppendItem(&pDlgData->llControls,
+                                  pColumnDef);
+
+                    // if this is the first control with WS_TABSTOP,
+                    // we give it the focus later
+                    if (    (flStyle & WS_TABSTOP)
+                         && (!pDlgData->hwndFirstFocus)
+                       )
+                        pDlgData->hwndFirstFocus = pColumnDef->hwndControl;
+                }
+            }
+        break; }
+    }
+
+}
+
+/*
+ *@@ ProcessRow:
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+VOID ProcessRow(PROWDEF pRowDef,
+                PTABLEDEF pOwningTable,
+                PROCESSMODE ProcessMode,
+                PLONG plY,
+                PDLGPRIVATE pDlgData)
+{
+    ULONG   ul;
+    LONG    lX;
+    PLISTNODE pNode;
+
+    pRowDef->pOwningTable = pOwningTable;
+
+    if (ProcessMode == PROCESS_CALC_SIZES)
+    {
+        pRowDef->cpRow.cx = 0;
+        pRowDef->cpRow.cy = 0;
+    }
+    else if (ProcessMode == PROCESS_CALC_POSITIONS)
+    {
+        // set up x and y so that the columns can
+        // base on that
+        pRowDef->cpRow.x = pOwningTable->cpTable.x;
+        // decrease y by row height
+        *plY -= pRowDef->cpRow.cy;
+        // and use that for our bottom position
+        pRowDef->cpRow.y = *plY;
+
+        // set lX to left of row; used by column calls below
+        lX = pRowDef->cpRow.x;
+    }
+
+    FOR_ALL_NODES(&pRowDef->llColumns, pNode)
+    {
+        PCOLUMNDEF  pColumnDefThis = (PCOLUMNDEF)pNode->pItemData;
+
+        ProcessColumn(pColumnDefThis, pRowDef, ProcessMode, &lX, pDlgData);
+
+        if (ProcessMode == PROCESS_CALC_SIZES)
+        {
+            // row width = sum of all columns
+            pRowDef->cpRow.cx += pColumnDefThis->cpColumn.cx;
+
+            // row height = maximum height of a column
+            if (pRowDef->cpRow.cy < pColumnDefThis->cpColumn.cy)
+                pRowDef->cpRow.cy = pColumnDefThis->cpColumn.cy;
+        }
+    }
+}
+
+/*
+ *@@ ProcessTable:
+ *
+ *      This routine is a bit sick because it can be
+ *      called recursively if a nested table is found
+ *      in a COLUMNDEF.
+ *
+ *      With PROCESS_CALC_POSITIONS, pptl must specify
+ *      the lower left corner of the table. For the
+ *      root call, this will be {0, 0}; for nested calls,
+ *      this must be the lower left corner of the column
+ *      to which the nested table belongs.
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+VOID ProcessTable(PTABLEDEF pTableDef,
+                  const CONTROLPOS *pcpTable,       // in: table position with PROCESS_CALC_POSITIONS
+                  PROCESSMODE ProcessMode,
+                  PDLGPRIVATE pDlgData)
+{
+    ULONG   ul;
+    LONG    lY;
+    PLISTNODE pNode;
+
+    if (ProcessMode == PROCESS_CALC_SIZES)
+    {
+        pTableDef->cpTable.cx = 0;
+        pTableDef->cpTable.cy = 0;
+    }
+    else if (ProcessMode == PROCESS_CALC_POSITIONS)
+    {
+        pTableDef->cpTable.x = pcpTable->x;
+        pTableDef->cpTable.y = pcpTable->y;
+
+        // start the rows on top
+        lY = pcpTable->y + pTableDef->cpTable.cy;
+    }
+
+    FOR_ALL_NODES(&pTableDef->llRows, pNode)
+    {
+        PROWDEF pRowDefThis = (PROWDEF)pNode->pItemData;
+
+        ProcessRow(pRowDefThis, pTableDef, ProcessMode, &lY, pDlgData);
+
+        if (ProcessMode == PROCESS_CALC_SIZES)
+        {
+            // table width = maximum width of a row
+            if (pTableDef->cpTable.cx < pRowDefThis->cpRow.cx)
+                pTableDef->cpTable.cx = pRowDefThis->cpRow.cx;
+
+            // table height = sum of all rows
+            pTableDef->cpTable.cy += pRowDefThis->cpRow.cy;
+        }
+    }
+}
+
+/*
+ *@@ ProcessAll:
+ *
+ *      -- PROCESS_CALC_SIZES: calculates the sizes
+ *         of all tables, rows, columns, and controls.
+ *
+ *      -- PROCESS_CALC_POSITIONS: calculates the positions
+ *         based on the sizes calculated before.
+ *
+ *      -- PROCESS_CREATE_CONTROLS: creates the controls with the
+ *         positions and sizes calculated before.
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+VOID ProcessAll(PDLGPRIVATE pDlgData,
+                PSIZEL pszlClient,
+                PROCESSMODE ProcessMode)
+{
+    ULONG ul;
+    PLISTNODE pNode;
+    CONTROLPOS cpTable;
+    ZERO(&cpTable);
+
+    switch (ProcessMode)
+    {
+        case PROCESS_CALC_SIZES:
+            pszlClient->cx = 0;
+            pszlClient->cy = 0;
+        break;
+
+        case PROCESS_CALC_POSITIONS:
+            // start with the table on top
+            cpTable.y = pszlClient->cy;
+        break;
+    }
+
+    FOR_ALL_NODES(&pDlgData->llTables, pNode)
+    {
+        PTABLEDEF pTableDefThis = (PTABLEDEF)pNode->pItemData;
+
+        if (ProcessMode == PROCESS_CALC_POSITIONS)
+        {
+            cpTable.x = 0;
+            cpTable.y -= pTableDefThis->cpTable.cy;
+        }
+
+        ProcessTable(pTableDefThis,
+                     &cpTable,      // start pos
+                     ProcessMode,
+                     pDlgData);
+
+        if (ProcessMode == PROCESS_CALC_SIZES)
+        {
+            pszlClient->cx += pTableDefThis->cpTable.cx;
+            pszlClient->cy += pTableDefThis->cpTable.cy;
+        }
+    }
+}
+
+/*
+ *@@ CreateColumn:
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+APIRET CreateColumn(PROWDEF pCurrentRow,
+                    BOOL fIsNestedTable,
+                    PVOID pvDefinition,       // in: either PTABLEDEF or PCONTROLDEF
+                    PCOLUMNDEF *ppColumnDef)    // out: new COLUMNDEF
+{
+    APIRET arc = NO_ERROR;
+
+    if (!pCurrentRow)
+        arc = DLGERR_CONTROL_BEFORE_ROW;
+    else
+    {
+        // append the control def
+        if (!pvDefinition)
+            arc = DLGERR_NULL_CTL_DEF;
+        else
+        {
+            // create column and store ctl def
+            PCOLUMNDEF pColumnDef = NEW(COLUMNDEF);
+            if (!pColumnDef)
+                arc = ERROR_NOT_ENOUGH_MEMORY;
+            else
+            {
+                memset(pColumnDef, 0, sizeof(COLUMNDEF));
+                pColumnDef->pOwningRow = pCurrentRow;
+                pColumnDef->fIsNestedTable = fIsNestedTable;
+                pColumnDef->pvDefinition = pvDefinition;
+
+                *ppColumnDef = pColumnDef;
+            }
+        }
+    }
+
+    return (arc);
+}
+
+/* ******************************************************************
+ *
+ *   Public APIs
+ *
+ ********************************************************************/
+
+/*
+ *@@ STACKITEM:
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+typedef struct _STACKITEM
+{
+    PTABLEDEF       pLastTable;
+    PROWDEF         pLastRow;
+
+} STACKITEM, *PSTACKITEM;
+
+/*
+ *@@ dlghCreateDlg:
+ *      replacement for WinCreateDlg for creating a dialog
+ *      from a settings array in memory, which is formatted
+ *      automatically.
+ *
+ *      This does NOT use regular dialog templates from
+ *      module resources. Instead, you pass in an array
+ *      of _DLGHITEM structures, which define the controls
+ *      and how they are to be formatted.
+ *
+ *      A regular standard dialog would use something like
+ *
+ +          FCF_TITLEBAR | FCF_SYSMENU | FCF_DLGBORDER | FCF_NOBYTEALIGN
+ *
+ *      for flCreateFlags. To make the dlg sizeable, specify
+ *      FCF_SIZEBORDER instead of FCF_DLGBORDER.
+ *
+ *      <B>Usage:</B>
+ *
+ *      Like WinLoadDlg, this creates a standard WC_FRAME and
+ *      subclasses it with fnwpMyDlgProc. It then sends WM_INITDLG
+ *      to the dialog with pCreateParams in mp2. You can use
+ *      WinProcessDlg as usual. In your dlg proc, use WinDefDlgProc
+ *      as usual.
+ *
+ *      There is NO run-time overhead after creation; after this
+ *      function returns, the dialog is a standard dialog as if
+ *      loaded from WinLoadDlg.
+ *
+ *      The array of _DLGHITEM structures defines how the
+ *      dialog is set up. All this is ONLY used by this function
+ *      and NOT needed after the dialog has been created.
+ *
+ *      In _DLGHITEM, the "Type" field determines what this
+ *      structure defines. A number of handy macros have been
+ *      defined to make this easier and to provide type-checking
+ *      at compile time.
+ *
+ *      The macros are:
+ *
+ *      --  START_TABLE starts a new table. The tables may nest,
+ *          but must each be properly terminated with END_TABLE.
+ *
+ *      --  START_ROW(fl) starts a new row in a table.
+ *
+ *          fl specifies formatting flags for the row. This
+ *          can be one of ROW_VALIGN_BOTTOM, ROW_VALIGN_CENTER,
+ *          ROW_VALIGN_TOP and affects all items in the control.
+ *
+ *      --  START_GROUP_TABLE(pDef) starts a group. This
+ *          behaves exacly like START_TABLE, but in addition,
+ *          it produces a control around the table. Useful
+ *          for group boxes. pDef must point to a _CONTROLDEF,
+ *          whose size parameter is ignored.
+ *
+ *          This must also be terminated with END_TABLE.
+ *
+ *      --  CONTROL_DEF(pDef) defines a control in a table row.
+ *          pDef must point to a _CONTROLDEF structure.
+ *
+ *          The main difference (and advantage) of this
+ *          function is that there is NO information in
+ *          _CONTROLDEF about a control's _position_.
+ *          Instead, the structure only contains the _size_
+ *          of the control. All positions are computed by
+ *          this function, depending on the position of the
+ *          control and its nesting within the various tables.
+ *
+ *          If you specify SZL_AUTOSIZE, the size of the
+ *          control is even computed automatically. Presently,
+ *          this only works for statics with SS_TEXT and
+ *          SS_BITMAP.
+ *
+ *          Unless separated with TYPE_START_NEW_ROW items,
+ *          subsequent control items will be considered
+ *          to be in the same row (== positioned next to
+ *          each other).
+ *
+ *      There are a few rules, whose violation will produce
+ *      an error:
+ *
+ *      -- The entire array must be enclosed in a table
+ *         (the "root" table).
+ *
+ *      -- After START_TABLE or START_GROUP_TABLE, there must
+ *         always be a START_ROW first.
+ *
+ *      To create a dialog, set up arrays like the following:
+ *
+ +          // control definitions referenced by DlgTemplate:
+ +          CONTROLDEF
+ +      (1)             GroupDef = {
+ +                                  WC_STATIC, "", ....,
+ +                                  { 0, 0 },       // size, ignored for groups
+ +                                  5               // spacing
+ +                               },
+ +      (2)             CnrDef = {
+ +                                  WC_CONTAINER, "", ....,
+ +                                  { 50, 50 },     // size
+ +                                  5               // spacing
+ +                               },
+ +      (3)             Static = {
+ +                                  WC_STATIC, "Static below cnr", ...,
+ +                                  { SZL_AUTOSIZE, SZL_AUTOSIZE },     // size
+ +                                  5               // spacing
+ +                               },
+ +      (4)             OKButton = {
+ +                                  WC_STATIC, "~OK", ...,
+ +                                  { 100, 30 },    // size
+ +                                  5               // spacing
+ +                               },
+ +      (5)             CancelButton = {
+ +                                  WC_STATIC, "~Cancel", ...,
+ +                                  { 100, 30 },    // size
+ +                                  5               // spacing
+ +                               };
+ +
+ +          DLGHITEM DlgTemplate[] =
+ +              {
+ +                  START_TABLE,            // root table, must exist
+ +                      START_ROW(0),       // row 1 in the root table
+ +                          // create group on top
+ +      (1)                 START_GROUP_TABLE(&Group),
+ +                              START_ROW(0),
+ +      (2)                         CONTROL_DEF(&CnrDef),
+ +                              START_ROW(0),
+ +      (3)                         CONTROL_DEF(&Static),
+ +                          END_TABLE,      // end of group
+ +                      START_ROW(0),       // row 2 in the root table
+ +                          // two buttons next to each other
+ +      (4)                 CONTROL_DEF(&OKButton),
+ +      (5)                 CONTROL_DEF(&CancelButton),
+ +                  END_TABLE
+ +              }
+ *
+ *      This will produce a dlg like this:
+ *
+ +          ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
+ +          º                                   º
+ +          º ÚÄ Group (1) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿  º
+ +          º ³                              ³  º
+ +          º ³  ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿  ³  º
+ +          º ³  ³                        ³  ³  º
+ +          º ³  ³  Cnr inside group (2)  ³  ³  º
+ +          º ³  ³                        ³  ³  º
+ +          º ³  ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ  ³  º
+ +          º ³  Static below cnr (3)        ³  º
+ +          º ³                              ³  º
+ +          º ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ  º
+ +          º ÚÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿     º
+ +          º ³   OK (4)  ³ ³  Cancel (5) ³     º
+ +          º ÀÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ     º
+ +          º                                   º
+ +          ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍŒ
+ *
+ *      <B>Errors:</B>
+ *
+ *      This does not return a HWND, but an APIRET. This will be
+ *      one of the following:
+ *
+ *      --  NO_ERROR: only in that case, the phwndDlg ptr
+ *          receives the HWND of the new dialog, which can
+ *          then be given to WinProcessDlg. Don't forget
+ *          WinDestroyWindow.
+ *
+ *      --  ERROR_NOT_ENOUGH_MEMORY
+ *
+ *      --  DLGERR_ROW_BEFORE_TABLE: a row definition appeared
+ *          outside a table definition.
+ *
+ *      --  DLGERR_CONTROL_BEFORE_ROW: a control definition
+ *          appeared right after a table definition. You must
+ *          specify a row first.
+ *
+ *      --  DLGERR_NULL_CTL_DEF: TYPE_END_TABLE was specified,
+ *          but the CONTROLDEF ptr was NULL.
+ *
+ *      --  DLGERR_CANNOT_CREATE_FRAME: unable to create the
+ *          WC_FRAME window. Maybe an invalid owner was specified.
+ *
+ *      --  DLGERR_INVALID_CODE: invalid "Type" field in
+ *          DLGHITEM.
+ *
+ *      --  DLGERR_TABLE_NOT_CLOSED, DLGERR_TOO_MANY_TABLES_CLOSED:
+ *          improper nesting of TYPE_START_NEW_TABLE and
+ *          TYPE_END_TABLE fields.
+ *
+ *      <B>Example:</B>
+ *
+ *      The typical calling sequence would be:
+ *
+ +          HWND hwndDlg = NULLHANDLE;
+ +          if (NO_ERROR == dlghCreateDlg(&hwndDlg,
+ +                                        hwndOwner,
+ +                                        FCF_DLGBORDER | FCF_NOBYTEALIGN,
+ +                                        fnwpMyDlgProc,
+ +                                        "My Dlg Title",
+ +                                        G_aMyDialogTemplate,
+ +                                        ARRAYITEMSIZE(G_aMyDialogTemplate),
+ +                                        NULL))
+ +          {
+ +              ULONG idReturn = WinProcessDlg(pDlgData->hwndDlg);
+ +              WinDestroyWindow(hwndDlg);
+ +          }
+ *
+ *
+ *@@added V0.9.9 (2001-03-30) [umoeller]
+ */
+
+APIRET dlghCreateDlg(HWND *phwndDlg,            // out: new dialog
+                     HWND hwndOwner,
+                     ULONG flCreateFlags,       // in: standard FCF_* frame flags
+                     PFNWP pfnwpDialogProc,
+                     const char *pcszDlgTitle,
+                     PDLGHITEM paDlgItems,      // in: definition array
+                     ULONG cDlgItems,           // in: array item count (NOT array size)
+                     PVOID pCreateParams,       // in: for mp2 of WM_INITDLG
+                     const char *pcszControlsFont) // in: font for ctls with CTL_COMMON_FONT
+{
+    APIRET      arc = NO_ERROR;
+
+    #define SPACING     10
+
+    PTABLEDEF   pCurrentTable = NULL;
+    PROWDEF     pCurrentRow = NULL;
+    ULONG       ul;
+    LINKLIST    llStack;
+
+    PDLGPRIVATE  pDlgData = NEW(DLGPRIVATE);
+
+    if (!pDlgData)
+        return (ERROR_NOT_ENOUGH_MEMORY);
+
+    ZERO(pDlgData);
+    lstInit(&pDlgData->llTables, FALSE);
+    lstInit(&pDlgData->llControls, FALSE);
+
+    pDlgData->pcszControlsFont = pcszControlsFont;
+
+    /*
+     *  1) parse the table and create structures from it
+     *
+     */
+
+    lstInit(&llStack, TRUE);      // this is our stack for nested table definitions
+
+    for (ul = 0;
+         ul < cDlgItems;
+         ul++)
+    {
+        PDLGHITEM   pItemThis = &paDlgItems[ul];
+
+        switch (pItemThis->Type)
+        {
+            /*
+             * TYPE_START_NEW_TABLE:
+             *
+             */
+
+            case TYPE_START_NEW_TABLE:
+            {
+                // root table or nested?
+                BOOL fIsRoot = (pCurrentTable == NULL);
+
+                // push the current table on the stack
+                PSTACKITEM pStackItem = NEW(STACKITEM);
+                if (pStackItem)
+                {
+                    pStackItem->pLastTable = pCurrentTable;
+                    pStackItem->pLastRow = pCurrentRow;
+                    lstPush(&llStack, pStackItem);
+                }
+
+                // create new table
+                pCurrentTable = NEW(TABLEDEF);
+                if (!pCurrentTable)
+                    arc = ERROR_NOT_ENOUGH_MEMORY;
+                else
+                {
+                    ZERO(pCurrentTable);
+
+                    lstInit(&pCurrentTable->llRows, FALSE);
+
+                    if (pItemThis->ulData)
+                        // control specified: store it (this will become a PM group)
+                        pCurrentTable->pCtlDef = (PCONTROLDEF)pItemThis->ulData;
+
+                    if (fIsRoot)
+                        // root table:
+                        // append to dialog data list
+                        lstAppendItem(&pDlgData->llTables, pCurrentTable);
+                    else
+                    {
+                        // nested table:
+                        // create "table" column for this
+                        PCOLUMNDEF pColumnDef;
+                        arc = CreateColumn(pCurrentRow,
+                                           TRUE,        // nested table
+                                           pCurrentTable,
+                                           &pColumnDef);
+                        if (!arc)
+                            lstAppendItem(&pCurrentRow->llColumns, pColumnDef);
+                    }
+                }
+
+                pCurrentRow = NULL;
+            break; }
+
+            /*
+             * TYPE_START_NEW_ROW:
+             *
+             */
+
+            case TYPE_START_NEW_ROW:
+            {
+                if (!pCurrentTable)
+                    arc = DLGERR_ROW_BEFORE_TABLE;
+                else
+                {
+                    // create new row
+                    pCurrentRow = NEW(ROWDEF);
+                    if (!pCurrentRow)
+                        arc = ERROR_NOT_ENOUGH_MEMORY;
+                    else
+                    {
+                        ZERO(pCurrentRow);
+
+                        pCurrentRow->pOwningTable = pCurrentTable;
+                        lstInit(&pCurrentRow->llColumns, FALSE);
+
+                        lstAppendItem(&pCurrentTable->llRows, pCurrentRow);
+                    }
+                }
+            break; }
+
+            /*
+             * TYPE_CONTROL_DEF:
+             *
+             */
+
+            case TYPE_CONTROL_DEF:
+            {
+                PCOLUMNDEF pColumnDef;
+                arc = CreateColumn(pCurrentRow,
+                                   FALSE,        // no nested table
+                                   (PVOID)pItemThis->ulData,
+                                   &pColumnDef);
+                if (!arc)
+                    lstAppendItem(&pCurrentRow->llColumns, pColumnDef);
+            break; }
+
+            /*
+             * TYPE_END_TABLE:
+             *
+             */
+
+            case TYPE_END_TABLE:
+            {
+                PLISTNODE pNode = lstPop(&llStack);
+                if (!pNode)
+                    // nothing on the stack:
+                    arc = DLGERR_TOO_MANY_TABLES_CLOSED;
+                else
+                {
+                    PSTACKITEM pStackItem = (PSTACKITEM)pNode->pItemData;
+                    pCurrentTable = pStackItem->pLastTable;
+                    pCurrentRow = pStackItem->pLastRow;
+
+                    lstRemoveNode(&llStack, pNode);
+                }
+            break; }
+
+            default:
+                arc = DLGERR_INVALID_CODE;
+        }
+
+        if (arc)
+            break;
+    }
+
+    if (arc == NO_ERROR)
+        if (lstCountItems(&llStack))
+            arc = DLGERR_TABLE_NOT_CLOSED;
+
+    lstClear(&llStack);
+
+    if (arc == NO_ERROR)
+    {
+        /*
+         *  2) create empty dialog frame
+         *
+         */
+
+        FRAMECDATA      fcData = {0};
+
+        #define FCF_DIALOGBOX   0x40000000L
+
+        fcData.cb = sizeof(FRAMECDATA);
+        fcData.flCreateFlags = flCreateFlags | FCF_DIALOGBOX;
+
+        pDlgData->hwndDlg = WinCreateWindow(HWND_DESKTOP,
+                                            WC_FRAME,
+                                            (PSZ)pcszDlgTitle,
+                                            0,               // style; invisible for now
+                                            0, 0, 0, 0,
+                                            hwndOwner,
+                                            HWND_TOP,
+                                            0,               // ID
+                                            &fcData,
+                                            NULL);           // presparams
+
+        if (!pDlgData->hwndDlg)
+            arc = DLGERR_CANNOT_CREATE_FRAME;
+        else
+        {
+            SIZEL   szlClient = {0};
+            RECTL   rclClient;
+            HWND    hwndFocusItem = NULLHANDLE;
+
+            /*
+             *  3) compute size of client after computing all control sizes
+             *
+             */
+
+            ProcessAll(pDlgData,
+                       &szlClient,
+                       PROCESS_CALC_SIZES);
+
+            // this might have created a HPS and a font in dlgdata
+            // for auto-sizing controls
+            if (pDlgData->hps)
+            {
+                if (pDlgData->lcidLast)
+                {
+                    // delete old font
+                    GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);
+                    GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast);
+                    pDlgData->lcidLast = 0;
+                }
+
+                WinReleasePS(pDlgData->hps);
+                pDlgData->hps = NULLHANDLE;
+            }
+
+            WinSubclassWindow(pDlgData->hwndDlg, pfnwpDialogProc);
+
+            // calculate the frame size from the client size
+            rclClient.xLeft = 10;
+            rclClient.yBottom = 10;
+            rclClient.xRight = szlClient.cx + 2 * SPACING;
+            rclClient.yTop = szlClient.cy + 2 * SPACING;
+            WinCalcFrameRect(pDlgData->hwndDlg,
+                             &rclClient,
+                             FALSE);            // frame from client
+
+            WinSetWindowPos(pDlgData->hwndDlg,
+                            0,
+                            10,
+                            10,
+                            rclClient.xRight,
+                            rclClient.yTop,
+                            SWP_MOVE | SWP_SIZE | SWP_NOADJUST);
+
+            /*
+             *  4) compute positions of all controls
+             *
+             */
+
+            ProcessAll(pDlgData,
+                       &szlClient,
+                       PROCESS_CALC_POSITIONS);
+
+            /*
+             *  5) create control windows, finally
+             *
+             */
+
+            pDlgData->ptlTotalOfs.x = SPACING;
+            pDlgData->ptlTotalOfs.y = SPACING;
+
+            ProcessAll(pDlgData,
+                       &szlClient,
+                       PROCESS_CREATE_CONTROLS);
+
+            /*
+             *  6) WM_INITDLG, set focus
+             *
+             */
+
+            hwndFocusItem = (pDlgData->hwndFirstFocus)
+                                    ? pDlgData->hwndFirstFocus
+                                    : pDlgData->hwndDlg;
+            if (!WinSendMsg(pDlgData->hwndDlg,
+                            WM_INITDLG,
+                            (MPARAM)hwndFocusItem,
+                            (MPARAM)pCreateParams))
+            {
+                // if WM_INITDLG returns FALSE, this means
+                // the dlg proc has not changed the focus;
+                // we must then set the focus here
+                WinSetFocus(HWND_DESKTOP, hwndFocusItem);
+            }
+        }
+    }
+
+    if (pDlgData)
+    {
+        PLISTNODE pTableNode;
+
+        if (arc)
+        {
+            // error: clean up
+            if (pDlgData->hwndDlg)
+                WinDestroyWindow(pDlgData->hwndDlg);
+        }
+        else
+            // no error: output dialog
+            *phwndDlg = pDlgData->hwndDlg;
+
+        // in any case, clean up our mess:
+
+        // clean up the tables
+        FOR_ALL_NODES(&pDlgData->llTables, pTableNode)
+        {
+            PTABLEDEF pTable = (PTABLEDEF)pTableNode->pItemData;
+
+            // for each table, clean up the rows
+            PLISTNODE pRowNode;
+            FOR_ALL_NODES(&pTable->llRows, pRowNode)
+            {
+                PROWDEF pRow = (PROWDEF)pRowNode->pItemData;
+
+                // for each row, clean up the columns
+                PLISTNODE pColumnNode;
+                FOR_ALL_NODES(&pRow->llColumns, pColumnNode)
+                {
+                    PCOLUMNDEF pColumn = (PCOLUMNDEF)pColumnNode->pItemData;
+                    free(pColumn);
+                }
+                lstClear(&pRow->llColumns);
+
+                free(pRow);
+            }
+            lstClear(&pTable->llRows);
+
+            free(pTable);
+        }
+
+        lstClear(&pDlgData->llTables);
+        lstClear(&pDlgData->llControls);
+
+        free(pDlgData);
+    }
+
+    return (arc);
+}
+
+
Index: trunk/src/helpers/dosh.c
===================================================================
--- trunk/src/helpers/dosh.c	(revision 52)
+++ trunk/src/helpers/dosh.c	(revision 53)
@@ -1344,5 +1344,5 @@
  */
 
-APIRET doshCreatePath(PSZ pszPath,
+APIRET doshCreatePath(const char *pcszPath,
                       BOOL fHidden) // in: if TRUE, the new directories will get FILE_HIDDEN
 {
@@ -1352,5 +1352,5 @@
     ULONG   cbPath;
 
-    strcpy(path, pszPath);
+    strcpy(path, pcszPath);
     cbPath = strlen(path);
 
Index: trunk/src/helpers/gpih.c
===================================================================
--- trunk/src/helpers/gpih.c	(revision 52)
+++ trunk/src/helpers/gpih.c	(revision 53)
@@ -33,7 +33,9 @@
     // as unsigned char
 
+#define INCL_DOSSEMAPHORES
 #define INCL_DOSERRORS
 
 #define INCL_WINWINDOWMGR
+#define INCL_WINMESSAGEMGR
 #define INCL_WINPOINTERS
 #define INCL_WINSYS
@@ -92,4 +94,12 @@
  *      By contrast, the GpiBox expects an inclusive-inclusive rectangle.
  */
+
+/* ******************************************************************
+ *
+ *   Global variables
+ *
+ ********************************************************************/
+
+static HMTX     G_hmtxLCIDs = NULLHANDLE;
 
 /* ******************************************************************
@@ -492,4 +502,58 @@
 
 /*
+ *@@ gpihLockLCIDs:
+ *      requests the mutex for serializing the
+ *      lcids.
+ *
+ *      With GPI, lcids are a process-wide resource and not
+ *      guaranteed to be unique. In the worst case, while your
+ *      font routines are running, another thread modifies the
+ *      lcids and you get garbage. If your fonts suddenly
+ *      turn to "System Proportional", you know this has
+ *      happened.
+ *
+ *      As a result, whenever you work on lcids, request this
+ *      mutex during your processing. If you do this consistently
+ *      across all your code, you should be safe.
+ *
+ *      gpihFindFont uses this mutex. If you call GpiCreateLogFont
+ *      yourself somewhere, do this after you called this function.
+ *
+ *      Call gpihUnlockLCIDs to unlock.
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+BOOL gpihLockLCIDs(VOID)
+{
+    BOOL brc = FALSE;
+
+    if (G_hmtxLCIDs == NULLHANDLE)
+        // first call: create
+        brc = !DosCreateMutexSem(NULL,
+                                 &G_hmtxLCIDs,
+                                 0,
+                                 TRUE);     // request!
+    else
+        // subsequent calls: request
+        brc = !WinRequestMutexSem(G_hmtxLCIDs, SEM_INDEFINITE_WAIT);
+
+    return (brc);
+}
+
+/*
+ *@@ UnlockLCIDs:
+ *      releases the mutex for serializing the
+ *      lcids.
+ *
+ *@@added V0.9.9 (2001-04-01) [umoeller]
+ */
+
+VOID gpihUnlockLCIDs(VOID)
+{
+    DosReleaseMutexSem(G_hmtxLCIDs);
+}
+
+/*
  *@@ gpihQueryNextLCID:
  *      returns the next available lcid for the given HPS.
@@ -498,13 +562,9 @@
  *      Gets called by gpihFindFont automatically.
  *
- *      This is possibly the sickest code I've ever written.
- *
- *      Warning: This function is _not_ thread-safe.
- *      We use GpiQueryNumberSetIds to find out the no. of
- *      LCID's in use by the process. In the worst case,
- *      between that call and the call to GpiCreateLogFont,
- *      another font has been created, and you'll get garbage.
+ *      WARNING: This function by itself is not thread-safe.
+ *      See gpihLockLCIDs for how to serialize this.
  *
  *@@added V0.9.3 (2000-05-06) [umoeller]
+ *@@changed V0.9.9 (2001-04-01) [umoeller]: removed all those sick sub-allocs
  */
 
@@ -524,7 +584,63 @@
     else
     {
-        #define GQNCL_BLOCK_SIZE 400*sizeof(LONG)
+        // #define GQNCL_BLOCK_SIZE 400*sizeof(LONG)
+
+        PLONG  alTypes = NULL;  // object types
+        PSTR8  aNames = NULL;   // font names
+        PLONG  allcids = NULL;  // local identifiers
+
+        if (    (alTypes = (PLONG)malloc(lCount * sizeof(LONG)))
+             && (aNames = (PSTR8)malloc(lCount * sizeof(STR8)))
+             && (allcids = (PLONG)malloc(lCount * sizeof(LONG)))
+           )
+        {
+            if (GpiQuerySetIds(hps,
+                               lCount,
+                               alTypes,
+                               aNames,
+                               allcids))
+            {
+                // FINALLY we have all the lcids in use.
+                BOOL    fContinue = TRUE;
+                lcidNext = 1;
+
+                // now, check if this lcid is in use already:
+                while (fContinue)
+                {
+                    BOOL fFound = FALSE;
+                    ULONG ul;
+                    fContinue = FALSE;
+                    for (ul = 0;
+                         ul < lCount;
+                         ul++)
+                    {
+                        if (allcids[ul] == lcidNext)
+                        {
+                            fFound = TRUE;
+                            break;
+                        }
+                    }
+
+                    if (fFound)
+                    {
+                        // lcid found:
+                        // try next higher one
+                        lcidNext++;
+                        fContinue = TRUE;
+                    }
+                    // else: return that one
+                }
+            }
+        }
+
+        if (alTypes)
+            free(alTypes);
+        if (aNames)
+            free(aNames);
+        if (allcids)
+            free(allcids);
+
+/*
         PLONG   pBase;
-
         APIRET  arc;
 
@@ -612,4 +728,5 @@
             arc = DosFreeMem(pBase);
         }
+        */
     }
 
@@ -744,19 +861,11 @@
  *          certain point sizes only.
  *
- *      4)  <B>Warning: This function is _not_ thread-safe.</B>
- *          Since logical font IDs are shared across the
- *          process, this function should _not_ be called
- *          from several threads at the same time.
+ *      4)  Since logical font IDs are shared across the
+ *          process, a mutex is requested while the lcids are
+ *          being queried and/or manipulated. In other words,
+ *          this func is now thread-safe (V0.9.9).
  *
  *          This calls gpihQueryNextFontID in turn to find the
  *          next free lcid. See remarks there.
- *          If your fonts suddenly change to "System Proportional"
- *          in your application, you probably have a serialization
- *          problem.
- *
- *          To make this thread-safe, create your own wrapper
- *          function which calls this function while a mutex
- *          semaphore is held by the wrapper. Then use only
- *          the wrapper in your code.
  *
  *      <B>Font metrics:</B>
@@ -793,4 +902,5 @@
  *@@changed V0.9.3 (2000-05-06) [umoeller]: usFormat didn't work; fixed
  *@@changed V0.9.4 (2000-08-08) [umoeller]: added fFamily
+ *@@changed V0.9.9 (2001-04-01) [umoeller]: made this thread-safe, finally
  */
 
@@ -981,11 +1091,16 @@
     free(pfm);
 
-    // new logical font ID: last used plus one
-    lLCIDReturn = gpihQueryNextFontID(hps);
-
-    GpiCreateLogFont(hps,
-                     NULL,  // don't create "logical font name" (STR8)
-                     lLCIDReturn,
-                     &FontAttrs);
+    if (gpihLockLCIDs())        // V0.9.9 (2001-04-01) [umoeller]
+    {
+        // new logical font ID: last used plus one
+        lLCIDReturn = gpihQueryNextFontID(hps);
+
+        GpiCreateLogFont(hps,
+                         NULL,  // don't create "logical font name" (STR8)
+                         lLCIDReturn,
+                         &FontAttrs);
+
+        gpihUnlockLCIDs();
+    }
 
     return (lLCIDReturn);
@@ -1025,39 +1140,42 @@
  *
  *@@added V0.9.0 [umoeller]
- */
-
-LONG gpihFindPresFont(HWND hwnd,          // in: window to search for presparam
+ *@@changed V0.9.9 (2001-04-01) [umoeller]: now supporting NULLHANDLE hwnd
+ */
+
+LONG gpihFindPresFont(HWND hwnd,          // in: window to search for presparam or NULLHANDLE
                       BOOL fInherit,      // in: search parent windows too?
                       HPS hps,            // in: HPS for font selection
-                      PSZ pszDefaultFont, // in: default font if not found (i.e. "8.Helv")
+                      const char *pcszDefaultFont, // in: default font if not found (i.e. "8.Helv")
                       PFONTMETRICS pFontMetrics, // out: font metrics of created font (optional)
                       PLONG plSize)       // out: presparam's point size (optional)
 {
     CHAR    szPPFont[200] = "";
-    PSZ     pszFontFound = 0;
+    const char *pcszFontFound = 0;
     CHAR    szFaceName[300] = "";
     ULONG   ulFontSize = 0;
 
-    if (WinQueryPresParam(hwnd,
-                          PP_FONTNAMESIZE,          // first PP to query
-                          0,                        // second PP to query
-                          NULL,                     // out: which one is returned
-                          (ULONG)sizeof(szPPFont),  // in: buffer size
-                          (PVOID)&szPPFont,         // out: PP value returned
-                          (fInherit)
-                               ? 0
-                               : QPF_NOINHERIT))
+    if (    (hwnd)        // V0.9.9 (2001-04-01) [umoeller]
+         && (WinQueryPresParam(hwnd,
+                               PP_FONTNAMESIZE,          // first PP to query
+                               0,                        // second PP to query
+                               NULL,                     // out: which one is returned
+                               (ULONG)sizeof(szPPFont),  // in: buffer size
+                               (PVOID)&szPPFont,         // out: PP value returned
+                               (fInherit)
+                                    ? 0
+                                    : QPF_NOINHERIT))
+       )
         // PP found:
-        pszFontFound = szPPFont;
+        pcszFontFound = szPPFont;
     else
-        pszFontFound = pszDefaultFont;
-
-    if (pszFontFound)
+        pcszFontFound = pcszDefaultFont;
+
+    if (pcszFontFound)
     {
-        PCHAR   pcDot = strchr(pszFontFound, '.');
+        const char *pcDot = strchr(pcszFontFound, '.');
         if (pcDot)
         {
             // _Pmpf(("Found font PP: %s", pszFontFound));
-            sscanf(pszFontFound, "%lu", &ulFontSize);
+            sscanf(pcszFontFound, "%lu", &ulFontSize);
             if (plSize)
                 *plSize = ulFontSize;
Index: trunk/src/helpers/helpers_post.in
===================================================================
--- trunk/src/helpers/helpers_post.in	(revision 52)
+++ trunk/src/helpers/helpers_post.in	(revision 53)
@@ -85,22 +85,28 @@
                $(HLPINC)\dosh.h $(HLPINC)\stringh.h $(HLPINC)\xstring.h
 
+$(OUTPUTDIR)\datetime.obj:          $(@B).c $(HLPINC)\$(@B).h \
+               $(PROJECTINC)\setup.h
+
+$(OUTPUTDIR)\debug.obj:     $(@B).c $(HLPINC)\$(@B).h \
+               $(PROJECTINC)\setup.h \
+               $(HLPINC)\dosh.h
+
+$(OUTPUTDIR)\dialog.obj:     $(@B).c $(HLPINC)\$(@B).h \
+               $(PROJECTINC)\setup.h \
+               $(HLPINC)\comctl.h $(HLPINC)\gpih.h $(HLPINC)\linklist.h \
+                $(HLPINC)\standards.h $(HLPINC)\stringh.h \
+               $(HLPINC)\winh.h
+
+$(OUTPUTDIR)\dosh.obj:      $(@B).c $(HLPINC)\$(@B).h \
+               $(PROJECTINC)\setup.h
+
+$(OUTPUTDIR)\dosh2.obj:      $(@B).c $(HLPINC)\dosh.h \
+               $(PROJECTINC)\setup.h \
+               $(HLPINC)\stringh.h
+
+$(OUTPUTDIR)\eah.obj:       $(@B).c $(HLPINC)\$(@B).h \
+               $(PROJECTINC)\setup.h
+
 $(OUTPUTDIR)\encodings.obj:         $(@B).c $(INC)\encodings\*.h \
-               $(PROJECTINC)\setup.h
-
-$(OUTPUTDIR)\datetime.obj:          $(@B).c $(HLPINC)\$(@B).h \
-               $(PROJECTINC)\setup.h
-
-$(OUTPUTDIR)\debug.obj:     $(@B).c $(HLPINC)\$(@B).h \
-               $(PROJECTINC)\setup.h \
-               $(HLPINC)\dosh.h
-
-$(OUTPUTDIR)\dosh.obj:      $(@B).c $(HLPINC)\$(@B).h \
-               $(PROJECTINC)\setup.h
-
-$(OUTPUTDIR)\dosh2.obj:      $(@B).c $(HLPINC)\dosh.h \
-               $(PROJECTINC)\setup.h \
-               $(HLPINC)\stringh.h
-
-$(OUTPUTDIR)\eah.obj:       $(@B).c $(HLPINC)\$(@B).h \
                $(PROJECTINC)\setup.h
 
Index: trunk/src/helpers/helpers_pre.in
===================================================================
--- trunk/src/helpers/helpers_pre.in	(revision 52)
+++ trunk/src/helpers/helpers_pre.in	(revision 53)
@@ -80,4 +80,5 @@
 $(OUTPUTDIR)\configsys.obj \
 $(OUTPUTDIR)\cnrh.obj \
+$(OUTPUTDIR)\dialog.obj \
 $(OUTPUTDIR)\gpih.obj \
 $(OUTPUTDIR)\memdebug.obj \
Index: trunk/src/helpers/linklist.c
===================================================================
--- trunk/src/helpers/linklist.c	(revision 52)
+++ trunk/src/helpers/linklist.c	(revision 53)
@@ -1071,9 +1071,7 @@
  *      a pseudo-stack LIFO LINKLIST using lstPush.
  *
- *      Note that to really "pop" the item, i.e. remove
- *      it from the list, you MUST call lstRemoveNode
- *      with the list node returned from this function,
- *      or otherwise you keep getting the same last item
- *      on subsequent calls.
+ *      If (fRemove == TRUE), the item is also removed
+ *      from the stack. Otherwise it remains there so the
+ *      next lstPop would return the next item.
  *
  *      Example:
@@ -1081,13 +1079,12 @@
  +          LINKLIST ll;
  +          PLISTNODE pNode;
- +          lstInit(&ll, FALSE;
+ +          lstInit(&ll, FALSE);
  +
  +          lstPush(&ll, (PVOID)1);
  +          lstPush(&ll, (PVOID)2);
  +
- +          pNode = lstPop(&ll);     // returns the node containing "2"
+ +          pNode = lstPop(&ll);  // returns the node containing "2"
  +          lstRemoveNode(pNode);
- +          pNode = lstPop(&ll);     // returns the node containing "1"
- +          lstRemoveNode(pNode);
+ +          pNode = lstPop(&ll);  // returns the node containing "1"
  *
  *@@added V0.9.3 (2000-05-18) [umoeller]
@@ -1097,13 +1094,5 @@
 {
     return (pList->pLast);
-    /* unsigned long cItems = lstCountItems(pList);
-    if (cItems)
-    {
-        // list not empty:
-        // get last item on the list
-        pNodeReturn = lstNodeFromIndex(pList,
-                                       cItems - 1);
-    }
-    return (pNodeReturn); */
-}
-
+}
+
+
Index: trunk/src/helpers/tmsgfile.c
===================================================================
--- trunk/src/helpers/tmsgfile.c	(revision 52)
+++ trunk/src/helpers/tmsgfile.c	(revision 53)
@@ -541,5 +541,5 @@
         GetTimeStamp(&fs3MessageFile, szFileStampCurrent, sizeof(szFileStampCurrent));
 
-        _Pmpf(("    Timestamp for %s: '%s'", pszMessageFile, szFileStampCurrent));
+        // _Pmpf(("    Timestamp for %s: '%s'", pszMessageFile, szFileStampCurrent));
 
         // determine saved timestamp
@@ -550,5 +550,5 @@
                           &ulStampLength);
 
-        _Pmpf(("    Saved timestamp for %s: '%s'", pszMessageFile, szFileStampOld));
+        // _Pmpf(("    Saved timestamp for %s: '%s'", pszMessageFile, szFileStampOld));
 
         // compare timestamps
@@ -587,10 +587,10 @@
             if (rc == NO_ERROR)
             {
-                _Pmpf(("      --> using precompiled table"));
+                // _Pmpf(("      --> using precompiled table"));
                 break;
             }
         } // end if
 
-        _Pmpf(("      --> recompiling table"));
+        // _Pmpf(("      --> recompiling table"));
 
         // recompilation needed:
@@ -735,8 +735,8 @@
                     ulCurrentMessageLen);
 
-            _Pmpf(("Found %s at %d, length %d",
+            /* _Pmpf(("Found %s at %d, length %d",
                     pCurrentNameStart,
                     ulCurrentMessagePos,
-                    ulCurrentMessageLen));
+                    ulCurrentMessageLen)); */
 
             // need more space ?
@@ -744,5 +744,5 @@
             {
                 PBYTE           pbTmp;
-                _Pmpf(("  Re-allocating!!"));
+                // _Pmpf(("  Re-allocating!!"));
 
                 cbTableDataAllocated += ulFileDataLength / 2;
@@ -790,5 +790,5 @@
                 {
                     // restore original "last write" date
-                    _Pmpf(("Resetting file date"));
+                    // _Pmpf(("Resetting file date"));
                     fs3Tmp.fdateLastWrite = fs3MessageFile.fdateLastWrite;
                     fs3Tmp.ftimeLastWrite = fs3MessageFile.ftimeLastWrite;
