Index: trunk/src/helpers/cctl_chart.c
===================================================================
--- trunk/src/helpers/cctl_chart.c	(revision 67)
+++ trunk/src/helpers/cctl_chart.c	(revision 68)
@@ -2,8 +2,159 @@
 /*
  *@@sourcefile cctl_chart.c:
- *      implementation for the "chart" common control.
- *      See comctl.c for an overview.
+ *      implementation for the "chart" common control, which can
+ *      represent an array of "double" values as either a pie chart
+ *      or a bar chart. Selections of chart slices and owner
+ *      notifications are also supported.
  *
  *      This has been extracted from comctl.c with V0.9.3 (2000-05-21) [umoeller].
+ *
+ *      The "chart" common control presently is a subclassed static
+ *      text control. This way you can easily create a chart control
+ *      as a static control in any Dialog Editor; after loading the
+ *      dlg template, simply call ctlChartFromStatic with the hwnd
+ *      of the static control to make it a chart.
+ *
+ *      Note: even though you can use _any_ type of static control
+ *      with this function, you should use a static _text_ control,
+ *      because not all types of static controls react to fonts and
+ *      colors dragged upon them. The static _text_ control does.
+ *
+ *      The pie chart consumes all available space in the static control.
+ *
+ *      In XWorkplace, this is used for the pie chart on the new
+ *      XFldDisk "Details" settings page to display the free space
+ *      on a certain drive.
+ *
+ *      <B>Chart data:</B>
+ *
+ *      The pie chart control operates on an array of "double" values.
+ *      Each value in that array corresponds to a color in a second
+ *      array of (LONG) RGB values and, if description texts are
+ *      enabled, to a third array of PSZ's with description texts.
+ *      In each of the three arrays, the array item count must be
+ *      the same, naturally.
+ *
+ *      The data on which the pie chart operates is initialized to
+ *      be void, so that the pie chart will not paint anything
+ *      initially. In order to have the pie chart display something,
+ *      send a CHTM_SETCHARTDATA message (comctl.h) to the static
+ *      control after it has been subclassed.
+ *
+ *      CHTM_SETCHARTDATA takes a CHARTDATA structure (comctl.h) in mp1,
+ *      which must contain the three arrays with the chart data, colors,
+ *      and descriptive texts to be displayed.
+ *
+ *      The chart data will automatically compute the sum of all values
+ *      so that each data item will be displayed as a fraction of the
+ *      total chart control size, proportionally to the item's value
+ *      against the sum of all values. In other words,
+ *
+ +          (dValue / dTotal) == (sizeSlize / sizeTotal).
+ *
+ *      The display depends on whether the chart control operates in
+ *      "pie chart" or "bar chart" mode.
+ *
+ *      --  In "pie chart" mode, data items are displayed as slices of
+ *          a pie chart. The total sum of the "double" values will
+ *          represent the angle in CHARTDATA.usSweepAngle.
+ *
+ *          For example, if two values of 50 and 100 are passed to the
+ *          control and usSweepAngle is 270 (i.e. a three-quarter pie),
+ *          the chart control will calculate the following:
+ *
+ *          1)  The sum of the data is 150.
+ *
+ *          2)  The first sub-arc will span an angle of 270 * (50/150)
+ *              = 90 degrees.
+ *
+ *          3)  The second sub-arc will span an angle of 270 * (100/150)
+ *              = 180 degrees.
+ *
+ *          You can also have descriptions displayed along the different
+ *          chart items by specifying CHARTDATA.papszDescriptions and
+ *          setting the CHS_DESCRIPTIONS flag (below).
+ *
+ *      --  In "bar chart" mode, the angle values make no sense and
+ *          are therefore ignored. In "bar chart" mode, the chart will
+ *          be drawn as a simple rectangle, with subrectangles representing
+ *          the chart slices.
+ *
+ *      <B>Chart styles:</B>
+ *
+ *      Use CHTM_SETCHARTSTYLE with a PCHARTSTYLE (comctl.h) in mp1.
+ *      This can be sent to the chart control several times.
+ *
+ *      Set either CHS_PIECHART or CHS_BARCHART to switch the control
+ *      to "pie chart" or "bar chart" mode, respectively.
+ *
+ *      For pie charts, there are several "display sub-styles":
+ *
+ *      -- CHS_3D_BRIGHT: paint a "3D" socket below the actual chart.
+ *
+ *      -- CHS_3D_DARKEN: like CHS_3D_BRIGHT, but the socket will be made
+ *                        darker compared to the surface.
+ *
+ *      General styles (for all modes):
+ *
+ *      -- CHS_DESCRIPTIONS: show descriptions on the chart
+ *                           (CHARTDATA.papszDescriptions data).
+ *
+ *      -- CHS_SELECTIONS: allow pie chart slices to be selectable,
+ *                         using the mouse and the keyboard (see below).
+ *
+ *      <B>Display:</B>
+ *
+ *      The chart control creates an internal bitmap for the display
+ *      only once (ctlCreateChartBitmap). This bitmap is refreshed if
+ *      neccessary, e.g. because chart data or styles have changed.
+ *
+ *      The chart control uses presentation parameters, as listed below.
+ *      Presentation parameters are inherited from the parent window.
+ *      If a presparam is not set, the corresponding system color is
+ *      used. The following color pairs are recognized:
+ *
+ *      --  PP_BACKGROUNDCOLOR / SYSCLR_DIALOGBACKGROUND:
+ *          background of the chart control (outside the chart).
+ *
+ *      --  PP_FOREGROUNDCOLOR / SYSCLR_WINDOWTEXT:
+ *          text color, if description texts are enabled and valid.
+ *
+ *      --  PP_FONTNAMESIZE:
+ *          text font, if description texts are enabled and valid.
+ *          If this presparam is not set, the system font is used.
+ *
+ *      The control reacts to fonts and colors dropped upon it, if
+ *      it has been subclassed from a static _text_ control (see above).
+ *      It also recalculates the bitmap when it's resized.
+ *
+ *      <B>Example usage:</B>
+ *
+ +          // get static control:
+ +          HWND    hwndChart = WinWindowFromID(hwndDialog, ID_...);
+ +          CHARTSTYLE      cs;
+ +          CHARTDATA       cd;
+ +          // define data:
+ +          double          adData[3] = { 100, 200, 300 };
+ +          // define corresponding colors:
+ +          LONG            alColors[3] = { 0x800000, 0x008000, 0x000080 };
+ +          // define correspdonding descriptions:
+ +          PSZ             apszDescriptions[3] = { "item 1", "item 3", "item 3" };
+ +
+ +          ctlChartFromStatic(hwndChart);     // create chart
+ +
+ +          cs.ulStyle = CHS_3D_DARKEN | CHS_DESCRIPTIONS;
+ +          cs.ulThickness = 20;
+ +          cs.dPieSize = .8;           // use 80% of control size
+ +          cs.dDescriptions = .5       // draw descriptions at 50% from center
+ +          WinSendMsg(hwndChart, CHTM_SETCHARTSTYLE, &cs, NULL);
+ +
+ +          cd.usStartAngle = 15;       // start at 15ø from right
+ +          cd.usSweepAngle = 270;      // three-quarter pie (for the sum of the
+ +                                      // above values: 100+200+300 = 600)
+ +          cd.cValues = 3;             // array count
+ +          cd.padValues = &adData[0];
+ +          cd.palColors = &alColors[0];
+ +          cd.papszDescriptions = &apszDescriptions[0];
+ +          WinSendMsg(hwndChart, CHTM_SETCHARTDATA, &cd, NULL);
  *
  *      Note: Version numbering in this file relates to XWorkplace version
@@ -86,7 +237,720 @@
 /* ******************************************************************
  *
+ *   Private declarations
+ *
+ ********************************************************************/
+
+/*
+ *@@ CHARTCDATA:
+ *      pie chart control data. Composed from the various
+ *      chart initialization data.
+ *      Stored in QWL_USER of the subclassed static control.
+ *      Not available to the application.
+ */
+
+typedef struct _CHARTCDATA
+{
+    // data which is initialized upon creation:
+    PFNWP           OldStaticProc;  // old static window procedure (from WinSubclassWindow)
+
+    // data which is initialized upon CHTM_SETCHARTDATA/CHTM_SETCHARTSTYLE:
+    HDC             hdcMem;         // memory device context for bitmap
+    HPS             hpsMem;         // memory presentation space for bitmap
+    CHARTDATA       cd;             // chart data: initialized to null values
+    CHARTSTYLE      cs;             // chart style: initialized to null values
+
+    HBITMAP         hbmChart;       // chart bitmap (for quick painting)
+    HRGN*           paRegions;      // pointer to array of GPI regions for each data item
+
+    // user interaction data:
+    LONG            lSelected;      // zero-based index of selected chart item, or -1 if none
+    LONG            lSourceEmphasis;    // zero-based index of item with source emphysis, or -1 if none
+    BOOL            fHasFocus;
+} CHARTCDATA, *PCHARTCDATA;
+
+/* ******************************************************************
+ *
  *   Chart Control
  *
  ********************************************************************/
