
/*
 *@@sourcefile cctl_chart.c:
 *      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) == (sizeSlice / 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
 *            numbering.
 *
 *@@header "helpers\comctl.h"
 *@@added V0.9.3 (2000-05-21) [umoeller].
 */

/*
 *      Copyright (C) 1997-2002 Ulrich M”ller.
 *      This file is part of the "XWorkplace helpers" source package.
 *      This is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published
 *      by the Free Software Foundation, in version 2 as it comes in the
 *      "COPYING" file of the XWorkplace main distribution.
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 */

#define OS2EMX_PLAIN_CHAR
    // this is needed for "os2emx.h"; if this is defined,
    // emx will define PSZ as _signed_ char, otherwise
    // as unsigned char

#define INCL_DOSEXCEPTIONS
#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#define INCL_DOSERRORS

#define INCL_WINWINDOWMGR
#define INCL_WINFRAMEMGR
#define INCL_WINMESSAGEMGR
#define INCL_WININPUT
#define INCL_WINPOINTERS
#define INCL_WINTRACKRECT
#define INCL_WINTIMER
#define INCL_WINSYS

#define INCL_WINRECTANGLES      /// xxx temporary

#define INCL_WINMENUS
#define INCL_WINSTATICS
#define INCL_WINBUTTONS
#define INCL_WINSTDCNR

#define INCL_GPIPRIMITIVES
#define INCL_GPILOGCOLORTABLE
#define INCL_GPILCIDS
#define INCL_GPIPATHS
#define INCL_GPIREGIONS
#define INCL_GPIBITMAPS             // added V0.9.1 (2000-01-04) [umoeller]: needed for EMX headers
#include <os2.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <setjmp.h>             // needed for except.h
#include <assert.h>             // needed for except.h

#include "setup.h"                      // code generation and debugging options

#include "helpers\cnrh.h"
#include "helpers\except.h"             // exception handling
#include "helpers\gpih.h"
#include "helpers\linklist.h"
#include "helpers\winh.h"

#include "helpers\comctl.h"

#pragma hdrstop

/*
 *@@category: Helpers\PM helpers\Window classes\Chart control
 *      See cctl_chart.c.
 */

/* ******************************************************************
 *
 *   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]
 */

STATIC 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
 */

STATIC 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
 */

STATIC 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?!?)
        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);
}

/*
 *@@ ctlCreateChartBitmap:
 *      this creates a new bitmap and paints the
 *      chart into it. This bitmap will be used
 *      in WM_PAINT of ctl_fnwpChart to quickly paint
 *      the chart image.
 *
 *      The bitmap will be created with the specified
 *      size. The chart will consume all available
 *      space in the bitmap. The returned bitmap is
 *      not selected into any presentation space.
 *
 *      Note: Description text will be drawn with the
 *      current font specified for the memory PS, and
 *      with the current character cell box.
 *
 *      This function gets called automatically from
 *      WM_PAINT if we determine that the bitmap for
 *      painting has not been created yet or has been
 *      invalidated (because data or styles changed).
 *
 *      However, you can also use this function
 *      independently to have a chart bitmap created,
 *      wherever you might need it.
 *      You will then have to fill in the CHARTDATA
 *      and CHARTSTYLE structures before calling
 *      this function.
 *
 *      If (paRegions != NULL), this function will
 *      create a GPI region for each data item. Each GPI
 *      region will then contain the outline of the
 *      corresponding pie chart slice. This allows
 *      you to easily relate coordinates to pie slieces,
 *      for example for mouse clicks.
 *
 *      In that case, paRegions must point to an array
 *      of HRGN items, each of which will contain a
 *      region handle later. It is the responsibility
 *      of the caller to free the regions later, using
 *      GpiDestroyRegion on each region handle (with
 *      the hpsMem specified with this function).
 *
 *      If you're not interested in the regions, set
 *      paRegions to NULL.
 *
 *      This returns NULLHANDLE if an error occurred.
 *      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
 */

