1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program 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
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <string.h>
21 
22 #include <gegl.h>
23 #include <gtk/gtk.h>
24 #include <gdk/gdkkeysyms.h>
25 
26 #include "libgimpmath/gimpmath.h"
27 #include "libgimpcolor/gimpcolor.h"
28 #include "libgimpconfig/gimpconfig.h"
29 #include "libgimpwidgets/gimpwidgets.h"
30 
31 #include "widgets-types.h"
32 
33 #include "core/gimp.h"
34 #include "core/gimpcurve.h"
35 #include "core/gimpcurve-map.h"
36 #include "core/gimpmarshal.h"
37 
38 #include "gimpclipboard.h"
39 #include "gimpcurveview.h"
40 #include "gimpwidgets-utils.h"
41 
42 
43 #define POINT_MAX_DISTANCE 16.0
44 
45 
46 enum
47 {
48   PROP_0,
49   PROP_GIMP,
50   PROP_BASE_LINE,
51   PROP_GRID_ROWS,
52   PROP_GRID_COLUMNS,
53   PROP_X_AXIS_LABEL,
54   PROP_Y_AXIS_LABEL
55 };
56 
57 enum
58 {
59   SELECTION_CHANGED,
60   CUT_CLIPBOARD,
61   COPY_CLIPBOARD,
62   PASTE_CLIPBOARD,
63   LAST_SIGNAL
64 };
65 
66 
67 typedef struct
68 {
69   GimpCurve *curve;
70   GimpRGB    color;
71   gboolean   color_set;
72 } BGCurve;
73 
74 
75 static void       gimp_curve_view_finalize              (GObject          *object);
76 static void       gimp_curve_view_dispose               (GObject          *object);
77 static void       gimp_curve_view_set_property          (GObject          *object,
78                                                          guint             property_id,
79                                                          const GValue     *value,
80                                                          GParamSpec       *pspec);
81 static void       gimp_curve_view_get_property          (GObject          *object,
82                                                          guint             property_id,
83                                                          GValue           *value,
84                                                          GParamSpec       *pspec);
85 
86 static void       gimp_curve_view_style_set             (GtkWidget        *widget,
87                                                          GtkStyle         *prev_style);
88 static gboolean   gimp_curve_view_expose                (GtkWidget        *widget,
89                                                          GdkEventExpose   *event);
90 static gboolean   gimp_curve_view_button_press          (GtkWidget        *widget,
91                                                          GdkEventButton   *bevent);
92 static gboolean   gimp_curve_view_button_release        (GtkWidget        *widget,
93                                                          GdkEventButton   *bevent);
94 static gboolean   gimp_curve_view_motion_notify         (GtkWidget        *widget,
95                                                          GdkEventMotion   *bevent);
96 static gboolean   gimp_curve_view_leave_notify          (GtkWidget        *widget,
97                                                          GdkEventCrossing *cevent);
98 static gboolean   gimp_curve_view_key_press             (GtkWidget        *widget,
99                                                          GdkEventKey      *kevent);
100 
101 static void       gimp_curve_view_cut_clipboard         (GimpCurveView    *view);
102 static void       gimp_curve_view_copy_clipboard        (GimpCurveView    *view);
103 static void       gimp_curve_view_paste_clipboard       (GimpCurveView    *view);
104 
105 static void       gimp_curve_view_curve_dirty           (GimpCurve        *curve,
106                                                          GimpCurveView    *view);
107 static void       gimp_curve_view_curve_notify_n_points (GimpCurve        *curve,
108                                                          GParamSpec       *pspec,
109                                                          GimpCurveView    *view);
110 
111 static void       gimp_curve_view_set_cursor            (GimpCurveView    *view,
112                                                          gdouble           x,
113                                                          gdouble           y);
114 static void       gimp_curve_view_unset_cursor          (GimpCurveView *view);
115 
116 
117 G_DEFINE_TYPE (GimpCurveView, gimp_curve_view,
118                GIMP_TYPE_HISTOGRAM_VIEW)
119 
120 #define parent_class gimp_curve_view_parent_class
121 
122 static guint curve_view_signals[LAST_SIGNAL] = { 0 };
123 
124 
125 static void
gimp_curve_view_class_init(GimpCurveViewClass * klass)126 gimp_curve_view_class_init (GimpCurveViewClass *klass)
127 {
128   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
129   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
130   GtkBindingSet  *binding_set;
131 
132   object_class->finalize             = gimp_curve_view_finalize;
133   object_class->dispose              = gimp_curve_view_dispose;
134   object_class->set_property         = gimp_curve_view_set_property;
135   object_class->get_property         = gimp_curve_view_get_property;
136 
137   widget_class->style_set            = gimp_curve_view_style_set;
138   widget_class->expose_event         = gimp_curve_view_expose;
139   widget_class->button_press_event   = gimp_curve_view_button_press;
140   widget_class->button_release_event = gimp_curve_view_button_release;
141   widget_class->motion_notify_event  = gimp_curve_view_motion_notify;
142   widget_class->leave_notify_event   = gimp_curve_view_leave_notify;
143   widget_class->key_press_event      = gimp_curve_view_key_press;
144 
145   klass->selection_changed           = NULL;
146   klass->cut_clipboard               = gimp_curve_view_cut_clipboard;
147   klass->copy_clipboard              = gimp_curve_view_copy_clipboard;
148   klass->paste_clipboard             = gimp_curve_view_paste_clipboard;
149 
150   g_object_class_install_property (object_class, PROP_GIMP,
151                                    g_param_spec_object ("gimp",
152                                                         NULL, NULL,
153                                                         GIMP_TYPE_GIMP,
154                                                         GIMP_PARAM_READWRITE));
155 
156   g_object_class_install_property (object_class, PROP_BASE_LINE,
157                                    g_param_spec_boolean ("base-line",
158                                                          NULL, NULL,
159                                                          TRUE,
160                                                          GIMP_PARAM_READWRITE |
161                                                          G_PARAM_CONSTRUCT_ONLY));
162 
163   g_object_class_install_property (object_class, PROP_GRID_ROWS,
164                                    g_param_spec_int ("grid-rows", NULL, NULL,
165                                                      0, 100, 8,
166                                                      GIMP_PARAM_READWRITE |
167                                                      G_PARAM_CONSTRUCT_ONLY));
168 
169   g_object_class_install_property (object_class, PROP_GRID_COLUMNS,
170                                    g_param_spec_int ("grid-columns", NULL, NULL,
171                                                      0, 100, 8,
172                                                      GIMP_PARAM_READWRITE |
173                                                      G_PARAM_CONSTRUCT_ONLY));
174 
175   g_object_class_install_property (object_class, PROP_X_AXIS_LABEL,
176                                    g_param_spec_string ("x-axis-label", NULL, NULL,
177                                                         NULL,
178                                                         GIMP_PARAM_READWRITE));
179 
180   g_object_class_install_property (object_class, PROP_Y_AXIS_LABEL,
181                                    g_param_spec_string ("y-axis-label", NULL, NULL,
182                                                         NULL,
183                                                         GIMP_PARAM_READWRITE));
184 
185   curve_view_signals[SELECTION_CHANGED] =
186     g_signal_new ("selection-changed",
187                   G_TYPE_FROM_CLASS (klass),
188                   G_SIGNAL_RUN_FIRST,
189                   G_STRUCT_OFFSET (GimpCurveViewClass, selection_changed),
190                   NULL, NULL,
191                   gimp_marshal_VOID__VOID,
192                   G_TYPE_NONE, 0);
193 
194   curve_view_signals[CUT_CLIPBOARD] =
195     g_signal_new ("cut-clipboard",
196                   G_TYPE_FROM_CLASS (klass),
197                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
198                   G_STRUCT_OFFSET (GimpCurveViewClass, cut_clipboard),
199                   NULL, NULL,
200                   gimp_marshal_VOID__VOID,
201                   G_TYPE_NONE, 0);
202 
203   curve_view_signals[COPY_CLIPBOARD] =
204     g_signal_new ("copy-clipboard",
205                   G_TYPE_FROM_CLASS (klass),
206                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
207                   G_STRUCT_OFFSET (GimpCurveViewClass, copy_clipboard),
208                   NULL, NULL,
209                   gimp_marshal_VOID__VOID,
210                   G_TYPE_NONE, 0);
211 
212   curve_view_signals[PASTE_CLIPBOARD] =
213     g_signal_new ("paste-clipboard",
214                   G_TYPE_FROM_CLASS (klass),
215                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
216                   G_STRUCT_OFFSET (GimpCurveViewClass, paste_clipboard),
217                   NULL, NULL,
218                   gimp_marshal_VOID__VOID,
219                   G_TYPE_NONE, 0);
220 
221   binding_set = gtk_binding_set_by_class (klass);
222 
223   gtk_binding_entry_add_signal (binding_set, GDK_KEY_x, GDK_CONTROL_MASK,
224                                 "cut-clipboard", 0);
225   gtk_binding_entry_add_signal (binding_set, GDK_KEY_c, GDK_CONTROL_MASK,
226                                 "copy-clipboard", 0);
227   gtk_binding_entry_add_signal (binding_set, GDK_KEY_v, GDK_CONTROL_MASK,
228                                 "paste-clipboard", 0);
229 }
230 
231 static void
gimp_curve_view_init(GimpCurveView * view)232 gimp_curve_view_init (GimpCurveView *view)
233 {
234   view->curve       = NULL;
235   view->selected    = -1;
236   view->offset_x    = 0.0;
237   view->offset_y    = 0.0;
238   view->last_x      = 0.0;
239   view->last_y      = 0.0;
240   view->cursor_type = -1;
241   view->xpos        = -1.0;
242   view->cursor_x    = -1.0;
243   view->cursor_y    = -1.0;
244   view->range_x_min = 0.0;
245   view->range_x_max = 1.0;
246   view->range_y_min = 0.0;
247   view->range_y_max = 1.0;
248 
249   view->x_axis_label = NULL;
250   view->y_axis_label = NULL;
251 
252   gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE);
253   gtk_widget_add_events (GTK_WIDGET (view),
254                          GDK_BUTTON_PRESS_MASK   |
255                          GDK_BUTTON_RELEASE_MASK |
256                          GDK_BUTTON1_MOTION_MASK |
257                          GDK_POINTER_MOTION_MASK |
258                          GDK_KEY_PRESS_MASK      |
259                          GDK_LEAVE_NOTIFY_MASK);
260 }
261 
262 static void
gimp_curve_view_finalize(GObject * object)263 gimp_curve_view_finalize (GObject *object)
264 {
265   GimpCurveView *view = GIMP_CURVE_VIEW (object);
266 
267   g_clear_object (&view->orig_curve);
268 
269   g_clear_object (&view->layout);
270   g_clear_object (&view->cursor_layout);
271 
272   g_clear_pointer (&view->x_axis_label, g_free);
273   g_clear_pointer (&view->y_axis_label, g_free);
274 
275   G_OBJECT_CLASS (parent_class)->finalize (object);
276 }
277 
278 static void
gimp_curve_view_dispose(GObject * object)279 gimp_curve_view_dispose (GObject *object)
280 {
281   GimpCurveView *view = GIMP_CURVE_VIEW (object);
282 
283   gimp_curve_view_set_curve (view, NULL, NULL);
284 
285   if (view->bg_curves)
286     gimp_curve_view_remove_all_backgrounds (view);
287 
288   G_OBJECT_CLASS (parent_class)->dispose (object);
289 }
290 
291 static void
gimp_curve_view_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)292 gimp_curve_view_set_property (GObject      *object,
293                               guint         property_id,
294                               const GValue *value,
295                               GParamSpec   *pspec)
296 {
297   GimpCurveView *view = GIMP_CURVE_VIEW (object);
298 
299   switch (property_id)
300     {
301     case PROP_GIMP:
302       view->gimp = g_value_get_object (value); /* don't ref */
303       break;
304     case PROP_GRID_ROWS:
305       view->grid_rows = g_value_get_int (value);
306       break;
307     case PROP_GRID_COLUMNS:
308       view->grid_columns = g_value_get_int (value);
309       break;
310     case PROP_BASE_LINE:
311       view->draw_base_line = g_value_get_boolean (value);
312       break;
313     case PROP_X_AXIS_LABEL:
314       gimp_curve_view_set_x_axis_label (view, g_value_get_string (value));
315       break;
316     case PROP_Y_AXIS_LABEL:
317       gimp_curve_view_set_y_axis_label (view, g_value_get_string (value));
318       break;
319     default:
320       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
321       break;
322     }
323 }
324 
325 static void
gimp_curve_view_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)326 gimp_curve_view_get_property (GObject    *object,
327                               guint       property_id,
328                               GValue     *value,
329                               GParamSpec *pspec)
330 {
331   GimpCurveView *view = GIMP_CURVE_VIEW (object);
332 
333   switch (property_id)
334     {
335     case PROP_GIMP:
336       g_value_set_object (value, view->gimp);
337       break;
338     case PROP_GRID_ROWS:
339       g_value_set_int (value, view->grid_rows);
340       break;
341     case PROP_GRID_COLUMNS:
342       g_value_set_int (value, view->grid_columns);
343       break;
344     case PROP_BASE_LINE:
345       g_value_set_boolean (value, view->draw_base_line);
346       break;
347     case PROP_X_AXIS_LABEL:
348       g_value_set_string (value, view->x_axis_label);
349       break;
350     case PROP_Y_AXIS_LABEL:
351       g_value_set_string (value, view->y_axis_label);
352       break;
353     default:
354       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
355       break;
356     }
357 }
358 
359 static void
gimp_curve_view_style_set(GtkWidget * widget,GtkStyle * prev_style)360 gimp_curve_view_style_set (GtkWidget *widget,
361                            GtkStyle  *prev_style)
362 {
363   GimpCurveView *view = GIMP_CURVE_VIEW (widget);
364 
365   GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
366 
367   g_clear_object (&view->layout);
368   g_clear_object (&view->cursor_layout);
369 }
370 
371 static void
gimp_curve_view_draw_grid(GimpCurveView * view,cairo_t * cr,gint width,gint height,gint border)372 gimp_curve_view_draw_grid (GimpCurveView *view,
373                            cairo_t       *cr,
374                            gint           width,
375                            gint           height,
376                            gint           border)
377 {
378   gint i;
379 
380   for (i = 1; i < view->grid_rows; i++)
381     {
382       gint y = i * (height - 1) / view->grid_rows;
383 
384       if ((view->grid_rows % 2) == 0 && (i == view->grid_rows / 2))
385         continue;
386 
387       cairo_move_to (cr, border,             border + y);
388       cairo_line_to (cr, border + width - 1, border + y);
389     }
390 
391   for (i = 1; i < view->grid_columns; i++)
392     {
393       gint x = i * (width - 1) / view->grid_columns;
394 
395       if ((view->grid_columns % 2) == 0 && (i == view->grid_columns / 2))
396         continue;
397 
398       cairo_move_to (cr, border + x, border);
399       cairo_line_to (cr, border + x, border + height - 1);
400     }
401 
402   if (view->draw_base_line)
403     {
404       cairo_move_to (cr, border, border + height - 1);
405       cairo_line_to (cr, border + width - 1, border);
406     }
407 
408   cairo_set_line_width (cr, 0.6);
409   cairo_stroke (cr);
410 
411   if ((view->grid_rows % 2) == 0)
412     {
413       gint y = (height - 1) / 2;
414 
415       cairo_move_to (cr, border,             border + y);
416       cairo_line_to (cr, border + width - 1, border + y);
417     }
418 
419   if ((view->grid_columns % 2) == 0)
420     {
421       gint x = (width - 1) / 2;
422 
423       cairo_move_to (cr, border + x, border);
424       cairo_line_to (cr, border + x, border + height - 1);
425     }
426 
427   cairo_set_line_width (cr, 1.0);
428   cairo_stroke (cr);
429 }
430 
431 static void
gimp_curve_view_draw_point(GimpCurveView * view,cairo_t * cr,gint i,gint width,gint height,gint border)432 gimp_curve_view_draw_point (GimpCurveView *view,
433                             cairo_t       *cr,
434                             gint           i,
435                             gint           width,
436                             gint           height,
437                             gint           border)
438 {
439   gdouble x, y;
440 
441   gimp_curve_get_point (view->curve, i, &x, &y);
442 
443   y = 1.0 - y;
444 
445 #define CIRCLE_RADIUS  3
446 #define DIAMOND_RADIUS (G_SQRT2 * CIRCLE_RADIUS)
447 
448   switch (gimp_curve_get_point_type (view->curve, i))
449     {
450     case GIMP_CURVE_POINT_SMOOTH:
451       cairo_move_to (cr,
452                      border + (gdouble) (width  - 1) * x + CIRCLE_RADIUS,
453                      border + (gdouble) (height - 1) * y);
454       cairo_arc     (cr,
455                      border + (gdouble) (width  - 1) * x,
456                      border + (gdouble) (height - 1) * y,
457                      CIRCLE_RADIUS,
458                      0, 2 * G_PI);
459       break;
460 
461     case GIMP_CURVE_POINT_CORNER:
462       cairo_move_to    (cr,
463                         border + (gdouble) (width  - 1) * x,
464                         border + (gdouble) (height - 1) * y - DIAMOND_RADIUS);
465       cairo_line_to    (cr,
466                         border + (gdouble) (width  - 1) * x + DIAMOND_RADIUS,
467                         border + (gdouble) (height - 1) * y);
468       cairo_line_to    (cr,
469                         border + (gdouble) (width  - 1) * x,
470                         border + (gdouble) (height - 1) * y + DIAMOND_RADIUS);
471       cairo_line_to    (cr,
472                         border + (gdouble) (width  - 1) * x - DIAMOND_RADIUS,
473                         border + (gdouble) (height - 1) * y);
474       cairo_close_path (cr);
475       break;
476     }
477 }
478 
479 static void
gimp_curve_view_draw_curve(GimpCurveView * view,cairo_t * cr,GimpCurve * curve,gint width,gint height,gint border)480 gimp_curve_view_draw_curve (GimpCurveView *view,
481                             cairo_t       *cr,
482                             GimpCurve     *curve,
483                             gint           width,
484                             gint           height,
485                             gint           border)
486 {
487   gdouble x, y;
488   gint    i;
489 
490   x = 0.0;
491   y = 1.0 - gimp_curve_map_value (curve, 0.0);
492 
493   cairo_move_to (cr,
494                  border + (gdouble) (width  - 1) * x,
495                  border + (gdouble) (height - 1)* y);
496 
497   for (i = 1; i < 256; i++)
498     {
499       x = (gdouble) i / 255.0;
500       y = 1.0 - gimp_curve_map_value (curve, x);
501 
502       cairo_line_to (cr,
503                      border + (gdouble) (width  - 1) * x,
504                      border + (gdouble) (height - 1) * y);
505     }
506 
507   cairo_stroke (cr);
508 }
509 
510 static gboolean
gimp_curve_view_expose(GtkWidget * widget,GdkEventExpose * event)511 gimp_curve_view_expose (GtkWidget      *widget,
512                         GdkEventExpose *event)
513 {
514   GimpCurveView *view   = GIMP_CURVE_VIEW (widget);
515   GdkWindow     *window = gtk_widget_get_window (widget);
516   GtkStyle      *style  = gtk_widget_get_style (widget);
517   GtkAllocation  allocation;
518   cairo_t       *cr;
519   GList         *list;
520   gint           border;
521   gint           width;
522   gint           height;
523   gint           layout_x;
524   gint           layout_y;
525   gdouble        x, y;
526   gint           i;
527 
528   GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
529 
530   if (! view->curve)
531     return FALSE;
532 
533   gtk_widget_get_allocation (widget, &allocation);
534 
535   border = GIMP_HISTOGRAM_VIEW (view)->border_width;
536   width  = allocation.width  - 2 * border;
537   height = allocation.height - 2 * border;
538 
539   cr = gdk_cairo_create (gtk_widget_get_window (widget));
540 
541   gdk_cairo_region (cr, event->region);
542   cairo_clip (cr);
543 
544   if (gtk_widget_has_focus (widget))
545     {
546       gtk_paint_focus (style, window,
547                        gtk_widget_get_state (widget),
548                        &event->area, widget, NULL,
549                        border - 2, border - 2,
550                        width + 4, height + 4);
551     }
552 
553   cairo_set_line_width (cr, 1.0);
554   cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
555   cairo_translate (cr, 0.5, 0.5);
556 
557   /*  Draw the grid lines  */
558   gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]);
559 
560   gimp_curve_view_draw_grid (view, cr, width, height, border);
561 
562   /*  Draw the axis labels  */
563 
564   if (view->x_axis_label)
565     {
566       if (! view->layout)
567         view->layout = gtk_widget_create_pango_layout (widget, NULL);
568 
569       pango_layout_set_text (view->layout, view->x_axis_label, -1);
570       pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y);
571 
572       cairo_move_to (cr,
573                      width - border - layout_x,
574                      height - border - layout_y);
575 
576       gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
577       pango_cairo_show_layout (cr, view->layout);
578     }
579 
580   if (view->y_axis_label)
581     {
582       if (! view->layout)
583         view->layout = gtk_widget_create_pango_layout (widget, NULL);
584 
585       pango_layout_set_text (view->layout, view->y_axis_label, -1);
586       pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y);
587 
588       cairo_save (cr);
589 
590       cairo_move_to (cr,
591                      2 * border,
592                      2 * border + layout_x);
593       cairo_rotate (cr, - G_PI / 2);
594 
595       gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
596       pango_cairo_show_layout (cr, view->layout);
597 
598       cairo_restore (cr);
599     }
600 
601 
602   /*  Draw the background curves  */
603   for (list = view->bg_curves; list; list = g_list_next (list))
604     {
605       BGCurve *bg = list->data;
606 
607       if (bg->color_set)
608         {
609           cairo_set_source_rgba (cr,
610                                  bg->color.r,
611                                  bg->color.g,
612                                  bg->color.b,
613                                  0.5);
614         }
615       else
616         {
617           cairo_set_source_rgba (cr,
618                                  style->text[GTK_STATE_NORMAL].red / 65535.0,
619                                  style->text[GTK_STATE_NORMAL].green / 65535.0,
620                                  style->text[GTK_STATE_NORMAL].blue / 65535.0,
621                                  0.5);
622         }
623 
624       gimp_curve_view_draw_curve (view, cr, bg->curve,
625                                   width, height, border);
626     }
627 
628   /*  Draw the curve  */
629   if (view->curve_color)
630     gimp_cairo_set_source_rgb (cr, view->curve_color);
631   else
632     gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
633 
634   gimp_curve_view_draw_curve (view, cr, view->curve,
635                               width, height, border);
636 
637   /*  Draw the points  */
638   if (gimp_curve_get_curve_type (view->curve) == GIMP_CURVE_SMOOTH)
639     {
640       gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
641 
642       /*  Draw the unselected points  */
643       for (i = 0; i < view->curve->n_points; i++)
644         {
645           if (i == view->selected)
646             continue;
647 
648           gimp_curve_view_draw_point (view, cr, i, width, height, border);
649         }
650 
651       cairo_stroke (cr);
652 
653       /*  Draw the selected point  */
654       if (view->selected != -1)
655         {
656           gimp_curve_view_draw_point (view, cr, view->selected,
657                                       width, height, border);
658           cairo_fill (cr);
659        }
660     }
661 
662   if (view->xpos >= 0.0)
663     {
664       gchar buf[32];
665 
666       gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
667 
668       /* draw the color line */
669       cairo_move_to (cr,
670                      border + ROUND ((gdouble) (width - 1) * view->xpos),
671                      border + 1);
672       cairo_line_to (cr,
673                      border + ROUND ((gdouble) (width - 1) * view->xpos),
674                      border + height - 1);
675       cairo_stroke (cr);
676 
677       if (view->range_x_max == 255.0)
678         {
679           /*  stupid heuristic: special-case for 0..255  */
680 
681           g_snprintf (buf, sizeof (buf), "x:%3d",
682                       (gint) (view->xpos *
683                               (view->range_x_max - view->range_x_min) +
684                               view->range_x_min));
685         }
686       else if (view->range_x_max == 100.0)
687         {
688           /*  and for 0..100  */
689 
690           g_snprintf (buf, sizeof (buf), "x:%0.2f",
691                       view->xpos *
692                       (view->range_x_max - view->range_x_min) +
693                       view->range_x_min);
694         }
695       else
696         {
697           g_snprintf (buf, sizeof (buf), "x:%0.3f",
698                       view->xpos *
699                       (view->range_x_max - view->range_x_min) +
700                       view->range_x_min);
701         }
702 
703       if (! view->layout)
704         view->layout = gtk_widget_create_pango_layout (widget, NULL);
705 
706       pango_layout_set_text (view->layout, buf, -1);
707       pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y);
708 
709       if (view->xpos < 0.5)
710         layout_x = border;
711       else
712         layout_x = -(layout_x + border);
713 
714       cairo_move_to (cr,
715                      border + (gdouble) width * view->xpos + layout_x,
716                      border + height - border - layout_y);
717       pango_cairo_show_layout (cr, view->layout);
718     }
719 
720   if (view->cursor_x >= 0.0 && view->cursor_x <= 1.0 &&
721       view->cursor_y >= 0.0 && view->cursor_y <= 1.0)
722     {
723       gchar  buf[32];
724       gint   w, h;
725 
726       if (! view->cursor_layout)
727         view->cursor_layout = gtk_widget_create_pango_layout (widget, NULL);
728 
729       if (view->range_x_max == 255.0 &&
730           view->range_y_max == 255.0)
731         {
732           /*  stupid heuristic: special-case for 0..255  */
733 
734           g_snprintf (buf, sizeof (buf), "x:%3d y:%3d",
735                       (gint) round (view->cursor_x *
736                                     (view->range_x_max - view->range_x_min) +
737                                     view->range_x_min),
738                       (gint) round ((1.0 - view->cursor_y) *
739                                     (view->range_y_max - view->range_y_min) +
740                                     view->range_y_min));
741         }
742       else if (view->range_x_max == 100.0 &&
743                view->range_y_max == 100.0)
744         {
745           /*  and for 0..100  */
746 
747           g_snprintf (buf, sizeof (buf), "x:%0.2f y:%0.2f",
748                       view->cursor_x *
749                       (view->range_x_max - view->range_x_min) +
750                       view->range_x_min,
751                       (1.0 - view->cursor_y) *
752                       (view->range_y_max - view->range_y_min) +
753                       view->range_y_min);
754         }
755       else
756         {
757           g_snprintf (buf, sizeof (buf), "x:%0.3f y:%0.3f",
758                       view->cursor_x *
759                       (view->range_x_max - view->range_x_min) +
760                       view->range_x_min,
761                       (1.0 - view->cursor_y) *
762                       (view->range_y_max - view->range_y_min) +
763                       view->range_y_min);
764         }
765 
766       pango_layout_set_text (view->cursor_layout, buf, -1);
767       pango_layout_get_pixel_extents (view->cursor_layout,
768                                       NULL, &view->cursor_rect);
769 
770       x = border * 2 + 3;
771       y = border * 2 + 3;
772       w = view->cursor_rect.width;
773       h = view->cursor_rect.height;
774 
775       if (view->x_axis_label)
776         x += border + view->cursor_rect.height; /* coincidentially the right value */
777 
778       cairo_push_group (cr);
779 
780       gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
781       cairo_rectangle (cr, x + 0.5, y + 0.5, w, h);
782       cairo_fill_preserve (cr);
783 
784       cairo_set_line_width (cr, 6);
785       cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
786       cairo_stroke (cr);
787 
788       gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
789       cairo_move_to (cr, x, y);
790       pango_cairo_show_layout (cr, view->cursor_layout);
791 
792       cairo_pop_group_to_source (cr);
793       cairo_paint_with_alpha (cr, 0.6);
794     }
795 
796   cairo_destroy (cr);
797 
798   return FALSE;
799 }
800 
801 static void
set_cursor(GimpCurveView * view,GdkCursorType new_cursor)802 set_cursor (GimpCurveView *view,
803             GdkCursorType  new_cursor)
804 {
805   if (new_cursor != view->cursor_type)
806     {
807       GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (view));
808       GdkCursor  *cursor  = gdk_cursor_new_for_display (display, new_cursor);
809 
810       gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (view)), cursor);
811       gdk_cursor_unref (cursor);
812 
813       view->cursor_type = new_cursor;
814     }
815 }
816 
817 static gboolean
gimp_curve_view_button_press(GtkWidget * widget,GdkEventButton * bevent)818 gimp_curve_view_button_press (GtkWidget      *widget,
819                               GdkEventButton *bevent)
820 {
821   GimpCurveView *view  = GIMP_CURVE_VIEW (widget);
822   GimpCurve     *curve = view->curve;
823   GtkAllocation  allocation;
824   gint           border;
825   gint           width, height;
826   gdouble        x;
827   gdouble        y;
828   gint           point;
829   gdouble        point_x;
830   gdouble        point_y;
831 
832   if (! curve || bevent->button != 1)
833     return TRUE;
834 
835   gtk_widget_get_allocation (widget, &allocation);
836 
837   border = GIMP_HISTOGRAM_VIEW (view)->border_width;
838   width  = allocation.width  - 2 * border;
839   height = allocation.height - 2 * border;
840 
841   x = (gdouble) (bevent->x - border) / (gdouble) width;
842   y = (gdouble) (bevent->y - border) / (gdouble) height;
843 
844   x = CLAMP (x, 0.0, 1.0);
845   y = CLAMP (y, 0.0, 1.0);
846 
847   view->grabbed = TRUE;
848 
849   view->orig_curve = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (curve)));
850 
851   set_cursor (view, GDK_TCROSS);
852 
853   switch (gimp_curve_get_curve_type (curve))
854     {
855     case GIMP_CURVE_SMOOTH:
856       point = gimp_curve_get_closest_point (curve, x, 1.0 - y,
857                                             POINT_MAX_DISTANCE /
858                                             MAX (width, height));
859 
860       if (point < 0)
861         {
862           GimpCurvePointType type = GIMP_CURVE_POINT_SMOOTH;
863 
864           if (bevent->state & gimp_get_constrain_behavior_mask ())
865             y = 1.0 - gimp_curve_map_value (view->orig_curve, x);
866 
867           if (view->selected >= 0)
868             type = gimp_curve_get_point_type (curve, view->selected);
869 
870           point = gimp_curve_add_point (curve, x, 1.0 - y);
871 
872           gimp_curve_set_point_type (curve, point, type);
873         }
874 
875       if (point > 0)
876         gimp_curve_get_point (curve, point - 1, &view->leftmost, NULL);
877       else
878         view->leftmost = -1.0;
879 
880       if (point < gimp_curve_get_n_points (curve) - 1)
881         gimp_curve_get_point (curve, point + 1, &view->rightmost, NULL);
882       else
883         view->rightmost = 2.0;
884 
885       gimp_curve_view_set_selected (view, point);
886 
887       gimp_curve_get_point (curve, point, &point_x, &point_y);
888 
889       view->offset_x = point_x         - x;
890       view->offset_y = (1.0 - point_y) - y;
891 
892       view->point_type = gimp_curve_get_point_type (curve, point);
893       break;
894 
895     case GIMP_CURVE_FREE:
896       view->last_x = x;
897       view->last_y = y;
898 
899       gimp_curve_set_curve (curve, x, 1.0 - y);
900       break;
901     }
902 
903   if (! gtk_widget_has_focus (widget))
904     gtk_widget_grab_focus (widget);
905 
906   return TRUE;
907 }
908 
909 static gboolean
gimp_curve_view_button_release(GtkWidget * widget,GdkEventButton * bevent)910 gimp_curve_view_button_release (GtkWidget      *widget,
911                                 GdkEventButton *bevent)
912 {
913   GimpCurveView *view = GIMP_CURVE_VIEW (widget);
914 
915   if (bevent->button != 1)
916     return TRUE;
917 
918   g_clear_object (&view->orig_curve);
919 
920   view->offset_x = 0.0;
921   view->offset_y = 0.0;
922 
923   view->grabbed = FALSE;
924 
925   set_cursor (view, GDK_FLEUR);
926 
927   return TRUE;
928 }
929 
930 static gboolean
gimp_curve_view_motion_notify(GtkWidget * widget,GdkEventMotion * mevent)931 gimp_curve_view_motion_notify (GtkWidget      *widget,
932                                GdkEventMotion *mevent)
933 {
934   GimpCurveView  *view       = GIMP_CURVE_VIEW (widget);
935   GimpCurve      *curve      = view->curve;
936   GtkAllocation   allocation;
937   GdkCursorType   new_cursor = GDK_X_CURSOR;
938   gint            border;
939   gint            width, height;
940   gdouble         x;
941   gdouble         y;
942   gdouble         point_x;
943   gdouble         point_y;
944   gint            point;
945 
946   if (! curve)
947     return TRUE;
948 
949   gtk_widget_get_allocation (widget, &allocation);
950 
951   border = GIMP_HISTOGRAM_VIEW (view)->border_width;
952   width  = allocation.width  - 2 * border;
953   height = allocation.height - 2 * border;
954 
955   x = (gdouble) (mevent->x - border) / (gdouble) width;
956   y = (gdouble) (mevent->y - border) / (gdouble) height;
957 
958   x += view->offset_x;
959   y += view->offset_y;
960 
961   x = CLAMP (x, 0.0, 1.0);
962   y = CLAMP (y, 0.0, 1.0);
963 
964   switch (gimp_curve_get_curve_type (curve))
965     {
966     case GIMP_CURVE_SMOOTH:
967       if (! view->grabbed) /*  If no point is grabbed...  */
968         {
969           point = gimp_curve_get_closest_point (curve, x, 1.0 - y,
970                                                 POINT_MAX_DISTANCE /
971                                                 MAX (width, height));
972 
973           if (point >= 0)
974             {
975               gimp_curve_get_point (curve, point, &point_x, &point_y);
976 
977               new_cursor = GDK_FLEUR;
978 
979               x = point_x;
980               y = 1.0 - point_y;
981             }
982           else
983             {
984               new_cursor = GDK_TCROSS;
985 
986               if (mevent->state & gimp_get_constrain_behavior_mask ())
987                 y = 1.0 - gimp_curve_map_value (view->curve, x);
988             }
989         }
990       else /*  Else, drag the grabbed point  */
991         {
992           new_cursor = GDK_TCROSS;
993 
994           if (mevent->state & gimp_get_constrain_behavior_mask ())
995             y = 1.0 - gimp_curve_map_value (view->orig_curve, x);
996 
997           gimp_data_freeze (GIMP_DATA (curve));
998 
999           if (x > view->leftmost && x < view->rightmost)
1000             {
1001               if (view->selected < 0)
1002                 {
1003                   gimp_curve_view_set_selected (
1004                     view,
1005                     gimp_curve_add_point (curve, x, 1.0 - y));
1006 
1007                   gimp_curve_set_point_type (curve,
1008                                              view->selected, view->point_type);
1009                 }
1010               else
1011                 {
1012                   gimp_curve_set_point (curve, view->selected, x, 1.0 - y);
1013                 }
1014             }
1015           else
1016             {
1017               if (view->selected >= 0)
1018                 {
1019                   gimp_curve_delete_point (curve, view->selected);
1020 
1021                   gimp_curve_view_set_selected (view, -1);
1022                 }
1023             }
1024 
1025           gimp_data_thaw (GIMP_DATA (curve));
1026         }
1027       break;
1028 
1029     case GIMP_CURVE_FREE:
1030       if (view->grabbed)
1031         {
1032           gint    n_samples = gimp_curve_get_n_samples (curve);
1033           gdouble x1, x2;
1034           gdouble y1, y2;
1035 
1036           if (view->last_x > x)
1037             {
1038               x1 = x;
1039               x2 = view->last_x;
1040               y1 = y;
1041               y2 = view->last_y;
1042             }
1043           else
1044             {
1045               x1 = view->last_x;
1046               x2 = x;
1047               y1 = view->last_y;
1048               y2 = y;
1049             }
1050 
1051           if (x2 != x1)
1052             {
1053               gint from = ROUND (x1 * (gdouble) (n_samples - 1));
1054               gint to   = ROUND (x2 * (gdouble) (n_samples - 1));
1055               gint i;
1056 
1057               gimp_data_freeze (GIMP_DATA (curve));
1058 
1059               for (i = from; i <= to; i++)
1060                 {
1061                   gdouble xpos = (gdouble) i / (gdouble) (n_samples - 1);
1062                   gdouble ypos = (y1 + ((y2 - y1) * (xpos - x1)) / (x2 - x1));
1063 
1064                   xpos = CLAMP (xpos, 0.0, 1.0);
1065                   ypos = CLAMP (ypos, 0.0, 1.0);
1066 
1067                   gimp_curve_set_curve (curve, xpos, 1.0 - ypos);
1068                 }
1069 
1070               gimp_data_thaw (GIMP_DATA (curve));
1071             }
1072           else
1073             {
1074               gimp_curve_set_curve (curve, x, 1.0 - y);
1075             }
1076 
1077           view->last_x = x;
1078           view->last_y = y;
1079         }
1080 
1081       if (mevent->state & GDK_BUTTON1_MASK)
1082         new_cursor = GDK_TCROSS;
1083       else
1084         new_cursor = GDK_PENCIL;
1085 
1086       break;
1087     }
1088 
1089   set_cursor (view, new_cursor);
1090 
1091   gimp_curve_view_set_cursor (view, x, y);
1092 
1093   return TRUE;
1094 }
1095 
1096 static gboolean
gimp_curve_view_leave_notify(GtkWidget * widget,GdkEventCrossing * cevent)1097 gimp_curve_view_leave_notify (GtkWidget        *widget,
1098                               GdkEventCrossing *cevent)
1099 {
1100   GimpCurveView *view = GIMP_CURVE_VIEW (widget);
1101 
1102   gimp_curve_view_unset_cursor (view);
1103 
1104   return TRUE;
1105 }
1106 
1107 static gboolean
gimp_curve_view_key_press(GtkWidget * widget,GdkEventKey * kevent)1108 gimp_curve_view_key_press (GtkWidget   *widget,
1109                            GdkEventKey *kevent)
1110 {
1111   GimpCurveView *view    = GIMP_CURVE_VIEW (widget);
1112   GimpCurve     *curve   = view->curve;
1113   gboolean       handled = FALSE;
1114 
1115   if (! view->grabbed                                        &&
1116       curve                                                  &&
1117       gimp_curve_get_curve_type (curve) == GIMP_CURVE_SMOOTH &&
1118       view->selected                    >= 0)
1119     {
1120       gint    i = view->selected;
1121       gdouble x, y;
1122 
1123       gimp_curve_get_point (curve, i, NULL, &y);
1124 
1125       switch (kevent->keyval)
1126         {
1127         case GDK_KEY_Left:
1128           for (i = i - 1; i >= 0 && ! handled; i--)
1129             {
1130               gimp_curve_get_point (curve, i, &x, NULL);
1131 
1132               if (x >= 0.0)
1133                 {
1134                   gimp_curve_view_set_selected (view, i);
1135 
1136                   handled = TRUE;
1137                 }
1138             }
1139           break;
1140 
1141         case GDK_KEY_Right:
1142           for (i = i + 1; i < curve->n_points && ! handled; i++)
1143             {
1144               gimp_curve_get_point (curve, i, &x, NULL);
1145 
1146               if (x >= 0.0)
1147                 {
1148                   gimp_curve_view_set_selected (view, i);
1149 
1150                   handled = TRUE;
1151                 }
1152             }
1153           break;
1154 
1155         case GDK_KEY_Up:
1156           if (y < 1.0)
1157             {
1158               y = y + (kevent->state & GDK_SHIFT_MASK ?
1159                        (16.0 / 255.0) : (1.0 / 255.0));
1160 
1161               gimp_curve_move_point (curve, i, CLAMP (y, 0.0, 1.0));
1162 
1163               handled = TRUE;
1164             }
1165           break;
1166 
1167         case GDK_KEY_Down:
1168           if (y > 0)
1169             {
1170               y = y - (kevent->state & GDK_SHIFT_MASK ?
1171                        (16.0 / 255.0) : (1.0 / 255.0));
1172 
1173               gimp_curve_move_point (curve, i, CLAMP (y, 0.0, 1.0));
1174 
1175               handled = TRUE;
1176             }
1177           break;
1178 
1179         case GDK_KEY_Delete:
1180           gimp_curve_delete_point (curve, i);
1181           break;
1182 
1183         default:
1184           break;
1185         }
1186     }
1187 
1188   if (handled)
1189     {
1190       set_cursor (view, GDK_TCROSS);
1191 
1192       return TRUE;
1193     }
1194 
1195   return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, kevent);
1196 }
1197 
1198 static void
gimp_curve_view_cut_clipboard(GimpCurveView * view)1199 gimp_curve_view_cut_clipboard (GimpCurveView *view)
1200 {
1201   g_printerr ("%s\n", G_STRFUNC);
1202 
1203   if (! view->curve || ! view->gimp)
1204     {
1205       gtk_widget_error_bell (GTK_WIDGET (view));
1206       return;
1207     }
1208 
1209   gimp_curve_view_copy_clipboard (view);
1210 
1211   gimp_curve_reset (view->curve, FALSE);
1212 }
1213 
1214 static void
gimp_curve_view_copy_clipboard(GimpCurveView * view)1215 gimp_curve_view_copy_clipboard (GimpCurveView *view)
1216 {
1217   GimpCurve *copy;
1218 
1219   g_printerr ("%s\n", G_STRFUNC);
1220 
1221   if (! view->curve || ! view->gimp)
1222     {
1223       gtk_widget_error_bell (GTK_WIDGET (view));
1224       return;
1225     }
1226 
1227   copy = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (view->curve)));
1228   gimp_clipboard_set_curve (view->gimp, copy);
1229   g_object_unref (copy);
1230 }
1231 
1232 static void
gimp_curve_view_paste_clipboard(GimpCurveView * view)1233 gimp_curve_view_paste_clipboard (GimpCurveView *view)
1234 {
1235   GimpCurve *copy;
1236 
1237   g_printerr ("%s\n", G_STRFUNC);
1238 
1239   if (! view->curve || ! view->gimp)
1240     {
1241       gtk_widget_error_bell (GTK_WIDGET (view));
1242       return;
1243     }
1244 
1245   copy = gimp_clipboard_get_curve (view->gimp);
1246 
1247   if (copy)
1248     {
1249       gimp_config_copy (GIMP_CONFIG (copy),
1250                         GIMP_CONFIG (view->curve), 0);
1251       g_object_unref (copy);
1252     }
1253 }
1254 
1255 static void
gimp_curve_view_curve_dirty(GimpCurve * curve,GimpCurveView * view)1256 gimp_curve_view_curve_dirty (GimpCurve     *curve,
1257                              GimpCurveView *view)
1258 {
1259   gtk_widget_queue_draw (GTK_WIDGET (view));
1260 }
1261 
1262 static void
gimp_curve_view_curve_notify_n_points(GimpCurve * curve,GParamSpec * pspec,GimpCurveView * view)1263 gimp_curve_view_curve_notify_n_points (GimpCurve     *curve,
1264                                        GParamSpec    *pspec,
1265                                        GimpCurveView *view)
1266 {
1267   gimp_curve_view_set_selected (view, -1);
1268 }
1269 
1270 
1271 /*  public functions  */
1272 
1273 GtkWidget *
gimp_curve_view_new(void)1274 gimp_curve_view_new (void)
1275 {
1276   return g_object_new (GIMP_TYPE_CURVE_VIEW, NULL);
1277 }
1278 
1279 void
gimp_curve_view_set_curve(GimpCurveView * view,GimpCurve * curve,const GimpRGB * color)1280 gimp_curve_view_set_curve (GimpCurveView *view,
1281                            GimpCurve     *curve,
1282                            const GimpRGB *color)
1283 {
1284   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1285   g_return_if_fail (curve == NULL || GIMP_IS_CURVE (curve));
1286 
1287   if (view->curve == curve)
1288     return;
1289 
1290   if (view->curve)
1291     {
1292       g_signal_handlers_disconnect_by_func (view->curve,
1293                                             gimp_curve_view_curve_dirty,
1294                                             view);
1295       g_signal_handlers_disconnect_by_func (view->curve,
1296                                             gimp_curve_view_curve_notify_n_points,
1297                                             view);
1298       g_object_unref (view->curve);
1299     }
1300 
1301   view->curve = curve;
1302 
1303   if (view->curve)
1304     {
1305       g_object_ref (view->curve);
1306       g_signal_connect (view->curve, "dirty",
1307                         G_CALLBACK (gimp_curve_view_curve_dirty),
1308                         view);
1309       g_signal_connect (view->curve, "notify::n-points",
1310                         G_CALLBACK (gimp_curve_view_curve_notify_n_points),
1311                         view);
1312     }
1313 
1314   if (view->curve_color)
1315     g_free (view->curve_color);
1316 
1317   if (color)
1318     view->curve_color = g_memdup (color, sizeof (GimpRGB));
1319   else
1320     view->curve_color = NULL;
1321 
1322   gimp_curve_view_set_selected (view, -1);
1323 
1324   gtk_widget_queue_draw (GTK_WIDGET (view));
1325 }
1326 
1327 GimpCurve *
gimp_curve_view_get_curve(GimpCurveView * view)1328 gimp_curve_view_get_curve (GimpCurveView *view)
1329 {
1330   g_return_val_if_fail (GIMP_IS_CURVE_VIEW (view), NULL);
1331 
1332   return view->curve;
1333 }
1334 
1335 void
gimp_curve_view_add_background(GimpCurveView * view,GimpCurve * curve,const GimpRGB * color)1336 gimp_curve_view_add_background (GimpCurveView *view,
1337                                 GimpCurve     *curve,
1338                                 const GimpRGB *color)
1339 {
1340   GList   *list;
1341   BGCurve *bg;
1342 
1343   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1344   g_return_if_fail (GIMP_IS_CURVE (curve));
1345 
1346   for (list = view->bg_curves; list; list = g_list_next (list))
1347     {
1348       bg = list->data;
1349 
1350       g_return_if_fail (curve != bg->curve);
1351     }
1352 
1353   bg = g_slice_new0 (BGCurve);
1354 
1355   bg->curve = g_object_ref (curve);
1356 
1357   if (color)
1358     {
1359       bg->color     = *color;
1360       bg->color_set = TRUE;
1361     }
1362 
1363   g_signal_connect (bg->curve, "dirty",
1364                     G_CALLBACK (gimp_curve_view_curve_dirty),
1365                     view);
1366 
1367   view->bg_curves = g_list_append (view->bg_curves, bg);
1368 
1369   gtk_widget_queue_draw (GTK_WIDGET (view));
1370 }
1371 
1372 void
gimp_curve_view_remove_background(GimpCurveView * view,GimpCurve * curve)1373 gimp_curve_view_remove_background (GimpCurveView *view,
1374                                    GimpCurve     *curve)
1375 {
1376   GList *list;
1377 
1378   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1379   g_return_if_fail (GIMP_IS_CURVE (curve));
1380 
1381   for (list = view->bg_curves; list; list = g_list_next (list))
1382     {
1383       BGCurve *bg = list->data;
1384 
1385       if (bg->curve == curve)
1386         {
1387           g_signal_handlers_disconnect_by_func (bg->curve,
1388                                                 gimp_curve_view_curve_dirty,
1389                                                 view);
1390           g_object_unref (bg->curve);
1391 
1392           view->bg_curves = g_list_remove (view->bg_curves, bg);
1393 
1394           g_slice_free (BGCurve, bg);
1395 
1396           gtk_widget_queue_draw (GTK_WIDGET (view));
1397 
1398           break;
1399         }
1400     }
1401 
1402   if (! list)
1403     g_return_if_reached ();
1404 }
1405 
1406 void
gimp_curve_view_remove_all_backgrounds(GimpCurveView * view)1407 gimp_curve_view_remove_all_backgrounds (GimpCurveView *view)
1408 {
1409   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1410 
1411   while (view->bg_curves)
1412     {
1413       BGCurve *bg = view->bg_curves->data;
1414 
1415       g_signal_handlers_disconnect_by_func (bg->curve,
1416                                             gimp_curve_view_curve_dirty,
1417                                             view);
1418       g_object_unref (bg->curve);
1419 
1420       view->bg_curves = g_list_remove (view->bg_curves, bg);
1421 
1422       g_slice_free (BGCurve, bg);
1423     }
1424 
1425   gtk_widget_queue_draw (GTK_WIDGET (view));
1426 }
1427 
1428 void
gimp_curve_view_set_selected(GimpCurveView * view,gint selected)1429 gimp_curve_view_set_selected (GimpCurveView *view,
1430                               gint           selected)
1431 {
1432   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1433 
1434   if (selected != view->selected)
1435     {
1436       view->selected = selected;
1437 
1438       g_signal_emit (view, curve_view_signals[SELECTION_CHANGED], 0);
1439 
1440       gtk_widget_queue_draw (GTK_WIDGET (view));
1441     }
1442 }
1443 
1444 gint
gimp_curve_view_get_selected(GimpCurveView * view)1445 gimp_curve_view_get_selected (GimpCurveView *view)
1446 {
1447   g_return_val_if_fail (GIMP_IS_CURVE_VIEW (view), -1);
1448 
1449   if (view->curve && view->selected < gimp_curve_get_n_points (view->curve))
1450     return view->selected;
1451   else
1452     return -1;
1453 }
1454 
1455 void
gimp_curve_view_set_range_x(GimpCurveView * view,gdouble min,gdouble max)1456 gimp_curve_view_set_range_x (GimpCurveView *view,
1457                              gdouble        min,
1458                              gdouble        max)
1459 {
1460   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1461 
1462   view->range_x_min = min;
1463   view->range_x_max = max;
1464 
1465   gtk_widget_queue_draw (GTK_WIDGET (view));
1466 }
1467 
1468 void
gimp_curve_view_set_range_y(GimpCurveView * view,gdouble min,gdouble max)1469 gimp_curve_view_set_range_y (GimpCurveView *view,
1470                              gdouble        min,
1471                              gdouble        max)
1472 {
1473   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1474 
1475   view->range_y_min = min;
1476   view->range_y_max = max;
1477 
1478   gtk_widget_queue_draw (GTK_WIDGET (view));
1479 }
1480 
1481 void
gimp_curve_view_set_xpos(GimpCurveView * view,gdouble x)1482 gimp_curve_view_set_xpos (GimpCurveView *view,
1483                           gdouble        x)
1484 {
1485   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1486 
1487   view->xpos = x;
1488 
1489   gtk_widget_queue_draw (GTK_WIDGET (view));
1490 }
1491 
1492 void
gimp_curve_view_set_x_axis_label(GimpCurveView * view,const gchar * label)1493 gimp_curve_view_set_x_axis_label (GimpCurveView *view,
1494                                   const gchar   *label)
1495 {
1496   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1497 
1498   if (view->x_axis_label)
1499     g_free (view->x_axis_label);
1500 
1501   view->x_axis_label = g_strdup (label);
1502 
1503   g_object_notify (G_OBJECT (view), "x-axis-label");
1504 
1505   gtk_widget_queue_draw (GTK_WIDGET (view));
1506 }
1507 
1508 void
gimp_curve_view_set_y_axis_label(GimpCurveView * view,const gchar * label)1509 gimp_curve_view_set_y_axis_label (GimpCurveView *view,
1510                                   const gchar   *label)
1511 {
1512   g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
1513 
1514   if (view->y_axis_label)
1515     g_free (view->y_axis_label);
1516 
1517   view->y_axis_label = g_strdup (label);
1518 
1519   g_object_notify (G_OBJECT (view), "y-axis-label");
1520 
1521   gtk_widget_queue_draw (GTK_WIDGET (view));
1522 }
1523 
1524 
1525 /*  private functions  */
1526 
1527 static void
gimp_curve_view_set_cursor(GimpCurveView * view,gdouble x,gdouble y)1528 gimp_curve_view_set_cursor (GimpCurveView *view,
1529                             gdouble        x,
1530                             gdouble        y)
1531 {
1532   view->cursor_x = x;
1533   view->cursor_y = y;
1534 
1535   /* TODO: only invalidate the cursor label area */
1536   gtk_widget_queue_draw (GTK_WIDGET (view));
1537 }
1538 
1539 static void
gimp_curve_view_unset_cursor(GimpCurveView * view)1540 gimp_curve_view_unset_cursor (GimpCurveView *view)
1541 {
1542   view->cursor_x = -1.0;
1543   view->cursor_y = -1.0;
1544 
1545   /* TODO: only invalidate the cursor label area */
1546   gtk_widget_queue_draw (GTK_WIDGET (view));
1547 }
1548