+
+/*
+ *@@ DrawCenteredText:
+ *
+ *@@added V0.9.12 (2001-05-03) [umoeller]
+ */
+
+VOID DrawCenteredText(HPS hpsMem,
+                      PPOINTL pptlMiddlePoint,
+                      const char *pcsz)
+{
+    if (pcsz)
+    {
+        PSZ psz = strdup(pcsz),
+            p2 = psz;
+        if (psz)
+        {
+            ULONG cLineBreaks = 0;
+            LONG lLineSpacing = 0;
+
+            POINTL ptl;
+            memcpy(&ptl, pptlMiddlePoint, sizeof(ptl));
+
+            // count how many lines we have
+            while (p2 = strchr(p2, '\n'))
+            {
+                cLineBreaks++;
+                p2++;
+            }
+
+            if (cLineBreaks)
+            {
+                // if we have more than one line:
+                lLineSpacing = gpihQueryLineSpacing(hpsMem);
+
+                // center vertically
+                ptl.y += (lLineSpacing * cLineBreaks) / 2;
+            }
+
+            p2 = psz;
+            while (TRUE)
+            {
+                PSZ pNext = strchr(p2, '\n');
+                if (pNext)
+                {
+                    *pNext = '\0';
+                    pNext++;
+                }
+
+                GpiCharStringAt(hpsMem,
+                                &ptl,
+                                strlen(p2),
+                                (PSZ)p2);
+
+                if (pNext)
+                {
+                    p2 = pNext;
+                    ptl.y -= lLineSpacing;
+                }
+                else
+                    break;
+            }
+
+            free(psz);
+        }
+    }
+}
+
+/*
+ *@@ PaintBarChart:
+ *      paint implementation for the "bar chart".
+ *
+ *      This gets called from ctlCreateChartBitmap if the
+ *      current style for the chart control is the "bar
+ *      chart" style.
+ *
+ *@@added V0.9.12 (2001-05-03) [umoeller]
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed another maaajor PM resource leak with regions
+ */
+
+VOID PaintBarChart(HPS hpsMem,
+                   PRECTL prclWholeStatic,    // in: rectl to paint into
+                   PCHARTDATA pChartData,     // in: chart data
+                   PCHARTSTYLE pChartStyle,   // in: chart style
+                   double dTotal,             // in: sum of all values in pChartData
+                   LONG lTextColor,           // in: description text color (RGB)
+                   HRGN* paRegions)           // out: GPI regions for each data item
+{
+    ULONG       ulYBottomNow = 0;
+    ULONG       ul;
+
+    PLONG       plColorThis;
+    PSZ         *ppszDescriptionThis = NULL;
+    HRGN        *phRegionThis;
+    double      *pdValueThis;
+
+    // allocate array for storing text positions later
+    PPOINTL     paptlDescriptions = (PPOINTL)malloc(sizeof(POINTL)
+                                                    * pChartData->cValues);
+
+    RECTL       rclPaint;
+
+    POINTL      ptlLowerLeft,
+                ptlUpperRight;
+
+    // thickness of line separators for CHS_DRAWLINES
+    // V0.9.12 (2001-05-03) [umoeller]
+    const SIZEL cszlDrawLines = {1, 1};
+
+    // calculate rectangle;
+    // this is the size of the static control
+    // minus the "3D thickness", if enabled
+    memcpy(&rclPaint, prclWholeStatic, sizeof(RECTL));
+    if (pChartStyle->ulStyle & CHS_3D_BRIGHT)
+            // this includes CHS_3D_DARKEN
+    {
+        rclPaint.yBottom = prclWholeStatic->yBottom
+                                    + ulYBottomNow;
+        rclPaint.yTop = prclWholeStatic->yTop
+                                    - pChartStyle->ulThickness
+                                    + ulYBottomNow;
+    }
+
+    // c)  Strangely, GpiSetPattern does work,
+    //     while GpiSetColor doesn't (see below).
+    GpiSetPattern(hpsMem, PATSYM_SOLID);
+
+    // initialize per-item data pointers for
+    // loop below
+    pdValueThis = pChartData->padValues;
+    plColorThis = pChartData->palColors;
+    ppszDescriptionThis = pChartData->papszDescriptions;
+    phRegionThis = paRegions;
+
+    // initialize corners
+    ptlLowerLeft.x = 0;
+    ptlLowerLeft.y = 0;
+    ptlUpperRight.x = 0;
+    ptlUpperRight.y = rclPaint.yTop;
+
+    // inner "slice loop":
+    // this loop goes over the data pointers
+    // and paints accordingly. At the end of
+    // the loop, we'll advance all those pointers.
+    for (ul = 0;
+         ul < pChartData->cValues;
+         ul++)
+    {
+        HRGN        hrgnThis;
+        SHORT       sSweepAngle,
+                    sStartAngle;
+
+        GpiSetCurrentPosition(hpsMem, &ptlLowerLeft);
+
+        _Pmpf((__FUNCTION__ ": ptlLowerLeft.x = %d", ptlLowerLeft.x));
+
+        // calc upper right from data values
+        ptlUpperRight.x += (ULONG)(   (double)rclPaint.xRight
+                                    * (*pdValueThis)
+                                    / dTotal);
+
+        // set the area (fill) color
+        GpiSetColor(hpsMem,
+                    *plColorThis);
+
+        // now draw the slice;
+        // we could use an area, but since we need
+        // to remember the coordinates of the slice
+        // for mouse click handling, we require a
+        // region. But areas cannot be converted
+        // to regions, so we use a path instead.
+        GpiBeginPath(hpsMem,
+                     1);    // path ID, must be 1
+
+        GpiBox(hpsMem,
+               DRO_OUTLINE,
+               &ptlUpperRight,
+               0, 0);       // no rounding
+
+        GpiEndPath(hpsMem);
+
+        // convert the path to a region;
+        // we'll need the region for mouse hit testing later
+        hrgnThis = GpiPathToRegion(hpsMem,
+                                   1,
+                                   FPATH_ALTERNATE);
+            // after this, the path is deleted
+
+        // now, this FINALLY paints the slice
+        GpiPaintRegion(hpsMem, hrgnThis);
+
+        // now paint descriptions
+        if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
+        {
+            // description strings valid?
+            if (ppszDescriptionThis)
+            {
+                POINTL  ptlMiddlePoint;
+
+                // set presentation color
+                GpiSetColor(hpsMem, lTextColor);
+
+                // set text aligment to centered
+                // both horizontally and vertically;
+                // this affects subsequent GpiCharStringAt
+                // calls in that the output text will
+                // be centered around the specified
+                // point
+                GpiSetTextAlignment(hpsMem,
+                                    TA_CENTER,      // horizontally
+                                    TA_HALF);       // center vertically
+
+
+
+                // center the text in the box
+                ptlMiddlePoint.x =   ptlLowerLeft.x
+                                   + (ptlUpperRight.x - ptlLowerLeft.x) / 2;
+                ptlMiddlePoint.y =   ptlLowerLeft.y
+                                   + (ptlUpperRight.y - ptlLowerLeft.y) / 2;
+
+                // FINALLY, draw the description
+                // at this point; since we have used
+                // GpiSetTextAlignment above, the
+                // text will be centered on exactly
+                // that point
+                DrawCenteredText(hpsMem,
+                                 &ptlMiddlePoint,
+                                 *ppszDescriptionThis);
+
+            } // end if (ppszDescriptionThis)
+        } // end if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
+
+        // last run (are we painting the top now)?
+        if (    // if separator lines are enabled,
+                // draw a frame around the thing
+                (pChartStyle->ulStyle & CHS_DRAWLINES)
+           )
+        {
+            GpiSetColor(hpsMem, lTextColor);
+            GpiFrameRegion(hpsMem,
+                           hrgnThis,
+                           (PSIZEL)&cszlDrawLines);      // always (1, 1)
+        }
+
+        if (    phRegionThis
+           )
+            // region output requested by caller:
+            // store region, the caller will clean this up
+            *phRegionThis = hrgnThis;
+        else
+            // no region output requested: destroy region
+            GpiDestroyRegion(hpsMem, hrgnThis);
+
+        // use the "right" xpos we had now as the "left"
+        // xpos for the next loop
+        ptlLowerLeft.x = ptlUpperRight.x;
+
+        // advance the data pointers
+        pdValueThis++;
+        plColorThis++;
+        if (ppszDescriptionThis)
+            ppszDescriptionThis++;
+        if (phRegionThis)
+            phRegionThis++;
+    } // end for (ul...)
+
+    // cleanup
+    free(paptlDescriptions);
+}
+
+/*
+ *@@ PaintPieChart:
+ *      paint implementation for the "pie chart".
+ *
+ *      This gets called from ctlCreateChartBitmap if the
+ *      current style for the chart control is the "pie
+ *      chart" style.
+ *
+ *@@added V0.9.12 (2001-05-03) [umoeller]
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed another maaajor PM resource leak with regions
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed rounding errors
+ */
+
+VOID PaintPieChart(HPS hpsMem,
+                   PRECTL prclWholeStatic,    // in: rectl to paint into
+                   PCHARTDATA pChartData,     // in: chart data
+                   PCHARTSTYLE pChartStyle,   // in: chart style
+                   double dTotal,             // in: sum of all values in pChartData
+                   LONG lTextColor,           // in: description text color (RGB)
+                   HRGN* paRegions)           // out: GPI regions for each data item
+{
+    ULONG       ulYBottomNow = 0;
+    ULONG       ul;
+
+    // allocate array for storing text positions later
+    PPOINTL     paptlDescriptions = (PPOINTL)malloc(sizeof(POINTL)
+                                                    * pChartData->cValues);
+    POINTL      ptlCenter;
+
+    FIXED       fxPieSize = (LONG)(pChartStyle->dPieSize * 65536),
+                fxDescriptions = (LONG)(pChartStyle->dDescriptions * 65536);
+
+    // We'll paint into the bitmap in two loops:
+    // +--  The outer "3D" loop is executed
+    // |    pChartStyle->ulThickness-fold, if
+    // |    CHS_3Dxxx has been enabled; otherwise
+    // |    just once.
+    // |
+    // |    +-- The inner "slice" loop goes thru the
+    // |        data fields in pChartData and draws
+    // |        the pies accordingly.
+    // |
+    // +--  We then increase the base Y point (ulYBottomNow)
+    //      by one and draw again, thereby getting the 3D
+    //      effect.
+
+    // 1) outer 3D loop
+    do // while (   (pChartStyle->ulStyle & CHS_3D_BRIGHT)...
+    {
+        RECTL       rclArc;
+        PLONG       plColorThis;
+        PSZ         *ppszDescriptionThis = NULL;
+        PPOINTL     pptlDescriptionThis;
+        HRGN        *phRegionThis;
+        double      *pdValueThis;
+
+        ARCPARAMS   ap;
+        AREABUNDLE  ab;
+
+        double      dStartAngle = pChartData->usStartAngle,
+                    dSweepAngle = 0;
+
+        // thickness of line separators for CHS_DRAWLINES
+        // V0.9.12 (2001-05-03) [umoeller]
+        const SIZEL cszlDrawLines = {1, 1};
+
+        // this is only TRUE for the last loop
+        BOOL        fNowDrawingSurface =
+                      (
+                           ((pChartStyle->ulStyle & CHS_3D_BRIGHT) == 0)
+                        ||
+                           (ulYBottomNow == pChartStyle->ulThickness - 1)
+                      );
+
+        // // _Pmpf(("Looping, ulYBottomNow: %d", ulYBottomNow));
+
+        // calculate pie rectangle for this loop;
+        // this is the size of the static control
+        // minus the "3D thickness", if enabled
+        memcpy(&rclArc, prclWholeStatic, sizeof(RECTL));
+        if (pChartStyle->ulStyle & CHS_3D_BRIGHT)
+                // this includes CHS_3D_DARKEN
+        {
+            rclArc.yBottom = prclWholeStatic->yBottom
+                                        + ulYBottomNow;
+            rclArc.yTop = prclWholeStatic->yTop
+                                        - pChartStyle->ulThickness
+                                        + ulYBottomNow;
+        }
+
+        // calculate center point
+        ptlCenter.x = rclArc.xRight / 2;
+        ptlCenter.y = ((rclArc.yTop - rclArc.yBottom) / 2) + ulYBottomNow;
+
+        // Now, the "arc" APIs really suck. The following
+        // has cost me hours of testing to find out:
+
+        // a) The arc functions expect some kind of
+        //    "default arc" to be defined, which they
+        //    refer to. We define the arc as elliptical;
+        //    this will be used as the "current arc"
+        //    for subsequent arc calls.
+        //    (P, S) and (R, Q) define the end points
+        //    of the major axes of the ellipse.
+        //    The center of the arc will later be
+        //    specified with GpiPartialArc (while GpiFullArc
+        //    uses the current pen position...
+        //    Who created these APIs?!? This might be a most
+        //    flexible way to do things, but where's the
+        //    simple stuff?!?)
+        ap.lP = ptlCenter.x;        // X-axis X
+        ap.lS = 0;                  // X-axis Y
+        ap.lR = 0;                  // Y-axis X
+        ap.lQ = ((rclArc.yTop - rclArc.yBottom) / 2);
+                                    // Y-axis Y
+        GpiSetArcParams(hpsMem, &ap);
+
+        // b)  The line primitives determine lines
+        //     to be drawn around the pie slices.
+        //     We don't want any.
+        GpiSetLineType(hpsMem, LINETYPE_INVISIBLE);
+
+        // c)  Strangely, GpiSetPattern does work,
+        //     while GpiSetColor doesn't (see below).
+        GpiSetPattern(hpsMem, PATSYM_SOLID);
+
+        // initialize per-item data pointers for
+        // loop below
+        pdValueThis = pChartData->padValues;
+        plColorThis = pChartData->palColors;
+        ppszDescriptionThis = pChartData->papszDescriptions;
+        pptlDescriptionThis = paptlDescriptions;
+        phRegionThis = paRegions;
+
+        // 2) inner "pie slice loop":
+        // this loop goes over the data pointers
+        // and paints accordingly. At the end of
+        // the loop, we'll advance all those pointers.
+        for (ul = 0;
+             ul < pChartData->cValues;
+             ul++)
+        {
+            HRGN        hrgnThis;
+            double      dSweepAngleRounded,
+                        dStartAngleRounded;
+
+            // calculate the angle to sweep to:
+            // a simple rule of three
+            dSweepAngle = *pdValueThis      // current data pointer
+                          * (double)pChartData->usSweepAngle
+                                            // maximum angle
+                          / dTotal;         // total data sum
+
+            // d)  And now comes the real fun part.
+            //     GpiPartialArc is too dumb to draw
+            //     anything on its own, it must _always_
+            //     appear within an area or path definition.
+            //     Unfortunately, this isn't really said
+            //     clearly anywhere.
+            //     Even worse, in order to set the color
+            //     with which the slice is to be drawn,
+            //     one has to define an AREABUNDLE because
+            //     the regular GpiSetColor functions don't
+            //     seem to work here. Or maybe it's my fault,
+            //     but with this awful documentation, who knows.
+
+            //     We set the current color in the AREABUNDLE
+            //     from the color which was defined in the
+            //     pie chart data (this pointer was set above
+            //     and will be advanced for the next slice).
+            ab.lColor = *plColorThis;
+
+            // "3D mode" enabled with darkened socket?
+            if (    (pChartStyle->ulStyle & CHS_3D_DARKEN)
+                    // not last loop?
+                 && (!fNowDrawingSurface)
+               )
+                // darken the current fill color
+                // by halving each color component
+                gpihManipulateRGB(&ab.lColor,
+                                  .5);       // factor
+
+            // set the area (fill) color
+            GpiSetAttrs(hpsMem,
+                        PRIM_AREA,
+                        ABB_COLOR,
+                        0,
+                        (PBUNDLE)&ab);
+
+            GpiSetCurrentPosition(hpsMem, &ptlCenter);
+
+            // round the angle values properly
+            dStartAngleRounded = (dStartAngle + .5);
+            dSweepAngleRounded = (dSweepAngle + .5);
+
+            // now draw the pie slice;
+            // we could use an area, but since we need
+            // to remember the coordinates of the slice
+            // for mouse click handling, we require a
+            // region. But areas cannot be converted
+            // to regions, so we use a path instead.
+            GpiBeginPath(hpsMem,
+                         1);    // path ID, must be 1
+
+            // note that the arc functions use the FIXED type
+            // for representing fractions... GPI was written back
+            // at the days when coprocessors were a luxury, so
+            // a floating point value is represented by a LONG
+            // with the hiword containing the rounded integer
+            // and the loword the remaining fraction. So we
+            // can simply multiply the "double" by 65536, and
+            // we get the FIXED value V0.9.12 (2001-05-03) [umoeller]
+            GpiPartialArc(hpsMem,
+                          &ptlCenter,
+                          fxPieSize,    // calculated from CHARTSTYLE
+                          (FIXED)(dStartAngleRounded * (double)65536),
+                          (FIXED)(dSweepAngleRounded * (double)65536));
+                // this moves the current position to the outer
+                // point on the ellipse which corresponds to
+                // sSweepAngle
+            GpiEndPath(hpsMem);
+
+            // convert the path to a region;
+            // we'll need the region for mouse hit testing later
+            hrgnThis = GpiPathToRegion(hpsMem, 1,
+                                       FPATH_ALTERNATE);
+                // after this, the path is deleted
+
+            // last run (are we painting the top now)?
+            if (    (fNowDrawingSurface)
+                    // descriptions enabled?
+                 && (ppszDescriptionThis)
+                 && (*ppszDescriptionThis)
+               )
+            {
+                // yes: calculate position to paint
+                // text at later (we can't do this now,
+                // because it might be overpainted by
+                // the next arc again)
+
+                GpiSetCurrentPosition(hpsMem, &ptlCenter);
+                // move the current position to
+                // the center outer point on the ellipse
+                // (in between sStartAngle and sSweepAngle);
+                // since we're outside an area, this will not
+                // paint anything
+                GpiPartialArc(hpsMem,
+                              &ptlCenter,
+                              fxDescriptions, // calculated from CHARTSTYLE
+                              (FIXED)(dStartAngleRounded * (double)65536),
+                              // only half the sweep now:
+                              (FIXED)((dSweepAngleRounded / 2) * (double)65536));
+
+                // store this outer point in the array
+                // of description coordinates for later
+                GpiQueryCurrentPosition(hpsMem, pptlDescriptionThis);
+
+                _Pmpf(("pptlDescriptionThis = %d, %d",
+                            pptlDescriptionThis->x,
+                            pptlDescriptionThis->y));
+            }
+
+            // now, this FINALLY paints the arc
+            GpiPaintRegion(hpsMem, hrgnThis);
+
+            // last run (are we painting the top now)?
+            if (    (fNowDrawingSurface)
+                    // if separator lines are enabled,
+                    // draw a frame around the thing
+                 && (pChartStyle->ulStyle & CHS_DRAWLINES)
+               )
+            {
+                GpiSetColor(hpsMem, lTextColor);
+                GpiFrameRegion(hpsMem,
+                               hrgnThis,
+                               (PSIZEL)&cszlDrawLines);      // always (1, 1)
+            }
+
+            if (    phRegionThis
+                 && fNowDrawingSurface      // this was missing
+                                            // V0.9.12 (2001-05-03) [umoeller]
+                                            // this created tons of regions which
+                                            // were never deleted
+               )
+                // region output requested by caller:
+                // store region, the caller will clean this up
+                *phRegionThis = hrgnThis;
+            else
+                // no region output requested: destroy region
+                GpiDestroyRegion(hpsMem, hrgnThis);
+
+            // increase the start angle by the sweep angle for next loop
+            dStartAngle += dSweepAngle;
+
+            // advance the data pointers
+            pdValueThis++;
+            plColorThis++;
+            if (ppszDescriptionThis)
+                ppszDescriptionThis++;
+            pptlDescriptionThis++;
+            if (phRegionThis)
+                phRegionThis++;
+        } // end for (ul...)
+
+        // go for next "3D thickness" iteration
+        ulYBottomNow++;
+    } while (   (pChartStyle->ulStyle & CHS_3D_BRIGHT)
+             && (ulYBottomNow < pChartStyle->ulThickness)
+            );
+
+    // now paint descriptions
+    if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
+    {
+        // we use two pointers during the iteration,
+        // which point to the item corresponding
+        // to the current data item:
+        // 1)  pointer to center point on outer border
+        //     of partial arc
+        //     (calculated above)
+        PPOINTL     pptlDescriptionThis = paptlDescriptions;
+        // 2)  pointer to current description string
+        PSZ*        ppszDescriptionThis = pChartData->papszDescriptions;
+
+        // description strings valid?
+        if (ppszDescriptionThis)
+        {
+            // set presentation color
+            GpiSetColor(hpsMem, lTextColor);
+
+            // set text aligment to centered
+            // both horizontally and vertically;
+            // this affects subsequent GpiCharStringAt
+            // calls in that the output text will
+            // be centered around the specified
+            // point
+            GpiSetTextAlignment(hpsMem,
+                                TA_CENTER,      // horizontally
+                                TA_HALF);       // center vertically
+
+            // loop thru data items
+            for (ul = 0;
+                 ul < pChartData->cValues;
+                 ul++)
+            {
+                POINTL  ptlMiddlePoint;
+
+                // when drawing the arcs above, we have,
+                // for each pie slice, stored the middle
+                // point on the outer edge of the ellipse
+                // in the paptlDescriptions POINTL array:
+
+                //                ++++
+                //                +    +
+                //                +      +
+                //    ptlCenter\  +       +
+                //              \ +        + <-- current partial arc
+                //               \+        +
+                //     +++++++++++X        +
+                //     +                   +
+                //      +                  +
+                //       +               XX  <-- point calculated above
+                //        +             +
+                //           +        +
+                //             ++++++
+
+                // now calculate a middle point between
+                // that outer point on the ellipse and
+                // the center of the ellipse, which will
+                // be the center point for the text
+
+                //                ++++
+                //                +    +
+                //                +      +
+                //    ptlCenter\  +       +
+                //              \ +        + <-- current partial arc
+                //               \+        +
+                //     ++++++++++++        +
+                //     +             XX    + <-- new middle point
+                //      +                  +
+                //       +               XX  <-- point calculated above
+                //        +             +
+                //           +        +
+                //             ++++++
+
+                ptlMiddlePoint.x =
+                        ptlCenter.x
+                        + ((pptlDescriptionThis->x - ptlCenter.x) * 2 / 3);
+                ptlMiddlePoint.y =
+                        ptlCenter.y
+                        - (ptlCenter.y - pptlDescriptionThis->y) * 2 / 3;
+
+                // FINALLY, draw the description
+                // at this point; since we have used
+                // GpiSetTextAlignment above, the
+                // text will be centered on exactly
+                // that point
+                DrawCenteredText(hpsMem,
+                                 &ptlMiddlePoint,
+                                 *ppszDescriptionThis);
+
+                pptlDescriptionThis++;
+                ppszDescriptionThis++;
+            } // end for (ul = 0; ul < pChartData->cValues; ul++)
+        } // end if (ppszDescriptionThis)
+    } // end if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
+
+    // cleanup
+    free(paptlDescriptions);
+}
 
 /*
@@ -119,5 +983,5 @@
  *
  *      If (paRegions != NULL), this function will
- *      create GPI regions for data item. Each GPI
+ *      create a GPI region for each data item. Each GPI
  *      region will then contain the outline of the
  *      corresponding pie chart slice. This allows
@@ -137,6 +1001,11 @@
  *      This returns NULLHANDLE if an error occured.
  *      This can mean the following:
+ *
  *      --  The data is invalid, because the total is 0.
+ *
  *      --  The bitmap could not be created (memory?).
+ *
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: extracted PaintPieChart
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: added bar chart style
  */
 