HBITMAP ctlCreateChartBitmap(HPS hpsMem,                // in: memory PS to use for bitmap creation
                             LONG lcx,                  // in: width of bitmap
                             LONG lcy,                  // in: height of bitmap
                             PCHARTDATA pChartData,     // in: chart data
                             PCHARTSTYLE pChartStyle,   // in: chart style
                             LONG lBackgroundColor,     // in: color around the chart (RGB)
                             LONG lTextColor,           // in: description text color (RGB)
                             HRGN* paRegions)           // out: GPI regions for each data item
{
    HBITMAP hbmReturn = NULLHANDLE;

    // sum up the values to get the total
    ULONG   ul = 0;
    double  dTotal = 0;
    double*  pdThis = pChartData->padValues;
    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
    if (dTotal > 0)
    {
        RECTL       rclWholeStatic;
        // get window rectangle (bottom left is 0, 0)
        rclWholeStatic.xLeft = 0;
        rclWholeStatic.yBottom = 0;
        rclWholeStatic.xRight = lcx;
        rclWholeStatic.yTop = lcy;

        // create bitmap of that size
        if ((hbmReturn = gpihCreateBitmap(hpsMem,
                                          rclWholeStatic.xRight,
                                          rclWholeStatic.yTop)))
        {
            // successfully created:

            // associate bitmap with memory PS
            GpiSetBitmap(hpsMem, hbmReturn);

            // switch the HPS to RGB mode
            gpihSwitchToRGB(hpsMem);

            // fill bitmap with static's background color
            GpiSetColor(hpsMem, lBackgroundColor);
            gpihBox(hpsMem,
                    DRO_FILL,
                    &rclWholeStatic);

            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
            GpiSetBitmap(hpsMem, NULLHANDLE);
        } // end if (pChtCData->hbmChart ...)
    } // end if (dTotal > 0)

    return hbmReturn;
}

/*
 *@@ CleanupBitmap:
 *      this frees the resources associated with
 *      the chart bitmap.
 *
 *@@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
 */

STATIC VOID CleanupBitmap(PCHARTCDATA pChtCData)
{
    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);
            // delete bitmap; fails if not freed!
            if (!GpiDeleteBitmap(pChtCData->hbmChart))
                _Pmpf((__FUNCTION__ ": GpiDeleteBitmap failed."));
            pChtCData->hbmChart = NULLHANDLE;
        }

        if (pChtCData->hpsMem)
        {
            GpiDestroyPS(pChtCData->hpsMem);
            pChtCData->hpsMem = NULLHANDLE;
        }
        if (pChtCData->hdcMem)
        {
            DevCloseDC(pChtCData->hdcMem);
            pChtCData->hdcMem = NULLHANDLE;
        }
    }
}

/*
 *@@ CleanupData:
 *      this frees all allocated resources of the chart
 *      control, except the bitmap (use CleanupBitmap
 *      for that) and the CHARTCDATA itself.
 *
 *      Note: CleanupBitmap must be called _before_ this
 *      function.
 */

STATIC VOID CleanupData(PCHARTCDATA pChtCData)
{
    if (pChtCData)
        if (pChtCData->cd.cValues)
        {
            // _Pmpf(("Cleaning up data"));
            // values array
            if (pChtCData->cd.padValues)
                free(pChtCData->cd.padValues);

            // colors array
            if (pChtCData->cd.palColors)
                free(pChtCData->cd.palColors);

            // strings array
            if (pChtCData->cd.papszDescriptions)
            {
                ULONG   ul;
                PSZ     *ppszDescriptionThis = pChtCData->cd.papszDescriptions;
                for (ul = 0;
                     ul < pChtCData->cd.cValues;
                     ul++)
                {
                    if (*ppszDescriptionThis)
                        free(*ppszDescriptionThis);

                    ppszDescriptionThis++;
                }

                free(pChtCData->cd.papszDescriptions);
            }

            pChtCData->cd.cValues = 0;

            // GPI regions array
            if (pChtCData->paRegions)
            {
                free(pChtCData->paRegions);
                pChtCData->paRegions = NULL;
            }
        }
}

/*
 *@@ SetChartData:
 *      implementation for CHTM_SETCHARTDATA
 *      in ctl_fnwpChart.
 *
 *@@added V0.9.2 (2000-02-29) [umoeller]
 *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed trap if ptr to descriptions was null
 */

