Index: trunk/src/helpers/dosh.c
===================================================================
--- trunk/src/helpers/dosh.c	(revision 68)
+++ trunk/src/helpers/dosh.c	(revision 69)
@@ -230,4 +230,22 @@
     }
     return (pszReturn);
+}
+
+/*
+ *@@ doshQuerySysUptime:
+ *      returns the system uptime in milliseconds.
+ *      This can be used for time comparisons.
+ *
+ *@@added V0.9.12 (2001-05-18) [umoeller]
+ */
+
+ULONG doshQuerySysUptime(VOID)
+{
+    ULONG ulms;
+    DosQuerySysInfo(QSV_MS_COUNT,
+                    QSV_MS_COUNT,
+                    &ulms,
+                    sizeof(ulms));
+    return (ulms);
 }
 
Index: trunk/src/helpers/dosh2.c
===================================================================
--- trunk/src/helpers/dosh2.c	(revision 68)
+++ trunk/src/helpers/dosh2.c	(revision 69)
@@ -1115,4 +1115,182 @@
 
 /*
+ *@@ ParseBldLevel:
+ *      called from doshExecQueryBldLevel to parse
+ *      the BLDLEVEL string.
+ *
+ *      On entry, caller has copied the string into
+ *      pExec->pszDescription. The string is
+ *      null-terminated.
+ *
+ *      The BLDLEVEL string comes in two flavors.
+ *
+ *      --  The standard format is:
+ *
+ +              @#VENDOR:VERSION#@DESCRIPTION
+ *
+ *          DESCRIPTION can have leading spaces, but
+ *          need to have them.
+ *
+ *      --  However, there is an extended version
+ *          in that the DESCRIPTION field is split
+ *          up even more. The marker for this seems
+ *          to be that the description starts out
+ *          with "##1##".
+ *
+ +              ##1## DATETIME BUILDMACHINE:ASD:LANG:CTRY:REVISION:UNKNOWN:FIXPAK@@DESCRIPTION
+ *
+ *          The problem is that the DATETIME field comes
+ *          in several flavors. IBM uses things like
+ *
+ +              "Thu Nov 30 15:30:37 2000 BWBLD228"
+ *
+ *          while DANIS506.ADD has
+ *
+ +              "15.12.2000 18:22:57      Nachtigall"
+ *
+ *          Looks like the date/time string is standardized
+ *          to have 24 characters then.
+ *
+ *@@added V0.9.12 (2001-05-18) [umoeller]
+ *@@changed V0.9.12 (2001-05-19) [umoeller]: added extended BLDLEVEL support
+ */
+
+VOID ParseBldLevel(PEXECUTABLE pExec)
+{
+    const char  *pStartOfAuthor = 0,
+                *pStartOfVendor = 0;
+
+    // @#VENDOR:VERSION#@ DESCRIPTION
+    // but skip the first byte, which has the string length
+    pStartOfVendor = strstr(pExec->pszDescription,
+                            "@#");
+    if (pStartOfVendor)
+    {
+        const char *pStartOfInfo = strstr(pStartOfVendor + 2,
+                                          "#@");
+        if (pStartOfInfo)
+        {
+            const char *pEndOfVendor = strchr(pStartOfVendor + 2,
+                                              ':');
+            if (pEndOfVendor)
+            {
+                pExec->pszVendor = strhSubstr(pStartOfVendor + 2,
+                                              pEndOfVendor);
+                pExec->pszVersion = strhSubstr(pEndOfVendor + 1,
+                                               pStartOfInfo);
+                // skip "@#" in DESCRIPTION string
+                pStartOfInfo += 2;
+
+                // now check if we have extended DESCRIPTION V0.9.12 (2001-05-19) [umoeller]
+                if (    (strlen(pStartOfInfo) > 6)
+                     && (!memcmp(pStartOfInfo, "##1##", 5))
+                   )
+                {
+                    // yes: parse that beast
+                    const char *p = pStartOfInfo + 5;
+
+                    // get build date/time
+                    if (strlen(p) > 24)
+                    {
+                        // date/time seems to be fixed 24 chars in length
+                        pExec->pszBuildDateTime = (PSZ)malloc(25);
+                        if (pExec->pszBuildDateTime)
+                        {
+                            memcpy(pExec->pszBuildDateTime,
+                                   p,
+                                   24);
+                            pExec->pszBuildDateTime[24] = '\0';
+
+                            p += 24;
+
+                            // now we're at the colon-separated
+                            // strings, first of which is the
+                            // build machine;
+                            // skip leading spaces
+                            while (*p == ' ')
+                                p++;
+
+                            if (*p)
+                            {
+                                char **papsz[] =
+                                    {
+                                        &pExec->pszBuildMachine,
+                                        &pExec->pszASD,
+                                        &pExec->pszLanguage,
+                                        &pExec->pszCountry,
+                                        &pExec->pszRevision,
+                                        &pExec->pszUnknown,
+                                        &pExec->pszFixpak
+                                    };
+                                ULONG ul;
+
+                                for (ul = 0;
+                                     ul < sizeof(papsz) / sizeof(papsz[0]);
+                                     ul++)
+                                {
+                                    BOOL fStop = FALSE;
+                                    const char *pNextColon = strchr(p, ':'),
+                                               *pDoubleAt = strstr(p, "@@");
+                                    if (!pNextColon)
+                                    {
+                                        // last item:
+                                        if (pDoubleAt)
+                                            pNextColon = pDoubleAt;
+                                        else
+                                            pNextColon = p + strlen(p);
+
+                                        fStop = TRUE;
+                                    }
+
+                                    if (    (fStop)
+                                         || (    (pNextColon)
+                                              && (    (!pDoubleAt)
+                                                   || (pNextColon < pDoubleAt)
+                                                 )
+                                            )
+                                       )
+                                    {
+                                        if (pNextColon > p + 1)
+                                            *(papsz[ul]) = strhSubstr(p, pNextColon);
+                                    }
+                                    else
+                                        break;
+
+                                    if (fStop)
+                                        break;
+
+                                    p = pNextColon + 1;
+                                }
+                            }
+                        }
+                    }
+
+                    pStartOfInfo = strstr(p,
+                                          "@@");
+                    if (pStartOfInfo)
+                        pStartOfInfo += 2;
+                }
+
+                // -- if we had no extended DESCRIPTION,
+                //    pStartOfInfo points to regular description now
+                // -- if we parse the extended DESCRIPTION above,
+                //    pStartOfInfo points to after @@ now
+                // -- if we had an error, pStartOfInfo is NULL
+                if (pStartOfInfo)
+                {
+                    // add the regular DESCRIPTION then
+                    // skip leading spaces in info string
+                    while (*pStartOfInfo == ' ')
+                        pStartOfInfo++;
+                    if (*pStartOfInfo)  // V0.9.9 (2001-04-04) [umoeller]
+                        // and copy until end of string
+                        pExec->pszInfo = strdup(pStartOfInfo);
+                }
+            }
+        }
+    }
+}
+
+/*
  *@@ doshExecQueryBldLevel:
  *      this retrieves buildlevel information for an
@@ -1168,4 +1346,5 @@
  *@@changed V0.9.1 (99-12-06): fixed memory leak
  *@@changed V0.9.9 (2001-04-04) [umoeller]: added more error checking
+ *@@changed V0.9.12 (2001-05-18) [umoeller]: extracted ParseBldLevel
  */
 