@@ -158,7 +1027,11 @@
     for (ul = 0; ul < pChartData->cValues; ul++)
     {
+        _Pmpf(("    dThis is %d", (ULONG)(*pdThis))); // printf
+
         dTotal += *pdThis;
         pdThis++;
     }
+
+    _Pmpf((__FUNCTION__ ": dTotal is %d", (ULONG)dTotal)); // printf
 
     // avoid division by zero
@@ -166,6 +1039,4 @@
     {
         RECTL       rclWholeStatic;
-        ULONG       ulYBottomNow = 0;
-
         // get window rectangle (bottom left is 0, 0)
         rclWholeStatic.xLeft = 0;
@@ -181,12 +1052,4 @@
             // successfully created:
 
-            // allocate array for storing text positions later
-            PPOINTL     paptlDescriptions = (PPOINTL)malloc(sizeof(POINTL)
-                                                            * pChartData->cValues);
-            POINTL      ptlCenter;
-
-            FIXED       fxPieSize = (LONG)(pChartStyle->dPieSize * 65536),
-                        fxDescriptions = (LONG)(pChartStyle->dDescriptions * 65536);
-
             // associate bitmap with memory PS
             GpiSetBitmap(hpsMem, hbmReturn);
@@ -201,339 +1064,20 @@
                     &rclWholeStatic);
 
-            // We'll paint into the bitmap in two loops:
-            // +--  The outer "3D" loop is executed
-            // |    pChartStyle->ulThickness-fold, if
-            // |    CHS_3Dxxx has been enabled; otherwise
-            // |    just once.
-            // |
-            // |    +-- The inner "slice" loop goes thru the
-            // |        data fields in pChartData and draws
-            // |        the pies accordingly.
-            // |
-            // +--  We then increase the base Y point (ulYBottomNow)
-            //      by one and draw again, thereby getting the 3D
-            //      effect.
-
-            // 1) outer 3D loop
-            do // while (   (pChartStyle->ulStyle & CHS_3D_BRIGHT)...
-            {
-                RECTL       rclArc;
-                PLONG       plColorThis;
-                PSZ         *ppszDescriptionThis = NULL;
-                PPOINTL     pptlDescriptionThis;
-                HRGN*       phRegionThis;
-
-                ARCPARAMS   ap;
-                AREABUNDLE  ab;
-
-                double      dStartAngle = pChartData->usStartAngle,
-                            dSweepAngle = 0;
-
-                // this is only TRUE for the last loop
-                BOOL        fNowDrawingSurface =
-                              (
-                                   ((pChartStyle->ulStyle & CHS_3D_BRIGHT) == 0)
-                                ||
-                                   (ulYBottomNow == pChartStyle->ulThickness - 1)
-                              );
-
-                // // _Pmpf(("Looping, ulYBottomNow: %d", ulYBottomNow));
-
-                // calculate pie rectangle for this loop;
-                // this is the size of the static control
-                // minus the "3D thickness", if enabled
-                memcpy(&rclArc, &rclWholeStatic, sizeof(RECTL));
-                if (pChartStyle->ulStyle & CHS_3D_BRIGHT)
-                        // this includes CHS_3D_DARKEN
-                {
-                    rclArc.yBottom = rclWholeStatic.yBottom
-                                                + ulYBottomNow;
-                    rclArc.yTop = rclWholeStatic.yTop
-                                                - pChartStyle->ulThickness
-                                                + ulYBottomNow;
-                }
-
-                // calculate center point
-                ptlCenter.x = rclArc.xRight / 2;
-                ptlCenter.y = ((rclArc.yTop - rclArc.yBottom) / 2) + ulYBottomNow;
-
-                // Now, the "arc" APIs really suck. The following
-                // has cost me hours of testing to find out:
-
-                // a) The arc functions expect some kind of
-                //    "default arc" to be defined, which they
-                //    refer to. We define the arc as elliptical;
-                //    this will be used as the "current arc"
-                //    for subsequent arc calls.
-                //    (P, S) and (R, Q) define the end points
-                //    of the major axes of the ellipse.
-                //    The center of the arc will later be
-                //    specified with GpiPartialArc (while GpiFullArc
-                //    uses the current pen position...
-                //    Who created these APIs?!? This might be a most
-                //    flexible way to do things, but where's the
-                //    simple stuff?!?)
-                ap.lP = ptlCenter.x;        // X-axis X
-                ap.lS = 0;                  // X-axis Y
-                ap.lR = 0;                  // Y-axis X
-                ap.lQ = ((rclArc.yTop - rclArc.yBottom) / 2);
-                                            // Y-axis Y
-                GpiSetArcParams(hpsMem, &ap);
-
-                // b)  The line primitives determine lines
-                //     to be drawn around the pie slices.
-                //     We don't want any.
-                GpiSetLineType(hpsMem, LINETYPE_INVISIBLE);
-
-                // c)  Strangely, GpiSetPattern does work,
-                //     while GpiSetColor doesn't (see below).
-                GpiSetPattern(hpsMem, PATSYM_SOLID);
-
-                // initialize per-item data pointers for
-                // loop below
-                pdThis = pChartData->padValues;
-                plColorThis = pChartData->palColors;
-                ppszDescriptionThis = pChartData->papszDescriptions;
-                pptlDescriptionThis = paptlDescriptions;
-                phRegionThis = paRegions;
-
-                // 2) inner "pie slice loop":
-                // this loop goes over the data pointers
-                // and paints accordingly. At the end of
-                // the loop, we'll advance all those pointers.
-                for (ul = 0; ul < pChartData->cValues; ul++)
-                {
-                    HRGN        hrgnThis;
-                    SHORT       sSweepAngle,
-                                sStartAngle;
-
-                    // calculate the angle to sweep to:
-                    // a simple rule of three
-                    dSweepAngle = *pdThis           // current data pointer
-                                  * pChartData->usSweepAngle
-                                                    // maximum angle
-                                  / dTotal;         // total data sum
-
-                    // d)  And now comes the real fun part.
-                    //     GpiPartialArc is too dumb to draw
-                    //     anything on its own, it must _always_
-                    //     appear within an area or path definition.
-                    //     Unfortunately, this isn't really said
-                    //     clearly anywhere.
-                    //     Even worse, in order to set the color
-                    //     with which the slice is to be drawn,
-                    //     one has to define an AREABUNDLE because
-                    //     the regular GpiSetColor functions don't
-                    //     seem to work here. Or maybe it's my fault,
-                    //     but with this awful documentation, who knows.
-                    //     We use the current color defined in the
-                    //     pie chart data (this pointer was set above
-                    //     and will be advanced for the next slice).
-                    ab.lColor = *plColorThis;
-
-                    // "3D mode" enabled with darkened socket?
-                    if (    (pChartStyle->ulStyle & CHS_3D_DARKEN)
-                            // not last loop?
-                         && (!fNowDrawingSurface)
-                       )
-                        // darken the current fill color
-                        // by halving each color component
-                        gpihManipulateRGB(&ab.lColor,
-                                          .5);       // factor
-
-                    // set the area (fill) color
-                    GpiSetAttrs(hpsMem,
-                                PRIM_AREA,
-                                ABB_COLOR,
-                                0,
-                                (PBUNDLE)&ab);
-
-                    GpiSetCurrentPosition(hpsMem, &ptlCenter);
-
-                    // round the angle values properly
-                    sStartAngle = (SHORT)(dStartAngle + .5);
-                    sSweepAngle = (SHORT)(dSweepAngle + .5);
-
-                    // now draw the pie slice;
-                    // we could use an area, but since we need
-                    // to remember the coordinates of the slice
-                    // for mouse click handling, we require a
-                    // region. But areas cannot be converted
-                    // to regions, so we use a path instead.
-                    GpiBeginPath(hpsMem,
-                                 1);    // path ID, must be 1
-                    GpiPartialArc(hpsMem,
-                                  &ptlCenter,
-                                  fxPieSize,    // calculated from CHARTSTYLE
-                                  MAKEFIXED(sStartAngle, 0),
-                                  MAKEFIXED(sSweepAngle, 0));
-                        // this moves the current position to the outer
-                        // point on the ellipse which corresponds to
-                        // sSweepAngle
-                    GpiEndPath(hpsMem);
-
-                    // convert the path to a region;
-                    // we'll need the region for mouse hit testing later
-                    hrgnThis = GpiPathToRegion(hpsMem, 1,
-                                               FPATH_ALTERNATE);
-                        // after this, the path is deleted
-                    GpiPaintRegion(hpsMem, hrgnThis);
-                    if (phRegionThis)
-                        // region output requested by caller:
-                        // store region, the caller will clean this up
-                        *phRegionThis = hrgnThis;
-                    else
-                        // drop region
-                        GpiDestroyRegion(hpsMem, hrgnThis);
-
-                    // descriptions enabled and last run?
-                    if (    (ppszDescriptionThis)
-                         && (fNowDrawingSurface)
-                       )
-                    {
-                        if (*ppszDescriptionThis)
-                        {
-                            // yes: calculate position to paint
-                            // text at later (we can't do this now,
-                            // because it might be overpainted by
-                            // the next arc again)
-
-                            GpiSetCurrentPosition(hpsMem, &ptlCenter);
-                            // move the current position to
-                            // the center outer point on the ellipse
-                            // (in between sStartAngle and sSweepAngle);
-                            // since we're outside an area, this will not
-                            // paint anything
-                            GpiPartialArc(hpsMem,
-                                          &ptlCenter,
-                                          fxDescriptions, // calculated from CHARTSTYLE
-                                          MAKEFIXED(sStartAngle, 0),
-                                            // only half the sweep now:
-                                          MAKEFIXED(sSweepAngle / 2, 0));
-
-                            // store this outer point in the array
-                            // of description coordinates for later
-                            GpiQueryCurrentPosition(hpsMem, pptlDescriptionThis);
-                        }
-                    }
-
-                    // increase the start angle by the sweep angle for next loop
-                    dStartAngle += dSweepAngle;
-
-                    // advance the data pointers
-                    pdThis++;
-                    plColorThis++;
-                    if (ppszDescriptionThis)
-                        ppszDescriptionThis++;
-                    pptlDescriptionThis++;
-                    if (phRegionThis)
-                        phRegionThis++;
-                } // end for (ul...)
-
-                // go for next "3D thickness" iteration
-                ulYBottomNow++;
-            } while (   (pChartStyle->ulStyle & CHS_3D_BRIGHT)
-                     && (ulYBottomNow < pChartStyle->ulThickness)
-                    );
-
-            // now paint descriptions
-            if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
-            {
-                // we use two pointers during the iteration,
-                // which point to the item corresponding
-                // to the current data item:
-                // 1)  pointer to center point on outer border
-                //     of partial arc
-                //     (calculated above)
-                PPOINTL     pptlDescriptionThis = paptlDescriptions;
-                // 2)  pointer to current description string
-                PSZ*        ppszDescriptionThis = pChartData->papszDescriptions;
-
-                // description strings valid?
-                if (ppszDescriptionThis)
-                {
-                    // set presentation color
-                    GpiSetColor(hpsMem, lTextColor);
-
-                    // set text aligment to centered
-                    // both horizontally and vertically;
-                    // this affects subsequent GpiCharStringAt
-                    // calls in that the output text will
-                    // be centered around the specified
-                    // point
-                    GpiSetTextAlignment(hpsMem,
-                                        TA_CENTER,      // horizontally
-                                        TA_HALF);       // center vertically
-
-                    // loop thru data items
-                    for (ul = 0;
-                         ul < pChartData->cValues;
-                         ul++)
-                    {
-                        POINTL  ptlMiddlePoint;
-
-                        // when drawing the arcs above, we have,
-                        // for each pie slice, stored the middle
-                        // point on the outer edge of the ellipse
-                        // in the paptlDescriptions POINTL array:
-
-                        //                ++++
-                        //                +    +
-                        //                +      +
-                        //    ptlCenter\  +       +
-                        //              \ +        + <-- current partial arc
-                        //               \+        +
-                        //     +++++++++++X        +
-                        //     +                   +
-                        //      +                  +
-                        //       +               XX  <-- point calculated above
-                        //        +             +
-                        //           +        +
-                        //             ++++++
-
-                        // now calculate a middle point between
-                        // that outer point on the ellipse and
-                        // the center of the ellipse, which will
-                        // be the center point for the text
-
-                        //                ++++
-                        //                +    +
-                        //                +      +
-                        //    ptlCenter\  +       +
-                        //              \ +        + <-- current partial arc
-                        //               \+        +
-                        //     ++++++++++++        +
-                        //     +             XX    + <-- new middle point
-                        //      +                  +
-                        //       +               XX  <-- point calculated above
-                        //        +             +
-                        //           +        +
-                        //             ++++++
-
-                        ptlMiddlePoint.x =
-                                ptlCenter.x
-                                + ((pptlDescriptionThis->x - ptlCenter.x) * 2 / 3);
-                        ptlMiddlePoint.y =
-                                ptlCenter.y
-                                - (ptlCenter.y - pptlDescriptionThis->y) * 2 / 3;
-
-                        // FINALLY, draw the description
-                        // at this point; since we have used
-                        // GpiSetTextAlignment above, the
-                        // text will be centered on exactly
-                        // that point
-                        GpiCharStringAt(hpsMem,
-                                        &ptlMiddlePoint,
-                                        strlen(*ppszDescriptionThis),
-                                        *ppszDescriptionThis);
-
-                        pptlDescriptionThis++;
-                        ppszDescriptionThis++;
-                    } // end for (ul = 0; ul < pChartData->cValues; ul++)
-                } // end if (ppszDescriptionThis)
-            } // end if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
-
-            // cleanup
-            free(paptlDescriptions);
+            if (pChartStyle->ulStyle & CHS_BARCHART)
+                PaintBarChart(hpsMem,
+                              &rclWholeStatic,
+                              pChartData,
+                              pChartStyle,
+                              dTotal,
+                              lTextColor,
+                              paRegions);
+            else
+                PaintPieChart(hpsMem,
+                              &rclWholeStatic,
+                              pChartData,
+                              pChartStyle,
+                              dTotal,
+                              lTextColor,
+                              paRegions);
 
             // deselect (free) bitmap
