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