1 /*
2  * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  * -------------------------------------------------------------------------
18  *
19  * Code shamelessly stolen from Ardour by Paul Davis, thanks man!
20  * This is actually an adaptation of the C++ gtkmm2ext class in the
21  * GTK C style.
22  *
23  * -------------------------------------------------------------------------
24  */
25 #include <math.h>
26 #include <stdlib.h>
27 #include "GxFastMeter.h"
28 #include "GxControlParameter.h"
29 #include "log_meter.h"
30 
31 #ifndef max
32 #define max(x,y) (((x)>(y)) ? (x) : (y))
33 #endif
34 
35 #ifndef min
36 #define min(x,y) (((x)<(y)) ? (x) : (y))
37 #endif
38 
39 #define P_(s) (s)   // FIXME -> gettext
40 
41 #define FALLOFF_DURATION 500  // time in ms for linear falloff (max scale -> 0)
42 #define FALLOFF_UPDATE_RATE 60  // time in ms between falloff updates
43 
44 #define FALLOFF_VALUE (FALLOFF_UPDATE_RATE/(float)FALLOFF_DURATION)
45 
46 struct _GxFastMeterPrivate {
47 	cairo_surface_t *surface, *overlay;
48 	gint	      top_of_meter;
49 	GdkRectangle  last_peak_rect, bar;
50 
51 	gchar *var_id;
52 	int hold_cnt;
53 	int hold_state;                    // countdown for peak bar display
54 	float falloff;
55 	guint falloff_timer_id;
56 	bool horiz;
57 
58 	float	      target_level;         // target deflection (0 .. 1)
59 	float	      current_level;        // current deflection (with falloff)
60 	float         min_level, max_level; // range for linear display
61 	float	      current_peak;         // for display of peak bar
62 	gboolean      is_power;
63 	gint dimen, clr0, clr1, clr2, clr3, type;
64 };
65 
66 enum {
67 	PROP_HOLD = 1,
68 	PROP_DIMEN,
69 	PROP_VAR_ID,
70 	PROP_ORIENTATION,
71 	PROP_FALLOFF,
72 	PROP_POWER,
73 };
74 
75 static const int min_size = 1;
76 
77 static void gx_fast_meter_class_init(GxFastMeterClass*);
78 static void gx_fast_meter_init(GxFastMeter*);
79 static void gx_fast_meter_destroy(GtkWidget *object);
80 
81 static void gx_control_parameter_interface_init (GxControlParameterIface *iface);
82 
83 static gboolean gx_fast_meter_draw(GtkWidget*, cairo_t *);
84 static void gx_fast_meter_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
85 static void gx_fast_meter_state_changed(GtkWidget*, GtkStateType);
86 static void gx_fast_meter_get_preferred_width(GtkWidget*, gint *min_width, gint *natural_width);
87 static void gx_fast_meter_get_preferred_height(GtkWidget*, gint *min_height, gint *natural_height);
88 static void gx_fast_meter_size_request(GtkWidget*, gint *width, gint *height);
89 static void gx_fast_meter_set_is_power(GxFastMeter *fm, bool state);
90 static void gx_fast_meter_set_property(
91 	GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
92 static void gx_fast_meter_get_property(
93 	GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
94 static void queue_redraw(GxFastMeter*);
95 static void request_meter(GtkWidget *widget);
96 static void remove_handler(GxFastMeter *fm);
97 static void gx_fast_meter_style_updated(GtkWidget *widget);
98 static void orientation_set_css_class(GxFastMeter *fm);
99 
G_DEFINE_TYPE_WITH_CODE(GxFastMeter,gx_fast_meter,GTK_TYPE_DRAWING_AREA,G_ADD_PRIVATE (GxFastMeter)G_IMPLEMENT_INTERFACE (GX_TYPE_CONTROL_PARAMETER,gx_control_parameter_interface_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,NULL))100 G_DEFINE_TYPE_WITH_CODE(GxFastMeter, gx_fast_meter, GTK_TYPE_DRAWING_AREA,
101                         G_ADD_PRIVATE(GxFastMeter)
102                         G_IMPLEMENT_INTERFACE(GX_TYPE_CONTROL_PARAMETER,
103                                               gx_control_parameter_interface_init)
104 						G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
105 
106 static void
107 gx_fast_meter_cp_configure(GxControlParameter *self, const gchar* group, const gchar *name, gdouble lower, gdouble upper, gdouble step)
108 {
109 	g_return_if_fail(GX_IS_FAST_METER(self));
110 	GxFastMeter *fm = GX_FAST_METER(self);
111 	fm->priv->min_level = lower;
112 	fm->priv->max_level = upper;
113 	gx_fast_meter_set_is_power(fm, step != 0);
114 }
115 
116 static gdouble
gx_fast_meter_cp_get_value(GxControlParameter * self)117 gx_fast_meter_cp_get_value(GxControlParameter *self)
118 {
119 	return GX_FAST_METER(self)->priv->current_level;
120 }
121 
122 static void
gx_fast_meter_cp_set_value(GxControlParameter * self,gdouble value)123 gx_fast_meter_cp_set_value(GxControlParameter *self, gdouble value)
124 {
125 	GxFastMeter *fm = GX_FAST_METER(self);
126 	if (!gtk_widget_get_sensitive(GTK_WIDGET(self))) { //FIXME
127 		return;
128 	}
129 	if (fm->priv->is_power) {
130 		gx_fast_meter_set_by_power(fm, value);
131 	} else {
132 		gx_fast_meter_set(fm, value);
133 	}
134 }
135 
136 static void
gx_control_parameter_interface_init(GxControlParameterIface * iface)137 gx_control_parameter_interface_init(GxControlParameterIface *iface)
138 {
139   iface->cp_configure = gx_fast_meter_cp_configure;
140   iface->cp_set_value = gx_fast_meter_cp_set_value;
141   iface->cp_get_value = gx_fast_meter_cp_get_value;
142 }
143 
144 /* ----- fast meter class init ----- */
gx_fast_meter_class_init(GxFastMeterClass * klass)145 void gx_fast_meter_class_init(GxFastMeterClass* klass)
146 {
147 	GtkWidgetClass* widget_class = (GtkWidgetClass*)klass;
148 	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
149 
150 	widget_class->get_preferred_width = gx_fast_meter_get_preferred_width;
151 	widget_class->get_preferred_height = gx_fast_meter_get_preferred_height;
152 	widget_class->size_allocate = gx_fast_meter_size_allocate;
153 	widget_class->draw  = gx_fast_meter_draw;
154 	widget_class->destroy = gx_fast_meter_destroy;
155 	widget_class->style_updated = gx_fast_meter_style_updated;
156 	widget_class->state_changed = gx_fast_meter_state_changed;
157 	gobject_class->set_property = gx_fast_meter_set_property;
158 	gobject_class->get_property = gx_fast_meter_get_property;
159 
160 	gtk_widget_class_set_css_name(widget_class, "gx-fast-meter");
161 
162 	g_object_class_install_property (
163 		gobject_class, PROP_VAR_ID, g_param_spec_string(
164 			"var-id", P_("Variable"),
165 			P_("The id of the linked variable"),
166 			NULL, GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
167 	g_object_class_install_property(
168 		gobject_class, PROP_HOLD, g_param_spec_int(
169 			"hold", P_("Hold"),
170 			P_("Count of cycles for which the peak value is held on display"),
171 			0, 1000, 2, GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
172 	g_object_class_install_property(
173 		gobject_class, PROP_DIMEN, g_param_spec_int(
174 			"dimen", P_("Dimension"),
175 			P_("Size of meter"),
176 			0, 100, 2, GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
177     g_object_class_install_property(
178 		gobject_class, PROP_FALLOFF, g_param_spec_boolean(
179 			"falloff", P_("Falloff"),
180 			P_("Meter peak falloff"),
181 			FALSE, GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
182     g_object_class_install_property(
183 		gobject_class, PROP_POWER, g_param_spec_boolean(
184 			"power", P_("Powermeter"),
185 			P_("Meter is showing signal power (input range: 0 .. 2)"),
186 			FALSE, GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
187 	g_object_class_override_property(gobject_class, PROP_ORIENTATION, "orientation");
188 	gtk_widget_class_install_style_property(
189 		widget_class,
190 		g_param_spec_boxed("clr-bottom",P_("bottom color"),
191 		                   P_("indicator color gradient: value at the bottom"),
192 		                   GDK_TYPE_RGBA,
193 		                   GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
194 	gtk_widget_class_install_style_property(
195 		widget_class,
196 		g_param_spec_boxed("clr-middle",P_("middle color"),
197 		                   P_("indicator color gradient: value in the middle"),
198 		                   GDK_TYPE_RGBA,
199 		                   GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
200 	gtk_widget_class_install_style_property(
201 		widget_class,
202 		g_param_spec_boxed("clr-top",P_("top color"),
203 		                   P_("indicator color gradient: value near the top"),
204 		                   GDK_TYPE_RGBA,
205 		                   GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
206 	gtk_widget_class_install_style_property(
207 		widget_class,
208 		g_param_spec_boxed("over",P_("clip warn color"),
209 		                   P_("indicator color for values > 0 dbFS"),
210 		                   GDK_TYPE_RGBA,
211 		                   GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
212 	gtk_widget_class_install_style_property(
213 		widget_class,
214 		g_param_spec_int("dimen",P_("width of indicator"),
215 		                   P_("width of (vertical) indicator"),
216 		                 0, 100, 2, GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
217     gtk_widget_class_install_style_property(
218 		widget_class,
219 		g_param_spec_int("led-border",P_("LED Border"),
220 		                   P_("Border around LED"),
221 		                 0, 100, 1, GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
222     gtk_widget_class_install_style_property(
223 		widget_class,
224 		g_param_spec_int("led-width",P_("LED Width"),
225 		                   P_("Width of LED"),
226 		                 0, 100, 2, GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
227     gtk_widget_class_install_style_property(
228 		widget_class,
229 		g_param_spec_int("led-height",P_("LED Height"),
230 		                   P_("Height of LED"),
231 		                 0, 100, 2, GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
232     gtk_widget_class_install_style_property(
233 		widget_class,
234 		g_param_spec_int("border-radius",
235             P_("Border Radius"),
236             P_("The radius of the corners in pixels"),
237             0, 100, 0,
238             GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
239 
240     gtk_widget_class_install_style_property(
241 		widget_class,
242 		g_param_spec_float("bevel",
243             P_("Bevel"),
244             P_("The bevel effect"),
245             -1.0, 1.0, 0.0,
246             GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
247     gtk_widget_class_install_style_property(
248 		widget_class,
249 		g_param_spec_float("mid-pos",
250             P_("Mid-Position"),
251             P_("Position of the middle color"),
252             0, 1, 0.5,
253             GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
254 }
255 
256 /* ----- fast meter init ----- */
gx_fast_meter_init(GxFastMeter * fm)257 void gx_fast_meter_init(GxFastMeter* fm)
258 {
259 	GtkWidget *widget = GTK_WIDGET(fm);
260 	fm->priv = (GxFastMeterPrivate*)gx_fast_meter_get_instance_private(fm);
261 	fm->priv->surface = 0;
262 	fm->priv->overlay = 0;
263 	fm->priv->top_of_meter = 0;
264 	fm->priv->last_peak_rect.width = 0;
265 	fm->priv->last_peak_rect.height = 0;
266 	fm->priv->var_id = NULL;
267 	fm->priv->hold_cnt = 0;
268 	fm->priv->hold_state = 0;
269 	fm->priv->falloff = 0;
270 	fm->priv->falloff_timer_id = 0;
271 	fm->priv->horiz = TRUE;
272 	orientation_set_css_class(fm);
273 	fm->priv->current_peak = 0;
274 	fm->priv->target_level = 0;
275 	fm->priv->min_level = 0;
276 	fm->priv->max_level = 1;
277 	fm->priv->is_power = FALSE;
278 	fm->priv->current_level =  0;
279 	gtk_widget_set_events(widget,
280 						  GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK);
281     GdkScreen *screen = gdk_screen_get_default();
282     GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
283     if (visual && gdk_screen_is_composited (screen))
284         gtk_widget_set_visual(widget, visual);
285 }
286 
gx_fast_meter_destroy(GtkWidget * object)287 static void gx_fast_meter_destroy(GtkWidget *object)
288 {
289 	GxFastMeter *fm = GX_FAST_METER(object);
290 	remove_handler(fm);
291 	if (fm->priv->surface) {
292 		cairo_surface_destroy(fm->priv->surface);
293 		fm->priv->surface = nullptr;
294 	}
295 	if (fm->priv->overlay) {
296         cairo_surface_destroy(fm->priv->overlay);
297 		fm->priv->overlay = nullptr;
298 	}
299 	g_free(fm->priv->var_id);
300 	fm->priv->var_id = nullptr;
301 	GTK_WIDGET_CLASS(gx_fast_meter_parent_class)->destroy (object);
302 }
303 
304 /* -------------- */
gtk_fast_meter_new(int hold)305 GtkWidget* gtk_fast_meter_new (int hold)
306 {
307 	GxFastMeter* fm;
308 	fm = GX_FAST_METER(g_object_new(GX_TYPE_FAST_METER, NULL));
309 	fm->priv->hold_cnt = hold;
310 	return GTK_WIDGET (fm);
311 }
312 
313 
314 /* ------ hold count ----- */
gx_fast_meter_set_hold_count(GxFastMeter * fm,int val)315 void gx_fast_meter_set_hold_count(GxFastMeter* fm, int val)
316 {
317 	if (val < 1) val = 1;
318 
319 	fm->priv->hold_cnt     = val;
320 	fm->priv->hold_state   = 0;
321 	fm->priv->current_peak = 0;
322 
323 	gtk_widget_queue_draw(GTK_WIDGET(fm));
324 }
325 
gx_fast_meter_size_allocate(GtkWidget * widget,GtkAllocation * allocation)326 static void gx_fast_meter_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
327 {
328 	GTK_WIDGET_CLASS(gx_fast_meter_parent_class)->size_allocate(widget, allocation);
329 	request_meter(widget);
330 }
331 
gx_fast_meter_set_is_power(GxFastMeter * fm,bool state)332 static void gx_fast_meter_set_is_power(GxFastMeter *fm, bool state)
333 {
334 	fm->priv->is_power = state;
335 	g_object_notify(G_OBJECT(fm), "power");
336 }
337 
gx_fast_meter_set_var_id(GxFastMeter * fm,const gchar * str)338 static void gx_fast_meter_set_var_id(GxFastMeter *fm, const gchar *str)
339 {
340 	g_free(fm->priv->var_id);
341 	fm->priv->var_id = g_strdup(str ? str : "");
342 	g_object_notify(G_OBJECT(fm), "var-id");
343 }
344 
orientation_set_css_class(GxFastMeter * fm)345 static void orientation_set_css_class(GxFastMeter *fm)
346 {
347 	GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(fm));
348 	if (fm->priv->horiz) {
349 		gtk_style_context_add_class (context, GTK_STYLE_CLASS_HORIZONTAL);
350 		gtk_style_context_remove_class (context, GTK_STYLE_CLASS_VERTICAL);
351 	} else {
352       gtk_style_context_add_class (context, GTK_STYLE_CLASS_VERTICAL);
353       gtk_style_context_remove_class (context, GTK_STYLE_CLASS_HORIZONTAL);
354     }
355 }
356 
gx_fast_meter_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)357 static void gx_fast_meter_set_property(GObject *object, guint prop_id,
358                                            const GValue *value, GParamSpec *pspec)
359 {
360 	GxFastMeter *fm = GX_FAST_METER(object);
361 	switch(prop_id) {
362 	case PROP_HOLD:
363 		fm->priv->hold_cnt = g_value_get_int(value);
364 		g_object_notify(object, "hold");
365 		fm->priv->hold_state = 0;
366 		break;
367 	case PROP_DIMEN:
368 		fm->priv->dimen = g_value_get_int(value);
369 		g_object_notify(object, "dimen");
370 		gtk_widget_queue_resize(GTK_WIDGET(object));
371 		break;
372     case PROP_ORIENTATION:
373 		if (fm->priv->horiz != (g_value_get_enum (value) == GTK_ORIENTATION_HORIZONTAL)) {
374 			fm->priv->horiz = !fm->priv->horiz;
375 			orientation_set_css_class(fm);
376 			gtk_widget_queue_resize (GTK_WIDGET (object));
377 			g_object_notify_by_pspec (object, pspec);
378         }
379 		break;
380 	case PROP_FALLOFF:
381 		fm->priv->falloff = g_value_get_boolean(value) ? FALLOFF_VALUE : 0;
382 		g_object_notify(object, "falloff");
383 		break;
384 	case PROP_POWER:
385 		gx_fast_meter_set_is_power(fm, g_value_get_boolean(value));
386 		break;
387 	case PROP_VAR_ID:
388 		gx_fast_meter_set_var_id (fm, g_value_get_string (value));
389 		break;
390 	default:
391 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
392 		break;
393 	}
394 }
395 
gx_fast_meter_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)396 static void gx_fast_meter_get_property(GObject *object, guint prop_id,
397                                            GValue *value, GParamSpec *pspec)
398 {
399 	GxFastMeter *fm = GX_FAST_METER(object);
400 
401 	switch(prop_id) {
402 	case PROP_HOLD:
403 		g_value_set_int(value, fm->priv->hold_cnt);
404 		break;
405 	case PROP_DIMEN:
406 		g_value_set_int(value, fm->priv->dimen);
407 		break;
408     case PROP_ORIENTATION:
409 		g_value_set_enum (value, fm->priv->horiz ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
410 		break;
411 	case PROP_FALLOFF:
412 		g_value_set_boolean(value, fm->priv->falloff != 0);
413 		break;
414 	case PROP_POWER:
415 		g_value_set_boolean(value, fm->priv->is_power);
416 		break;
417 	case PROP_VAR_ID:
418 		g_value_set_string (value, fm->priv->var_id);
419 		break;
420 	default:
421 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
422 		break;
423 	}
424 }
425 
gx_fast_meter_style_updated(GtkWidget * widget)426 static void gx_fast_meter_style_updated(GtkWidget *widget)
427 {
428 	request_meter(widget);
429 }
430 
431 /* ------- setting meter level ----------- */
falloff_handler(gpointer data)432 static gboolean falloff_handler(gpointer data)
433 {
434 	GxFastMeter *fm = GX_FAST_METER(data);
435 	bool hold_redraw = false;
436 	bool falloff_redraw = false;
437 	float falloff = fm->priv->falloff;
438 	float target_level = fm->priv->target_level;
439 
440 	if (!gtk_widget_get_sensitive(GTK_WIDGET(fm))) {
441 		target_level = 0;
442 		fm->priv->hold_state = 0;
443 		falloff = 2 * falloff;
444 	}
445 	if (fm->priv->hold_state > 0) {
446 		fm->priv->hold_state--;
447 		if (!fm->priv->hold_state) {
448 			hold_redraw = true;
449 		}
450 	}
451 	if (falloff) {
452 		if (fm->priv->current_level > target_level) {
453 			fm->priv->current_level = max(
454 				target_level, fm->priv->current_level - falloff);
455 			falloff_redraw = true;
456 		}
457 	}
458 	if (hold_redraw || falloff_redraw) {
459 		queue_redraw(fm);
460 	}
461 	if (!fm->priv->hold_state && fm->priv->current_level == target_level) {
462 		fm->priv->falloff_timer_id = 0;
463 		return FALSE;
464 	}
465 	return TRUE;
466 }
467 
remove_handler(GxFastMeter * fm)468 static void remove_handler(GxFastMeter *fm)
469 {
470 	if (fm->priv->falloff_timer_id) {
471 		g_source_remove(fm->priv->falloff_timer_id);
472 		fm->priv->falloff_timer_id = 0;
473 	}
474 }
475 
check_falloff_timer(GxFastMeter * fm)476 static void check_falloff_timer(GxFastMeter *fm)
477 {
478 	if (!fm->priv->falloff_timer_id && (
479 			fm->priv->hold_state || fm->priv->current_level > fm->priv->target_level)) {
480 		fm->priv->falloff_timer_id = g_timeout_add(FALLOFF_UPDATE_RATE, falloff_handler, fm);
481 	}
482 }
483 
_meter_set(GxFastMeter * fm,float lvl)484 static void _meter_set(GxFastMeter* fm, float lvl)
485 {
486 	float old_level = fm->priv->current_level;
487 	float old_peak  = fm->priv->current_peak;
488 
489 	fm->priv->target_level = lvl;
490 	if (fm->priv->falloff && lvl < fm->priv->current_level) {
491 		lvl = fm->priv->current_level;
492 	} else {
493 		fm->priv->current_level = lvl;
494 	}
495 	if (fm->priv->hold_cnt) {
496 		if (lvl > fm->priv->current_peak) {
497 			fm->priv->current_peak = lvl;
498 			fm->priv->hold_state   = fm->priv->hold_cnt;
499 		} else if (fm->priv->hold_state == 0) {
500 			fm->priv->current_peak = lvl;
501 		}
502 	}
503 	check_falloff_timer(fm);
504 	if (fm->priv->current_level == old_level &&
505 	    (fm->priv->hold_state == 0 || fm->priv->current_peak  == old_peak)) {
506 		return;
507 	}
508 	queue_redraw(fm);
509 }
510 
gx_fast_meter_set(GxFastMeter * fm,gdouble lvl)511 void gx_fast_meter_set(GxFastMeter* fm, gdouble lvl)
512 {
513 	float minl = fm->priv->min_level;
514 	float maxl = fm->priv->max_level;
515 	_meter_set(fm, (max(minl, min(maxl, lvl)) - minl) / (maxl - minl));
516 }
517 
power2db(float power)518 inline float power2db(float power) {
519     return  20.*log10(power);
520 }
521 
meter_level_by_power(GxFastMeter * fm,float new_level)522 static float meter_level_by_power(GxFastMeter* fm, float new_level) {
523     // calculate peak dB and translate into meter
524     float peak_db = 0;
525     if (new_level > 0) {
526         peak_db = log_meter(power2db(new_level));
527     }
528     return peak_db;
529 }
530 
531 
532 /* ------- setting meter level ----------- */
gx_fast_meter_set_by_power(GxFastMeter * fm,gdouble lvl)533 void gx_fast_meter_set_by_power(GxFastMeter* fm, gdouble lvl)
534 {
535 	_meter_set(fm, meter_level_by_power(fm,lvl));
536 }
537 
538 /* ------- setting compressor meter level ----------- */
gx_fast_meter_set_c_level(GxFastMeter * fm,gdouble lvl)539 void gx_fast_meter_set_c_level(GxFastMeter* fm, gdouble lvl)
540 {
541 	gx_fast_meter_set(fm, lvl*0.25);
542 }
543 /* ------------- clear fast meter object ------------ */
gx_fast_meter_clear(GxFastMeter * fm)544 void gx_fast_meter_clear(GxFastMeter* fm)
545 {
546 	fm->priv->target_level  = 0;
547 	fm->priv->current_level = 0;
548 	fm->priv->current_peak  = 0;
549 	fm->priv->hold_state    = 0;
550 	gtk_widget_queue_draw(GTK_WIDGET(fm));
551 }
552 
gx_fast_meter_state_changed(GtkWidget * widget,GtkStateType oldstate)553 static void gx_fast_meter_state_changed(GtkWidget *widget, GtkStateType oldstate)
554 {
555 	if (!gtk_widget_get_sensitive(widget)) {
556 		GxFastMeter *fm = GX_FAST_METER(widget);
557 		fm->priv->hold_state = 0;
558 		fm->priv->target_level = 0;
559 		if (gtk_widget_is_visible(widget)) {
560 			check_falloff_timer(fm);
561 		} else {
562 			fm->priv->current_level = 0;
563 		}
564 	}
565 }
566 
567 /* ------------------------------ static functions ------------------------- */
568 
gx_fast_meter_get_preferred_width(GtkWidget * wd,gint * min_width,gint * natural_width)569 static void gx_fast_meter_get_preferred_width(GtkWidget* wd, gint *min_width, gint *natural_width)
570 {
571 	gint width, height;
572 	gx_fast_meter_size_request(wd, &width, &height);
573 
574 	if (min_width) {
575 		*min_width = width;
576 	}
577 	if (natural_width) {
578 		*natural_width = width;
579 	}
580 }
581 
gx_fast_meter_get_preferred_height(GtkWidget * wd,gint * min_height,gint * natural_height)582 static void gx_fast_meter_get_preferred_height(GtkWidget* wd, gint *min_height, gint *natural_height)
583 {
584 	gint width, height;
585 	gx_fast_meter_size_request(wd, &width, &height);
586 
587 	if (min_height) {
588 		*min_height = height;
589 	}
590 	if (natural_height) {
591 		*natural_height = height;
592 	}
593 }
594 
gx_fast_meter_size_request(GtkWidget * wd,gint * width,gint * height)595 static void gx_fast_meter_size_request (GtkWidget* wd, gint *width, gint *height)
596 {
597     GxFastMeter * fm = GX_FAST_METER(wd);
598     int lw, lh, lb, dim_, dim;
599     gtk_widget_style_get(wd, "led-width", &lw, "led-height", &lh,
600 						 "led-border", &lb, "dimen", &dim_, NULL);
601     dim = fm->priv->dimen ? fm->priv->dimen : dim_;
602 	// vertical layout
603 	int w = lb + dim * (lw + lb);
604 	int h = lb + min_size * (lh + lb);
605     if (fm->priv->horiz) {
606         *width  = h;
607         *height = w;
608     } else {
609         *width  =  w;
610         *height =  h;
611     }
612 	// min-width / -height
613 	GtkStateFlags state_flags = gtk_widget_get_state_flags(wd);
614     GtkStyleContext *sc = gtk_widget_get_style_context(wd);
615 	int min_width, min_height;
616 	gtk_style_context_get(
617 		sc, state_flags, "min-width", &min_width, "min-height", &min_height, NULL);
618 	*width = max(min_width, *width);
619 	*height = max(min_height, *height);
620 	// add border and margin
621     GtkBorder margin, border;
622     gtk_style_context_get_margin(sc, state_flags, &margin);
623     gtk_style_context_get_border(sc, state_flags, &border);
624 	*width += margin.left + margin.right + border.left + border.right;
625 	*height += margin.top + margin.bottom + border.top + border.bottom;
626 }
627 
628 /* --------- drawing queue ----------- */
629 
calc_top(float level,bool hrz,GdkRectangle & b,int lh,int lb)630 inline int calc_top(float level, bool hrz, GdkRectangle& b, int lh, int lb)
631 {
632     // number of led positions (add missing first led border)
633     int sz = ((hrz ? b.width : b.height) + lb) / (lh+lb);
634 	int pos = (int)round(sz * level);
635     if (pos == 0) {
636         return 0;
637     }
638     return pos * (lh + lb) - lb; // subtract missing first led border
639 }
640 
queue_redraw(GxFastMeter * fm)641 void queue_redraw (GxFastMeter* fm)
642 {
643     if (!fm->priv->surface)
644 		return;
645 	GtkWidget *widget = GTK_WIDGET(fm);
646 	GdkRectangle rect;
647     GdkRectangle& b = fm->priv->bar;
648 	int lw, lh, lb;
649     gtk_widget_style_get(widget, "led-width", &lw, "led-height", &lh, "led-border", &lb, NULL);
650 
651 	cairo_region_t* region = nullptr;
652 
653     bool hrz   = fm->priv->horiz;
654     int tom    = fm->priv->top_of_meter;
655 
656 	int new_top = calc_top(fm->priv->current_level, hrz, b, lh, lb);
657 
658 	if (new_top != tom) {
659 		GtkStateFlags state_flags = gtk_widget_get_state_flags(widget);
660 		GtkStyleContext *sc = gtk_widget_get_style_context(widget);
661 		GtkBorder margin;
662 		gtk_style_context_get_margin(sc, state_flags, &margin);
663 		rect = b;
664 		rect.x += margin.left;
665 		rect.y += margin.top;
666 		int df = new_top - tom;
667 		if (df < 0) {
668 			tom += df;
669 			df = -df;
670 		}
671 		// tom is now start of update region, df is size
672 		if (hrz) {
673 			rect.x += tom;
674 			rect.width = df;
675 		} else {
676 			rect.y += rect.height - tom - df;
677 			rect.height = df;
678 		}
679 
680 		if (rect.height + rect.width != 0) {
681 			/* ok, first region to draw ... */
682 			region = cairo_region_create_rectangle (&rect);
683 		}
684 	}
685 
686 	/* redraw the last place where the last peak hold bar was;
687 	   the next expose will draw the new one whether its part of
688 	   expose region or not. */
689 
690 	if (fm->priv->last_peak_rect.width + fm->priv->last_peak_rect.height != 0) {
691 		if (!region) {
692 			region = cairo_region_create();
693 		}
694 		rect = fm->priv->last_peak_rect;
695 		cairo_region_union_rectangle (region, &rect);
696 	}
697 
698 	if (region) {
699 		gtk_widget_queue_draw_region (widget, region);
700 		cairo_region_destroy(region);
701 	}
702 }
703 
704 
705 /* ------- expose event -------- */
gx_fast_meter_draw(GtkWidget * wd,cairo_t * cr)706 static gboolean gx_fast_meter_draw (GtkWidget* wd, cairo_t *cr)
707 {
708 	GxFastMeter* fm = GX_FAST_METER(wd);
709     GdkRectangle b = fm->priv->bar;
710 	gint         top_of_meter;
711     int lw, lh, lb;
712     gtk_widget_style_get(wd, "led-width", &lw, "led-height", &lh, "led-border", &lb, NULL);
713 
714 	if (!fm->priv->surface) {
715 		return FALSE;
716 	}
717     bool hrz   = fm->priv->horiz;
718     int height = cairo_image_surface_get_height(fm->priv->surface);
719     int width  = cairo_image_surface_get_width(fm->priv->surface);
720 	GtkStateFlags state_flags = gtk_widget_get_state_flags(wd);
721     GtkStyleContext *sc = gtk_widget_get_style_context(wd);
722     GtkBorder margin;
723     gtk_style_context_get_margin(sc, state_flags, &margin);
724     int x = margin.left;
725     int y = margin.top;
726 
727 	top_of_meter = calc_top(fm->priv->current_level, hrz, b, lh, lb);
728     fm->priv->top_of_meter = top_of_meter;
729 
730     cairo_set_source_surface(cr, fm->priv->surface, x, y);
731 	cairo_rectangle(cr, x, y, width, height);
732 	cairo_fill(cr);
733 
734 	x += b.x;
735 	y += b.y;
736     cairo_set_source_surface(cr, fm->priv->overlay, x, y);
737 	if (hrz) {
738 		cairo_rectangle(cr, x, y, top_of_meter, b.height);
739 	} else {
740 		cairo_rectangle(cr, x, y + b.height - top_of_meter, b.width, top_of_meter);
741 	}
742 	cairo_fill(cr);
743 
744 	// draw peak bar
745 	if (fm->priv->hold_state) {
746         GdkRectangle *r = &fm->priv->last_peak_rect;
747 		int t = calc_top(fm->priv->current_peak, hrz, b, lh, lb);
748 		if (t > 0) {
749 			if (hrz) {
750 				r->width = lb + lh;
751 				r->x = x + t - r->width;
752 				r->y = y;
753 				r->height = b.height;
754 			} else {
755 				r->x = x;
756 				r->y = y + b.height - t;
757 				r->width = b.width;
758 				r->height = lb + lh;
759 			}
760 			cairo_rectangle(cr, r->x, r->y, r->width, r->height);
761 			cairo_fill(cr);
762 		}
763 		return FALSE;
764 	}
765 	fm->priv->last_peak_rect.width  = 0;
766 	fm->priv->last_peak_rect.height = 0;
767 	return FALSE;
768 }
769 
770 
771 
772 #define grad_size 4
773 
774 GdkRGBA default_gradient_color[grad_size] = {
775 	// red green  blue alpha
776 	{ 0.0, 1.0,   0.0, 1.0 }, // clr-bottom
777 	{ 1.0, 1.0,   0.0, 1.0 }, // clr-middle
778 	{ 1.0, 0.664, 0.0, 1.0 }, // clr-top
779 	{ 1.0, 0.0,   0.0, 1.0 }  // over
780 };
781 
782 #define CVALUE(i,c,y,mx) (guint8)floor(((int)rgb[i]->c + (((int)rgb[i+1]->c - (int)rgb[i]->c) * (y)) / (float)(mx))/256)
783 
784 /* ----- create pixbuf for meter (content + border, without margin) ------ */
request_meter(GtkWidget * widget)785 static void request_meter(GtkWidget *widget)
786 {
787 	GxFastMeter* fm = GX_FAST_METER(widget);
788 	if (fm->priv->surface) {
789 		cairo_surface_destroy(fm->priv->surface);
790         cairo_surface_destroy(fm->priv->overlay);
791 	}
792     int lw, lh, lb, dim_, dim, rad;
793     float bevel;
794     gtk_widget_style_get(widget, "led-width", &lw, "led-height", &lh, "led-border",
795 						 &lb, "dimen", &dim_, "border-radius", &rad, "bevel", &bevel, NULL);
796     dim = fm->priv->dimen ? fm->priv->dimen : dim_;
797     bool hrz = fm->priv->horiz;
798 
799 	GtkStateFlags state_flags = gtk_widget_get_state_flags(widget);
800     GtkStyleContext *sc = gtk_widget_get_style_context(widget);
801     GtkBorder margin, border;
802     gtk_style_context_get_margin(sc, state_flags, &margin);
803     gtk_style_context_get_border(sc, state_flags, &border);
804 	int xmargin = margin.left + margin.right;
805 	int ymargin = margin.top + margin.bottom;
806     int xborder = border.left + border.right;
807 	int yborder = border.top + border.bottom;
808 
809     GtkAllocation allocation;
810     gtk_widget_get_allocation(widget, &allocation);
811 	// vertical layout
812 	int b = lb + dim * (lw + lb);
813 	int width, height;
814 	// width, height: content + border (without margin)
815     if (hrz) {
816 		width = allocation.width - xmargin;
817 		height = b + yborder;
818 	} else {
819 		width = b + xborder;
820 		height = allocation.height - ymargin;
821 	}
822     if (width <= xborder || height <= yborder) {
823         return;
824     }
825 
826     cairo_t *cr;
827     fm->priv->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
828 	if (cairo_surface_status(fm->priv->surface) != CAIRO_STATUS_SUCCESS) {
829 		fm->priv->surface = nullptr;
830 		return;
831 	}
832     cr = cairo_create(fm->priv->surface);
833     //gtk_render_background(sc, cr, 0, 0, width, height);
834     gtk_render_frame(sc, cr, 0, 0, width, height);
835     if (bevel) {
836         gx_bevel(cr, 0, 0, width, height, rad, bevel);
837 	}
838     gtk_render_background(sc, cr, border.left, -rad, width, height+yborder+2*rad);
839 
840 	int x = border.left;
841 	int y = border.top;
842 	width -= xborder;
843 	height -= yborder;
844 	// width, height: just content
845 
846 	// don't display clipped led's
847     if (hrz) {
848         width -= (width - lb) % (lh + lb);
849     } else {
850         height -= (height - lb) % (lh + lb);
851     }
852 	// width, height: led bar
853 
854     // gradient for led bar
855     GdkRGBA *rgb[4];
856     unsigned int i;
857     float midpos;
858     gtk_widget_style_get(widget, "clr-bottom", &rgb[0], "clr-middle", &rgb[1], "clr-top", &rgb[2],
859 						 "over", &rgb[3], "mid-pos", &midpos, NULL);
860 	for (i = 0; i < sizeof(rgb)/sizeof(rgb[0]); i++) {
861 		if (!rgb[i]) {
862 			rgb[i] = gdk_rgba_copy(&default_gradient_color[i]);
863 		}
864 	}
865 
866 	// draw surface with led's switched on
867     float bars = hrz ? width - 2*lb : height - 2*lb; // size of only the leds
868     float lpos = (bars - lh) / bars;
869     cairo_pattern_t *pat = cairo_pattern_create_linear(
870         x + lb,
871         y + height - lb,
872         hrz ? x + width - lb : x + lb,
873         hrz ? y + height - lb : y + lb);
874     cairo_pattern_add_color_stop_rgb(pat, 0, rgb[0]->red, rgb[0]->green, rgb[0]->blue);
875     cairo_pattern_add_color_stop_rgb(pat, midpos, rgb[1]->red, rgb[1]->green, rgb[1]->blue);
876     cairo_pattern_add_color_stop_rgb(pat, lpos, rgb[2]->red, rgb[2]->green, rgb[2]->blue);
877     cairo_pattern_add_color_stop_rgb(pat, lpos + 0.0001, rgb[3]->red, rgb[3]->green, rgb[3]->blue);
878 
879     cairo_rectangle(cr, x + lb, y + lb, width - 2*lb, height - 2*lb);
880     cairo_set_source(cr, pat);
881     cairo_fill(cr);
882 
883 	// bar dimension (without led borders)
884 	GdkRectangle& bar = fm->priv->bar;
885     bar.x      = x + lb;
886     bar.y      = y + lb;
887     bar.width  = width - 2*lb;
888     bar.height = height - 2*lb;
889 
890     // led borders
891     GtkStyleContext *entry_context = gx_get_entry_style_context(); //FIXME
892     int max = hrz ? width : height;
893     for (int j = 0; j < max; j += lh + lb) {
894         gtk_render_background(entry_context, cr,
895             x + (hrz ? j : 0),
896             y + (hrz ? 0 : j),
897             hrz ? lb : width,
898             hrz ? height : lb);
899     }
900     for (int j = 1; j < dim; j++) {
901         gtk_render_background(entry_context, cr,
902             x + (hrz ? 0 : j * (lb + lw)),
903             y + (hrz ? j * (lb + lw) : 0),
904             hrz ? width : lb,
905             hrz ? lb : height);
906     }
907 
908     // inset
909     if (hrz) {
910         gx_draw_inset(cr, x, y, width, height, rad, 1);
911     } else {
912         gx_draw_inset(cr, x + 1, y + 1, width, height, rad, 0.5);
913 	}
914     // copy bar to overlay
915     fm->priv->overlay = cairo_image_surface_create(
916 		CAIRO_FORMAT_ARGB32, bar.width, bar.height);
917 	if (cairo_surface_status(fm->priv->overlay) != CAIRO_STATUS_SUCCESS) {
918 		cairo_surface_destroy(fm->priv->surface);
919 		fm->priv->surface = nullptr;
920 		fm->priv->overlay = nullptr;
921 		return;
922 	}
923     cairo_t *co;
924     co = cairo_create(fm->priv->overlay);
925     cairo_set_source_surface(co, fm->priv->surface, -bar.x, -bar.y);
926     cairo_paint(co);
927 
928 	// dim the bar on the surface (not on the overlay)
929     cairo_rectangle(cr, x, y, width, height);
930     cairo_set_source_rgba(cr, 0., 0., 0., 0.8);
931     cairo_fill(cr);
932 
933 	for (i = 0; i < sizeof(rgb)/sizeof(rgb[0]); i++) {
934         gdk_rgba_free(rgb[i]);
935 	}
936     g_clear_object(&entry_context);
937     cairo_destroy(cr);
938     cairo_destroy(co);
939     cairo_pattern_destroy(pat);
940 }
941 
942 /* -------------- */
943 /* EOF */
944 
945 // Local Variables:
946 // tab-width: 4
947 // End:
948