1 /***************************************************************************
7 * Fri Jan 10 20:06:23 2003
8 * Copyright 2003 Todd Goyen
9 * wettoad@knighthoodofbuh.org
11 * Mon May 01 04:04:00 2006
12 * Copyright 2006 Ulf Lamping
15 * Source code is LGPL'd,
16 * but may be distributed under any other open source license
17 ****************************************************************************/
19 #if defined(GDK_DISABLE_DEPRECATED)
20 # undef GDK_DISABLE_DEPRECATED
25 #include "gtk/gtkvumeter.h"
27 #include "gtk/old-gtk-compat.h"
29 #define MIN_DYNAMIC_SIDE 40
31 #define SMALL_PITCH_LINE 2
32 #define LARGE_PITCH_LINE 4
37 #define SPARE_BOTTOM 1
40 static void gtk_vumeter_init (GtkVUMeter *vumeter);
41 static void gtk_vumeter_class_init (GtkVUMeterClass *class);
42 static void gtk_vumeter_destroy (GtkObject *object);
43 static void gtk_vumeter_realize (GtkWidget *widget);
44 static void gtk_vumeter_size_calculate (GtkWidget *widget, GtkRequisition *requisition);
45 static void gtk_vumeter_size_request (GtkWidget *widget, GtkRequisition *requisition);
46 static void gtk_vumeter_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
47 static gboolean gtk_vumeter_expose (GtkWidget *widget, GdkEventExpose *event);
48 static void gtk_vumeter_free_colors (GtkVUMeter *vumeter);
49 static void gtk_vumeter_setup_colors (GtkVUMeter *vumeter);
50 static gint gtk_vumeter_sound_level_to_draw_level (GtkVUMeter *vumeter, gint sound_level);
51 static gboolean gtk_vumeter_redraw_timeout (gpointer data);
52 static void gtk_vumeter_setup_scale_items(GtkVUMeter *vumeter, GList *scale_items);
54 static GtkWidgetClass *parent_class = NULL;
56 GType gtk_vumeter_get_type (void)
58 static GType vumeter_type = 0;
61 static const GTypeInfo vumeter_info = {
62 sizeof (GtkVUMeterClass),
64 (GClassInitFunc) gtk_vumeter_class_init, NULL, NULL,
65 sizeof (GtkVUMeter), 0, (GInstanceInitFunc) gtk_vumeter_init,
68 vumeter_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkVUMeter", &vumeter_info, 0);
77 * Creates a new VUMeter widget.
79 GtkWidget* gtk_vumeter_new (void)
83 vumeter = GTK_VUMETER (g_object_new (GTK_TYPE_VUMETER, NULL));
85 return GTK_WIDGET (vumeter);
88 static void gtk_vumeter_init (GtkVUMeter *vumeter)
90 vumeter->vertical = TRUE;
91 vumeter->scale_inverted = FALSE;
92 vumeter->thickness = 30;
93 vumeter->reduced_thickness = 0;
94 vumeter->scaling = GTK_VUMETER_SCALING_LINEAR;
95 vumeter->scale_items = NULL;
96 vumeter->scale_pitch_holes = 0;
98 vumeter->padding_left = 1;
99 vumeter->padding_right = 1;
100 vumeter->padding_top = 1;
101 vumeter->padding_bottom = 1;
103 vumeter->colormap = NULL;
105 vumeter->f_gc = NULL;
106 vumeter->b_gc = NULL;
107 vumeter->f_colors = NULL;
108 vumeter->b_colors = NULL;
109 vumeter->f_brightness = 65535;
110 vumeter->b_brightness = 49151;
111 vumeter->yellow_level = 16383;
112 vumeter->colors_inverted = FALSE;
115 vumeter->level_min = 0;
116 vumeter->level_max = 32767;
118 vumeter->peak = FALSE;
119 vumeter->peak_level = 0;
120 vumeter->peak_timeout = 0;
121 vumeter->peak_hold_factor = 0;
122 vumeter->peak_hold = 0;
123 vumeter->peak_falloff_mode = GTK_VUMETER_PEAK_FALLOFF_MEDIUM;
124 vumeter->peak_falloff_rate = 3278;
127 static void gtk_vumeter_class_init (GtkVUMeterClass *class)
129 GtkObjectClass *object_class;
130 GtkWidgetClass *widget_class;
132 object_class = (GtkObjectClass*) class;
133 widget_class = (GtkWidgetClass*) class;
134 parent_class = g_type_class_ref (gtk_widget_get_type ());
136 object_class->destroy = gtk_vumeter_destroy;
138 widget_class->realize = gtk_vumeter_realize;
139 widget_class->expose_event = gtk_vumeter_expose;
140 widget_class->size_request = gtk_vumeter_size_request;
141 widget_class->size_allocate = gtk_vumeter_size_allocate;
144 static void gtk_vumeter_destroy (GtkObject *object)
146 GtkVUMeter *vumeter = GTK_VUMETER (object);
148 if(vumeter->peak_timeout) {
149 g_source_remove(vumeter->peak_timeout);
152 gtk_vumeter_free_colors (vumeter);
154 GTK_OBJECT_CLASS (parent_class)->destroy (object);
157 static void gtk_vumeter_realize (GtkWidget *widget)
160 GdkWindowAttr attributes;
161 gint attributes_mask;
162 GtkAllocation widget_alloc;
164 g_return_if_fail (widget != NULL);
165 g_return_if_fail (GTK_IS_VUMETER (widget));
167 #if GTK_CHECK_VERSION(2,20,0)
168 gtk_widget_set_realized(widget, TRUE);
170 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
172 vumeter = GTK_VUMETER (widget);
174 #if GTK_CHECK_VERSION(2,18,0)
175 gtk_widget_get_allocation(widget, &widget_alloc);
177 widget_alloc = widget->allocation;
180 attributes.x = widget_alloc.x;
181 attributes.y = widget_alloc.y;
182 attributes.width = widget_alloc.width;
183 attributes.height = widget_alloc.height;
184 attributes.wclass = GDK_INPUT_OUTPUT;
185 attributes.window_type = GDK_WINDOW_CHILD;
186 attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
187 attributes.visual = gtk_widget_get_visual (widget);
188 attributes.colormap = gtk_widget_get_colormap (widget);
189 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
190 #if GTK_CHECK_VERSION(2,18,0)
191 gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask));
193 widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
196 gtk_widget_set_style(widget, gtk_style_attach(gtk_widget_get_style(widget), gtk_widget_get_window(widget)));
198 gdk_window_set_user_data (gtk_widget_get_window(widget), widget);
199 gtk_style_set_background (gtk_widget_get_style(widget), gtk_widget_get_window(widget), GTK_STATE_NORMAL);
202 vumeter->colormap = gdk_colormap_get_system ();
203 gtk_vumeter_setup_colors (vumeter);
206 static void gtk_vumeter_size_calculate (GtkWidget *widget, GtkRequisition *requisition)
216 g_return_if_fail (GTK_IS_VUMETER (widget));
217 g_return_if_fail (requisition != NULL);
219 vumeter = GTK_VUMETER (widget);
221 if(vumeter->scale_items != NULL) {
222 /* iterate through scale items to get the highest scale item */
223 for (current = vumeter->scale_items; current != NULL; current = g_list_next(current)) {
224 GtkVUMeterScaleItem * item = current->data;
229 PangoLayout * layout = gtk_widget_create_pango_layout (widget, item->label);
230 pango_layout_get_pixel_size(layout, &layout_width, &layout_height);
237 if (vumeter->vertical == TRUE) {
238 max_x = MAX(max_x, item->large ? LARGE_PITCH_LINE : SMALL_PITCH_LINE);
239 max_y = MAX(max_y, 1);
241 max_x = MAX(max_x, LARGE_PITCH_LINE+3+layout_width);
242 max_y = MAX(max_y, layout_height);
245 max_x = MAX(max_x, 1);
246 max_y = MAX(max_y, item->large ? LARGE_PITCH_LINE : SMALL_PITCH_LINE);
248 max_x = MAX(max_x, layout_width);
249 max_y = MAX(max_y, LARGE_PITCH_LINE+/*3+*/layout_height-2);
255 pitches = MAX((vumeter->scale_pitch_holes+1)*pitches-1, MIN_DYNAMIC_SIDE);
257 if (vumeter->vertical == TRUE) {
258 vumeter->padding_left = SPARE_LEFT;
259 vumeter->padding_right = SPARE_RIGHT + max_x;
260 vumeter->padding_top = SPARE_TOP + max_y / 2;
261 vumeter->padding_bottom = SPARE_BOTTOM + max_y / 2;
262 requisition->width = vumeter->padding_left + vumeter->thickness + vumeter->padding_right;
263 requisition->height = vumeter->padding_top + pitches + vumeter->padding_bottom;
265 vumeter->padding_left = SPARE_LEFT + max_x / 2;
266 vumeter->padding_right = SPARE_RIGHT + max_x / 2;
267 vumeter->padding_top = SPARE_TOP;
268 vumeter->padding_bottom = SPARE_BOTTOM + max_y;
269 requisition->width = vumeter->padding_left + pitches + vumeter->padding_right;
270 requisition->height = vumeter->padding_top + vumeter->thickness + vumeter->padding_bottom;
274 static void gtk_vumeter_size_request (GtkWidget *widget, GtkRequisition *requisition)
276 gtk_vumeter_size_calculate(widget, requisition);
279 static void gtk_vumeter_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
282 GtkRequisition requisition;
284 g_return_if_fail (GTK_IS_VUMETER (widget));
285 g_return_if_fail (allocation != NULL);
287 #if GTK_CHECK_VERSION(2,18,0)
288 gtk_widget_set_allocation(widget, allocation);
290 widget->allocation = *allocation;
292 vumeter = GTK_VUMETER (widget);
294 gtk_vumeter_size_calculate(widget, &requisition);
296 if (gtk_widget_get_realized(widget)) {
297 gdk_window_move_resize (gtk_widget_get_window(widget), allocation->x, allocation->y,
298 MAX (allocation->width, requisition.width),
299 MAX (allocation->height, requisition.height));
301 /* Fix the colours */
302 gtk_vumeter_setup_colors (vumeter);
306 static gboolean gtk_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
309 gint index, level, peak_level = 0;
313 GtkAllocation widget_alloc;
314 GdkWindow *widget_window = gtk_widget_get_window(widget);
316 g_return_val_if_fail (GTK_IS_VUMETER (widget), FALSE);
317 g_return_val_if_fail (event != NULL, FALSE);
319 vumeter = GTK_VUMETER (widget);
321 level = gtk_vumeter_sound_level_to_draw_level (vumeter, vumeter->level);
322 if (vumeter->peak == TRUE) {
323 peak_level = gtk_vumeter_sound_level_to_draw_level (vumeter, vumeter->peak_level);
326 #if GTK_CHECK_VERSION(2,18,0)
327 gtk_widget_get_allocation(widget, &widget_alloc);
329 widget_alloc = widget->allocation;
332 /* the dimentions of the bar (leaving some space for the scale) */
333 width = widget_alloc.width - vumeter->padding_left - vumeter->padding_right;
334 height = widget_alloc.height - vumeter->padding_top - vumeter->padding_bottom;
336 /* clear widget and draw border */
337 gtk_paint_box (gtk_widget_get_style(widget), widget_window, GTK_STATE_NORMAL, GTK_SHADOW_IN,
338 NULL, widget, "trough", 0, 0, widget_alloc.width, widget_alloc.height);
342 gtk_paint_box (widget->style, widget_window, GTK_STATE_NORMAL, GTK_SHADOW_NONE,
343 NULL, widget, "trough", vumeter->padding_left, vumeter->padding_top, width+1, height+1);
346 if (vumeter->vertical == TRUE) {
347 if (vumeter->scale_inverted == TRUE) {
348 h = height + vumeter->padding_top;
351 h = vumeter->padding_top;
356 if(vumeter->scale_items != NULL) {
357 /* iterate through scale items */
358 for (current = vumeter->scale_items; current != NULL; current = g_list_next(current)) {
359 GtkVUMeterScaleItem * item = current->data;
360 int scale_level = gtk_vumeter_sound_level_to_draw_level (vumeter, item->level);
362 /* XXX - use a fixed color for the scale? */
363 gdk_draw_line (widget_window, vumeter->b_gc[scale_level],
364 vumeter->padding_left+width, h + inc*scale_level,
365 vumeter->padding_left+width+(item->large==TRUE ? LARGE_PITCH_LINE : SMALL_PITCH_LINE)-1, h + inc*scale_level);
370 PangoLayout * layout = gtk_widget_create_pango_layout (widget, item->label);
371 pango_layout_get_pixel_size(layout, &layout_width, &layout_height);
372 gdk_draw_layout(widget_window,
373 vumeter->b_gc[scale_level],
374 vumeter->padding_left+width+vumeter->padding_right-1-layout_width,
375 h + inc*scale_level - layout_height/2 - 1,
381 /* draw background gradient */
382 for (index = 0; index < level; index++, h += inc) {
383 gdk_draw_line (widget_window, vumeter->b_gc[index],
384 vumeter->padding_left+vumeter->reduced_thickness, h,
385 vumeter->padding_left+width-1-vumeter->reduced_thickness, h);
387 /* draw foreground gradient */
388 for (index = level; index < height; index++, h += inc) {
389 gdk_draw_line (widget_window, vumeter->f_gc[index],
390 vumeter->padding_left,h,
391 vumeter->padding_left+width-1, h);
394 if (vumeter->peak == TRUE) {
396 index = MAX (peak_level - 1, 0);
397 for (; index < MIN (peak_level + 1, height - 2); index++) {
398 h = vumeter->scale_inverted == TRUE ? height +vumeter->padding_top - (index + 2) : vumeter->padding_top + index + 1;
399 gdk_draw_line (widget_window, vumeter->f_gc[index], vumeter->padding_left, h, vumeter->padding_left+width-1, h);
402 } else { /* Horizontal */
403 /* the start point of the bar */
404 if (vumeter->scale_inverted == TRUE) {
405 w = width-1 + vumeter->padding_left;
408 w = vumeter->padding_left;
413 if(vumeter->scale_items != NULL) {
414 /* iterate through scale items */
415 for (current = vumeter->scale_items; current != NULL; current = g_list_next(current)) {
416 GtkVUMeterScaleItem * item = current->data;
417 int scale_level = gtk_vumeter_sound_level_to_draw_level (vumeter, item->level);
419 /* XXX - use a fixed color for the scale? */
420 gdk_draw_line (widget_window, vumeter->b_gc[scale_level],
421 w + inc*scale_level, vumeter->padding_top+height,
422 w + inc*scale_level, vumeter->padding_top+height+(item->large==TRUE ? LARGE_PITCH_LINE : SMALL_PITCH_LINE)-1);
427 PangoLayout * layout = gtk_widget_create_pango_layout (widget, item->label);
428 pango_layout_get_pixel_size(layout, &layout_width, &layout_height);
429 gdk_draw_layout(widget_window, vumeter->b_gc[scale_level],
430 w + inc*scale_level - layout_width/2,
431 height + vumeter->padding_top + 3,
437 /* draw background gradient */
438 for (index = 0; index < level; index++, w += inc) {
439 gdk_draw_line (widget_window, vumeter->b_gc[index],
440 w, vumeter->padding_top+vumeter->reduced_thickness,
441 w, vumeter->padding_top+height-1-vumeter->reduced_thickness);
443 /* draw foreground gradient */
444 for (index = level; index < width; index++, w+= inc) {
445 gdk_draw_line (widget_window, vumeter->f_gc[index],
446 w, vumeter->padding_top,
447 w, vumeter->padding_top+height-1);
451 if (vumeter->peak == TRUE) {
453 index = MAX (peak_level - 1, 0);
454 w = vumeter->scale_inverted == TRUE ? width + vumeter->padding_left - (index + 1) : vumeter->padding_left + index + 1;
455 for (; index <= MIN (peak_level, width - 2); index++, w+= inc) {
456 gdk_draw_line (widget_window, vumeter->f_gc[index], w, vumeter->padding_top, w, vumeter->padding_top+height-1);
464 static void gtk_vumeter_free_colors (GtkVUMeter *vumeter)
468 if (vumeter->colors == 0) { return; }
471 if (vumeter->f_gc && vumeter->b_gc) {
472 for (index = 0; index < vumeter->colors; index++) {
473 if (vumeter->f_gc[index]) {
474 g_object_unref (G_OBJECT(vumeter->f_gc[index]));
476 if (vumeter->b_gc[index]) {
477 g_object_unref (G_OBJECT(vumeter->b_gc[index]));
480 g_free(vumeter->f_gc);
481 g_free(vumeter->b_gc);
482 vumeter->f_gc = NULL;
483 vumeter->b_gc = NULL;
486 /* Free old Colors */
487 if (vumeter->f_colors) {
488 gdk_colormap_free_colors (vumeter->colormap, vumeter->f_colors, vumeter->colors);
489 g_free (vumeter->f_colors);
490 vumeter->f_colors = NULL;
492 if (vumeter->b_colors) {
493 gdk_colormap_free_colors (vumeter->colormap, vumeter->b_colors, vumeter->colors);
494 g_free (vumeter->b_colors);
495 vumeter->b_colors = NULL;
499 static void gtk_vumeter_setup_colors (GtkVUMeter *vumeter)
504 gint max = 0, min = 0, log_max = 0;
505 GtkAllocation vumeter_alloc;
507 g_return_if_fail (vumeter->colormap != NULL);
509 gtk_vumeter_free_colors (vumeter);
511 #if GTK_CHECK_VERSION(2,18,0)
512 gtk_widget_get_allocation(GTK_WIDGET(vumeter), &vumeter_alloc);
514 vumeter_alloc = GTK_WIDGET(vumeter)->allocation;
518 if (vumeter->vertical == TRUE) {
519 vumeter->colors = MAX(vumeter_alloc.height - vumeter->padding_top - vumeter->padding_bottom, 0);
521 vumeter->colors = MAX(vumeter_alloc.width - vumeter->padding_left - vumeter->padding_right, 0);
524 /* allocate new memory */
525 vumeter->f_colors = g_malloc (vumeter->colors * sizeof(GdkColor));
526 vumeter->b_colors = g_malloc (vumeter->colors * sizeof(GdkColor));
527 vumeter->f_gc = g_malloc (vumeter->colors * sizeof(GdkGC *));
528 vumeter->b_gc = g_malloc (vumeter->colors * sizeof(GdkGC *));
530 /* Initialize stuff */
531 if (vumeter->scaling == GTK_VUMETER_SCALING_LINEAR) {
532 first = 1+gtk_vumeter_sound_level_to_draw_level (vumeter, vumeter->yellow_level);
533 second = vumeter->colors;
535 max = vumeter->level_max;
536 min = vumeter->level_min;
537 log_max = (gint) (- 20.0 * log10(1.0/(max - min + 1.0)));
538 first = (gint)((gdouble)vumeter->colors * 6.0 / log_max);
539 second = (gint)((gdouble)vumeter->colors * 18.0 / log_max);
542 if(vumeter->colors_inverted) {
543 vumeter->f_colors[0].red = 0;
544 vumeter->f_colors[0].green = vumeter->f_brightness;
545 vumeter->f_colors[0].blue = 0;
547 vumeter->b_colors[0].red = 0;
548 vumeter->b_colors[0].green = vumeter->b_brightness;
549 vumeter->b_colors[0].blue = 0;
551 /* Allocate from Green to Yellow */
552 f_step = vumeter->f_brightness / (first - 1);
553 b_step = vumeter->b_brightness / (first - 1);
554 for (index = 1; index < first; index++) {
556 vumeter->f_colors[index].red = vumeter->f_colors[index - 1].red + f_step;
557 vumeter->f_colors[index].green = vumeter->f_brightness;
558 vumeter->f_colors[index].blue = 0;
560 vumeter->b_colors[index].red = vumeter->b_colors[index - 1].red + b_step;
561 vumeter->b_colors[index].green = vumeter->b_brightness;
562 vumeter->b_colors[index].blue = 0;
564 /* Allocate from Yellow to Red */
565 if(second != first) {
566 f_step = vumeter->f_brightness / (second - first);
567 b_step = vumeter->b_brightness / (second - first);
568 for (index = first; index < second; index++) {
570 vumeter->f_colors[index].red = vumeter->f_colors[index - 1].red;
571 vumeter->f_colors[index].green = vumeter->f_colors[index - 1].green - f_step;
572 vumeter->f_colors[index].blue = 0;
574 vumeter->b_colors[index].red = vumeter->b_colors[index - 1].red;
575 vumeter->b_colors[index].green = vumeter->b_colors[index - 1].green - b_step;
576 vumeter->b_colors[index].blue = 0;
580 vumeter->f_colors[0].red = vumeter->f_brightness;
581 vumeter->f_colors[0].green = 0;
582 vumeter->f_colors[0].blue = 0;
584 vumeter->b_colors[0].red = vumeter->b_brightness;
585 vumeter->b_colors[0].green = 0;
586 vumeter->b_colors[0].blue = 0;
588 /* Allocate from Red to Yellow */
589 f_step = vumeter->f_brightness / MAX(first - 1, 1);
590 b_step = vumeter->b_brightness / MAX(first - 1, 1);
591 for (index = 1; index < first; index++) {
593 vumeter->f_colors[index].red = vumeter->f_brightness;
594 vumeter->f_colors[index].green = vumeter->f_colors[index - 1].green + f_step;
595 vumeter->f_colors[index].blue = 0;
597 vumeter->b_colors[index].red = vumeter->b_brightness;
598 vumeter->b_colors[index].green = vumeter->b_colors[index - 1].green + b_step;
599 vumeter->b_colors[index].blue = 0;
601 /* Allocate from Yellow to Green */
602 f_step = vumeter->f_brightness / MAX(second - first, 1);
603 b_step = vumeter->b_brightness / MAX(second - first, 1);
604 for (index = first; index < second; index++) {
606 vumeter->f_colors[index].red = vumeter->f_colors[index - 1].red - f_step;
607 vumeter->f_colors[index].green = vumeter->f_colors[index - 1].green;
608 vumeter->f_colors[index].blue = 0;
610 vumeter->b_colors[index].red = vumeter->b_colors[index - 1].red - b_step;
611 vumeter->b_colors[index].green = vumeter->b_colors[index - 1].green;
612 vumeter->b_colors[index].blue = 0;
614 if (vumeter->scaling == GTK_VUMETER_SCALING_LOG && (vumeter->colors - second) > 0) {
615 /* Allocate from Green to Dark Green */
616 f_step = vumeter->f_brightness / 2 / (vumeter->colors - second);
617 b_step = vumeter->b_brightness / 2 / (vumeter->colors - second);
618 for (index = second; index < vumeter->colors; index++) {
620 vumeter->f_colors[index].red = 0;
621 vumeter->f_colors[index].green = vumeter->f_colors[index - 1].green - f_step;
622 vumeter->f_colors[index].blue = 0;
624 vumeter->b_colors[index].red = 0;
625 vumeter->b_colors[index].green = vumeter->b_colors[index - 1].green - b_step;
626 vumeter->b_colors[index].blue = 0;
629 } /* colors_inverted */
631 /* Allocate the Colours */
632 for (index = 0; index < vumeter->colors; index++) {
634 gdk_colormap_alloc_color (vumeter->colormap, &vumeter->f_colors[index], FALSE, TRUE);
635 vumeter->f_gc[index] = gdk_gc_new(gtk_widget_get_window(GTK_WIDGET(vumeter)));
636 gdk_gc_set_foreground(vumeter->f_gc[index], &vumeter->f_colors[index]);
638 gdk_colormap_alloc_color (vumeter->colormap, &vumeter->b_colors[index], FALSE, TRUE);
639 vumeter->b_gc[index] = gdk_gc_new(gtk_widget_get_window(GTK_WIDGET(vumeter)));
640 gdk_gc_set_foreground(vumeter->b_gc[index], &vumeter->b_colors[index]);
644 static gint gtk_vumeter_sound_level_to_draw_level (GtkVUMeter *vumeter, gint sound_level)
647 gdouble level, min, max, height;
648 gdouble log_level, log_max;
650 level = (gdouble)sound_level;
651 min = (gdouble)vumeter->level_min;
652 max = (gdouble)vumeter->level_max;
653 height = (gdouble)vumeter->colors;
655 if (vumeter->scaling == GTK_VUMETER_SCALING_LINEAR) {
656 draw_level = (1.0 - (level - min)/(max - min)) * (height - 1.0);
657 /* to avoid rounding problems */
660 log_level = log10((level - min + 1)/(max - min + 1));
661 log_max = log10(1/(max - min + 1));
662 draw_level = log_level/log_max * (height - 1.0);
665 return ((gint)draw_level);
668 static gboolean gtk_vumeter_redraw_timeout (gpointer data)
670 GtkVUMeter *vumeter = data;
671 /* Immediately return if need be */
672 if (!gtk_widget_get_realized (GTK_WIDGET(vumeter))) { return TRUE; }
673 if (vumeter->peak == FALSE) { return TRUE; }
674 if (vumeter->peak_level == vumeter->level) { return TRUE; }
676 if(vumeter->peak_hold != 0) {
677 vumeter->peak_hold--;
681 /* Drop the peak_level by rate */
682 vumeter->peak_level -= vumeter->peak_falloff_rate;
683 vumeter->peak_level = MAX (vumeter->peak_level, vumeter->level);
685 gtk_widget_queue_draw (GTK_WIDGET(vumeter));
690 static GList *gtk_vumeter_clone_scale_items(GList *scale_items)
692 GList * new_list = NULL;
695 for ( ; scale_items != NULL; scale_items = g_list_next(scale_items)) {
696 GtkVUMeterScaleItem * item = scale_items->data;
697 GtkVUMeterScaleItem * new_item;
699 new_item = g_malloc(sizeof(GtkVUMeterScaleItem));
700 new_item->level = item->level;
701 new_item->large = item->large;
702 new_item->label = g_strdup(item->label);
703 new_list = g_list_append(new_list, new_item);
709 static void gtk_vumeter_setup_scale_items(GtkVUMeter *vumeter, GList *scale_items)
711 GList * new_list = NULL;
712 GList * new_list_item = NULL;
714 /* clone the whole list */
715 new_list = gtk_vumeter_clone_scale_items(scale_items);
717 /* clamp the levels */
718 for (new_list_item = new_list; new_list_item != NULL; new_list_item = g_list_next(new_list_item)) {
719 GtkVUMeterScaleItem * item = new_list_item->data;
721 item->level = CLAMP(item->level, vumeter->level_min, vumeter->level_max);
724 gtk_vumeter_free_scale_items(vumeter->scale_items);
725 vumeter->scale_items = new_list;
728 void gtk_vumeter_free_scale_items(GList *scale_items)
732 if(scale_items == NULL) return;
734 for (current = scale_items; current != NULL; current = g_list_next(current)) {
735 GtkVUMeterScaleItem * item = current->data;
737 g_free((void *) item->label);
741 g_list_free(scale_items);
745 * gtk_vumeter_set_orientation:
746 * @param vumeter the vumeter widget
747 * @param orientation the direction in which the graph is going for increasing values
749 void gtk_vumeter_set_orientation (GtkVUMeter *vumeter, GtkVUMeterOrientation orientation)
751 g_return_if_fail (GTK_IS_VUMETER (vumeter));
753 if(orientation == GTK_VUMETER_BOTTOM_TO_TOP || orientation == GTK_VUMETER_TOP_TO_BOTTOM) {
754 vumeter->vertical = TRUE;
756 vumeter->vertical = FALSE;
759 if(orientation == GTK_VUMETER_LEFT_TO_RIGHT || orientation == GTK_VUMETER_BOTTOM_TO_TOP) {
760 vumeter->scale_inverted = TRUE;
762 vumeter->scale_inverted = FALSE;
765 if (gtk_widget_get_realized (GTK_WIDGET(vumeter))) {
766 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
771 * gtk_vumeter_get_orientation:
772 * @param vumeter the vumeter widget
773 * @return the direction in which the graph is going for increasing values
775 GtkVUMeterOrientation gtk_vumeter_get_orientation (GtkVUMeter *vumeter)
777 if(!GTK_IS_VUMETER (vumeter)) {
778 return GTK_VUMETER_BOTTOM_TO_TOP;
781 /* XXX - might be faster using a lookup table */
782 if(vumeter->vertical == TRUE && vumeter->scale_inverted == TRUE) {
783 return GTK_VUMETER_BOTTOM_TO_TOP;
786 if(vumeter->vertical == TRUE && vumeter->scale_inverted == FALSE) {
787 return GTK_VUMETER_TOP_TO_BOTTOM;
790 if(vumeter->vertical == FALSE && vumeter->scale_inverted == TRUE) {
791 return GTK_VUMETER_LEFT_TO_RIGHT;
794 if(vumeter->vertical == FALSE && vumeter->scale_inverted == FALSE) {
795 return GTK_VUMETER_LEFT_TO_RIGHT;
798 g_assert_not_reached();
799 return GTK_VUMETER_BOTTOM_TO_TOP;
803 * gtk_vumeter_set_thickness:
804 * @param vumeter the vumeter widget
805 * @param thickness gtkvumeter's minimum graph thickness in pixels (default:30)
807 * Allows the user program to change the dimension of the vumeter.
808 * For a vertical meter, this is the width.
809 * Likewise for a horizontal meter, this is the height.
811 void gtk_vumeter_set_thickness (GtkVUMeter *vumeter, gint thickness)
813 g_return_if_fail (GTK_IS_VUMETER (vumeter));
815 if (vumeter->thickness != thickness) {
816 vumeter->thickness = thickness;
817 vumeter->reduced_thickness = MIN(vumeter->reduced_thickness, vumeter->thickness);
818 gtk_widget_queue_resize (GTK_WIDGET (vumeter));
823 * gtk_vumeter_get_thickness:
824 * @param vumeter the vumeter widget
825 * @return gtkvumeter's minimum graph thickness in pixels (default:30)
827 * For a vertical meter, this is the width.
828 * Likewise for a horizontal meter, this is the height.
830 gint gtk_vumeter_get_thickness (GtkVUMeter *vumeter)
832 if(!GTK_IS_VUMETER (vumeter)) {
835 return vumeter->thickness;
840 * gtk_vumeter_set_thickness_reduction:
841 * @param vumeter the vumeter widget
842 * @param reduced_thickness pixels to reduce the "none active" part of the graph (default:0)
844 * Allows the user program to reduce the thickness of the "background" part of the vumeter graph.
845 * This can be useful to distinguish the border between the foreground and background graph.
847 void gtk_vumeter_set_thickness_reduction (GtkVUMeter *vumeter, gint reduced_thickness)
849 g_return_if_fail (GTK_IS_VUMETER (vumeter));
851 if (vumeter->reduced_thickness != reduced_thickness) {
852 vumeter->reduced_thickness = reduced_thickness;
853 vumeter->reduced_thickness = CLAMP(vumeter->reduced_thickness, 0, vumeter->thickness/2);
854 gtk_widget_queue_resize (GTK_WIDGET (vumeter));
859 * gtk_vumeter_get_thickness_reduction:
860 * @param vumeter the vumeter widget
861 * @return pixels to reduce the "none active" part of the graph (default:0)
863 * The reduced thickness of the "background" part of the vumeter graph.
865 gint gtk_vumeter_get_thickness_reduction (GtkVUMeter *vumeter)
867 if(!GTK_IS_VUMETER (vumeter)) {
870 return vumeter->reduced_thickness;
875 * gtk_vumeter_set_min_max:
876 * @param vumeter the vumeter widget
877 * @param min the new minimum level shown (default: 0)
878 * @param max the new maximum level shown (default: 32768)
880 * Sets the minimum and maximum of the VU Meters scale.
881 * It will increment max by one if min == max.
882 * And finally it will clamp the relevant levels into the min, max range.
883 * Either value can be NULL, to keep the current value.
885 * Don't forget to call %gtk_vumeter_set_yellow_level() if required!
887 * WARNING: negative values for min or max will currently not work!!!
889 void gtk_vumeter_set_min_max (GtkVUMeter *vumeter, gint *min, gint *max)
893 g_return_if_fail (GTK_IS_VUMETER (vumeter));
895 /* Allow min or max to be NULL */
896 mi = (min != NULL) ? *min : vumeter->level_min;
897 ma = (max != NULL) ? *max : vumeter->level_max;
899 /* Ensure that max > min */
900 vumeter->level_max = MAX(ma, mi);
901 vumeter->level_min = MIN(mi, ma);
902 if (vumeter->level_max == vumeter->level_min) {
903 /* Increment max so we have a range */
904 vumeter->level_max++;
906 /* Clamp the levels to the new range */
907 vumeter->level = CLAMP (vumeter->level, vumeter->level_min, vumeter->level_max);
908 vumeter->peak_level = CLAMP (vumeter->peak_level, vumeter->level, vumeter->level_max);
909 vumeter->yellow_level = CLAMP (vumeter->yellow_level, vumeter->level_min, vumeter->level_max);
911 gtk_widget_queue_draw (GTK_WIDGET(vumeter));
915 * gtk_vumeter_get_min_max:
916 * @param vumeter the vumeter widget
917 * @param min the new minimum level shown (default: 0)
918 * @param max the new maximum level shown (default: 32768)
920 * The minimum and maximum of the VU Meters scale.
922 void gtk_vumeter_get_min_max (GtkVUMeter *vumeter, gint *min, gint *max)
924 if(!GTK_IS_VUMETER (vumeter)) {
928 *min = vumeter->level_min;
929 *max = vumeter->level_max;
934 * gtk_vumeter_set_level:
935 * @param vumeter the vumeter widget
936 * @param level the new level shown (default: 0)
938 * Sets new level value for the vumeter.
939 * The level is clamped to the min max range.
940 * The peak_level will be increased to level if needed.
942 void gtk_vumeter_set_level (GtkVUMeter *vumeter, gint level)
944 g_return_if_fail (GTK_IS_VUMETER (vumeter));
946 if (vumeter->level != level) {
947 vumeter->level = CLAMP (level, vumeter->level_min, vumeter->level_max);
948 if(vumeter->level > vumeter->peak_level) {
949 vumeter->peak_hold = vumeter->peak_hold_factor;
950 vumeter->peak_level = vumeter->level;
952 gtk_widget_queue_draw (GTK_WIDGET(vumeter));
957 * gtk_vumeter_get_level:
958 * @param vumeter the vumeter widget
959 * @return the level shown (default: 0)
961 * Gets the level value of the vumeter.
963 gint gtk_vumeter_get_level (GtkVUMeter *vumeter)
965 if(!GTK_IS_VUMETER (vumeter)) {
968 return vumeter->level;
973 * gtk_vumeter_set_scaling:
974 * @param vumeter the vumeter widget
975 * @param scaling the scaling mode either GTK_VUMETER_SCALING_LINEAR or GTK_VUMETER_SCALING_LOG
977 * Sets the scaling mode of the VU Meter.
978 * It is either log or linear and defaults to linear.
979 * No matter which scale you set the input should always be linear, gtkVUMeter
980 * does the log calculation/display.
982 void gtk_vumeter_set_scaling (GtkVUMeter *vumeter, GtkVUMeterScaling scaling)
984 g_return_if_fail (GTK_IS_VUMETER (vumeter));
986 if (scaling != vumeter->scaling) {
987 vumeter->scaling = CLAMP (scaling, GTK_VUMETER_SCALING_LINEAR, GTK_VUMETER_SCALING_LOG);
988 if (gtk_widget_get_realized (GTK_WIDGET(vumeter))) {
989 gtk_vumeter_setup_colors (vumeter);
990 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
996 * gtk_vumeter_get_scaling:
997 * @param vumeter the vumeter widget
998 * @return the scaling mode either GTK_VUMETER_SCALING_LINEAR or GTK_VUMETER_SCALING_LOG
1000 * Gets the scaling mode of the VU Meter.
1001 * It is either log or linear and defaults to linear.
1003 GtkVUMeterScaling gtk_vumeter_get_scaling (GtkVUMeter *vumeter)
1005 if(!GTK_IS_VUMETER (vumeter)) {
1008 return vumeter->scaling;
1013 * gtk_vumeter_set_scale_items:
1014 * @param vumeter the vumeter widget
1015 * @param scale_items a GList of the pitch lines and labels (default:NULL)
1017 * Set the scale pitch lines and labels.
1018 * Must be NULL or a GList containing filled %GtkVUMeterScaleItem items.
1019 * Function will make a deep copy of the GList and it's items,
1020 * so the given GList and it's items can be safely thrown away after the call.
1021 * A side effect: This also sets the minimum size of the widget.
1023 void gtk_vumeter_set_scale_items(GtkVUMeter *vumeter, GList *scale_items)
1025 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1027 gtk_vumeter_setup_scale_items(vumeter, scale_items);
1029 if (gtk_widget_get_realized(GTK_WIDGET(vumeter))) {
1030 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1035 * gtk_vumeter_get_scale_items:
1036 * @param vumeter the vumeter widget
1037 * @return a GList of the pitch lines and labels (default:NULL)
1039 * Get the scale pitch lines and labels, a GList containing %GtkVUMeterScaleItem items.
1040 * The returned GList must be freed with gtk_vumeter_free_scale_items() by the user!!!
1042 GList *gtk_vumeter_get_scale_items(GtkVUMeter *vumeter)
1044 if(!GTK_IS_VUMETER (vumeter)) {
1047 return gtk_vumeter_clone_scale_items(vumeter->scale_items);
1052 * gtk_vumeter_set_scale_hole_size:
1053 * @param vumeter the vumeter widget
1054 * @param hole_size (default:0)
1056 * Set the size of the "holes" between the pitch lines.
1057 * A side effect: This also sets the minimum size of the widget.
1059 void gtk_vumeter_set_scale_hole_size (GtkVUMeter *vumeter, gint hole_size)
1061 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1063 if (vumeter->scale_pitch_holes != hole_size) {
1064 vumeter->scale_pitch_holes = hole_size;
1065 gtk_widget_queue_resize (GTK_WIDGET (vumeter));
1070 * gtk_vumeter_get_scale_hole_size:
1071 * @param vumeter the vumeter widget
1072 * @return (default:0)
1074 * Get the size of the "holes" between the pitch lines.
1076 gint gtk_vumeter_get_scale_hole_size (GtkVUMeter *vumeter)
1078 if(!GTK_IS_VUMETER (vumeter)) {
1081 return vumeter->scale_pitch_holes;
1086 * gtk_vumeter_set_peak:
1087 * @param vumeter the vumeter widget
1088 * @param peak whether or not the peak indicator is drawn
1089 * @param redraw_rate the rate (in milliseconds) at which the peak indicator is redrawn
1091 * Enables/Disables the peak meachanism and sets the redraw timeout to redraw_rate milliseconds.
1092 * The redraw operation is intelligent in that the widget is only redrawn
1093 * if the peak_level != level and peak == %TRUE.
1095 * Hint: A good redraw_rate is 200ms (default: 0ms -> off)
1097 void gtk_vumeter_set_peak (GtkVUMeter *vumeter, gboolean peak, guint redraw_rate)
1099 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1101 if (vumeter->peak != peak) {
1102 vumeter->peak = peak;
1103 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1106 vumeter->peak_redraw_rate = redraw_rate;
1108 if(vumeter->peak_timeout) {
1109 g_source_remove(vumeter->peak_timeout);
1112 if(redraw_rate != 0 && vumeter->peak) {
1113 vumeter->peak_timeout = g_timeout_add (redraw_rate, gtk_vumeter_redraw_timeout, vumeter);
1118 * gtk_vumeter_get_peak:
1119 * @param vumeter the vumeter widget
1120 * @param peak whether or not the peak indicator is drawn
1121 * @param redraw_rate the rate (in milliseconds) at which the peak indicator is redrawn
1124 void gtk_vumeter_get_peak (GtkVUMeter *vumeter, gboolean *peak, guint *redraw_rate)
1126 if(!GTK_IS_VUMETER (vumeter)) {
1130 *peak = vumeter->peak;
1131 *redraw_rate = vumeter->peak_redraw_rate;
1136 * gtk_vumeter_set_peak_hold_factor:
1137 * @param vumeter the vumeter widget
1138 * @param hold_factor number of redraw_rates to wait until peak indicator is decayed (default:0 -> off)
1140 * Holds the peak indicator for a limited time at it's highest position.
1141 * The actual rate is dependent on the redraw_rate given to %gtk_vumeter_set_peak().
1143 * Hint: For a VU meter, a good hold_factor is 7 with a redraw_rate of 200ms.
1145 void gtk_vumeter_set_peak_hold_factor (GtkVUMeter *vumeter, gint hold_factor)
1147 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1149 if (vumeter->peak_hold_factor != hold_factor) {
1150 vumeter->peak_hold_factor = hold_factor;
1155 * gtk_vumeter_get_peak_hold_factor:
1156 * @param vumeter the vumeter widget
1157 * @return number of redraw_rates to wait until peak indicator is decayed (default:0 -> off)
1159 gint gtk_vumeter_get_peak_hold_factor (GtkVUMeter *vumeter)
1161 if(!GTK_IS_VUMETER (vumeter)) {
1164 return vumeter->peak_hold_factor;
1169 * gtk_vumeter_set_peak_falloff:
1170 * @param vumeter the vumeter widget
1171 * @param peak_falloff controls the speed to the peak decay
1172 * @param user_rate pixels to reduce the peak level at each redraw_rate in GTK_VUMETER_PEAK_FALLOFF_USER mode, otherwise ignored
1174 * Set the numbers of pixel reduced from the peak indicator each redraw_rate (after the hold period is over).
1175 * The peak_falloff will be around: SLOW:5%, MEDIUM:10%, FAST:20%, USER:user_rate
1176 * of the current range, reduced from peak at each redraw_rate (%gtk_vumeter_set_peak()).
1178 * Hint: a user_rate of 0 can be used to hold the peak indicator at the highest position ever.
1180 void gtk_vumeter_set_peak_falloff (GtkVUMeter *vumeter, GtkVUMeterPeakFalloff peak_falloff, guint user_rate)
1183 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1185 vumeter->peak_falloff_mode = CLAMP(peak_falloff, GTK_VUMETER_PEAK_FALLOFF_SLOW, GTK_VUMETER_PEAK_FALLOFF_USER);
1186 range = vumeter->level_max - vumeter->level_min;
1188 switch (peak_falloff) {
1189 case GTK_VUMETER_PEAK_FALLOFF_SLOW:
1190 vumeter->peak_falloff_rate = range/20;
1193 case GTK_VUMETER_PEAK_FALLOFF_MEDIUM:
1194 vumeter->peak_falloff_rate = range/10;
1196 case GTK_VUMETER_PEAK_FALLOFF_FAST:
1197 vumeter->peak_falloff_rate = range/5;
1199 case GTK_VUMETER_PEAK_FALLOFF_USER:
1200 vumeter->peak_falloff_rate = (gint)user_rate;
1206 * gtk_vumeter_get_peak_falloff:
1207 * @param vumeter the vumeter widget
1208 * @param peak_falloff controls the speed to the peak decay
1209 * @param user_rate pixels to lower the peak level each redraw_rate (value valid in every peak_falloff mode)
1211 void gtk_vumeter_get_peak_falloff (GtkVUMeter *vumeter, GtkVUMeterPeakFalloff *peak_falloff, guint *user_rate)
1213 if(!GTK_IS_VUMETER (vumeter)) {
1217 *peak_falloff = vumeter->peak_falloff_mode;
1218 *user_rate = vumeter->peak_falloff_rate;
1223 * gtk_vumeter_set_colors_inverted:
1224 * @param vumeter the vumeter widget
1225 * @param inverted whether or not the colors are inverted (default:%FALSE)
1227 * Usually the graph will be colored with: 0:green, half:yellow, full:red.
1228 * This is used to display signals that won't "work correct" above a maximum level
1229 * (e.g. audio signals may distort if their amplitude is too high).
1231 * The inverted colors will be: 0:red, half:yellow, full:green.
1232 * This is used to display signals that need a minimum level to work correct
1233 * (e.g. a received antenna signal must have a minimum amplitude to "work correct").
1235 void gtk_vumeter_set_colors_inverted (GtkVUMeter *vumeter, gboolean inverted)
1237 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1239 vumeter->colors_inverted = inverted;
1240 if (gtk_widget_get_realized(GTK_WIDGET(vumeter))) {
1241 gtk_vumeter_setup_colors (vumeter);
1242 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1247 * gtk_vumeter_get_colors_inverted:
1248 * @param vumeter the vumeter widget
1249 * @return whether or not the colors are inverted (default:%FALSE)
1251 gboolean gtk_vumeter_get_colors_inverted (GtkVUMeter *vumeter)
1253 if(!GTK_IS_VUMETER (vumeter)) {
1256 return vumeter->colors_inverted;
1261 * gtk_vumeter_set_yellow_level:
1262 * @param vumeter the vumeter widget
1263 * @param yellow_level set the position of the yellow area (default:16383)
1265 * Will be clamped between min and max.
1267 void gtk_vumeter_set_yellow_level (GtkVUMeter *vumeter, gint yellow_level)
1269 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1271 vumeter->yellow_level = CLAMP (yellow_level, vumeter->level_min, vumeter->level_max);
1272 if (gtk_widget_get_realized(GTK_WIDGET(vumeter))) {
1273 gtk_vumeter_setup_colors (vumeter);
1274 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1279 * gtk_vumeter_get_yellow_level:
1280 * @param vumeter the vumeter widget
1281 * @return get the position of the yellow area (default:16383)
1283 gint gtk_vumeter_get_yellow_level (GtkVUMeter *vumeter)
1285 if(!GTK_IS_VUMETER (vumeter)) {
1288 return vumeter->yellow_level;
1293 * gtk_vumeter_set_brightness:
1294 * @param vumeter the vumeter widget
1295 * @param foreground set the brightness of the graphs foreground (default:65535)
1296 * @param background set the brightness of the graphs background (default:49151)
1298 * Hint: don't turn the brightness too low, otherwise you'll only see a black bar :-)
1300 void gtk_vumeter_set_brightness (GtkVUMeter *vumeter, gint foreground, gint background)
1302 vumeter->f_brightness = CLAMP(foreground, 0, 65535);
1303 vumeter->b_brightness = CLAMP(background, 0, vumeter->f_brightness);
1304 if (gtk_widget_get_realized(GTK_WIDGET(vumeter))) {
1305 gtk_vumeter_setup_colors (vumeter);
1306 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1311 * gtk_vumeter_get_brightness:
1312 * @param vumeter the vumeter widget
1313 * @param foreground get the brightness of the graphs foreground (default:65535)
1314 * @param background get the brightness of the graphs background (default:49151)
1316 void gtk_vumeter_get_brightness (GtkVUMeter *vumeter, gint *foreground, gint *background)
1318 if(!GTK_IS_VUMETER (vumeter)) {
1322 *foreground = vumeter->f_brightness;
1323 *background = vumeter->b_brightness;