@@ -551,4 +1095,5 @@
  *
  *@@changed V0.9.2 (2000-02-29) [umoeller]: fixed maaajor memory leak
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed major PM resource leaks
  */
 
@@ -557,31 +1102,41 @@
     if (pChtCData)
     {
+        // destroy regions, but not the array itself
+        // (this is done in CleanupData)
+        if (pChtCData->hpsMem && pChtCData->paRegions)
+        {
+            ULONG   ul;
+            for (ul = 0;
+                 ul < pChtCData->cd.cValues;
+                 ul++)
+            {
+                if (pChtCData->paRegions[ul])
+                {
+                    GpiDestroyRegion(pChtCData->hpsMem, pChtCData->paRegions[ul]);
+                    pChtCData->paRegions[ul] = NULLHANDLE;
+                }
+            }
+        }
+
         // bitmap already created?
         if (pChtCData->hbmChart)
         {
             // free current bitmap
-            // GpiSetBitmap(pChtCData->hpsMem, NULLHANDLE);
+            GpiSetBitmap(pChtCData->hpsMem, NULLHANDLE);
             // delete bitmap; fails if not freed!
-            GpiDeleteBitmap(pChtCData->hbmChart);
+            if (!GpiDeleteBitmap(pChtCData->hbmChart))
+                _Pmpf((__FUNCTION__ ": GpiDeleteBitmap failed."));
             pChtCData->hbmChart = NULLHANDLE;
         }
 
-        // destroy regions, but not the array itself
-        // (this is done in CleanupData)
-        if (pChtCData->paRegions)
+        if (pChtCData->hpsMem)
         {
-            ULONG   ul;
-            HRGN    *phRegionThis = pChtCData->paRegions;
-            for (ul = 0;
-                 ul < pChtCData->cd.cValues;
-                 ul++)
-            {
-                if (*phRegionThis)
-                {
-                    GpiDestroyRegion(pChtCData->hpsMem, *phRegionThis);
-                    *phRegionThis = NULLHANDLE;
-                }
-                phRegionThis++;
-            }
+            GpiDestroyPS(pChtCData->hpsMem);
+            pChtCData->hpsMem = NULLHANDLE;
+        }
+        if (pChtCData->hdcMem)
+        {
+            DevCloseDC(pChtCData->hdcMem);
+            pChtCData->hdcMem = NULLHANDLE;
         }
     }
@@ -647,4 +1202,5 @@
  *
  *@@added V0.9.2 (2000-02-29) [umoeller]
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed trap if ptr to descriptions was null
  */
 
@@ -663,19 +1219,4 @@
     CleanupData(pChtCData);
 
-    // _Pmpf(("Setting up data"));
-
-    if (pChtCData->hpsMem == NULLHANDLE)
-    {
-        // first call:
-        // create a memory PS for the bitmap
-        SIZEL szlPage = {0, 0};
-        gpihCreateMemPS(WinQueryAnchorBlock(hwndChart),
-                        &szlPage,
-                        &pChtCData->hdcMem,
-                        &pChtCData->hpsMem);
-        // _Pmpf(("Created HPS 0x%lX", pChtCData->hpsMem));
-        // _Pmpf(("Created HDC 0x%lX", pChtCData->hdcMem));
-    }
-
     pChtCData->cd.usStartAngle = pcdNew->usStartAngle;
     pChtCData->cd.usSweepAngle = pcdNew->usSweepAngle;
@@ -695,17 +1236,22 @@
 
     // copy strings
-    pChtCData->cd.papszDescriptions = (PSZ*)malloc(sizeof(PSZ) * pcdNew->cValues);
-    ppszDescriptionSource = pcdNew->papszDescriptions;
-    ppszDescriptionTarget = pChtCData->cd.papszDescriptions;
-    for (ul = 0;
-         ul < pcdNew->cValues;
-         ul++)
+    if (!pcdNew->papszDescriptions)
+        pChtCData->cd.papszDescriptions = NULL;
+    else
     {
-        if (*ppszDescriptionSource)
-            *ppszDescriptionTarget = strdup(*ppszDescriptionSource);
-        else
-            *ppszDescriptionTarget = NULL;
-        ppszDescriptionSource++;
-        ppszDescriptionTarget++;
+        pChtCData->cd.papszDescriptions = (PSZ*)malloc(sizeof(PSZ) * pcdNew->cValues);
+        ppszDescriptionSource = pcdNew->papszDescriptions;
+        ppszDescriptionTarget = pChtCData->cd.papszDescriptions;
+        for (ul = 0;
+             ul < pcdNew->cValues;
+             ul++)
+        {
+            if (*ppszDescriptionSource)
+                *ppszDescriptionTarget = strdup(*ppszDescriptionSource);
+            else
+                *ppszDescriptionTarget = NULL;
+            ppszDescriptionSource++;
+            ppszDescriptionTarget++;
+        }
     }
 
@@ -716,4 +1262,5 @@
 
     pChtCData->lSelected = -1;     // none selected
+    pChtCData->lSourceEmphasis = -1;     // none selected
 
     WinInvalidateRect(hwndChart, NULL, FALSE);
@@ -726,4 +1273,5 @@
  *
  *@@added V0.9.2 (2000-02-29) [umoeller]
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed major PM resource leaks
  */
 
@@ -735,6 +1283,4 @@
     RECTL   rclStatic;
     WinQueryWindowRect(hwndChart, &rclStatic);
-
-    // _Pmpf(("ctl_fnwpChart: WM_PAINT, cValues: %d", pChtCData->cd.cValues));
 
     // do we have any values yet?
@@ -762,30 +1308,42 @@
                                                    SYSCLR_WINDOWTEXT);
 
-        // yes: check if we created the bitmap
-        // already
+        gpihSwitchToRGB(hps);
+
+        // check if the bitmap needs to be (re)created
         if (pChtCData->hbmChart == NULLHANDLE)
         {
             // no: do it now
             HPOINTER    hptrOld = winhSetWaitPointer();
-
-            // get presentation font
-            FONTMETRICS FontMetrics;
-            LONG        lPointSize;
-            LONG lLCIDSet = gpihFindPresFont(hwndChart,
-                                             TRUE,      // inherit PP
-                                             pChtCData->hpsMem,
-                                             "8.Helv",
-                                             &FontMetrics,
-                                             &lPointSize);
-            // set presentation font
-            if (lLCIDSet)
+            LONG        lLCIDSet = 0;
+
+            if (pChtCData->hpsMem == NULLHANDLE)
             {
-                GpiSetCharSet(pChtCData->hpsMem, lLCIDSet);
-                if (FontMetrics.fsDefn & FM_DEFN_OUTLINE)
-                    gpihSetPointSize(pChtCData->hpsMem, lPointSize);
+                // first call:
+                FONTMETRICS FontMetrics;
+                LONG        lPointSize;
+
+                // create a memory PS for the bitmap
+                SIZEL szlPage = {rclStatic.xRight,
+                                 rclStatic.yTop};
+                gpihCreateMemPS(WinQueryAnchorBlock(hwndChart),
+                                &szlPage,
+                                &pChtCData->hdcMem,
+                                &pChtCData->hpsMem);
+
+                // get presentation font
+                lLCIDSet = gpihFindPresFont(hwndChart,
+                                            TRUE,      // inherit PP
+                                            pChtCData->hpsMem,
+                                            "8.Helv",
+                                            &FontMetrics,
+                                            &lPointSize);
+                // set presentation font
+                if (lLCIDSet)
+                {
+                    GpiSetCharSet(pChtCData->hpsMem, lLCIDSet);
+                    if (FontMetrics.fsDefn & FM_DEFN_OUTLINE)
+                        gpihSetPointSize(pChtCData->hpsMem, lPointSize);
+                }
             }
-
-
-            gpihSwitchToRGB(hps);
 
             pChtCData->hbmChart = ctlCreateChartBitmap(
@@ -804,10 +1362,11 @@
                       pChtCData->paRegions);
                               // out: regions array
-            // _Pmpf(("Created bitmap 0x%lX", pChtCData->hbmChart));
 
             // unset and delete font
-            GpiSetCharSet(pChtCData->hpsMem, LCID_DEFAULT);
             if (lLCIDSet)
+            {
+                GpiSetCharSet(pChtCData->hpsMem, LCID_DEFAULT);
                 GpiDeleteSetId(pChtCData->hpsMem, lLCIDSet);
+            }
 
             WinSetPointer(HWND_DESKTOP, hptrOld);
@@ -816,4 +1375,5 @@
         if (pChtCData->hbmChart)
         {
+            // draw the chart bitmap
             POINTL  ptlDest = { 0, 0 };
             WinDrawBitmap(hps,
@@ -824,22 +1384,60 @@
                           DBM_NORMAL);
 
-            // do we have the focus?
-            if (pChtCData->fHasFocus)
-                // something selected?
-                if (pChtCData->lSelected != -1)
-                    if (pChtCData->paRegions)
+            // now draw emphasis, if we have regions
+            if (pChtCData->paRegions)
+            {
+                AREABUNDLE ab;
+                SIZEL   sl = {2, 2};
+
+                // 1) source emphasis (even if we don't have focus)
+                if (pChtCData->lSourceEmphasis != -1)
+                {
+                    if (pChtCData->paRegions[pChtCData->lSourceEmphasis])
                     {
-                        HRGN* pRegionThis = pChtCData->paRegions; // first region
-                        pRegionThis += pChtCData->lSelected;      // array item
-
-                        if (*pRegionThis)
-                        {
-                            SIZEL   sl = {2, 2};
-                            GpiSetColor(hps, lForegroundColor);
-                            GpiFrameRegion(hps,
-                                           *pRegionThis,
-                                           &sl);
-                        }
+                        // GpiFrameRegion uses the current pattern
+                        // attributes, so we must set the correct
+                        // color in there (GpiSetColor doesn't work)
+                        // V0.9.12 (2001-05-03) [umoeller]
+
+                        ab.lColor = lForegroundColor;
+                        GpiSetAttrs(hps,
+                                    PRIM_AREA,
+                                    ABB_COLOR,
+                                    0,
+                                    (PBUNDLE)&ab);
+                        GpiSetPattern(hps,
+                                      PATSYM_DIAG1);
+                        GpiPaintRegion(hps,
+                                       pChtCData->paRegions[pChtCData->lSourceEmphasis]);
                     }
+                }
+
+                // 2) selection
+
+                // do we have the focus?
+                if (    (pChtCData->fHasFocus)
+                        // something selected?
+                     && (pChtCData->lSelected != -1)
+                   )
+                {
+                    if (pChtCData->paRegions[pChtCData->lSelected])
+                    {
+                        // GpiFrameRegion uses the current pattern
+                        // attributes, so we must set the correct
+                        // color in there (GpiSetColor doesn't work)
+                        // V0.9.12 (2001-05-03) [umoeller]
+
+                        ab.lColor = RGBCOL_RED; // lForegroundColor;
+                        GpiSetAttrs(hps,
+                                    PRIM_AREA,
+                                    ABB_COLOR,
+                                    0,
+                                    (PBUNDLE)&ab);
+                        GpiFrameRegion(hps,
+                                       pChtCData->paRegions[pChtCData->lSelected],
+                                       &sl);
+                    }
+                }
+            }
         }
     }
