/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimpmeter.c * Copyright (C) 2017 Ell * * 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 . */ #include "config.h" #include #include #include #include "libgimpmath/gimpmath.h" #include "libgimpcolor/gimpcolor.h" #include "libgimpwidgets/gimpwidgets.h" #include "widgets-types.h" #include "gimpmeter.h" #define BORDER_WIDTH 1.0 #define REV (2.0 * G_PI) #define SAMPLE(i) (meter->priv->samples + (i) * meter->priv->n_values) #define SAMPLE_SIZE (meter->priv->n_values * sizeof (gdouble)) #define VALUE(i, j) ((SAFE_CLAMP (SAMPLE (i)[j], \ meter->priv->range_min, \ meter->priv->range_max) - \ meter->priv->range_min) / \ (meter->priv->range_max - meter->priv->range_min)) enum { PROP_0, PROP_SIZE, PROP_REFRESH_RATE, PROP_RANGE_MIN, PROP_RANGE_MAX, PROP_N_VALUES, PROP_HISTORY_VISIBLE, PROP_HISTORY_DURATION, PROP_HISTORY_RESOLUTION, PROP_LED_ACTIVE, PROP_LED_COLOR }; typedef struct { gboolean active; gboolean show_in_gauge; gboolean show_in_history; GimpRGB color; GimpInterpolationType interpolation; } Value; struct _GimpMeterPrivate { GMutex mutex; gint size; gdouble refresh_rate; gdouble range_min; gdouble range_max; gint n_values; Value *values; gboolean history_visible; gdouble history_duration; gdouble history_resolution; gboolean led_active; GimpRGB led_color; gdouble *samples; gint n_samples; gint sample_duration; gint64 last_sample_time; gint64 current_time; gdouble *uniform_sample; gint timeout_id; }; /* local function prototypes */ static void gimp_meter_dispose (GObject *object); static void gimp_meter_finalize (GObject *object); static void gimp_meter_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void gimp_meter_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gimp_meter_map (GtkWidget *widget); static void gimp_meter_unmap (GtkWidget *widget); static void gimp_meter_size_request (GtkWidget *widget, GtkRequisition *requisition); static gboolean gimp_meter_expose_event (GtkWidget *widget, GdkEventExpose *event); static gboolean gimp_meter_timeout (GimpMeter *meter); static void gimp_meter_clear_history_unlocked (GimpMeter *meter); static void gimp_meter_update_samples (GimpMeter *meter); static void gimp_meter_shift_samples (GimpMeter *meter); static void gimp_meter_mask_sample (GimpMeter *meter, const gdouble *sample, gdouble *result); G_DEFINE_TYPE_WITH_PRIVATE (GimpMeter, gimp_meter, GTK_TYPE_WIDGET) #define parent_class gimp_meter_parent_class /* private functions */ static void gimp_meter_class_init (GimpMeterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = gimp_meter_dispose; object_class->finalize = gimp_meter_finalize; object_class->get_property = gimp_meter_get_property; object_class->set_property = gimp_meter_set_property; widget_class->map = gimp_meter_map; widget_class->unmap = gimp_meter_unmap; widget_class->size_request = gimp_meter_size_request; widget_class->expose_event = gimp_meter_expose_event; g_object_class_install_property (object_class, PROP_SIZE, g_param_spec_int ("size", NULL, NULL, 32, 1024, 48, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_REFRESH_RATE, g_param_spec_double ("refresh-rate", NULL, NULL, 0.001, 1000.0, 8.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_RANGE_MIN, g_param_spec_double ("range-min", NULL, NULL, 0.0, G_MAXDOUBLE, 0.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_RANGE_MAX, g_param_spec_double ("range-max", NULL, NULL, 0.0, G_MAXDOUBLE, 1.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_N_VALUES, g_param_spec_int ("n-values", NULL, NULL, 0, 32, 0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_HISTORY_VISIBLE, g_param_spec_boolean ("history-visible", NULL, NULL, TRUE, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_HISTORY_DURATION, g_param_spec_double ("history-duration", NULL, NULL, 0.0, 3600.0, 60.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_HISTORY_RESOLUTION, g_param_spec_double ("history-resolution", NULL, NULL, 0.1, 3600.0, 1.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_LED_ACTIVE, g_param_spec_boolean ("led-active", NULL, NULL, FALSE, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_LED_COLOR, gimp_param_spec_rgb ("led-color", NULL, NULL, TRUE, &(GimpRGB) {}, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void gimp_meter_init (GimpMeter *meter) { meter->priv = gimp_meter_get_instance_private (meter); g_mutex_init (&meter->priv->mutex); gtk_widget_set_has_window (GTK_WIDGET (meter), FALSE); meter->priv->range_min = 0.0; meter->priv->range_max = 1.0; meter->priv->n_values = 0; meter->priv->history_duration = 60.0; meter->priv->history_resolution = 1.0; gimp_meter_update_samples (meter); } static void gimp_meter_dispose (GObject *object) { GimpMeter *meter = GIMP_METER (object); g_clear_pointer (&meter->priv->values, g_free); g_clear_pointer (&meter->priv->samples, g_free); g_clear_pointer (&meter->priv->uniform_sample, g_free); if (meter->priv->timeout_id) { g_source_remove (meter->priv->timeout_id); meter->priv->timeout_id = 0; } G_OBJECT_CLASS (parent_class)->dispose (object); } static void gimp_meter_finalize (GObject *object) { GimpMeter *meter = GIMP_METER (object); g_mutex_clear (&meter->priv->mutex); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gimp_meter_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GimpMeter *meter = GIMP_METER (object); switch (property_id) { case PROP_SIZE: gimp_meter_set_size (meter, g_value_get_int (value)); break; case PROP_REFRESH_RATE: gimp_meter_set_refresh_rate (meter, g_value_get_double (value)); break; case PROP_RANGE_MIN: gimp_meter_set_range (meter, g_value_get_double (value), gimp_meter_get_range_max (meter)); break; case PROP_RANGE_MAX: gimp_meter_set_range (meter, gimp_meter_get_range_min (meter), g_value_get_double (value)); break; case PROP_N_VALUES: gimp_meter_set_n_values (meter, g_value_get_int (value)); break; case PROP_HISTORY_VISIBLE: gimp_meter_set_history_visible (meter, g_value_get_boolean (value)); break; case PROP_HISTORY_DURATION: gimp_meter_set_history_duration (meter, g_value_get_double (value)); break; case PROP_HISTORY_RESOLUTION: gimp_meter_set_history_resolution (meter, g_value_get_double (value)); break; case PROP_LED_ACTIVE: gimp_meter_set_led_active (meter, g_value_get_boolean (value)); break; case PROP_LED_COLOR: gimp_meter_set_led_color (meter, g_value_get_boxed (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_meter_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GimpMeter *meter = GIMP_METER (object); switch (property_id) { case PROP_SIZE: g_value_set_int (value, gimp_meter_get_size (meter)); break; case PROP_REFRESH_RATE: g_value_set_double (value, gimp_meter_get_refresh_rate (meter)); break; case PROP_RANGE_MIN: g_value_set_double (value, gimp_meter_get_range_min (meter)); break; case PROP_RANGE_MAX: g_value_set_double (value, gimp_meter_get_range_max (meter)); break; case PROP_N_VALUES: g_value_set_int (value, gimp_meter_get_n_values (meter)); break; case PROP_HISTORY_VISIBLE: g_value_set_boolean (value, gimp_meter_get_history_visible (meter)); break; case PROP_HISTORY_DURATION: g_value_set_int (value, gimp_meter_get_history_duration (meter)); break; case PROP_HISTORY_RESOLUTION: g_value_set_int (value, gimp_meter_get_history_resolution (meter)); break; case PROP_LED_ACTIVE: g_value_set_boolean (value, gimp_meter_get_led_active (meter)); break; case PROP_LED_COLOR: g_value_set_boxed (value, gimp_meter_get_led_color (meter)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_meter_map (GtkWidget *widget) { GimpMeter *meter = GIMP_METER (widget); GTK_WIDGET_CLASS (parent_class)->map (widget); if (! meter->priv->timeout_id) { gint interval = ROUND (1000.0 / meter->priv->refresh_rate); meter->priv->timeout_id = g_timeout_add (interval, (GSourceFunc) gimp_meter_timeout, meter); } } static void gimp_meter_unmap (GtkWidget *widget) { GimpMeter *meter = GIMP_METER (widget); if (meter->priv->timeout_id) { g_source_remove (meter->priv->timeout_id); meter->priv->timeout_id = 0; } GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void gimp_meter_size_request (GtkWidget *widget, GtkRequisition *requisition) { gint hsize; gint vsize; GimpMeter *meter = GIMP_METER (widget); hsize = meter->priv->size; vsize = meter->priv->size; if (meter->priv->history_visible) hsize *= 3; requisition->width = ceil ( hsize + 2.0 * BORDER_WIDTH); requisition->height = ceil (3.0 / 4.0 * vsize + 4.0 * BORDER_WIDTH); } static gboolean gimp_meter_expose_event (GtkWidget *widget, GdkEventExpose *event) { GimpMeter *meter = GIMP_METER (widget); GtkAllocation allocation; gint size = meter->priv->size; GtkStyle *style = gtk_widget_get_style (widget); GtkStateType state = gtk_widget_get_state (widget); cairo_t *cr; gint i; gint j; gint k; if (! gtk_widget_is_drawable (widget)) return FALSE; g_mutex_lock (&meter->priv->mutex); cr = gdk_cairo_create (event->window); gdk_cairo_region (cr, event->region); cairo_clip (cr); gtk_widget_get_allocation (widget, &allocation); /* translate to allocation top-left */ cairo_translate (cr, allocation.x, allocation.y); cairo_save (cr); /* translate to gauge center */ cairo_translate (cr, 0.5 * BORDER_WIDTH + 0.5 * size, 1.5 * BORDER_WIDTH + 2.0 / 3.0 * (allocation.height - 4.0 * BORDER_WIDTH)); cairo_save (cr); /* paint led */ if (meter->priv->led_active) { cairo_arc (cr, 0.0, 0.0, 0.06 * size, 0.0 * REV, 1.0 * REV); gimp_cairo_set_source_rgba (cr, &meter->priv->led_color); cairo_fill (cr); } /* clip to gauge interior */ cairo_arc (cr, 0.0, 0.0, 0.5 * size, 5.0 / 12.0 * REV, 1.0 / 12.0 * REV); cairo_arc_negative (cr, 0.0, 0.0, 0.1 * size, 1.0 / 12.0 * REV, 5.0 / 12.0 * REV); cairo_close_path (cr); cairo_clip (cr); /* paint gauge background */ gdk_cairo_set_source_color (cr, &style->light[state]); cairo_paint (cr); /* paint values of last sample */ if (meter->priv->range_min < meter->priv->range_max) { for (i = 0; i < meter->priv->n_values; i++) { gdouble v = VALUE (0, i); if (! meter->priv->values[i].active || ! meter->priv->values[i].show_in_gauge) { continue; } gimp_cairo_set_source_rgba (cr, &meter->priv->values[i].color); cairo_move_to (cr, 0.0, 0.0); cairo_arc (cr, 0.0, 0.0, 0.5 * size, 5.0 / 12.0 * REV, (5.0 / 12.0 + 2.0 / 3.0 * v) * REV); cairo_line_to (cr, 0.0, 0.0); cairo_close_path (cr); cairo_fill (cr); } } cairo_restore (cr); /* paint gauge border */ gdk_cairo_set_source_color (cr, &style->fg[state]); cairo_set_line_width (cr, BORDER_WIDTH); cairo_arc (cr, 0.0, 0.0, 0.5 * size, 5.0 / 12.0 * REV, 1.0 / 12.0 * REV); cairo_arc_negative (cr, 0.0, 0.0, 0.1 * size, 1.0 / 12.0 * REV, 5.0 / 12.0 * REV); cairo_close_path (cr); cairo_stroke (cr); /* history */ if (meter->priv->history_visible) { gdouble a1, a2; gdouble history_x1, history_y1; gdouble history_x2, history_y2; cairo_save (cr); a1 = +asin (0.25 / 0.6); a2 = -asin (0.50 / 0.6); /* clip to history interior */ cairo_arc_negative (cr, 0.0, 0.0, 0.6 * size, a1, a2); cairo_line_to (cr, allocation.width - BORDER_WIDTH - 0.5 * size, -0.50 * size); cairo_line_to (cr, allocation.width - BORDER_WIDTH - 0.5 * size, 0.25 * size); cairo_close_path (cr); cairo_path_extents (cr, &history_x1, &history_y1, &history_x2, &history_y2); history_x1 = floor (history_x1); history_y1 = floor (history_y1); history_x2 = ceil (history_x2); history_y2 = ceil (history_y2); cairo_clip (cr); /* paint history background */ gdk_cairo_set_source_color (cr, &style->light[state]); cairo_paint (cr); /* history graph */ if (meter->priv->range_min < meter->priv->range_max) { gdouble sample_width = (history_x2 - history_x1) / (meter->priv->n_samples - 4); gdouble dx = 1.0 / sample_width; cairo_save (cr); /* translate to history bottom-right, and scale so that the * x-axis points left, and has a length of one sample, and * the y-axis points up, and has a length of the history * window. */ cairo_translate (cr, history_x2, history_y2); cairo_scale (cr, -sample_width, -(history_y2 - history_y1)); cairo_translate (cr, (gdouble) (meter->priv->current_time - meter->priv->last_sample_time * meter->priv->sample_duration) / meter->priv->sample_duration - 2.0, 0.0); /* paint history graph for each value */ for (i = 0; i < meter->priv->n_values; i++) { gdouble y; if (! meter->priv->values[i].active || ! meter->priv->values[i].show_in_history) { continue; } gimp_cairo_set_source_rgba (cr, &meter->priv->values[i].color); cairo_move_to (cr, 0.0, 0.0); switch (meter->priv->values[i].interpolation) { case GIMP_INTERPOLATION_NONE: { for (j = 1; j < meter->priv->n_samples - 2; j++) { gdouble y0 = VALUE (j - 1, i); gdouble y1 = VALUE (j, i); cairo_line_to (cr, j, y0); cairo_line_to (cr, j, y1); } } break; case GIMP_INTERPOLATION_LINEAR: { for (j = 1; j < meter->priv->n_samples - 2; j++) { gdouble y = VALUE (j, i); cairo_line_to (cr, j, y); } } break; case GIMP_INTERPOLATION_CUBIC: default: { for (j = 1; j < meter->priv->n_samples - 2; j++) { gdouble y[4]; gdouble t[2]; gdouble c[4]; gdouble x; for (k = 0; k < 4; k++) y[k] = VALUE (j + k - 1, i); for (k = 0; k < 2; k++) { t[k] = (y[k + 2] - y[k]) / 2.0; t[k] = CLAMP (t[k], y[k + 1] - 1.0, y[k + 1]); t[k] = CLAMP (t[k], -y[k + 1], 1.0 - y[k + 1]); } c[0] = y[1]; c[1] = t[0]; c[2] = 3 * (y[2] - y[1]) - 2 * t[0] - t[1]; c[3] = t[0] + t[1] - 2 * (y[2] - y[1]); for (x = 0.0; x < 1.0; x += dx) { gdouble y = ((c[3] * x + c[2]) * x + c[1]) * x + c[0]; cairo_line_to (cr, j + x, y); } } } break; } y = VALUE (j, i); cairo_line_to (cr, meter->priv->n_samples - 2, y); cairo_line_to (cr, meter->priv->n_samples - 2, 0.0); cairo_close_path (cr); cairo_fill (cr); } cairo_restore (cr); } /* paint history grid */ cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); cairo_set_source_rgba (cr, (gdouble) style->fg[state].red / 0xffff, (gdouble) style->fg[state].green / 0xffff, (gdouble) style->fg[state].blue / 0xffff, 0.3); for (i = 1; i < 4; i++) { cairo_move_to (cr, history_x1, history_y1 + i / 4.0 * (history_y2 - history_y1)); cairo_rel_line_to (cr, history_x2 - history_x1, 0.0); cairo_stroke (cr); } for (i = 1; i < 6; i++) { cairo_move_to (cr, history_x1 + i / 6.0 * (history_x2 - history_x1), history_y1); cairo_rel_line_to (cr, 0.0, history_y2 - history_y1); cairo_stroke (cr); } cairo_restore (cr); /* paint history border */ cairo_arc_negative (cr, 0.0, 0.0, 0.6 * size, a1, a2); cairo_line_to (cr, allocation.width - BORDER_WIDTH - 0.5 * size, -0.50 * size); cairo_line_to (cr, allocation.width - BORDER_WIDTH - 0.5 * size, 0.25 * size); cairo_close_path (cr); cairo_stroke (cr); } cairo_restore (cr); cairo_destroy (cr); g_mutex_unlock (&meter->priv->mutex); return FALSE; } static gboolean gimp_meter_timeout (GimpMeter *meter) { gboolean uniform = TRUE; gboolean redraw = TRUE; gdouble sample0[meter->priv->n_values]; gint i; g_mutex_lock (&meter->priv->mutex); gimp_meter_shift_samples (meter); gimp_meter_mask_sample (meter, SAMPLE (0), sample0); if (meter->priv->history_visible) { for (i = 1; uniform && i < meter->priv->n_samples; i++) { gdouble sample[meter->priv->n_values]; gimp_meter_mask_sample (meter, SAMPLE (i), sample); uniform = ! memcmp (sample0, sample, SAMPLE_SIZE); } } if (uniform && meter->priv->uniform_sample) redraw = memcmp (sample0, meter->priv->uniform_sample, SAMPLE_SIZE); if (uniform) { if (! meter->priv->uniform_sample) meter->priv->uniform_sample = g_malloc (SAMPLE_SIZE); memcpy (meter->priv->uniform_sample, sample0, SAMPLE_SIZE); } else { g_clear_pointer (&meter->priv->uniform_sample, g_free); } g_mutex_unlock (&meter->priv->mutex); if (redraw) gtk_widget_queue_draw (GTK_WIDGET (meter)); return G_SOURCE_CONTINUE; } static void gimp_meter_clear_history_unlocked (GimpMeter *meter) { meter->priv->current_time = g_get_monotonic_time (); meter->priv->last_sample_time = meter->priv->current_time / meter->priv->sample_duration; memset (meter->priv->samples, 0, meter->priv->n_samples * SAMPLE_SIZE); g_clear_pointer (&meter->priv->uniform_sample, g_free); } static void gimp_meter_update_samples (GimpMeter *meter) { meter->priv->n_samples = ceil (meter->priv->history_duration / meter->priv->history_resolution) + 4; meter->priv->samples = g_renew (gdouble, meter->priv->samples, meter->priv->n_samples * meter->priv->n_values); meter->priv->sample_duration = ROUND (meter->priv->history_resolution * G_TIME_SPAN_SECOND); gimp_meter_clear_history_unlocked (meter); } static void gimp_meter_shift_samples (GimpMeter *meter) { gint64 time; gint n_new_samples; meter->priv->current_time = g_get_monotonic_time (); time = meter->priv->current_time / meter->priv->sample_duration; n_new_samples = MIN (time - meter->priv->last_sample_time, meter->priv->n_samples - 1); memmove (SAMPLE (n_new_samples), SAMPLE (0), (meter->priv->n_samples - n_new_samples) * SAMPLE_SIZE); gegl_memset_pattern (SAMPLE (0), SAMPLE (n_new_samples), SAMPLE_SIZE, n_new_samples); meter->priv->last_sample_time = time; } static void gimp_meter_mask_sample (GimpMeter *meter, const gdouble *sample, gdouble *result) { gint i; for (i = 0; i < meter->priv->n_values; i++) { if (meter->priv->values[i].active && (meter->priv->values[i].show_in_gauge || meter->priv->values[i].show_in_history)) { result[i] = sample[i]; } else { result[i] = 0.0; } } } /* public functions */ GtkWidget * gimp_meter_new (gint n_values) { return g_object_new (GIMP_TYPE_METER, "n-values", n_values, NULL); } void gimp_meter_set_size (GimpMeter *meter, gint size) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (size > 0); if (size != meter->priv->size) { meter->priv->size = size; gtk_widget_queue_resize (GTK_WIDGET (meter)); g_object_notify (G_OBJECT (meter), "size"); } } gint gimp_meter_get_size (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), 0); return meter->priv->size; } void gimp_meter_set_refresh_rate (GimpMeter *meter, gdouble rate) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (rate > 0.0); if (rate != meter->priv->refresh_rate) { meter->priv->refresh_rate = rate; if (meter->priv->timeout_id) { gint interval = ROUND (1000.0 / meter->priv->refresh_rate); g_source_remove (meter->priv->timeout_id); meter->priv->timeout_id = g_timeout_add (interval, (GSourceFunc) gimp_meter_timeout, meter); } g_object_notify (G_OBJECT (meter), "refresh-rate"); } } gdouble gimp_meter_get_refresh_rate (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), 0); return meter->priv->refresh_rate; } void gimp_meter_set_range (GimpMeter *meter, gdouble min, gdouble max) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (min <= max); if (min != meter->priv->range_min) { g_mutex_lock (&meter->priv->mutex); meter->priv->range_min = min; g_mutex_unlock (&meter->priv->mutex); gtk_widget_queue_draw (GTK_WIDGET (meter)); g_object_notify (G_OBJECT (meter), "range-min"); } if (max != meter->priv->range_max) { g_mutex_lock (&meter->priv->mutex); meter->priv->range_max = max; g_mutex_unlock (&meter->priv->mutex); gtk_widget_queue_draw (GTK_WIDGET (meter)); g_object_notify (G_OBJECT (meter), "range-max"); } } gdouble gimp_meter_get_range_min (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), 0.0); return meter->priv->range_min; } gdouble gimp_meter_get_range_max (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), 0.0); return meter->priv->range_max; } void gimp_meter_set_n_values (GimpMeter *meter, gint n_values) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (n_values >= 0); if (n_values != meter->priv->n_values) { g_mutex_lock (&meter->priv->mutex); meter->priv->values = g_renew (Value, meter->priv->values, n_values); if (n_values > meter->priv->n_values) { gegl_memset_pattern (meter->priv->values, &(Value) { .active = TRUE, .show_in_gauge = TRUE, .show_in_history = TRUE, .interpolation = GIMP_INTERPOLATION_CUBIC}, sizeof (Value), n_values - meter->priv->n_values); } meter->priv->n_values = n_values; gimp_meter_update_samples (meter); g_mutex_unlock (&meter->priv->mutex); gtk_widget_queue_draw (GTK_WIDGET (meter)); g_object_notify (G_OBJECT (meter), "n-values"); } } gint gimp_meter_get_n_values (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), 0); return meter->priv->n_values; } void gimp_meter_set_value_active (GimpMeter *meter, gint value, gboolean active) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (value >= 0 && value < meter->priv->n_values); if (active != meter->priv->values[value].active) { meter->priv->values[value].active = active; gtk_widget_queue_draw (GTK_WIDGET (meter)); } } gboolean gimp_meter_get_value_active (GimpMeter *meter, gint value) { g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, FALSE); return meter->priv->values[value].active; } void gimp_meter_set_value_color (GimpMeter *meter, gint value, const GimpRGB *color) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (value >= 0 && value < meter->priv->n_values); g_return_if_fail (color != NULL); if (memcmp (color, &meter->priv->values[value].color, sizeof (GimpRGB))) { meter->priv->values[value].color = *color; gtk_widget_queue_draw (GTK_WIDGET (meter)); } } const GimpRGB * gimp_meter_get_value_color (GimpMeter *meter, gint value) { g_return_val_if_fail (GIMP_IS_METER (meter), NULL); g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, NULL); return &meter->priv->values[value].color; } void gimp_meter_set_value_interpolation (GimpMeter *meter, gint value, GimpInterpolationType interpolation) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (value >= 0 && value < meter->priv->n_values); if (meter->priv->values[value].interpolation != interpolation) { meter->priv->values[value].interpolation = interpolation; gtk_widget_queue_draw (GTK_WIDGET (meter)); } } GimpInterpolationType gimp_meter_get_value_interpolation (GimpMeter *meter, gint value) { g_return_val_if_fail (GIMP_IS_METER (meter), GIMP_INTERPOLATION_NONE); g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, GIMP_INTERPOLATION_NONE); return meter->priv->values[value].interpolation; } void gimp_meter_set_value_show_in_gauge (GimpMeter *meter, gint value, gboolean show) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (value >= 0 && value < meter->priv->n_values); if (meter->priv->values[value].show_in_gauge != show) { meter->priv->values[value].show_in_gauge = show; gtk_widget_queue_draw (GTK_WIDGET (meter)); } } gboolean gimp_meter_get_value_show_in_gauge (GimpMeter *meter, gint value) { g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, FALSE); return meter->priv->values[value].show_in_gauge; } void gimp_meter_set_value_show_in_history (GimpMeter *meter, gint value, gboolean show) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (value >= 0 && value < meter->priv->n_values); if (meter->priv->values[value].show_in_history != show) { meter->priv->values[value].show_in_history = show; gtk_widget_queue_draw (GTK_WIDGET (meter)); } } gboolean gimp_meter_get_value_show_in_history (GimpMeter *meter, gint value) { g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); g_return_val_if_fail (value >= 0 && value < meter->priv->n_values, FALSE); return meter->priv->values[value].show_in_history; } void gimp_meter_set_history_visible (GimpMeter *meter, gboolean visible) { g_return_if_fail (GIMP_IS_METER (meter)); if (visible != meter->priv->history_visible) { meter->priv->history_visible = visible; gtk_widget_queue_resize (GTK_WIDGET (meter)); g_object_notify (G_OBJECT (meter), "history-visible"); } } gboolean gimp_meter_get_history_visible (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); return meter->priv->history_visible; } void gimp_meter_set_history_duration (GimpMeter *meter, gdouble duration) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (duration >= 0.0); if (duration != meter->priv->history_duration) { g_mutex_lock (&meter->priv->mutex); meter->priv->history_duration = duration; gimp_meter_update_samples (meter); g_mutex_unlock (&meter->priv->mutex); g_object_notify (G_OBJECT (meter), "history-duration"); } } gdouble gimp_meter_get_history_duration (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), 0.0); return meter->priv->history_duration; } void gimp_meter_set_history_resolution (GimpMeter *meter, gdouble resolution) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (resolution > 0.0); if (resolution != meter->priv->history_resolution) { g_mutex_lock (&meter->priv->mutex); meter->priv->history_resolution = resolution; gimp_meter_update_samples (meter); g_mutex_unlock (&meter->priv->mutex); g_object_notify (G_OBJECT (meter), "history-resolution"); } } gdouble gimp_meter_get_history_resolution (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), 0.0); return meter->priv->history_resolution; } void gimp_meter_clear_history (GimpMeter *meter) { g_return_if_fail (GIMP_IS_METER (meter)); g_mutex_lock (&meter->priv->mutex); gimp_meter_clear_history_unlocked (meter); g_mutex_unlock (&meter->priv->mutex); gtk_widget_queue_draw (GTK_WIDGET (meter)); } void gimp_meter_add_sample (GimpMeter *meter, const gdouble *sample) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (sample != NULL || meter->priv->n_values == 0); g_mutex_lock (&meter->priv->mutex); gimp_meter_shift_samples (meter); memcpy (SAMPLE (0), sample, SAMPLE_SIZE); g_mutex_unlock (&meter->priv->mutex); } void gimp_meter_set_led_active (GimpMeter *meter, gboolean active) { g_return_if_fail (GIMP_IS_METER (meter)); if (active != meter->priv->led_active) { meter->priv->led_active = active; gtk_widget_queue_draw (GTK_WIDGET (meter)); g_object_notify (G_OBJECT (meter), "led-active"); } } gboolean gimp_meter_get_led_active (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), FALSE); return meter->priv->led_active; } void gimp_meter_set_led_color (GimpMeter *meter, const GimpRGB *color) { g_return_if_fail (GIMP_IS_METER (meter)); g_return_if_fail (color != NULL); if (memcmp (color, &meter->priv->led_color, sizeof (GimpRGB))) { meter->priv->led_color = *color; if (meter->priv->led_active) gtk_widget_queue_draw (GTK_WIDGET (meter)); g_object_notify (G_OBJECT (meter), "led-color"); } } const GimpRGB * gimp_meter_get_led_color (GimpMeter *meter) { g_return_val_if_fail (GIMP_IS_METER (meter), NULL); return &meter->priv->led_color; }