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

Last change on this file since 80 was 68, checked in by umoeller, 24 years ago

Lotsa fixes from the last two weeks.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 67.7 KB
Line 
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) == (sizeSlize / 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-2000 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
251typedef 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
283VOID 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
356VOID 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
559VOID 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?!? This might be a most
654 // flexible way to do things, but where's the
655 // simple stuff?!?)
656 ap.lP = ptlCenter.x; // X-axis X
657 ap.lS = 0; // X-axis Y
658 ap.lR = 0; // Y-axis X
659 ap.lQ = ((rclArc.yTop - rclArc.yBottom) / 2);
660 // Y-axis Y
661 GpiSetArcParams(hpsMem, &ap);
662
663 // b) The line primitives determine lines
664 // to be drawn around the pie slices.
665 // We don't want any.
666 GpiSetLineType(hpsMem, LINETYPE_INVISIBLE);
667
668 // c) Strangely, GpiSetPattern does work,
669 // while GpiSetColor doesn't (see below).
670 GpiSetPattern(hpsMem, PATSYM_SOLID);
671
672 // initialize per-item data pointers for
673 // loop below
674 pdValueThis = pChartData->padValues;
675 plColorThis = pChartData->palColors;
676 ppszDescriptionThis = pChartData->papszDescriptions;
677 pptlDescriptionThis = paptlDescriptions;
678 phRegionThis = paRegions;
679
680 // 2) inner "pie slice loop":
681 // this loop goes over the data pointers
682 // and paints accordingly. At the end of
683 // the loop, we'll advance all those pointers.
684 for (ul = 0;
685 ul < pChartData->cValues;
686 ul++)
687 {
688 HRGN hrgnThis;
689 double dSweepAngleRounded,
690 dStartAngleRounded;
691
692 // calculate the angle to sweep to:
693 // a simple rule of three
694 dSweepAngle = *pdValueThis // current data pointer
695 * (double)pChartData->usSweepAngle
696 // maximum angle
697 / dTotal; // total data sum
698
699 // d) And now comes the real fun part.
700 // GpiPartialArc is too dumb to draw
701 // anything on its own, it must _always_
702 // appear within an area or path definition.
703 // Unfortunately, this isn't really said
704 // clearly anywhere.
705 // Even worse, in order to set the color
706 // with which the slice is to be drawn,
707 // one has to define an AREABUNDLE because
708 // the regular GpiSetColor functions don't
709 // seem to work here. Or maybe it's my fault,
710 // but with this awful documentation, who knows.
711
712 // We set the current color in the AREABUNDLE
713 // from the color which was defined in the
714 // pie chart data (this pointer was set above
715 // and will be advanced for the next slice).
716 ab.lColor = *plColorThis;
717
718 // "3D mode" enabled with darkened socket?
719 if ( (pChartStyle->ulStyle & CHS_3D_DARKEN)
720 // not last loop?
721 && (!fNowDrawingSurface)
722 )
723 // darken the current fill color
724 // by halving each color component
725 gpihManipulateRGB(&ab.lColor,
726 .5); // factor
727
728 // set the area (fill) color
729 GpiSetAttrs(hpsMem,
730 PRIM_AREA,
731 ABB_COLOR,
732 0,
733 (PBUNDLE)&ab);
734
735 GpiSetCurrentPosition(hpsMem, &ptlCenter);
736
737 // round the angle values properly
738 dStartAngleRounded = (dStartAngle + .5);
739 dSweepAngleRounded = (dSweepAngle + .5);
740
741 // now draw the pie slice;
742 // we could use an area, but since we need
743 // to remember the coordinates of the slice
744 // for mouse click handling, we require a
745 // region. But areas cannot be converted
746 // to regions, so we use a path instead.
747 GpiBeginPath(hpsMem,
748 1); // path ID, must be 1
749
750 // note that the arc functions use the FIXED type
751 // for representing fractions... GPI was written back
752 // at the days when coprocessors were a luxury, so
753 // a floating point value is represented by a LONG
754 // with the hiword containing the rounded integer
755 // and the loword the remaining fraction. So we
756 // can simply multiply the "double" by 65536, and
757 // we get the FIXED value V0.9.12 (2001-05-03) [umoeller]
758 GpiPartialArc(hpsMem,
759 &ptlCenter,
760 fxPieSize, // calculated from CHARTSTYLE
761 (FIXED)(dStartAngleRounded * (double)65536),
762 (FIXED)(dSweepAngleRounded * (double)65536));
763 // this moves the current position to the outer
764 // point on the ellipse which corresponds to
765 // sSweepAngle
766 GpiEndPath(hpsMem);
767
768 // convert the path to a region;
769 // we'll need the region for mouse hit testing later
770 hrgnThis = GpiPathToRegion(hpsMem, 1,
771 FPATH_ALTERNATE);
772 // after this, the path is deleted
773
774 // last run (are we painting the top now)?
775 if ( (fNowDrawingSurface)
776 // descriptions enabled?
777 && (ppszDescriptionThis)
778 && (*ppszDescriptionThis)
779 )
780 {
781 // yes: calculate position to paint
782 // text at later (we can't do this now,
783 // because it might be overpainted by
784 // the next arc again)
785
786 GpiSetCurrentPosition(hpsMem, &ptlCenter);
787 // move the current position to
788 // the center outer point on the ellipse
789 // (in between sStartAngle and sSweepAngle);
790 // since we're outside an area, this will not
791 // paint anything
792 GpiPartialArc(hpsMem,
793 &ptlCenter,
794 fxDescriptions, // calculated from CHARTSTYLE
795 (FIXED)(dStartAngleRounded * (double)65536),
796 // only half the sweep now:
797 (FIXED)((dSweepAngleRounded / 2) * (double)65536));
798
799 // store this outer point in the array
800 // of description coordinates for later
801 GpiQueryCurrentPosition(hpsMem, pptlDescriptionThis);
802
803 _Pmpf(("pptlDescriptionThis = %d, %d",
804 pptlDescriptionThis->x,
805 pptlDescriptionThis->y));
806 }
807
808 // now, this FINALLY paints the arc
809 GpiPaintRegion(hpsMem, hrgnThis);
810
811 // last run (are we painting the top now)?
812 if ( (fNowDrawingSurface)
813 // if separator lines are enabled,
814 // draw a frame around the thing
815 && (pChartStyle->ulStyle & CHS_DRAWLINES)
816 )
817 {
818 GpiSetColor(hpsMem, lTextColor);
819 GpiFrameRegion(hpsMem,
820 hrgnThis,
821 (PSIZEL)&cszlDrawLines); // always (1, 1)
822 }
823
824 if ( phRegionThis
825 && fNowDrawingSurface // this was missing
826 // V0.9.12 (2001-05-03) [umoeller]
827 // this created tons of regions which
828 // were never deleted
829 )
830 // region output requested by caller:
831 // store region, the caller will clean this up
832 *phRegionThis = hrgnThis;
833 else
834 // no region output requested: destroy region
835 GpiDestroyRegion(hpsMem, hrgnThis);
836
837 // increase the start angle by the sweep angle for next loop
838 dStartAngle += dSweepAngle;
839
840 // advance the data pointers
841 pdValueThis++;
842 plColorThis++;
843 if (ppszDescriptionThis)
844 ppszDescriptionThis++;
845 pptlDescriptionThis++;
846 if (phRegionThis)
847 phRegionThis++;
848 } // end for (ul...)
849
850 // go for next "3D thickness" iteration
851 ulYBottomNow++;
852 } while ( (pChartStyle->ulStyle & CHS_3D_BRIGHT)
853 && (ulYBottomNow < pChartStyle->ulThickness)
854 );
855
856 // now paint descriptions
857 if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
858 {
859 // we use two pointers during the iteration,
860 // which point to the item corresponding
861 // to the current data item:
862 // 1) pointer to center point on outer border
863 // of partial arc
864 // (calculated above)
865 PPOINTL pptlDescriptionThis = paptlDescriptions;
866 // 2) pointer to current description string
867 PSZ* ppszDescriptionThis = pChartData->papszDescriptions;
868
869 // description strings valid?
870 if (ppszDescriptionThis)
871 {
872 // set presentation color
873 GpiSetColor(hpsMem, lTextColor);
874
875 // set text aligment to centered
876 // both horizontally and vertically;
877 // this affects subsequent GpiCharStringAt
878 // calls in that the output text will
879 // be centered around the specified
880 // point
881 GpiSetTextAlignment(hpsMem,
882 TA_CENTER, // horizontally
883 TA_HALF); // center vertically
884
885 // loop thru data items
886 for (ul = 0;
887 ul < pChartData->cValues;
888 ul++)
889 {
890 POINTL ptlMiddlePoint;
891
892 // when drawing the arcs above, we have,
893 // for each pie slice, stored the middle
894 // point on the outer edge of the ellipse
895 // in the paptlDescriptions POINTL array:
896
897 // ++++
898 // + +
899 // + +
900 // ptlCenter\ + +
901 // \ + + <-- current partial arc
902 // \+ +
903 // +++++++++++X +
904 // + +
905 // + +
906 // + XX <-- point calculated above
907 // + +
908 // + +
909 // ++++++
910
911 // now calculate a middle point between
912 // that outer point on the ellipse and
913 // the center of the ellipse, which will
914 // be the center point for the text
915
916 // ++++
917 // + +
918 // + +
919 // ptlCenter\ + +
920 // \ + + <-- current partial arc
921 // \+ +
922 // ++++++++++++ +
923 // + XX + <-- new middle point
924 // + +
925 // + XX <-- point calculated above
926 // + +
927 // + +
928 // ++++++
929
930 ptlMiddlePoint.x =
931 ptlCenter.x
932 + ((pptlDescriptionThis->x - ptlCenter.x) * 2 / 3);
933 ptlMiddlePoint.y =
934 ptlCenter.y
935 - (ptlCenter.y - pptlDescriptionThis->y) * 2 / 3;
936
937 // FINALLY, draw the description
938 // at this point; since we have used
939 // GpiSetTextAlignment above, the
940 // text will be centered on exactly
941 // that point
942 DrawCenteredText(hpsMem,
943 &ptlMiddlePoint,
944 *ppszDescriptionThis);
945
946 pptlDescriptionThis++;
947 ppszDescriptionThis++;
948 } // end for (ul = 0; ul < pChartData->cValues; ul++)
949 } // end if (ppszDescriptionThis)
950 } // end if (pChartStyle->ulStyle & CHS_DESCRIPTIONS)
951
952 // cleanup
953 free(paptlDescriptions);
954}
955
956/*
957 *@@ ctlCreateChartBitmap:
958 * this creates a new bitmap and paints the
959 * chart into it. This bitmap will be used
960 * in WM_PAINT of ctl_fnwpChart to quickly paint
961 * the chart image.
962 *
963 * The bitmap will be created with the specified
964 * size. The chart will consume all available
965 * space in the bitmap. The returned bitmap is
966 * not selected into any presentation space.
967 *
968 * Note: Description text will be drawn with the
969 * current font specified for the memory PS, and
970 * with the current character cell box.
971 *
972 * This function gets called automatically from
973 * WM_PAINT if we determine that the bitmap for
974 * painting has not been created yet or has been
975 * invalidated (because data or styles changed).
976 *
977 * However, you can also use this function
978 * independently to have a chart bitmap created,
979 * wherever you might need it.
980 * You will then have to fill in the CHARTDATA
981 * and CHARTSTYLE structures before calling
982 * this function.
983 *
984 * If (paRegions != NULL), this function will
985 * create a GPI region for each data item. Each GPI
986 * region will then contain the outline of the
987 * corresponding pie chart slice. This allows
988 * you to easily relate coordinates to pie slieces,
989 * for example for mouse clicks.
990 *
991 * In that case, paRegions must point to an array
992 * of HRGN items, each of which will contain a
993 * region handle later. It is the responsibility
994 * of the caller to free the regions later, using
995 * GpiDestroyRegion on each region handle (with
996 * the hpsMem specified with this function).
997 *
998 * If you're not interested in the regions, set
999 * paRegions to NULL.
1000 *
1001 * This returns NULLHANDLE if an error occured.
1002 * This can mean the following:
1003 *
1004 * -- The data is invalid, because the total is 0.
1005 *
1006 * -- The bitmap could not be created (memory?).
1007 *
1008 *@@changed V0.9.12 (2001-05-03) [umoeller]: extracted PaintPieChart
1009 *@@changed V0.9.12 (2001-05-03) [umoeller]: added bar chart style
1010 */
1011
1012HBITMAP ctlCreateChartBitmap(HPS hpsMem, // in: memory PS to use for bitmap creation
1013 LONG lcx, // in: width of bitmap
1014 LONG lcy, // in: height of bitmap
1015 PCHARTDATA pChartData, // in: chart data
1016 PCHARTSTYLE pChartStyle, // in: chart style
1017 LONG lBackgroundColor, // in: color around the chart (RGB)
1018 LONG lTextColor, // in: description text color (RGB)
1019 HRGN* paRegions) // out: GPI regions for each data item
1020{
1021 HBITMAP hbmReturn = NULLHANDLE;
1022
1023 // sum up the values to get the total
1024 ULONG ul = 0;
1025 double dTotal = 0;
1026 double* pdThis = pChartData->padValues;
1027 for (ul = 0; ul < pChartData->cValues; ul++)
1028 {
1029 _Pmpf((" dThis is %d", (ULONG)(*pdThis))); // printf
1030
1031 dTotal += *pdThis;
1032 pdThis++;
1033 }
1034
1035 _Pmpf((__FUNCTION__ ": dTotal is %d", (ULONG)dTotal)); // printf
1036
1037 // avoid division by zero
1038 if (dTotal > 0)
1039 {
1040 RECTL rclWholeStatic;
1041 // get window rectangle (bottom left is 0, 0)
1042 rclWholeStatic.xLeft = 0;
1043 rclWholeStatic.yBottom = 0;
1044 rclWholeStatic.xRight = lcx;
1045 rclWholeStatic.yTop = lcy;
1046
1047 // create bitmap of that size
1048 if ((hbmReturn = gpihCreateBitmap(hpsMem,
1049 rclWholeStatic.xRight,
1050 rclWholeStatic.yTop)))
1051 {
1052 // successfully created:
1053
1054 // associate bitmap with memory PS
1055 GpiSetBitmap(hpsMem, hbmReturn);
1056
1057 // switch the HPS to RGB mode
1058 gpihSwitchToRGB(hpsMem);
1059
1060 // fill bitmap with static's background color
1061 GpiSetColor(hpsMem, lBackgroundColor);
1062 gpihBox(hpsMem,
1063 DRO_FILL,
1064 &rclWholeStatic);
1065
1066 if (pChartStyle->ulStyle & CHS_BARCHART)
1067 PaintBarChart(hpsMem,
1068 &rclWholeStatic,
1069 pChartData,
1070 pChartStyle,
1071 dTotal,
1072 lTextColor,
1073 paRegions);
1074 else
1075 PaintPieChart(hpsMem,
1076 &rclWholeStatic,
1077 pChartData,
1078 pChartStyle,
1079 dTotal,
1080 lTextColor,
1081 paRegions);
1082
1083 // deselect (free) bitmap
1084 GpiSetBitmap(hpsMem, NULLHANDLE);
1085 } // end if (pChtCData->hbmChart ...)
1086 } // end if (dTotal > 0)
1087
1088 return (hbmReturn);
1089}
1090
1091/*
1092 *@@ CleanupBitmap:
1093 * this frees the resources associated with
1094 * the chart bitmap.
1095 *
1096 *@@changed V0.9.2 (2000-02-29) [umoeller]: fixed maaajor memory leak
1097 *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed major PM resource leaks
1098 */
1099
1100VOID CleanupBitmap(PCHARTCDATA pChtCData)
1101{
1102 if (pChtCData)
1103 {
1104 // destroy regions, but not the array itself
1105 // (this is done in CleanupData)
1106 if (pChtCData->hpsMem && pChtCData->paRegions)
1107 {
1108 ULONG ul;
1109 for (ul = 0;
1110 ul < pChtCData->cd.cValues;
1111 ul++)
1112 {
1113 if (pChtCData->paRegions[ul])
1114 {
1115 GpiDestroyRegion(pChtCData->hpsMem, pChtCData->paRegions[ul]);
1116 pChtCData->paRegions[ul] = NULLHANDLE;
1117 }
1118 }
1119 }
1120
1121 // bitmap already created?
1122 if (pChtCData->hbmChart)
1123 {
1124 // free current bitmap
1125 GpiSetBitmap(pChtCData->hpsMem, NULLHANDLE);
1126 // delete bitmap; fails if not freed!
1127 if (!GpiDeleteBitmap(pChtCData->hbmChart))
1128 _Pmpf((__FUNCTION__ ": GpiDeleteBitmap failed."));
1129 pChtCData->hbmChart = NULLHANDLE;
1130 }
1131
1132 if (pChtCData->hpsMem)
1133 {
1134 GpiDestroyPS(pChtCData->hpsMem);
1135 pChtCData->hpsMem = NULLHANDLE;
1136 }
1137 if (pChtCData->hdcMem)
1138 {
1139 DevCloseDC(pChtCData->hdcMem);
1140 pChtCData->hdcMem = NULLHANDLE;
1141 }
1142 }
1143}
1144
1145/*
1146 *@@ CleanupData:
1147 * this frees all allocated resources of the chart
1148 * control, except the bitmap (use CleanupBitmap
1149 * for that) and the CHARTCDATA itself.
1150 *
1151 * Note: CleanupBitmap must be called _before_ this
1152 * function.
1153 */
1154
1155VOID CleanupData(PCHARTCDATA pChtCData)
1156{
1157 if (pChtCData)
1158 if (pChtCData->cd.cValues)
1159 {
1160 // _Pmpf(("Cleaning up data"));
1161 // values array
1162 if (pChtCData->cd.padValues)
1163 free(pChtCData->cd.padValues);
1164
1165 // colors array
1166 if (pChtCData->cd.palColors)
1167 free(pChtCData->cd.palColors);
1168
1169 // strings array
1170 if (pChtCData->cd.papszDescriptions)
1171 {
1172 ULONG ul;
1173 PSZ *ppszDescriptionThis = pChtCData->cd.papszDescriptions;
1174 for (ul = 0;
1175 ul < pChtCData->cd.cValues;
1176 ul++)
1177 {
1178 if (*ppszDescriptionThis)
1179 free(*ppszDescriptionThis);
1180
1181 ppszDescriptionThis++;
1182 }
1183
1184 free(pChtCData->cd.papszDescriptions);
1185 }
1186
1187 pChtCData->cd.cValues = 0;
1188
1189 // GPI regions array
1190 if (pChtCData->paRegions)
1191 {
1192 free(pChtCData->paRegions);
1193 pChtCData->paRegions = NULL;
1194 }
1195 }
1196}
1197
1198/*
1199 *@@ SetChartData:
1200 * implementation for CHTM_SETCHARTDATA
1201 * in ctl_fnwpChart.
1202 *
1203 *@@added V0.9.2 (2000-02-29) [umoeller]
1204 *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed trap if ptr to descriptions was null
1205 */
1206
1207VOID SetChartData(HWND hwndChart,
1208 PCHARTCDATA pChtCData,
1209 PCHARTDATA pcdNew)
1210{
1211 ULONG ul = 0;
1212 PSZ *ppszDescriptionSource,
1213 *ppszDescriptionTarget;
1214
1215 // free previous values, if set
1216 if (pChtCData->hbmChart)
1217 CleanupBitmap(pChtCData);
1218
1219 CleanupData(pChtCData);
1220
1221 pChtCData->cd.usStartAngle = pcdNew->usStartAngle;
1222 pChtCData->cd.usSweepAngle = pcdNew->usSweepAngle;
1223 pChtCData->cd.cValues = pcdNew->cValues;
1224
1225 // copy values
1226 pChtCData->cd.padValues = (double*)malloc(sizeof(double) * pcdNew->cValues);
1227 memcpy(pChtCData->cd.padValues,
1228 pcdNew->padValues,
1229 sizeof(double) * pcdNew->cValues);
1230
1231 // copy colors
1232 pChtCData->cd.palColors = (LONG*)malloc(sizeof(LONG) * pcdNew->cValues);
1233 memcpy(pChtCData->cd.palColors,
1234 pcdNew->palColors,
1235 sizeof(LONG) * pcdNew->cValues);
1236
1237 // copy strings
1238 if (!pcdNew->papszDescriptions)
1239 pChtCData->cd.papszDescriptions = NULL;
1240 else
1241 {
1242 pChtCData->cd.papszDescriptions = (PSZ*)malloc(sizeof(PSZ) * pcdNew->cValues);
1243 ppszDescriptionSource = pcdNew->papszDescriptions;
1244 ppszDescriptionTarget = pChtCData->cd.papszDescriptions;
1245 for (ul = 0;
1246 ul < pcdNew->cValues;
1247 ul++)
1248 {
1249 if (*ppszDescriptionSource)
1250 *ppszDescriptionTarget = strdup(*ppszDescriptionSource);
1251 else
1252 *ppszDescriptionTarget = NULL;
1253 ppszDescriptionSource++;
1254 ppszDescriptionTarget++;
1255 }
1256 }
1257
1258 // create an array of GPI region handles
1259 pChtCData->paRegions = (HRGN*)malloc(sizeof(HRGN) * pcdNew->cValues);
1260 // initialize all regions to null
1261 memset(pChtCData->paRegions, 0, sizeof(HRGN) * pcdNew->cValues);
1262
1263 pChtCData->lSelected = -1; // none selected
1264 pChtCData->lSourceEmphasis = -1; // none selected
1265
1266 WinInvalidateRect(hwndChart, NULL, FALSE);
1267}
1268
1269/*
1270 *@@ PaintChart:
1271 * implementation for WM_PAINT
1272 * in ctl_fnwpChart.
1273 *
1274 *@@added V0.9.2 (2000-02-29) [umoeller]
1275 *@@changed V0.9.12 (2001-05-03) [umoeller]: fixed major PM resource leaks
1276 */
1277
1278VOID PaintChart(HWND hwndChart,
1279 PCHARTCDATA pChtCData,
1280 HPS hps,
1281 PRECTL prclPaint)
1282{
1283 RECTL rclStatic;
1284 WinQueryWindowRect(hwndChart, &rclStatic);
1285
1286 // do we have any values yet?
1287 if (pChtCData->cd.cValues == 0)
1288 {
1289 CHAR szDebug[200];
1290 sprintf(szDebug, "Error, no values set");
1291 WinFillRect(hps,
1292 &rclStatic, // exclusive
1293 CLR_WHITE);
1294 WinDrawText(hps,
1295 strlen(szDebug),
1296 szDebug,
1297 &rclStatic,
1298 CLR_BLACK,
1299 CLR_WHITE,
1300 DT_LEFT | DT_TOP);
1301 }
1302 else
1303 {
1304 // valid values, apparently:
1305 LONG lForegroundColor = winhQueryPresColor(hwndChart,
1306 PP_FOREGROUNDCOLOR,
1307 TRUE, // inherit presparams
1308 SYSCLR_WINDOWTEXT);
1309
1310 gpihSwitchToRGB(hps);
1311
1312 // check if the bitmap needs to be (re)created
1313 if (pChtCData->hbmChart == NULLHANDLE)
1314 {
1315 // no: do it now
1316 HPOINTER hptrOld = winhSetWaitPointer();
1317 LONG lLCIDSet = 0;
1318
1319 if (pChtCData->hpsMem == NULLHANDLE)
1320 {
1321 // first call:
1322 FONTMETRICS FontMetrics;
1323 LONG lPointSize;
1324
1325 // create a memory PS for the bitmap
1326 SIZEL szlPage = {rclStatic.xRight,
1327 rclStatic.yTop};
1328 gpihCreateMemPS(WinQueryAnchorBlock(hwndChart),
1329 &szlPage,
1330 &pChtCData->hdcMem,
1331 &pChtCData->hpsMem);
1332
1333 // get presentation font
1334 lLCIDSet = gpihFindPresFont(hwndChart,
1335 TRUE, // inherit PP
1336 pChtCData->hpsMem,
1337 "8.Helv",
1338 &FontMetrics,
1339 &lPointSize);
1340 // set presentation font
1341 if (lLCIDSet)
1342 {
1343 GpiSetCharSet(pChtCData->hpsMem, lLCIDSet);
1344 if (FontMetrics.fsDefn & FM_DEFN_OUTLINE)
1345 gpihSetPointSize(pChtCData->hpsMem, lPointSize);
1346 }
1347 }
1348
1349 pChtCData->hbmChart = ctlCreateChartBitmap(
1350 pChtCData->hpsMem, // mem PS
1351 rclStatic.xRight, // cx
1352 rclStatic.yTop, // cy
1353 &pChtCData->cd, // data
1354 &pChtCData->cs, // style
1355 // background color:
1356 winhQueryPresColor(hwndChart,
1357 PP_BACKGROUNDCOLOR,
1358 TRUE, // inherit presparams
1359 SYSCLR_DIALOGBACKGROUND),
1360 // description text color:
1361 lForegroundColor,
1362 pChtCData->paRegions);
1363 // out: regions array
1364
1365 // unset and delete font
1366 if (lLCIDSet)
1367 {
1368 GpiSetCharSet(pChtCData->hpsMem, LCID_DEFAULT);
1369 GpiDeleteSetId(pChtCData->hpsMem, lLCIDSet);
1370 }
1371
1372 WinSetPointer(HWND_DESKTOP, hptrOld);
1373 }
1374
1375 if (pChtCData->hbmChart)
1376 {
1377 // draw the chart bitmap
1378 POINTL ptlDest = { 0, 0 };
1379 WinDrawBitmap(hps,
1380 pChtCData->hbmChart,
1381 NULL, // draw whole bitmap
1382 &ptlDest,
1383 0, 0, // colors (don't care)
1384 DBM_NORMAL);
1385
1386 // now draw emphasis, if we have regions
1387 if (pChtCData->paRegions)
1388 {
1389 AREABUNDLE ab;
1390 SIZEL sl = {2, 2};
1391
1392 // 1) source emphasis (even if we don't have focus)
1393 if (pChtCData->lSourceEmphasis != -1)
1394 {
1395 if (pChtCData->paRegions[pChtCData->lSourceEmphasis])
1396 {
1397 // GpiFrameRegion uses the current pattern
1398 // attributes, so we must set the correct
1399 // color in there (GpiSetColor doesn't work)
1400 // V0.9.12 (2001-05-03) [umoeller]
1401
1402 ab.lColor = lForegroundColor;
1403 GpiSetAttrs(hps,
1404 PRIM_AREA,
1405 ABB_COLOR,
1406 0,
1407 (PBUNDLE)&ab);
1408 GpiSetPattern(hps,
1409 PATSYM_DIAG1);
1410 GpiPaintRegion(hps,
1411 pChtCData->paRegions[pChtCData->lSourceEmphasis]);
1412 }
1413 }
1414
1415 // 2) selection
1416
1417 // do we have the focus?
1418 if ( (pChtCData->fHasFocus)
1419 // something selected?
1420 && (pChtCData->lSelected != -1)
1421 )
1422 {
1423 if (pChtCData->paRegions[pChtCData->lSelected])
1424 {
1425 // GpiFrameRegion uses the current pattern
1426 // attributes, so we must set the correct
1427 // color in there (GpiSetColor doesn't work)
1428 // V0.9.12 (2001-05-03) [umoeller]
1429
1430 ab.lColor = RGBCOL_RED; // lForegroundColor;
1431 GpiSetAttrs(hps,
1432 PRIM_AREA,
1433 ABB_COLOR,
1434 0,
1435 (PBUNDLE)&ab);
1436 GpiFrameRegion(hps,
1437 pChtCData->paRegions[pChtCData->lSelected],
1438 &sl);
1439 }
1440 }
1441 }
1442 }
1443 }
1444}
1445
1446/*
1447 *@@ FindItemFromPoint:
1448 * returns the index of the slice under the
1449 * given window coordinates, or -1 if there's
1450 * none.
1451 *
1452 *@@added V0.9.12 (2001-05-03) [umoeller]
1453 */
1454
1455LONG FindItemFromPoint(PCHARTCDATA pChtCData,
1456 LONG lx,
1457 LONG ly)
1458{
1459 LONG lRegionFound = -1; // none
1460
1461 POINTL ptlMouse = {lx, ly};
1462
1463 // data set?
1464 if ( (pChtCData->cd.cValues)
1465 && (pChtCData->paRegions)
1466 )
1467 {
1468 ULONG ul;
1469 for (ul = 0;
1470 ul < pChtCData->cd.cValues;
1471 ul++)
1472 {
1473 HRGN hRgnThis = pChtCData->paRegions[ul];
1474 if (hRgnThis)
1475 {
1476 if (GpiPtInRegion(pChtCData->hpsMem,
1477 hRgnThis,
1478 &ptlMouse)
1479 == PRGN_INSIDE)
1480 {
1481 return (ul);
1482 }
1483 }
1484 }
1485 }
1486
1487 return (-1);
1488}
1489
1490/*
1491 *@@ SendWMControl:
1492 *
1493 *@@added V0.9.12 (2001-05-03) [umoeller]
1494 */
1495
1496VOID SendWMControl(HWND hwndChart,
1497 MPARAM mp1Mouse,
1498 USHORT usNotify,
1499 ULONG ulEmphasis, // 0 or 1
1500 LONG lIndex)
1501{
1502 HWND hwndOwner;
1503
1504 if (hwndOwner = WinQueryWindow(hwndChart, QW_OWNER))
1505 {
1506 EMPHASISNOTIFY en;
1507 en.hwndSource = hwndChart;
1508 en.lIndex = lIndex; // can be -1
1509 en.ulEmphasis = ulEmphasis;
1510 en.ptl.x = SHORT1FROMMP(mp1Mouse);
1511 en.ptl.y = SHORT2FROMMP(mp1Mouse);
1512
1513 WinSendMsg(hwndOwner,
1514 WM_CONTROL,
1515 MPFROM2SHORT(WinQueryWindowUShort(hwndChart,
1516 QWS_ID),
1517 usNotify),
1518 &en);
1519 }
1520}
1521
1522/*
1523 *@@ SetEmphasis:
1524 *
1525 *@@added V0.9.12 (2001-05-03) [umoeller]
1526 */
1527
1528BOOL SetEmphasis(HWND hwndChart,
1529 PCHARTCDATA pChtCData,
1530 ULONG ulEmphasis, // in: 0 == selection, 1 == source emphasis
1531 MPARAM mp1, // in: mp1 with mouse values or -1 if none
1532 LONG lIndex,
1533 BOOL fIsContextMenu)
1534{
1535 BOOL brc = FALSE;
1536
1537 PLONG plOld;
1538
1539 if (ulEmphasis == 0)
1540 plOld = &pChtCData->lSelected;
1541 else if (ulEmphasis == 1)
1542 plOld = &pChtCData->lSourceEmphasis;
1543
1544 if (plOld)
1545 {
1546 if ( (*plOld != lIndex)
1547 || (fIsContextMenu)
1548 )
1549 {
1550 // selection changed:
1551 *plOld = lIndex;
1552
1553 brc = TRUE;
1554
1555 // repaint
1556 WinInvalidateRect(hwndChart, NULL, FALSE);
1557
1558 // send notification to owner
1559 // V0.9.12 (2001-05-03) [umoeller]
1560 SendWMControl(hwndChart,
1561 mp1,
1562 CHTN_EMPHASISCHANGED,
1563 ulEmphasis,
1564 lIndex);
1565 }
1566 }
1567
1568 return (brc);
1569}
1570
1571/*
1572 *@@ ctl_fnwpChart:
1573 * window procedure for the "chart" control.
1574 *
1575 * This is not a stand-alone window procedure, but must only
1576 * be used with static controls subclassed by ctlChartFromStatic.
1577 *
1578 *@@added V0.9.0 [umoeller]
1579 *@@changed V0.9.2 (2000-02-29) [umoeller]: added resize support
1580 *@@changed V0.9.2 (2000-02-29) [umoeller]: fixed baaad PM resource leaks, the bitmap was never freed
1581 *@@changed V0.9.12 (2001-05-03) [umoeller]: added WM_CONTEXTMENU support
1582 */
1583
1584MRESULT EXPENTRY ctl_fnwpChart(HWND hwndChart, ULONG msg, MPARAM mp1, MPARAM mp2)
1585{
1586 MRESULT mrc = 0;
1587 PCHARTCDATA pChtCData = (PCHARTCDATA)WinQueryWindowULong(hwndChart, QWL_USER);
1588
1589 if (pChtCData)
1590 {
1591 PFNWP OldStaticProc = pChtCData->OldStaticProc;
1592
1593 switch (msg)
1594 {
1595 /*
1596 *@@ CHTM_SETCHARTDATA:
1597 * user msg to set the pie chart data,
1598 * which is copied to the control's
1599 * memory.
1600 * This can be sent several times to
1601 * have the chart refreshed with new
1602 * values.
1603 *
1604 * Parameters:
1605 * -- PCHARTDATA mp1: new values.
1606 * These will be copied to the chart's internal data.
1607 * -- mp2: unused.
1608 */
1609
1610 case CHTM_SETCHARTDATA:
1611 // _Pmpf(("CHTM_SETCHARTDATA, mp1: 0x%lX", mp1));
1612 if (mp1)
1613 SetChartData(hwndChart,
1614 pChtCData,
1615 (PCHARTDATA)mp1);
1616 break;
1617
1618 /*
1619 *@@ CHTM_SETCHARTSTYLE:
1620 * user msg to set the chart style,
1621 * which is copied to the control's
1622 * memory.
1623 * This can be sent several times to
1624 * have the chart refreshed with new
1625 * styles.
1626 *
1627 * Parameters:
1628 * -- PCHARTSTYLE mp1: new style data.
1629 * This will be copied to the chart's internal data.
1630 * -- mp2: unused.
1631 */
1632
1633 case CHTM_SETCHARTSTYLE:
1634 // _Pmpf(("CHTM_SETCHARTSTYLE, mp1: 0x%lX", mp1));
1635 memcpy(&(pChtCData->cs), mp1, sizeof(CHARTSTYLE));
1636 if (pChtCData->hbmChart)
1637 {
1638 CleanupBitmap(pChtCData);
1639 WinInvalidateRect(hwndChart, NULL, FALSE);
1640 }
1641 break;
1642
1643 /*
1644 *@@ CHTM_ITEMFROMPOINT:
1645 * this can be _sent_ to the chart control
1646 * to query the chart item which surrounds
1647 * the given point. Specify the coordinates
1648 * in two SHORT's in mp1 (as with WM_BUTTON1*
1649 * messages), in window coordinates.
1650 *
1651 * Parameters:
1652 * -- SHORT SHORT1FROMMP(mp1): x
1653 * -- SHORT SHORT1FROMMP(mp1): y
1654 * -- mp2: unused.
1655 *
1656 * Returns:
1657 * -- LONG: data item index (counting from
1658 * 0) or -1 if none, e.g. if no
1659 * data has been set yet or if
1660 * the point is outside the pie
1661 * slices.
1662 *
1663 *@@added V0.9.2 (2000-02-29) [umoeller]
1664 */
1665
1666 case CHTM_ITEMFROMPOINT:
1667 {
1668 mrc = (MPARAM)FindItemFromPoint(pChtCData,
1669 SHORT1FROMMP(mp1),
1670 SHORT2FROMMP(mp1));
1671 break; }
1672
1673 /*
1674 *@@ CHTM_SETEMPHASIS:
1675 * sets emphasis on a chart slice.
1676 *
1677 * Parameters:
1678 *
1679 * LONG mp1: emphasis to set. Currently
1680 * defined values:
1681 *
1682 * -- 0: set selection.
1683 *
1684 * -- 1: set source emphasis.
1685 *
1686 * LONG mp2: zero-based index of slice to
1687 * set emphasis for, or -1 to
1688 * remove that emphasis.
1689 *
1690 * Returns: TRUE if emphasis was changed.
1691 *
1692 *@@added V0.9.12 (2001-05-03) [umoeller]
1693 */
1694
1695 case CHTM_SETEMPHASIS:
1696 {
1697 LONG lEmph = (LONG)mp1;
1698 LONG lIndex = (LONG)mp2;
1699 if ( lEmph > 0
1700 && lEmph < 2
1701 && ( lIndex >= -1
1702 || ( lIndex > 0
1703 && lIndex < pChtCData->cd.cValues
1704 )
1705 )
1706 )
1707 {
1708 mrc = (MPARAM)SetEmphasis(hwndChart,
1709 pChtCData,
1710 lEmph,
1711 (MPARAM)-1,
1712 lIndex,
1713 // force flag:
1714 TRUE);
1715 }
1716 }
1717 break;
1718
1719 /*
1720 * WM_BUTTON1CLICK:
1721 * WM_CONTEXTMENU: V0.9.12 (2001-05-03) [umoeller]
1722 */
1723
1724 case WM_BUTTON1CLICK:
1725 case WM_BUTTON1DBLCLK:
1726 case WM_CONTEXTMENU:
1727 if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
1728 {
1729 LONG lRegionFound = FindItemFromPoint(pChtCData,
1730 SHORT1FROMMP(mp1),
1731 SHORT2FROMMP(mp1));
1732
1733 // selections allowed:
1734 // we then accept WM_CHAR msgs, so
1735 // we need the focus
1736 WinSetFocus(HWND_DESKTOP, hwndChart);
1737 // this invalidates the window
1738
1739 if (msg == WM_CONTEXTMENU)
1740 {
1741 // context menu:
1742 SendWMControl(hwndChart,
1743 mp1,
1744 CHTN_CONTEXTMENU,
1745 -1,
1746 lRegionFound);
1747 }
1748 else
1749 {
1750 // selection changed:
1751 SetEmphasis(hwndChart,
1752 pChtCData,
1753 0, // set selection; caller must set source emphasis
1754 mp1,
1755 lRegionFound,
1756 // force flag:
1757 FALSE);
1758
1759 if (msg == WM_BUTTON1DBLCLK)
1760 SendWMControl(hwndChart,
1761 mp1,
1762 CHTN_ENTER,
1763 0,
1764 lRegionFound);
1765 }
1766 }
1767 else
1768 // no selections allowed:
1769 // just activate the window
1770 WinSetActiveWindow(HWND_DESKTOP, hwndChart);
1771 // the parent frame gets activated this way
1772 mrc = (MPARAM)TRUE;
1773 break;
1774
1775 /*
1776 * WM_SETFOCUS:
1777 * we might need to redraw the selection.
1778 */
1779
1780 case WM_SETFOCUS:
1781 if (pChtCData->cs.ulStyle & CHS_SELECTIONS)
1782 {
1783 // selections allowed:
1784 pChtCData->fHasFocus = (BOOL)mp2;
1785 if (pChtCData->lSelected != -1)
1786 WinInvalidateRect(hwndChart, NULL, FALSE);
1787 }
1788 break;
1789
1790 /*
1791 * WM_WINDOWPOSCHANGED:
1792 *
1793 */
1794
1795 case WM_WINDOWPOSCHANGED:
1796 {
1797 // this msg is passed two SWP structs:
1798 // one for the old, one for the new data
1799 // (from PM docs)
1800 PSWP pswpNew = (PSWP)mp1;
1801 // PSWP pswpOld = pswpNew + 1;
1802
1803 // resizing?
1804 if (pswpNew->fl & SWP_SIZE)
1805 {
1806 if (pChtCData->hbmChart)
1807 // invalidate bitmap so that
1808 // it will be recreated with new size
1809 CleanupBitmap(pChtCData);
1810
1811 WinInvalidateRect(hwndChart, NULL, FALSE);
1812 }
1813 // return default NULL
1814 break; }
1815
1816 /*
1817 * WM_PRESPARAMCHANGED:
1818 *
1819 */
1820
1821 case WM_PRESPARAMCHANGED:
1822 if (pChtCData->hbmChart)
1823 {
1824 // invalidate bitmap so that
1825 // it will be recreated with new
1826 // fonts and colors
1827 CleanupBitmap(pChtCData);
1828 WinInvalidateRect(hwndChart, NULL, FALSE);
1829 }
1830 break;
1831
1832 /*
1833 * WM_PAINT:
1834 * paint the chart bitmap, which is created
1835 * if necessary (calling ctlCreateChartBitmap).
1836 */
1837
1838 case WM_PAINT:
1839 {
1840 RECTL rclPaint;
1841 HPS hps = WinBeginPaint(hwndChart,
1842 NULLHANDLE, // obtain cached micro-PS
1843 &rclPaint);
1844
1845 PaintChart(hwndChart,
1846 pChtCData,
1847 hps,
1848 &rclPaint);
1849
1850 WinEndPaint(hps);
1851 mrc = 0;
1852 break; }
1853
1854 /*
1855 * WM_DESTROY:
1856 * clean up resources
1857 */
1858
1859 case WM_DESTROY:
1860 CleanupBitmap(pChtCData);
1861
1862 CleanupData(pChtCData);
1863
1864 free(pChtCData);
1865
1866 mrc = (*OldStaticProc)(hwndChart, msg, mp1, mp2);
1867 break;
1868
1869 default:
1870 mrc = (*OldStaticProc)(hwndChart, msg, mp1, mp2);
1871 }
1872 }
1873
1874 return (mrc);
1875}
1876
1877/*
1878 *@@ ctlChartFromStatic:
1879 * this function turns an existing static text control into
1880 * a chart control (for visualizing data) by subclassing its
1881 * window procedure with ctl_fnwpChart.
1882 *
1883 * See cctl_chart.c for an overview and usage instructions.
1884 *
1885 *@@added V0.9.0 [umoeller]
1886 */
1887
1888BOOL ctlChartFromStatic(HWND hwndChart) // in: static control to subclass
1889{
1890 if (hwndChart)
1891 {
1892 PFNWP OldStaticProc = WinSubclassWindow(hwndChart, ctl_fnwpChart);
1893
1894 if (OldStaticProc)
1895 {
1896 PCHARTCDATA pChtCData = (PCHARTCDATA)malloc(sizeof(CHARTCDATA));
1897 memset(pChtCData, 0, sizeof(CHARTCDATA));
1898 pChtCData->OldStaticProc = OldStaticProc;
1899 pChtCData->fHasFocus = FALSE;
1900 WinSetWindowPtr(hwndChart, QWL_USER, pChtCData);
1901 return (TRUE);
1902 }
1903 }
1904
1905 return (FALSE);
1906}
1907
1908
Note: See TracBrowser for help on using the repository browser.