]> Pileus Git - ~andy/gtk/blob - gtk/gtkcurve.c
Use the correct screen for getting the height. (Fix from Stephen Browne,
[~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 Lesser 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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include <stdlib.h>
28 #include <string.h>
29 #include <math.h>
30
31 #include "gtkcurve.h"
32 #include "gtkdrawingarea.h"
33 #include "gtkmain.h"
34 #include "gtkmarshalers.h"
35 #include "gtkradiobutton.h"
36 #include "gtktable.h"
37 #include "gtkintl.h"
38
39 #define RADIUS          3       /* radius of the control points */
40 #define MIN_DISTANCE    8       /* min distance between control points */
41
42 #define GRAPH_MASK      (GDK_EXPOSURE_MASK |            \
43                          GDK_POINTER_MOTION_MASK |      \
44                          GDK_POINTER_MOTION_HINT_MASK | \
45                          GDK_ENTER_NOTIFY_MASK |        \
46                          GDK_BUTTON_PRESS_MASK |        \
47                          GDK_BUTTON_RELEASE_MASK |      \
48                          GDK_BUTTON1_MOTION_MASK)
49
50 enum {
51   PROP_0,
52   PROP_CURVE_TYPE,
53   PROP_MIN_X,
54   PROP_MAX_X,
55   PROP_MIN_Y,
56   PROP_MAX_Y
57 };
58
59 static GtkDrawingAreaClass *parent_class = NULL;
60 static guint curve_type_changed_signal = 0;
61
62
63 /* forward declarations: */
64 static void gtk_curve_class_init   (GtkCurveClass *class);
65 static void gtk_curve_init         (GtkCurve      *curve);
66 static void gtk_curve_get_property  (GObject              *object,
67                                      guint                 param_id,
68                                      GValue               *value,
69                                      GParamSpec           *pspec);
70 static void gtk_curve_set_property  (GObject              *object,
71                                      guint                 param_id,
72                                      const GValue         *value,
73                                      GParamSpec           *pspec);
74 static void gtk_curve_finalize     (GObject       *object);
75 static gint gtk_curve_graph_events (GtkWidget     *widget, 
76                                     GdkEvent      *event, 
77                                     GtkCurve      *c);
78 static void gtk_curve_size_graph   (GtkCurve      *curve);
79
80 GType
81 gtk_curve_get_type (void)
82 {
83   static GType curve_type = 0;
84
85   if (!curve_type)
86     {
87       static const GTypeInfo curve_info =
88       {
89         sizeof (GtkCurveClass),
90         NULL,           /* base_init */
91         NULL,           /* base_finalize */
92         (GClassInitFunc) gtk_curve_class_init,
93         NULL,           /* class_finalize */
94         NULL,           /* class_data */
95         sizeof (GtkCurve),
96         0,              /* n_preallocs */
97         (GInstanceInitFunc) gtk_curve_init,
98       };
99
100       curve_type = g_type_register_static (GTK_TYPE_DRAWING_AREA, "GtkCurve",
101                                            &curve_info, 0);
102     }
103   return curve_type;
104 }
105
106 static void
107 gtk_curve_class_init (GtkCurveClass *class)
108 {
109   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
110   
111   parent_class = g_type_class_peek_parent (class);
112   
113   gobject_class->finalize = gtk_curve_finalize;
114
115   gobject_class->set_property = gtk_curve_set_property;
116   gobject_class->get_property = gtk_curve_get_property;
117   
118   g_object_class_install_property (gobject_class,
119                                    PROP_CURVE_TYPE,
120                                    g_param_spec_enum ("curve_type",
121                                                       _("Curve type"),
122                                                       _("Is this curve linear, spline interpolated, or free-form"),
123                                                       GTK_TYPE_CURVE_TYPE,
124                                                       GTK_CURVE_TYPE_LINEAR,
125                                                       G_PARAM_READABLE |
126                                                       G_PARAM_WRITABLE));
127   g_object_class_install_property (gobject_class,
128                                    PROP_MIN_X,
129                                    g_param_spec_float ("min_x",
130                                                        _("Minimum X"),
131                                                        _("Minimum possible value for X"),
132                                                        -G_MAXFLOAT,
133                                                        G_MAXFLOAT,
134                                                        0.0,
135                                                        G_PARAM_READABLE |
136                                                        G_PARAM_WRITABLE));
137   g_object_class_install_property (gobject_class,
138                                    PROP_MAX_X,
139                                    g_param_spec_float ("max_x",
140                                                        _("Maximum X"),
141                                                        _("Maximum possible X value"),
142                                                        -G_MAXFLOAT,
143                                                        G_MAXFLOAT,
144                                                        1.0,
145                                                        G_PARAM_READABLE |
146                                                        G_PARAM_WRITABLE));
147   g_object_class_install_property (gobject_class,
148                                    PROP_MIN_Y,
149                                    g_param_spec_float ("min_y",
150                                                        _("Minimum Y"),
151                                                        _("Minimum possible value for Y"),
152                                                        -G_MAXFLOAT,
153                                                        G_MAXFLOAT,
154                                                        0.0,
155                                                        G_PARAM_READABLE |
156                                                        G_PARAM_WRITABLE));  
157   g_object_class_install_property (gobject_class,
158                                    PROP_MAX_Y,
159                                    g_param_spec_float ("max_y",
160                                                        _("Maximum Y"),
161                                                        _("Maximum possible value for Y"),
162                                                        -G_MAXFLOAT,
163                                                        G_MAXFLOAT,
164                                                        1.0,
165                                                        G_PARAM_READABLE |
166                                                        G_PARAM_WRITABLE));
167
168   curve_type_changed_signal =
169     g_signal_new ("curve_type_changed",
170                    G_OBJECT_CLASS_TYPE (gobject_class),
171                    G_SIGNAL_RUN_FIRST,
172                    G_STRUCT_OFFSET (GtkCurveClass, curve_type_changed),
173                    NULL, NULL,
174                    _gtk_marshal_VOID__VOID,
175                    G_TYPE_NONE, 0);
176 }
177
178 static void
179 gtk_curve_init (GtkCurve *curve)
180 {
181   gint old_mask;
182
183   curve->cursor_type = GDK_TOP_LEFT_ARROW;
184   curve->pixmap = NULL;
185   curve->curve_type = GTK_CURVE_TYPE_SPLINE;
186   curve->height = 0;
187   curve->grab_point = -1;
188
189   curve->num_points = 0;
190   curve->point = 0;
191
192   curve->num_ctlpoints = 0;
193   curve->ctlpoint = NULL;
194
195   curve->min_x = 0.0;
196   curve->max_x = 1.0;
197   curve->min_y = 0.0;
198   curve->max_y = 1.0;
199
200   old_mask = gtk_widget_get_events (GTK_WIDGET (curve));
201   gtk_widget_set_events (GTK_WIDGET (curve), old_mask | GRAPH_MASK);
202   g_signal_connect (curve, "event",
203                     G_CALLBACK (gtk_curve_graph_events), curve);
204   gtk_curve_size_graph (curve);
205 }
206
207 static void
208 gtk_curve_set_property (GObject              *object,
209                         guint                 prop_id,
210                         const GValue         *value,
211                         GParamSpec           *pspec)
212 {
213   GtkCurve *curve = GTK_CURVE (object);
214   
215   switch (prop_id)
216     {
217     case PROP_CURVE_TYPE:
218       gtk_curve_set_curve_type (curve, g_value_get_enum (value));
219       break;
220     case PROP_MIN_X:
221       gtk_curve_set_range (curve, g_value_get_float (value), curve->max_x,
222                            curve->min_y, curve->max_y);
223       break;
224     case PROP_MAX_X:
225       gtk_curve_set_range (curve, curve->min_x, g_value_get_float (value),
226                            curve->min_y, curve->max_y);
227       break;    
228     case PROP_MIN_Y:
229       gtk_curve_set_range (curve, curve->min_x, curve->max_x,
230                            g_value_get_float (value), curve->max_y);
231       break;
232     case PROP_MAX_Y:
233       gtk_curve_set_range (curve, curve->min_x, curve->max_x,
234                            curve->min_y, g_value_get_float (value));
235       break;
236     default:
237       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
238       break;
239     }
240 }
241
242 static void
243 gtk_curve_get_property (GObject              *object,
244                         guint                 prop_id,
245                         GValue               *value,
246                         GParamSpec           *pspec)
247 {
248   GtkCurve *curve = GTK_CURVE (object);
249
250   switch (prop_id)
251     {
252     case PROP_CURVE_TYPE:
253       g_value_set_enum (value, curve->curve_type);
254       break;
255     case PROP_MIN_X:
256       g_value_set_float (value, curve->min_x);
257       break;
258     case PROP_MAX_X:
259       g_value_set_float (value, curve->max_x);
260       break;
261     case PROP_MIN_Y:
262       g_value_set_float (value, curve->min_y);
263       break;
264     case PROP_MAX_Y:
265       g_value_set_float (value, curve->max_y);
266       break;
267     default:
268       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
269       break;
270     }
271 }
272
273 static int
274 project (gfloat value, gfloat min, gfloat max, int norm)
275 {
276   return (norm - 1) * ((value - min) / (max - min)) + 0.5;
277 }
278
279 static gfloat
280 unproject (gint value, gfloat min, gfloat max, int norm)
281 {
282   return value / (gfloat) (norm - 1) * (max - min) + min;
283 }
284
285 /* Solve the tridiagonal equation system that determines the second
286    derivatives for the interpolation points.  (Based on Numerical
287    Recipies 2nd Edition.) */
288 static void
289 spline_solve (int n, gfloat x[], gfloat y[], gfloat y2[])
290 {
291   gfloat p, sig, *u;
292   gint i, k;
293
294   u = g_malloc ((n - 1) * sizeof (u[0]));
295
296   y2[0] = u[0] = 0.0;   /* set lower boundary condition to "natural" */
297
298   for (i = 1; i < n - 1; ++i)
299     {
300       sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]);
301       p = sig * y2[i - 1] + 2.0;
302       y2[i] = (sig - 1.0) / p;
303       u[i] = ((y[i + 1] - y[i])
304               / (x[i + 1] - x[i]) - (y[i] - y[i - 1]) / (x[i] - x[i - 1]));
305       u[i] = (6.0 * u[i] / (x[i + 1] - x[i - 1]) - sig * u[i - 1]) / p;
306     }
307
308   y2[n - 1] = 0.0;
309   for (k = n - 2; k >= 0; --k)
310     y2[k] = y2[k] * y2[k + 1] + u[k];
311
312   g_free (u);
313 }
314
315 static gfloat
316 spline_eval (int n, gfloat x[], gfloat y[], gfloat y2[], gfloat val)
317 {
318   gint k_lo, k_hi, k;
319   gfloat h, b, a;
320
321   /* do a binary search for the right interval: */
322   k_lo = 0; k_hi = n - 1;
323   while (k_hi - k_lo > 1)
324     {
325       k = (k_hi + k_lo) / 2;
326       if (x[k] > val)
327         k_hi = k;
328       else
329         k_lo = k;
330     }
331
332   h = x[k_hi] - x[k_lo];
333   g_assert (h > 0.0);
334
335   a = (x[k_hi] - val) / h;
336   b = (val - x[k_lo]) / h;
337   return a*y[k_lo] + b*y[k_hi] +
338     ((a*a*a - a)*y2[k_lo] + (b*b*b - b)*y2[k_hi]) * (h*h)/6.0;
339 }
340
341 static void
342 gtk_curve_interpolate (GtkCurve *c, gint width, gint height)
343 {
344   gfloat *vector;
345   int i;
346
347   vector = g_malloc (width * sizeof (vector[0]));
348
349   gtk_curve_get_vector (c, width, vector);
350
351   c->height = height;
352   if (c->num_points != width)
353     {
354       c->num_points = width;
355       if (c->point)
356         g_free (c->point);
357       c->point = g_malloc (c->num_points * sizeof (c->point[0]));
358     }
359
360   for (i = 0; i < width; ++i)
361     {
362       c->point[i].x = RADIUS + i;
363       c->point[i].y = RADIUS + height
364         - project (vector[i], c->min_y, c->max_y, height);
365     }
366
367   g_free (vector);
368 }
369
370 static void
371 gtk_curve_draw (GtkCurve *c, gint width, gint height)
372 {
373   GtkStateType state;
374   GtkStyle *style;
375   gint i;
376
377   if (!c->pixmap)
378     return;
379
380   if (c->height != height || c->num_points != width)
381     gtk_curve_interpolate (c, width, height);
382
383   state = GTK_STATE_NORMAL;
384   if (!GTK_WIDGET_IS_SENSITIVE (GTK_WIDGET (c)))
385     state = GTK_STATE_INSENSITIVE;
386
387   style = GTK_WIDGET (c)->style;
388
389   /* clear the pixmap: */
390   gtk_paint_flat_box (style, c->pixmap, GTK_STATE_NORMAL, GTK_SHADOW_NONE,
391                       NULL, GTK_WIDGET (c), "curve_bg",
392                       0, 0, width + RADIUS * 2, height + RADIUS * 2);
393   /* draw the grid lines: (XXX make more meaningful) */
394   for (i = 0; i < 5; i++)
395     {
396       gdk_draw_line (c->pixmap, style->dark_gc[state],
397                      RADIUS, i * (height / 4.0) + RADIUS,
398                      width + RADIUS, i * (height / 4.0) + RADIUS);
399       gdk_draw_line (c->pixmap, style->dark_gc[state],
400                      i * (width / 4.0) + RADIUS, RADIUS,
401                      i * (width / 4.0) + RADIUS, height + RADIUS);
402     }
403
404   gdk_draw_points (c->pixmap, style->fg_gc[state], c->point, c->num_points);
405   if (c->curve_type != GTK_CURVE_TYPE_FREE)
406     for (i = 0; i < c->num_ctlpoints; ++i)
407       {
408         gint x, y;
409
410         if (c->ctlpoint[i][0] < c->min_x)
411           continue;
412
413         x = project (c->ctlpoint[i][0], c->min_x, c->max_x,
414                      width);
415         y = height -
416           project (c->ctlpoint[i][1], c->min_y, c->max_y,
417                    height);
418
419         /* draw a bullet: */
420         gdk_draw_arc (c->pixmap, style->fg_gc[state], TRUE, x, y,
421                       RADIUS * 2, RADIUS*2, 0, 360*64);
422       }
423   gdk_draw_drawable (GTK_WIDGET (c)->window, style->fg_gc[state], c->pixmap,
424                      0, 0, 0, 0, width + RADIUS * 2, height + RADIUS * 2);
425 }
426
427 static gint
428 gtk_curve_graph_events (GtkWidget *widget, GdkEvent *event, GtkCurve *c)
429 {
430   GdkCursorType new_type = c->cursor_type;
431   gint i, src, dst, leftbound, rightbound;
432   GdkEventButton *bevent;
433   GdkEventMotion *mevent;
434   GtkWidget *w;
435   gint tx, ty;
436   gint cx, x, y, width, height;
437   gint closest_point = 0;
438   gfloat rx, ry, min_x;
439   guint distance;
440   gint x1, x2, y1, y2;
441   gint retval = FALSE;
442
443   w = GTK_WIDGET (c);
444   width = w->allocation.width - RADIUS * 2;
445   height = w->allocation.height - RADIUS * 2;
446
447   if ((width < 0) || (height < 0))
448     return FALSE;
449
450   /*  get the pointer position  */
451   gdk_window_get_pointer (w->window, &tx, &ty, NULL);
452   x = CLAMP ((tx - RADIUS), 0, width-1);
453   y = CLAMP ((ty - RADIUS), 0, height-1);
454
455   min_x = c->min_x;
456
457   distance = ~0U;
458   for (i = 0; i < c->num_ctlpoints; ++i)
459     {
460       cx = project (c->ctlpoint[i][0], min_x, c->max_x, width);
461       if ((guint) abs (x - cx) < distance)
462         {
463           distance = abs (x - cx);
464           closest_point = i;
465         }
466     }
467
468   switch (event->type)
469     {
470     case GDK_CONFIGURE:
471       if (c->pixmap)
472         g_object_unref (c->pixmap);
473       c->pixmap = NULL;
474       /* fall through */
475     case GDK_EXPOSE:
476       if (!c->pixmap)
477         c->pixmap = gdk_pixmap_new (w->window,
478                                     w->allocation.width,
479                                     w->allocation.height, -1);
480       gtk_curve_draw (c, width, height);
481       break;
482
483     case GDK_BUTTON_PRESS:
484       gtk_grab_add (widget);
485
486       bevent = (GdkEventButton *) event;
487       new_type = GDK_TCROSS;
488
489       switch (c->curve_type)
490         {
491         case GTK_CURVE_TYPE_LINEAR:
492         case GTK_CURVE_TYPE_SPLINE:
493           if (distance > MIN_DISTANCE)
494             {
495               /* insert a new control point */
496               if (c->num_ctlpoints > 0)
497                 {
498                   cx = project (c->ctlpoint[closest_point][0], min_x,
499                                 c->max_x, width);
500                   if (x > cx)
501                     ++closest_point;
502                 }
503               ++c->num_ctlpoints;
504               c->ctlpoint =
505                 g_realloc (c->ctlpoint,
506                            c->num_ctlpoints * sizeof (*c->ctlpoint));
507               for (i = c->num_ctlpoints - 1; i > closest_point; --i)
508                 memcpy (c->ctlpoint + i, c->ctlpoint + i - 1,
509                         sizeof (*c->ctlpoint));
510             }
511           c->grab_point = closest_point;
512           c->ctlpoint[c->grab_point][0] =
513             unproject (x, min_x, c->max_x, width);
514           c->ctlpoint[c->grab_point][1] =
515             unproject (height - y, c->min_y, c->max_y, height);
516
517           gtk_curve_interpolate (c, width, height);
518           break;
519
520         case GTK_CURVE_TYPE_FREE:
521           c->point[x].x = RADIUS + x;
522           c->point[x].y = RADIUS + y;
523           c->grab_point = x;
524           c->last = y;
525           break;
526         }
527       gtk_curve_draw (c, width, height);
528       retval = TRUE;
529       break;
530
531     case GDK_BUTTON_RELEASE:
532       gtk_grab_remove (widget);
533
534       /* delete inactive points: */
535       if (c->curve_type != GTK_CURVE_TYPE_FREE)
536         {
537           for (src = dst = 0; src < c->num_ctlpoints; ++src)
538             {
539               if (c->ctlpoint[src][0] >= min_x)
540                 {
541                   memcpy (c->ctlpoint + dst, c->ctlpoint + src,
542                           sizeof (*c->ctlpoint));
543                   ++dst;
544                 }
545             }
546           if (dst < src)
547             {
548               c->num_ctlpoints -= (src - dst);
549               if (c->num_ctlpoints <= 0)
550                 {
551                   c->num_ctlpoints = 1;
552                   c->ctlpoint[0][0] = min_x;
553                   c->ctlpoint[0][1] = c->min_y;
554                   gtk_curve_interpolate (c, width, height);
555                   gtk_curve_draw (c, width, height);
556                 }
557               c->ctlpoint =
558                 g_realloc (c->ctlpoint,
559                            c->num_ctlpoints * sizeof (*c->ctlpoint));
560             }
561         }
562       new_type = GDK_FLEUR;
563       c->grab_point = -1;
564       retval = TRUE;
565       break;
566
567     case GDK_MOTION_NOTIFY:
568       mevent = (GdkEventMotion *) event;
569
570       switch (c->curve_type)
571         {
572         case GTK_CURVE_TYPE_LINEAR:
573         case GTK_CURVE_TYPE_SPLINE:
574           if (c->grab_point == -1)
575             {
576               /* if no point is grabbed...  */
577               if (distance <= MIN_DISTANCE)
578                 new_type = GDK_FLEUR;
579               else
580                 new_type = GDK_TCROSS;
581             }
582           else
583             {
584               /* drag the grabbed point  */
585               new_type = GDK_TCROSS;
586
587               leftbound = -MIN_DISTANCE;
588               if (c->grab_point > 0)
589                 leftbound = project (c->ctlpoint[c->grab_point - 1][0],
590                                      min_x, c->max_x, width);
591
592               rightbound = width + RADIUS * 2 + MIN_DISTANCE;
593               if (c->grab_point + 1 < c->num_ctlpoints)
594                 rightbound = project (c->ctlpoint[c->grab_point + 1][0],
595                                       min_x, c->max_x, width);
596
597               if (tx <= leftbound || tx >= rightbound
598                   || ty > height + RADIUS * 2 + MIN_DISTANCE
599                   || ty < -MIN_DISTANCE)
600                 c->ctlpoint[c->grab_point][0] = min_x - 1.0;
601               else
602                 {
603                   rx = unproject (x, min_x, c->max_x, width);
604                   ry = unproject (height - y, c->min_y, c->max_y, height);
605                   c->ctlpoint[c->grab_point][0] = rx;
606                   c->ctlpoint[c->grab_point][1] = ry;
607                 }
608               gtk_curve_interpolate (c, width, height);
609               gtk_curve_draw (c, width, height);
610             }
611           break;
612
613         case GTK_CURVE_TYPE_FREE:
614           if (c->grab_point != -1)
615             {
616               if (c->grab_point > x)
617                 {
618                   x1 = x;
619                   x2 = c->grab_point;
620                   y1 = y;
621                   y2 = c->last;
622                 }
623               else
624                 {
625                   x1 = c->grab_point;
626                   x2 = x;
627                   y1 = c->last;
628                   y2 = y;
629                 }
630
631               if (x2 != x1)
632                 for (i = x1; i <= x2; i++)
633                   {
634                     c->point[i].x = RADIUS + i;
635                     c->point[i].y = RADIUS +
636                       (y1 + ((y2 - y1) * (i - x1)) / (x2 - x1));
637                   }
638               else
639                 {
640                   c->point[x].x = RADIUS + x;
641                   c->point[x].y = RADIUS + y;
642                 }
643               c->grab_point = x;
644               c->last = y;
645               gtk_curve_draw (c, width, height);
646             }
647           if (mevent->state & GDK_BUTTON1_MASK)
648             new_type = GDK_TCROSS;
649           else
650             new_type = GDK_PENCIL;
651           break;
652         }
653       if (new_type != (GdkCursorType) c->cursor_type)
654         {
655           GdkCursor *cursor;
656
657           c->cursor_type = new_type;
658
659           cursor = gdk_cursor_new_for_display (gtk_widget_get_display (w),
660                                               c->cursor_type);
661           gdk_window_set_cursor (w->window, cursor);
662           gdk_cursor_unref (cursor);
663         }
664       retval = TRUE;
665       break;
666
667     default:
668       break;
669     }
670
671   return retval;
672 }
673
674 void
675 gtk_curve_set_curve_type (GtkCurve *c, GtkCurveType new_type)
676 {
677   gfloat rx, dx;
678   gint x, i;
679
680   if (new_type != c->curve_type)
681     {
682       gint width, height;
683
684       width  = GTK_WIDGET (c)->allocation.width - RADIUS * 2;
685       height = GTK_WIDGET (c)->allocation.height - RADIUS * 2;
686
687       if (new_type == GTK_CURVE_TYPE_FREE)
688         {
689           gtk_curve_interpolate (c, width, height);
690           c->curve_type = new_type;
691         }
692       else if (c->curve_type == GTK_CURVE_TYPE_FREE)
693         {
694           if (c->ctlpoint)
695             g_free (c->ctlpoint);
696           c->num_ctlpoints = 9;
697           c->ctlpoint = g_malloc (c->num_ctlpoints * sizeof (*c->ctlpoint));
698
699           rx = 0.0;
700           dx = (width - 1) / (gfloat) (c->num_ctlpoints - 1);
701
702           for (i = 0; i < c->num_ctlpoints; ++i, rx += dx)
703             {
704               x = (int) (rx + 0.5);
705               c->ctlpoint[i][0] =
706                 unproject (x, c->min_x, c->max_x, width);
707               c->ctlpoint[i][1] =
708                 unproject (RADIUS + height - c->point[x].y,
709                            c->min_y, c->max_y, height);
710             }
711           c->curve_type = new_type;
712           gtk_curve_interpolate (c, width, height);
713         }
714       else
715         {
716           c->curve_type = new_type;
717           gtk_curve_interpolate (c, width, height);
718         }
719       g_signal_emit (c, curve_type_changed_signal, 0);
720       g_object_notify (G_OBJECT (c), "curve_type");
721       gtk_curve_draw (c, width, height);
722     }
723 }
724
725 static void
726 gtk_curve_size_graph (GtkCurve *curve)
727 {
728   gint width, height;
729   gfloat aspect;
730   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (curve)); 
731
732   width  = (curve->max_x - curve->min_x) + 1;
733   height = (curve->max_y - curve->min_y) + 1;
734   aspect = width / (gfloat) height;
735   if (width > gdk_screen_get_width (screen) / 4)
736     width  = gdk_screen_get_width (screen) / 4;
737   if (height > gdk_screen_get_height (screen) / 4)
738     height = gdk_screen_get_height (screen) / 4;
739
740   if (aspect < 1.0)
741     width  = height * aspect;
742   else
743     height = width / aspect;
744
745   gtk_widget_set_size_request (GTK_WIDGET (curve),
746                                width + RADIUS * 2,
747                                height + RADIUS * 2);
748 }
749
750 static void
751 gtk_curve_reset_vector (GtkCurve *curve)
752 {
753   if (curve->ctlpoint)
754     g_free (curve->ctlpoint);
755
756   curve->num_ctlpoints = 2;
757   curve->ctlpoint = g_malloc (2 * sizeof (curve->ctlpoint[0]));
758   curve->ctlpoint[0][0] = curve->min_x;
759   curve->ctlpoint[0][1] = curve->min_y;
760   curve->ctlpoint[1][0] = curve->max_x;
761   curve->ctlpoint[1][1] = curve->max_y;
762
763   if (curve->pixmap)
764     {
765       gint width, height;
766
767       width = GTK_WIDGET (curve)->allocation.width - RADIUS * 2;
768       height = GTK_WIDGET (curve)->allocation.height - RADIUS * 2;
769
770       if (curve->curve_type == GTK_CURVE_TYPE_FREE)
771         {
772           curve->curve_type = GTK_CURVE_TYPE_LINEAR;
773           gtk_curve_interpolate (curve, width, height);
774           curve->curve_type = GTK_CURVE_TYPE_FREE;
775         }
776       else
777         gtk_curve_interpolate (curve, width, height);
778       gtk_curve_draw (curve, width, height);
779     }
780 }
781
782 void
783 gtk_curve_reset (GtkCurve *c)
784 {
785   GtkCurveType old_type;
786
787   old_type = c->curve_type;
788   c->curve_type = GTK_CURVE_TYPE_SPLINE;
789   gtk_curve_reset_vector (c);
790
791   if (old_type != GTK_CURVE_TYPE_SPLINE)
792     {
793        g_signal_emit (c, curve_type_changed_signal, 0);
794        g_object_notify (G_OBJECT (c), "curve_type");
795     }
796 }
797
798 void
799 gtk_curve_set_gamma (GtkCurve *c, gfloat gamma)
800 {
801   gfloat x, one_over_gamma, height, one_over_width;
802   GtkCurveType old_type;
803   gint i;
804
805   if (c->num_points < 2)
806     return;
807
808   old_type = c->curve_type;
809   c->curve_type = GTK_CURVE_TYPE_FREE;
810
811   if (gamma <= 0)
812     one_over_gamma = 1.0;
813   else
814     one_over_gamma = 1.0 / gamma;
815   one_over_width = 1.0 / (c->num_points - 1);
816   height = c->height;
817   for (i = 0; i < c->num_points; ++i)
818     {
819       x = (gfloat) i / (c->num_points - 1);
820       c->point[i].x = RADIUS + i;
821       c->point[i].y =
822         RADIUS + (height * (1.0 - pow (x, one_over_gamma)) + 0.5);
823     }
824
825   if (old_type != GTK_CURVE_TYPE_FREE)
826     g_signal_emit (c, curve_type_changed_signal, 0);
827
828   gtk_curve_draw (c, c->num_points, c->height);
829 }
830
831 void
832 gtk_curve_set_range (GtkCurve *curve,
833                      gfloat    min_x,
834                      gfloat    max_x,
835                      gfloat    min_y,
836                      gfloat    max_y)
837 {
838   g_object_freeze_notify (G_OBJECT (curve));
839   if (curve->min_x != min_x) {
840      curve->min_x = min_x;
841      g_object_notify (G_OBJECT (curve), "min_x");
842   }
843   if (curve->max_x != max_x) {
844      curve->max_x = max_x;
845      g_object_notify (G_OBJECT (curve), "max_x");
846   }
847   if (curve->min_y != min_y) {
848      curve->min_y = min_y;
849      g_object_notify (G_OBJECT (curve), "min_y");
850   }
851   if (curve->max_y != max_y) {
852      curve->max_y = max_y;
853      g_object_notify (G_OBJECT (curve), "max_y");
854   }
855   g_object_thaw_notify (G_OBJECT (curve));
856
857   gtk_curve_size_graph (curve);
858   gtk_curve_reset_vector (curve);
859 }
860
861 void
862 gtk_curve_set_vector (GtkCurve *c, int veclen, gfloat vector[])
863 {
864   GtkCurveType old_type;
865   gfloat rx, dx, ry;
866   gint i, height;
867   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (c));
868
869   old_type = c->curve_type;
870   c->curve_type = GTK_CURVE_TYPE_FREE;
871
872   if (c->point)
873     height = GTK_WIDGET (c)->allocation.height - RADIUS * 2;
874   else
875     {
876       height = (c->max_y - c->min_y);
877       if (height > gdk_screen_get_height (screen) / 4)
878         height = gdk_screen_get_height (screen) / 4;
879
880       c->height = height;
881       c->num_points = veclen;
882       c->point = g_malloc (c->num_points * sizeof (c->point[0]));
883     }
884   rx = 0;
885   dx = (veclen - 1.0) / (c->num_points - 1.0);
886
887   for (i = 0; i < c->num_points; ++i, rx += dx)
888     {
889       ry = vector[(int) (rx + 0.5)];
890       if (ry > c->max_y) ry = c->max_y;
891       if (ry < c->min_y) ry = c->min_y;
892       c->point[i].x = RADIUS + i;
893       c->point[i].y =
894         RADIUS + height - project (ry, c->min_y, c->max_y, height);
895     }
896   if (old_type != GTK_CURVE_TYPE_FREE)
897     {
898        g_signal_emit (c, curve_type_changed_signal, 0);
899        g_object_notify (G_OBJECT (c), "curve_type");
900     }
901
902   gtk_curve_draw (c, c->num_points, height);
903 }
904
905 void
906 gtk_curve_get_vector (GtkCurve *c, int veclen, gfloat vector[])
907 {
908   gfloat rx, ry, dx, dy, min_x, delta_x, *mem, *xv, *yv, *y2v, prev;
909   gint dst, i, x, next, num_active_ctlpoints = 0, first_active = -1;
910
911   min_x = c->min_x;
912
913   if (c->curve_type != GTK_CURVE_TYPE_FREE)
914     {
915       /* count active points: */
916       prev = min_x - 1.0;
917       for (i = num_active_ctlpoints = 0; i < c->num_ctlpoints; ++i)
918         if (c->ctlpoint[i][0] > prev)
919           {
920             if (first_active < 0)
921               first_active = i;
922             prev = c->ctlpoint[i][0];
923             ++num_active_ctlpoints;
924           }
925
926       /* handle degenerate case: */
927       if (num_active_ctlpoints < 2)
928         {
929           if (num_active_ctlpoints > 0)
930             ry = c->ctlpoint[first_active][1];
931           else
932             ry = c->min_y;
933           if (ry < c->min_y) ry = c->min_y;
934           if (ry > c->max_y) ry = c->max_y;
935           for (x = 0; x < veclen; ++x)
936             vector[x] = ry;
937           return;
938         }
939     }
940
941   switch (c->curve_type)
942     {
943     case GTK_CURVE_TYPE_SPLINE:
944       mem = g_malloc (3 * num_active_ctlpoints * sizeof (gfloat));
945       xv  = mem;
946       yv  = mem + num_active_ctlpoints;
947       y2v = mem + 2*num_active_ctlpoints;
948
949       prev = min_x - 1.0;
950       for (i = dst = 0; i < c->num_ctlpoints; ++i)
951         if (c->ctlpoint[i][0] > prev)
952           {
953             prev    = c->ctlpoint[i][0];
954             xv[dst] = c->ctlpoint[i][0];
955             yv[dst] = c->ctlpoint[i][1];
956             ++dst;
957           }
958
959       spline_solve (num_active_ctlpoints, xv, yv, y2v);
960
961       rx = min_x;
962       dx = (c->max_x - min_x) / (veclen - 1);
963       for (x = 0; x < veclen; ++x, rx += dx)
964         {
965           ry = spline_eval (num_active_ctlpoints, xv, yv, y2v, rx);
966           if (ry < c->min_y) ry = c->min_y;
967           if (ry > c->max_y) ry = c->max_y;
968           vector[x] = ry;
969         }
970
971       g_free (mem);
972       break;
973
974     case GTK_CURVE_TYPE_LINEAR:
975       dx = (c->max_x - min_x) / (veclen - 1);
976       rx = min_x;
977       ry = c->min_y;
978       dy = 0.0;
979       i  = first_active;
980       for (x = 0; x < veclen; ++x, rx += dx)
981         {
982           if (rx >= c->ctlpoint[i][0])
983             {
984               if (rx > c->ctlpoint[i][0])
985                 ry = c->min_y;
986               dy = 0.0;
987               next = i + 1;
988               while (next < c->num_ctlpoints
989                      && c->ctlpoint[next][0] <= c->ctlpoint[i][0])
990                 ++next;
991               if (next < c->num_ctlpoints)
992                 {
993                   delta_x = c->ctlpoint[next][0] - c->ctlpoint[i][0];
994                   dy = ((c->ctlpoint[next][1] - c->ctlpoint[i][1])
995                         / delta_x);
996                   dy *= dx;
997                   ry = c->ctlpoint[i][1];
998                   i = next;
999                 }
1000             }
1001           vector[x] = ry;
1002           ry += dy;
1003         }
1004       break;
1005
1006     case GTK_CURVE_TYPE_FREE:
1007       if (c->point)
1008         {
1009           rx = 0.0;
1010           dx = c->num_points / (double) veclen;
1011           for (x = 0; x < veclen; ++x, rx += dx)
1012             vector[x] = unproject (RADIUS + c->height - c->point[(int) rx].y,
1013                                    c->min_y, c->max_y,
1014                                    c->height);
1015         }
1016       else
1017         memset (vector, 0, veclen * sizeof (vector[0]));
1018       break;
1019     }
1020 }
1021
1022 GtkWidget*
1023 gtk_curve_new (void)
1024 {
1025   return g_object_new (GTK_TYPE_CURVE, NULL);
1026 }
1027
1028 static void
1029 gtk_curve_finalize (GObject *object)
1030 {
1031   GtkCurve *curve;
1032
1033   g_return_if_fail (GTK_IS_CURVE (object));
1034
1035   curve = GTK_CURVE (object);
1036   if (curve->pixmap)
1037     g_object_unref (curve->pixmap);
1038   if (curve->point)
1039     g_free (curve->point);
1040   if (curve->ctlpoint)
1041     g_free (curve->ctlpoint);
1042
1043   G_OBJECT_CLASS (parent_class)->finalize (object);
1044 }