Index: trunk/src/helpers/helpers_pre.in
===================================================================
--- trunk/src/helpers/helpers_pre.in	(revision 173)
+++ trunk/src/helpers/helpers_pre.in	(revision 174)
@@ -64,4 +64,5 @@
 $(OUTPUTDIR)\level.obj \
 $(OUTPUTDIR)\nls.obj \
+$(OUTPUTDIR)\nlscache.obj \
 $(OUTPUTDIR)\procstat.obj \
 $(OUTPUTDIR)\prfh.obj \
Index: trunk/src/helpers/math.c
===================================================================
--- trunk/src/helpers/math.c	(revision 173)
+++ trunk/src/helpers/math.c	(revision 174)
@@ -67,41 +67,4 @@
 
 }
-
-/* binary GCD alg. Based on following facts.
-   If N and M are even, then gcd(N,M) = 2 * gcd (N/2, M/2)
-   If N even and M odd, then gcd(N,M) = gcd(N/2, M)
-   If N,M odd, then |N-M|<max(N,M) thus guaranteeing that
-             we can use gcd(N,M) = gcd(N-M,M) to reduce.
-   fact: a&1 is 1 iff a is odd. Mult. and Div. by 2 more efficiently
-   implemented as shift-left 1 and shift-right 1 resp.
-
-    Found at http://remus.rutgers.edu/~rhoads/Code/int_gcd.c
-*/
-
-/* long gcd( long a, long b)
-{
-  int t;
-
-  if (a < 0) a = -a;
-  if (!b) return a;
-  if (b < 0) b = -b;
-  if (!a) return b;
-  t = 0;
-  while (! ((a|b) & 1)) {a >>= 1; b >>= 1; ++t;}
-  while (! (a&1)) a >>= 1;
-  while (! (b&1)) b >>= 1;
-  while (a != b)
-     {
-     if (a > b)
-         {
-         a -= b;
-         do {a >>= 1;} while (! (a&1));
-         }
-     else {
-         b -= a;
-         do {b >>= 1;} while (! (b&1));
-         }
-     }
-  return(a<<t); */
 
 /*
@@ -393,294 +356,3 @@
 }
 
-/* typedef struct _PRIMEDATA
-{
-    LINKLIST    llPrimes;
-    int         iCurrentInt;
-} PRIMEDATA, *PPRIMEDATA;
-
-
-typedef struct _PRIMEENTRY
-{
-    int         iPrime;
-    int         iLowestPowerFound;
-                // lowest power that was found for this prime number;
-                // if 0, a prime was previously present and then not
-                // for a later prime
-    int         iLastInt;
-} PRIMEENTRY, *PPRIMEENTRY; */
-
-/*
- *@@ GCDMultiCallbackCreate:
- *      first callback for mathGCDMulti.
- */
-
-/* int XWPENTRY GCDMultiCallbackCreate(int n,             // in: prime
-                                    int p,             // in: power
-                                    void *pUser)       // in: user param, here root of tree
-{
-    // see if we had this
-    PPRIMEDATA pData = (PPRIMEDATA)pUser;
-    PLISTNODE pNode;
-    PPRIMEENTRY pEntry;
-    for (pNode = lstQueryFirstNode(&pData->llPrimes);
-         pNode;
-         pNode = pNode->pNext)
-    {
-        pEntry = (PPRIMEENTRY)pNode->pItemData;
-        if (pEntry->iPrime == n)
-        {
-            // mark this as processed so we can later see
-            // if the current integer had this prime factor
-            // at all
-            pEntry->iLastInt = pData->iCurrentInt;
-
-            // printf(" %d^%d", n, p);
-
-            // and stop
-            return 0;
-        }
-    }
-
-    // no entry for this yet:
-    // printf(" %d^%d", n, p);
-    pEntry = NEW(PRIMEENTRY);
-    pEntry->iPrime = n;
-    pEntry->iLowestPowerFound = p;
-    pEntry->iLastInt = pData->iCurrentInt;
-    lstAppendItem(&pData->llPrimes, pEntry);
-
-    return (0);
-} */
-
-/*
- *@@ GCDMultiCallbackLowest:
- *      second callback for mathGCDMulti.
- */
-
-/* int XWPENTRY GCDMultiCallbackLowest(int n,             // in: prime
-                                    int p,             // in: power
-                                    void *pUser)       // in: user param, here root of tree
-{
-    // see if we had this
-    PPRIMEDATA pData = (PPRIMEDATA)pUser;
-    PLISTNODE pNode;
-    PPRIMEENTRY pEntry;
-    for (pNode = lstQueryFirstNode(&pData->llPrimes);
-         pNode;
-         pNode = pNode->pNext)
-    {
-        pEntry = (PPRIMEENTRY)pNode->pItemData;
-        if (pEntry->iPrime == n)
-        {
-            if (p < pEntry->iLowestPowerFound)
-            {
-                pEntry->iLowestPowerFound = p;
-                // printf(" %d^%d", n, p);
-            }
-            pEntry->iLastInt = pData->iCurrentInt;
-
-            // and stop
-            return 0;
-        }
-    }
-
-    exit(1);
-
-    return (0);
-} */
-
-/* int mathGCDMulti(int *paNs,             // in: array of integers
-                 int cNs)               // in: array item count (NOT array size)
-{
-    int i;
-    double dGCD;
-
-    PLISTNODE pNode;
-    PPRIMEENTRY pEntry;
-
-    PRIMEDATA Data;
-    lstInit(&Data.llPrimes, TRUE);
-
-    // this is done by prime-factoring each frequency
-    // and then multiplying all primes with the lowest
-    // common power that we found:
-
-    // Example 1: If we have two timers with freq.
-    // 1000 and 1500, obviously, the master timer
-    // should run at 500 ms.
-
-    // With prime factorization, we end up like this:
-
-    // 1000 = 2^3 *       5^3
-    // 1500 = 2^2 * 3^1 * 5^3
-
-    // so the highest power for factor 2 is 2;
-    //    the highest power for factor 3 is 0;
-    //    the highest power for factor 5 is 3;
-
-    // freq = 2^2 *       5^3 = 500
-
-    // Example 2: If we have three timers with freq.
-    // 1000 and 1500 and 1800:
-
-    // 1000 = 2^3 *       5^3
-    // 1500 = 2^2 * 3^1 * 5^3
-    // 1800 = 2^3 * 3^2 * 5^2
-
-    // so the highest power for factor 2 is 2;
-    //    the highest power for factor 3 is 0;
-    //    the highest power for factor 5 is 2;
-
-    // freq = 2^2 *     * 5^2 = 100
-
-    // Example 3: If we have four timers with freq.
-    // 1200 and 1500 and 1800 and 60:
-
-    // 1200 = 2^4 * 3^1 * 5^2
-    // 1500 = 2^2 * 3^1 * 5^3
-    // 1800 = 2^3 * 3^2 * 5^2
-    //   60 = 2^2 * 3^1 * 5^1
-
-    // so the highest power for factor 2 is 2;
-    //    the highest power for factor 3 is 1;
-    //    the highest power for factor 5 is 1;
-
-    // freq = 2^2 * 3^1 * 5^1 = 60
-
-    // go thru the ints array to create
-    // an entry for each prime there is
-    for (i = 0;
-         i < cNs;
-         i++)
-    {
-        // printf("\nFactoring %d\n", paNs[i]);
-        Data.iCurrentInt = paNs[i];
-        mathFactorPrime(paNs[i],
-                        GCDMultiCallbackCreate,
-                        &Data);
-    }
-
-    // now run this a second time to find the
-    // lowest prime for each
-    for (i = 0;
-         i < cNs;
-         i++)
-    {
-        // printf("\nTouching %d\n", paNs[i]);
-        Data.iCurrentInt = paNs[i];
-        mathFactorPrime(paNs[i],
-                        GCDMultiCallbackLowest,
-                        &Data);
-
-        // all list items that were just touched
-        // now have iLastInt set to the current
-        // integer; so go thru the list and set
-        // all items which do NOT have that set
-        // to power 0 because we must not use them
-        // in factoring
-        for (pNode = lstQueryFirstNode(&Data.llPrimes);
-             pNode;
-             pNode = pNode->pNext)
-        {
-            pEntry = (PPRIMEENTRY)pNode->pItemData;
-            if (pEntry->iLastInt != paNs[i])
-            {
-                pEntry->iLowestPowerFound = 0;
-                // printf(" ###%d^%d", pEntry->iPrime, 0);
-            }
-        }
-    }
-
-    // printf("\nNew:\n");
-
-    // now compose the GCD
-    dGCD = 1;
-    for (pNode = lstQueryFirstNode(&Data.llPrimes);
-         pNode;
-         pNode = pNode->pNext)
-    {
-        pEntry = (PPRIMEENTRY)pNode->pItemData;
-
-        // printf(" %d^%d", pEntry->iPrime, pEntry->iLowestPowerFound);
-
-        if (pEntry->iLowestPowerFound)
-            dGCD *= pow(pEntry->iPrime, pEntry->iLowestPowerFound);
-    }
-
-    lstClear(&Data.llPrimes);
-
-    return dGCD;
-} */
-
-// testcase
-
-#ifdef BUILD_MAIN
-
-#define INCL_DOSMISC
-#include <os2.h>
-
-int callback(int n,
-             int p,
-             void *pUser)
-{
-    if (p > 1)
-        printf("%d^%d ", n, p);
-    else
-        printf("%d ", n);
-    fflush(stdout);
-
-    return (0);
-}
-
-int main (int argc, char *argv[])
-{
-    if (argc > 1)
-    {
-        int i,
-            c,
-            cInts = argc - 1;
-        ULONG ulms, ulms2;
-        int *aInts = malloc(sizeof(int) * cInts);
-
-        for (c = 0;
-             c < cInts;
-             c++)
-        {
-            aInts[c] = atoi(argv[c + 1]);
-        }
-
-        DosQuerySysInfo(QSV_MS_COUNT,
-                        QSV_MS_COUNT,
-                        &ulms,
-                        sizeof(ulms));
-
-        c = mathGCDMulti(aInts,
-                         cInts);
-
-        DosQuerySysInfo(QSV_MS_COUNT,
-                        QSV_MS_COUNT,
-                        &ulms2,
-                        sizeof(ulms2));
-
-        printf("\nGCD is %d, %d ms time.\n",
-               c,
-               ulms2 - ulms);
-
-        for (i = 0;
-             i < cInts;
-             i++)
-        {
-            printf("  %d / %d = %d rem. %d\n",
-                   aInts[i], c, aInts[i] / c, aInts[i] % c);
-        }
-
-        /* c = mathFactorBrute(atoi(argv[1]),
-                            callback,
-                            0);
-        printf("\n%d factors found.\n", c); */
-    }
-
-    return (0);
-}
-
-#endif
+
Index: trunk/src/helpers/nls.c
===================================================================
--- trunk/src/helpers/nls.c	(revision 173)
+++ trunk/src/helpers/nls.c	(revision 174)
@@ -40,7 +40,5 @@
 
 #define INCL_DOSNLS