@@ -1243,7 +1422,4 @@
                             else
                             {
-                                const char  *pStartOfAuthor = 0,
-                                            *pStartOfVendor = 0;
-
                                 memcpy(pExec->pszDescription,
                                        pszNameTable + 1,        // skip length byte
@@ -1252,34 +1428,5 @@
                                 *(pExec->pszDescription + (*pszNameTable)) = 0;
 
-                                // now parse the damn thing:
-                                // @#VENDOR:VERSION#@ DESCRIPTION
-                                // but skip the first byte, which has the string length
-                                pStartOfVendor = strstr(pExec->pszDescription,
-                                                        "@#");
-                                if (pStartOfVendor)
-                                {
-                                    const char *pStartOfInfo = strstr(pStartOfVendor + 2,
-                                                                      "#@");
-                                    if (pStartOfInfo)
-                                    {
-                                        PSZ pEndOfVendor = strchr(pStartOfVendor + 2,
-                                                                  ':');
-                                        if (pEndOfVendor)
-                                        {
-                                            pExec->pszVendor = strhSubstr(pStartOfVendor + 2,
-                                                                          pEndOfVendor);
-                                            pExec->pszVersion = strhSubstr(pEndOfVendor + 1,
-                                                                           pStartOfInfo);
-                                            // skip "@#" in info string
-                                            pStartOfInfo += 2;
-                                            // skip leading spaces in info string
-                                            while (*pStartOfInfo == ' ')
-                                                pStartOfInfo++;
-                                            if (*pStartOfInfo)  // V0.9.9 (2001-04-04) [umoeller]
-                                                // and copy until end of string
-                                                pExec->pszInfo = strdup(pStartOfInfo);
-                                        }
-                                    }
-                                }
+                                ParseBldLevel(pExec);
                             }
                         }
