diff options
Diffstat (limited to 'app/widgets/gimpcurveview.c')
-rw-r--r-- | app/widgets/gimpcurveview.c | 1547 |
1 files changed, 1547 insertions, 0 deletions
diff --git a/app/widgets/gimpcurveview.c b/app/widgets/gimpcurveview.c new file mode 100644 index 0000000..c34d06e --- /dev/null +++ b/app/widgets/gimpcurveview.c @@ -0,0 +1,1547 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpmath/gimpmath.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcurve.h" +#include "core/gimpcurve-map.h" +#include "core/gimpmarshal.h" + +#include "gimpclipboard.h" +#include "gimpcurveview.h" +#include "gimpwidgets-utils.h" + + +#define POINT_MAX_DISTANCE 16.0 + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_BASE_LINE, + PROP_GRID_ROWS, + PROP_GRID_COLUMNS, + PROP_X_AXIS_LABEL, + PROP_Y_AXIS_LABEL +}; + +enum +{ + SELECTION_CHANGED, + CUT_CLIPBOARD, + COPY_CLIPBOARD, + PASTE_CLIPBOARD, + LAST_SIGNAL +}; + + +typedef struct +{ + GimpCurve *curve; + GimpRGB color; + gboolean color_set; +} BGCurve; + + +static void gimp_curve_view_finalize (GObject *object); +static void gimp_curve_view_dispose (GObject *object); +static void gimp_curve_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_curve_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_curve_view_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static gboolean gimp_curve_view_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gimp_curve_view_button_press (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_curve_view_button_release (GtkWidget *widget, + GdkEventButton *bevent); +static gboolean gimp_curve_view_motion_notify (GtkWidget *widget, + GdkEventMotion *bevent); +static gboolean gimp_curve_view_leave_notify (GtkWidget *widget, + GdkEventCrossing *cevent); +static gboolean gimp_curve_view_key_press (GtkWidget *widget, + GdkEventKey *kevent); + +static void gimp_curve_view_cut_clipboard (GimpCurveView *view); +static void gimp_curve_view_copy_clipboard (GimpCurveView *view); +static void gimp_curve_view_paste_clipboard (GimpCurveView *view); + +static void gimp_curve_view_curve_dirty (GimpCurve *curve, + GimpCurveView *view); +static void gimp_curve_view_curve_notify_n_points (GimpCurve *curve, + GParamSpec *pspec, + GimpCurveView *view); + +static void gimp_curve_view_set_cursor (GimpCurveView *view, + gdouble x, + gdouble y); +static void gimp_curve_view_unset_cursor (GimpCurveView *view); + + +G_DEFINE_TYPE (GimpCurveView, gimp_curve_view, + GIMP_TYPE_HISTOGRAM_VIEW) + +#define parent_class gimp_curve_view_parent_class + +static guint curve_view_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_curve_view_class_init (GimpCurveViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkBindingSet *binding_set; + + object_class->finalize = gimp_curve_view_finalize; + object_class->dispose = gimp_curve_view_dispose; + object_class->set_property = gimp_curve_view_set_property; + object_class->get_property = gimp_curve_view_get_property; + + widget_class->style_set = gimp_curve_view_style_set; + widget_class->expose_event = gimp_curve_view_expose; + widget_class->button_press_event = gimp_curve_view_button_press; + widget_class->button_release_event = gimp_curve_view_button_release; + widget_class->motion_notify_event = gimp_curve_view_motion_notify; + widget_class->leave_notify_event = gimp_curve_view_leave_notify; + widget_class->key_press_event = gimp_curve_view_key_press; + + klass->selection_changed = NULL; + klass->cut_clipboard = gimp_curve_view_cut_clipboard; + klass->copy_clipboard = gimp_curve_view_copy_clipboard; + klass->paste_clipboard = gimp_curve_view_paste_clipboard; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_BASE_LINE, + g_param_spec_boolean ("base-line", + NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_GRID_ROWS, + g_param_spec_int ("grid-rows", NULL, NULL, + 0, 100, 8, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_GRID_COLUMNS, + g_param_spec_int ("grid-columns", NULL, NULL, + 0, 100, 8, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_X_AXIS_LABEL, + g_param_spec_string ("x-axis-label", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_Y_AXIS_LABEL, + g_param_spec_string ("y-axis-label", NULL, NULL, + NULL, + GIMP_PARAM_READWRITE)); + + curve_view_signals[SELECTION_CHANGED] = + g_signal_new ("selection-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpCurveViewClass, selection_changed), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + curve_view_signals[CUT_CLIPBOARD] = + g_signal_new ("cut-clipboard", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpCurveViewClass, cut_clipboard), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + curve_view_signals[COPY_CLIPBOARD] = + g_signal_new ("copy-clipboard", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpCurveViewClass, copy_clipboard), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + curve_view_signals[PASTE_CLIPBOARD] = + g_signal_new ("paste-clipboard", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GimpCurveViewClass, paste_clipboard), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_x, GDK_CONTROL_MASK, + "cut-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_c, GDK_CONTROL_MASK, + "copy-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_v, GDK_CONTROL_MASK, + "paste-clipboard", 0); +} + +static void +gimp_curve_view_init (GimpCurveView *view) +{ + view->curve = NULL; + view->selected = -1; + view->offset_x = 0.0; + view->offset_y = 0.0; + view->last_x = 0.0; + view->last_y = 0.0; + view->cursor_type = -1; + view->xpos = -1.0; + view->cursor_x = -1.0; + view->cursor_y = -1.0; + view->range_x_min = 0.0; + view->range_x_max = 1.0; + view->range_y_min = 0.0; + view->range_y_max = 1.0; + + view->x_axis_label = NULL; + view->y_axis_label = NULL; + + gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE); + gtk_widget_add_events (GTK_WIDGET (view), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON1_MOTION_MASK | + GDK_POINTER_MOTION_MASK | + GDK_KEY_PRESS_MASK | + GDK_LEAVE_NOTIFY_MASK); +} + +static void +gimp_curve_view_finalize (GObject *object) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (object); + + g_clear_object (&view->orig_curve); + + g_clear_object (&view->layout); + g_clear_object (&view->cursor_layout); + + g_clear_pointer (&view->x_axis_label, g_free); + g_clear_pointer (&view->y_axis_label, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_curve_view_dispose (GObject *object) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (object); + + gimp_curve_view_set_curve (view, NULL, NULL); + + if (view->bg_curves) + gimp_curve_view_remove_all_backgrounds (view); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_curve_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (object); + + switch (property_id) + { + case PROP_GIMP: + view->gimp = g_value_get_object (value); /* don't ref */ + break; + case PROP_GRID_ROWS: + view->grid_rows = g_value_get_int (value); + break; + case PROP_GRID_COLUMNS: + view->grid_columns = g_value_get_int (value); + break; + case PROP_BASE_LINE: + view->draw_base_line = g_value_get_boolean (value); + break; + case PROP_X_AXIS_LABEL: + gimp_curve_view_set_x_axis_label (view, g_value_get_string (value)); + break; + case PROP_Y_AXIS_LABEL: + gimp_curve_view_set_y_axis_label (view, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_curve_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, view->gimp); + break; + case PROP_GRID_ROWS: + g_value_set_int (value, view->grid_rows); + break; + case PROP_GRID_COLUMNS: + g_value_set_int (value, view->grid_columns); + break; + case PROP_BASE_LINE: + g_value_set_boolean (value, view->draw_base_line); + break; + case PROP_X_AXIS_LABEL: + g_value_set_string (value, view->x_axis_label); + break; + case PROP_Y_AXIS_LABEL: + g_value_set_string (value, view->y_axis_label); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_curve_view_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); + + g_clear_object (&view->layout); + g_clear_object (&view->cursor_layout); +} + +static void +gimp_curve_view_draw_grid (GimpCurveView *view, + cairo_t *cr, + gint width, + gint height, + gint border) +{ + gint i; + + for (i = 1; i < view->grid_rows; i++) + { + gint y = i * (height - 1) / view->grid_rows; + + if ((view->grid_rows % 2) == 0 && (i == view->grid_rows / 2)) + continue; + + cairo_move_to (cr, border, border + y); + cairo_line_to (cr, border + width - 1, border + y); + } + + for (i = 1; i < view->grid_columns; i++) + { + gint x = i * (width - 1) / view->grid_columns; + + if ((view->grid_columns % 2) == 0 && (i == view->grid_columns / 2)) + continue; + + cairo_move_to (cr, border + x, border); + cairo_line_to (cr, border + x, border + height - 1); + } + + if (view->draw_base_line) + { + cairo_move_to (cr, border, border + height - 1); + cairo_line_to (cr, border + width - 1, border); + } + + cairo_set_line_width (cr, 0.6); + cairo_stroke (cr); + + if ((view->grid_rows % 2) == 0) + { + gint y = (height - 1) / 2; + + cairo_move_to (cr, border, border + y); + cairo_line_to (cr, border + width - 1, border + y); + } + + if ((view->grid_columns % 2) == 0) + { + gint x = (width - 1) / 2; + + cairo_move_to (cr, border + x, border); + cairo_line_to (cr, border + x, border + height - 1); + } + + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); +} + +static void +gimp_curve_view_draw_point (GimpCurveView *view, + cairo_t *cr, + gint i, + gint width, + gint height, + gint border) +{ + gdouble x, y; + + gimp_curve_get_point (view->curve, i, &x, &y); + + y = 1.0 - y; + +#define CIRCLE_RADIUS 3 +#define DIAMOND_RADIUS (G_SQRT2 * CIRCLE_RADIUS) + + switch (gimp_curve_get_point_type (view->curve, i)) + { + case GIMP_CURVE_POINT_SMOOTH: + cairo_move_to (cr, + border + (gdouble) (width - 1) * x + CIRCLE_RADIUS, + border + (gdouble) (height - 1) * y); + cairo_arc (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1) * y, + CIRCLE_RADIUS, + 0, 2 * G_PI); + break; + + case GIMP_CURVE_POINT_CORNER: + cairo_move_to (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1) * y - DIAMOND_RADIUS); + cairo_line_to (cr, + border + (gdouble) (width - 1) * x + DIAMOND_RADIUS, + border + (gdouble) (height - 1) * y); + cairo_line_to (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1) * y + DIAMOND_RADIUS); + cairo_line_to (cr, + border + (gdouble) (width - 1) * x - DIAMOND_RADIUS, + border + (gdouble) (height - 1) * y); + cairo_close_path (cr); + break; + } +} + +static void +gimp_curve_view_draw_curve (GimpCurveView *view, + cairo_t *cr, + GimpCurve *curve, + gint width, + gint height, + gint border) +{ + gdouble x, y; + gint i; + + x = 0.0; + y = 1.0 - gimp_curve_map_value (curve, 0.0); + + cairo_move_to (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1)* y); + + for (i = 1; i < 256; i++) + { + x = (gdouble) i / 255.0; + y = 1.0 - gimp_curve_map_value (curve, x); + + cairo_line_to (cr, + border + (gdouble) (width - 1) * x, + border + (gdouble) (height - 1) * y); + } + + cairo_stroke (cr); +} + +static gboolean +gimp_curve_view_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + GdkWindow *window = gtk_widget_get_window (widget); + GtkStyle *style = gtk_widget_get_style (widget); + GtkAllocation allocation; + cairo_t *cr; + GList *list; + gint border; + gint width; + gint height; + gint layout_x; + gint layout_y; + gdouble x, y; + gint i; + + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + if (! view->curve) + return FALSE; + + gtk_widget_get_allocation (widget, &allocation); + + border = GIMP_HISTOGRAM_VIEW (view)->border_width; + width = allocation.width - 2 * border; + height = allocation.height - 2 * border; + + cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + if (gtk_widget_has_focus (widget)) + { + gtk_paint_focus (style, window, + gtk_widget_get_state (widget), + &event->area, widget, NULL, + border - 2, border - 2, + width + 4, height + 4); + } + + cairo_set_line_width (cr, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_translate (cr, 0.5, 0.5); + + /* Draw the grid lines */ + gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]); + + gimp_curve_view_draw_grid (view, cr, width, height, border); + + /* Draw the axis labels */ + + if (view->x_axis_label) + { + if (! view->layout) + view->layout = gtk_widget_create_pango_layout (widget, NULL); + + pango_layout_set_text (view->layout, view->x_axis_label, -1); + pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y); + + cairo_move_to (cr, + width - border - layout_x, + height - border - layout_y); + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + pango_cairo_show_layout (cr, view->layout); + } + + if (view->y_axis_label) + { + if (! view->layout) + view->layout = gtk_widget_create_pango_layout (widget, NULL); + + pango_layout_set_text (view->layout, view->y_axis_label, -1); + pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y); + + cairo_save (cr); + + cairo_move_to (cr, + 2 * border, + 2 * border + layout_x); + cairo_rotate (cr, - G_PI / 2); + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + pango_cairo_show_layout (cr, view->layout); + + cairo_restore (cr); + } + + + /* Draw the background curves */ + for (list = view->bg_curves; list; list = g_list_next (list)) + { + BGCurve *bg = list->data; + + if (bg->color_set) + { + cairo_set_source_rgba (cr, + bg->color.r, + bg->color.g, + bg->color.b, + 0.5); + } + else + { + cairo_set_source_rgba (cr, + style->text[GTK_STATE_NORMAL].red / 65535.0, + style->text[GTK_STATE_NORMAL].green / 65535.0, + style->text[GTK_STATE_NORMAL].blue / 65535.0, + 0.5); + } + + gimp_curve_view_draw_curve (view, cr, bg->curve, + width, height, border); + } + + /* Draw the curve */ + if (view->curve_color) + gimp_cairo_set_source_rgb (cr, view->curve_color); + else + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + + gimp_curve_view_draw_curve (view, cr, view->curve, + width, height, border); + + /* Draw the points */ + if (gimp_curve_get_curve_type (view->curve) == GIMP_CURVE_SMOOTH) + { + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + + /* Draw the unselected points */ + for (i = 0; i < view->curve->n_points; i++) + { + if (i == view->selected) + continue; + + gimp_curve_view_draw_point (view, cr, i, width, height, border); + } + + cairo_stroke (cr); + + /* Draw the selected point */ + if (view->selected != -1) + { + gimp_curve_view_draw_point (view, cr, view->selected, + width, height, border); + cairo_fill (cr); + } + } + + if (view->xpos >= 0.0) + { + gchar buf[32]; + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + + /* draw the color line */ + cairo_move_to (cr, + border + ROUND ((gdouble) (width - 1) * view->xpos), + border + 1); + cairo_line_to (cr, + border + ROUND ((gdouble) (width - 1) * view->xpos), + border + height - 1); + cairo_stroke (cr); + + if (view->range_x_max == 255.0) + { + /* stupid heuristic: special-case for 0..255 */ + + g_snprintf (buf, sizeof (buf), "x:%3d", + (gint) (view->xpos * + (view->range_x_max - view->range_x_min) + + view->range_x_min)); + } + else if (view->range_x_max == 100.0) + { + /* and for 0..100 */ + + g_snprintf (buf, sizeof (buf), "x:%0.2f", + view->xpos * + (view->range_x_max - view->range_x_min) + + view->range_x_min); + } + else + { + g_snprintf (buf, sizeof (buf), "x:%0.3f", + view->xpos * + (view->range_x_max - view->range_x_min) + + view->range_x_min); + } + + if (! view->layout) + view->layout = gtk_widget_create_pango_layout (widget, NULL); + + pango_layout_set_text (view->layout, buf, -1); + pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y); + + if (view->xpos < 0.5) + layout_x = border; + else + layout_x = -(layout_x + border); + + cairo_move_to (cr, + border + (gdouble) width * view->xpos + layout_x, + border + height - border - layout_y); + pango_cairo_show_layout (cr, view->layout); + } + + if (view->cursor_x >= 0.0 && view->cursor_x <= 1.0 && + view->cursor_y >= 0.0 && view->cursor_y <= 1.0) + { + gchar buf[32]; + gint w, h; + + if (! view->cursor_layout) + view->cursor_layout = gtk_widget_create_pango_layout (widget, NULL); + + if (view->range_x_max == 255.0 && + view->range_y_max == 255.0) + { + /* stupid heuristic: special-case for 0..255 */ + + g_snprintf (buf, sizeof (buf), "x:%3d y:%3d", + (gint) round (view->cursor_x * + (view->range_x_max - view->range_x_min) + + view->range_x_min), + (gint) round ((1.0 - view->cursor_y) * + (view->range_y_max - view->range_y_min) + + view->range_y_min)); + } + else if (view->range_x_max == 100.0 && + view->range_y_max == 100.0) + { + /* and for 0..100 */ + + g_snprintf (buf, sizeof (buf), "x:%0.2f y:%0.2f", + view->cursor_x * + (view->range_x_max - view->range_x_min) + + view->range_x_min, + (1.0 - view->cursor_y) * + (view->range_y_max - view->range_y_min) + + view->range_y_min); + } + else + { + g_snprintf (buf, sizeof (buf), "x:%0.3f y:%0.3f", + view->cursor_x * + (view->range_x_max - view->range_x_min) + + view->range_x_min, + (1.0 - view->cursor_y) * + (view->range_y_max - view->range_y_min) + + view->range_y_min); + } + + pango_layout_set_text (view->cursor_layout, buf, -1); + pango_layout_get_pixel_extents (view->cursor_layout, + NULL, &view->cursor_rect); + + x = border * 2 + 3; + y = border * 2 + 3; + w = view->cursor_rect.width; + h = view->cursor_rect.height; + + if (view->x_axis_label) + x += border + view->cursor_rect.height; /* coincidentially the right value */ + + cairo_push_group (cr); + + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]); + cairo_rectangle (cr, x + 0.5, y + 0.5, w, h); + cairo_fill_preserve (cr); + + cairo_set_line_width (cr, 6); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + cairo_stroke (cr); + + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]); + cairo_move_to (cr, x, y); + pango_cairo_show_layout (cr, view->cursor_layout); + + cairo_pop_group_to_source (cr); + cairo_paint_with_alpha (cr, 0.6); + } + + cairo_destroy (cr); + + return FALSE; +} + +static void +set_cursor (GimpCurveView *view, + GdkCursorType new_cursor) +{ + if (new_cursor != view->cursor_type) + { + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (view)); + GdkCursor *cursor = gdk_cursor_new_for_display (display, new_cursor); + + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (view)), cursor); + gdk_cursor_unref (cursor); + + view->cursor_type = new_cursor; + } +} + +static gboolean +gimp_curve_view_button_press (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + GimpCurve *curve = view->curve; + GtkAllocation allocation; + gint border; + gint width, height; + gdouble x; + gdouble y; + gint point; + gdouble point_x; + gdouble point_y; + + if (! curve || bevent->button != 1) + return TRUE; + + gtk_widget_get_allocation (widget, &allocation); + + border = GIMP_HISTOGRAM_VIEW (view)->border_width; + width = allocation.width - 2 * border; + height = allocation.height - 2 * border; + + x = (gdouble) (bevent->x - border) / (gdouble) width; + y = (gdouble) (bevent->y - border) / (gdouble) height; + + x = CLAMP (x, 0.0, 1.0); + y = CLAMP (y, 0.0, 1.0); + + view->grabbed = TRUE; + + view->orig_curve = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (curve))); + + set_cursor (view, GDK_TCROSS); + + switch (gimp_curve_get_curve_type (curve)) + { + case GIMP_CURVE_SMOOTH: + point = gimp_curve_get_closest_point (curve, x, 1.0 - y, + POINT_MAX_DISTANCE / + MAX (width, height)); + + if (point < 0) + { + GimpCurvePointType type = GIMP_CURVE_POINT_SMOOTH; + + if (bevent->state & gimp_get_constrain_behavior_mask ()) + y = 1.0 - gimp_curve_map_value (view->orig_curve, x); + + if (view->selected >= 0) + type = gimp_curve_get_point_type (curve, view->selected); + + point = gimp_curve_add_point (curve, x, 1.0 - y); + + gimp_curve_set_point_type (curve, point, type); + } + + if (point > 0) + gimp_curve_get_point (curve, point - 1, &view->leftmost, NULL); + else + view->leftmost = -1.0; + + if (point < gimp_curve_get_n_points (curve) - 1) + gimp_curve_get_point (curve, point + 1, &view->rightmost, NULL); + else + view->rightmost = 2.0; + + gimp_curve_view_set_selected (view, point); + + gimp_curve_get_point (curve, point, &point_x, &point_y); + + view->offset_x = point_x - x; + view->offset_y = (1.0 - point_y) - y; + + view->point_type = gimp_curve_get_point_type (curve, point); + break; + + case GIMP_CURVE_FREE: + view->last_x = x; + view->last_y = y; + + gimp_curve_set_curve (curve, x, 1.0 - y); + break; + } + + if (! gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + return TRUE; +} + +static gboolean +gimp_curve_view_button_release (GtkWidget *widget, + GdkEventButton *bevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + + if (bevent->button != 1) + return TRUE; + + g_clear_object (&view->orig_curve); + + view->offset_x = 0.0; + view->offset_y = 0.0; + + view->grabbed = FALSE; + + set_cursor (view, GDK_FLEUR); + + return TRUE; +} + +static gboolean +gimp_curve_view_motion_notify (GtkWidget *widget, + GdkEventMotion *mevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + GimpCurve *curve = view->curve; + GtkAllocation allocation; + GdkCursorType new_cursor = GDK_X_CURSOR; + gint border; + gint width, height; + gdouble x; + gdouble y; + gdouble point_x; + gdouble point_y; + gint point; + + if (! curve) + return TRUE; + + gtk_widget_get_allocation (widget, &allocation); + + border = GIMP_HISTOGRAM_VIEW (view)->border_width; + width = allocation.width - 2 * border; + height = allocation.height - 2 * border; + + x = (gdouble) (mevent->x - border) / (gdouble) width; + y = (gdouble) (mevent->y - border) / (gdouble) height; + + x += view->offset_x; + y += view->offset_y; + + x = CLAMP (x, 0.0, 1.0); + y = CLAMP (y, 0.0, 1.0); + + switch (gimp_curve_get_curve_type (curve)) + { + case GIMP_CURVE_SMOOTH: + if (! view->grabbed) /* If no point is grabbed... */ + { + point = gimp_curve_get_closest_point (curve, x, 1.0 - y, + POINT_MAX_DISTANCE / + MAX (width, height)); + + if (point >= 0) + { + gimp_curve_get_point (curve, point, &point_x, &point_y); + + new_cursor = GDK_FLEUR; + + x = point_x; + y = 1.0 - point_y; + } + else + { + new_cursor = GDK_TCROSS; + + if (mevent->state & gimp_get_constrain_behavior_mask ()) + y = 1.0 - gimp_curve_map_value (view->curve, x); + } + } + else /* Else, drag the grabbed point */ + { + new_cursor = GDK_TCROSS; + + if (mevent->state & gimp_get_constrain_behavior_mask ()) + y = 1.0 - gimp_curve_map_value (view->orig_curve, x); + + gimp_data_freeze (GIMP_DATA (curve)); + + if (x > view->leftmost && x < view->rightmost) + { + if (view->selected < 0) + { + gimp_curve_view_set_selected ( + view, + gimp_curve_add_point (curve, x, 1.0 - y)); + + gimp_curve_set_point_type (curve, + view->selected, view->point_type); + } + else + { + gimp_curve_set_point (curve, view->selected, x, 1.0 - y); + } + } + else + { + if (view->selected >= 0) + { + gimp_curve_delete_point (curve, view->selected); + + gimp_curve_view_set_selected (view, -1); + } + } + + gimp_data_thaw (GIMP_DATA (curve)); + } + break; + + case GIMP_CURVE_FREE: + if (view->grabbed) + { + gint n_samples = gimp_curve_get_n_samples (curve); + gdouble x1, x2; + gdouble y1, y2; + + if (view->last_x > x) + { + x1 = x; + x2 = view->last_x; + y1 = y; + y2 = view->last_y; + } + else + { + x1 = view->last_x; + x2 = x; + y1 = view->last_y; + y2 = y; + } + + if (x2 != x1) + { + gint from = ROUND (x1 * (gdouble) (n_samples - 1)); + gint to = ROUND (x2 * (gdouble) (n_samples - 1)); + gint i; + + gimp_data_freeze (GIMP_DATA (curve)); + + for (i = from; i <= to; i++) + { + gdouble xpos = (gdouble) i / (gdouble) (n_samples - 1); + gdouble ypos = (y1 + ((y2 - y1) * (xpos - x1)) / (x2 - x1)); + + xpos = CLAMP (xpos, 0.0, 1.0); + ypos = CLAMP (ypos, 0.0, 1.0); + + gimp_curve_set_curve (curve, xpos, 1.0 - ypos); + } + + gimp_data_thaw (GIMP_DATA (curve)); + } + else + { + gimp_curve_set_curve (curve, x, 1.0 - y); + } + + view->last_x = x; + view->last_y = y; + } + + if (mevent->state & GDK_BUTTON1_MASK) + new_cursor = GDK_TCROSS; + else + new_cursor = GDK_PENCIL; + + break; + } + + set_cursor (view, new_cursor); + + gimp_curve_view_set_cursor (view, x, y); + + return TRUE; +} + +static gboolean +gimp_curve_view_leave_notify (GtkWidget *widget, + GdkEventCrossing *cevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + + gimp_curve_view_unset_cursor (view); + + return TRUE; +} + +static gboolean +gimp_curve_view_key_press (GtkWidget *widget, + GdkEventKey *kevent) +{ + GimpCurveView *view = GIMP_CURVE_VIEW (widget); + GimpCurve *curve = view->curve; + gboolean handled = FALSE; + + if (! view->grabbed && + curve && + gimp_curve_get_curve_type (curve) == GIMP_CURVE_SMOOTH && + view->selected >= 0) + { + gint i = view->selected; + gdouble x, y; + + gimp_curve_get_point (curve, i, NULL, &y); + + switch (kevent->keyval) + { + case GDK_KEY_Left: + for (i = i - 1; i >= 0 && ! handled; i--) + { + gimp_curve_get_point (curve, i, &x, NULL); + + if (x >= 0.0) + { + gimp_curve_view_set_selected (view, i); + + handled = TRUE; + } + } + break; + + case GDK_KEY_Right: + for (i = i + 1; i < curve->n_points && ! handled; i++) + { + gimp_curve_get_point (curve, i, &x, NULL); + + if (x >= 0.0) + { + gimp_curve_view_set_selected (view, i); + + handled = TRUE; + } + } + break; + + case GDK_KEY_Up: + if (y < 1.0) + { + y = y + (kevent->state & GDK_SHIFT_MASK ? + (16.0 / 255.0) : (1.0 / 255.0)); + + gimp_curve_move_point (curve, i, CLAMP (y, 0.0, 1.0)); + + handled = TRUE; + } + break; + + case GDK_KEY_Down: + if (y > 0) + { + y = y - (kevent->state & GDK_SHIFT_MASK ? + (16.0 / 255.0) : (1.0 / 255.0)); + + gimp_curve_move_point (curve, i, CLAMP (y, 0.0, 1.0)); + + handled = TRUE; + } + break; + + case GDK_KEY_Delete: + gimp_curve_delete_point (curve, i); + break; + + default: + break; + } + } + + if (handled) + { + set_cursor (view, GDK_TCROSS); + + return TRUE; + } + + return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, kevent); +} + +static void +gimp_curve_view_cut_clipboard (GimpCurveView *view) +{ + g_printerr ("%s\n", G_STRFUNC); + + if (! view->curve || ! view->gimp) + { + gtk_widget_error_bell (GTK_WIDGET (view)); + return; + } + + gimp_curve_view_copy_clipboard (view); + + gimp_curve_reset (view->curve, FALSE); +} + +static void +gimp_curve_view_copy_clipboard (GimpCurveView *view) +{ + GimpCurve *copy; + + g_printerr ("%s\n", G_STRFUNC); + + if (! view->curve || ! view->gimp) + { + gtk_widget_error_bell (GTK_WIDGET (view)); + return; + } + + copy = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (view->curve))); + gimp_clipboard_set_curve (view->gimp, copy); + g_object_unref (copy); +} + +static void +gimp_curve_view_paste_clipboard (GimpCurveView *view) +{ + GimpCurve *copy; + + g_printerr ("%s\n", G_STRFUNC); + + if (! view->curve || ! view->gimp) + { + gtk_widget_error_bell (GTK_WIDGET (view)); + return; + } + + copy = gimp_clipboard_get_curve (view->gimp); + + if (copy) + { + gimp_config_copy (GIMP_CONFIG (copy), + GIMP_CONFIG (view->curve), 0); + g_object_unref (copy); + } +} + +static void +gimp_curve_view_curve_dirty (GimpCurve *curve, + GimpCurveView *view) +{ + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +static void +gimp_curve_view_curve_notify_n_points (GimpCurve *curve, + GParamSpec *pspec, + GimpCurveView *view) +{ + gimp_curve_view_set_selected (view, -1); +} + + +/* public functions */ + +GtkWidget * +gimp_curve_view_new (void) +{ + return g_object_new (GIMP_TYPE_CURVE_VIEW, NULL); +} + +void +gimp_curve_view_set_curve (GimpCurveView *view, + GimpCurve *curve, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + g_return_if_fail (curve == NULL || GIMP_IS_CURVE (curve)); + + if (view->curve == curve) + return; + + if (view->curve) + { + g_signal_handlers_disconnect_by_func (view->curve, + gimp_curve_view_curve_dirty, + view); + g_signal_handlers_disconnect_by_func (view->curve, + gimp_curve_view_curve_notify_n_points, + view); + g_object_unref (view->curve); + } + + view->curve = curve; + + if (view->curve) + { + g_object_ref (view->curve); + g_signal_connect (view->curve, "dirty", + G_CALLBACK (gimp_curve_view_curve_dirty), + view); + g_signal_connect (view->curve, "notify::n-points", + G_CALLBACK (gimp_curve_view_curve_notify_n_points), + view); + } + + if (view->curve_color) + g_free (view->curve_color); + + if (color) + view->curve_color = g_memdup (color, sizeof (GimpRGB)); + else + view->curve_color = NULL; + + gimp_curve_view_set_selected (view, -1); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +GimpCurve * +gimp_curve_view_get_curve (GimpCurveView *view) +{ + g_return_val_if_fail (GIMP_IS_CURVE_VIEW (view), NULL); + + return view->curve; +} + +void +gimp_curve_view_add_background (GimpCurveView *view, + GimpCurve *curve, + const GimpRGB *color) +{ + GList *list; + BGCurve *bg; + + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + g_return_if_fail (GIMP_IS_CURVE (curve)); + + for (list = view->bg_curves; list; list = g_list_next (list)) + { + bg = list->data; + + g_return_if_fail (curve != bg->curve); + } + + bg = g_slice_new0 (BGCurve); + + bg->curve = g_object_ref (curve); + + if (color) + { + bg->color = *color; + bg->color_set = TRUE; + } + + g_signal_connect (bg->curve, "dirty", + G_CALLBACK (gimp_curve_view_curve_dirty), + view); + + view->bg_curves = g_list_append (view->bg_curves, bg); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_remove_background (GimpCurveView *view, + GimpCurve *curve) +{ + GList *list; + + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + g_return_if_fail (GIMP_IS_CURVE (curve)); + + for (list = view->bg_curves; list; list = g_list_next (list)) + { + BGCurve *bg = list->data; + + if (bg->curve == curve) + { + g_signal_handlers_disconnect_by_func (bg->curve, + gimp_curve_view_curve_dirty, + view); + g_object_unref (bg->curve); + + view->bg_curves = g_list_remove (view->bg_curves, bg); + + g_slice_free (BGCurve, bg); + + gtk_widget_queue_draw (GTK_WIDGET (view)); + + break; + } + } + + if (! list) + g_return_if_reached (); +} + +void +gimp_curve_view_remove_all_backgrounds (GimpCurveView *view) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + while (view->bg_curves) + { + BGCurve *bg = view->bg_curves->data; + + g_signal_handlers_disconnect_by_func (bg->curve, + gimp_curve_view_curve_dirty, + view); + g_object_unref (bg->curve); + + view->bg_curves = g_list_remove (view->bg_curves, bg); + + g_slice_free (BGCurve, bg); + } + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_selected (GimpCurveView *view, + gint selected) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + if (selected != view->selected) + { + view->selected = selected; + + g_signal_emit (view, curve_view_signals[SELECTION_CHANGED], 0); + + gtk_widget_queue_draw (GTK_WIDGET (view)); + } +} + +gint +gimp_curve_view_get_selected (GimpCurveView *view) +{ + g_return_val_if_fail (GIMP_IS_CURVE_VIEW (view), -1); + + if (view->curve && view->selected < gimp_curve_get_n_points (view->curve)) + return view->selected; + else + return -1; +} + +void +gimp_curve_view_set_range_x (GimpCurveView *view, + gdouble min, + gdouble max) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + view->range_x_min = min; + view->range_x_max = max; + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_range_y (GimpCurveView *view, + gdouble min, + gdouble max) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + view->range_y_min = min; + view->range_y_max = max; + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_xpos (GimpCurveView *view, + gdouble x) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + view->xpos = x; + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_x_axis_label (GimpCurveView *view, + const gchar *label) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + if (view->x_axis_label) + g_free (view->x_axis_label); + + view->x_axis_label = g_strdup (label); + + g_object_notify (G_OBJECT (view), "x-axis-label"); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +void +gimp_curve_view_set_y_axis_label (GimpCurveView *view, + const gchar *label) +{ + g_return_if_fail (GIMP_IS_CURVE_VIEW (view)); + + if (view->y_axis_label) + g_free (view->y_axis_label); + + view->y_axis_label = g_strdup (label); + + g_object_notify (G_OBJECT (view), "y-axis-label"); + + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + + +/* private functions */ + +static void +gimp_curve_view_set_cursor (GimpCurveView *view, + gdouble x, + gdouble y) +{ + view->cursor_x = x; + view->cursor_y = y; + + /* TODO: only invalidate the cursor label area */ + gtk_widget_queue_draw (GTK_WIDGET (view)); +} + +static void +gimp_curve_view_unset_cursor (GimpCurveView *view) +{ + view->cursor_x = -1.0; + view->cursor_y = -1.0; + + /* TODO: only invalidate the cursor label area */ + gtk_widget_queue_draw (GTK_WIDGET (view)); +} |