-#define INCL_DOSSEMAPHORES
-#define INCL_DOSEXCEPTIONS
-#define INCL_DOSPROCESS
+#define INCL_DOSDATETIME
 #define INCL_DOSERRORS
 #define INCL_WINSHELLDATA
@@ -51,15 +49,13 @@
 #include <string.h>
 #include <math.h>
-#include <setjmp.h>
-
+
+// XWP's setup.h replaces strchr and the like, and
+// we want the originals in here
+#define DONT_REPLACE_FOR_DBCS
 #include "setup.h"                      // code generation and debugging options
 
-#include "helpers\except.h"
 #include "helpers\nls.h"
 #include "helpers\prfh.h"
 #include "helpers\standards.h"
-#include "helpers\stringh.h"
-#include "helpers\tree.h"
-#include "helpers\xstring.h"
 
 #pragma hdrstop
@@ -69,4 +65,212 @@
  *      See nls.c.
  */
+
+/* ******************************************************************
+ *
+ *   DBCS support
+ *
+ ********************************************************************/
+
+#define MAX_LEADBYTE        256
+
+#pragma pack(1)
+
+typedef struct _DBCSVECTOR
+{
+   BYTE bLow;
+   BYTE bHigh;
+} DBCSVECTOR;
+
+#pragma pack()
+
+BOOL        G_afLeadByte[MAX_LEADBYTE] = {0};
+ULONG       G_fDBCS = -1;       // not queried yet
+COUNTRYCODE G_cc = { 0, 0 };
+DBCSVECTOR  G_aDBCSVector[8];
+
+/*
+ *@@ nlsDBCS:
+ *      returns TRUE if the system is currently using DBCS.
+ *
+ *@@added V0.9.19 (2002-06-13) [umoeller]
+ */
+
+BOOL nlsDBCS(VOID)
+{
+    int i;
+
+    if (G_fDBCS != -1)
+        // already queried:
+        return G_fDBCS;
+
+    if (DosQueryDBCSEnv(8 * sizeof(DBCSVECTOR),
+                        &G_cc,
+                        (PCHAR)G_aDBCSVector))
+        // not DBCS:
+        return (G_fDBCS = FALSE);
+
+    for (i = 0;
+         i < 8;
+         ++i)
+    {
+        if (    (G_aDBCSVector[i].bLow)
+             && (G_aDBCSVector[i].bHigh)
+           )
+        {
+            int n;
+            for (n = G_aDBCSVector[i].bLow;
+                 n <= G_aDBCSVector[i].bHigh;
+                 ++n)
+                G_afLeadByte[n] = TRUE;
+            G_fDBCS = TRUE;
+        }
+        else
+            break;
+    }
+
+    return G_fDBCS;
+}
+
+/*
+ *@@ nlsQueryDBCSChar:
+ *      returns the type of the DBCS character with
+ *      the given index. Note that the index is the
+ *      DBCS character index, not just the array
+ *      index into the CHAR array.
+ *
+ *      Returns:
+ *
+ *      --  TYPE_SBCS: ulOfs is single byte.
+ *
+ *      --  TYPE_DBCS_1ST: ulOfs is a double-byte lead char.
+ *
+ *      --  TYPE_DBCS_2ND: ulOfs is a double-byte trail char.
+ *
+ *      Preconditions:
+ *
+ *      --  nlsDBCS must have been called to initialize our
+ *          globals, and must have returned TRUE.
+ *
+ *@@added V0.9.19 (2002-06-13) [umoeller]
+ */
+
+ULONG nlsQueryDBCSChar(PCSZ pcszString,
+                       ULONG ulOfs)
+
+{
+    ULONG ulDBCSType = TYPE_SBCS;
+    ULONG i;
+
+    for (i = 0;
+         i <= ulOfs;
+         ++i)
+    {
+        switch (ulDBCSType)
+        {
+            case TYPE_SBCS:
+            case TYPE_DBCS_2ND:
+                ulDBCSType = G_afLeadByte[pcszString[i]];
+            break;
+
+            case TYPE_DBCS_1ST :
+                ulDBCSType = TYPE_DBCS_2ND;
+            break;
+        }
+    }
+
+    return ulDBCSType;
+}
+
+/*
+ *@@ nlschr:
+ *      replacement for strchr with DBCS support.
+ *
+ *      If the system is not running with DBCS,
+ *      this calls plain strchr automatically.
+ *
+ *@@added V0.9.19 (2002-06-13) [umoeller]
+ */
+
+PSZ nlschr(PCSZ p, char c)
+{
+    PCSZ    p2;
+    ULONG   ulDBCS;
+
+    if (!nlsDBCS())
+        // not DBCS:
+        return strchr(p, c);
+
+    // we're on DBCS:
+    for (p2 = p;
+         *p2;
+         ++p2)
+    {
+        if (*p2 == c)
+        {
+            // match: return this only if it's SBCS;
+            // if it's a DBCS lead char, skip it
+            switch (ulDBCS = nlsQueryDBCSChar(p, p2 - p))
+            {
+                case TYPE_SBCS:
+                    return (PSZ)p2;
+
+                case TYPE_DBCS_1ST:
+                    ++p2;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ *@@ nlsrchr:
+ *      replacement for strrchr with DBCS support.
+ *
+ *      If the system is not running with DBCS,
+ *      this calls plain strrchr automatically.
+ *
+ *@@added V0.9.19 (2002-06-13) [umoeller]
+ */
+
+PSZ nlsrchr(PCSZ p, char c)
+{
+    PCSZ    p2;
+    ULONG   ulDBCS,
+            ulLength;
+
+    if (!nlsDBCS())
+        // not DBCS:
+        return strrchr(p, c);
+
+    // we're on DBCS:
+    ulLength = strlen(p);
+    for (p2 = p + ulLength - 1;
+         p2 >= p;
+         --p2)
+    {
+        if (*p2 == c)
+        {
+            // match: return this only if it's SBCS;
+            // if it's a DBCS trail char, skip it
+            switch (ulDBCS = nlsQueryDBCSChar(p, p2 - p))
+            {
+                case TYPE_SBCS:
+                    return (PSZ)p2;
+
+                case TYPE_DBCS_2ND:
+                    --p2;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+/* ******************************************************************
+ *
+ *   Country-dependent formatting
+ *
+ ********************************************************************/
 
 /*
@@ -505,269 +709,3 @@
 }
 
-/* ******************************************************************
- *
- *   NLS strings
- *
- ********************************************************************/
-
-static HAB                 G_hab = NULLHANDLE;
-static HMODULE             G_hmod = NULLHANDLE;
-static PCSTRINGENTITY      G_paEntities = NULL;
-static ULONG               G_cEntities = 0;
-
-/*
- *@@ ReplaceEntities:
- *
- *@@added V0.9.16 (2001-09-29) [umoeller]
- */
-
-static ULONG ReplaceEntities(PXSTRING pstr)
-{
-    ULONG ul,
-          rc = 0;
-
-    for (ul = 0;
-         ul < G_cEntities;
-         ul++)
-    {
-        ULONG ulOfs = 0;
-        PCSTRINGENTITY pThis = &G_paEntities[ul];
-        while (xstrFindReplaceC(pstr,
-                                &ulOfs,
-                                pThis->pcszEntity,
-                                *(pThis->ppcszString)))
-            rc++;
-    }
-
-    return (rc);
-}
-
-/*
- *@@ LoadString:
- *
- *@@added V0.9.18 (2002-03-08) [umoeller]
- */
-
-static void LoadString(ULONG ulID,
-                       PSZ *ppsz,
-                       PULONG pulLength)        // out: length of new string (ptr can be NULL)
-{
-    CHAR szBuf[500];
-    XSTRING str;
-
-    if (*ppsz)
-        free(*ppsz);
-
-    if (!WinLoadString(G_hab,
-                       G_hmod,
-                       ulID,
-                       sizeof(szBuf),
-                       szBuf))
-        // loading failed:
-        sprintf(szBuf,
-                "LoadString error: string resource %d not found in module 0x%lX",
-                ulID,
-                G_hmod);
-
-    xstrInitCopy(&str, szBuf, 0);
-    ReplaceEntities(&str);
-    *ppsz = str.psz;
-    if (pulLength)
-        *pulLength = str.ulLength;
-    // do not free string
-}
-
-static HMTX        G_hmtxStringsCache = NULLHANDLE;
-static TREE        *G_StringsCache;
-static LONG        G_cStringsInCache = 0;
-
-/*
- *@@ LockStrings:
- *
- *@@added V0.9.9 (2001-04-04) [umoeller]
- */
-
-static BOOL LockStrings(VOID)
-{
-    BOOL brc = FALSE;
-
-    if (G_hmtxStringsCache == NULLHANDLE)
-    {
-        brc = !DosCreateMutexSem(NULL,
-                                 &G_hmtxStringsCache,
-                                 0,
-                                 TRUE);
-        treeInit(&G_StringsCache,
-                 &G_cStringsInCache);
-    }
-    else
-        brc = !DosRequestMutexSem(G_hmtxStringsCache, SEM_INDEFINITE_WAIT);
-
-    return brc;
-}
-
-/*
- *@@ UnlockStrings:
- *
- *@@added V0.9.9 (2001-04-04) [umoeller]
- */
-
-static VOID UnlockStrings(VOID)
-{
-    DosReleaseMutexSem(G_hmtxStringsCache);
-}
-
-/*
- *@@ STRINGTREENODE:
- *      internal string node structure for cmnGetString.
- *
- *@@added V0.9.9 (2001-04-04) [umoeller]
- *@@changed V0.9.16 (2002-01-26) [umoeller]: no longer using malloc() for string
- */
-
-typedef struct _STRINGTREENODE
-{
-    TREE        Tree;               // tree node (src\helpers\tree.c)
-    CHAR        szLoaded[1];        // string that was loaded;
-                                    // the struct is dynamic in size now
-                                    // V0.9.16 (2002-01-26) [umoeller]
-} STRINGTREENODE, *PSTRINGTREENODE;
-
-/*
- *@@ nlsInitStrings:
- *      initializes the NLS strings cache. Call this
- *      before calling nlsGetString for the first time.
- *
- *@@added V0.9.18 (2002-03-08) [umoeller]
- */
-
-VOID nlsInitStrings(HAB hab,                    // in: anchor block
-                    HMODULE hmod,               // in: module handle to load strings from
-                    PCSTRINGENTITY paEntities,  // in: entities array or NULL
-                    ULONG cEntities)            // in: array item count of paEntities or 0
-{
-    G_hab = hab;
-    G_hmod = hmod;
-    G_paEntities = paEntities;
-    G_cEntities = cEntities;
-}
-
-/*
- *@@ nlsGetString:
- *      returns a resource NLS string.
- *
- *      Before calling this for the first time, initialize
- *      the engine with nlsInitStrings.
- *
- *      After that, this function implements a fast string
- *      cache for various NLS strings. Compared to the
- *      standard method, this has the following advantages:
- *
- *      -- Memory is only consumed for strings that are actually
- *         used. The NLSSTRINGS array had become terribly big,
- *         and lots of strings were loaded that were never used.
- *
- *      -- Program startup should be a bit faster because we don't
- *         have to load a thousand strings at startup.
- *
- *      -- The memory buffer holding the string is probably close
- *         to the rest of the heap data that the caller allocated,
- *         so this might lead to less memory page fragmentation.
- *
- *      -- To add a new NLS string, before this mechanism existed,
- *         three files had to be changed (and kept in sync): common.h
- *         to add a field to the NLSSTRINGS structure, dlgids.h to
- *         add the string ID, and xfldrXXX.rc to add the resource.
- *         With the new mechanism, there's no need to change common.h
- *         any more, so the danger of forgetting something is a bit
- *         reduced. Anyway, fewer recompiles are needed (maybe),
- *         and sending in patches to the code is a bit easier.
- *
- *      On input, specify a string resouce ID that exists
- *      in the hmod that was given to nlsInitStrings.
- *
- *      The way this works is that the function maintains a
- *      fast cache of string IDs and only loads the string
- *      resources on demand from the given NLS DLL. If a
- *      string ID is queried for the first time, the string
- *      is loaded. Otherwise the cached copy is returned.
- *
- *      There is a slight overhead to this function compared to
- *      simply getting a static string from an array, because
- *      the cache needs to be searched for the string ID. However,
- *      this uses a binary tree (balanced according to string IDs)
- *      internally, so this is quite fast still.
- *
- *      This never releases the strings again.
- *
- *      This never returns NULL. Even if loading the string failed,
- *      a string is returned; in that case, it's a meaningful error
- *      message specifying the ID that failed.
- *
- *@@added V0.9.9 (2001-04-04) [umoeller]
- *@@changed V0.9.16 (2001-10-19) [umoeller]: fixed bad string count which was never set
- *@@changed V0.9.16 (2002-01-26) [umoeller]: optimized heap locality
- */
-
-PCSZ nlsGetString(ULONG ulStringID)
-{
-    BOOL    fLocked = FALSE;
-    PSZ     pszReturn = "Error";
-
-    TRY_LOUD(excpt1)
-    {
-        if (fLocked = LockStrings())
-        {
-            PSTRINGTREENODE pNode;
-
-            if (pNode = (PSTRINGTREENODE)treeFind(G_StringsCache,
-                                                  ulStringID,
-                                                  treeCompareKeys))
-                // already loaded:
-                pszReturn = pNode->szLoaded;
-            else
-            {
-                // not loaded: load now
-                PSZ     psz = NULL;
-                ULONG   ulLength = 0;
-
-                LoadString(ulStringID,
-                           &psz,
-                           &ulLength);
-
-                if (    (!psz)
-                     || (!(pNode = (PSTRINGTREENODE)malloc(   sizeof(STRINGTREENODE)
-                                                               // has one byte for null
-                                                               // terminator already
-                                                            + ulLength)))
-                   )
-                    pszReturn = "malloc() failed.";
-                else
-                {
-                    pNode->Tree.ulKey = ulStringID;
-                    memcpy(pNode->szLoaded,
-                           psz,
-                           ulLength + 1);
-                    treeInsert(&G_StringsCache,
-                               &G_cStringsInCache,      // fixed V0.9.16 (2001-10-19) [umoeller]
-                               (TREE*)pNode,
-                               treeCompareKeys);
-                    pszReturn = pNode->szLoaded;
-                }
-
-                if (psz)
-                    free(psz);
-            }
-        }
-        else
-        // we must always return a string, never NULL
-        pszReturn = "Cannot get strings lock.";
-    }
-    CATCH(excpt1) {} END_CATCH();
-
-    if (fLocked)
-        UnlockStrings();
-
-    return (pszReturn);
-}
-
+
Index: trunk/src/helpers/nlscache.c
===================================================================
--- trunk/src/helpers/nlscache.c	(revision 174)
+++ trunk/src/helpers/nlscache.c	(revision 174)
@@ -0,0 +1,341 @@
+
+/*
+ *@@sourcefile nlscache.c:
+ *
+ *      Usage: All OS/2 programs.
+ *
+ *      Function prefixes (new with V0.81):
+ *      --  stc*
+ *
+ *      This file is new with V0.9.19, but contains functions
+ *      formerly in nls.c.
+ *
+ *      Note: Version numbering in this file relates to XWorkplace version
+ *            numbering.
+ *
+ *@@header "helpers\nlscache.h"
+ *@@added V0.9.19 (2002-06-13) [umoeller]
+ */
+
+/*
+ *      Copyright (C) 2001-2002 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_DOSNLS
+#define INCL_DOSDATETIME
+#define INCL_DOSSEMAPHORES
+#define INCL_DOSEXCEPTIONS
+#define INCL_DOSPROCESS
+#define INCL_DOSERRORS
+#define INCL_WINSHELLDATA
+#include <os2.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <setjmp.h>
+
+// XWP's setup.h replaces strchr and the like, and
+// we want the originals in here
+#define DONT_REPLACE_FOR_DBCS
+#include "setup.h"                      // code generation and debugging options
+
+#include "helpers\except.h"
+#include "helpers\nls.h"
+#include "helpers\nlscache.h"
+#include "helpers\prfh.h"
+#include "helpers\standards.h"
+#include "helpers\stringh.h"
+#include "helpers\tree.h"
+#include "helpers\xstring.h"
+
+#pragma hdrstop
+
+/*
+ *@@category: Helpers\National Language Support\String cache
+ *      See nls.c.
+ */
+
+/* ******************************************************************
+ *
+ *   NLS string cache
+ *
+ ********************************************************************/
+
+static HAB                 G_hab = NULLHANDLE;
+static HMODULE             G_hmod = NULLHANDLE;
+static PCSTRINGENTITY      G_paEntities = NULL;
+static ULONG               G_cEntities = 0;
+
+/*
+ *@@ ReplaceEntities:
+ *
+ *@@added V0.9.16 (2001-09-29) [umoeller]
+ */
+
+static ULONG ReplaceEntities(PXSTRING pstr)
+{
+    ULONG ul,
+          rc = 0;
+
+    for (ul = 0;
+         ul < G_cEntities;
+         ul++)
+    {
+        ULONG ulOfs = 0;
+        PCSTRINGENTITY pThis = &G_paEntities[ul];
+        while (xstrFindReplaceC(pstr,
+                                &ulOfs,
+                                pThis->pcszEntity,
+                                *(pThis->ppcszString)))
+            rc++;
+    }
+
+    return (rc);
+}
+
+/*
+ *@@ LoadString:
+ *
+ *@@added V0.9.18 (2002-03-08) [umoeller]
+ */
+
+static void LoadString(ULONG ulID,
+                       PSZ *ppsz,
+                       PULONG pulLength)        // out: length of new string (ptr can be NULL)
+{
+    CHAR szBuf[500];
+    XSTRING str;
+
+    if (*ppsz)
+        free(*ppsz);
+
+    if (!WinLoadString(G_hab,
+                       G_hmod,
+                       ulID,
+                       sizeof(szBuf),
+                       szBuf))
+        // loading failed:
+        sprintf(szBuf,
+                "LoadString error: string resource %d not found in module 0x%lX",
+                ulID,
+                G_hmod);
+
+    xstrInitCopy(&str, szBuf, 0);
+    ReplaceEntities(&str);
+    *ppsz = str.psz;
+    if (pulLength)
+        *pulLength = str.ulLength;
+    // do not free string
+}
+
+static HMTX        G_hmtxStringsCache = NULLHANDLE;
+static TREE        *G_StringsCache;
+static LONG        G_cStringsInCache = 0;
+
+/*
+ *@@ LockStrings:
+ *
+ *@@added V0.9.9 (2001-04-04) [umoeller]
+ */
+
+static BOOL LockStrings(VOID)
+{
+    BOOL brc = FALSE;
+
+    if (G_hmtxStringsCache == NULLHANDLE)
+    {
+        brc = !DosCreateMutexSem(NULL,
+                                 &G_hmtxStringsCache,
+                                 0,
+                                 TRUE);
+        treeInit(&G_StringsCache,
+                 &G_cStringsInCache);
+    }
+    else
+        brc = !DosRequestMutexSem(G_hmtxStringsCache, SEM_INDEFINITE_WAIT);
+
+    return brc;
+}
+
+/*
+ *@@ UnlockStrings:
+ *
+ *@@added V0.9.9 (2001-04-04) [umoeller]
+ */
+
+static VOID UnlockStrings(VOID)
+{
+    DosReleaseMutexSem(G_hmtxStringsCache);
+}
+
+/*
+ *@@ STRINGTREENODE:
+ *      internal string node structure for cmnGetString.
+ *
+ *@@added V0.9.9 (2001-04-04) [umoeller]
+ *@@changed V0.9.16 (2002-01-26) [umoeller]: no longer using malloc() for string
+ */
+
+typedef struct _STRINGTREENODE
+{
+    TREE        Tree;               // tree node (src\helpers\tree.c)
+    CHAR        szLoaded[1];        // string that was loaded;
+                                    // the struct is dynamic in size now
+                                    // V0.9.16 (2002-01-26) [umoeller]
+} STRINGTREENODE, *PSTRINGTREENODE;
+
+/*
+ *@@ nlsInitStrings:
+ *      initializes the NLS strings cache. Call this
+ *      before calling nlsGetString for the first time.
+ *
+ *@@added V0.9.18 (2002-03-08) [umoeller]
+ */
+
+VOID nlsInitStrings(HAB hab,                    // in: anchor block
+                    HMODULE hmod,               // in: module handle to load strings from
+                    PCSTRINGENTITY paEntities,  // in: entities array or NULL
+                    ULONG cEntities)            // in: array item count of paEntities or 0
+{
+    G_hab = hab;
+    G_hmod = hmod;
+    G_paEntities = paEntities;
+    G_cEntities = cEntities;
+}
+
+/*
+ *@@ nlsGetString:
+ *      returns a resource NLS string.
+ *
+ *      Before calling this for the first time, initialize
+ *      the engine with nlsInitStrings.
+ *
+ *      After that, this function implements a fast string
+ *      cache for various NLS strings. Compared to the
+ *      standard method, this has the following advantages:
+ *
+ *      -- Memory is only consumed for strings that are actually
+ *         used. The NLSSTRINGS array had become terribly big,
+ *         and lots of strings were loaded that were never used.
+ *
+ *      -- Program startup should be a bit faster because we don't
+ *         have to load a thousand strings at startup.
+ *
+ *      -- The memory buffer holding the string is probably close
+ *         to the rest of the heap data that the caller allocated,
+ *         so this might lead to less memory page fragmentation.
+ *
+ *      -- To add a new NLS string, before this mechanism existed,
+ *         three files had to be changed (and kept in sync): common.h
+ *         to add a field to the NLSSTRINGS structure, dlgids.h to
+ *         add the string ID, and xfldrXXX.rc to add the resource.
+ *         With the new mechanism, there's no need to change common.h
+ *         any more, so the danger of forgetting something is a bit
+ *         reduced. Anyway, fewer recompiles are needed (maybe),
+ *         and sending in patches to the code is a bit easier.
+ *
+ *      On input, specify a string resouce ID that exists
+ *      in the hmod that was given to nlsInitStrings.
+ *
+ *      The way this works is that the function maintains a
+ *      fast cache of string IDs and only loads the string
+ *      resources on demand from the given NLS DLL. If a
+ *      string ID is queried for the first time, the string
+ *      is loaded. Otherwise the cached copy is returned.
+ *
+ *      There is a slight overhead to this function compared to
+ *      simply getting a static string from an array, because
+ *      the cache needs to be searched for the string ID. However,
+ *      this uses a binary tree (balanced according to string IDs)
+ *      internally, so this is quite fast still.
+ *
+ *      This never releases the strings again.
+ *
+ *      This never returns NULL. Even if loading the string failed,
+ *      a string is returned; in that case, it's a meaningful error
+ *      message specifying the ID that failed.
+ *
+ *@@added V0.9.9 (2001-04-04) [umoeller]
+ *@@changed V0.9.16 (2001-10-19) [umoeller]: fixed bad string count which was never set
+ *@@changed V0.9.16 (2002-01-26) [umoeller]: optimized heap locality
+ */
+
+PCSZ nlsGetString(ULONG ulStringID)
+{
+    BOOL    fLocked = FALSE;
+    PSZ     pszReturn = "Error";
+
+    TRY_LOUD(excpt1)
+    {
+        if (fLocked = LockStrings())
+        {
+            PSTRINGTREENODE pNode;
+
+            if (pNode = (PSTRINGTREENODE)treeFind(G_StringsCache,
+                                                  ulStringID,
+                                                  treeCompareKeys))
+                // already loaded:
+                pszReturn = pNode->szLoaded;
+            else
+            {
+                // not loaded: load now
+                PSZ     psz = NULL;
+                ULONG   ulLength = 0;
+
+                LoadString(ulStringID,
+                           &psz,
+                           &ulLength);
+
+                if (    (!psz)
+                     || (!(pNode = (PSTRINGTREENODE)malloc(   sizeof(STRINGTREENODE)
+                                                               // has one byte for null
+                                                               // terminator already
+                                                            + ulLength)))
+                   )
+                    pszReturn = "malloc() failed.";
+                else
+                {
+                    pNode->Tree.ulKey = ulStringID;
+                    memcpy(pNode->szLoaded,
+                           psz,
+                           ulLength + 1);
+                    treeInsert(&G_StringsCache,
+                               &G_cStringsInCache,      // fixed V0.9.16 (2001-10-19) [umoeller]
+                               (TREE*)pNode,
+                               treeCompareKeys);
+                    pszReturn = pNode->szLoaded;
+                }
+
+                if (psz)
+                    free(psz);
+            }
+        }
+        else
+            // we must always return a string, never NULL
+            pszReturn = "Cannot get strings lock.";
+    }
+    CATCH(excpt1) {} END_CATCH();
+
+    if (fLocked)
+        UnlockStrings();
+
+    return (pszReturn);
+}
+
+
Index: trunk/src/helpers/stringh.c
===================================================================
--- trunk/src/helpers/stringh.c	(revision 173)
+++ trunk/src/helpers/stringh.c	(revision 174)
@@ -104,5 +104,5 @@
                 memcpy(*ppszTarget, pcszSource, ulLength + 1);
             else
-                return (ERROR_NOT_ENOUGH_MEMORY);
+                return ERROR_NOT_ENOUGH_MEMORY;
         }
         else
@@ -149,5 +149,5 @@
                 memcpy(*ppszTarget, pcszSource, ulLength + 1);
             else
-                return (ERROR_NOT_ENOUGH_MEMORY);
+                return ERROR_NOT_ENOUGH_MEMORY;
         }
         else
@@ -155,5 +155,5 @@
     }
     else
-        return (ERROR_INVALID_PARAMETER);
+        return ERROR_INVALID_PARAMETER;
 
     if (pulLength)
@@ -174,8 +174,8 @@
 {
     if (string2)
-        return (strcpy(string1, string2));
+        return strcpy(string1, string2);
 
     *string1 = '\0';
-    return (string1);
+    return string1;
 }
 
@@ -212,5 +212,5 @@
         *pulLength = ulLength;
 
-    return (pszReturn);
+    return pszReturn;
 }
 
@@ -244,5 +244,5 @@
         *pulLength = ulLength;
 
-    return (pszReturn);
+    return pszReturn;
 }
 
@@ -266,16 +266,16 @@
     {
         int i = strcmp(p1, p2);
-        if (i < 0) return (-1);
-        if (i > 0) return (+1);
+        if (i < 0) return -1;
+        if (i > 0) return +1;
     }
     else if (p1)
         // but p2 is NULL: p1 greater than p2 then
-        return (+1);
+        return +1;
     else if (p2)
         // but p1 is NULL: p1 less than p2 then
-        return (-1);
+        return -1;
 
     // return 0 if strcmp returned 0 above or both strings are NULL
-    return (0);
+    return 0;
 }
 
@@ -293,16 +293,16 @@
     {
         int i = stricmp(p1, p2);
-        if (i < 0) return (-1);
-        if (i > 0) return (+1);
+        if (i < 0) return -1;
+        if (i > 0) return +1;
     }
     else if (p1)
         // but p2 is NULL: p1 greater than p2 then
-        return (+1);
+        return +1;
     else if (p2)
         // but p1 is NULL: p1 less than p2 then
-        return (-1);
+        return -1;
 
     // return 0 if strcmp returned 0 above or both strings are NULL
-    return (0);
+    return 0;
 }
 
@@ -342,5 +342,6 @@
             free(pszSrchIn);
     }
