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