]> Pileus Git - ~andy/gtk/blob - gtk/gtkcurve.c
Initial revision
[~andy/gtk] / gtk / gtkcurve.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1997 David Mosberger
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 #include <string.h>
19 #include <math.h>
20
21 #include "gtkcurve.h"
22 #include "gtkdrawingarea.h"
23 #include "gtkmain.h"
24 #include "gtkradiobutton.h"
25 #include "gtksignal.h"
26 #include "gtktable.h"
27
28 #define BOUNDS(a,x,y)   (((a) < (x)) ? (x) : (((a) > (y)) ? (y) : (a)))
29 #define RADIUS          3       /* radius of the control points */
30 #define MIN_DISTANCE    8       /* min distance between control points */
31
32 #define GRAPH_MASK      (GDK_EXPOSURE_MASK |            \
33                          GDK_POINTER_MOTION_MASK |      \
34                          GDK_POINTER_MOTION_HINT_MASK | \
35                          GDK_ENTER_NOTIFY_MASK |        \
36                          GDK_BUTTON_PRESS_MASK |        \
37                          GDK_BUTTON_RELEASE_MASK |      \
38                          GDK_BUTTON1_MOTION_MASK)
39
40 static GtkDrawingAreaClass *parent_class = NULL;
41 static gint curve_type_changed_signal = 0;
42
43
44 /* forward declarations: */
45 static void gtk_curve_class_init (GtkCurveClass *class);
46 static void gtk_curve_init (GtkCurve *curve);
47 static void gtk_curve_destroy (GtkObject *object);
48
49
50 guint
51 gtk_curve_get_type (void)
52 {
53   static guint curve_type = 0;
54
55   if (!curve_type)
56     {
57       GtkTypeInfo curve_info =
58       {
59         "GtkCurve",
60         sizeof (GtkCurve),
61         sizeof (GtkCurveClass),
62         (GtkClassInitFunc) gtk_curve_class_init,
63         (GtkObjectInitFunc) gtk_curve_init,
64         (GtkArgFunc) NULL,
65       };
66
67       curve_type = gtk_type_unique (gtk_drawing_area_get_type (), &curve_info);
68     }
69   return curve_type;
70 }
71
72 static void
73 gtk_curve_class_init (GtkCurveClass *class)
74 {
75   GtkObjectClass *object_class;
76
77   parent_class = gtk_type_class (gtk_drawing_area_get_type ());
78
79   object_class = (GtkObjectClass *) class;
80
81   curve_type_changed_signal =
82     gtk_signal_new ("curve_type_changed", GTK_RUN_FIRST, object_class->type,
83                     GTK_SIGNAL_OFFSET (GtkCurveClass, curve_type_changed),
84                     gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
85   gtk_object_class_add_signals (object_class, &curve_type_changed_signal, 1);
86
87   object_class->destroy = gtk_curve_destroy;
88 }
89
90 static void
91 gtk_curve_init (GtkCurve *curve)
92 {
93   curve->cursor_type = GDK_TOP_LEFT_ARROW;
94   curve->pixmap = NULL;
95   curve->curve_type = GTK_CURVE_TYPE_SPLINE;
96   curve->height = 0;
97   curve->grab_point = -1;
98
99   curve->num_points = 0;
100   curve->point = 0;
101
102   curve->num_ctlpoints = 0;
103   curve->ctlpoint = NULL;
104 }
105
106 static int
107 project (gfloat value, gfloat min, gfloat max, int norm)
108 {
109   return (norm - 1) * ((value - min) / (max - min)) + 0.5;
110 }
111
112 static gfloat
113 unproject (gint value, gfloat min, gfloat max, int norm)
114 {
115   return value / (gfloat) (norm - 1) * (max - min) + min;
116 }
117
118 /* Solve the tridiagonal equation system that determines the second
119    derivatives for the interpolation points.  (Based on Numerical
120    Recipies 2nd Edition.) */
121 static void
122 spline_solve (int n, gfloat x[], gfloat y[], gfloat y2[])
123 {
124   gfloat p, sig, *u;
125   gint i, k;
126
127   u = g_malloc ((n - 1) * sizeof (u[0]));
128
129   y2[0] = u[0] = 0.0;   /* set lower boundary condition to "natural" */
130
131   for (i = 1; i < n - 1; ++i)
132     {
133       sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]);
134       p = sig * y2[i - 1] + 2.0;
135       y2[i] = (sig - 1.0) / p;
136       u[i] = ((y[i + 1] - y[i])
137               / (x[i + 1] - x[i]) - (y[i] - y[i - 1]) / (x[i] - x[i - 1]));
138       u[i] = (6.0 * u[i] / (x[i + 1] - x[i - 1]) - sig * u[i - 1]) / p;
139     }
140
141   y2[n - 1] = 0.0;
142   for (k = n - 2; k >= 0; --k)
143     y2[k] = y2[k] * y2[k + 1] + u[k];
144
145   g_free (u);
146 }
147
148 static gfloat
149 spline_eval (int n, gfloat x[], gfloat y[], gfloat y2[], gfloat val)
150 {
151   gint k_lo, k_hi, k;
152   gfloat h, b, a;
153
154   /* do a binary search for the right interval: */
155   k_lo = 0; k_hi = n - 1;
156   while (k_hi - k_lo > 1)
157     {
158       k = (k_hi + k_lo) / 2;
159       if (x[k] > val)
160         k_hi = k;
161       else
162         k_lo = k;
163     }
164
165   h = x[k_hi] - x[k_lo];
166   g_assert (h > 0.0);
167
168   a = (x[k_hi] - val) / h;
169   b = (val - x[k_lo]) / h;
170   return a*y[k_lo] + b*y[k_hi] +
171     ((a*a*a - a)*y2[k_lo] + (b*b*b - b)*y2[k_hi]) * (h*h)/6.0;
172 }
173
174 static void
175 gtk_curve_interpolate (GtkCurve *c, gint width, gint height)
176 {
177   gfloat *vector;
178   int i;
179
180   vector = g_malloc (width * sizeof (vector[0]));
181
182   gtk_curve_get_vector (c, width, vector);
183
184   c->height = height;
185   if (c->num_points != width)
186     {
187       c->num_points = width;
188       if (c->point)
189         g_free (c->point);
190       c->point = g_malloc (c->num_points * sizeof (c->point[0]));
191     }
192
193   for (i = 0; i < width; ++i)
194     {
195       c->point[i].x = RADIUS + i;
196       c->point[i].y = RADIUS + height
197         - project (vector[i], c->min_y, c->max_y, height);
198     }
199 }
200
201 static void
202 gtk_curve_draw (GtkCurve *c, gint width, gint height)
203 {
204   GtkStateType state;
205   GtkStyle *style;
206   gint i;
207
208   if (!c->pixmap)
209     return;
210
211   if (c->height != height || c->num_points != width)
212     gtk_curve_interpolate (c, width, height);
213
214   state = GTK_STATE_NORMAL;
215   if (!GTK_WIDGET_IS_SENSITIVE (GTK_WIDGET (c)))
216     state = GTK_STATE_INSENSITIVE;
217
218   style = GTK_WIDGET (c)->style;
219
220   /* clear the pixmap: */
221   gdk_draw_rectangle (c->pixmap, style->bg_gc[state], TRUE,
222                       0, 0, width + RADIUS * 2, height + RADIUS * 2);
223
224   /* draw the grid lines: (XXX make more meaningful) */
225   for (i = 0; i < 5; i++)
226     {
227       gdk_draw_line (c->pixmap, style->dark_gc[state],
228                      RADIUS, i * (height / 4.0) + RADIUS,
229                      width + RADIUS, i * (height / 4.0) + RADIUS);
230       gdk_draw_line (c->pixmap, style->dark_gc[state],
231                      i * (width / 4.0) + RADIUS, RADIUS,
232                      i * (width / 4.0) + RADIUS, height + RADIUS);
233     }
234
235   gdk_draw_points (c->pixmap, style->fg_gc[state], c->point, c->num_points);
236   if (c->curve_type != GTK_CURVE_TYPE_FREE)
237     for (i = 0; i < c->num_ctlpoints; ++i)
238       {
239         gint x, y;
240
241         if (c->ctlpoint[i][0] < c->min_x)
242           continue;
243
244         x = project (c->ctlpoint[i][0], c->min_x, c->max_x,
245                      width);
246         y = height -
247           project (c->ctlpoint[i][1], c->min_y, c->max_y,
248                    height);
249
250         /* draw a bullet: */
251         gdk_draw_arc (c->pixmap, style->fg_gc[state], TRUE, x, y,
252                       RADIUS * 2, RADIUS*2, 0, 360*64);
253       }
254   gdk_draw_pixmap (GTK_WIDGET (c)->window, style->fg_gc[state], c->pixmap,
255                    0, 0, 0, 0, width + RADIUS * 2, height + RADIUS * 2);
256 }
257
258 static gint
259 gtk_curve_graph_events (GtkWidget *widget, GdkEvent *event, GtkCurve *c)
260 {
261   GdkCursorType new_type = c->cursor_type;
262   gint i, src, dst, leftbound, rightbound;
263   GdkEventButton *bevent;
264   GdkEventMotion *mevent;
265   GtkWidget *w;
266   gint tx, ty;
267   gint cx, x, y, width, height;
268   gint closest_point = 0;
269   gfloat rx, ry, min_x;
270   guint distance;
271   gint x1, x2, y1, y2;
272
273   w = GTK_WIDGET (c);
274   width = w->allocation.width - RADIUS * 2;
275   height = w->allocation.height - RADIUS * 2;
276
277   /*  get the pointer position  */
278   gdk_window_get_pointer (w->window, &tx, &ty, NULL);
279   x = BOUNDS ((tx - RADIUS), 0, width);
280   y = BOUNDS ((ty - RADIUS), 0, height);
281
282   min_x = c->min_x;
283
284   distance = ~0U;
285   for (i = 0; i < c->num_ctlpoints; ++i)
286     {
287       cx = project (c->ctlpoint[i][0], min_x, c->max_x, width);
288       if ((guint) abs (x - cx) < distance)
289         {
290           distance = abs (x - cx);
291           closest_point = i;
292         }
293     }
294
295   switch (event->type)
296     {
297     case GDK_CONFIGURE:
298       if (c->pixmap)
299         gdk_pixmap_destroy (c->pixmap);
300       c->pixmap = 0;
301       /* fall through */
302     case GDK_EXPOSE:
303       if (!c->pixmap)
304         c->pixmap = gdk_pixmap_new (w->window,
305                                     w->allocation.width,
306                                     w->allocation.height, -1);
307       gtk_curve_draw (c, width, height);
308       break;
309
310     case GDK_BUTTON_PRESS:
311       gtk_grab_add (widget);
312
313       bevent = (GdkEventButton *) event;
314       new_type = GDK_TCROSS;
315
316       switch (c->curve_type)
317         {
318         case GTK_CURVE_TYPE_LINEAR:
319         case GTK_CURVE_TYPE_SPLINE:
320           if (distance > MIN_DISTANCE)
321             {
322               /* insert a new control point */
323               if (c->num_ctlpoints > 0)
324                 {
325                   cx = project (c->ctlpoint[closest_point][0], min_x,
326                                 c->max_x, width);
327                   if (x > cx)
328                     ++closest_point;
329                 }
330               ++c->num_ctlpoints;
331               c->ctlpoint =
332                 g_realloc (c->ctlpoint,
333                            c->num_ctlpoints * sizeof (*c->ctlpoint));
334               for (i = c->num_ctlpoints - 1; i > closest_point; --i)
335                 memcpy (c->ctlpoint + i, c->ctlpoint + i - 1,
336                         sizeof (*c->ctlpoint));
337             }
338           c->grab_point = closest_point;
339           c->ctlpoint[c->grab_point][0] =
340             unproject (x, min_x, c->max_x, width);
341           c->ctlpoint[c->grab_point][1] =
342             unproject (height - y, c->min_y, c->max_y, height);
343
344           gtk_curve_interpolate (c, width, height);
345           break;
346
347         case GTK_CURVE_TYPE_FREE:
348           c->point[x].x = RADIUS + x;
349           c->point[x].y = RADIUS + y;
350           c->grab_point = x;
351           c->last = y;
352           break;
353         }
354       gtk_curve_draw (c, width, height);
355       break;
356
357     case GDK_BUTTON_RELEASE:
358       gtk_grab_remove (widget);
359
360       /* delete inactive points: */
361       if (c->curve_type != GTK_CURVE_TYPE_FREE)
362         {
363           for (src = dst = 0; src < c->num_ctlpoints; ++src)
364             {
365               if (c->ctlpoint[src][0] >= min_x)
366                 {
367                   memcpy (c->ctlpoint + dst, c->ctlpoint + src,
368                           sizeof (*c->ctlpoint));
369                   ++dst;
370                 }
371             }
372           if (dst < src)
373             {
374               c->num_ctlpoints -= (src - dst);
375               if (c->num_ctlpoints <= 0)
376                 {
377                   c->num_ctlpoints = 1;
378                   c->ctlpoint[0][0] = min_x;
379                   c->ctlpoint[0][1] = c->min_y;
380                   gtk_curve_interpolate (c, width, height);
381                   gtk_curve_draw (c, width, height);
382                 }
383               c->ctlpoint =
384                 g_realloc (c->ctlpoint,
385                            c->num_ctlpoints * sizeof (*c->ctlpoint));
386             }
387         }
388       new_type = GDK_FLEUR;
389       c->grab_point = -1;
390       break;
391
392     case GDK_MOTION_NOTIFY:
393       mevent = (GdkEventMotion *) event;
394       if (mevent->is_hint)
395         {
396           mevent->x = tx;
397           mevent->y = ty;
398         }
399       switch (c->curve_type)
400         {
401         case GTK_CURVE_TYPE_LINEAR:
402         case GTK_CURVE_TYPE_SPLINE:
403           if (c->grab_point == -1)
404             {
405               /* if no point is grabbed...  */
406               if (distance <= MIN_DISTANCE)
407                 new_type = GDK_FLEUR;
408               else
409                 new_type = GDK_TCROSS;
410             }
411           else
412             {
413               /* drag the grabbed point  */
414               new_type = GDK_TCROSS;
415
416               leftbound = -MIN_DISTANCE;
417               if (c->grab_point > 0)
418                 leftbound = project (c->ctlpoint[c->grab_point - 1][0],
419                                      min_x, c->max_x, width);
420
421               rightbound = width + RADIUS * 2 + MIN_DISTANCE;
422               if (c->grab_point + 1 < c->num_ctlpoints)
423                 rightbound = project (c->ctlpoint[c->grab_point + 1][0],
424                                       min_x, c->max_x, width);
425
426               if (tx <= leftbound || tx >= rightbound
427                   || ty > height + RADIUS * 2 + MIN_DISTANCE
428                   || ty < -MIN_DISTANCE)
429                 c->ctlpoint[c->grab_point][0] = min_x - 1.0;
430               else
431                 {
432                   rx = unproject (x, min_x, c->max_x, width);
433                   ry = unproject (height - y, c->min_y, c->max_y, height);
434                   c->ctlpoint[c->grab_point][0] = rx;
435                   c->ctlpoint[c->grab_point][1] = ry;
436                 }
437               gtk_curve_interpolate (c, width, height);
438               gtk_curve_draw (c, width, height);
439             }
440           break;
441
442         case GTK_CURVE_TYPE_FREE:
443           if (c->grab_point != -1)
444             {
445               if (c->grab_point > x)
446                 {
447                   x1 = x;
448                   x2 = c->grab_point;
449                   y1 = y;
450                   y2 = c->last;
451                 }
452               else
453                 {
454                   x1 = c->grab_point;
455                   x2 = x;
456                   y1 = c->last;
457                   y2 = y;
458                 }
459
460               if (x2 != x1)
461                 for (i = x1; i <= x2; i++)
462                   {
463                     c->point[i].x = RADIUS + i;
464                     c->point[i].y = RADIUS +
465                       (y1 + ((y2 - y1) * (i - x1)) / (x2 - x1));
466                   }
467               else
468                 {
469                   c->point[x].x = RADIUS + x;
470                   c->point[x].y = RADIUS + y;
471                 }
472               c->grab_point = x;
473               c->last = y;
474               gtk_curve_draw (c, width, height);
475             }
476           if (mevent->state & GDK_BUTTON1_MASK)
477             new_type = GDK_TCROSS;
478           else
479             new_type = GDK_PENCIL;
480           break;
481         }
482       if (new_type != (GdkCursorType) c->cursor_type)
483         {
484           GdkCursor *cursor;
485
486           c->cursor_type = new_type;
487
488           cursor = gdk_cursor_new (c->cursor_type);
489           gdk_window_set_cursor (w->window, cursor);
490           gdk_cursor_destroy (cursor);
491         }
492       break;
493
494     default:
495       break;
496     }
497   return FALSE;
498 }
499
500 void
501 gtk_curve_set_curve_type (GtkCurve *c, GtkCurveType new_type)
502 {
503   gfloat rx, dx;
504   gint x, i;
505
506   if (new_type != c->curve_type)
507     {
508       gint width, height;
509
510       width  = GTK_WIDGET(c)->allocation.width - RADIUS * 2;
511       height = GTK_WIDGET(c)->allocation.height - RADIUS * 2;
512
513       if (new_type == GTK_CURVE_TYPE_FREE)
514         {
515           gtk_curve_interpolate (c, width, height);
516           c->curve_type = new_type;
517         }
518       else if (c->curve_type == GTK_CURVE_TYPE_FREE)
519         {
520           if (c->ctlpoint)
521             g_free (c->ctlpoint);
522           c->num_ctlpoints = 9;
523           c->ctlpoint = g_malloc (c->num_ctlpoints * sizeof (*c->ctlpoint));
524
525           rx = 0.0;
526           dx = (width - 1) / (gfloat) (c->num_ctlpoints - 1);
527
528           for (i = 0; i < c->num_ctlpoints; ++i, rx += dx)
529             {
530               x = (int) (rx + 0.5);
531               c->ctlpoint[i][0] =
532                 unproject (x, c->min_x, c->max_x, width);
533               c->ctlpoint[i][1] =
534                 unproject (RADIUS + height - c->point[x].y,
535                            c->min_y, c->max_y, height);
536             }
537           c->curve_type = new_type;
538           gtk_curve_interpolate (c, width, height);
539         }
540       else
541         {
542           c->curve_type = new_type;
543           gtk_curve_interpolate (c, width, height);
544         }
545       gtk_signal_emit (GTK_OBJECT (c), curve_type_changed_signal);
546       gtk_curve_draw (c, width, height);
547     }
548 }
549
550 static void
551 gtk_curve_size_graph (GtkCurve *curve)
552 {
553   gint width, height;
554   gfloat aspect;
555
556   width  = (curve->max_x - curve->min_x) + 1;
557   height = (curve->max_y - curve->min_y) + 1;
558   aspect = width / (gfloat) height;
559   if (width > gdk_screen_width () / 4)
560     width  = gdk_screen_width () / 4;
561   if (height > gdk_screen_height () / 4)
562     height = gdk_screen_height () / 4;
563
564   if (aspect < 1.0)
565     width  = height * aspect;
566   else
567     height = width / aspect;
568
569   gtk_drawing_area_size (GTK_DRAWING_AREA (curve),
570                          width + RADIUS * 2, height + RADIUS * 2);
571 }
572
573 static void
574 gtk_curve_reset_vector (GtkCurve *curve)
575 {
576   if (curve->ctlpoint)
577     g_free (curve->ctlpoint);
578
579   curve->num_ctlpoints = 2;
580   curve->ctlpoint = g_malloc (2 * sizeof (curve->ctlpoint[0]));
581   curve->ctlpoint[0][0] = curve->min_x;
582   curve->ctlpoint[0][1] = curve->min_y;
583   curve->ctlpoint[1][0] = curve->max_x;
584   curve->ctlpoint[1][1] = curve->max_y;
585
586   if (curve->pixmap)
587     {
588       gint width, height;
589
590       width = GTK_WIDGET (curve)->allocation.width - RADIUS * 2;
591       height = GTK_WIDGET (curve)->allocation.height - RADIUS * 2;
592
593       if (curve->curve_type == GTK_CURVE_TYPE_FREE)
594         {
595           curve->curve_type = GTK_CURVE_TYPE_LINEAR;
596           gtk_curve_interpolate (curve, width, height);
597           curve->curve_type = GTK_CURVE_TYPE_FREE;
598         }
599       else
600         gtk_curve_interpolate (curve, width, height);
601       gtk_curve_draw (curve, width, height);
602     }
603 }
604
605 void
606 gtk_curve_reset (GtkCurve *c)
607 {
608   GtkCurveType old_type;
609
610   old_type = c->curve_type;
611   c->curve_type = GTK_CURVE_TYPE_SPLINE;
612   gtk_curve_reset_vector (c);
613
614   if (old_type != GTK_CURVE_TYPE_SPLINE)
615     gtk_signal_emit (GTK_OBJECT (c), curve_type_changed_signal);
616 }
617
618 void
619 gtk_curve_set_gamma (GtkCurve *c, gfloat gamma)
620 {
621   gfloat x, one_over_gamma, height, one_over_width;
622   GtkCurveType old_type;
623   gint i;
624
625   if (c->num_points < 2)
626     return;
627
628   old_type = c->curve_type;
629   c->curve_type = GTK_CURVE_TYPE_FREE;
630
631   if (gamma <= 0)
632     one_over_gamma = 1.0;
633   else
634     one_over_gamma = 1.0 / gamma;
635   one_over_width = 1.0 / (c->num_points - 1);
636   height = c->height;
637   for (i = 0; i < c->num_points; ++i)
638     {
639       x = (gfloat) i / (c->num_points - 1);
640       c->point[i].x = RADIUS + i;
641       c->point[i].y =
642         RADIUS + (height * (1.0 - pow (x, one_over_gamma)) + 0.5);
643     }
644
645   if (old_type != GTK_CURVE_TYPE_FREE)
646     gtk_signal_emit (GTK_OBJECT (c), curve_type_changed_signal);
647
648   gtk_curve_draw (c, c->num_points, c->height);
649 }
650
651 void
652 gtk_curve_set_range (GtkCurve *curve,
653                      gfloat min_x, gfloat max_x, gfloat min_y, gfloat max_y)
654 {
655   curve->min_x = min_x;
656   curve->max_x = max_x;
657   curve->min_y = min_y;
658   curve->max_y = max_y;
659
660   gtk_curve_size_graph (curve);
661   gtk_curve_reset_vector (curve);
662 }
663
664 void
665 gtk_curve_set_vector (GtkCurve *c, int veclen, gfloat vector[])
666 {
667   GtkCurveType old_type;
668   gfloat rx, dx, ry;
669   gint i, height;
670
671   old_type = c->curve_type;
672   c->curve_type = GTK_CURVE_TYPE_FREE;
673
674   if (c->point)
675     height = GTK_WIDGET (c)->allocation.height - RADIUS * 2;
676   else
677     {
678       height = (c->max_y - c->min_y);
679       if (height > gdk_screen_height () / 4)
680         height = gdk_screen_height () / 4;
681
682       c->height = height;
683       c->num_points = veclen;
684       c->point = g_malloc (c->num_points * sizeof (c->point[0]));
685     }
686   rx = 0;
687   dx = (veclen - 1.0) / (c->num_points - 1.0);
688
689   for (i = 0; i < c->num_points; ++i, rx += dx)
690     {
691       ry = vector[(int) (rx + 0.5)];
692       if (ry > c->max_y) ry = c->max_y;
693       if (ry < c->min_y) ry = c->min_y;
694       c->point[i].x = RADIUS + i;
695       c->point[i].y =
696         RADIUS + height - project (ry, c->min_y, c->max_y, height);
697     }
698   if (old_type != GTK_CURVE_TYPE_FREE)
699     gtk_signal_emit (GTK_OBJECT (c), curve_type_changed_signal);
700
701   gtk_curve_draw (c, c->num_points, height);
702 }
703
704 void
705 gtk_curve_get_vector (GtkCurve *c, int veclen, gfloat vector[])
706 {
707   gfloat rx, ry, dx, dy, min_x, delta_x, *mem, *xv, *yv, *y2v, prev;
708   gint dst, i, x, next, num_active_ctlpoints = 0, first_active = -1;
709
710   min_x = c->min_x;
711
712   if (c->curve_type != GTK_CURVE_TYPE_FREE)
713     {
714       /* count active points: */
715       prev = min_x - 1.0;
716       for (i = num_active_ctlpoints = 0; i < c->num_ctlpoints; ++i)
717         if (c->ctlpoint[i][0] > prev)
718           {
719             if (first_active < 0)
720               first_active = i;
721             prev = c->ctlpoint[i][0];
722             ++num_active_ctlpoints;
723           }
724
725       /* handle degenerate case: */
726       if (num_active_ctlpoints < 2)
727         {
728           if (num_active_ctlpoints > 0)
729             ry = c->ctlpoint[first_active][1];
730           else
731             ry = c->min_y;
732           if (ry < c->min_y) ry = c->min_y;
733           if (ry > c->max_y) ry = c->max_y;
734           for (x = 0; x < veclen; ++x)
735             vector[x] = ry;
736           return;
737         }
738     }
739
740   switch (c->curve_type)
741     {
742     case GTK_CURVE_TYPE_SPLINE:
743       mem = g_malloc (3 * num_active_ctlpoints * sizeof (gfloat));
744       xv  = mem;
745       yv  = mem + num_active_ctlpoints;
746       y2v = mem + 2*num_active_ctlpoints;
747
748       prev = min_x - 1.0;
749       for (i = dst = 0; i < c->num_ctlpoints; ++i)
750         if (c->ctlpoint[i][0] > prev)
751           {
752             prev    = c->ctlpoint[i][0];
753             xv[dst] = c->ctlpoint[i][0];
754             yv[dst] = c->ctlpoint[i][1];
755             ++dst;
756           }
757
758       spline_solve (num_active_ctlpoints, xv, yv, y2v);
759
760       rx = min_x;
761       dx = (c->max_x - min_x) / (veclen - 1);
762       for (x = 0; x < veclen; ++x, rx += dx)
763         {
764           ry = spline_eval (num_active_ctlpoints, xv, yv, y2v, rx);
765           if (ry < c->min_y) ry = c->min_y;
766           if (ry > c->max_y) ry = c->max_y;
767           vector[x] = ry;
768         }
769
770       g_free (mem);
771       break;
772
773     case GTK_CURVE_TYPE_LINEAR:
774       dx = (c->max_x - min_x) / (veclen - 1);
775       rx = min_x;
776       ry = c->min_y;
777       dy = 0.0;
778       i  = first_active;
779       for (x = 0; x < veclen; ++x, rx += dx)
780         {
781           if (rx >= c->ctlpoint[i][0])
782             {
783               if (rx > c->ctlpoint[i][0])
784                 ry = c->min_y;
785               dy = 0.0;
786               next = i + 1;
787               while (next < c->num_ctlpoints
788                      && c->ctlpoint[next][0] <= c->ctlpoint[i][0])
789                 ++next;
790               if (next < c->num_ctlpoints)
791                 {
792                   delta_x = c->ctlpoint[next][0] - c->ctlpoint[i][0];
793                   dy = ((c->ctlpoint[next][1] - c->ctlpoint[i][1])
794                         / delta_x);
795                   dy *= dx;
796                   ry = c->ctlpoint[i][1];
797                   i = next;
798                 }
799             }
800           vector[x] = ry;
801           ry += dy;
802         }
803       break;
804
805     case GTK_CURVE_TYPE_FREE:
806       if (c->point)
807         {
808           rx = 0.0;
809           dx = c->num_points / (double) veclen;
810           for (x = 0; x < veclen; ++x, rx += dx)
811             vector[x] = unproject (RADIUS + c->height - c->point[(int) rx].y,
812                                    c->min_y, c->max_y,
813                                    c->height);
814         }
815       else
816         memset (vector, 0, veclen * sizeof (vector[0]));
817       break;
818     }
819 }
820
821 GtkWidget*
822 gtk_curve_new (void)
823 {
824   GtkCurve *curve;
825   gint old_mask;
826
827   curve = gtk_type_new (gtk_curve_get_type ());
828   curve->min_x = 0.0;
829   curve->max_x = 1.0;
830   curve->min_y = 0.0;
831   curve->max_y = 1.0;
832
833   old_mask = gtk_widget_get_events (GTK_WIDGET (curve));
834   gtk_widget_set_events (GTK_WIDGET (curve), old_mask | GRAPH_MASK);
835   gtk_signal_connect (GTK_OBJECT (curve), "event",
836                       (GtkSignalFunc) gtk_curve_graph_events, curve);
837   gtk_curve_size_graph (curve);
838
839   return GTK_WIDGET (curve);
840 }
841
842 static void
843 gtk_curve_destroy (GtkObject *object)
844 {
845   GtkCurve *curve;
846
847   g_return_if_fail (object != NULL);
848   g_return_if_fail (GTK_IS_CURVE (object));
849
850   curve = GTK_CURVE (object);
851   if (curve->pixmap)
852     gdk_pixmap_destroy (curve->pixmap);
853   if (curve->point)
854     g_free (curve->point);
855   if (curve->ctlpoint)
856     g_free (curve->ctlpoint);
857
858   if (GTK_OBJECT_CLASS (parent_class)->destroy)
859     (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
860 }