-    return (prc);
+
+    return prc;
 }
 
@@ -371,5 +372,5 @@
     *pTarget = 0;
 
-    return (ul);
+    return ul;
 }
 
@@ -385,5 +386,5 @@
 {
     if (pcsz)
-        return (strlen(pcsz));
+        return strlen(pcsz);
 
     return 0;
@@ -408,5 +409,5 @@
         return (strlen(pcsz) + 1);
 
-    return (0);
+    return 0;
 }
 
@@ -432,5 +433,5 @@
             break;
     }
-    return (ulCount);
+    return ulCount;
 }
 
@@ -446,9 +447,9 @@
     {
         if (isdigit(*p) == 0)
-            return (FALSE);
+            return FALSE;
         p++;
     }
 
-    return (TRUE);
+    return TRUE;
 }
 
@@ -484,5 +485,5 @@
     }
 
-    return (pszSubstr);
+    return pszSubstr;
 }
 
@@ -522,5 +523,5 @@
     }
 
-    return (pszSubstr);
+    return pszSubstr;
 }
 
@@ -619,5 +620,5 @@
     }
 
-    return (pszReturn);
+    return pszReturn;
 }
 
@@ -651,5 +652,5 @@
     }
 
-    return (pszReturn);
+    return pszReturn;
 }
 
