2 * Custom controls (line graph, knob).
3 * Copyright (C) 2007-2010 Krzysztof Foltman, Torben Hohn, Markus Schmidt
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
23 #include <calf/primitives.h>
24 #include <calf/ctl_vumeter.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <cairo/cairo.h>
31 #include <calf/drawingutils.h>
34 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
37 calf_vumeter_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
39 g_assert(CALF_IS_VUMETER(widget
));
41 CalfVUMeter
*vu
= CALF_VUMETER(widget
);
43 cairo_t
*c
= gdk_cairo_create(GDK_DRAWABLE(widget
->window
));
47 int x
= widget
->allocation
.x
;
48 int y
= widget
->allocation
.y
;
49 int width
= widget
->allocation
.width
;
50 int height
= widget
->allocation
.height
;
51 int border_x
= widget
->style
->xthickness
;
52 int border_y
= widget
->style
->ythickness
;
53 int space_x
= 1; int space_y
= 1; // inner border around led bar
54 int led
= 2; // single LED size
55 int led_m
= 1; // margin between LED
56 int led_s
= led
+ led_m
; // size of LED with margin
57 int led_x
= widget
->style
->xthickness
;
58 int led_y
= widget
->style
->ythickness
; // position of first LED
59 int led_w
= width
- 2 * led_x
+ led_m
; // width of LED bar w/o text calc (additional led margin, is removed later; used for filling the led bar completely w/o margin gap)
60 int led_h
= height
- 2 * led_y
; // height of LED bar w/o text calc
61 int text_x
= 0; int text_y
= 0;
62 int text_w
= 0; int text_h
= 0;
64 // only valid if vumeter is enabled
65 cairo_text_extents_t extents
;
67 if(vu
->vumeter_position
) {
68 cairo_select_font_face(c
, "cairo:sans-serif", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
69 cairo_set_font_size(c
, 8);
71 cairo_text_extents(c
, "-88.88", &extents
);
72 text_w
= extents
.width
;
73 text_h
= extents
.height
;
74 switch(vu
->vumeter_position
) {
76 text_x
= width
/ 2 - text_w
/ 2;
77 text_y
= border_y
+ led_y
- extents
.y_bearing
;
78 led_y
+= text_h
+ led_y
;
79 led_h
-= text_h
+ led_y
;
82 text_x
= width
- border_x
- led_x
- text_w
;
83 text_y
= height
/ 2 - text_h
/ 2 - extents
.y_bearing
;
84 led_w
-= led_x
+ text_w
;
87 text_x
= width
/ 2 - text_w
/ 2;
88 text_y
= height
- border_y
- led_y
- text_h
- extents
.y_bearing
;
89 led_h
-= led_y
+ text_h
;
92 text_x
= border_x
+ led_x
;
93 text_y
= height
/ 2 - text_h
/ 2 - extents
.y_bearing
;
94 led_x
+= led_x
+ text_w
;
95 led_w
-= led_x
+ text_w
;
100 led_w
-= led_w
% led_s
+ led_m
; //round LED width to LED size and remove margin gap, width is filled with LED without margin gap now
102 if( vu
->cache_surface
== NULL
) {
103 // looks like its either first call or the widget has been resized.
104 // create the cache_surface.
105 vu
->cache_surface
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, width
, height
);
106 cairo_t
*cache_cr
= cairo_create( vu
->cache_surface
);
109 get_bg_color(widget
, NULL
, &r
, &g
, &b
);
110 gtk_widget_style_get(widget
, "border-radius", &radius
, "bevel", &bevel
, NULL
);
111 create_rectangle(cache_cr
, 0, 0, width
, height
, radius
);
112 cairo_set_source_rgb(cache_cr
, r
, g
, b
);
113 cairo_fill(cache_cr
);
114 draw_bevel(cache_cr
, 0, 0, width
, height
, radius
, bevel
);
117 cairo_rectangle(cache_cr
, led_x
, led_y
, led_w
, led_h
);
118 cairo_set_source_rgb (cache_cr
, 0, 0, 0);
119 cairo_fill(cache_cr
);
123 led_w
-= 2 * space_x
;
124 led_h
-= 2 * space_y
;
127 cairo_set_line_width(cache_cr
, 1);
128 for (int x
= led_x
; x
+ led
<= led_x
+ led_w
; x
+= led_s
)
130 float ts
= (x
- led_x
) * 1.0 / led_w
;
131 float r
= 0.f
, g
= 0.f
, b
= 0.f
;
137 r
= ts
/ 0.75, g
= 0.5 + ts
* 0.66, b
= 1 - ts
/ 0.75;
139 r
= 1, g
= 1 - (ts
- 0.75) / 0.25, b
= 0;
140 // if (vu->value < ts || vu->value <= 0)
141 // r *= 0.5, g *= 0.5, b *= 0.5;
143 case VU_STANDARD_CENTER
:
147 r
= 1, g
= (ts
) / 0.25, b
= 0;
151 r
= 1, g
= 1 - (ts
- 0.75) / 0.25, b
= 0;
157 r
= (ts
- 0.5) / 0.25, g
= 0.5 + (ts
- 0.5) * 2.f
, b
= 1 - (ts
- 0.5) / 0.25;
163 r
= 1 - (ts
- 0.25) / 0.25, g
= 1.f
- (ts
* 2.f
- .5f
), b
= (ts
- 0.25) / 0.25;
164 // if (vu->value < ts || vu->value <= 0)
165 // r *= 0.5, g *= 0.5, b *= 0.5;
167 case VU_MONOCHROME_REVERSE
:
168 r
= 0, g
= 170.0 / 255.0, b
= 1;
169 // if (!(vu->value < ts) || vu->value >= 1.0)
170 // r *= 0.5, g *= 0.5, b *= 0.5;
173 r
= 0, g
= 170.0 / 255.0, b
= 1;
174 // if (vu->value < ts || vu->value <= 0)
175 // r *= 0.5, g *= 0.5, b *= 0.5;
177 case VU_MONOCHROME_CENTER
:
178 r
= 0, g
= 170.0 / 255.0, b
= 1;
179 // if (vu->value < ts || vu->value <= 0)
180 // r *= 0.5, g *= 0.5, b *= 0.5;
183 GdkColor sc2
= { 0, (guint16
)(65535 * r
+ 0.2), (guint16
)(65535 * g
), (guint16
)(65535 * b
) };
184 GdkColor sc3
= { 0, (guint16
)(65535 * r
* 0.7), (guint16
)(65535 * g
* 0.7), (guint16
)(65535 * b
* 0.7) };
185 gdk_cairo_set_source_color(cache_cr
, &sc2
);
186 cairo_move_to(cache_cr
, x
+ 0.5, led_y
);
187 cairo_line_to(cache_cr
, x
+ 0.5, led_y
+ led_h
);
188 cairo_stroke(cache_cr
);
189 gdk_cairo_set_source_color(cache_cr
, &sc3
);
190 cairo_move_to(cache_cr
, x
+ 1.5, led_y
+ led_h
);
191 cairo_line_to(cache_cr
, x
+ 1.5, led_y
);
192 cairo_stroke(cache_cr
);
194 // create blinder pattern
195 cairo_pattern_t
*pat
= cairo_pattern_create_linear (led_x
, led_y
, led_x
, led_y
+ led_h
);
196 cairo_pattern_add_color_stop_rgba (pat
, 0, 1, 1, 1, 0.25);
197 cairo_pattern_add_color_stop_rgba (pat
, 0.5, 0.5, 0.5, 0.5, 0.0);
198 cairo_pattern_add_color_stop_rgba (pat
, 1, 0.0, 0.0, 0.0, 0.25);
199 cairo_rectangle(cache_cr
, led_x
, led_y
, led_w
, led_h
);
200 cairo_set_source(cache_cr
, pat
);
201 cairo_fill(cache_cr
);
204 vu
->cache_overlay
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, width
, height
);
205 cairo_t
*over_cr
= cairo_create(vu
->cache_overlay
);
207 // copy surface to overlay
208 cairo_set_source_surface(over_cr
, vu
->cache_surface
, 0, 0);
209 cairo_rectangle(over_cr
, 0, 0, width
, height
);
212 // create blinder pattern
213 pat
= cairo_pattern_create_linear (led_x
, led_y
, led_x
, led_y
+ led_h
);
214 cairo_pattern_add_color_stop_rgba (pat
, 0, 0.2, 0.2, 0.2, 0.7);
215 cairo_pattern_add_color_stop_rgba (pat
, 0.4, 0.05, 0.05, 0.05, 0.7);
216 cairo_pattern_add_color_stop_rgba (pat
, 0.401, 0.05, 0.05, 0.05, 0.9);
217 cairo_pattern_add_color_stop_rgba (pat
, 1, 0.05, 0.05, 0.05, 0.75);
219 // draw on top of overlay
220 cairo_set_source(over_cr
, pat
);
221 cairo_rectangle(over_cr
, 0, 0, width
, height
);
222 cairo_paint(over_cr
);
225 cairo_destroy(cache_cr
);
226 cairo_destroy(over_cr
);
230 led_w
-= 2 * space_x
;
231 led_h
-= 2 * space_y
;
238 cairo_set_source_surface( c
, vu
->cache_surface
, x
, y
);
240 cairo_set_source_surface( c
, vu
->cache_overlay
, x
, y
);
244 gettimeofday(&tv
, 0);
245 long time
= tv
.tv_sec
* 1000 * 1000 + tv
.tv_usec
;
248 float value_orig
= std::max(std::min(vu
->value
, 1.f
), 0.f
);
252 if(vu
->vumeter_falloff
> 0.f
and vu
->mode
!= VU_MONOCHROME_REVERSE
) {
254 float s
= ((float)(time
- vu
->last_falltime
) / 1000000.0);
255 float m
= vu
->last_falloff
* s
* vu
->vumeter_falloff
;
256 vu
->last_falloff
-= m
;
258 if(value_orig
> vu
->last_falloff
) {
259 vu
->last_falloff
= value_orig
;
261 value
= vu
->last_falloff
;
262 vu
->last_falltime
= time
;
263 vu
->falling
= vu
->last_falloff
> 0.00000001;
266 vu
->last_falloff
= 0.f
;
267 vu
->last_falltime
= 0.f
;
273 float draw_last
= 0.f
;
275 if(vu
->vumeter_hold
> 0.0) {
277 if(time
- (long)(vu
->vumeter_hold
* 1000 * 1000) > vu
->last_hold
) {
279 vu
->last_value
= value
;
280 vu
->last_hold
= time
;
282 vu
->disp_value
= value_orig
;
284 if( vu
->mode
== VU_MONOCHROME_REVERSE
) {
285 if(value
< vu
->last_value
) {
286 // value is above peak hold
287 vu
->last_value
= value
;
288 vu
->last_hold
= time
;
291 draw
= log10(1 + value
* 9);
292 draw_last
= log10(1 + vu
->last_value
* 9);
294 // blinder left -> hold LED
295 int hold_x
= round((draw_last
) * (led_w
+ led_m
)); // add last led_m removed earlier
296 hold_x
-= hold_x
% led_s
+ led_m
;
297 hold_x
= std::max(0, hold_x
);
298 cairo_rectangle( c
, led_x
, led_y
, hold_x
, led_h
);
300 // blinder hold LED -> value
301 int val_x
= round((1 - draw
) * (led_w
+ led_m
)); // add last led_m removed earlier
302 val_x
-= val_x
% led_s
;
303 int blind_x
= std::min(hold_x
+ led_s
, led_w
);
304 int blind_w
= std::min(std::max(led_w
- val_x
- hold_x
- led_s
, 0), led_w
);
305 cairo_rectangle(c
, led_x
+ blind_x
, led_y
, blind_w
, led_h
);
306 } else if( vu
->mode
== VU_STANDARD_CENTER
) {
307 if(value
> vu
->last_value
) {
308 // value is above peak hold
309 vu
->last_value
= value
;
310 vu
->last_hold
= time
;
313 draw
= log10(1 + value
* 9);
314 int val_x
= round((1 - draw
) / 2.f
* (led_w
+ led_m
)); // add last led_m removed earlier
315 cairo_rectangle(c
, led_x
, led_y
, val_x
, led_h
);
316 cairo_rectangle(c
, led_x
+ led_w
- val_x
, led_y
, val_x
, led_h
);
319 if(value
> vu
->last_value
) {
320 // value is above peak hold
321 vu
->last_value
= value
;
322 vu
->last_hold
= time
;
325 draw
= log10(1 + value
* 9);
326 draw_last
= log10(1 + vu
->last_value
* 9);
328 int hold_x
= round((1 - draw_last
) * (led_w
+ led_m
)); // add last led_m removed earlier
329 hold_x
-= hold_x
% led_s
;
330 int val_x
= round(draw
* (led_w
+ led_m
)); // add last led_m removed earlier
331 val_x
-= val_x
% led_s
;
332 int blind_w
= led_w
- hold_x
- led_s
- val_x
;
333 blind_w
= std::min(std::max(blind_w
, 0), led_w
);
334 cairo_rectangle(c
, led_x
+ val_x
, led_y
, blind_w
, led_h
);
335 cairo_rectangle( c
, led_x
+ led_w
- hold_x
, led_y
, hold_x
, led_h
);
339 float draw
= log10(1 + value
* 9);
340 if( vu
->mode
== VU_MONOCHROME_REVERSE
)
341 cairo_rectangle( c
, led_x
, led_y
, draw
* led_w
, led_h
);
342 else if( vu
->mode
== VU_STANDARD_CENTER
) {
343 int val_x
= round((1 - draw
) / 2.f
* (led_w
+ led_m
)); // add last led_m removed earlier
344 cairo_rectangle(c
, led_x
, led_y
, val_x
, led_h
);
345 cairo_rectangle(c
, led_x
+ led_w
- val_x
, led_y
, val_x
, led_h
);
347 cairo_rectangle( c
, led_x
+ draw
* led_w
, led_y
, led_w
* (1 - draw
), led_h
);
351 if (vu
->vumeter_position
)
354 if((vu
->value
> vu
->disp_value
and vu
->mode
!= VU_MONOCHROME_REVERSE
)
355 or (vu
->value
< vu
->disp_value
and vu
->mode
== VU_MONOCHROME_REVERSE
))
356 vu
->disp_value
= vu
->value
;
357 if (vu
->disp_value
< 1.0 / 32768.0)
358 snprintf(str
, sizeof(str
), "-inf");
360 snprintf(str
, sizeof(str
), "%0.2f", dsp::amp2dB(vu
->disp_value
));
361 // draw value as number
362 cairo_text_extents(c
, str
, &extents
);
363 cairo_move_to(c
, text_x
+ (text_w
- extents
.width
) / 2.0, text_y
);
365 if(vu
->disp_value
> 1.f
and vu
->mode
!= VU_MONOCHROME_REVERSE
)
366 state
= GTK_STATE_ACTIVE
;
368 state
= GTK_STATE_NORMAL
;
369 get_fg_color(widget
, &state
, &r
, &g
, &b
);
370 cairo_set_source_rgba (c
, r
, g
, b
, 1);
371 cairo_show_text(c
, str
);
375 //gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 2, oy - 2, sx + 4, sy + 4);
376 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
382 calf_vumeter_size_request (GtkWidget
*widget
,
383 GtkRequisition
*requisition
)
385 g_assert(CALF_IS_VUMETER(widget
));
386 CalfVUMeter
*self
= CALF_VUMETER(widget
);
387 requisition
->width
= self
->vumeter_width
;
388 requisition
->height
= self
->vumeter_height
;
392 calf_vumeter_unrealize (GtkWidget
*widget
, CalfVUMeter
*vu
)
394 if( vu
->cache_surface
)
395 cairo_surface_destroy( vu
->cache_surface
);
396 vu
->cache_surface
= NULL
;
397 if( vu
->cache_overlay
)
398 cairo_surface_destroy( vu
->cache_overlay
);
399 vu
->cache_overlay
= NULL
;
403 calf_vumeter_size_allocate (GtkWidget
*widget
,
404 GtkAllocation
*allocation
)
406 g_assert(CALF_IS_VUMETER(widget
));
407 CalfVUMeter
*vu
= CALF_VUMETER(widget
);
409 GtkWidgetClass
*parent_class
= (GtkWidgetClass
*) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu
) );
411 parent_class
->size_allocate( widget
, allocation
);
413 calf_vumeter_unrealize(widget
, vu
);
417 calf_vumeter_class_init (CalfVUMeterClass
*klass
)
419 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
420 widget_class
->expose_event
= calf_vumeter_expose
;
421 widget_class
->size_request
= calf_vumeter_size_request
;
422 widget_class
->size_allocate
= calf_vumeter_size_allocate
;
423 gtk_widget_class_install_style_property(
424 widget_class
, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
425 0, 24, 4, GParamFlags(G_PARAM_READWRITE
)));
426 gtk_widget_class_install_style_property(
427 widget_class
, g_param_spec_float("bevel", "Bevel", "Bevel the object",
428 -2, 2, 0.2, GParamFlags(G_PARAM_READWRITE
)));
432 calf_vumeter_init (CalfVUMeter
*self
)
434 GtkWidget
*widget
= GTK_WIDGET(self
);
435 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
436 widget
->requisition
.width
= self
->vumeter_width
;
437 widget
->requisition
.height
= self
->vumeter_height
;
438 self
->cache_surface
= NULL
;
439 self
->falling
= false;
440 self
->holding
= false;
441 self
->meter_width
= 0;
442 self
->disp_value
= 0.f
;
444 gtk_widget_set_has_window(widget
, FALSE
);
445 g_signal_connect(GTK_OBJECT(widget
), "unrealize", G_CALLBACK(calf_vumeter_unrealize
), (gpointer
)self
);
451 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER
, NULL
));
455 calf_vumeter_get_type (void)
457 static GType type
= 0;
459 static const GTypeInfo type_info
= {
460 sizeof(CalfVUMeterClass
),
461 NULL
, /* base_init */
462 NULL
, /* base_finalize */
463 (GClassInitFunc
)calf_vumeter_class_init
,
464 NULL
, /* class_finalize */
465 NULL
, /* class_data */
468 (GInstanceInitFunc
)calf_vumeter_init
471 GTypeInfo
*type_info_copy
= new GTypeInfo(type_info
);
473 for (int i
= 0; ; i
++) {
474 const char *name
= "CalfVUMeter";
475 //char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
476 if (g_type_from_name(name
)) {
480 type
= g_type_register_static( GTK_TYPE_DRAWING_AREA
,
491 extern void calf_vumeter_set_value(CalfVUMeter
*meter
, float value
)
493 if (value
!= meter
->value
or meter
->holding
or meter
->falling
)
495 meter
->value
= value
;
496 gtk_widget_queue_draw(GTK_WIDGET(meter
));
500 extern float calf_vumeter_get_value(CalfVUMeter
*meter
)
505 extern void calf_vumeter_set_mode(CalfVUMeter
*meter
, CalfVUMeterMode mode
)
507 if (mode
!= meter
->mode
)
510 if(mode
== VU_MONOCHROME_REVERSE
) {
512 meter
->last_value
= 1.f
;
515 meter
->last_value
= 0.f
;
517 meter
->vumeter_falloff
= 0.f
;
518 meter
->last_falloff
= (long)0;
519 meter
->last_hold
= (long)0;
520 gtk_widget_queue_draw(GTK_WIDGET(meter
));
524 extern CalfVUMeterMode
calf_vumeter_get_mode(CalfVUMeter
*meter
)
529 extern void calf_vumeter_set_falloff(CalfVUMeter
*meter
, float value
)
531 if (value
!= meter
->vumeter_falloff
)
533 meter
->vumeter_falloff
= value
;
534 gtk_widget_queue_draw(GTK_WIDGET(meter
));
538 extern float calf_vumeter_get_falloff(CalfVUMeter
*meter
)
540 return meter
->vumeter_falloff
;
543 extern void calf_vumeter_set_hold(CalfVUMeter
*meter
, float value
)
545 if (value
!= meter
->vumeter_hold
)
547 meter
->vumeter_hold
= value
;
548 gtk_widget_queue_draw(GTK_WIDGET(meter
));
552 extern float calf_vumeter_get_hold(CalfVUMeter
*meter
)
554 return meter
->vumeter_hold
;
557 extern void calf_vumeter_set_width(CalfVUMeter
*meter
, int value
)
559 if (value
!= meter
->vumeter_width
)
561 meter
->vumeter_width
= value
;
562 gtk_widget_queue_draw(GTK_WIDGET(meter
));
566 extern int calf_vumeter_get_width(CalfVUMeter
*meter
)
568 return meter
->vumeter_width
;
571 extern void calf_vumeter_set_height(CalfVUMeter
*meter
, int value
)
573 if (value
!= meter
->vumeter_height
)
575 meter
->vumeter_height
= value
;
576 gtk_widget_queue_draw(GTK_WIDGET(meter
));
580 extern int calf_vumeter_get_height(CalfVUMeter
*meter
)
582 return meter
->vumeter_height
;
584 extern void calf_vumeter_set_position(CalfVUMeter
*meter
, int value
)
586 if (value
!= meter
->vumeter_height
)
588 meter
->vumeter_position
= value
;
589 gtk_widget_queue_draw(GTK_WIDGET(meter
));
593 extern int calf_vumeter_get_position(CalfVUMeter
*meter
)
595 return meter
->vumeter_position
;