STATIC VOID SetChartData(HWND hwndChart,
                         PCHARTCDATA pChtCData,
                         PCHARTDATA pcdNew)
{
    ULONG        ul = 0;
    PSZ          *ppszDescriptionSource,
                 *ppszDescriptionTarget;

    // free previous values, if set
    if (pChtCData->hbmChart)
        CleanupBitmap(pChtCData);

    CleanupData(pChtCData);

    pChtCData->cd.usStartAngle = pcdNew->usStartAngle;
    pChtCData->cd.usSweepAngle = pcdNew->usSweepAngle;
    pChtCData->cd.cValues = pcdNew->cValues;

    // copy values
    pChtCData->cd.padValues = (double*)malloc(sizeof(double) * pcdNew->cValues);
    memcpy(pChtCData->cd.padValues,
           pcdNew->padValues,
           sizeof(double) * pcdNew->cValues);

    // copy colors
    pChtCData->cd.palColors = (LONG*)malloc(sizeof(LONG) * pcdNew->cValues);
    memcpy(pChtCData->cd.palColors,
           pcdNew->palColors,
           sizeof(LONG) * pcdNew->cValues);

    // copy strings
    if (!pcdNew->papszDescriptions)
        pChtCData->cd.papszDescriptions = NULL;
    else
    {
        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++;
        }
    }

    // create an array of GPI region handles
    pChtCData->paRegions = (HRGN*)malloc(sizeof(HRGN) * pcdNew->cValues);
    // initialize all regions to null
    memset(pChtCData->paRegions, 0, sizeof(HRGN) * pcdNew->cValues);

    pChtCData->lSelected = -1;     // none selected
    pChtCData->lSourceEmphasis = -1;     // none selected

    WinInvalidateRect(hwndChart, NULL, FALSE);
}

/*
 *@@ PaintChart:
 *      implementation for WM_PAINT
 *      in ctl_fnwpChart.
 *
 *@@added V0.9.2 (2000-02-29) [umoeller]
 *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed major PM resource leaks
 */

STATIC VOID PaintChart(HWND hwndChart,
                       PCHARTCDATA pChtCData,
                       HPS hps,
                       PRECTL prclPaint)
{
    RECTL   rclStatic;
    WinQueryWindowRect(hwndChart, &rclStatic);

    // do we have any values yet?
    if (pChtCData->cd.cValues == 0)
    {
        CHAR szDebug[200];
        sprintf(szDebug, "Error, no values set");
        WinFillRect(hps,
                    &rclStatic,  // exclusive
                    CLR_WHITE);
        WinDrawText(hps,
                    strlen(szDebug),
                    szDebug,
                    &rclStatic,
                    CLR_BLACK,
                    CLR_WHITE,
                    DT_LEFT | DT_TOP);
    }
    else
    {
        // valid values, apparently:
        LONG lForegroundColor = winhQueryPresColor(hwndChart,
                                                   PP_FOREGROUNDCOLOR,
                                                   TRUE,       // inherit presparams
                                                   SYSCLR_WINDOWTEXT);

        gpihSwitchToRGB(hps);

        // check if the bitmap needs to be (re)created
        if (pChtCData->hbmChart == NULLHANDLE)
        {
            // no: do it now
            HPOINTER    hptrOld = winhSetWaitPointer();
            LONG        lLCIDSet = 0;

            if (pChtCData->hpsMem == NULLHANDLE)
            {
                // 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);
                }
            }

            pChtCData->hbmChart = ctlCreateChartBitmap(
                      pChtCData->hpsMem,        // mem PS
                      rclStatic.xRight,         // cx
                      rclStatic.yTop,           // cy
                      &pChtCData->cd,           // data
                      &pChtCData->cs,           // style
                      // background color:
                      winhQueryPresColor(hwndChart,
                                         PP_BACKGROUNDCOLOR,
                                         TRUE,       // inherit presparams
                                         SYSCLR_DIALOGBACKGROUND),
                      // description text color:
                      lForegroundColor,
                      pChtCData->paRegions);
                              // out: regions array

            // unset and delete font
            if (lLCIDSet)
            {
                GpiSetCharSet(pChtCData->hpsMem, LCID_DEFAULT);
                GpiDeleteSetId(pChtCData->hpsMem, lLCIDSet);
            }

            WinSetPointer(HWND_DESKTOP, hptrOld);
        }

        if (pChtCData->hbmChart)
        {
            // draw the chart bitmap
            POINTL  ptlDest = { 0, 0 };
            WinDrawBitmap(hps,
                          pChtCData->hbmChart,
                          NULL,     // draw whole bitmap
                          &ptlDest,
                          0, 0,     // colors (don't care)
                          DBM_NORMAL);

            // 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])
                    {
                        // 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);
                    }
                }
            }
        }
    }
}

/*
 *@@ 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]
 */

STATIC LONG FindItemFromPoint(PCHARTCDATA pChtCData,
                              LONG lx,
                              LONG ly)
{
    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]
 */