@@ -847,4 +1445,129 @@
 
 /*
+ *@@ FindItemFromPoint:
+ *      returns the index of the slice under the
+ *      given window coordinates, or -1 if there's
+ *      none.
+ *
+ *@@added V0.9.12 (2001-05-03) [umoeller]
+ */
+
+LONG FindItemFromPoint(PCHARTCDATA pChtCData,
+                       LONG lx,
+                       LONG ly)
+{
+    LONG lRegionFound = -1; // none
+
+    POINTL ptlMouse = {lx, ly};
+
+    // data set?
+    if (    (pChtCData->cd.cValues)
+         && (pChtCData->paRegions)
+       )
+    {
+        ULONG   ul;
+        for (ul = 0;
+             ul < pChtCData->cd.cValues;
+             ul++)
+        {
+            HRGN hRgnThis = pChtCData->paRegions[ul];
+            if (hRgnThis)
+            {
+                if (GpiPtInRegion(pChtCData->hpsMem,
+                                  hRgnThis,
+                                  &ptlMouse)
+                     == PRGN_INSIDE)
+                {
+                    return (ul);
+                }
+            }
+        }
+    }
+
+    return (-1);
+}
+
+/*
+ *@@ SendWMControl:
+ *
+ *@@added V0.9.12 (2001-05-03) [umoeller]
+ */
+
+VOID SendWMControl(HWND hwndChart,
+                   MPARAM mp1Mouse,
+                   USHORT usNotify,
+                   ULONG ulEmphasis,            // 0 or 1
+                   LONG lIndex)
+{
+    HWND hwndOwner;
+
+    if (hwndOwner = WinQueryWindow(hwndChart, QW_OWNER))
+    {
+        EMPHASISNOTIFY en;
+        en.hwndSource = hwndChart;
+        en.lIndex = lIndex;        // can be -1
+        en.ulEmphasis = ulEmphasis;
+        en.ptl.x = SHORT1FROMMP(mp1Mouse);
+        en.ptl.y = SHORT2FROMMP(mp1Mouse);
+
+        WinSendMsg(hwndOwner,
+                   WM_CONTROL,
+                   MPFROM2SHORT(WinQueryWindowUShort(hwndChart,
+                                                     QWS_ID),
+                                usNotify),
+                   &en);
+    }
+}
+
+/*
+ *@@ SetEmphasis:
+ *
+ *@@added V0.9.12 (2001-05-03) [umoeller]
+ */
+
+BOOL SetEmphasis(HWND hwndChart,
+                 PCHARTCDATA pChtCData,
+                 ULONG ulEmphasis,      // in: 0 == selection, 1 == source emphasis
+                 MPARAM mp1,            // in: mp1 with mouse values or -1 if none
+                 LONG lIndex,
+                 BOOL fIsContextMenu)
+{
+    BOOL brc = FALSE;
+
+    PLONG plOld;
+
+    if (ulEmphasis == 0)
+        plOld = &pChtCData->lSelected;
+    else if (ulEmphasis == 1)
+        plOld = &pChtCData->lSourceEmphasis;
+
+    if (plOld)
+    {
+        if (    (*plOld != lIndex)
+             || (fIsContextMenu)
+           )
+        {
+            // selection changed:
+            *plOld = lIndex;
+
+            brc = TRUE;
+
+            // repaint
+            WinInvalidateRect(hwndChart, NULL, FALSE);
+
+            // send notification to owner
+            // V0.9.12 (2001-05-03) [umoeller]
+            SendWMControl(hwndChart,
+                          mp1,
+                          CHTN_EMPHASISCHANGED,
+                          ulEmphasis,
+                          lIndex);
+        }
+    }
+
+    return (brc);
+}
+
+/*
  *@@ ctl_fnwpChart:
  *      window procedure for the "chart" control.
@@ -856,4 +1579,5 @@
  *@@changed V0.9.2 (2000-02-29) [umoeller]: added resize support
  *@@changed V0.9.2 (2000-02-29) [umoeller]: fixed baaad PM resource leaks, the bitmap was never freed
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: added WM_CONTEXTMENU support
  */
 
@@ -887,8 +1611,7 @@
                 // _Pmpf(("CHTM_SETCHARTDATA, mp1: 0x%lX", mp1));
                 if (mp1)
-                {
-                    PCHARTDATA   pcdNew = (PCHARTDATA)mp1;
-                    SetChartData(hwndChart, pChtCData, pcdNew);
-                }
+                    SetChartData(hwndChart,
+                                 pChtCData,
+                                 (PCHARTDATA)mp1);
             break;
 
@@ -943,56 +1666,68 @@
             case CHTM_ITEMFROMPOINT:
             {
-                LONG lRegionFound = -1; // none
-
-                // get mouse coordinates
-                POINTL ptlMouse;
-                ptlMouse.x = SHORT1FROMMP(mp1);
-                ptlMouse.y = SHORT2FROMMP(mp1);
-
-                // data set?
-                if (pChtCData->cd.cValues)
+                mrc = (MPARAM)FindItemFromPoint(pChtCData,
+                                                SHORT1FROMMP(mp1),
+                                                SHORT2FROMMP(mp1));
+            break; }
+
+            /*
+             *@@ CHTM_SETEMPHASIS:
+             *      sets emphasis on a chart slice.
+             *
+             *      Parameters:
+             *
+             *      LONG mp1: emphasis to set. Currently
+             *              defined values:
+             *
+             *              -- 0: set selection.
+             *
+             *              -- 1: set source emphasis.
+             *
+             *      LONG mp2: zero-based index of slice to
+             *              set emphasis for, or -1 to
+             *              remove that emphasis.
+             *
+             *      Returns: TRUE if emphasis was changed.
+             *
+             *@@added V0.9.12 (2001-05-03) [umoeller]
+             */
+
+            case CHTM_SETEMPHASIS:
+            {
+                LONG lEmph = (LONG)mp1;
+                LONG lIndex = (LONG)mp2;
+                if (    lEmph > 0
+                     && lEmph < 2
+                     && (    lIndex >= -1
+                          || (    lIndex > 0
+                               && lIndex < pChtCData->cd.cValues
+                             )
+                        )
+                   )
                 {
-                    // regions defined?
-                    if (pChtCData->paRegions)
-                    {
-                        ULONG   ul;
-                        HRGN*   phRegionThis = pChtCData->paRegions;
-                        for (ul = 0;
-                             ul < pChtCData->cd.cValues;
-                             ul++)
-                        {
-                            if (*phRegionThis)
-                            {
-                                if (GpiPtInRegion(pChtCData->hpsMem,
-                                                  *phRegionThis,
-                                                  &ptlMouse)
-                                     == PRGN_INSIDE)
-                                {
-                                    // _Pmpf(("Clicked in region %d", ul));
-                                    lRegionFound = ul;
-                                    break;
-                                }
-                            }
-                            phRegionThis++;
-                        }
-
-                    }
+                    mrc = (MPARAM)SetEmphasis(hwndChart,
+                                              pChtCData,
+                                              lEmph,
+                                              (MPARAM)-1,
+                                              lIndex,
+                                              // force flag:
+                                              TRUE);
                 }
-
-                mrc = (MPARAM)lRegionFound;
-            break; }
+            }
+            break;
 
             /*
-             * WM_BUTTON1DOWN:
-             *
+             * WM_BUTTON1CLICK:
+             * WM_CONTEXTMENU: V0.9.12 (2001-05-03) [umoeller]
              */
 
-            case WM_BUTTON1DOWN:
+            case WM_BUTTON1CLICK:
+            case WM_BUTTON1DBLCLK:
+            case WM_CONTEXTMENU:
                 if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
                 {
-                    LONG lRegionFound = (LONG)WinSendMsg(hwndChart,
-                                                         CHTM_ITEMFROMPOINT,
-                                                         mp1,
-                                                         NULL);
+                    LONG lRegionFound = FindItemFromPoint(pChtCData,
+                                                SHORT1FROMMP(mp1),
+                                                SHORT2FROMMP(mp1));
 
                     // selections allowed:
@@ -1001,10 +1736,31 @@
                     WinSetFocus(HWND_DESKTOP, hwndChart);
                         // this invalidates the window
-                    if (pChtCData->lSelected != lRegionFound)
+
+                    if (msg == WM_CONTEXTMENU)
+                    {
+                        // context menu:
+                        SendWMControl(hwndChart,
+                                      mp1,
+                                      CHTN_CONTEXTMENU,
+                                      -1,
+                                      lRegionFound);
+                    }
+                    else
                     {
                         // selection changed:
-                        pChtCData->lSelected = lRegionFound;
-                        // repaint
-                        WinInvalidateRect(hwndChart, NULL, FALSE);
+                        SetEmphasis(hwndChart,
+                                    pChtCData,
+                                    0,    // set selection; caller must set source emphasis
+                                    mp1,
+                                    lRegionFound,
+                                    // force flag:
+                                    FALSE);
+
+                        if (msg == WM_BUTTON1DBLCLK)
+                            SendWMControl(hwndChart,
+                                          mp1,
+                                          CHTN_ENTER,
+                                          0,
+                                          lRegionFound);
                     }
                 }
@@ -1047,12 +1803,12 @@
                 // resizing?
                 if (pswpNew->fl & SWP_SIZE)
+                {
                     if (pChtCData->hbmChart)
-                    {
                         // invalidate bitmap so that
                         // it will be recreated with new size
                         CleanupBitmap(pChtCData);
-                        WinInvalidateRect(hwndChart, NULL, FALSE);
-                    }
-
+
+                    WinInvalidateRect(hwndChart, NULL, FALSE);
+                }
                 // return default NULL
             break; }
@@ -1103,12 +1859,7 @@
             case WM_DESTROY:
                 CleanupBitmap(pChtCData);
+
                 CleanupData(pChtCData);
 
-                // _Pmpf(("Destroying HPS 0x%lX", pChtCData->hpsMem));
-                if (!GpiDestroyPS(pChtCData->hpsMem));
-                    // _Pmpf(("  Error!"));
-                // _Pmpf(("Destroying HDC 0x%lX", pChtCData->hdcMem));
-                if (!DevCloseDC(pChtCData->hdcMem));
-                    // _Pmpf(("  Error!"));
                 free(pChtCData);
 
@@ -1130,123 +1881,5 @@
  *      window procedure with ctl_fnwpChart.
  *