Index: trunk/src/helpers/stringh.c
===================================================================
--- trunk/src/helpers/stringh.c	(revision 68)
+++ trunk/src/helpers/stringh.c	(revision 69)
@@ -1115,4 +1115,6 @@
  *@@ strhBeautifyTitle:
  *      replaces all line breaks (0xd, 0xa) with spaces.
+ *
+ *@@changed V0.9.12 (2001-05-17) [pr]: multiple line break chars. end up as only 1 space
  */
 
@@ -1120,15 +1122,22 @@
 {
     BOOL rc = FALSE;
-    CHAR *p;
-    while ((p = strchr(psz, 0xa)))
-    {
-        *p = ' ';
-        rc = TRUE;
-    }
-    while ((p = strchr(psz, 0xd)))
-    {
-        *p = ' ';
-        rc = TRUE;
-    }
+    CHAR *p = psz;
+
+    while(*p)
+        if (   (*p == '\r')
+            || (*p == '\n')
+           )
+        {
+            rc = TRUE;
+            if (   (p != psz)
+                && (p[-1] == ' ')
+               )
+                memmove(p, p + 1, strlen(p));
+            else
+                *p++ = ' ';
+        }
+        else
+            p++;
+
     return (rc);
 }
Index: trunk/src/helpers/timer.c
===================================================================
--- trunk/src/helpers/timer.c	(revision 68)
+++ trunk/src/helpers/timer.c	(revision 69)
@@ -443,5 +443,7 @@
                     PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
 