@@ -690,5 +691,5 @@
         }
     }
-    return (ulrc);
+    return ulrc;
 }
 
@@ -742,5 +743,5 @@
     }
 
-    return (pszNew);
+    return pszNew;
 }
 
@@ -785,5 +786,5 @@
         *ppszBuf = xstrBuf.psz;
 
-    return (ulrc);
+    return ulrc;
 }
 
@@ -809,5 +810,5 @@
                 ulWords++;
     }
-    return (ulWords);
+    return ulWords;
 }
 
@@ -874,9 +875,9 @@
             *ppszStart = pStart;
             *ppszEnd = pEndOfWord;
-            return (TRUE);
-        }
-    }
-
-    return (FALSE);
+            return TRUE;
+        }
+    }
+
+    return FALSE;
 }
 
@@ -995,5 +996,5 @@
 
     }
-    return (pszReturn);
+    return pszReturn;
 }
 
@@ -1049,5 +1050,5 @@
     if (pulOffset)
         *pulOffset = pNextLine - pszSearchIn;
-    return (pNextLine);
+    return pNextLine;
 }
 
@@ -1080,5 +1081,5 @@
             p++;
 
-    return (rc);
+    return rc;
 }
 
@@ -1143,5 +1144,5 @@
    }
 
-   return (prc);
+   return prc;
 }
 
@@ -1196,5 +1197,5 @@
         pszSearchIn2++;
     }
-    return (prc);
+    return prc;
 } */
 
@@ -1231,5 +1232,5 @@
     }
 
-    return (pParam);
+    return pParam;
 }
 
@@ -1314,5 +1315,5 @@
         }
     }
-    return (prc);
+    return prc;
 }
 
@@ -1362,5 +1363,5 @@
     }
 
-    return (prc);
+    return prc;
 }
 
@@ -1548,5 +1549,5 @@
     }
 
-    return (ulrc);
+    return ulrc;
 }
 
@@ -1705,5 +1706,5 @@
         pszReturn = strReturn.psz;
 
-    return (pszReturn);
+    return pszReturn;
 }
 