- *      This way you can easily create a chart control as a static
- *      control in any Dialog Editor;
- *      after loading the dlg template, simply call this function
- *      with the hwnd of the static control to make it a chart.
- *
- *      The pie chart consumes all available space in the static control.
- *
- *      In XWorkplace, this is used for the pie chart on the new
- *      XFldDisk "Details" settings page to display the free space
- *      on a certain drive.
- *
- *      Note: even though you can use _any_ type of static control
- *      with this function, you should use a static _text_ control,
- *      because not all types of static controls react to fonts and
- *      colors dragged upon them. The static _text_ control does.
- *
- *      <B>Chart data:</B>
- *
- *      The pie chart control operates on an array of "double" values.
- *      Each value in that array corresponds to a color in a second
- *      array of (LONG) RGB values and, if description texts are
- *      enabled, to a third array of PSZ's.
- *
- *      The data on which the pie chart operates is initialized to
- *      be void, so that the pie chart will not paint anything
- *      initially. In order to have the pie chart display something,
- *      post or send a CHTM_SETCHARTDATA message (comctl.h) to the static
- *      control after it has been subclassed.
- *
- *      CHTM_SETCHARTDATA takes a CHARTDATA structure (comctl.h) in mp1,
- *      which must contain the chart data and corresponding colors to be
- *      displayed.
- *
- *      The total sum of the "double" values will represent the angle in
- *      CHARTDATA.usSweepAngle.
- *
- *      For example, if two values of 50 and 100 are passed to the
- *      control and usSweepAngle is 270 (i.e. a three-quarter pie),
- *      the chart control will calculate the following:
- *
- *      1)  The sum of the data is 150.
- *
- *      2)  The first sub-arc will span an angle of 270 * (50/150)
- *          = 90 degrees.
- *
- *      3)  The second sub-arc will span an angle of 270 * (100/150)
- *          = 180 degrees.
- *
- *      You can also have descriptions displayed along the different
- *      chart items by specifying CHARTDATA.papszDescriptions and
- *      setting the CHS_DESCRIPTIONS flag (below).
- *
- *      <B>Chart styles:</B>
- *
- *      Use CHTM_SETCHARTSTYLE with a PCHARTSTYLE (comctl.h) in mp1.
- *      This can be sent to the chart control several times.
- *
- *      Presently, only pie charts are implemented. However, we do
- *      have several "sub-styles" for pie charts:
- *      -- CHS_3D_BRIGHT: paint a "3D" socket below the actual chart.
- *      -- CHS_3D_DARKEN: like CHS_3D_BRIGHT, but the socket will be made
- *                        darker compared to the surface.
- *
- *      General styles:
- *      -- CHS_DESCRIPTIONS: show descriptions on the chart
- *                           (CHARTDATA.papszDescriptions data).
- *      -- CHS_SELECTIONS: allow pie chart slices to be selectable,
- *                         using the mouse and the keyboard.
- *
- *      <B>Display:</B>
- *
- *      The chart control creates an internal bitmap for the display
- *      only once (ctlCreateChartBitmap). This bitmap is refreshed if
- *      neccessary, e.g. because chart data or styles have changed.
- *
- *      The chart control uses presentation parameters, as listed below.
- *      Presentation parameters are inherited from the parent window.
- *      If a presparam is not set, the corresponding system color is
- *      used. The following color pairs are recognized:
- *
- *      --  PP_BACKGROUNDCOLOR / SYSCLR_DIALOGBACKGROUND:
- *          background of the chart control (outside the chart).
- *      --  PP_FOREGROUNDCOLOR / SYSCLR_WINDOWTEXT:
- *          text color, if description texts are enabled and valid.
- *      --  PP_FONTNAMESIZE:
- *          text font, if description texts are enabled and valid.
- *          If this presparam is not set, the system font is used.
- *
- *      The control reacts to fonts and colors dropped upon it, if
- *      it has been subclassed from a static _text_ control (see above).
- *      It also recalculates the bitmap when it's resized.
- *
- *      <B>Example usage:</B>
- *
- +          // get static control:
- +          HWND    hwndChart = WinWindowFromID(hwndDialog, ID_...);
- +          CHARTSTYLE      cs;
- +          CHARTDATA       cd;
- +          // define data:
- +          double          adData[3] = { 100, 200, 300 };
- +          // define corresponding colors:
- +          LONG            alColors[3] = { 0x800000, 0x008000, 0x000080 };
- +          // define correspdonding descriptions:
- +          PSZ             apszDescriptions[3] = { "item 1", "item 3", "item 3" };
- +
- +          ctlChartFromStatic(hwndChart);     // create chart
- +
- +          cs.ulStyle = CHS_3D_DARKEN | CHS_DESCRIPTIONS;
- +          cs.ulThickness = 20;
- +          WinSendMsg(hwndChart, CHTM_SETCHARTSTYLE, &cs, NULL);
- +
- +          cd.usStartAngle = 15;       // start at 15ø from right
- +          cd.usSweepAngle = 270;      // three-quarter pie (for the sum of the
- +                                      // above values: 100+200+300 = 600)
- +          cd.cValues = 3;             // array count
- +          cd.padValues = &adData[0];
- +          cd.palColors = &alColors[0];
- +          cd.papszDescriptions = &apszDescriptions[0];
- +          WinSendMsg(hwndChart, CHTM_SETCHARTDATA, &cd, NULL);
+ *      See cctl_chart.c for an overview and usage instructions.
  *
  *@@added V0.9.0 [umoeller]
Index: trunk/src/helpers/cctl_tooltip.c
===================================================================
--- trunk/src/helpers/cctl_tooltip.c	(revision 67)
+++ trunk/src/helpers/cctl_tooltip.c	(revision 68)
@@ -164,8 +164,10 @@
  *
  *@@added V0.9.12 (2001-04-28) [umoeller]
+ *@@changed V0.9.12 (2001-05-03) [umoeller]: this did nothing... fixed
  */
 
 VOID UnlockSubclassedTools(VOID)
 {
+    DosReleaseMutexSem(G_hmtxSubclassedTools);      // was missing V0.9.12 (2001-05-03) [umoeller]
 }
 
Index: trunk/src/helpers/debug.c
===================================================================
--- trunk/src/helpers/debug.c	(revision 67)
+++ trunk/src/helpers/debug.c	(revision 68)
@@ -366,7 +366,37 @@
 
 /*
+ *@@ WriteAddressInfo:
+ *      formats and writes a line into the trap log
+ *      file.
+ *
+ *      This gets called for each line from the
+ *      stack dump. At this point, the line in the
+ *      trap log already has:
+ *
+ +          CS:EIP  : 000109FF  XMLVIEW :0
+ +                                          ^^^ and we write here
+ *      After this call, we have.
+ *
+ +          CS:EIP  : 000109FF  XMLVIEW :0  xxx.c  123 ConfirmCreate__Fv
+ +                                          ^^^ and we write here
+ *
+ *@@added V0.9.12 (2001-05-12) [umoeller]
+ */
+
+VOID WriteDebugInfo(FILE *LogFile,              // in: open log file
+                    PXDEBUGINFO pxdi)           // in: debug info
+{
+    fprintf(LogFile,
+            "%s%s%s",
+            pxdi->szNrFile,
+            pxdi->szNrLine,
+            pxdi->szNrPub);
+}
+
+/*
  *@@ dbgPrintDebugInfo:
  *      this is the main entry point into analyzing debug
  *      code.
+ *
  *      This analyzes a given address and tries to find
  *      debug code descriptions for this address. If found,
@@ -383,6 +413,6 @@
 APIRET dbgPrintDebugInfo(FILE *LogFile,         // out: log file to write to
                          CHAR *FileName,        // in: EXE/DLL module file name
-                         ULONG Object,          // in: trapping object
-                         ULONG TrapOffset)      // in: trapping address
+                         ULONG Object,          // in: trapping object (from DosQueryModFromEIP)
+                         ULONG TrapOffset)      // in: trapping address (from DosQueryModFromEIP)
 {
     APIRET                  rc = 0;
@@ -391,4 +421,6 @@
     static struct new_exe   NewExeHeader;
 
+    ULONG                   ulSegment = Object + 1;     // segment no. is object no. + 1
+
     XDEBUGINFO              xdi;
     memset(&xdi, 0, sizeof(xdi));
@@ -430,16 +462,12 @@
 
             // do analysis for 32-bit code
-            rc = Read32PmDebug(LogFile,
-                               &xdi,
-                               ModuleFile,
-                               Object + 1,
-                               TrapOffset,
-                               FileName);
-            if (rc == 0)
-            {
-                fprintf(LogFile, "%s", xdi.szNrFile);
-                fprintf(LogFile, "%s", xdi.szNrLine);
-                fprintf(LogFile, "%s", xdi.szNrPub);
-            }                   // endif
+            if (!(rc = Read32PmDebug(LogFile,
+                                     &xdi,                // output
+                                     ModuleFile,
+                                     ulSegment,
+                                     TrapOffset,
+                                     FileName)))
+                WriteDebugInfo(LogFile, &xdi);
+
             close(ModuleFile);
 
@@ -451,14 +479,15 @@
                 if (ModuleFile != -1)
                 {
-                    rc = Read32PmDebug(LogFile, &xdi, ModuleFile, Object + 1, TrapOffset, FileName);
-                    if (rc == 0)
-                    {
-                        fprintf(LogFile, "%s", xdi.szNrFile);
-                        fprintf(LogFile, "%s", xdi.szNrLine);
-                        fprintf(LogFile, "%s", xdi.szNrPub);
-                    }           // endif
+                    if (!(rc = Read32PmDebug(LogFile,
+                                             &xdi,
+                                             ModuleFile,
+                                             ulSegment,
+                                             TrapOffset,
+                                             FileName)))
+                        WriteDebugInfo(LogFile, &xdi);
+
                     close(ModuleFile);
                 }
-            }                   // endif
+            }
 
             return rc;
@@ -473,5 +502,7 @@
                  */
 
-                if ((xdi.pseg = (struct new_seg *)calloc(NE_CSEG(NewExeHeader), sizeof(struct new_seg))) == NULL)
+                if ((xdi.pseg = (struct new_seg *)calloc(NE_CSEG(NewExeHeader),
+                                                         sizeof(struct new_seg)))
+                            == NULL)
                 {
                     fprintf(LogFile, "Out of memory!");
@@ -479,5 +510,7 @@
                     return -1;
                 }
-                if (lseek(ModuleFile, E_LFANEW(OldExeHeader) + NE_SEGTAB(NewExeHeader), SEEK_SET) == -1L)
+                if (  lseek(ModuleFile,
+                            E_LFANEW(OldExeHeader) + NE_SEGTAB(NewExeHeader),
+                            SEEK_SET) == -1L)
                 {
                     fprintf(LogFile, "Error %u seeking segment table in %s\n", errno, FileName);
@@ -487,5 +520,8 @@
                 }
 
-                if (read(ModuleFile, (void *)xdi.pseg, NE_CSEG(NewExeHeader) * sizeof(struct new_seg)) == -1)
+                if (read(ModuleFile,
+                         (void *)xdi.pseg,
+                         NE_CSEG(NewExeHeader) * sizeof(struct new_seg))
+                      == -1)
                 {
                     fprintf(LogFile, "Error %u reading segment table from %s\n", errno, FileName);
@@ -494,11 +530,13 @@
                     return 10;
                 }
-                rc = Read16CodeView(LogFile, &xdi, ModuleFile, Object + 1, TrapOffset, FileName);
-                if (rc == 0)
-                {
-                    fprintf(LogFile, "%s", xdi.szNrFile);
-                    fprintf(LogFile, "%s", xdi.szNrLine);
-                    fprintf(LogFile, "%s", xdi.szNrPub);
-                }               // endif
+
+                if (!(rc = Read16CodeView(LogFile,
+                                          &xdi,
+                                          ModuleFile,
+                                          ulSegment,
+                                          TrapOffset,
+                                          FileName)))
+                    WriteDebugInfo(LogFile, &xdi);
+
                 free(xdi.pseg);
                 close(ModuleFile);
@@ -508,19 +546,20 @@
                 {
                     strcpy(FileName + strlen(FileName) - 3, "DBG");     // Build DBG File name
-                    ModuleFile = sopen(FileName, O_RDONLY | O_BINARY, SH_DENYNO);
+                    ModuleFile = sopen(FileName,
+                                       O_RDONLY | O_BINARY, SH_DENYNO);
                     if (ModuleFile != -1)
                     {
-                        rc = Read16CodeView(LogFile, &xdi, ModuleFile, Object + 1, TrapOffset, FileName);
-                        if (rc == 0)
-                        {
-                            fprintf(LogFile, "%s", xdi.szNrFile);
-                            fprintf(LogFile, "%s", xdi.szNrLine);
-                            fprintf(LogFile, "%s", xdi.szNrPub);
-                        }       // endif
+                        if (!(rc = Read16CodeView(LogFile,
+                                                  &xdi,
+                                                  ModuleFile,
+                                                  ulSegment,
+                                                  TrapOffset,
+                                                  FileName)))
+                            WriteDebugInfo(LogFile, &xdi);
+
                         close(ModuleFile);
                     }
-                }               // endif
+                }
                 return rc;
-
             }
             else
@@ -608,5 +647,7 @@
     }
 
-    if (read(ModuleFile, (void *)&G_eodbug, 8) == -1)
+    if (read(ModuleFile,
+             (void *)&G_eodbug, 8)
+            == -1)
     {
         fprintf(LogFile, "Error %u reading debug info from %s\n", errno, FileName);
@@ -619,5 +660,8 @@
     }
 
-    if ((pxdi->lfaBase = lseek(ModuleFile, -(LONG)G_eodbug.dfaBase, SEEK_END)) == -1L)
+    if (    (pxdi->lfaBase = lseek(ModuleFile,
+                                   -(LONG)G_eodbug.dfaBase,
+                                   SEEK_END))
+         == -1L)
     {
         fprintf(LogFile, "Error %u seeking base codeview data in %s\n", errno, FileName);
@@ -625,5 +669,7 @@
     }
 
-    if (read(ModuleFile, (void *)&pxdi->base, 8) == -1)
+    if (read(ModuleFile,
+             (void *)&pxdi->base, 8)
+        == -1)
     {
         fprintf(LogFile, "Error %u reading base codeview data in %s\n", errno, FileName);
@@ -631,5 +677,8 @@
     }
 
-    if (lseek(ModuleFile, pxdi->base.lfoDir - 8 + 4, SEEK_CUR) == -1)
+    if (lseek(ModuleFile,
+              pxdi->base.lfoDir - 8 + 4,
+              SEEK_CUR)
+        == -1)
     {
         fprintf(LogFile, "Error %u seeking dir codeview data in %s\n", errno, FileName);
@@ -637,5 +686,7 @@
     }
 