-                    if (pTimer->ulNextFire < ulTimeNow)
+                    if (    (pTimer)
+                         && (pTimer->ulNextFire < ulTimeNow)
+                       )
                     {
                         // this timer has elapsed:
Index: trunk/src/helpers/winh.c
===================================================================
--- trunk/src/helpers/winh.c	(revision 68)
+++ trunk/src/helpers/winh.c	(revision 69)
@@ -57,4 +57,5 @@
 #define INCL_WINPROGRAMLIST
 #define INCL_WINSWITCHLIST
+#define INCL_WINBUTTONS
 #define INCL_WINMENUS
 #define INCL_WINSCROLLBARS
@@ -88,4 +89,80 @@
 
 /*
+ *@@category: Helpers\PM helpers\Wrappers
+ */
+
+/* ******************************************************************
+ *
+ *   Wrappers
+ *
+ ********************************************************************/
+
+#ifdef WINH_STANDARDWRAPPERS
+
+    /*
+     *@@ winhSendMsg:
+     *      wrapper for WinSendMsg.
+     *
+     *      If WINH_STANDARDWRAPPERS is #defined before
+     *      including win.h, all WinSendMsg calls are
+     *      redefined to use this wrapper instead. This
+     *      reduces the amount of external fixups required
+     *      for loading the module.
+     *
+     *@@added V0.9.12 (2001-05-18) [umoeller]
+     */
+
+    MRESULT winhSendMsg(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+    {
+        // put the call in brackets so the macro won't apply here
+        return ((WinSendMsg)(hwnd, msg, mp1, mp2));
+    }
+
+    /*
+     *@@ winhPostMsg:
+     *      wrapper for WinPostMsg.
+     *
+     *      If WINH_STANDARDWRAPPERS is #defined before
+     *      including win.h, all WinSendMsg calls are
+     *      redefined to use this wrapper instead. This
+     *      reduces the amount of external fixups required
+     *      for loading the module.
+     *
+     *@@added V0.9.12 (2001-05-18) [umoeller]
+     */
+
+    BOOL winhPostMsg(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+    {
+        // put the call in brackets so the macro won't apply here
+        return ((WinPostMsg)(hwnd, msg, mp1, mp2));
+    }
+
+    /*
+     *@@ winhWindowFromID:
+     *
+     *@@added V0.9.12 (2001-05-18) [umoeller]
+     */
+
+    HWND winhWindowFromID(HWND hwnd, ULONG id)
+    {
+        // put the call in brackets so the macro won't apply here
+        return ((WinWindowFromID)(hwnd, id));
+    }
+
+    /*
+     *@@ winhQueryWindow:
+     *
+     *@@added V0.9.12 (2001-05-18) [umoeller]
+     */
+
+    HWND winhQueryWindow(HWND hwnd, LONG lCode)
+    {
+        // put the call in brackets so the macro won't apply here
+        return ((WinQueryWindow)(hwnd, lCode));
+    }
+
+#endif // WINH_STANDARDWRAPPERS
+
+/*
  *@@category: Helpers\PM helpers\Rectangle helpers
  */
@@ -119,4 +196,82 @@
 
 /*
+ *@@category: Helpers\PM helpers\Generics
+ */
+
+/* ******************************************************************
+ *
+ *   Generics
+ *
+ ********************************************************************/
+
+/*
+ *@@ winhSetDlgItemChecked:
+ *      checks a check box.
+ *
+ *      This has been turned into a real function
+ *      because this is used hundreds of times in
+ *      XWP, and each WinSendDlgItemMsg in each
+ *      macro produced a fixup relocation record.
+ *
+ *@@added V0.9.12 (2001-05-18) [umoeller]
+ */
+
+BOOL winhSetDlgItemChecked(HWND hwnd,       // in: dialog
+                           SHORT id,        // in: dialog item ID
+                           SHORT bCheck)    // in: 0, 1, or (for tri-state) 2
+{
+    return ((BOOL)WinSendDlgItemMsg(hwnd,
+                                    id,
+                                    BM_SETCHECK,
+                                    MPFROMSHORT(bCheck),
+                                    MPNULL));
+}
+
+/*
+ *@@ winhIsDlgItemChecked:
+ *      returns the current check state of the
+ *      specified check box, which can be 0, 1,
+ *      or (for tri-state checkboxes) 2.
+ *
+ *@@added V0.9.12 (2001-05-18) [umoeller]
+ */
+
+SHORT winhIsDlgItemChecked(HWND hwnd,       // in: dialog
+                           SHORT id)        // in: dialog item ID
+{
+    return (SHORT1FROMMR(WinSendDlgItemMsg(hwnd,
+                                           id,
+                                           BM_QUERYCHECK,
+                                           MPNULL,
+                                           MPNULL)));
+}
+
+/*
+ *@@ winhEnableDlgItem:
+ *
+ *@@added V0.9.12 (2001-05-18) [umoeller]
+ */
+
+BOOL winhEnableDlgItem(HWND hwndDlg,
+                       SHORT id,
+                       BOOL fEnable)
+{
+    return (WinEnableWindow(WinWindowFromID(hwndDlg, id), fEnable));
+}
+
+/*
+ *@@ winhIsDlgItemEnabled:
+ *
+ *@@added V0.9.12 (2001-05-18) [umoeller]
+ */
+
+BOOL winhIsDlgItemEnabled(HWND hwndDlg,
+                          SHORT id)
+{
+    return (WinIsWindowEnabled(WinWindowFromID(hwndDlg, id)));
+}
+
+
+/*
  *@@category: Helpers\PM helpers\Menu helpers
  */
@@ -127,4 +282,22 @@
  *
  ********************************************************************/
+
+/*
+ *@@ winhQueryMenuItem:
+ *      wrapper around MM_QUERYITEM.
+ *
+ *@@added V0.9.12 (2001-05-18) [umoeller]
+ */
+
+BOOL winhQueryMenuItem(HWND hwndMenu,
+                       USHORT usItemID,
+                       BOOL fSearchSubmenus,
+                       PMENUITEM pmi)           // out: MENUITEM data
+{
+    return ((BOOL)WinSendMsg(hwndMenu,
+                             MM_QUERYITEM,
+                             MPFROM2SHORT(usItemID, fSearchSubmenus),
+                             (MPARAM)pmi));
+}
 
 /*
@@ -1525,5 +1698,5 @@
                                                     // which considers this inclusive!
                          LONG ulViewportPels,       // in: total viewport dimension,
-                                                    // into which *plCurPelsOfs is an offset
+                                                    // into which *pulCurPelsOfs is an offset
                          USHORT usLineStepPels,     // in: pixels to scroll line-wise
                                                     // (scroll bar buttons pressed)
@@ -1602,5 +1775,4 @@
     if (*pulCurPelsOfs > (lMaxAllowedUnitOfs * usScrollUnitPels))
     {
-        // _Pmpf(("        !!! limiting 2: %d to %d", *plCurUnitOfs, lMaxAllowedUnitOfs));
         *pulCurPelsOfs = (lMaxAllowedUnitOfs * usScrollUnitPels);
     }
