diff options
Diffstat (limited to 'app/widgets/gimpmeter.c')
-rw-r--r-- | app/widgets/gimpmeter.c | 1328 |
1 files changed, 1328 insertions, 0 deletions
diff --git a/app/widgets/gimpmeter.c b/app/widgets/gimpmeter.c new file mode 100644 index 0000000..9a5ec92 --- /dev/null +++ b/app/widgets/gimpmeter.c @@ -0,0 +1,1328 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#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; +} |