-    if (read(ModuleFile, (void *)&numdir, 4) == -1)
+    if (read(ModuleFile,
+             (void *)&numdir, 4)
+        == -1)
     {
         fprintf(LogFile, "Error %u reading dir codeview data in %s\n", errno, FileName);
@@ -644,7 +695,7 @@
 
     // Read dir table into buffer
-    if ((pxdi->pDirTab32 = (SSDIR32*)calloc(numdir,
-                                             sizeof(SSDIR32))
-                     ) == NULL)
+    if (    (pxdi->pDirTab32 = (SSDIR32*)calloc(numdir,
+                                             sizeof(SSDIR32)))
+        == NULL)
     {
         fprintf(LogFile, "Out of memory!");
@@ -652,5 +703,8 @@
     }
 
-    if (read(ModuleFile, (void *)pxdi->pDirTab32, numdir * sizeof(SSDIR32)) == -1)
+    if (read(ModuleFile,
+             (void*)pxdi->pDirTab32,
+             numdir * sizeof(SSDIR32))
+        == -1)
     {
         fprintf(LogFile, "Error %u reading codeview dir table from %s\n", errno, FileName);
@@ -667,4 +721,5 @@
             continue;
         }
+
         NrPublic = 0x0;
         NrSymbol = 0;
@@ -673,7 +728,13 @@
         CurrSymSeg = 0;
         // point to subsection
-        lseek(ModuleFile, pxdi->pDirTab32[i].lfoStart + pxdi->lfaBase, SEEK_SET);
-        read(ModuleFile, (void*)&(pxdi->ssmod32.csBase), sizeof(SSMOD32));
-        read(ModuleFile, (void*)ModName, (unsigned)pxdi->ssmod32.csize);
+        lseek(ModuleFile,
+              pxdi->pDirTab32[i].lfoStart + pxdi->lfaBase,
+              SEEK_SET);
+        read(ModuleFile,
+             (void*)&pxdi->ssmod32.csBase,
+             sizeof(SSMOD32));
+        read(ModuleFile,
+             (void*)ModName,
+             (unsigned)pxdi->ssmod32.csize);
         ModIndex = pxdi->pDirTab32[i].modindex;
         ModName[pxdi->ssmod32.csize] = '\0';
@@ -687,5 +748,8 @@
         {
             // point to subsection
-            lseek(ModuleFile, pxdi->pDirTab32[i].lfoStart + pxdi->lfaBase, SEEK_SET);
+            lseek(ModuleFile,
+                  pxdi->pDirTab32[i].lfoStart + pxdi->lfaBase,
+                  SEEK_SET);
+
             switch (pxdi->pDirTab32[i].sst)
             {
@@ -694,6 +758,10 @@
                     while (bytesread < pxdi->pDirTab32[i].cb)
                     {
-                        bytesread += read(ModuleFile, (void *)&pxdi->sspub32.offset, sizeof(pxdi->sspub32));
-                        bytesread += read(ModuleFile, (void *)ename, (unsigned)pxdi->sspub32.csize);
+                        bytesread += read(ModuleFile,
+                                          (void *)&pxdi->sspub32.offset,
+                                          sizeof(pxdi->sspub32));
+                        bytesread += read(ModuleFile,
+                                          (void*)ename,
+                                          (unsigned)pxdi->sspub32.csize);
                         ename[pxdi->sspub32.csize] = '\0';
                         if (    (pxdi->sspub32.segment == TrapSeg)
@@ -705,10 +773,10 @@
                             read_types = TRUE;
                             sprintf(pxdi->szNrPub,
-                                    "%s %s (%s, seg %04lX : ofs %08lX\n",
-                                    (pxdi->sspub32.type == 1) ? " Abs" : " ",
+                                    "%s %s (%s)\n",
+                                    (pxdi->sspub32.type == 1)
+                                            ? " Abs"
+                                            : " ",
                                     ename,
-                                    ModName, // ()
-                                    (ULONG)pxdi->sspub32.segment,
-                                    pxdi->sspub32.offset
+                                    ModName
                                 );
                             // but continue, because there might be a
Index: trunk/src/helpers/except.c
===================================================================
--- trunk/src/helpers/except.c	(revision 67)
+++ trunk/src/helpers/except.c	(revision 68)
@@ -313,4 +313,5 @@
  *
  *@@added V0.9.2 (2000-03-10) [umoeller]
+ *@@changed V0.9.12 (2001-05-12) [umoeller]: added seg:ofs to output always
  */
 
@@ -338,7 +339,6 @@
         // error:
         fprintf(file,
-                " %-8s:%lu   Error: DosQueryModFromEIP returned %lu\n",
+                " %-8s Error: DosQueryModFromEIP returned %lu\n",
                 szMod1,
-                ulObject,
                 arc);
     }
@@ -348,10 +348,10 @@
 
         fprintf(file,
-                " %-8s:%lu   ",
+                " %-8s %02lX:%08lX\n                                 ",
                 szMod1,
-                ulObject);
+                ulObject + 1,       // V0.9.12 (2001-05-12) [umoeller]
+                ulOffset);          // V0.9.12 (2001-05-12) [umoeller]
 
         DosQueryModuleName(hmod1, sizeof(szFullName), szFullName);
-
         dbgPrintStackFrame(file,
                            szFullName,
@@ -381,5 +381,5 @@
     PULONG pulStackWord = 0;
 
-    fprintf(file, "\n\nStack frames:\n              Address   Module:Object\n");
+    fprintf(file, "\n\nStack frames:\n              Address   Module   seg:ofs\n");
 
     // first the trapping address itself
Index: trunk/src/helpers/helpers_post.in
===================================================================
--- trunk/src/helpers/helpers_post.in	(revision 67)
+++ trunk/src/helpers/helpers_post.in	(revision 68)
@@ -196,10 +196,9 @@
 $(OUTPUTDIR)\xprf.obj:  $(@B).c $(HLPINC)\$(@B).h \
                $(PROJECTINC)\setup.h \
-               $(HLPINC)\linklist.h
+               $(HLPINC)\linklist.h $(HLPINC)\prfh.h
 
 $(OUTPUTDIR)\xprf2.obj:  $(@B).c $(HLPINC)\xprf.h \
                $(PROJECTINC)\setup.h \
-               $(INC)\expat\expat.h \
-               $(HLPINC)\linklist.h $(HLPINC)\stringh.h $(HLPINC)\xstring.h
+               $(HLPINC)\dosh.h $(HLPINC)\prfh.h $(HLPINC)\stringh.h
 
 $(OUTPUTDIR)\xstring.obj:  $(@B).c $(HLPINC)\$(@B).h \
Index: trunk/src/helpers/prfh.c
===================================================================
--- trunk/src/helpers/prfh.c	(revision 67)
+++ trunk/src/helpers/prfh.c	(revision 68)
@@ -70,4 +70,5 @@
  *
  *      <B>Example</B> for iterating over a keys list:
+ *
  +          PSZ pszKeysList = prfhQueryKeysForApp(...);
  +          if (pszKeysList)
@@ -85,23 +86,38 @@
  *      You can also use this function to query the applications
  *      list for hIni, if you specifiy pszApp as NULL.
- */
-
-PSZ prfhQueryKeysForApp(HINI hIni,      // in: INI handle (can be HINI_USER or HINI_SYSTEM)
-                        const char *pcszApp)     // in: application to query list for (or NULL for applications list)
-{
+ *
+ *@@changed V0.9.12 (2001-05-12) [umoeller]: changed prototypes to return APIRET now
+ */
+
+APIRET prfhQueryKeysForApp(HINI hIni,      // in: INI handle (can be HINI_USER or HINI_SYSTEM)
+                           const char *pcszApp, // in: application to query list for (or NULL for applications list)
+                           PSZ *ppszKeys)   // out: keys list (newly allocated)
+{
+    APIRET  arc = NO_ERROR;
     PSZ     pKeys = NULL;
     ULONG   ulSizeOfKeysList = 0;
 
     // get size of keys list for pszApp
-    if (PrfQueryProfileSize(hIni, (PSZ)pcszApp, NULL, &ulSizeOfKeysList))
+    if (    (!PrfQueryProfileSize(hIni, (PSZ)pcszApp, NULL, &ulSizeOfKeysList))
+         || (ulSizeOfKeysList == 0)
+       )
+        arc = PRFERR_KEYSLIST;
+    else
     {
         pKeys = (PSZ)malloc(ulSizeOfKeysList);
-        if (!PrfQueryProfileData(hIni, (PSZ)pcszApp, NULL, pKeys, &ulSizeOfKeysList))
-        {
+        if (!pKeys)
+            arc = ERROR_NOT_ENOUGH_MEMORY;
+        else
+            if (!PrfQueryProfileData(hIni, (PSZ)pcszApp, NULL, pKeys, &ulSizeOfKeysList))
+                arc = PRFERR_KEYSLIST;
+    }
+
+    if (!arc)       // V0.9.12 (2001-05-12) [umoeller]
+        *ppszKeys = pKeys;
+    else
+        if (pKeys)
             free(pKeys);
-            pKeys = NULL;
-        }
-    }
-    return (pKeys);
+
+    return (arc);
 }
 
@@ -295,7 +311,11 @@
  *      Returns:
  *      --  0: no error
+ *
  *      --  PRFERR_DATASIZE: couldn't query data size for key
+ *
  *      --  PRFERR_MEMORY: couldn't allocate memory
+ *
  *      --  PRFERR_READ: couldn't read data from source (PrfQueryProfileData error)
+ *
  *      --  PRFERR_WRITE: couldn't write data to target (PrfWriteProfileData error)
  *
@@ -303,9 +323,9 @@
  */
 
-ULONG prfhCopyKey(HINI hiniSource,       // in: source profile (can be HINI_USER or HINI_SYSTEM)
-                  const char *pcszSourceApp,      // in: source application
-                  const char *pcszKey,            // in: source/target key
-                  HINI hiniTarget,       // in: target profile (can be HINI_USER or HINI_SYSTEM)
-                  const char *pcszTargetApp)      // in: target app
+APIRET prfhCopyKey(HINI hiniSource,       // in: source profile (can be HINI_USER or HINI_SYSTEM)
+                   const char *pcszSourceApp,      // in: source application
+                   const char *pcszKey,            // in: source/target key
+                   HINI hiniTarget,       // in: target profile (can be HINI_USER or HINI_SYSTEM)
+                   const char *pcszTargetApp)      // in: target app
 {
     ULONG   ulSizeOfData = 0,
@@ -365,7 +385,9 @@
  *
  *      You can use this function in several contexts:
+ *
  *      -- copy one application within the same profile
  *         (i.e. hiniSource == hiniTarget);
  *         in this case, pszSourceApp must be != pszTargetApp;
+ *
  *      -- copy an application from one profile to another
  *         (i.e. hiniSource != hiniTarget);
@@ -375,5 +397,7 @@
  *      WARNING: This does _not_ check for whether the target
  *      application exists already. This has two consequences:
+ *
  *      --  existing data will be overwritten without warning;
+ *
  *      --  if the existing target application has keys that are
  *          not in the source application, they are not deleted.
@@ -383,4 +407,5 @@
  *      So you should delete the target application before
  *      calling this function, like this:
+ *
  +          PrfWriteProfileString(hiniTarget, pszTargetApp, NULL, NULL);
  *
@@ -407,18 +432,19 @@
  */
 
-ULONG prfhCopyApp(HINI hiniSource,   // in: source profile (can be HINI_USER or HINI_SYSTEM)
-                  const char *pcszSourceApp,  // in: source application
-                  HINI hiniTarget,   // in: target profile (can be HINI_USER or HINI_SYSTEM)
-                  const char *pcszTargetApp,  // in: name of pszSourceApp in hiniTarget
-                  PSZ pszErrorKey)   // out: failing key in case of error; ptr can be NULL
-{
-    ULONG   ulrc;
-    PSZ pszKeysList;
+APIRET prfhCopyApp(HINI hiniSource,   // in: source profile (can be HINI_USER or HINI_SYSTEM)
+                   const char *pcszSourceApp,  // in: source application
+                   HINI hiniTarget,   // in: target profile (can be HINI_USER or HINI_SYSTEM)
+                   const char *pcszTargetApp,  // in: name of pszSourceApp in hiniTarget
+                   PSZ pszErrorKey)   // out: failing key in case of error; ptr can be NULL
+{
+    APIRET  arc = NO_ERROR;
+    PSZ     pszKeysList = NULL;
 
     if (pszErrorKey)
         *pszErrorKey = 0;
 
-    pszKeysList = prfhQueryKeysForApp(hiniSource, (PSZ)pcszSourceApp);
-    if (pszKeysList)
+    if (!(arc = prfhQueryKeysForApp(hiniSource,
+                                    (PSZ)pcszSourceApp,
+                                    &pszKeysList)))
     {
         PSZ pKey2 = pszKeysList;
@@ -427,10 +453,10 @@
         {
             // copy this key
-            ulrc = prfhCopyKey(hiniSource,
-                               pcszSourceApp,
-                               pKey2,
-                               hiniTarget,
-                               pcszTargetApp);
-            if (ulrc)
+            arc = prfhCopyKey(hiniSource,
+                              pcszSourceApp,
+                              pKey2,
+                              hiniTarget,
+                              pcszTargetApp);
+            if (arc)
             {
                 // error: copy failing key to buffer
@@ -444,8 +470,6 @@
         free (pszKeysList);
     }
-    else
-        ulrc = PRFERR_KEYSLIST;
-
-    return (ulrc);
+
+    return (arc);
 }
 
Index: trunk/src/helpers/syssound.c
===================================================================
--- trunk/src/helpers/syssound.c	(revision 67)
+++ trunk/src/helpers/syssound.c	(revision 68)
@@ -467,7 +467,8 @@
     {
         // get applications list for sounds list in MMPM.INI
-        PSZ pszKeysList = prfhQueryKeysForApp(hiniMMPM,
-                                              MMINIKEY_SYSSOUNDS); // "MMPM2_AlarmSounds"
-        if (pszKeysList)
+        PSZ pszKeysList = NULL;
+        if (!(arc = prfhQueryKeysForApp(hiniMMPM,
+                                        MMINIKEY_SYSSOUNDS, // "MMPM2_AlarmSounds"
+                                        &pszKeysList)))
         {
             PSZ     pKey2 = pszKeysList;
@@ -594,7 +595,8 @@
 
             // get applications list for sounds list in MMPM.INI
-            PSZ pszMMPMKeysList = prfhQueryKeysForApp(hiniMMPM,
-                                                  MMINIKEY_SYSSOUNDS); // "MMPM2_AlarmSounds"
-            if (pszMMPMKeysList)
+            PSZ pszMMPMKeysList = NULL;
+            if (!(arc = prfhQueryKeysForApp(hiniMMPM,
+                                            MMINIKEY_SYSSOUNDS,// "MMPM2_AlarmSounds"
+                                            &pszMMPMKeysList)))
             {
                 PSZ     pMMPMKey2 = pszMMPMKeysList,
Index: trunk/src/helpers/timer.c
===================================================================
--- trunk/src/helpers/timer.c	(revision 67)
+++ trunk/src/helpers/timer.c	(revision 68)
@@ -144,86 +144,5 @@
  ********************************************************************/
 
-// timers thread
-// HMTX                G_hmtxTimers = NULLHANDLE;  // timers lock mutex
-// THREADINFO          G_tiTimers = {0};           // timers thread (only running
-                                                // if any timers were requested)
-// BOOL                G_fTimersThreadRunning = FALSE;
-// LINKLIST            G_llTimers;         // linked list of XTIMER pointers
-
-/*
- *@@ fntTimersThread:
- *      the actual thread which fires the timers by
- *      posting WM_TIMER messages to the respecive
- *      target windows when a timer has elapsed.
- *
- *      This thread is dynamically started when the
- *      first timer is started thru tmrStartXTimer.
- *      It is automatically stopped (to be precise:
- *      it terminates itself) when the last timer
- *      is stopped thru tmrStopXTimer, which then
- *      sets the thread's fExit flag to TRUE.
- *
- *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
- */
-
-/* void _Optlink fntTimersThread(PTHREADINFO ptiMyself)
-{
-    ULONG       ulInterval = 25;
-    HAB         hab = WinInitialize(0);
-    BOOL        fLocked = FALSE;
-
-    // linked list of timers found to be invalid;
-    // this holds LISTNODE pointers from the global
-    // list to be removed
-    LINKLIST    llInvalidTimers;
-    lstInit(&llInvalidTimers,
-            FALSE);     // no auto-free
-
-    #ifdef __DEBUG__
-        DosBeep(3000, 30);
-    #endif
-
-    // keep running while we have timers
-    while (!ptiMyself->fExit)
-    {
-        // ULONG ulNesting = 0;
-
-        ULONG ulTimeNow;
-
-        DosSleep(ulInterval);
-
-        // minimum interval: 100 ms; this is lowered
-        // if we find any timers in the list which
-        // have a lower timeout to make sure we can
-        // fire at a lower interval...
-        ulInterval = 100;
-
-        // DosEnterMustComplete(&ulNesting);
-
-        TRY_LOUD(excpt1)
-        {
-            fLocked = LockTimers();
-            if (fLocked)
-            {
-            } // end if (fLocked)
-        }
-        CATCH(excpt1) { } END_CATCH();
-
-        if (fLocked)
-        {
-            UnlockTimers();
-            fLocked = FALSE;
-        }
-
-        // DosExitMustComplete(&ulNesting);
-
-    } // end while (!ptiMyself->fExit)
-
-    WinTerminate(hab);
-
-    #ifdef __DEBUG__
-        DosBeep(1500, 30);
-    #endif
-} */
+HMTX                G_hmtxTimers = NULLHANDLE;  // timers lock mutex
 
 /* ******************************************************************
@@ -232,4 +151,32 @@
  *
  ********************************************************************/
+
+/*
+ *@@ LockTimers:
+ *
+ *@@added V0.9.12 (2001-05-12) [umoeller]
+ */
+
+BOOL LockTimers(VOID)
+{
+    if (!G_hmtxTimers)
+        return (!DosCreateMutexSem(NULL,
+                                   &G_hmtxTimers,
+                                   0,
+                                   TRUE));      // request!
+    else
+        return (!WinRequestMutexSem(G_hmtxTimers, SEM_INDEFINITE_WAIT));
+}
+
+/*
+ *@@ UnlockTimers:
+ *
+ *@@added V0.9.12 (2001-05-12) [umoeller]
+ */
+
+VOID UnlockTimers(VOID)
+{
+    DosReleaseMutexSem(G_hmtxTimers);
+}
 
 /*
@@ -239,5 +186,5 @@
  *      _and_ timer ID.
  *
- *      Internal function.
+ *      Internal function. Caller must hold the mutex.
  */
 
@@ -272,5 +219,5 @@
  *      the global linked list of running timers.
  *
- *      Internal function.
+ *      Internal function. Caller must hold the mutex.
  */
 
@@ -339,4 +286,5 @@
  *
  *@@added V0.9.9 (2001-02-28) [umoeller]
+ *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
  */
 
@@ -347,15 +295,20 @@
         if (pSet->pvllXTimers)
         {
-            PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
-
-            PLISTNODE pTimerNode;
-
-            while (pTimerNode = lstQueryFirstNode(pllXTimers))
+            if (LockTimers())
             {
-                PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
-                RemoveTimer(pSet, pTimer);
+                PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
+
+                PLISTNODE pTimerNode;
+
+                while (pTimerNode = lstQueryFirstNode(pllXTimers))
+                {
+                    PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
+                    RemoveTimer(pSet, pTimer);
+                }
+
+                lstFree(pllXTimers);
+
+                UnlockTimers();
             }
-
-            lstFree(pllXTimers);
         }
 
@@ -373,4 +326,6 @@
  *      goes thru all XTimers in the sets and starts
  *      or stops the PM timer with a decent frequency.
+ *
+ *      Internal function. Caller must hold the mutex.
  *
  *@@added V0.9.9 (2001-03-07) [umoeller]
@@ -435,4 +390,5 @@
  *
  *@@added V0.9.9 (2001-02-28) [umoeller]
+ *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
  */
 
@@ -441,96 +397,101 @@
     if (pSet && pSet->pvllXTimers)
     {
-        PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
-        // go thru all XTimers and see which one
-        // has elapsed; for all of these, post WM_TIMER
-        // to the target window proc
-        PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
-
-        if (!pTimerNode)
+        if (LockTimers())
         {
-            // no timers left:
-            if (pSet->idPMTimerRunning)
+            PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
+            // go thru all XTimers and see which one
+            // has elapsed; for all of these, post WM_TIMER
+            // to the target window proc
+            PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
+
+            if (!pTimerNode)
             {
-                // but PM timer running:
-                // stop it
-                WinStopTimer(pSet->hab,
-                             pSet->hwndOwner,
-                             pSet->idPMTimer);
-                pSet->idPMTimerRunning = 0;
+                // no timers left:
+                if (pSet->idPMTimerRunning)
+                {
+                    // but PM timer running:
+                    // stop it
+                    WinStopTimer(pSet->hab,
+                                 pSet->hwndOwner,
+                                 pSet->idPMTimer);
+                    pSet->idPMTimerRunning = 0;
+                }
+
+                pSet->ulPMTimeout = 0;
             }
-
-            pSet->ulPMTimeout = 0;
-        }
-        else
-        {
-            // we have timers:
-            BOOL    fFoundInvalid = FALSE;
-
-            ULONG   // ulInterval = 100,
-                    ulTimeNow = 0;
-
-            // linked list of timers found to be invalid;
-            // this holds LISTNODE pointers from the global
-            // list to be removed
-            LINKLIST    llInvalidTimers;
-            lstInit(&llInvalidTimers,
-                    FALSE);     // no auto-free
-
-            // get current time
-            DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
-                            &ulTimeNow, sizeof(ulTimeNow));
-
-            while (pTimerNode)
+            else
             {
-                PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
-
-                if (pTimer->ulNextFire < ulTimeNow)
+                // we have timers:
+                BOOL    fFoundInvalid = FALSE;
+
+                ULONG   // ulInterval = 100,
+                        ulTimeNow = 0;
+
+                // linked list of timers found to be invalid;
+                // this holds LISTNODE pointers from the global
+                // list to be removed
+                LINKLIST    llInvalidTimers;
+                lstInit(&llInvalidTimers,
+                        FALSE);     // no auto-free
+
+                // get current time
+                DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
+                                &ulTimeNow, sizeof(ulTimeNow));
+
+                while (pTimerNode)
                 {
-                    // this timer has elapsed:
-                    // fire!
-                    if (WinIsWindow(pSet->hab,
-                                    pTimer->hwndTarget))
+                    PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
+
+                    if (pTimer->ulNextFire < ulTimeNow)
                     {
-                        // window still valid:
-                        // get the window's window proc
-                        PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
-                                                               QWP_PFNWP);
-                        // call the window proc DIRECTLY
-                        pfnwp(pTimer->hwndTarget,
-                              WM_TIMER,
-                              (MPARAM)pTimer->usTimerID,
-                              0);
-                        pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
+                        // this timer has elapsed:
+                        // fire!
+                        if (WinIsWindow(pSet->hab,
+                                        pTimer->hwndTarget))
+                        {
+                            // window still valid:
+                            // get the window's window proc
+                            PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
+                                                                   QWP_PFNWP);
+                            // call the window proc DIRECTLY
+                            pfnwp(pTimer->hwndTarget,
+                                  WM_TIMER,
+                                  (MPARAM)pTimer->usTimerID,
+                                  0);
+                            pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
+                        }
+                        else
+                        {
+                            // window has been destroyed:
+                            lstAppendItem(&llInvalidTimers,
+                                          (PVOID)pTimerNode);
+                            fFoundInvalid = TRUE;
+                        }
+                    } // end if (pTimer->ulNextFire < ulTimeNow)
+
+                    // next timer
+                    pTimerNode = pTimerNode->pNext;
+                } // end while (pTimerNode)
+
+                // destroy invalid timers, if any
+                if (fFoundInvalid)
+                {
+                    PLISTNODE pNodeNode = lstQueryFirstNode(&llInvalidTimers);
+                    while (pNodeNode)
+                    {
+                        PLISTNODE pNode2Remove = (PLISTNODE)pNodeNode->pItemData;
+                        lstRemoveNode(pllXTimers,
+                                      pNode2Remove);
+                        pNodeNode = pNodeNode->pNext;
                     }
-                    else
-                    {
-                        // window has been destroyed:
-                        lstAppendItem(&llInvalidTimers,
-                                      (PVOID)pTimerNode);
-                        fFoundInvalid = TRUE;
-                    }
-                } // end if (pTimer->ulNextFire < ulTimeNow)
-
-                // next timer
-                pTimerNode = pTimerNode->pNext;
-            } // end while (pTimerNode)
-
-            // destroy invalid timers, if any
-            if (fFoundInvalid)
-            {
-                PLISTNODE pNodeNode = lstQueryFirstNode(&llInvalidTimers);
-                while (pNodeNode)
-                {
-                    PLISTNODE pNode2Remove = (PLISTNODE)pNodeNode->pItemData;
-                    lstRemoveNode(pllXTimers,
-                                  pNode2Remove);
-                    pNodeNode = pNodeNode->pNext;
+                    lstClear(&llInvalidTimers);
+
+                    AdjustPMTimer(pSet);
                 }
-                lstClear(&llInvalidTimers);
-
-                AdjustPMTimer(pSet);
-            }
-        } // end else if (!pTimerNode)
-    }
+            } // end else if (!pTimerNode)
+
+            UnlockTimers();
+        } // end if (LockTimers())
+    } // end if (pSet && pSet->pvllXTimers)
 }
 
@@ -552,4 +513,5 @@
  *
  *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
+ *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
  */
 
@@ -565,52 +527,55 @@
     if (pSet && pSet->pvllXTimers)
     {
-        PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
-
-        if ((hwnd) && (ulTimeout))
+        if (LockTimers())
         {
-            PXTIMER pTimer;
-
-            // check if this timer exists already
-            pTimer = FindTimer(pSet,
-                               hwnd,
-                               usTimerID);
-            if (pTimer)
+            PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
+
+            if ((hwnd) && (ulTimeout))
             {
-                // exists already: reset only
-                ULONG ulTimeNow;
-                DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
-                                &ulTimeNow, sizeof(ulTimeNow));
-                pTimer->ulNextFire = ulTimeNow + ulTimeout;
-                usrc = pTimer->usTimerID;
-            }
-            else
-            {
-                // new timer needed:
-                pTimer = (PXTIMER)malloc(sizeof(XTIMER));
+                PXTIMER pTimer;
+
+                // check if this timer exists already
+                pTimer = FindTimer(pSet,
+                                   hwnd,
+                                   usTimerID);
                 if (pTimer)
                 {
+                    // exists already: reset only
                     ULONG ulTimeNow;
                     DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
                                     &ulTimeNow, sizeof(ulTimeNow));
-                    pTimer->usTimerID = usTimerID;
-                    pTimer->hwndTarget = hwnd;
-                    pTimer->ulTimeout = ulTimeout;
                     pTimer->ulNextFire = ulTimeNow + ulTimeout;
-
-                    lstAppendItem(pllXTimers,
-                                  pTimer);
                     usrc = pTimer->usTimerID;
                 }
-            }
-
-            if (usrc)
-            {
-                // timer created or reset:
-                AdjustPMTimer(pSet);
-            }
-        } // if ((hwnd) && (ulTimeout))
-    }
-
-    // _Pmpf((__FUNCTION__ ": leaving, returning %d", usrc));
+                else
+                {
+                    // new timer needed:
+                    pTimer = (PXTIMER)malloc(sizeof(XTIMER));
+                    if (pTimer)
+                    {
+                        ULONG ulTimeNow;
+                        DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
+                                        &ulTimeNow, sizeof(ulTimeNow));
+                        pTimer->usTimerID = usTimerID;
+                        pTimer->hwndTarget = hwnd;
+                        pTimer->ulTimeout = ulTimeout;
+                        pTimer->ulNextFire = ulTimeNow + ulTimeout;
+
+                        lstAppendItem(pllXTimers,
+                                      pTimer);
+                        usrc = pTimer->usTimerID;
+                    }
+                }
+
+                if (usrc)
+                {
+                    // timer created or reset:
+                    AdjustPMTimer(pSet);
+                }
+            } // if ((hwnd) && (ulTimeout))
+
+            UnlockTimers();
+        }
+    }
 
     return (usrc);
