/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * Copyright (C) 2001-2014 The Free Software Foundation, Inc. * * 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 2 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 . */ #include #include #include #include #include #include "gth-curve.h" #include "gth-curve-editor.h" #include "gth-enum-types.h" #include "gth-points.h" /* Properties */ enum { PROP_0, PROP_HISTOGRAM, PROP_CURRENT_CHANNEL, PROP_SCALE_TYPE }; enum { CHANNEL_COLUMN_NAME, CHANNEL_COLUMN_SENSITIVE }; /* Signals */ enum { CHANGED, LAST_SIGNAL }; struct _GthCurveEditorPrivate { GthHistogram *histogram; gulong histogram_changed_event; GthHistogramScale scale_type; GthHistogramChannel current_channel; GtkWidget *view; GtkWidget *linear_histogram_button; GtkWidget *logarithmic_histogram_button; GtkWidget *channel_combo_box; GthCurve *curve[GTH_HISTOGRAM_N_CHANNELS]; GthPoint *active_point; int active_point_lower_limit; int active_point_upper_limit; GthPoint cursor; gboolean dragging; gboolean paint_position; }; static guint gth_curve_editor_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE_WITH_CODE (GthCurveEditor, gth_curve_editor, GTK_TYPE_BOX, G_ADD_PRIVATE (GthCurveEditor)) static void gth_curve_editor_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GthCurveEditor *self; self = GTH_CURVE_EDITOR (object); switch (property_id) { case PROP_HISTOGRAM: gth_curve_editor_set_histogram (self, g_value_get_object (value)); break; case PROP_CURRENT_CHANNEL: gth_curve_editor_set_current_channel (self, g_value_get_enum (value)); break; case PROP_SCALE_TYPE: gth_curve_editor_set_scale_type (self, g_value_get_enum (value)); break; default: break; } } static void gth_curve_editor_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GthCurveEditor *self; self = GTH_CURVE_EDITOR (object); switch (property_id) { case PROP_HISTOGRAM: g_value_set_object (value, self->priv->histogram); break; case PROP_CURRENT_CHANNEL: g_value_set_int (value, self->priv->current_channel); break; case PROP_SCALE_TYPE: g_value_set_enum (value, self->priv->scale_type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gth_curve_editor_finalize (GObject *obj) { GthCurveEditor *self; int c; self = GTH_CURVE_EDITOR (obj); if (self->priv->histogram_changed_event != 0) g_signal_handler_disconnect (self->priv->histogram, self->priv->histogram_changed_event); _g_object_unref (self->priv->histogram); for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) _g_object_unref (self->priv->curve[c]); G_OBJECT_CLASS (gth_curve_editor_parent_class)->finalize (obj); } static void gth_curve_editor_class_init (GthCurveEditorClass *klass) { GObjectClass *object_class; object_class = (GObjectClass*) klass; object_class->set_property = gth_curve_editor_set_property; object_class->get_property = gth_curve_editor_get_property; object_class->finalize = gth_curve_editor_finalize; /* properties */ g_object_class_install_property (object_class, PROP_HISTOGRAM, g_param_spec_object ("histogram", "Histogram", "The histogram to display", GTH_TYPE_HISTOGRAM, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_CURRENT_CHANNEL, g_param_spec_enum ("current-channel", "Channel", "The channel to display", GTH_TYPE_HISTOGRAM_CHANNEL, GTH_HISTOGRAM_CHANNEL_VALUE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SCALE_TYPE, g_param_spec_enum ("scale-type", "Scale", "The scale type", GTH_TYPE_HISTOGRAM_SCALE, GTH_HISTOGRAM_SCALE_LOGARITHMIC, G_PARAM_READWRITE)); /* signals */ gth_curve_editor_signals[CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GthCurveEditorClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } #define convert_to_scale(scale_type, value) (((scale_type) == GTH_HISTOGRAM_SCALE_LOGARITHMIC) ? log (value) : (value)) #define HISTOGRAM_TRANSPARENCY 0.20 static void _cairo_set_source_color_from_channel (cairo_t *cr, int channel, double alpha) { switch (channel) { case GTH_HISTOGRAM_CHANNEL_VALUE: default: cairo_set_source_rgba (cr, 0.8, 0.8, 0.8, alpha); break; case GTH_HISTOGRAM_CHANNEL_RED: cairo_set_source_rgba (cr, 0.68, 0.18, 0.19, alpha); /* #af2e31 */ break; case GTH_HISTOGRAM_CHANNEL_GREEN: cairo_set_source_rgba (cr, 0.33, 0.78, 0.30, alpha); /* #55c74d */ break; case GTH_HISTOGRAM_CHANNEL_BLUE: cairo_set_source_rgba (cr, 0.13, 0.54, 0.8, alpha); /* #238acc */ break; case GTH_HISTOGRAM_CHANNEL_ALPHA: cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, alpha); break; } } static void gth_histogram_paint_channel (GthCurveEditor *self, GtkStyleContext *style_context, cairo_t *cr, int channel, GtkAllocation *allocation) { double max; double step; int i; if (channel > gth_histogram_get_nchannels (self->priv->histogram)) return; _cairo_set_source_color_from_channel (cr, channel, HISTOGRAM_TRANSPARENCY); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); max = gth_histogram_get_channel_max (self->priv->histogram, channel); if (max > 0.0) max = convert_to_scale (self->priv->scale_type, max); else max = 1.0; step = (double) allocation->width / 256.0; cairo_set_line_width (cr, 0.5); for (i = 0; i <= 255; i++) { double value; int y; value = gth_histogram_get_value (self->priv->histogram, channel, i); y = CLAMP ((int) (allocation->height * convert_to_scale (self->priv->scale_type, value)) / max, 0, allocation->height); cairo_rectangle (cr, allocation->x + (i * step) + 0.5, allocation->y + allocation->height - y + 0.5, step, y); } cairo_fill (cr); cairo_restore (cr); } #define GRID_LINES 4 static void gth_histogram_paint_grid (GthCurveEditor *self, GtkStyleContext *style_context, cairo_t *cr, GtkAllocation *allocation) { GdkRGBA color; double grid_step; int i; cairo_save (cr); gtk_style_context_get_border_color (style_context, gtk_widget_get_state_flags (GTK_WIDGET (self)), &color); cairo_set_line_width (cr, 0.5); grid_step = (double) allocation->width / GRID_LINES; for (i = 0; i <= GRID_LINES; i++) { int ofs = round (grid_step * i); cairo_set_source_rgba (cr, color.red, color.green, color.blue, (i == 4) ? 1.0 : 0.5); cairo_move_to (cr, allocation->x + ofs + 0.5, allocation->y); cairo_line_to (cr, allocation->x + ofs + 0.5, allocation->y + allocation->height); cairo_stroke (cr); } grid_step = (double) allocation->height / GRID_LINES; for (i = 0; i <= GRID_LINES; i++) { int ofs = round (grid_step * i); cairo_set_source_rgba (cr, color.red, color.green, color.blue, (i == 4) ? 1.0 : 0.5); cairo_move_to (cr, allocation->x + 0.5, allocation->y + ofs + 0.5); cairo_line_to (cr, allocation->x + allocation->width + 0.5, allocation->y + ofs + 0.5); cairo_stroke (cr); } /* diagonal */ cairo_set_antialias (cr, CAIRO_ANTIALIAS_DEFAULT); cairo_set_line_width (cr, 1.0); cairo_set_source_rgba (cr, color.red, color.green, color.blue, 0.5); cairo_move_to (cr, allocation->x + 0.5, allocation->y + allocation->height + 0.5); cairo_line_to (cr, allocation->x + allocation->width+ 0.5, allocation->y + 0.5); cairo_stroke (cr); cairo_restore (cr); } static void gth_histogram_paint_points (GthCurveEditor *self, GtkStyleContext *style_context, cairo_t *cr, GthPoints *points, GtkAllocation *allocation) { double x_scale, y_scale; int i; cairo_save (cr); cairo_set_line_width (cr, 1.0); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0); x_scale = (double) allocation->width / 255; y_scale = (double) allocation->height / 255; for (i = 0; i < points->n; i++) { double x = points->p[i].x; double y = points->p[i].y; cairo_arc (cr, round (allocation->x + (x * x_scale)), round (allocation->y + allocation->height - (y * y_scale)), 3.5, 0.0, 2 * M_PI); if (&points->p[i] == self->priv->active_point) cairo_fill_preserve (cr); cairo_stroke (cr); } cairo_restore (cr); } static void gth_histogram_paint_curve (GthCurveEditor *self, GtkStyleContext *style_context, cairo_t *cr, GthCurve *spline, GtkAllocation *allocation) { double x_scale, y_scale; double i; cairo_save (cr); cairo_set_antialias (cr, CAIRO_ANTIALIAS_DEFAULT); cairo_set_line_width (cr, 1.0); x_scale = (double) allocation->width / 255; y_scale = (double) allocation->height / 255; for (i = 0; i <= 256; i += 1) { int j; double x, y; j = gth_curve_eval (spline, i); x = allocation->x + (i * x_scale); y = allocation->y + allocation->height - (j * y_scale); if (i == 0) cairo_move_to (cr, x, y); else cairo_line_to (cr, x, y); } cairo_stroke (cr); cairo_restore (cr); } static void gth_histogram_paint_point_position (GthCurveEditor *self, GtkStyleContext *style_context, cairo_t *cr, GthPoint *point, GtkAllocation *allocation) { char *text; cairo_text_extents_t extents; int top = 9, left = 9, padding = 3; if (point->x < 0 || point->y < 0) return; cairo_save (cr); /* Translators: the first number is converted to the second number */ text = g_strdup_printf (_("%d → %d"), (int) point->x, (int) point->y); cairo_text_extents (cr, text, &extents); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.5); cairo_rectangle (cr, left - padding, top - padding, extents.width + 2*padding, extents.height + 2*padding); cairo_fill (cr); cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0); cairo_move_to (cr, left - extents.x_bearing, top - extents.y_bearing); cairo_show_text (cr, text); g_free (text); cairo_restore (cr); } static void gth_curve_editor_get_graph_area (GthCurveEditor *self, GtkAllocation *area) { GtkAllocation allocation; GtkBorder padding; gtk_widget_get_allocation (GTK_WIDGET (self->priv->view), &allocation); padding.left = 5; padding.right = 5; padding.top = 5; padding.bottom = 5; area->x = padding.left; area->y = padding.top; area->width = allocation.width - (padding.right + padding.left) - 1; area->height = allocation.height - (padding.top + padding.bottom) - 1; } static gboolean curve_editor_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data) { GthCurveEditor *self = user_data; GtkAllocation allocation; GtkStyleContext *style_context; style_context = gtk_widget_get_style_context (widget); gtk_style_context_save (style_context); gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW); gtk_style_context_add_class (style_context, "histogram"); gtk_widget_get_allocation (widget, &allocation); gtk_render_background (style_context, cr, 0, 0, allocation.width, allocation.height); if ((self->priv->histogram != NULL) && ((int) self->priv->current_channel <= gth_histogram_get_nchannels (self->priv->histogram))) { GtkAllocation area; int c; cairo_save (cr); cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); gth_curve_editor_get_graph_area (self, &area); gth_histogram_paint_channel (self, style_context, cr, self->priv->current_channel, &area); gth_histogram_paint_grid (self, style_context, cr, &area); cairo_save (cr); for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) { GthCurve *curve; GthPoints *points; GthPoint *p; if (c == self->priv->current_channel) continue; curve = self->priv->curve[c]; points = gth_curve_get_points (curve); p = points->p; /* do not paint unchanged curves */ if ((points->n == 2) && (p[0].x == 0 && p[0].y == 0) && (p[1].x == 255 && p[1].y == 255)) continue; _cairo_set_source_color_from_channel (cr, c, 0.25); gth_histogram_paint_curve (self, style_context, cr, curve, &area); } _cairo_set_source_color_from_channel (cr, self->priv->current_channel, 1.0); gth_histogram_paint_curve (self, style_context, cr, self->priv->curve[self->priv->current_channel], &area); cairo_restore (cr); gth_histogram_paint_points (self, style_context, cr, gth_curve_get_points (self->priv->curve[self->priv->current_channel]), &area); if (self->priv->paint_position) { if (self->priv->active_point != NULL) gth_histogram_paint_point_position (self, style_context, cr, self->priv->active_point, &area); else gth_histogram_paint_point_position (self, style_context, cr, &self->priv->cursor, &area); } cairo_restore (cr); } gtk_style_context_restore (style_context); return TRUE; } static gboolean curve_editor_scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, gpointer user_data) { GthCurveEditor *self = user_data; int channel = 0; if (self->priv->histogram == NULL) return FALSE; if (event->direction == GDK_SCROLL_UP) channel = self->priv->current_channel - 1; else if (event->direction == GDK_SCROLL_DOWN) channel = self->priv->current_channel + 1; if (channel <= gth_histogram_get_nchannels (self->priv->histogram)) gth_curve_editor_set_current_channel (self, CLAMP (channel, 0, GTH_HISTOGRAM_N_CHANNELS - 1)); return TRUE; } static void gth_curve_editor_get_point_from_event (GthCurveEditor *self, GthPoint *p, double x, double y) { GtkAllocation area; gth_curve_editor_get_graph_area (self, &area); p->x = x - area.x; p->y = area.height - (y - area.y); p->x *= 255.0 / area.width; p->y *= 255.0 / area.height; p->x = round (p->x); p->y = round (p->y); } #define POINT_ATTRACTION_THRESHOLD 10 static void gth_curve_editor_get_nearest_point (GthCurveEditor *self, GthPoint *p, int *n) { double min = 0; GthPoints *points; int i; *n = -1; points = gth_curve_get_points (self->priv->curve[self->priv->current_channel]); for (i = 0; i < points->n; i++) { GthPoint *q = &points->p[i]; double d; d = q->x - p->x; d = ABS (d); if ((d < POINT_ATTRACTION_THRESHOLD) && ((*n == -1) || (d < min))) { min = d; *n = i; } } } static void gth_curve_editor_set_active_point (GthCurveEditor *self, int n) { GthCurve *curve; GthPoints *points; curve = self->priv->curve[self->priv->current_channel]; points = gth_curve_get_points (curve); if (n >= points->n) n = -1; if (n >= 0) { self->priv->active_point = points->p + n; self->priv->active_point_lower_limit = (n > 0) ? points->p[n-1].x + 1 : 0; self->priv->active_point_upper_limit = (n < points->n-1) ? points->p[n+1].x - 1 : 255; } else self->priv->active_point = NULL; } static void gth_curve_editor_changed (GthCurveEditor *self) { g_signal_emit (self, gth_curve_editor_signals[CHANGED], 0); } static gboolean curve_editor_button_press_event_cb (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { GthCurveEditor *self = user_data; GthPoint p; int n_active_point; gth_curve_editor_get_point_from_event (self, &p, event->x, event->y); gth_curve_editor_get_nearest_point (self, &p, &n_active_point); if (event->button == 1) { if (n_active_point < 0) { GthCurve *curve; GthPoints *points; curve = self->priv->curve[self->priv->current_channel]; points = gth_curve_get_points (curve); n_active_point = gth_points_add_point (points, p.x, p.y); gth_curve_setup (curve); gth_curve_editor_changed (self); } if (n_active_point >= 0) { GdkCursor *cursor; self->priv->dragging = TRUE; cursor = _gdk_cursor_new_for_widget (self->priv->view, GDK_BLANK_CURSOR); gdk_window_set_cursor (gtk_widget_get_window (self->priv->view), cursor); g_object_unref (cursor); } } else if (event->button == 3) { if (n_active_point >= 0) { GthCurve *curve; GthPoints *points; curve = self->priv->curve[self->priv->current_channel]; points = gth_curve_get_points (curve); if (points->n > 2) { gth_points_delete_point (points, n_active_point); n_active_point = -1; gth_curve_setup (curve); gth_curve_editor_changed (self); } } } gth_curve_editor_set_active_point (self, n_active_point); gtk_widget_queue_draw (self->priv->view); return TRUE; } static gboolean curve_editor_button_release_event_cb (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { GthCurveEditor *self = user_data; if (self->priv->dragging) { GdkCursor *cursor; cursor = _gdk_cursor_new_for_widget (self->priv->view, GDK_CROSSHAIR); gdk_window_set_cursor (gtk_widget_get_window (self->priv->view), cursor); g_object_unref (cursor); } self->priv->dragging = FALSE; return TRUE; } static gboolean curve_editor_motion_notify_event_cb (GtkWidget *widget, GdkEventMotion *event, gpointer user_data) { GthCurveEditor *self = user_data; GthPoint p; gth_curve_editor_get_point_from_event (self, &p, event->x, event->y); self->priv->cursor.x = (p.x >= 0 && p.x <= 255) ? p.x : -1; self->priv->cursor.y = (p.y >= 0 && p.y <= 255) ? p.y : -1; if (self->priv->dragging) { g_return_val_if_fail (self->priv->active_point != NULL, TRUE); self->priv->active_point->x = CLAMP (p.x, self->priv->active_point_lower_limit, self->priv->active_point_upper_limit); self->priv->active_point->y = CLAMP (p.y, 0, 255); gth_curve_setup (self->priv->curve[self->priv->current_channel]); gth_curve_editor_changed (self); } else { int n_active_point; gth_curve_editor_get_nearest_point (self, &p, &n_active_point); gth_curve_editor_set_active_point (self, n_active_point); } self->priv->paint_position = TRUE; gtk_widget_queue_draw (self->priv->view); return TRUE; } static gboolean curve_editor_leave_notify_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GthCurveEditor *self = user_data; self->priv->paint_position = FALSE; gtk_widget_queue_draw (self->priv->view); return FALSE; } static void curve_editor_realize_cb (GtkWidget *widget, gpointer user_data) { GthCurveEditor *self = user_data; GdkCursor *cursor; cursor = _gdk_cursor_new_for_widget (self->priv->view, GDK_CROSSHAIR); gdk_window_set_cursor (gtk_widget_get_window (self->priv->view), cursor); g_object_unref (cursor); } static void linear_histogram_button_toggled_cb (GtkToggleButton *button, gpointer user_data) { GthCurveEditor *self = user_data; if (gtk_toggle_button_get_active (button)) gth_curve_editor_set_scale_type (GTH_CURVE_EDITOR (self), GTH_HISTOGRAM_SCALE_LINEAR); } static void logarithmic_histogram_button_toggled_cb (GtkToggleButton *button, gpointer user_data) { GthCurveEditor *self = user_data; if (gtk_toggle_button_get_active (button)) gth_curve_editor_set_scale_type (GTH_CURVE_EDITOR (self), GTH_HISTOGRAM_SCALE_LOGARITHMIC); } static void channel_combo_box_changed_cb (GtkComboBox *combo_box, gpointer user_data) { GthCurveEditor *self = user_data; int n_channel; n_channel = gtk_combo_box_get_active (combo_box); if (n_channel < GTH_HISTOGRAM_N_CHANNELS) gth_curve_editor_set_current_channel (GTH_CURVE_EDITOR (self), n_channel); } static void gth_curve_editor_reset_channel (GthCurveEditor *self, GthHistogramChannel c) { GthCurve *curve; GthPoints *points; curve = self->priv->curve[c]; points = gth_curve_get_points (curve); gth_points_dispose (points); gth_points_init (points, 2); points->p[0].x = 0; points->p[0].y = 0; points->p[1].x = 255; points->p[1].y = 255; gth_curve_setup (curve); } static void reset_current_channel_button_clicked_cb (GtkButton *button, gpointer user_data) { GthCurveEditor *self = user_data; gth_curve_editor_reset_channel (self, self->priv->current_channel); gth_curve_editor_changed (self); gtk_widget_queue_draw (self->priv->view); } static void self_notify_current_channel_cb (GObject *gobject, GParamSpec *pspec, gpointer user_data) { GthCurveEditor *self = user_data; gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->channel_combo_box), self->priv->current_channel); } static void self_notify_scale_type_cb (GObject *gobject, GParamSpec *pspec, gpointer user_data) { GthCurveEditor *self = user_data; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->linear_histogram_button), self->priv->scale_type == GTH_HISTOGRAM_SCALE_LINEAR); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->logarithmic_histogram_button), self->priv->scale_type == GTH_HISTOGRAM_SCALE_LOGARITHMIC); } static void gth_curve_editor_init (GthCurveEditor *self) { GtkWidget *topbar_box; GtkWidget *sub_box; PangoAttrList *attr_list; GtkWidget *label; GtkWidget *view_container; GtkListStore *channel_model; GtkCellRenderer *renderer; GtkTreeIter iter; GtkWidget *button; int c; self->priv = gth_curve_editor_get_instance_private (self); self->priv->histogram = NULL; self->priv->current_channel = GTH_HISTOGRAM_CHANNEL_VALUE; self->priv->scale_type = GTH_HISTOGRAM_SCALE_LINEAR; for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) { self->priv->curve[c] = gth_curve_new (GTH_TYPE_BEZIER, NULL); gth_curve_editor_reset_channel (self, c); } gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL); gtk_box_set_spacing (GTK_BOX (self), 6); gtk_widget_set_vexpand (GTK_WIDGET (self), FALSE); /* topbar */ topbar_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_widget_show (topbar_box); /* linear / logarithmic buttons */ sub_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show (sub_box); gtk_box_pack_end (GTK_BOX (topbar_box), sub_box, FALSE, FALSE, 0); self->priv->linear_histogram_button = gtk_toggle_button_new (); gtk_widget_set_tooltip_text (self->priv->linear_histogram_button, _("Linear scale")); gtk_button_set_relief (GTK_BUTTON (self->priv->linear_histogram_button), GTK_RELIEF_NONE); gtk_container_add (GTK_CONTAINER (self->priv->linear_histogram_button), gtk_image_new_from_icon_name ("format-linear-symbolic", GTK_ICON_SIZE_MENU)); gtk_widget_show_all (self->priv->linear_histogram_button); gtk_box_pack_start (GTK_BOX (sub_box), self->priv->linear_histogram_button, FALSE, FALSE, 0); g_signal_connect (self->priv->linear_histogram_button, "toggled", G_CALLBACK (linear_histogram_button_toggled_cb), self); self->priv->logarithmic_histogram_button = gtk_toggle_button_new (); gtk_widget_set_tooltip_text (self->priv->logarithmic_histogram_button, _("Logarithmic scale")); gtk_button_set_relief (GTK_BUTTON (self->priv->logarithmic_histogram_button), GTK_RELIEF_NONE); gtk_container_add (GTK_CONTAINER (self->priv->logarithmic_histogram_button), gtk_image_new_from_icon_name ("format-logarithmic-symbolic", GTK_ICON_SIZE_MENU)); gtk_widget_show_all (self->priv->logarithmic_histogram_button); gtk_box_pack_start (GTK_BOX (sub_box), self->priv->logarithmic_histogram_button, FALSE, FALSE, 0); g_signal_connect (self->priv->logarithmic_histogram_button, "toggled", G_CALLBACK (logarithmic_histogram_button_toggled_cb), self); /* channel selector */ sub_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_widget_show (sub_box); gtk_box_pack_start (GTK_BOX (topbar_box), sub_box, FALSE, FALSE, 0); attr_list = pango_attr_list_new (); pango_attr_list_insert (attr_list, pango_attr_size_new (PANGO_SCALE * 8)); label = gtk_label_new (_("Channel:")); gtk_label_set_attributes (GTK_LABEL (label), attr_list); gtk_widget_show (label); gtk_box_pack_start (GTK_BOX (sub_box), label, FALSE, FALSE, 0); channel_model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); self->priv->channel_combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (channel_model)); g_object_unref (channel_model); renderer = gtk_cell_renderer_text_new (); g_object_set (renderer, "attributes", attr_list, NULL); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->priv->channel_combo_box), renderer, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self->priv->channel_combo_box), renderer, "text", CHANNEL_COLUMN_NAME, "sensitive", CHANNEL_COLUMN_SENSITIVE, NULL); gtk_list_store_append (channel_model, &iter); gtk_list_store_set (channel_model, &iter, CHANNEL_COLUMN_NAME, _("Value"), CHANNEL_COLUMN_SENSITIVE, TRUE, -1); gtk_list_store_append (channel_model, &iter); gtk_list_store_set (channel_model, &iter, CHANNEL_COLUMN_NAME, _("Red"), CHANNEL_COLUMN_SENSITIVE, TRUE, -1); gtk_list_store_append (channel_model, &iter); gtk_list_store_set (channel_model, &iter, CHANNEL_COLUMN_NAME, _("Green"), CHANNEL_COLUMN_SENSITIVE, TRUE, -1); gtk_list_store_append (channel_model, &iter); gtk_list_store_set (channel_model, &iter, CHANNEL_COLUMN_NAME, _("Blue"), CHANNEL_COLUMN_SENSITIVE, TRUE, -1); gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->channel_combo_box), self->priv->current_channel); gtk_widget_show (self->priv->channel_combo_box); gtk_box_pack_start (GTK_BOX (sub_box), self->priv->channel_combo_box, FALSE, FALSE, 0); g_signal_connect (self->priv->channel_combo_box, "changed", G_CALLBACK (channel_combo_box_changed_cb), self); pango_attr_list_unref (attr_list); /* reset channel button */ button = gtk_button_new (); gtk_container_add (GTK_CONTAINER (button), gtk_image_new_from_icon_name ("edit-undo-symbolic", GTK_ICON_SIZE_BUTTON)); gtk_widget_set_tooltip_text (button, _("Reset")); gtk_widget_show_all (button); gtk_box_pack_start (GTK_BOX (sub_box), button, FALSE, FALSE, 0); g_signal_connect (button, "clicked", G_CALLBACK (reset_current_channel_button_clicked_cb), self); /* histogram view */ view_container = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view_container), GTK_SHADOW_IN); gtk_widget_set_vexpand (view_container, TRUE); gtk_widget_show (view_container); self->priv->view = gtk_drawing_area_new (); gtk_widget_add_events (self->priv->view, (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK)); gtk_widget_show (self->priv->view); gtk_container_add (GTK_CONTAINER (view_container), self->priv->view); g_signal_connect (self->priv->view, "draw", G_CALLBACK (curve_editor_draw_cb), self); g_signal_connect (self->priv->view, "scroll-event", G_CALLBACK (curve_editor_scroll_event_cb), self); g_signal_connect (self->priv->view, "button-press-event", G_CALLBACK (curve_editor_button_press_event_cb), self); g_signal_connect (self->priv->view, "button-release-event", G_CALLBACK (curve_editor_button_release_event_cb), self); g_signal_connect (self->priv->view, "motion-notify-event", G_CALLBACK (curve_editor_motion_notify_event_cb), self); g_signal_connect (self->priv->view, "leave-notify-event", G_CALLBACK (curve_editor_leave_notify_event_cb), self); g_signal_connect (self->priv->view, "realize", G_CALLBACK (curve_editor_realize_cb), self); /* pack the widget */ gtk_box_pack_start (GTK_BOX (self), topbar_box, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (self), view_container, TRUE, TRUE, 0); /* update widgets when a property changes */ g_signal_connect (self, "notify::current-channel", G_CALLBACK (self_notify_current_channel_cb), self); g_signal_connect (self, "notify::scale-type", G_CALLBACK (self_notify_scale_type_cb), self); /* default values */ self->priv->active_point = NULL; self->priv->cursor.x = -1; self->priv->cursor.y = -1; self->priv->dragging = FALSE; self->priv->paint_position = FALSE; gth_curve_editor_set_scale_type (self, GTH_HISTOGRAM_SCALE_LINEAR); gth_curve_editor_set_current_channel (self, GTH_HISTOGRAM_CHANNEL_VALUE); } GtkWidget * gth_curve_editor_new (GthHistogram *histogram) { return (GtkWidget *) g_object_new (GTH_TYPE_CURVE_EDITOR, "histogram", histogram, NULL); } static void update_sensitivity (GthCurveEditor *self) { gboolean has_alpha; GtkTreePath *path; GtkTreeIter iter; /* view */ if ((self->priv->histogram == NULL) || ((int) self->priv->current_channel > gth_histogram_get_nchannels (self->priv->histogram))) { gtk_widget_set_sensitive (self->priv->view, FALSE); } else gtk_widget_set_sensitive (self->priv->view, TRUE); /* channel combobox */ has_alpha = (self->priv->histogram != NULL) && (gth_histogram_get_nchannels (self->priv->histogram) > 3); path = gtk_tree_path_new_from_indices (GTH_HISTOGRAM_CHANNEL_ALPHA, -1); if (gtk_tree_model_get_iter (GTK_TREE_MODEL (gtk_combo_box_get_model (GTK_COMBO_BOX (self->priv->channel_combo_box))), &iter, path)) { gtk_list_store_set (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (self->priv->channel_combo_box))), &iter, CHANNEL_COLUMN_SENSITIVE, has_alpha, -1); } gtk_tree_path_free (path); } static void histogram_changed_cb (GthHistogram *histogram, gpointer user_data) { GthCurveEditor *self = user_data; update_sensitivity (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } void gth_curve_editor_set_histogram (GthCurveEditor *self, GthHistogram *histogram) { g_return_if_fail (GTH_IS_CURVE_EDITOR (self)); if (self->priv->histogram == histogram) return; if (self->priv->histogram != NULL) { g_signal_handler_disconnect (self->priv->histogram, self->priv->histogram_changed_event); _g_object_unref (self->priv->histogram); self->priv->histogram_changed_event = 0; self->priv->histogram = NULL; } if (histogram != NULL) { self->priv->histogram = g_object_ref (histogram); self->priv->histogram_changed_event = g_signal_connect (self->priv->histogram, "changed", G_CALLBACK (histogram_changed_cb), self); } g_object_notify (G_OBJECT (self), "histogram"); update_sensitivity (self); } GthHistogram * gth_curve_editor_get_histogram (GthCurveEditor *self) { g_return_val_if_fail (GTH_IS_CURVE_EDITOR (self), NULL); return self->priv->histogram; } void gth_curve_editor_set_current_channel (GthCurveEditor *self, int n_channel) { g_return_if_fail (GTH_IS_CURVE_EDITOR (self)); if (n_channel == self->priv->current_channel) return; self->priv->current_channel = CLAMP (n_channel, 0, GTH_HISTOGRAM_N_CHANNELS); g_object_notify (G_OBJECT (self), "current-channel"); gtk_widget_queue_draw (GTK_WIDGET (self)); } gint gth_curve_editor_get_current_channel (GthCurveEditor *self) { g_return_val_if_fail (GTH_IS_CURVE_EDITOR (self), 0); return self->priv->current_channel; } void gth_curve_editor_set_scale_type (GthCurveEditor *self, GthHistogramScale scale_type) { g_return_if_fail (GTH_IS_CURVE_EDITOR (self)); self->priv->scale_type = scale_type; g_object_notify (G_OBJECT (self), "scale-type"); gtk_widget_queue_draw (GTK_WIDGET (self)); } GthHistogramScale gth_curve_editor_get_scale_type (GthCurveEditor *self) { g_return_val_if_fail (GTH_IS_CURVE_EDITOR (self), 0); return self->priv->scale_type; } void gth_curve_editor_set_points (GthCurveEditor *self, GthPoints *points) { int c; for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) gth_curve_set_points (self->priv->curve[c], points + c); gth_curve_editor_changed (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } void gth_curve_editor_get_points (GthCurveEditor *self, GthPoints *points) { int c; for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) { gth_points_dispose (points + c); gth_points_copy (gth_curve_get_points (self->priv->curve[c]), &points[c]); } } void gth_curve_editor_reset (GthCurveEditor *self) { int c; for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) gth_curve_editor_reset_channel (self, c); gth_curve_editor_changed (self); gtk_widget_queue_draw (self->priv->view); }