source: trunk/src/helpers/cctl_chart.c@ 56

Last change on this file since 56 was 25, checked in by umoeller, 25 years ago

Misc. changes for V0.9.7.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 48.6 KB
Line 
1
2/*
3 *@@sourcefile cctl_chart.c:
4 * implementation for the "chart" common control.
5 * See comctl.c for an overview.
6 *
7 * This has been extracted from comctl.c with V0.9.3 (2000-05-21) [umoeller].
8 *
9 * Note: Version numbering in this file relates to XWorkplace version
10 * numbering.
11 *
12 *@@header "helpers\comctl.h"
13 *@@added V0.9.3 (2000-05-21) [umoeller].
14 */
15
16/*
17 * Copyright (C) 1997-2000 Ulrich M”ller.
18 * This file is part of the "XWorkplace helpers" source package.
19 * This is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published
21 * by the Free Software Foundation, in version 2 as it comes in the
22 * "COPYING" file of the XWorkplace main distribution.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 */
28
29#define OS2EMX_PLAIN_CHAR
30 // this is needed for "os2emx.h"; if this is defined,
31 // emx will define PSZ as _signed_ char, otherwise
32 // as unsigned char
33
34#define INCL_DOSEXCEPTIONS
35#define INCL_DOSPROCESS
36#define INCL_DOSSEMAPHORES
37#define INCL_DOSERRORS
38
39#define INCL_WINWINDOWMGR
40#define INCL_WINFRAMEMGR
41#define INCL_WINMESSAGEMGR
42#define INCL_WININPUT
43#define INCL_WINPOINTERS
44#define INCL_WINTRACKRECT
45#define INCL_WINTIMER
46#define INCL_WINSYS
47
48#define INCL_WINRECTANGLES /// xxx temporary
49
50#define INCL_WINMENUS
51#define INCL_WINSTATICS
52#define INCL_WINBUTTONS
53#define INCL_WINSTDCNR
54
55#define INCL_GPIPRIMITIVES
56#define INCL_GPILOGCOLORTABLE
57#define INCL_GPILCIDS
58#define INCL_GPIPATHS
59#define INCL_GPIREGIONS
60#define INCL_GPIBITMAPS // added V0.9.1 (2000-01-04) [umoeller]: needed for EMX headers
61#include <os2.h>
62
63#include <stdlib.h>
64#include <stdio.h>
65#include <string.h>
66#include <setjmp.h> // needed for except.h
67#include <assert.h> // needed for except.h
68
69#include "setup.h" // code generation and debugging options
70
71#include "helpers\cnrh.h"
72#include "helpers\except.h" // exception handling
73#include "helpers\gpih.h"
74#include "helpers\linklist.h"
75#include "helpers\winh.h"
76
77#include "helpers\comctl.h"
78
79#pragma hdrstop
80
81/*
82 *@@category: Helpers\PM helpers\Window classes\Chart control
83 * See cctl_chart.c.
84 */
85
86/* ******************************************************************
87 *
88 * Chart Control
89 *
90 ********************************************************************/
91
92/*
93 *@@ ctlCreateChartBitmap:
94 * this creates a new bitmap and paints the
95 * chart into it. This bitmap will be used
96 * in WM_PAINT of ctl_fnwpChart to quickly paint
97 * the chart image.
98 *
99 * The bitmap will be created with the specified
100 * size. The chart will consume all available
101 * space in the bitmap. The returned bitmap is
102 * not selected into any presentation space.
103 *
104 * Note: Description text will be drawn with the
105 * current font specified for the memory PS, and
106 * with the current character cell box.
107 *
108 * This function gets called automatically from
109 * WM_PAINT if we determine that the bitmap for
110 * painting has not been created yet or has been
111 * invalidated (because data or styles changed).
112 *
113 * However, you can also use this function
114 * independently to have a chart bitmap created,
115 * wherever you might need it.
116 * You will then have to fill in the CHARTDATA
117 * and CHARTSTYLE structures before calling
118 * this function.
119 *
120 * If (paRegions != NULL), this function will
121 * create GPI regions for data item. Each GPI
122 * region will then contain the outline of the
123 * corresponding pie chart slice. This allows
124 * you to easily relate coordinates to pie slieces,
125 * for example for mouse clicks.
126 *
127 * In that case, paRegions must point to an array
128 * of HRGN items, each of which will contain a
129 * region handle later. It is the responsibility
130 * of the caller to free the regions later, using
131 * GpiDestroyRegion on each region handle (with
132 * the hpsMem specified with this function).
133 *
134 * If you're not interested in the regions, set
135 * paRegions to NULL.
136 *
137 * This returns NULLHANDLE if an error occured.
138 * This can mean the following:
139 * -- The data is invalid, because the total is 0.
140 * -- The bitmap could not be created (memory?).
141 */
142
143HBITMAP ctlCreateChartBitmap(HPS hpsMem, // in: memory PS to use for bitmap creation
144 LONG lcx, // in: width of bitmap
145 LONG lcy, // in: height of bitmap
146 PCHARTDATA pChartData, // in: chart data
147 PCHARTSTYLE pChartStyle, // in: chart style
148 LONG lBackgroundColor, // in: color around the chart (RGB)
149 LONG lTextColor, // in: description text color (RGB)
150 HRGN* paRegions) // out: GPI regions for each data item
151{
152 HBITMAP hbmReturn = NULLHANDLE;
153
154 // sum up the values to get the total
155 ULONG ul = 0;
156 double dTotal = 0;
157 double* pdThis = pChartData->padValues;
158 for (ul = 0; ul < pChartData->cValues; ul++)
159 {
160 dTotal += *pdThis;
161 pdThis++;
162 }
163
164 // avoid division by zero
165 if (dTotal > 0)
166 {
167 RECTL rclWholeStatic;
168 ULONG ulYBottomNow = 0;
169
170 // get window rectangle (bottom left is 0, 0)
171 rclWholeStatic.xLeft = 0;
172 rclWholeStatic.yBottom = 0;
173 rclWholeStatic.xRight = lcx;
174 rclWholeStatic.yTop = lcy;
175
176 // create bitmap of that size
177 if ((hbmReturn = gpihCreateBitmap(hpsMem,
178 rclWholeStatic.xRight,
179 rclWholeStatic.yTop)))
180 {
181 // successfully created:
182
183 // allocate array for storing text positions later
184 PPOINTL paptlDescriptions = (PPOINTL)malloc(sizeof(POINTL)
185 * pChartData->cValues);
186 POINTL ptlCenter;
187
188 FIXED fxPieSize = (LONG)(pChartStyle->dPieSize * 65536),
189 fxDescriptions = (LONG)(pChartStyle->dDescriptions * 65536);
190
191 // associate bitmap with memory PS
192 GpiSetBitmap(hpsMem, hbmReturn);
193
194 // switch the HPS to RGB mode
195 gpihSwitchToRGB(hpsMem);
196
197 // fill bitmap with static's background color
198 GpiSetColor(hpsMem, lBackgroundColor);
199 gpihBox(hpsMem,
200 DRO_FILL,
201 &rclWholeStatic);
202
203 // We'll paint into the bitmap in two loops:
204 // +-- The outer "3D" loop is executed
205 // | pChartStyle->ulThickness-fold, if
206 // | CHS_3Dxxx has been enabled; otherwise
207 // | just once.
208 // |
209 // | +-- The inner "slice" loop goes thru the
210 // | data fields in pChartData and draws
211 // | the pies accordingly.
212 // |
213 // +-- We then increase the base Y point (ulYBottomNow)
214 // by one and draw again, thereby getting the 3D
215 // effect.
216
217 // 1) outer 3D loop
218 do // while ( (pChartStyle->ulStyle & CHS_3D_BRIGHT)...
219 {
220 RECTL rclArc;
221 PLONG plColorThis;
222 PSZ *ppszDescriptionThis = NULL;
223 PPOINTL pptlDescriptionThis;
224 HRGN* phRegionThis;
225
226 ARCPARAMS ap;
227 AREABUNDLE ab;
228
229 double dStartAngle = pChartData->usStartAngle,
230 dSweepAngle = 0;
231
232 // this is only TRUE for the last loop
233 BOOL fNowDrawingSurface =
234 (
235 ((pChartStyle->ulStyle & CHS_3D_BRIGHT) == 0)
236 ||
237 (ulYBottomNow == pChartStyle->ulThickness - 1)
238 );
239
240 // // _Pmpf(("Looping, ulYBottomNow: %d", ulYBottomNow));
241
242 // calculate pie rectangle for this loop;
243 // this is the size of the static control
244 // minus the "3D thickness", if enabled
245 memcpy(&rclArc, &rclWholeStatic, sizeof(RECTL));
246 if (pChartStyle->ulStyle & CHS_3D_BRIGHT)
247 // this includes CHS_3D_DARKEN
248 {
249 rclArc.yBottom = rclWholeStatic.yBottom
250 + ulYBottomNow;
251 rclArc.yTop = rclWholeStatic.yTop
252 - pChartStyle->ulThickness
253 + ulYBottomNow;
254 }
255
256 // calculate center point
257 ptlCenter.x = rclArc.xRight / 2;
258 ptlCenter.y = ((rclArc.yTop - rclArc.yBottom) / 2) + ulYBottomNow;
259
260 // Now, the "arc" APIs really suck. The following
261 // has cost me hours of testing to find out:
262
263 // a) The arc functions expect some kind of
264 // "default arc" to be defined, which they
265 // refer to. We define the arc as elliptical;
266 // this will be used as the "current arc"
267 // for subsequent arc calls.
268 // (P, S) and (R, Q) define the end points
269 // of the major axes of the ellipse.
270 // The center of the arc will later be
271 // specified with GpiPartialArc (while GpiFullArc
272 // uses the current pen position...
273 // Who created these APIs?!? This might be a most
274 // flexible way to do things, but where's the
275 // simple stuff?!?)
276 ap.lP = ptlCenter.x; // X-axis X
277 ap.lS = 0; // X-axis Y
278 ap.lR = 0; // Y-axis X
279 ap.lQ = ((rclArc.yTop - rclArc.yBottom) / 2);
280 // Y-axis Y
281 GpiSetArcParams(hpsMem, &ap);
282
283 // b) The line primitives determine lines
284 // to be drawn around the pie slices.
285 // We don't want any.
286 GpiSetLineType(hpsMem, LINETYPE_INVISIBLE);
287
288 // c) Strangely, GpiSetPattern does work,
289 // while GpiSetColor doesn't (see below).
290 GpiSetPattern(hpsMem, PATSYM_SOLID);
291
292 // initialize per-item data pointers for
293 // loop below
294 pdThis = pChartData->padValues;
295 plColorThis = pChartData->palColors;
296 ppszDescriptionThis = pChartData->papszDescriptions;
297 pptlDescriptionThis = paptlDescriptions;
298 phRegionThis = paRegions;
299
300 // 2) inner "pie slice loop":
301 // this loop goes over the data pointers
302 // and paints accordingly. At the end of
303 // the loop, we'll advance all those pointers.
304 for (ul = 0; ul < pChartData->cValues; ul++)
305 {
306 HRGN hrgnThis;
307 SHORT sSweepAngle,
308 sStartAngle;
309
310 // calculate the angle to sweep to:
311 // a simple rule of three
312 dSweepAngle = *pdThis // current data pointer
313 * pChartData->usSweepAngle
314 // maximum angle
315 / dTotal; // total data sum
316
317 // d) And now comes the real fun part.
318 // GpiPartialArc is too dumb to draw
319 // anything on its own, it must _always_
320 // appear within an area or path definition.
321 // Unfortunately, this isn't really said
322 // clearly anywhere.
323 // Even worse, in order to set the color
324 // with which the slice is to be drawn,
325 // one has to define an AREABUNDLE because
326 // the regular GpiSetColor functions don't
327 // seem to work here. Or maybe it's my fault,
328 // but with this awful documentation, who knows.
329 // We use the current color defined in the
330 // pie chart data (this pointer was set above
331 // and will be advanced for the next slice).
332 ab.lColor = *plColorThis;
333
334 // "3D mode" enabled with darkened socket?
335 if ( (pChartStyle->ulStyle & CHS_3D_DARKEN)
336 // not last loop?
337 && (!fNowDrawingSurface)
338 )
339 // darken the current fill color
340 // by halving each color component
341 gpihManipulateRGB(&ab.lColor,
342 1, // multiplier
343 2); // divisor
344
345 // set the area (fill) color
346 GpiSetAttrs(hpsMem,
347 PRIM_AREA,
348 ABB_COLOR,
349 0,
350 (PBUNDLE)&ab);
351
352 GpiSetCurrentPosition(hpsMem, &ptlCenter);
353
354 // round the angle values properly
355 sStartAngle = (SHORT)(dStartAngle + .5);
356 sSweepAngle = (SHORT)(dSweepAngle + .5);
357
358 // now draw the pie slice;
359 // we could use an area, but since we need
360 // to remember the coordinates of the slice
361 // for mouse click handling, we require a
362 // region. But areas cannot be converted
363 // to regions, so we use a path instead.
364 GpiBeginPath(hpsMem,
365 1); // path ID, must be 1
366 GpiPartialArc(hpsMem,
367 &ptlCenter,
368 fxPieSize, // calculated from CHARTSTYLE
369 MAKEFIXED(sStartAngle, 0),
370 MAKEFIXED(sSweepAngle, 0));
371 // this moves the current position to the outer
372 // point on the ellipse which corresponds to
373 // sSweepAngle
374 GpiEndPath(hpsMem);
375
376 // convert the path to a region;
377 // we'll need the region for mouse hit testing later
378 hrgnThis = GpiPathToRegion(hpsMem, 1,
379 FPATH_ALTERNATE);
380 // after this, the path is deleted
381 GpiPaintRegion(hpsMem, hrgnThis);
382 if (phRegionThis)
383 // region output requested by caller:
384 // store region, the caller will clean this up
385 *phRegionThis = hrgnThis;
386 else
387 // drop region
388 GpiDestroyRegion(hpsMem, hrgnThis);
389
390 // descriptions enabled and last run?
391 if ( (ppszDescriptionThis)
392 && (fNowDrawingSurface)
393 )
394 {
395 if (*ppszDescriptionThis)
396 {
397 // yes: calculate position to paint
398 // text at later (we can't do this now,
399 // because it might be overpainted by
400 // the next arc again)
401
402 GpiSetCurrentPosition(hpsMem, &ptlCenter);
403 // move the current position to
404 // the center outer point on the ellipse
405 // (in between sStartAngle and sSweepAngle);
406 // since we're outside an area, this will not
407 // paint anything
408 GpiPartialArc(hpsMem,
409 &ptlCenter,
410 fxDescriptions, // calculated from CHARTSTYLE
411 MAKEFIXED(sStartAngle, 0),
412 // only half the sweep now:
413 MAKEFIXED(sSweepAngle / 2, 0));
414
415 // store this outer point in the array
416 // of description coordinates for later
417 GpiQueryCurrentPosition(hpsMem, pptlDescriptionThis);
418 }
419 }
420
421 // increase the start angle by the sweep angle for next loop
422 dStartAngle += dSweepAngle;
423
424 // advance the data pointers
425 pdThis++;
426 plColorThis++;
427 if (ppszDescriptionThis)
428 ppszDescriptionThis++;
429 pptlDescriptionThis++;
430 if (phRegionThis)
431 phRegionThis++;
432 } // end for (ul...)
433
434 // go for next "3D thickness" iteration
435 ulYBottomNow++;
436 } while ( (pChartStyle->ulStyle & CHS_3D_BRIGHT)
437 && (ulYBottomNow < pChartStyle->ulThickness)
438 );
439
440 // now paint descriptions
441 if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
442 {
443 // we use two pointers during the iteration,
444 // which point to the item corresponding
445 // to the current data item:
446 // 1) pointer to center point on outer border
447 // of partial arc
448 // (calculated above)
449 PPOINTL pptlDescriptionThis = paptlDescriptions;
450 // 2) pointer to current description string
451 PSZ* ppszDescriptionThis = pChartData->papszDescriptions;
452
453 // description strings valid?
454 if (ppszDescriptionThis)
455 {
456 // set presentation color
457 GpiSetColor(hpsMem, lTextColor);
458
459 // set text aligment to centered
460 // both horizontally and vertically;
461 // this affects subsequent GpiCharStringAt
462 // calls in that the output text will
463 // be centered around the specified
464 // point
465 GpiSetTextAlignment(hpsMem,
466 TA_CENTER, // horizontally
467 TA_HALF); // center vertically
468
469 // loop thru data items
470 for (ul = 0;
471 ul < pChartData->cValues;
472 ul++)
473 {
474 POINTL ptlMiddlePoint;
475
476 // when drawing the arcs above, we have,
477 // for each pie slice, stored the middle
478 // point on the outer edge of the ellipse
479 // in the paptlDescriptions POINTL array:
480
481 // ++++
482 // + +
483 // + +
484 // ptlCenter\ + +
485 // \ + + <-- current partial arc
486 // \+ +
487 // +++++++++++X +
488 // + +
489 // + +
490 // + XX <-- point calculated above
491 // + +
492 // + +
493 // ++++++
494
495 // now calculate a middle point between
496 // that outer point on the ellipse and
497 // the center of the ellipse, which will
498 // be the center point for the text
499
500 // ++++
501 // + +
502 // + +
503 // ptlCenter\ + +
504 // \ + + <-- current partial arc
505 // \+ +
506 // ++++++++++++ +
507 // + XX + <-- new middle point
508 // + +
509 // + XX <-- point calculated above
510 // + +
511 // + +
512 // ++++++
513
514 ptlMiddlePoint.x =
515 ptlCenter.x
516 + ((pptlDescriptionThis->x - ptlCenter.x) * 2 / 3);
517 ptlMiddlePoint.y =
518 ptlCenter.y
519 - (ptlCenter.y - pptlDescriptionThis->y) * 2 / 3;
520
521 // FINALLY, draw the description
522 // at this point; since we have used
523 // GpiSetTextAlignment above, the
524 // text will be centered on exactly
525 // that point
526 GpiCharStringAt(hpsMem,
527 &ptlMiddlePoint,
528 strlen(*ppszDescriptionThis),
529 *ppszDescriptionThis);
530
531 pptlDescriptionThis++;
532 ppszDescriptionThis++;
533 } // end for (ul = 0; ul < pChartData->cValues; ul++)
534 } // end if (ppszDescriptionThis)
535 } // end if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
536
537 // cleanup
538 free(paptlDescriptions);
539
540 // deselect (free) bitmap
541 GpiSetBitmap(hpsMem, NULLHANDLE);
542 } // end if (pChtCData->hbmChart ...)
543 } // end if (dTotal > 0)
544
545 return (hbmReturn);
546}
547
548/*
549 *@@ CleanupBitmap:
550 * this frees the resources associated with
551 * the chart bitmap.
552 *
553 *@@changed V0.9.2 (2000-02-29) [umoeller]: fixed maaajor memory leak
554 */
555
556VOID CleanupBitmap(PCHARTCDATA pChtCData)
557{
558 if (pChtCData)
559 {
560 // bitmap already created?
561 if (pChtCData->hbmChart)
562 {
563 // free current bitmap
564 // GpiSetBitmap(pChtCData->hpsMem, NULLHANDLE);
565 // delete bitmap; fails if not freed!
566 GpiDeleteBitmap(pChtCData->hbmChart);
567 pChtCData->hbmChart = NULLHANDLE;
568 }
569
570 // destroy regions, but not the array itself
571 // (this is done in CleanupData)
572 if (pChtCData->paRegions)
573 {
574 ULONG ul;
575 HRGN *phRegionThis = pChtCData->paRegions;
576 for (ul = 0;
577 ul < pChtCData->cd.cValues;
578 ul++)
579 {
580 if (*phRegionThis)
581 {
582 GpiDestroyRegion(pChtCData->hpsMem, *phRegionThis);
583 *phRegionThis = NULLHANDLE;
584 }
585 phRegionThis++;
586 }
587 }
588 }
589}
590
591/*
592 *@@ CleanupData:
593 * this frees all allocated resources of the chart
594 * control, except the bitmap (use CleanupBitmap
595 * for that) and the CHARTCDATA itself.
596 *
597 * Note: CleanupBitmap must be called _before_ this
598 * function.
599 */
600
601VOID CleanupData(PCHARTCDATA pChtCData)
602{
603 if (pChtCData)
604 if (pChtCData->cd.cValues)
605 {
606 // _Pmpf(("Cleaning up data"));
607 // values array
608 if (pChtCData->cd.padValues)
609 free(pChtCData->cd.padValues);
610
611 // colors array
612 if (pChtCData->cd.palColors)
613 free(pChtCData->cd.palColors);
614
615 // strings array
616 if (pChtCData->cd.papszDescriptions)
617 {
618 ULONG ul;
619 PSZ *ppszDescriptionThis = pChtCData->cd.papszDescriptions;
620 for (ul = 0;
621 ul < pChtCData->cd.cValues;
622 ul++)
623 {
624 if (*ppszDescriptionThis)
625 free(*ppszDescriptionThis);
626
627 ppszDescriptionThis++;
628 }
629
630 free(pChtCData->cd.papszDescriptions);
631 }
632
633 pChtCData->cd.cValues = 0;
634
635 // GPI regions array
636 if (pChtCData->paRegions)
637 {
638 free(pChtCData->paRegions);
639 pChtCData->paRegions = NULL;
640 }
641 }
642}
643
644/*
645 *@@ SetChartData:
646 * implementation for CHTM_SETCHARTDATA
647 * in ctl_fnwpChart.
648 *
649 *@@added V0.9.2 (2000-02-29) [umoeller]
650 */
651
652VOID SetChartData(HWND hwndChart,
653 PCHARTCDATA pChtCData,
654 PCHARTDATA pcdNew)
655{
656 ULONG ul = 0;
657 PSZ *ppszDescriptionSource,
658 *ppszDescriptionTarget;
659
660 // free previous values, if set
661 if (pChtCData->hbmChart)
662 CleanupBitmap(pChtCData);
663
664 CleanupData(pChtCData);
665
666 // _Pmpf(("Setting up data"));
667
668 if (pChtCData->hpsMem == NULLHANDLE)
669 {
670 // first call:
671 // create a memory PS for the bitmap
672 SIZEL szlPage = {0, 0};
673 gpihCreateMemPS(WinQueryAnchorBlock(hwndChart),
674 &szlPage,
675 &pChtCData->hdcMem,
676 &pChtCData->hpsMem);
677 // _Pmpf(("Created HPS 0x%lX", pChtCData->hpsMem));
678 // _Pmpf(("Created HDC 0x%lX", pChtCData->hdcMem));
679 }
680
681 pChtCData->cd.usStartAngle = pcdNew->usStartAngle;
682 pChtCData->cd.usSweepAngle = pcdNew->usSweepAngle;
683 pChtCData->cd.cValues = pcdNew->cValues;
684
685 // copy values
686 pChtCData->cd.padValues = (double*)malloc(sizeof(double) * pcdNew->cValues);
687 memcpy(pChtCData->cd.padValues,
688 pcdNew->padValues,
689 sizeof(double) * pcdNew->cValues);
690
691 // copy colors
692 pChtCData->cd.palColors = (LONG*)malloc(sizeof(LONG) * pcdNew->cValues);
693 memcpy(pChtCData->cd.palColors,
694 pcdNew->palColors,
695 sizeof(LONG) * pcdNew->cValues);
696
697 // copy strings
698 pChtCData->cd.papszDescriptions = (PSZ*)malloc(sizeof(PSZ) * pcdNew->cValues);
699 ppszDescriptionSource = pcdNew->papszDescriptions;
700 ppszDescriptionTarget = pChtCData->cd.papszDescriptions;
701 for (ul = 0;
702 ul < pcdNew->cValues;
703 ul++)
704 {
705 if (*ppszDescriptionSource)
706 *ppszDescriptionTarget = strdup(*ppszDescriptionSource);
707 else
708 *ppszDescriptionTarget = NULL;
709 ppszDescriptionSource++;
710 ppszDescriptionTarget++;
711 }
712
713 // create an array of GPI region handles
714 pChtCData->paRegions = (HRGN*)malloc(sizeof(HRGN) * pcdNew->cValues);
715 // initialize all regions to null
716 memset(pChtCData->paRegions, 0, sizeof(HRGN) * pcdNew->cValues);
717
718 pChtCData->lSelected = -1; // none selected
719
720 WinInvalidateRect(hwndChart, NULL, FALSE);
721}
722
723/*
724 *@@ PaintChart:
725 * implementation for WM_PAINT
726 * in ctl_fnwpChart.
727 *
728 *@@added V0.9.2 (2000-02-29) [umoeller]
729 */
730
731VOID PaintChart(HWND hwndChart,
732 PCHARTCDATA pChtCData,
733 HPS hps,
734 PRECTL prclPaint)
735{
736 RECTL rclStatic;
737 WinQueryWindowRect(hwndChart, &rclStatic);
738
739 // _Pmpf(("ctl_fnwpChart: WM_PAINT, cValues: %d", pChtCData->cd.cValues));
740
741 // do we have any values yet?
742 if (pChtCData->cd.cValues == 0)
743 {
744 CHAR szDebug[200];
745 sprintf(szDebug, "Error, no values set");
746 WinFillRect(hps,
747 &rclStatic, // exclusive
748 CLR_WHITE);
749 WinDrawText(hps,
750 strlen(szDebug),
751 szDebug,
752 &rclStatic,
753 CLR_BLACK,
754 CLR_WHITE,
755 DT_LEFT | DT_TOP);
756 }
757 else
758 {
759 // valid values, apparently:
760 LONG lForegroundColor = winhQueryPresColor(hwndChart,
761 PP_FOREGROUNDCOLOR,
762 TRUE, // inherit presparams
763 SYSCLR_WINDOWTEXT);
764
765 // yes: check if we created the bitmap
766 // already
767 if (pChtCData->hbmChart == NULLHANDLE)
768 {
769 // no: do it now
770 HPOINTER hptrOld = winhSetWaitPointer();
771
772 // get presentation font
773 FONTMETRICS FontMetrics;
774 LONG lPointSize;
775 LONG lLCIDSet = gpihFindPresFont(hwndChart,
776 TRUE, // inherit PP
777 pChtCData->hpsMem,
778 "8.Helv",
779 &FontMetrics,
780 &lPointSize);
781 // set presentation font
782 if (lLCIDSet)
783 {
784 GpiSetCharSet(pChtCData->hpsMem, lLCIDSet);
785 if (FontMetrics.fsDefn & FM_DEFN_OUTLINE)
786 gpihSetPointSize(pChtCData->hpsMem, lPointSize);
787 }
788
789
790 gpihSwitchToRGB(hps);
791
792 pChtCData->hbmChart = ctlCreateChartBitmap(
793 pChtCData->hpsMem, // mem PS
794 rclStatic.xRight, // cx
795 rclStatic.yTop, // cy
796 &pChtCData->cd, // data
797 &pChtCData->cs, // style
798 // background color:
799 winhQueryPresColor(hwndChart,
800 PP_BACKGROUNDCOLOR,
801 TRUE, // inherit presparams
802 SYSCLR_DIALOGBACKGROUND),
803 // description text color:
804 lForegroundColor,
805 pChtCData->paRegions);
806 // out: regions array
807 // _Pmpf(("Created bitmap 0x%lX", pChtCData->hbmChart));
808
809 // unset and delete font
810 GpiSetCharSet(pChtCData->hpsMem, LCID_DEFAULT);
811 if (lLCIDSet)
812 GpiDeleteSetId(pChtCData->hpsMem, lLCIDSet);
813
814 WinSetPointer(HWND_DESKTOP, hptrOld);
815 }
816
817 if (pChtCData->hbmChart)
818 {
819 POINTL ptlDest = { 0, 0 };
820 WinDrawBitmap(hps,
821 pChtCData->hbmChart,
822 NULL, // draw whole bitmap
823 &ptlDest,
824 0, 0, // colors (don't care)
825 DBM_NORMAL);
826
827 // do we have the focus?
828 if (pChtCData->fHasFocus)
829 // something selected?
830 if (pChtCData->lSelected != -1)
831 if (pChtCData->paRegions)
832 {
833 HRGN* pRegionThis = pChtCData->paRegions; // first region
834 pRegionThis += pChtCData->lSelected; // array item
835
836 if (*pRegionThis)
837 {
838 SIZEL sl = {2, 2};
839 GpiSetColor(hps, lForegroundColor);
840 GpiFrameRegion(hps,
841 *pRegionThis,
842 &sl);
843 }
844 }
845 }
846 }
847}
848
849/*
850 *@@ ctl_fnwpChart:
851 * window procedure for the "chart" control.
852 *
853 * This is not a stand-alone window procedure, but must only
854 * be used with static controls subclassed by ctlChartFromStatic.
855 *
856 *@@added V0.9.0 [umoeller]
857 *@@changed V0.9.2 (2000-02-29) [umoeller]: added resize support
858 *@@changed V0.9.2 (2000-02-29) [umoeller]: fixed baaad PM resource leaks, the bitmap was never freed
859 */
860
861MRESULT EXPENTRY ctl_fnwpChart(HWND hwndChart, ULONG msg, MPARAM mp1, MPARAM mp2)
862{
863 MRESULT mrc = 0;
864 PCHARTCDATA pChtCData = (PCHARTCDATA)WinQueryWindowULong(hwndChart, QWL_USER);
865
866 if (pChtCData)
867 {
868 PFNWP OldStaticProc = pChtCData->OldStaticProc;
869
870 switch (msg)
871 {
872 /*
873 *@@ CHTM_SETCHARTDATA:
874 * user msg to set the pie chart data,
875 * which is copied to the control's
876 * memory.
877 * This can be sent several times to
878 * have the chart refreshed with new
879 * values.
880 *
881 * Parameters:
882 * -- PCHARTDATA mp1: new values.
883 * These will be copied to the chart's internal data.
884 * -- mp2: unused.
885 */
886
887 case CHTM_SETCHARTDATA:
888 // _Pmpf(("CHTM_SETCHARTDATA, mp1: 0x%lX", mp1));
889 if (mp1)
890 {
891 PCHARTDATA pcdNew = (PCHARTDATA)mp1;
892 SetChartData(hwndChart, pChtCData, pcdNew);
893 }
894 break;
895
896 /*
897 *@@ CHTM_SETCHARTSTYLE:
898 * user msg to set the chart style,
899 * which is copied to the control's
900 * memory.
901 * This can be sent several times to
902 * have the chart refreshed with new
903 * styles.
904 *
905 * Parameters:
906 * -- PCHARTSTYLE mp1: new style data.
907 * This will be copied to the chart's internal data.
908 * -- mp2: unused.
909 */
910
911 case CHTM_SETCHARTSTYLE:
912 // _Pmpf(("CHTM_SETCHARTSTYLE, mp1: 0x%lX", mp1));
913 memcpy(&(pChtCData->cs), mp1, sizeof(CHARTSTYLE));
914 if (pChtCData->hbmChart)
915 {
916 CleanupBitmap(pChtCData);
917 WinInvalidateRect(hwndChart, NULL, FALSE);
918 }
919 break;
920
921 /*
922 *@@ CHTM_ITEMFROMPOINT:
923 * this can be _sent_ to the chart control
924 * to query the chart item which surrounds
925 * the given point. Specify the coordinates
926 * in two SHORT's in mp1 (as with WM_BUTTON1*
927 * messages), in window coordinates.
928 *
929 * Parameters:
930 * -- SHORT SHORT1FROMMP(mp1): x
931 * -- SHORT SHORT1FROMMP(mp1): y
932 * -- mp2: unused.
933 *
934 * Returns:
935 * -- LONG: data item index (counting from
936 * 0) or -1 if none, e.g. if no
937 * data has been set yet or if
938 * the point is outside the pie
939 * slices.
940 *
941 *@@added V0.9.2 (2000-02-29) [umoeller]
942 */
943
944 case CHTM_ITEMFROMPOINT:
945 {
946 LONG lRegionFound = -1; // none
947
948 // get mouse coordinates
949 POINTL ptlMouse;
950 ptlMouse.x = SHORT1FROMMP(mp1);
951 ptlMouse.y = SHORT2FROMMP(mp1);
952
953 // data set?
954 if (pChtCData->cd.cValues)
955 {
956 // regions defined?
957 if (pChtCData->paRegions)
958 {
959 ULONG ul;
960 HRGN* phRegionThis = pChtCData->paRegions;
961 for (ul = 0;
962 ul < pChtCData->cd.cValues;
963 ul++)
964 {
965 if (*phRegionThis)
966 {
967 if (GpiPtInRegion(pChtCData->hpsMem,
968 *phRegionThis,
969 &ptlMouse)
970 == PRGN_INSIDE)
971 {
972 // _Pmpf(("Clicked in region %d", ul));
973 lRegionFound = ul;
974 break;
975 }
976 }
977 phRegionThis++;
978 }
979
980 }
981 }
982
983 mrc = (MPARAM)lRegionFound;
984 break; }
985
986 /*
987 * WM_BUTTON1DOWN:
988 *
989 */
990
991 case WM_BUTTON1DOWN:
992 if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
993 {
994 LONG lRegionFound = (LONG)WinSendMsg(hwndChart,
995 CHTM_ITEMFROMPOINT,
996 mp1,
997 NULL);
998
999 // selections allowed:
1000 // we then accept WM_CHAR msgs, so
1001 // we need the focus
1002 WinSetFocus(HWND_DESKTOP, hwndChart);
1003 // this invalidates the window
1004 if (pChtCData->lSelected != lRegionFound)
1005 {
1006 // selection changed:
1007 pChtCData->lSelected = lRegionFound;
1008 // repaint
1009 WinInvalidateRect(hwndChart, NULL, FALSE);
1010 }
1011 }
1012 else
1013 // no selections allowed:
1014 // just activate the window
1015 WinSetActiveWindow(HWND_DESKTOP, hwndChart);
1016 // the parent frame gets activated this way
1017 mrc = (MPARAM)TRUE;
1018 break;
1019
1020 /*
1021 * WM_SETFOCUS:
1022 * we might need to redraw the selection.
1023 */
1024
1025 case WM_SETFOCUS:
1026 if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
1027 {
1028 // selections allowed:
1029 pChtCData->fHasFocus = (BOOL)mp2;
1030 if (pChtCData->lSelected != -1)
1031 WinInvalidateRect(hwndChart, NULL, FALSE);
1032 }
1033 break;
1034
1035 /*
1036 * WM_WINDOWPOSCHANGED:
1037 *
1038 */
1039
1040 case WM_WINDOWPOSCHANGED:
1041 {
1042 // this msg is passed two SWP structs:
1043 // one for the old, one for the new data
1044 // (from PM docs)
1045 PSWP pswpNew = (PSWP)mp1;
1046 // PSWP pswpOld = pswpNew + 1;
1047
1048 // resizing?
1049 if (pswpNew->fl & SWP_SIZE)
1050 if (pChtCData->hbmChart)
1051 {
1052 // invalidate bitmap so that
1053 // it will be recreated with new size
1054 CleanupBitmap(pChtCData);
1055 WinInvalidateRect(hwndChart, NULL, FALSE);
1056 }
1057
1058 // return default NULL
1059 break; }
1060
1061 /*
1062 * WM_PRESPARAMCHANGED:
1063 *
1064 */
1065
1066 case WM_PRESPARAMCHANGED:
1067 if (pChtCData->hbmChart)
1068 {
1069 // invalidate bitmap so that
1070 // it will be recreated with new
1071 // fonts and colors
1072 CleanupBitmap(pChtCData);
1073 WinInvalidateRect(hwndChart, NULL, FALSE);
1074 }
1075 break;
1076
1077 /*
1078 * WM_PAINT:
1079 * paint the chart bitmap, which is created
1080 * if necessary (calling ctlCreateChartBitmap).
1081 */
1082
1083 case WM_PAINT:
1084 {
1085 RECTL rclPaint;
1086 HPS hps = WinBeginPaint(hwndChart,
1087 NULLHANDLE, // obtain cached micro-PS
1088 &rclPaint);
1089
1090 PaintChart(hwndChart,
1091 pChtCData,
1092 hps,
1093 &rclPaint);
1094
1095 WinEndPaint(hps);
1096 mrc = 0;
1097 break; }
1098
1099 /*
1100 * WM_DESTROY:
1101 * clean up resources
1102 */
1103
1104 case WM_DESTROY:
1105 CleanupBitmap(pChtCData);
1106 CleanupData(pChtCData);
1107
1108 // _Pmpf(("Destroying HPS 0x%lX", pChtCData->hpsMem));
1109 if (!GpiDestroyPS(pChtCData->hpsMem));
1110 // _Pmpf((" Error!"));
1111 // _Pmpf(("Destroying HDC 0x%lX", pChtCData->hdcMem));
1112 if (!DevCloseDC(pChtCData->hdcMem));
1113 // _Pmpf((" Error!"));
1114 free(pChtCData);
1115
1116 mrc = (*OldStaticProc)(hwndChart, msg, mp1, mp2);
1117 break;
1118
1119 default:
1120 mrc = (*OldStaticProc)(hwndChart, msg, mp1, mp2);
1121 }
1122 }
1123
1124 return (mrc);
1125}
1126
1127/*
1128 *@@ ctlChartFromStatic:
1129 * this function turns an existing static text control into
1130 * a chart control (for visualizing data) by subclassing its
1131 * window procedure with ctl_fnwpChart.
1132 *
1133 * This way you can easily create a chart control as a static
1134 * control in any Dialog Editor;
1135 * after loading the dlg template, simply call this function
1136 * with the hwnd of the static control to make it a chart.
1137 *
1138 * The pie chart consumes all available space in the static control.
1139 *
1140 * In XWorkplace, this is used for the pie chart on the new
1141 * XFldDisk "Details" settings page to display the free space
1142 * on a certain drive.
1143 *
1144 * Note: even though you can use _any_ type of static control
1145 * with this function, you should use a static _text_ control,
1146 * because not all types of static controls react to fonts and
1147 * colors dragged upon them. The static _text_ control does.
1148 *
1149 * <B>Chart data:</B>
1150 *
1151 * The pie chart control operates on an array of "double" values.
1152 * Each value in that array corresponds to a color in a second
1153 * array of (LONG) RGB values and, if description texts are
1154 * enabled, to a third array of PSZ's.
1155 *
1156 * The data on which the pie chart operates is initialized to
1157 * be void, so that the pie chart will not paint anything
1158 * initially. In order to have the pie chart display something,
1159 * post or send a CHTM_SETCHARTDATA message (comctl.h) to the static
1160 * control after it has been subclassed.
1161 *
1162 * CHTM_SETCHARTDATA takes a CHARTDATA structure (comctl.h) in mp1,
1163 * which must contain the chart data and corresponding colors to be
1164 * displayed.
1165 *
1166 * The total sum of the "double" values will represent the angle in
1167 * CHARTDATA.usSweepAngle.
1168 *
1169 * For example, if two values of 50 and 100 are passed to the
1170 * control and usSweepAngle is 270 (i.e. a three-quarter pie),
1171 * the chart control will calculate the following:
1172 *
1173 * 1) The sum of the data is 150.
1174 *
1175 * 2) The first sub-arc will span an angle of 270 * (50/150)
1176 * = 90 degrees.
1177 *
1178 * 3) The second sub-arc will span an angle of 270 * (100/150)
1179 * = 180 degrees.
1180 *
1181 * You can also have descriptions displayed along the different
1182 * chart items by specifying CHARTDATA.papszDescriptions and
1183 * setting the CHS_DESCRIPTIONS flag (below).
1184 *
1185 * <B>Chart styles:</B>
1186 *
1187 * Use CHTM_SETCHARTSTYLE with a PCHARTSTYLE (comctl.h) in mp1.
1188 * This can be sent to the chart control several times.
1189 *
1190 * Presently, only pie charts are implemented. However, we do
1191 * have several "sub-styles" for pie charts:
1192 * -- CHS_3D_BRIGHT: paint a "3D" socket below the actual chart.
1193 * -- CHS_3D_DARKEN: like CHS_3D_BRIGHT, but the socket will be made
1194 * darker compared to the surface.
1195 *
1196 * General styles:
1197 * -- CHS_DESCRIPTIONS: show descriptions on the chart
1198 * (CHARTDATA.papszDescriptions data).
1199 * -- CHS_SELECTIONS: allow pie chart slices to be selectable,
1200 * using the mouse and the keyboard.
1201 *
1202 * <B>Display:</B>
1203 *
1204 * The chart control creates an internal bitmap for the display
1205 * only once (ctlCreateChartBitmap). This bitmap is refreshed if
1206 * neccessary, e.g. because chart data or styles have changed.
1207 *
1208 * The chart control uses presentation parameters, as listed below.
1209 * Presentation parameters are inherited from the parent window.
1210 * If a presparam is not set, the corresponding system color is
1211 * used. The following color pairs are recognized:
1212 *
1213 * -- PP_BACKGROUNDCOLOR / SYSCLR_DIALOGBACKGROUND:
1214 * background of the chart control (outside the chart).
1215 * -- PP_FOREGROUNDCOLOR / SYSCLR_WINDOWTEXT:
1216 * text color, if description texts are enabled and valid.
1217 * -- PP_FONTNAMESIZE:
1218 * text font, if description texts are enabled and valid.
1219 * If this presparam is not set, the system font is used.
1220 *
1221 * The control reacts to fonts and colors dropped upon it, if
1222 * it has been subclassed from a static _text_ control (see above).
1223 * It also recalculates the bitmap when it's resized.
1224 *
1225 * <B>Example usage:</B>
1226 *
1227 + // get static control:
1228 + HWND hwndChart = WinWindowFromID(hwndDialog, ID_...);
1229 + CHARTSTYLE cs;
1230 + CHARTDATA cd;
1231 + // define data:
1232 + double adData[3] = { 100, 200, 300 };
1233 + // define corresponding colors:
1234 + LONG alColors[3] = { 0x800000, 0x008000, 0x000080 };
1235 + // define correspdonding descriptions:
1236 + PSZ apszDescriptions[3] = { "item 1", "item 3", "item 3" };
1237 +
1238 + ctlChartFromStatic(hwndChart); // create chart
1239 +
1240 + cs.ulStyle = CHS_3D_DARKEN | CHS_DESCRIPTIONS;
1241 + cs.ulThickness = 20;
1242 + WinSendMsg(hwndChart, CHTM_SETCHARTSTYLE, &cs, NULL);
1243 +
1244 + cd.usStartAngle = 15; // start at 15ø from right
1245 + cd.usSweepAngle = 270; // three-quarter pie (for the sum of the
1246 + // above values: 100+200+300 = 600)
1247 + cd.cValues = 3; // array count
1248 + cd.padValues = &adData[0];
1249 + cd.palColors = &alColors[0];
1250 + cd.papszDescriptions = &apszDescriptions[0];
1251 + WinSendMsg(hwndChart, CHTM_SETCHARTDATA, &cd, NULL);
1252 *
1253 *@@added V0.9.0 [umoeller]
1254 */
1255
1256BOOL ctlChartFromStatic(HWND hwndChart) // in: static control to subclass
1257{
1258 if (hwndChart)
1259 {
1260 PFNWP OldStaticProc = WinSubclassWindow(hwndChart, ctl_fnwpChart);
1261
1262 if (OldStaticProc)
1263 {
1264 PCHARTCDATA pChtCData = (PCHARTCDATA)malloc(sizeof(CHARTCDATA));
1265 memset(pChtCData, 0, sizeof(CHARTCDATA));
1266 pChtCData->OldStaticProc = OldStaticProc;
1267 pChtCData->fHasFocus = FALSE;
1268 WinSetWindowPtr(hwndChart, QWL_USER, pChtCData);
1269 return (TRUE);
1270 }
1271 }
1272
1273 return (FALSE);
1274}
1275
1276
Note: See TracBrowser for help on using the repository browser.