home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / xwphescr.zip / XWPH0208.ZIP / src / helpers / cctl_chart.c < prev    next >
Text File  |  2002-08-08  |  70KB  |  1,914 lines

  1.  
  2. /*
  3.  *@@sourcefile cctl_chart.c:
  4.  *      implementation for the "chart" common control, which can
  5.  *      represent an array of "double" values as either a pie chart
  6.  *      or a bar chart. Selections of chart slices and owner
  7.  *      notifications are also supported.
  8.  *
  9.  *      This has been extracted from comctl.c with V0.9.3 (2000-05-21) [umoeller].
  10.  *
  11.  *      The "chart" common control presently is a subclassed static
  12.  *      text control. This way you can easily create a chart control
  13.  *      as a static control in any Dialog Editor; after loading the
  14.  *      dlg template, simply call ctlChartFromStatic with the hwnd
  15.  *      of the static control to make it a chart.
  16.  *
  17.  *      Note: even though you can use _any_ type of static control
  18.  *      with this function, you should use a static _text_ control,
  19.  *      because not all types of static controls react to fonts and
  20.  *      colors dragged upon them. The static _text_ control does.
  21.  *
  22.  *      The pie chart consumes all available space in the static control.
  23.  *
  24.  *      In XWorkplace, this is used for the pie chart on the new
  25.  *      XFldDisk "Details" settings page to display the free space
  26.  *      on a certain drive.
  27.  *
  28.  *      <B>Chart data:</B>
  29.  *
  30.  *      The pie chart control operates on an array of "double" values.
  31.  *      Each value in that array corresponds to a color in a second
  32.  *      array of (LONG) RGB values and, if description texts are
  33.  *      enabled, to a third array of PSZ's with description texts.
  34.  *      In each of the three arrays, the array item count must be
  35.  *      the same, naturally.
  36.  *
  37.  *      The data on which the pie chart operates is initialized to
  38.  *      be void, so that the pie chart will not paint anything
  39.  *      initially. In order to have the pie chart display something,
  40.  *      send a CHTM_SETCHARTDATA message (comctl.h) to the static
  41.  *      control after it has been subclassed.
  42.  *
  43.  *      CHTM_SETCHARTDATA takes a CHARTDATA structure (comctl.h) in mp1,
  44.  *      which must contain the three arrays with the chart data, colors,
  45.  *      and descriptive texts to be displayed.
  46.  *
  47.  *      The chart data will automatically compute the sum of all values
  48.  *      so that each data item will be displayed as a fraction of the
  49.  *      total chart control size, proportionally to the item's value
  50.  *      against the sum of all values. In other words,
  51.  *
  52.  +          (dValue / dTotal) == (sizeSlice / sizeTotal).
  53.  *
  54.  *      The display depends on whether the chart control operates in
  55.  *      "pie chart" or "bar chart" mode.
  56.  *
  57.  *      --  In "pie chart" mode, data items are displayed as slices of
  58.  *          a pie chart. The total sum of the "double" values will
  59.  *          represent the angle in CHARTDATA.usSweepAngle.
  60.  *
  61.  *          For example, if two values of 50 and 100 are passed to the
  62.  *          control and usSweepAngle is 270 (i.e. a three-quarter pie),
  63.  *          the chart control will calculate the following:
  64.  *
  65.  *          1)  The sum of the data is 150.
  66.  *
  67.  *          2)  The first sub-arc will span an angle of 270 * (50/150)
  68.  *              = 90 degrees.
  69.  *
  70.  *          3)  The second sub-arc will span an angle of 270 * (100/150)
  71.  *              = 180 degrees.
  72.  *
  73.  *          You can also have descriptions displayed along the different
  74.  *          chart items by specifying CHARTDATA.papszDescriptions and
  75.  *          setting the CHS_DESCRIPTIONS flag (below).
  76.  *
  77.  *      --  In "bar chart" mode, the angle values make no sense and
  78.  *          are therefore ignored. In "bar chart" mode, the chart will
  79.  *          be drawn as a simple rectangle, with subrectangles representing
  80.  *          the chart slices.
  81.  *
  82.  *      <B>Chart styles:</B>
  83.  *
  84.  *      Use CHTM_SETCHARTSTYLE with a PCHARTSTYLE (comctl.h) in mp1.
  85.  *      This can be sent to the chart control several times.
  86.  *
  87.  *      Set either CHS_PIECHART or CHS_BARCHART to switch the control
  88.  *      to "pie chart" or "bar chart" mode, respectively.
  89.  *
  90.  *      For pie charts, there are several "display sub-styles":
  91.  *
  92.  *      -- CHS_3D_BRIGHT: paint a "3D" socket below the actual chart.
  93.  *
  94.  *      -- CHS_3D_DARKEN: like CHS_3D_BRIGHT, but the socket will be made
  95.  *                        darker compared to the surface.
  96.  *
  97.  *      General styles (for all modes):
  98.  *
  99.  *      -- CHS_DESCRIPTIONS: show descriptions on the chart
  100.  *                           (CHARTDATA.papszDescriptions data).
  101.  *
  102.  *      -- CHS_SELECTIONS: allow pie chart slices to be selectable,
  103.  *                         using the mouse and the keyboard (see below).
  104.  *
  105.  *      <B>Display:</B>
  106.  *
  107.  *      The chart control creates an internal bitmap for the display
  108.  *      only once (ctlCreateChartBitmap). This bitmap is refreshed if
  109.  *      neccessary, e.g. because chart data or styles have changed.
  110.  *
  111.  *      The chart control uses presentation parameters, as listed below.
  112.  *      Presentation parameters are inherited from the parent window.
  113.  *      If a presparam is not set, the corresponding system color is
  114.  *      used. The following color pairs are recognized:
  115.  *
  116.  *      --  PP_BACKGROUNDCOLOR / SYSCLR_DIALOGBACKGROUND:
  117.  *          background of the chart control (outside the chart).
  118.  *
  119.  *      --  PP_FOREGROUNDCOLOR / SYSCLR_WINDOWTEXT:
  120.  *          text color, if description texts are enabled and valid.
  121.  *
  122.  *      --  PP_FONTNAMESIZE:
  123.  *          text font, if description texts are enabled and valid.
  124.  *          If this presparam is not set, the system font is used.
  125.  *
  126.  *      The control reacts to fonts and colors dropped upon it, if
  127.  *      it has been subclassed from a static _text_ control (see above).
  128.  *      It also recalculates the bitmap when it's resized.
  129.  *
  130.  *      <B>Example usage:</B>
  131.  *
  132.  +          // get static control:
  133.  +          HWND    hwndChart = WinWindowFromID(hwndDialog, ID_...);
  134.  +          CHARTSTYLE      cs;
  135.  +          CHARTDATA       cd;
  136.  +          // define data:
  137.  +          double          adData[3] = { 100, 200, 300 };
  138.  +          // define corresponding colors:
  139.  +          LONG            alColors[3] = { 0x800000, 0x008000, 0x000080 };
  140.  +          // define correspdonding descriptions:
  141.  +          PSZ             apszDescriptions[3] = { "item 1", "item 3", "item 3" };
  142.  +
  143.  +          ctlChartFromStatic(hwndChart);     // create chart
  144.  +
  145.  +          cs.ulStyle = CHS_3D_DARKEN | CHS_DESCRIPTIONS;
  146.  +          cs.ulThickness = 20;
  147.  +          cs.dPieSize = .8;           // use 80% of control size
  148.  +          cs.dDescriptions = .5       // draw descriptions at 50% from center
  149.  +          WinSendMsg(hwndChart, CHTM_SETCHARTSTYLE, &cs, NULL);
  150.  +
  151.  +          cd.usStartAngle = 15;       // start at 15° from right
  152.  +          cd.usSweepAngle = 270;      // three-quarter pie (for the sum of the
  153.  +                                      // above values: 100+200+300 = 600)
  154.  +          cd.cValues = 3;             // array count
  155.  +          cd.padValues = &adData[0];
  156.  +          cd.palColors = &alColors[0];
  157.  +          cd.papszDescriptions = &apszDescriptions[0];
  158.  +          WinSendMsg(hwndChart, CHTM_SETCHARTDATA, &cd, NULL);
  159.  *
  160.  *      Note: Version numbering in this file relates to XWorkplace version
  161.  *            numbering.
  162.  *
  163.  *@@header "helpers\comctl.h"
  164.  *@@added V0.9.3 (2000-05-21) [umoeller].
  165.  */
  166.  
  167. /*
  168.  *      Copyright (C) 1997-2002 Ulrich Möller.
  169.  *      This file is part of the "XWorkplace helpers" source package.
  170.  *      This is free software; you can redistribute it and/or modify
  171.  *      it under the terms of the GNU General Public License as published
  172.  *      by the Free Software Foundation, in version 2 as it comes in the
  173.  *      "COPYING" file of the XWorkplace main distribution.
  174.  *      This program is distributed in the hope that it will be useful,
  175.  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
  176.  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  177.  *      GNU General Public License for more details.
  178.  */
  179.  
  180. #define OS2EMX_PLAIN_CHAR
  181.     // this is needed for "os2emx.h"; if this is defined,
  182.     // emx will define PSZ as _signed_ char, otherwise
  183.     // as unsigned char
  184.  
  185. #define INCL_DOSEXCEPTIONS
  186. #define INCL_DOSPROCESS
  187. #define INCL_DOSSEMAPHORES
  188. #define INCL_DOSERRORS
  189.  
  190. #define INCL_WINWINDOWMGR
  191. #define INCL_WINFRAMEMGR
  192. #define INCL_WINMESSAGEMGR
  193. #define INCL_WININPUT
  194. #define INCL_WINPOINTERS
  195. #define INCL_WINTRACKRECT
  196. #define INCL_WINTIMER
  197. #define INCL_WINSYS
  198.  
  199. #define INCL_WINRECTANGLES      /// xxx temporary
  200.  
  201. #define INCL_WINMENUS
  202. #define INCL_WINSTATICS
  203. #define INCL_WINBUTTONS
  204. #define INCL_WINSTDCNR
  205.  
  206. #define INCL_GPIPRIMITIVES
  207. #define INCL_GPILOGCOLORTABLE
  208. #define INCL_GPILCIDS
  209. #define INCL_GPIPATHS
  210. #define INCL_GPIREGIONS
  211. #define INCL_GPIBITMAPS             // added V0.9.1 (2000-01-04) [umoeller]: needed for EMX headers
  212. #include <os2.h>
  213.  
  214. #include <stdlib.h>
  215. #include <stdio.h>
  216. #include <string.h>
  217. #include <setjmp.h>             // needed for except.h
  218. #include <assert.h>             // needed for except.h
  219.  
  220. #include "setup.h"                      // code generation and debugging options
  221.  
  222. #include "helpers\cnrh.h"
  223. #include "helpers\except.h"             // exception handling
  224. #include "helpers\gpih.h"
  225. #include "helpers\linklist.h"
  226. #include "helpers\winh.h"
  227.  
  228. #include "helpers\comctl.h"
  229.  
  230. #pragma hdrstop
  231.  
  232. /*
  233.  *@@category: Helpers\PM helpers\Window classes\Chart control
  234.  *      See cctl_chart.c.
  235.  */
  236.  
  237. /* ******************************************************************
  238.  *
  239.  *   Private declarations
  240.  *
  241.  ********************************************************************/
  242.  
  243. /*
  244.  *@@ CHARTCDATA:
  245.  *      pie chart control data. Composed from the various
  246.  *      chart initialization data.
  247.  *      Stored in QWL_USER of the subclassed static control.
  248.  *      Not available to the application.
  249.  */
  250.  
  251. typedef struct _CHARTCDATA
  252. {
  253.     // data which is initialized upon creation:
  254.     PFNWP           OldStaticProc;  // old static window procedure (from WinSubclassWindow)
  255.  
  256.     // data which is initialized upon CHTM_SETCHARTDATA/CHTM_SETCHARTSTYLE:
  257.     HDC             hdcMem;         // memory device context for bitmap
  258.     HPS             hpsMem;         // memory presentation space for bitmap
  259.     CHARTDATA       cd;             // chart data: initialized to null values
  260.     CHARTSTYLE      cs;             // chart style: initialized to null values
  261.  
  262.     HBITMAP         hbmChart;       // chart bitmap (for quick painting)
  263.     HRGN*           paRegions;      // pointer to array of GPI regions for each data item
  264.  
  265.     // user interaction data:
  266.     LONG            lSelected;      // zero-based index of selected chart item, or -1 if none
  267.     LONG            lSourceEmphasis;    // zero-based index of item with source emphysis, or -1 if none
  268.     BOOL            fHasFocus;
  269. } CHARTCDATA, *PCHARTCDATA;
  270.  
  271. /* ******************************************************************
  272.  *
  273.  *   Chart Control
  274.  *
  275.  ********************************************************************/
  276.  
  277. /*
  278.  *@@ DrawCenteredText:
  279.  *
  280.  *@@added V0.9.12 (2001-05-03) [umoeller]
  281.  */
  282.  
  283. static VOID DrawCenteredText(HPS hpsMem,
  284.                              PPOINTL pptlMiddlePoint,
  285.                              const char *pcsz)
  286. {
  287.     if (pcsz)
  288.     {
  289.         PSZ psz = strdup(pcsz),
  290.             p2 = psz;
  291.         if (psz)
  292.         {
  293.             ULONG cLineBreaks = 0;
  294.             LONG lLineSpacing = 0;
  295.  
  296.             POINTL ptl;
  297.             memcpy(&ptl, pptlMiddlePoint, sizeof(ptl));
  298.  
  299.             // count how many lines we have
  300.             while (p2 = strchr(p2, '\n'))
  301.             {
  302.                 cLineBreaks++;
  303.                 p2++;
  304.             }
  305.  
  306.             if (cLineBreaks)
  307.             {
  308.                 // if we have more than one line:
  309.                 lLineSpacing = gpihQueryLineSpacing(hpsMem);
  310.  
  311.                 // center vertically
  312.                 ptl.y += (lLineSpacing * cLineBreaks) / 2;
  313.             }
  314.  
  315.             p2 = psz;
  316.             while (TRUE)
  317.             {
  318.                 PSZ pNext = strchr(p2, '\n');
  319.                 if (pNext)
  320.                 {
  321.                     *pNext = '\0';
  322.                     pNext++;
  323.                 }
  324.  
  325.                 GpiCharStringAt(hpsMem,
  326.                                 &ptl,
  327.                                 strlen(p2),
  328.                                 (PSZ)p2);
  329.  
  330.                 if (pNext)
  331.                 {
  332.                     p2 = pNext;
  333.                     ptl.y -= lLineSpacing;
  334.                 }
  335.                 else
  336.                     break;
  337.             }
  338.  
  339.             free(psz);
  340.         }
  341.     }
  342. }
  343.  
  344. /*
  345.  *@@ PaintBarChart:
  346.  *      paint implementation for the "bar chart".
  347.  *
  348.  *      This gets called from ctlCreateChartBitmap if the
  349.  *      current style for the chart control is the "bar
  350.  *      chart" style.
  351.  *
  352.  *@@added V0.9.12 (2001-05-03) [umoeller]
  353.  *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed another maaajor PM resource leak with regions
  354.  */
  355.  
  356. static VOID PaintBarChart(HPS hpsMem,
  357.                           PRECTL prclWholeStatic,    // in: rectl to paint into
  358.                           PCHARTDATA pChartData,     // in: chart data
  359.                           PCHARTSTYLE pChartStyle,   // in: chart style
  360.                           double dTotal,             // in: sum of all values in pChartData
  361.                           LONG lTextColor,           // in: description text color (RGB)
  362.                           HRGN* paRegions)           // out: GPI regions for each data item
  363. {
  364.     ULONG       ulYBottomNow = 0;
  365.     ULONG       ul;
  366.  
  367.     PLONG       plColorThis;
  368.     PSZ         *ppszDescriptionThis = NULL;
  369.     HRGN        *phRegionThis;
  370.     double      *pdValueThis;
  371.  
  372.     // allocate array for storing text positions later
  373.     PPOINTL     paptlDescriptions = (PPOINTL)malloc(sizeof(POINTL)
  374.                                                     * pChartData->cValues);
  375.  
  376.     RECTL       rclPaint;
  377.  
  378.     POINTL      ptlLowerLeft,
  379.                 ptlUpperRight;
  380.  
  381.     // thickness of line separators for CHS_DRAWLINES
  382.     // V0.9.12 (2001-05-03) [umoeller]
  383.     const SIZEL cszlDrawLines = {1, 1};
  384.  
  385.     // calculate rectangle;
  386.     // this is the size of the static control
  387.     // minus the "3D thickness", if enabled
  388.     memcpy(&rclPaint, prclWholeStatic, sizeof(RECTL));
  389.     if (pChartStyle->ulStyle & CHS_3D_BRIGHT)
  390.             // this includes CHS_3D_DARKEN
  391.     {
  392.         rclPaint.yBottom = prclWholeStatic->yBottom
  393.                                     + ulYBottomNow;
  394.         rclPaint.yTop = prclWholeStatic->yTop
  395.                                     - pChartStyle->ulThickness
  396.                                     + ulYBottomNow;
  397.     }
  398.  
  399.     // c)  Strangely, GpiSetPattern does work,
  400.     //     while GpiSetColor doesn't (see below).
  401.     GpiSetPattern(hpsMem, PATSYM_SOLID);
  402.  
  403.     // initialize per-item data pointers for
  404.     // loop below
  405.     pdValueThis = pChartData->padValues;
  406.     plColorThis = pChartData->palColors;
  407.     ppszDescriptionThis = pChartData->papszDescriptions;
  408.     phRegionThis = paRegions;
  409.  
  410.     // initialize corners
  411.     ptlLowerLeft.x = 0;
  412.     ptlLowerLeft.y = 0;
  413.     ptlUpperRight.x = 0;
  414.     ptlUpperRight.y = rclPaint.yTop;
  415.  
  416.     // inner "slice loop":
  417.     // this loop goes over the data pointers
  418.     // and paints accordingly. At the end of
  419.     // the loop, we'll advance all those pointers.
  420.     for (ul = 0;
  421.          ul < pChartData->cValues;
  422.          ul++)
  423.     {
  424.         HRGN        hrgnThis;
  425.         /* SHORT       sSweepAngle,
  426.                     sStartAngle; */
  427.  
  428.         GpiSetCurrentPosition(hpsMem, &ptlLowerLeft);
  429.  
  430.         _Pmpf((__FUNCTION__ ": ptlLowerLeft.x = %d", ptlLowerLeft.x));
  431.  
  432.         // calc upper right from data values
  433.         ptlUpperRight.x += (ULONG)(   (double)rclPaint.xRight
  434.                                     * (*pdValueThis)
  435.                                     / dTotal);
  436.  
  437.         // set the area (fill) color
  438.         GpiSetColor(hpsMem,
  439.                     *plColorThis);
  440.  
  441.         // now draw the slice;
  442.         // we could use an area, but since we need
  443.         // to remember the coordinates of the slice
  444.         // for mouse click handling, we require a
  445.         // region. But areas cannot be converted
  446.         // to regions, so we use a path instead.
  447.         GpiBeginPath(hpsMem,
  448.                      1);    // path ID, must be 1
  449.  
  450.         GpiBox(hpsMem,
  451.                DRO_OUTLINE,
  452.                &ptlUpperRight,
  453.                0, 0);       // no rounding
  454.  
  455.         GpiEndPath(hpsMem);
  456.  
  457.         // convert the path to a region;
  458.         // we'll need the region for mouse hit testing later
  459.         hrgnThis = GpiPathToRegion(hpsMem,
  460.                                    1,
  461.                                    FPATH_ALTERNATE);
  462.             // after this, the path is deleted
  463.  
  464.         // now, this FINALLY paints the slice
  465.         GpiPaintRegion(hpsMem, hrgnThis);
  466.  
  467.         // now paint descriptions
  468.         if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
  469.         {
  470.             // description strings valid?
  471.             if (ppszDescriptionThis)
  472.             {
  473.                 POINTL  ptlMiddlePoint;
  474.  
  475.                 // set presentation color
  476.                 GpiSetColor(hpsMem, lTextColor);
  477.  
  478.                 // set text aligment to centered
  479.                 // both horizontally and vertically;
  480.                 // this affects subsequent GpiCharStringAt
  481.                 // calls in that the output text will
  482.                 // be centered around the specified
  483.                 // point
  484.                 GpiSetTextAlignment(hpsMem,
  485.                                     TA_CENTER,      // horizontally
  486.                                     TA_HALF);       // center vertically
  487.  
  488.  
  489.  
  490.                 // center the text in the box
  491.                 ptlMiddlePoint.x =   ptlLowerLeft.x
  492.                                    + (ptlUpperRight.x - ptlLowerLeft.x) / 2;
  493.                 ptlMiddlePoint.y =   ptlLowerLeft.y
  494.                                    + (ptlUpperRight.y - ptlLowerLeft.y) / 2;
  495.  
  496.                 // FINALLY, draw the description
  497.                 // at this point; since we have used
  498.                 // GpiSetTextAlignment above, the
  499.                 // text will be centered on exactly
  500.                 // that point
  501.                 DrawCenteredText(hpsMem,
  502.                                  &ptlMiddlePoint,
  503.                                  *ppszDescriptionThis);
  504.  
  505.             } // end if (ppszDescriptionThis)
  506.         } // end if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
  507.  
  508.         // last run (are we painting the top now)?
  509.         if (    // if separator lines are enabled,
  510.                 // draw a frame around the thing
  511.                 (pChartStyle->ulStyle & CHS_DRAWLINES)
  512.            )
  513.         {
  514.             GpiSetColor(hpsMem, lTextColor);
  515.             GpiFrameRegion(hpsMem,
  516.                            hrgnThis,
  517.                            (PSIZEL)&cszlDrawLines);      // always (1, 1)
  518.         }
  519.  
  520.         if (    phRegionThis
  521.            )
  522.             // region output requested by caller:
  523.             // store region, the caller will clean this up
  524.             *phRegionThis = hrgnThis;
  525.         else
  526.             // no region output requested: destroy region
  527.             GpiDestroyRegion(hpsMem, hrgnThis);
  528.  
  529.         // use the "right" xpos we had now as the "left"
  530.         // xpos for the next loop
  531.         ptlLowerLeft.x = ptlUpperRight.x;
  532.  
  533.         // advance the data pointers
  534.         pdValueThis++;
  535.         plColorThis++;
  536.         if (ppszDescriptionThis)
  537.             ppszDescriptionThis++;
  538.         if (phRegionThis)
  539.             phRegionThis++;
  540.     } // end for (ul...)
  541.  
  542.     // cleanup
  543.     free(paptlDescriptions);
  544. }
  545.  
  546. /*
  547.  *@@ PaintPieChart:
  548.  *      paint implementation for the "pie chart".
  549.  *
  550.  *      This gets called from ctlCreateChartBitmap if the
  551.  *      current style for the chart control is the "pie
  552.  *      chart" style.
  553.  *
  554.  *@@added V0.9.12 (2001-05-03) [umoeller]
  555.  *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed another maaajor PM resource leak with regions
  556.  *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed rounding errors
  557.  */
  558.  
  559. static VOID PaintPieChart(HPS hpsMem,
  560.                           PRECTL prclWholeStatic,    // in: rectl to paint into
  561.                           PCHARTDATA pChartData,     // in: chart data
  562.                           PCHARTSTYLE pChartStyle,   // in: chart style
  563.                           double dTotal,             // in: sum of all values in pChartData
  564.                           LONG lTextColor,           // in: description text color (RGB)
  565.                           HRGN* paRegions)           // out: GPI regions for each data item
  566. {
  567.     ULONG       ulYBottomNow = 0;
  568.     ULONG       ul;
  569.  
  570.     // allocate array for storing text positions later
  571.     PPOINTL     paptlDescriptions = (PPOINTL)malloc(sizeof(POINTL)
  572.                                                     * pChartData->cValues);
  573.     POINTL      ptlCenter;
  574.  
  575.     FIXED       fxPieSize = (LONG)(pChartStyle->dPieSize * 65536),
  576.                 fxDescriptions = (LONG)(pChartStyle->dDescriptions * 65536);
  577.  
  578.     // We'll paint into the bitmap in two loops:
  579.     // +--  The outer "3D" loop is executed
  580.     // |    pChartStyle->ulThickness-fold, if
  581.     // |    CHS_3Dxxx has been enabled; otherwise
  582.     // |    just once.
  583.     // |
  584.     // |    +-- The inner "slice" loop goes thru the
  585.     // |        data fields in pChartData and draws
  586.     // |        the pies accordingly.
  587.     // |
  588.     // +--  We then increase the base Y point (ulYBottomNow)
  589.     //      by one and draw again, thereby getting the 3D
  590.     //      effect.
  591.  
  592.     // 1) outer 3D loop
  593.     do // while (   (pChartStyle->ulStyle & CHS_3D_BRIGHT)...
  594.     {
  595.         RECTL       rclArc;
  596.         PLONG       plColorThis;
  597.         PSZ         *ppszDescriptionThis = NULL;
  598.         PPOINTL     pptlDescriptionThis;
  599.         HRGN        *phRegionThis;
  600.         double      *pdValueThis;
  601.  
  602.         ARCPARAMS   ap;
  603.         AREABUNDLE  ab;
  604.  
  605.         double      dStartAngle = pChartData->usStartAngle,
  606.                     dSweepAngle = 0;
  607.  
  608.         // thickness of line separators for CHS_DRAWLINES
  609.         // V0.9.12 (2001-05-03) [umoeller]
  610.         const SIZEL cszlDrawLines = {1, 1};
  611.  
  612.         // this is only TRUE for the last loop
  613.         BOOL        fNowDrawingSurface =
  614.                       (
  615.                            ((pChartStyle->ulStyle & CHS_3D_BRIGHT) == 0)
  616.                         ||
  617.                            (ulYBottomNow == pChartStyle->ulThickness - 1)
  618.                       );
  619.  
  620.         // // _Pmpf(("Looping, ulYBottomNow: %d", ulYBottomNow));
  621.  
  622.         // calculate pie rectangle for this loop;
  623.         // this is the size of the static control
  624.         // minus the "3D thickness", if enabled
  625.         memcpy(&rclArc, prclWholeStatic, sizeof(RECTL));
  626.         if (pChartStyle->ulStyle & CHS_3D_BRIGHT)
  627.                 // this includes CHS_3D_DARKEN
  628.         {
  629.             rclArc.yBottom = prclWholeStatic->yBottom
  630.                                         + ulYBottomNow;
  631.             rclArc.yTop = prclWholeStatic->yTop
  632.                                         - pChartStyle->ulThickness
  633.                                         + ulYBottomNow;
  634.         }
  635.  
  636.         // calculate center point
  637.         ptlCenter.x = rclArc.xRight / 2;
  638.         ptlCenter.y = ((rclArc.yTop - rclArc.yBottom) / 2) + ulYBottomNow;
  639.  
  640.         // Now, the "arc" APIs really suck. The following
  641.         // has cost me hours of testing to find out:
  642.  
  643.         // a) The arc functions expect some kind of
  644.         //    "default arc" to be defined, which they
  645.         //    refer to. We define the arc as elliptical;
  646.         //    this will be used as the "current arc"
  647.         //    for subsequent arc calls.
  648.         //    (P, S) and (R, Q) define the end points
  649.         //    of the major axes of the ellipse.
  650.         //    The center of the arc will later be
  651.         //    specified with GpiPartialArc (while GpiFullArc
  652.         //    uses the current pen position...
  653.         //    Who created these APIs?!?)
  654.         ap.lP = ptlCenter.x;        // X-axis X
  655.         ap.lS = 0;                  // X-axis Y
  656.         ap.lR = 0;                  // Y-axis X
  657.         ap.lQ = ((rclArc.yTop - rclArc.yBottom) / 2);
  658.                                     // Y-axis Y
  659.         GpiSetArcParams(hpsMem, &ap);
  660.  
  661.         // b)  The line primitives determine lines
  662.         //     to be drawn around the pie slices.
  663.         //     We don't want any.
  664.         GpiSetLineType(hpsMem, LINETYPE_INVISIBLE);
  665.  
  666.         // c)  Strangely, GpiSetPattern does work,
  667.         //     while GpiSetColor doesn't (see below).
  668.         GpiSetPattern(hpsMem, PATSYM_SOLID);
  669.  
  670.         // initialize per-item data pointers for
  671.         // loop below
  672.         pdValueThis = pChartData->padValues;
  673.         plColorThis = pChartData->palColors;
  674.         ppszDescriptionThis = pChartData->papszDescriptions;
  675.         pptlDescriptionThis = paptlDescriptions;
  676.         phRegionThis = paRegions;
  677.  
  678.         // 2) inner "pie slice loop":
  679.         // this loop goes over the data pointers
  680.         // and paints accordingly. At the end of
  681.         // the loop, we'll advance all those pointers.
  682.         for (ul = 0;
  683.              ul < pChartData->cValues;
  684.              ul++)
  685.         {
  686.             HRGN        hrgnThis;
  687.             double      dSweepAngleRounded,
  688.                         dStartAngleRounded;
  689.  
  690.             // calculate the angle to sweep to:
  691.             // a simple rule of three
  692.             dSweepAngle = *pdValueThis      // current data pointer
  693.                           * (double)pChartData->usSweepAngle
  694.                                             // maximum angle
  695.                           / dTotal;         // total data sum
  696.  
  697.             // d)  And now comes the real fun part.
  698.             //     GpiPartialArc is too dumb to draw
  699.             //     anything on its own, it must _always_
  700.             //     appear within an area or path definition.
  701.             //     Unfortunately, this isn't really said
  702.             //     clearly anywhere.
  703.             //     Even worse, in order to set the color
  704.             //     with which the slice is to be drawn,
  705.             //     one has to define an AREABUNDLE because
  706.             //     the regular GpiSetColor functions don't
  707.             //     seem to work here. Or maybe it's my fault,
  708.             //     but with this awful documentation, who knows.
  709.  
  710.             //     We set the current color in the AREABUNDLE
  711.             //     from the color which was defined in the
  712.             //     pie chart data (this pointer was set above
  713.             //     and will be advanced for the next slice).
  714.             ab.lColor = *plColorThis;
  715.  
  716.             // "3D mode" enabled with darkened socket?
  717.             if (    (pChartStyle->ulStyle & CHS_3D_DARKEN)
  718.                     // not last loop?
  719.                  && (!fNowDrawingSurface)
  720.                )
  721.                 // darken the current fill color
  722.                 // by halving each color component
  723.                 gpihManipulateRGB(&ab.lColor,
  724.                                   .5);       // factor
  725.  
  726.             // set the area (fill) color
  727.             GpiSetAttrs(hpsMem,
  728.                         PRIM_AREA,
  729.                         ABB_COLOR,
  730.                         0,
  731.                         (PBUNDLE)&ab);
  732.  
  733.             GpiSetCurrentPosition(hpsMem, &ptlCenter);
  734.  
  735.             // round the angle values properly
  736.             dStartAngleRounded = (dStartAngle + .5);
  737.             dSweepAngleRounded = (dSweepAngle + .5);
  738.  
  739.             // now draw the pie slice;
  740.             // we could use an area, but since we need
  741.             // to remember the coordinates of the slice
  742.             // for mouse click handling, we require a
  743.             // region. But areas cannot be converted
  744.             // to regions, so we use a path instead.
  745.             GpiBeginPath(hpsMem,
  746.                          1);    // path ID, must be 1
  747.  
  748.             // note that the arc functions use the FIXED type
  749.             // for representing fractions... GPI was written back
  750.             // at the days when coprocessors were a luxury, so
  751.             // a floating point value is represented by a LONG
  752.             // with the hiword containing the rounded integer
  753.             // and the loword the remaining fraction. So we
  754.             // can simply multiply the "double" by 65536, and
  755.             // we get the FIXED value V0.9.12 (2001-05-03) [umoeller]
  756.             GpiPartialArc(hpsMem,
  757.                           &ptlCenter,
  758.                           fxPieSize,    // calculated from CHARTSTYLE
  759.                           (FIXED)(dStartAngleRounded * (double)65536),
  760.                           (FIXED)(dSweepAngleRounded * (double)65536));
  761.                 // this moves the current position to the outer
  762.                 // point on the ellipse which corresponds to
  763.                 // sSweepAngle
  764.             GpiEndPath(hpsMem);
  765.  
  766.             // convert the path to a region;
  767.             // we'll need the region for mouse hit testing later
  768.             hrgnThis = GpiPathToRegion(hpsMem, 1,
  769.                                        FPATH_ALTERNATE);
  770.                 // after this, the path is deleted
  771.  
  772.             // last run (are we painting the top now)?
  773.             if (    (fNowDrawingSurface)
  774.                     // descriptions enabled?
  775.                  && (ppszDescriptionThis)
  776.                  && (*ppszDescriptionThis)
  777.                )
  778.             {
  779.                 // yes: calculate position to paint
  780.                 // text at later (we can't do this now,
  781.                 // because it might be overpainted by
  782.                 // the next arc again)
  783.  
  784.                 GpiSetCurrentPosition(hpsMem, &ptlCenter);
  785.                 // move the current position to
  786.                 // the center outer point on the ellipse
  787.                 // (in between sStartAngle and sSweepAngle);
  788.                 // since we're outside an area, this will not
  789.                 // paint anything
  790.                 GpiPartialArc(hpsMem,
  791.                               &ptlCenter,
  792.                               fxDescriptions, // calculated from CHARTSTYLE
  793.                               (FIXED)(dStartAngleRounded * (double)65536),
  794.                               // only half the sweep now:
  795.                               (FIXED)((dSweepAngleRounded / 2) * (double)65536));
  796.  
  797.                 // store this outer point in the array
  798.                 // of description coordinates for later
  799.                 GpiQueryCurrentPosition(hpsMem, pptlDescriptionThis);
  800.  
  801.                 _Pmpf(("pptlDescriptionThis = %d, %d",
  802.                             pptlDescriptionThis->x,
  803.                             pptlDescriptionThis->y));
  804.             }
  805.  
  806.             // now, this FINALLY paints the arc
  807.             GpiPaintRegion(hpsMem, hrgnThis);
  808.  
  809.             // last run (are we painting the top now)?
  810.             if (    (fNowDrawingSurface)
  811.                     // if separator lines are enabled,
  812.                     // draw a frame around the thing
  813.                  && (pChartStyle->ulStyle & CHS_DRAWLINES)
  814.                )
  815.             {
  816.                 GpiSetColor(hpsMem, lTextColor);
  817.                 GpiFrameRegion(hpsMem,
  818.                                hrgnThis,
  819.                                (PSIZEL)&cszlDrawLines);      // always (1, 1)
  820.             }
  821.  
  822.             if (    phRegionThis
  823.                  && fNowDrawingSurface      // this was missing
  824.                                             // V0.9.12 (2001-05-03) [umoeller]
  825.                                             // this created tons of regions which
  826.                                             // were never deleted
  827.                )
  828.                 // region output requested by caller:
  829.                 // store region, the caller will clean this up
  830.                 *phRegionThis = hrgnThis;
  831.             else
  832.                 // no region output requested: destroy region
  833.                 GpiDestroyRegion(hpsMem, hrgnThis);
  834.  
  835.             // increase the start angle by the sweep angle for next loop
  836.             dStartAngle += dSweepAngle;
  837.  
  838.             // advance the data pointers
  839.             pdValueThis++;
  840.             plColorThis++;
  841.             if (ppszDescriptionThis)
  842.                 ppszDescriptionThis++;
  843.             pptlDescriptionThis++;
  844.             if (phRegionThis)
  845.                 phRegionThis++;
  846.         } // end for (ul...)
  847.  
  848.         // go for next "3D thickness" iteration
  849.         ulYBottomNow++;
  850.     } while (   (pChartStyle->ulStyle & CHS_3D_BRIGHT)
  851.              && (ulYBottomNow < pChartStyle->ulThickness)
  852.             );
  853.  
  854.     // now paint descriptions
  855.     if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
  856.     {
  857.         // we use two pointers during the iteration,
  858.         // which point to the item corresponding
  859.         // to the current data item:
  860.         // 1)  pointer to center point on outer border
  861.         //     of partial arc
  862.         //     (calculated above)
  863.         PPOINTL     pptlDescriptionThis = paptlDescriptions;
  864.         // 2)  pointer to current description string
  865.         PSZ*        ppszDescriptionThis = pChartData->papszDescriptions;
  866.  
  867.         // description strings valid?
  868.         if (ppszDescriptionThis)
  869.         {
  870.             // set presentation color
  871.             GpiSetColor(hpsMem, lTextColor);
  872.  
  873.             // set text aligment to centered
  874.             // both horizontally and vertically;
  875.             // this affects subsequent GpiCharStringAt
  876.             // calls in that the output text will
  877.             // be centered around the specified
  878.             // point
  879.             GpiSetTextAlignment(hpsMem,
  880.                                 TA_CENTER,      // horizontally
  881.                                 TA_HALF);       // center vertically
  882.  
  883.             // loop thru data items
  884.             for (ul = 0;
  885.                  ul < pChartData->cValues;
  886.                  ul++)
  887.             {
  888.                 POINTL  ptlMiddlePoint;
  889.  
  890.                 // when drawing the arcs above, we have,
  891.                 // for each pie slice, stored the middle
  892.                 // point on the outer edge of the ellipse
  893.                 // in the paptlDescriptions POINTL array:
  894.  
  895.                 //                ++++
  896.                 //                +    +
  897.                 //                +      +
  898.                 //    ptlCenter\  +       +
  899.                 //              \ +        + <-- current partial arc
  900.                 //               \+        +
  901.                 //     +++++++++++X        +
  902.                 //     +                   +
  903.                 //      +                  +
  904.                 //       +               XX  <-- point calculated above
  905.                 //        +             +
  906.                 //           +        +
  907.                 //             ++++++
  908.  
  909.                 // now calculate a middle point between
  910.                 // that outer point on the ellipse and
  911.                 // the center of the ellipse, which will
  912.                 // be the center point for the text
  913.  
  914.                 //                ++++
  915.                 //                +    +
  916.                 //                +      +
  917.                 //    ptlCenter\  +       +
  918.                 //              \ +        + <-- current partial arc
  919.                 //               \+        +
  920.                 //     ++++++++++++        +
  921.                 //     +             XX    + <-- new middle point
  922.                 //      +                  +
  923.                 //       +               XX  <-- point calculated above
  924.                 //        +             +
  925.                 //           +        +
  926.                 //             ++++++
  927.  
  928.                 ptlMiddlePoint.x =
  929.                         ptlCenter.x
  930.                         + ((pptlDescriptionThis->x - ptlCenter.x) * 2 / 3);
  931.                 ptlMiddlePoint.y =
  932.                         ptlCenter.y
  933.                         - (ptlCenter.y - pptlDescriptionThis->y) * 2 / 3;
  934.  
  935.                 // FINALLY, draw the description
  936.                 // at this point; since we have used
  937.                 // GpiSetTextAlignment above, the
  938.                 // text will be centered on exactly
  939.                 // that point
  940.                 DrawCenteredText(hpsMem,
  941.                                  &ptlMiddlePoint,
  942.                                  *ppszDescriptionThis);
  943.  
  944.                 pptlDescriptionThis++;
  945.                 ppszDescriptionThis++;
  946.             } // end for (ul = 0; ul < pChartData->cValues; ul++)
  947.         } // end if (ppszDescriptionThis)
  948.     } // end if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
  949.  
  950.     // cleanup
  951.     free(paptlDescriptions);
  952. }
  953.  
  954. /*
  955.  *@@ ctlCreateChartBitmap:
  956.  *      this creates a new bitmap and paints the
  957.  *      chart into it. This bitmap will be used
  958.  *      in WM_PAINT of ctl_fnwpChart to quickly paint
  959.  *      the chart image.
  960.  *
  961.  *      The bitmap will be created with the specified
  962.  *      size. The chart will consume all available
  963.  *      space in the bitmap. The returned bitmap is
  964.  *      not selected into any presentation space.
  965.  *
  966.  *      Note: Description text will be drawn with the
  967.  *      current font specified for the memory PS, and
  968.  *      with the current character cell box.
  969.  *
  970.  *      This function gets called automatically from
  971.  *      WM_PAINT if we determine that the bitmap for
  972.  *      painting has not been created yet or has been
  973.  *      invalidated (because data or styles changed).
  974.  *
  975.  *      However, you can also use this function
  976.  *      independently to have a chart bitmap created,
  977.  *      wherever you might need it.
  978.  *      You will then have to fill in the CHARTDATA
  979.  *      and CHARTSTYLE structures before calling
  980.  *      this function.
  981.  *
  982.  *      If (paRegions != NULL), this function will
  983.  *      create a GPI region for each data item. Each GPI
  984.  *      region will then contain the outline of the
  985.  *      corresponding pie chart slice. This allows
  986.  *      you to easily relate coordinates to pie slieces,
  987.  *      for example for mouse clicks.
  988.  *
  989.  *      In that case, paRegions must point to an array
  990.  *      of HRGN items, each of which will contain a
  991.  *      region handle later. It is the responsibility
  992.  *      of the caller to free the regions later, using
  993.  *      GpiDestroyRegion on each region handle (with
  994.  *      the hpsMem specified with this function).
  995.  *
  996.  *      If you're not interested in the regions, set
  997.  *      paRegions to NULL.
  998.  *
  999.  *      This returns NULLHANDLE if an error occured.
  1000.  *      This can mean the following:
  1001.  *
  1002.  *      --  The data is invalid, because the total is 0.
  1003.  *
  1004.  *      --  The bitmap could not be created (memory?).
  1005.  *
  1006.  *@@changed V0.9.12 (2001-05-03) [umoeller]: extracted PaintPieChart
  1007.  *@@changed V0.9.12 (2001-05-03) [umoeller]: added bar chart style
  1008.  */
  1009.  
  1010. HBITMAP ctlCreateChartBitmap(HPS hpsMem,                // in: memory PS to use for bitmap creation
  1011.                              LONG lcx,                  // in: width of bitmap
  1012.                              LONG lcy,                  // in: height of bitmap
  1013.                              PCHARTDATA pChartData,     // in: chart data
  1014.                              PCHARTSTYLE pChartStyle,   // in: chart style
  1015.                              LONG lBackgroundColor,     // in: color around the chart (RGB)
  1016.                              LONG lTextColor,           // in: description text color (RGB)
  1017.                              HRGN* paRegions)           // out: GPI regions for each data item
  1018. {
  1019.     HBITMAP hbmReturn = NULLHANDLE;
  1020.  
  1021.     // sum up the values to get the total
  1022.     ULONG   ul = 0;
  1023.     double  dTotal = 0;
  1024.     double*  pdThis = pChartData->padValues;
  1025.     for (ul = 0; ul < pChartData->cValues; ul++)
  1026.     {
  1027.         _Pmpf(("    dThis is %d", (ULONG)(*pdThis))); // printf
  1028.  
  1029.         dTotal += *pdThis;
  1030.         pdThis++;
  1031.     }
  1032.  
  1033.     _Pmpf((__FUNCTION__ ": dTotal is %d", (ULONG)dTotal)); // printf
  1034.  
  1035.     // avoid division by zero
  1036.     if (dTotal > 0)
  1037.     {
  1038.         RECTL       rclWholeStatic;
  1039.         // get window rectangle (bottom left is 0, 0)
  1040.         rclWholeStatic.xLeft = 0;
  1041.         rclWholeStatic.yBottom = 0;
  1042.         rclWholeStatic.xRight = lcx;
  1043.         rclWholeStatic.yTop = lcy;
  1044.  
  1045.         // create bitmap of that size
  1046.         if ((hbmReturn = gpihCreateBitmap(hpsMem,
  1047.                                           rclWholeStatic.xRight,
  1048.                                           rclWholeStatic.yTop)))
  1049.         {
  1050.             // successfully created:
  1051.  
  1052.             // associate bitmap with memory PS
  1053.             GpiSetBitmap(hpsMem, hbmReturn);
  1054.  
  1055.             // switch the HPS to RGB mode
  1056.             gpihSwitchToRGB(hpsMem);
  1057.  
  1058.             // fill bitmap with static's background color
  1059.             GpiSetColor(hpsMem, lBackgroundColor);
  1060.             gpihBox(hpsMem,
  1061.                     DRO_FILL,
  1062.                     &rclWholeStatic);
  1063.  
  1064.             if (pChartStyle->ulStyle & CHS_BARCHART)
  1065.                 PaintBarChart(hpsMem,
  1066.                               &rclWholeStatic,
  1067.                               pChartData,
  1068.                               pChartStyle,
  1069.                               dTotal,
  1070.                               lTextColor,
  1071.                               paRegions);
  1072.             else
  1073.                 PaintPieChart(hpsMem,
  1074.                               &rclWholeStatic,
  1075.                               pChartData,
  1076.                               pChartStyle,
  1077.                               dTotal,
  1078.                               lTextColor,
  1079.                               paRegions);
  1080.  
  1081.             // deselect (free) bitmap
  1082.             GpiSetBitmap(hpsMem, NULLHANDLE);
  1083.         } // end if (pChtCData->hbmChart ...)
  1084.     } // end if (dTotal > 0)
  1085.  
  1086.     return hbmReturn;
  1087. }
  1088.  
  1089. /*
  1090.  *@@ CleanupBitmap:
  1091.  *      this frees the resources associated with
  1092.  *      the chart bitmap.
  1093.  *
  1094.  *@@changed V0.9.2 (2000-02-29) [umoeller]: fixed maaajor memory leak
  1095.  *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed major PM resource leaks
  1096.  */
  1097.  
  1098. static VOID CleanupBitmap(PCHARTCDATA pChtCData)
  1099. {
  1100.     if (pChtCData)
  1101.     {
  1102.         // destroy regions, but not the array itself
  1103.         // (this is done in CleanupData)
  1104.         if (pChtCData->hpsMem && pChtCData->paRegions)
  1105.         {
  1106.             ULONG   ul;
  1107.             for (ul = 0;
  1108.                  ul < pChtCData->cd.cValues;
  1109.                  ul++)
  1110.             {
  1111.                 if (pChtCData->paRegions[ul])
  1112.                 {
  1113.                     GpiDestroyRegion(pChtCData->hpsMem, pChtCData->paRegions[ul]);
  1114.                     pChtCData->paRegions[ul] = NULLHANDLE;
  1115.                 }
  1116.             }
  1117.         }
  1118.  
  1119.         // bitmap already created?
  1120.         if (pChtCData->hbmChart)
  1121.         {
  1122.             // free current bitmap
  1123.             GpiSetBitmap(pChtCData->hpsMem, NULLHANDLE);
  1124.             // delete bitmap; fails if not freed!
  1125.             if (!GpiDeleteBitmap(pChtCData->hbmChart))
  1126.                 _Pmpf((__FUNCTION__ ": GpiDeleteBitmap failed."));
  1127.             pChtCData->hbmChart = NULLHANDLE;
  1128.         }
  1129.  
  1130.         if (pChtCData->hpsMem)
  1131.         {
  1132.             GpiDestroyPS(pChtCData->hpsMem);
  1133.             pChtCData->hpsMem = NULLHANDLE;
  1134.         }
  1135.         if (pChtCData->hdcMem)
  1136.         {
  1137.             DevCloseDC(pChtCData->hdcMem);
  1138.             pChtCData->hdcMem = NULLHANDLE;
  1139.         }
  1140.     }
  1141. }
  1142.  
  1143. /*
  1144.  *@@ CleanupData:
  1145.  *      this frees all allocated resources of the chart
  1146.  *      control, except the bitmap (use CleanupBitmap
  1147.  *      for that) and the CHARTCDATA itself.
  1148.  *
  1149.  *      Note: CleanupBitmap must be called _before_ this
  1150.  *      function.
  1151.  */
  1152.  
  1153. static VOID CleanupData(PCHARTCDATA pChtCData)
  1154. {
  1155.     if (pChtCData)
  1156.         if (pChtCData->cd.cValues)
  1157.         {
  1158.             // _Pmpf(("Cleaning up data"));
  1159.             // values array
  1160.             if (pChtCData->cd.padValues)
  1161.                 free(pChtCData->cd.padValues);
  1162.  
  1163.             // colors array
  1164.             if (pChtCData->cd.palColors)
  1165.                 free(pChtCData->cd.palColors);
  1166.  
  1167.             // strings array
  1168.             if (pChtCData->cd.papszDescriptions)
  1169.             {
  1170.                 ULONG   ul;
  1171.                 PSZ     *ppszDescriptionThis = pChtCData->cd.papszDescriptions;
  1172.                 for (ul = 0;
  1173.                      ul < pChtCData->cd.cValues;
  1174.                      ul++)
  1175.                 {
  1176.                     if (*ppszDescriptionThis)
  1177.                         free(*ppszDescriptionThis);
  1178.  
  1179.                     ppszDescriptionThis++;
  1180.                 }
  1181.  
  1182.                 free(pChtCData->cd.papszDescriptions);
  1183.             }
  1184.  
  1185.             pChtCData->cd.cValues = 0;
  1186.  
  1187.             // GPI regions array
  1188.             if (pChtCData->paRegions)
  1189.             {
  1190.                 free(pChtCData->paRegions);
  1191.                 pChtCData->paRegions = NULL;
  1192.             }
  1193.         }
  1194. }
  1195.  
  1196. /*
  1197.  *@@ SetChartData:
  1198.  *      implementation for CHTM_SETCHARTDATA
  1199.  *      in ctl_fnwpChart.
  1200.  *
  1201.  *@@added V0.9.2 (2000-02-29) [umoeller]
  1202.  *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed trap if ptr to descriptions was null
  1203.  */
  1204.  
  1205. static VOID SetChartData(HWND hwndChart,
  1206.                          PCHARTCDATA pChtCData,
  1207.                          PCHARTDATA pcdNew)
  1208. {
  1209.     ULONG        ul = 0;
  1210.     PSZ          *ppszDescriptionSource,
  1211.                  *ppszDescriptionTarget;
  1212.  
  1213.     // free previous values, if set
  1214.     if (pChtCData->hbmChart)
  1215.         CleanupBitmap(pChtCData);
  1216.  
  1217.     CleanupData(pChtCData);
  1218.  
  1219.     pChtCData->cd.usStartAngle = pcdNew->usStartAngle;
  1220.     pChtCData->cd.usSweepAngle = pcdNew->usSweepAngle;
  1221.     pChtCData->cd.cValues = pcdNew->cValues;
  1222.  
  1223.     // copy values
  1224.     pChtCData->cd.padValues = (double*)malloc(sizeof(double) * pcdNew->cValues);
  1225.     memcpy(pChtCData->cd.padValues,
  1226.            pcdNew->padValues,
  1227.            sizeof(double) * pcdNew->cValues);
  1228.  
  1229.     // copy colors
  1230.     pChtCData->cd.palColors = (LONG*)malloc(sizeof(LONG) * pcdNew->cValues);
  1231.     memcpy(pChtCData->cd.palColors,
  1232.            pcdNew->palColors,
  1233.            sizeof(LONG) * pcdNew->cValues);
  1234.  
  1235.     // copy strings
  1236.     if (!pcdNew->papszDescriptions)
  1237.         pChtCData->cd.papszDescriptions = NULL;
  1238.     else
  1239.     {
  1240.         pChtCData->cd.papszDescriptions = (PSZ*)malloc(sizeof(PSZ) * pcdNew->cValues);
  1241.         ppszDescriptionSource = pcdNew->papszDescriptions;
  1242.         ppszDescriptionTarget = pChtCData->cd.papszDescriptions;
  1243.         for (ul = 0;
  1244.              ul < pcdNew->cValues;
  1245.              ul++)
  1246.         {
  1247.             if (*ppszDescriptionSource)
  1248.                 *ppszDescriptionTarget = strdup(*ppszDescriptionSource);
  1249.             else
  1250.                 *ppszDescriptionTarget = NULL;
  1251.             ppszDescriptionSource++;
  1252.             ppszDescriptionTarget++;
  1253.         }
  1254.     }
  1255.  
  1256.     // create an array of GPI region handles
  1257.     pChtCData->paRegions = (HRGN*)malloc(sizeof(HRGN) * pcdNew->cValues);
  1258.     // initialize all regions to null
  1259.     memset(pChtCData->paRegions, 0, sizeof(HRGN) * pcdNew->cValues);
  1260.  
  1261.     pChtCData->lSelected = -1;     // none selected
  1262.     pChtCData->lSourceEmphasis = -1;     // none selected
  1263.  
  1264.     WinInvalidateRect(hwndChart, NULL, FALSE);
  1265. }
  1266.  
  1267. /*
  1268.  *@@ PaintChart:
  1269.  *      implementation for WM_PAINT
  1270.  *      in ctl_fnwpChart.
  1271.  *
  1272.  *@@added V0.9.2 (2000-02-29) [umoeller]
  1273.  *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed major PM resource leaks
  1274.  */
  1275.  
  1276. static VOID PaintChart(HWND hwndChart,
  1277.                        PCHARTCDATA pChtCData,
  1278.                        HPS hps,
  1279.                        PRECTL prclPaint)
  1280. {
  1281.     RECTL   rclStatic;
  1282.     WinQueryWindowRect(hwndChart, &rclStatic);
  1283.  
  1284.     // do we have any values yet?
  1285.     if (pChtCData->cd.cValues == 0)
  1286.     {
  1287.         CHAR szDebug[200];
  1288.         sprintf(szDebug, "Error, no values set");
  1289.         WinFillRect(hps,
  1290.                     &rclStatic,  // exclusive
  1291.                     CLR_WHITE);
  1292.         WinDrawText(hps,
  1293.                     strlen(szDebug),
  1294.                     szDebug,
  1295.                     &rclStatic,
  1296.                     CLR_BLACK,
  1297.                     CLR_WHITE,
  1298.                     DT_LEFT | DT_TOP);
  1299.     }
  1300.     else
  1301.     {
  1302.         // valid values, apparently:
  1303.         LONG lForegroundColor = winhQueryPresColor(hwndChart,
  1304.                                                    PP_FOREGROUNDCOLOR,
  1305.                                                    TRUE,       // inherit presparams
  1306.                                                    SYSCLR_WINDOWTEXT);
  1307.  
  1308.         gpihSwitchToRGB(hps);
  1309.  
  1310.         // check if the bitmap needs to be (re)created
  1311.         if (pChtCData->hbmChart == NULLHANDLE)
  1312.         {
  1313.             // no: do it now
  1314.             HPOINTER    hptrOld = winhSetWaitPointer();
  1315.             LONG        lLCIDSet = 0;
  1316.  
  1317.             if (pChtCData->hpsMem == NULLHANDLE)
  1318.             {
  1319.                 // first call:
  1320.                 FONTMETRICS FontMetrics;
  1321.                 LONG        lPointSize;
  1322.  
  1323.                 // create a memory PS for the bitmap
  1324.                 SIZEL szlPage = {rclStatic.xRight,
  1325.                                  rclStatic.yTop};
  1326.                 gpihCreateMemPS(WinQueryAnchorBlock(hwndChart),
  1327.                                 &szlPage,
  1328.                                 &pChtCData->hdcMem,
  1329.                                 &pChtCData->hpsMem);
  1330.  
  1331.                 // get presentation font
  1332.                 lLCIDSet = gpihFindPresFont(hwndChart,
  1333.                                             TRUE,      // inherit PP
  1334.                                             pChtCData->hpsMem,
  1335.                                             "8.Helv",
  1336.                                             &FontMetrics,
  1337.                                             &lPointSize);
  1338.                 // set presentation font
  1339.                 if (lLCIDSet)
  1340.                 {
  1341.                     GpiSetCharSet(pChtCData->hpsMem, lLCIDSet);
  1342.                     if (FontMetrics.fsDefn & FM_DEFN_OUTLINE)
  1343.                         gpihSetPointSize(pChtCData->hpsMem, lPointSize);
  1344.                 }
  1345.             }
  1346.  
  1347.             pChtCData->hbmChart = ctlCreateChartBitmap(
  1348.                       pChtCData->hpsMem,        // mem PS
  1349.                       rclStatic.xRight,         // cx
  1350.                       rclStatic.yTop,           // cy
  1351.                       &pChtCData->cd,           // data
  1352.                       &pChtCData->cs,           // style
  1353.                       // background color:
  1354.                       winhQueryPresColor(hwndChart,
  1355.                                          PP_BACKGROUNDCOLOR,
  1356.                                          TRUE,       // inherit presparams
  1357.                                          SYSCLR_DIALOGBACKGROUND),
  1358.                       // description text color:
  1359.                       lForegroundColor,
  1360.                       pChtCData->paRegions);
  1361.                               // out: regions array
  1362.  
  1363.             // unset and delete font
  1364.             if (lLCIDSet)
  1365.             {
  1366.                 GpiSetCharSet(pChtCData->hpsMem, LCID_DEFAULT);
  1367.                 GpiDeleteSetId(pChtCData->hpsMem, lLCIDSet);
  1368.             }
  1369.  
  1370.             WinSetPointer(HWND_DESKTOP, hptrOld);
  1371.         }
  1372.  
  1373.         if (pChtCData->hbmChart)
  1374.         {
  1375.             // draw the chart bitmap
  1376.             POINTL  ptlDest = { 0, 0 };
  1377.             WinDrawBitmap(hps,
  1378.                           pChtCData->hbmChart,
  1379.                           NULL,     // draw whole bitmap
  1380.                           &ptlDest,
  1381.                           0, 0,     // colors (don't care)
  1382.                           DBM_NORMAL);
  1383.  
  1384.             // now draw emphasis, if we have regions
  1385.             if (pChtCData->paRegions)
  1386.             {
  1387.                 AREABUNDLE ab;
  1388.                 SIZEL   sl = {2, 2};
  1389.  
  1390.                 // 1) source emphasis (even if we don't have focus)
  1391.                 if (pChtCData->lSourceEmphasis != -1)
  1392.                 {
  1393.                     if (pChtCData->paRegions[pChtCData->lSourceEmphasis])
  1394.                     {
  1395.                         // GpiFrameRegion uses the current pattern
  1396.                         // attributes, so we must set the correct
  1397.                         // color in there (GpiSetColor doesn't work)
  1398.                         // V0.9.12 (2001-05-03) [umoeller]
  1399.  
  1400.                         ab.lColor = lForegroundColor;
  1401.                         GpiSetAttrs(hps,
  1402.                                     PRIM_AREA,
  1403.                                     ABB_COLOR,
  1404.                                     0,
  1405.                                     (PBUNDLE)&ab);
  1406.                         GpiSetPattern(hps,
  1407.                                       PATSYM_DIAG1);
  1408.                         GpiPaintRegion(hps,
  1409.                                        pChtCData->paRegions[pChtCData->lSourceEmphasis]);
  1410.                     }
  1411.                 }
  1412.  
  1413.                 // 2) selection
  1414.  
  1415.                 // do we have the focus?
  1416.                 if (    (pChtCData->fHasFocus)
  1417.                         // something selected?
  1418.                      && (pChtCData->lSelected != -1)
  1419.                    )
  1420.                 {
  1421.                     if (pChtCData->paRegions[pChtCData->lSelected])
  1422.                     {
  1423.                         // GpiFrameRegion uses the current pattern
  1424.                         // attributes, so we must set the correct
  1425.                         // color in there (GpiSetColor doesn't work)
  1426.                         // V0.9.12 (2001-05-03) [umoeller]
  1427.  
  1428.                         ab.lColor = RGBCOL_RED; // lForegroundColor;
  1429.                         GpiSetAttrs(hps,
  1430.                                     PRIM_AREA,
  1431.                                     ABB_COLOR,
  1432.                                     0,
  1433.                                     (PBUNDLE)&ab);
  1434.                         GpiFrameRegion(hps,
  1435.                                        pChtCData->paRegions[pChtCData->lSelected],
  1436.                                        &sl);
  1437.                     }
  1438.                 }
  1439.             }
  1440.         }
  1441.     }
  1442. }
  1443.  
  1444. /*
  1445.  *@@ FindItemFromPoint:
  1446.  *      returns the index of the slice under the
  1447.  *      given window coordinates, or -1 if there's
  1448.  *      none.
  1449.  *
  1450.  *@@added V0.9.12 (2001-05-03) [umoeller]
  1451.  */
  1452.  
  1453. static LONG FindItemFromPoint(PCHARTCDATA pChtCData,
  1454.                               LONG lx,
  1455.                               LONG ly)
  1456. {
  1457.     POINTL ptlMouse = {lx, ly};
  1458.  
  1459.     // data set?
  1460.     if (    (pChtCData->cd.cValues)
  1461.          && (pChtCData->paRegions)
  1462.        )
  1463.     {
  1464.         ULONG   ul;
  1465.         for (ul = 0;
  1466.              ul < pChtCData->cd.cValues;
  1467.              ul++)
  1468.         {
  1469.             HRGN hRgnThis = pChtCData->paRegions[ul];
  1470.             if (hRgnThis)
  1471.             {
  1472.                 if (GpiPtInRegion(pChtCData->hpsMem,
  1473.                                   hRgnThis,
  1474.                                   &ptlMouse)
  1475.                      == PRGN_INSIDE)
  1476.                 {
  1477.                     return ul;
  1478.                 }
  1479.             }
  1480.         }
  1481.     }
  1482.  
  1483.     return -1;
  1484. }
  1485.  
  1486. /*
  1487.  *@@ SendWMControl:
  1488.  *
  1489.  *@@added V0.9.12 (2001-05-03) [umoeller]
  1490.  */
  1491.  
  1492. static VOID SendWMControl(HWND hwndChart,
  1493.                           MPARAM mp1Mouse,
  1494.                           USHORT usNotify,
  1495.                           ULONG ulEmphasis,            // 0 or 1
  1496.                           LONG lIndex)
  1497. {
  1498.     HWND hwndOwner;
  1499.  
  1500.     if (hwndOwner = WinQueryWindow(hwndChart, QW_OWNER))
  1501.     {
  1502.         EMPHASISNOTIFY en;
  1503.         en.hwndSource = hwndChart;
  1504.         en.lIndex = lIndex;        // can be -1
  1505.         en.ulEmphasis = ulEmphasis;
  1506.         en.ptl.x = SHORT1FROMMP(mp1Mouse);
  1507.         en.ptl.y = SHORT2FROMMP(mp1Mouse);
  1508.  
  1509.         WinSendMsg(hwndOwner,
  1510.                    WM_CONTROL,
  1511.                    MPFROM2SHORT(WinQueryWindowUShort(hwndChart,
  1512.                                                      QWS_ID),
  1513.                                 usNotify),
  1514.                    &en);
  1515.     }
  1516. }
  1517.  
  1518. /*
  1519.  *@@ SetEmphasis:
  1520.  *
  1521.  *@@added V0.9.12 (2001-05-03) [umoeller]
  1522.  */
  1523.  
  1524. static BOOL SetEmphasis(HWND hwndChart,
  1525.                         PCHARTCDATA pChtCData,
  1526.                         ULONG ulEmphasis,      // in: 0 == selection, 1 == source emphasis
  1527.                         MPARAM mp1,            // in: mp1 with mouse values or -1 if none
  1528.                         LONG lIndex,
  1529.                         BOOL fIsContextMenu)
  1530. {
  1531.     BOOL brc = FALSE;
  1532.  
  1533.     PLONG plOld;
  1534.  
  1535.     if (ulEmphasis == 0)
  1536.         plOld = &pChtCData->lSelected;
  1537.     else if (ulEmphasis == 1)
  1538.         plOld = &pChtCData->lSourceEmphasis;
  1539.  
  1540.     if (plOld)
  1541.     {
  1542.         if (    (*plOld != lIndex)
  1543.              || (fIsContextMenu)
  1544.            )
  1545.         {
  1546.             // selection changed:
  1547.             *plOld = lIndex;
  1548.  
  1549.             brc = TRUE;
  1550.  
  1551.             // repaint
  1552.             WinInvalidateRect(hwndChart, NULL, FALSE);
  1553.  
  1554.             // send notification to owner
  1555.             // V0.9.12 (2001-05-03) [umoeller]
  1556.             SendWMControl(hwndChart,
  1557.                           mp1,
  1558.                           CHTN_EMPHASISCHANGED,
  1559.                           ulEmphasis,
  1560.                           lIndex);
  1561.         }
  1562.     }
  1563.  
  1564.     return brc;
  1565. }
  1566.  
  1567. /*
  1568.  *@@ ctl_fnwpChart:
  1569.  *      window procedure for the "chart" control.
  1570.  *
  1571.  *      This is not a stand-alone window procedure, but must only
  1572.  *      be used with static controls subclassed by ctlChartFromStatic.
  1573.  *
  1574.  *@@added V0.9.0 [umoeller]
  1575.  *@@changed V0.9.2 (2000-02-29) [umoeller]: added resize support
  1576.  *@@changed V0.9.2 (2000-02-29) [umoeller]: fixed baaad PM resource leaks, the bitmap was never freed
  1577.  *@@changed V0.9.12 (2001-05-03) [umoeller]: added WM_CONTEXTMENU support
  1578.  *@@changed V0.9.20 (2002-07-17) [pr]: added CHTN_SETFOCUS and CHTN_KILLFOCUS support
  1579.  */
  1580.  
  1581. MRESULT EXPENTRY ctl_fnwpChart(HWND hwndChart, ULONG msg, MPARAM mp1, MPARAM mp2)
  1582. {
  1583.     MRESULT     mrc = 0;
  1584.     PCHARTCDATA   pChtCData = (PCHARTCDATA)WinQueryWindowULong(hwndChart, QWL_USER);
  1585.  
  1586.     if (pChtCData)
  1587.     {
  1588.         PFNWP OldStaticProc = pChtCData->OldStaticProc;
  1589.  
  1590.         switch (msg)
  1591.         {
  1592.             /*
  1593.              *@@ CHTM_SETCHARTDATA:
  1594.              *      user msg to set the pie chart data,
  1595.              *      which is copied to the control's
  1596.              *      memory.
  1597.              *      This can be sent several times to
  1598.              *      have the chart refreshed with new
  1599.              *      values.
  1600.              *
  1601.              *      Parameters:
  1602.              *      --  PCHARTDATA mp1: new values.
  1603.              *              These will be copied to the chart's internal data.
  1604.              *      --  mp2: unused.
  1605.              */
  1606.  
  1607.             case CHTM_SETCHARTDATA:
  1608.                 // _Pmpf(("CHTM_SETCHARTDATA, mp1: 0x%lX", mp1));
  1609.                 if (mp1)
  1610.                     SetChartData(hwndChart,
  1611.                                  pChtCData,
  1612.                                  (PCHARTDATA)mp1);
  1613.             break;
  1614.  
  1615.             /*
  1616.              *@@ CHTM_SETCHARTSTYLE:
  1617.              *      user msg to set the chart style,
  1618.              *      which is copied to the control's
  1619.              *      memory.
  1620.              *      This can be sent several times to
  1621.              *      have the chart refreshed with new
  1622.              *      styles.
  1623.              *
  1624.              *      Parameters:
  1625.              *      --  PCHARTSTYLE mp1: new style data.
  1626.              *              This will be copied to the chart's internal data.
  1627.              *      --  mp2: unused.
  1628.              */
  1629.  
  1630.             case CHTM_SETCHARTSTYLE:
  1631.                 // _Pmpf(("CHTM_SETCHARTSTYLE, mp1: 0x%lX", mp1));
  1632.                 memcpy(&(pChtCData->cs), mp1, sizeof(CHARTSTYLE));
  1633.                 if (pChtCData->hbmChart)
  1634.                 {
  1635.                     CleanupBitmap(pChtCData);
  1636.                     WinInvalidateRect(hwndChart, NULL, FALSE);
  1637.                 }
  1638.             break;
  1639.  
  1640.             /*
  1641.              *@@ CHTM_ITEMFROMPOINT:
  1642.              *      this can be _sent_ to the chart control
  1643.              *      to query the chart item which surrounds
  1644.              *      the given point. Specify the coordinates
  1645.              *      in two SHORT's in mp1 (as with WM_BUTTON1*
  1646.              *      messages), in window coordinates.
  1647.              *
  1648.              *      Parameters:
  1649.              *      -- SHORT SHORT1FROMMP(mp1): x
  1650.              *      -- SHORT SHORT1FROMMP(mp1): y
  1651.              *      -- mp2: unused.
  1652.              *
  1653.              *      Returns:
  1654.              *      -- LONG: data item index (counting from
  1655.              *               0) or -1 if none, e.g. if no
  1656.              *               data has been set yet or if
  1657.              *               the point is outside the pie
  1658.              *               slices.
  1659.              *
  1660.              *@@added V0.9.2 (2000-02-29) [umoeller]
  1661.              */
  1662.  
  1663.             case CHTM_ITEMFROMPOINT:
  1664.             {
  1665.                 mrc = (MPARAM)FindItemFromPoint(pChtCData,
  1666.                                                 SHORT1FROMMP(mp1),
  1667.                                                 SHORT2FROMMP(mp1));
  1668.             break; }
  1669.  
  1670.             /*
  1671.              *@@ CHTM_SETEMPHASIS:
  1672.              *      sets emphasis on a chart slice.
  1673.              *
  1674.              *      Parameters:
  1675.              *
  1676.              *      LONG mp1: emphasis to set. Currently
  1677.              *              defined values:
  1678.              *
  1679.              *              -- 0: set selection.
  1680.              *
  1681.              *              -- 1: set source emphasis.
  1682.              *
  1683.              *      LONG mp2: zero-based index of slice to
  1684.              *              set emphasis for, or -1 to
  1685.              *              remove that emphasis.
  1686.              *
  1687.              *      Returns: TRUE if emphasis was changed.
  1688.              *
  1689.              *@@added V0.9.12 (2001-05-03) [umoeller]
  1690.              */
  1691.  
  1692.             case CHTM_SETEMPHASIS:
  1693.             {
  1694.                 LONG lEmph = (LONG)mp1;
  1695.                 LONG lIndex = (LONG)mp2;
  1696.                 if (    lEmph > 0
  1697.                      && lEmph < 2
  1698.                      && (    lIndex >= -1
  1699.                           || (    lIndex > 0
  1700.                                && lIndex < pChtCData->cd.cValues
  1701.                              )
  1702.                         )
  1703.                    )
  1704.                 {
  1705.                     mrc = (MPARAM)SetEmphasis(hwndChart,
  1706.                                               pChtCData,
  1707.                                               lEmph,
  1708.                                               (MPARAM)-1,
  1709.                                               lIndex,
  1710.                                               // force flag:
  1711.                                               TRUE);
  1712.                 }
  1713.             }
  1714.             break;
  1715.  
  1716.             /*
  1717.              * WM_BUTTON1CLICK:
  1718.              * WM_CONTEXTMENU: V0.9.12 (2001-05-03) [umoeller]
  1719.              */
  1720.  
  1721.             case WM_BUTTON1CLICK:
  1722.             case WM_BUTTON1DBLCLK:
  1723.             case WM_CONTEXTMENU:
  1724.                 if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
  1725.                 {
  1726.                     LONG lRegionFound = FindItemFromPoint(pChtCData,
  1727.                                                 SHORT1FROMMP(mp1),
  1728.                                                 SHORT2FROMMP(mp1));
  1729.  
  1730.                     // selections allowed:
  1731.                     // we then accept WM_CHAR msgs, so
  1732.                     // we need the focus
  1733.                     WinSetFocus(HWND_DESKTOP, hwndChart);
  1734.                         // this invalidates the window
  1735.  
  1736.                     if (msg == WM_CONTEXTMENU)
  1737.                     {
  1738.                         // context menu:
  1739.                         SendWMControl(hwndChart,
  1740.                                       mp1,
  1741.                                       CHTN_CONTEXTMENU,
  1742.                                       -1,
  1743.                                       lRegionFound);
  1744.                     }
  1745.                     else
  1746.                     {
  1747.                         // selection changed:
  1748.                         SetEmphasis(hwndChart,
  1749.                                     pChtCData,
  1750.                                     0,    // set selection; caller must set source emphasis
  1751.                                     mp1,
  1752.                                     lRegionFound,
  1753.                                     // force flag:
  1754.                                     FALSE);
  1755.  
  1756.                         if (msg == WM_BUTTON1DBLCLK)
  1757.                             SendWMControl(hwndChart,
  1758.                                           mp1,
  1759.                                           CHTN_ENTER,
  1760.                                           0,
  1761.                                           lRegionFound);
  1762.                     }
  1763.                 }
  1764.                 else
  1765.                     // no selections allowed:
  1766.                     // just activate the window
  1767.                     WinSetActiveWindow(HWND_DESKTOP, hwndChart);
  1768.                         // the parent frame gets activated this way
  1769.                 mrc = (MPARAM)TRUE;
  1770.             break;
  1771.  
  1772.             /*
  1773.              * WM_SETFOCUS:
  1774.              *      we might need to redraw the selection.
  1775.              */
  1776.  
  1777.             case WM_SETFOCUS:
  1778.                 if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
  1779.                 {
  1780.                     // selections allowed:
  1781.                     pChtCData->fHasFocus = (BOOL)mp2;
  1782.                     if (pChtCData->lSelected != -1)
  1783.                         WinInvalidateRect(hwndChart, NULL, FALSE);
  1784.                 }
  1785.  
  1786.                 // 0.9.20 (2002-07-17) [pr]
  1787.                 SendWMControl(hwndChart,
  1788.                               (MPARAM)-1,
  1789.                               (BOOL)mp2 ? CHTN_SETFOCUS : CHTN_KILLFOCUS,
  1790.                               pChtCData->lSourceEmphasis,
  1791.                               pChtCData->lSelected);
  1792.  
  1793.             break;
  1794.  
  1795.             /*
  1796.              * WM_WINDOWPOSCHANGED:
  1797.              *
  1798.              */
  1799.  
  1800.             case WM_WINDOWPOSCHANGED:
  1801.             {
  1802.                 // this msg is passed two SWP structs:
  1803.                 // one for the old, one for the new data
  1804.                 // (from PM docs)
  1805.                 PSWP pswpNew = (PSWP)mp1;
  1806.                 // PSWP pswpOld = pswpNew + 1;
  1807.  
  1808.                 // resizing?
  1809.                 if (pswpNew->fl & SWP_SIZE)
  1810.                 {
  1811.                     if (pChtCData->hbmChart)
  1812.                         // invalidate bitmap so that
  1813.                         // it will be recreated with new size
  1814.                         CleanupBitmap(pChtCData);
  1815.  
  1816.                     WinInvalidateRect(hwndChart, NULL, FALSE);
  1817.                 }
  1818.                 // return default NULL
  1819.             break; }
  1820.  
  1821.             /*
  1822.              * WM_PRESPARAMCHANGED:
  1823.              *
  1824.              */
  1825.  
  1826.             case WM_PRESPARAMCHANGED:
  1827.                 if (pChtCData->hbmChart)
  1828.                 {
  1829.                     // invalidate bitmap so that
  1830.                     // it will be recreated with new
  1831.                     // fonts and colors
  1832.                     CleanupBitmap(pChtCData);
  1833.                     WinInvalidateRect(hwndChart, NULL, FALSE);
  1834.                 }
  1835.             break;
  1836.  
  1837.             /*
  1838.              * WM_PAINT:
  1839.              *      paint the chart bitmap, which is created
  1840.              *      if necessary (calling ctlCreateChartBitmap).
  1841.              */
  1842.  
  1843.             case WM_PAINT:
  1844.             {
  1845.                 RECTL   rclPaint;
  1846.                 HPS     hps = WinBeginPaint(hwndChart,
  1847.                                             NULLHANDLE, // obtain cached micro-PS
  1848.                                             &rclPaint);
  1849.  
  1850.                 PaintChart(hwndChart,
  1851.                            pChtCData,
  1852.                            hps,
  1853.                            &rclPaint);
  1854.  
  1855.                 WinEndPaint(hps);
  1856.                 mrc = 0;
  1857.             break; }
  1858.  
  1859.             /*
  1860.              * WM_DESTROY:
  1861.              *      clean up resources
  1862.              */
  1863.  
  1864.             case WM_DESTROY:
  1865.                 CleanupBitmap(pChtCData);
  1866.  
  1867.                 CleanupData(pChtCData);
  1868.  
  1869.                 free(pChtCData);
  1870.  
  1871.                 mrc = (*OldStaticProc)(hwndChart, msg, mp1, mp2);
  1872.             break;
  1873.  
  1874.             default:
  1875.                 mrc = (*OldStaticProc)(hwndChart, msg, mp1, mp2);
  1876.         }
  1877.     }
  1878.  
  1879.     return mrc;
  1880. }
  1881.  
  1882. /*
  1883.  *@@ ctlChartFromStatic:
  1884.  *      this function turns an existing static text control into
  1885.  *      a chart control (for visualizing data) by subclassing its
  1886.  *      window procedure with ctl_fnwpChart.
  1887.  *
  1888.  *      See cctl_chart.c for an overview and usage instructions.
  1889.  *
  1890.  *@@added V0.9.0 [umoeller]
  1891.  */
  1892.  
  1893. BOOL ctlChartFromStatic(HWND hwndChart)     // in: static control to subclass
  1894. {
  1895.     if (hwndChart)
  1896.     {
  1897.         PFNWP OldStaticProc = WinSubclassWindow(hwndChart, ctl_fnwpChart);
  1898.  
  1899.         if (OldStaticProc)
  1900.         {
  1901.             PCHARTCDATA pChtCData = (PCHARTCDATA)malloc(sizeof(CHARTCDATA));
  1902.             memset(pChtCData, 0, sizeof(CHARTCDATA));
  1903.             pChtCData->OldStaticProc = OldStaticProc;
  1904.             pChtCData->fHasFocus = FALSE;
  1905.             WinSetWindowPtr(hwndChart, QWL_USER, pChtCData);
  1906.             return TRUE;
  1907.         }
  1908.     }
  1909.  
  1910.     return FALSE;
  1911. }
  1912.  
  1913.  
  1914.