STATIC VOID SendWMControl(HWND hwndChart,
                          MPARAM mp1Mouse,
                          USHORT usNotify,
                          ULONG ulEmphasis,            // 0 or 1
                          LONG lIndex)
{
    EMPHASISNOTIFY en;
    en.hwndSource = hwndChart;
    en.lIndex = lIndex;        // can be -1
    en.ulEmphasis = ulEmphasis;
    en.ptl.x = SHORT1FROMMP(mp1Mouse);
    en.ptl.y = SHORT2FROMMP(mp1Mouse);

    ctlSendWmControl(hwndChart,
                     usNotify,
                     &en);
}

/*
 *@@ SetEmphasis:
 *
 *@@added V0.9.12 (2001-05-03) [umoeller]
 */

STATIC 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.
 *
 *      This is not a stand-alone window procedure, but must only
 *      be used with static controls subclassed by ctlChartFromStatic.
 *
 *@@added V0.9.0 [umoeller]
 *@@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
 *@@changed V0.9.20 (2002-07-17) [pr]: added CHTN_SETFOCUS and CHTN_KILLFOCUS support
 */

MRESULT EXPENTRY ctl_fnwpChart(HWND hwndChart, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT     mrc = 0;
    PCHARTCDATA   pChtCData = (PCHARTCDATA)WinQueryWindowULong(hwndChart, QWL_USER);

    if (pChtCData)
    {
        PFNWP OldStaticProc = pChtCData->OldStaticProc;

        switch (msg)
        {
            /*
             *@@ CHTM_SETCHARTDATA:
             *      user msg to set the pie chart data,
             *      which is copied to the control's
             *      memory.
             *      This can be sent several times to
             *      have the chart refreshed with new
             *      values.
             *
             *      Parameters:
             *      --  PCHARTDATA mp1: new values.
             *              These will be copied to the chart's internal data.
             *      --  mp2: unused.
             */

            case CHTM_SETCHARTDATA:
                // _Pmpf(("CHTM_SETCHARTDATA, mp1: 0x%lX", mp1));
                if (mp1)
                    SetChartData(hwndChart,
                                 pChtCData,
                                 (PCHARTDATA)mp1);
            break;

            /*
             *@@ CHTM_SETCHARTSTYLE:
             *      user msg to set the chart style,
             *      which is copied to the control's
             *      memory.
             *      This can be sent several times to
             *      have the chart refreshed with new
             *      styles.
             *
             *      Parameters:
             *      --  PCHARTSTYLE mp1: new style data.
             *              This will be copied to the chart's internal data.
             *      --  mp2: unused.
             */

            case CHTM_SETCHARTSTYLE:
                // _Pmpf(("CHTM_SETCHARTSTYLE, mp1: 0x%lX", mp1));
                memcpy(&(pChtCData->cs), mp1, sizeof(CHARTSTYLE));
                if (pChtCData->hbmChart)
                {
                    CleanupBitmap(pChtCData);
                    WinInvalidateRect(hwndChart, NULL, FALSE);
                }
            break;

            /*
             *@@ CHTM_ITEMFROMPOINT:
             *      this can be _sent_ to the chart control
             *      to query the chart item which surrounds
             *      the given point. Specify the coordinates
             *      in two SHORT's in mp1 (as with WM_BUTTON1*
             *      messages), in window coordinates.
             *
             *      Parameters:
             *      -- SHORT SHORT1FROMMP(mp1): x
             *      -- SHORT SHORT1FROMMP(mp1): y
             *      -- mp2: unused.
             *
             *      Returns:
             *      -- LONG: data item index (counting from
             *               0) or -1 if none, e.g. if no
             *               data has been set yet or if
             *               the point is outside the pie
             *               slices.
             *
             *@@added V0.9.2 (2000-02-29) [umoeller]
             */

            case CHTM_ITEMFROMPOINT:
            {
                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
                             )
                        )
                   )
                {
                    mrc = (MPARAM)SetEmphasis(hwndChart,
                                              pChtCData,
                                              lEmph,
                                              (MPARAM)-1,
                                              lIndex,
                                              // force flag:
                                              TRUE);
                }
            }
            break;

            /*
             * WM_BUTTON1CLICK:
             * WM_CONTEXTMENU: V0.9.12 (2001-05-03) [umoeller]
             */

            case WM_BUTTON1CLICK:
            case WM_BUTTON1DBLCLK:
            case WM_CONTEXTMENU:
                if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
                {
                    LONG lRegionFound = FindItemFromPoint(pChtCData,
                                                SHORT1FROMMP(mp1),
                                                SHORT2FROMMP(mp1));

                    // selections allowed:
                    // we then accept WM_CHAR msgs, so
                    // we need the focus
                    WinSetFocus(HWND_DESKTOP, hwndChart);
                        // this invalidates the window

                    if (msg == WM_CONTEXTMENU)
                    {
                        // context menu:
                        SendWMControl(hwndChart,
                                      mp1,
                                      CHTN_CONTEXTMENU,
                                      -1,
                                      lRegionFound);
                    }
                    else
                    {
                        // selection changed:
                        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);
                    }
                }
                else
                    // no selections allowed:
                    // just activate the window
                    WinSetActiveWindow(HWND_DESKTOP, hwndChart);
                        // the parent frame gets activated this way
                mrc = (MPARAM)TRUE;
            break;

            /*
             * WM_SETFOCUS:
             *      we might need to redraw the selection.
             */

            case WM_SETFOCUS:
                if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
                {
                    // selections allowed:
                    pChtCData->fHasFocus = (BOOL)mp2;
                    if (pChtCData->lSelected != -1)
                        WinInvalidateRect(hwndChart, NULL, FALSE);
                }

                // 0.9.20 (2002-07-17) [pr]
                SendWMControl(hwndChart,
                              (MPARAM)-1,
                              (BOOL)mp2 ? CHTN_SETFOCUS : CHTN_KILLFOCUS,
                              pChtCData->lSourceEmphasis,
                              pChtCData->lSelected);

            break;

            /*
             * WM_WINDOWPOSCHANGED:
             *
             */

            case WM_WINDOWPOSCHANGED:
            {
                // this msg is passed two SWP structs:
                // one for the old, one for the new data
                // (from PM docs)
                PSWP pswpNew = (PSWP)mp1;
                // PSWP pswpOld = pswpNew + 1;

                // 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);
                }
                // return default NULL
            break; }

            /*
             * WM_PRESPARAMCHANGED:
             *
             */

            case WM_PRESPARAMCHANGED:
                if (pChtCData->hbmChart)
                {
                    // invalidate bitmap so that
                    // it will be recreated with new
                    // fonts and colors
                    CleanupBitmap(pChtCData);
                    WinInvalidateRect(hwndChart, NULL, FALSE);
                }
            break;

            /*
             * WM_PAINT:
             *      paint the chart bitmap, which is created
             *      if necessary (calling ctlCreateChartBitmap).
             */

            case WM_PAINT:
            {
                RECTL   rclPaint;
                HPS     hps = WinBeginPaint(hwndChart,
                                            NULLHANDLE, // obtain cached micro-PS
                                            &rclPaint);

                PaintChart(hwndChart,
                           pChtCData,
                           hps,
                           &rclPaint);

                WinEndPaint(hps);
                mrc = 0;
            break; }

            /*
             * WM_DESTROY:
             *      clean up resources
             */

            case WM_DESTROY:
                CleanupBitmap(pChtCData);

                CleanupData(pChtCData);

                free(pChtCData);

                mrc = (*OldStaticProc)(hwndChart, msg, mp1, mp2);
            break;

            default:
                mrc = (*OldStaticProc)(hwndChart, msg, mp1, mp2);
        }
    }

    return mrc;
}

/*
 *@@ ctlChartFromStatic:
 *      this function turns an existing static text control into
 *      a chart control (for visualizing data) by subclassing its
 *      window procedure with ctl_fnwpChart.
 *
 *      See cctl_chart.c for an overview and usage instructions.
 *
 *@@added V0.9.0 [umoeller]
 */

BOOL ctlChartFromStatic(HWND hwndChart)     // in: static control to subclass
{
    if (hwndChart)
    {
        PFNWP OldStaticProc = WinSubclassWindow(hwndChart, ctl_fnwpChart);

        if (OldStaticProc)
        {
            PCHARTCDATA pChtCData = (PCHARTCDATA)malloc(sizeof(CHARTCDATA));
            memset(pChtCData, 0, sizeof(CHARTCDATA));
            pChtCData->OldStaticProc = OldStaticProc;
            pChtCData->fHasFocus = FALSE;
            WinSetWindowPtr(hwndChart, QWL_USER, pChtCData);
            return TRUE;
        }
    }

    return FALSE;
}