@@ -625,4 +590,6 @@
  *
  *      Returns TRUE if the timer was stopped.
+ *
+ *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
  */
 
@@ -634,16 +601,21 @@
     if (pSet && pSet->pvllXTimers)
     {
-        PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
-        BOOL fLocked = FALSE;
-
-        PXTIMER pTimer = FindTimer(pSet,
-                                   hwnd,
-                                   usTimerID);
-        if (pTimer)
+        if (LockTimers())
         {
-            RemoveTimer(pSet, pTimer);
-            // recalculate
-            AdjustPMTimer(pSet);
-            brc = TRUE;
+            PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
+            BOOL fLocked = FALSE;
+
+            PXTIMER pTimer = FindTimer(pSet,
+                                       hwnd,
+                                       usTimerID);
+            if (pTimer)
+            {
+                RemoveTimer(pSet, pTimer);
+                // recalculate
+                AdjustPMTimer(pSet);
+                brc = TRUE;
+            }
+
+            UnlockTimers();
         }
     }
Index: trunk/src/helpers/xprf2.c
===================================================================
--- trunk/src/helpers/xprf2.c	(revision 67)
+++ trunk/src/helpers/xprf2.c	(revision 68)
@@ -153,11 +153,12 @@
 {
     APIRET arc = NO_ERROR;
-    PSZ pszKeysList;
+    PSZ pszKeysList = NULL;
 
     if (pszErrorKey)
         *pszErrorKey = 0;
 
-    pszKeysList = prfhQueryKeysForApp(hiniSource, pszSourceApp);
-    if (pszKeysList)
+    if (!(arc = prfhQueryKeysForApp(hiniSource,
+                                    pszSourceApp,
+                                    &pszKeysList)))
     {
         PSZ pKey2 = pszKeysList;
