1 /***************************************************************************
4 * Fri Jan 10 20:06:23 2003
5 * Copyright 2003 Todd Goyen
6 * wettoad@knighthoodofbuh.org
8 * Mon May 01 04:04:00 2006
9 * Copyright 2006 Ulf Lamping
12 * Source code is LGPL'd,
13 * but may be distributed under any other open source license
14 ****************************************************************************/
18 #include "gtk/gtkvumeter.h"
21 #define MIN_DYNAMIC_SIDE 40
23 #define SMALL_PITCH_LINE 2
24 #define LARGE_PITCH_LINE 4
29 #define SPARE_BOTTOM 1
32 static void gtk_vumeter_init (GtkVUMeter *vumeter);
33 static void gtk_vumeter_class_init (GtkVUMeterClass *class);
34 static void gtk_vumeter_destroy (GtkObject *object);
35 static void gtk_vumeter_realize (GtkWidget *widget);
36 static void gtk_vumeter_size_calculate (GtkWidget *widget, GtkRequisition *requisition);
37 static void gtk_vumeter_size_request (GtkWidget *widget, GtkRequisition *requisition);
38 static void gtk_vumeter_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
39 static gint gtk_vumeter_expose (GtkWidget *widget, GdkEventExpose *event);
40 static void gtk_vumeter_free_colors (GtkVUMeter *vumeter);
41 static void gtk_vumeter_setup_colors (GtkVUMeter *vumeter);
42 static gint gtk_vumeter_sound_level_to_draw_level (GtkVUMeter *vumeter, gint sound_level);
43 static gboolean gtk_vumeter_redraw_timeout (GtkVUMeter *vumeter);
44 static void gtk_vumeter_setup_scale_items(GtkVUMeter *vumeter, GList *scale_items);
46 static GtkWidgetClass *parent_class = NULL;
48 GtkType gtk_vumeter_get_type (void)
50 static GType vumeter_type = 0;
53 static const GTypeInfo vumeter_info = {
54 sizeof (GtkVUMeterClass),
56 (GClassInitFunc) gtk_vumeter_class_init, NULL, NULL,
57 sizeof (GtkVUMeter), 0, (GInstanceInitFunc) gtk_vumeter_init,
60 vumeter_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkVUMeter", &vumeter_info, 0);
69 * Creates a new VUMeter widget.
71 GtkWidget* gtk_vumeter_new (void)
75 vumeter = GTK_VUMETER (g_object_new (GTK_TYPE_VUMETER, NULL));
77 return GTK_WIDGET (vumeter);
80 static void gtk_vumeter_init (GtkVUMeter *vumeter)
82 vumeter->vertical = TRUE;
83 vumeter->scale_inverted = FALSE;
84 vumeter->thickness = 30;
85 vumeter->reduced_thickness = 0;
86 vumeter->scaling = GTK_VUMETER_SCALING_LINEAR;
87 vumeter->scale_items = NULL;
88 vumeter->scale_pitch_holes = 0;
90 vumeter->padding_left = 1;
91 vumeter->padding_right = 1;
92 vumeter->padding_top = 1;
93 vumeter->padding_bottom = 1;
95 vumeter->colormap = NULL;
99 vumeter->f_colors = NULL;
100 vumeter->b_colors = NULL;
101 vumeter->f_brightness = 65535;
102 vumeter->b_brightness = 49151;
103 vumeter->yellow_level = 16383;
104 vumeter->colors_inverted = FALSE;
107 vumeter->level_min = 0;
108 vumeter->level_max = 32767;
110 vumeter->peak = FALSE;
111 vumeter->peak_level = 0;
112 vumeter->peak_timeout = 0;
113 vumeter->peak_hold_factor = 0;
114 vumeter->peak_hold = 0;
115 vumeter->peak_falloff_mode = GTK_VUMETER_PEAK_FALLOFF_MEDIUM;
116 vumeter->peak_falloff_rate = 3278;
119 static void gtk_vumeter_class_init (GtkVUMeterClass *class)
121 GtkObjectClass *object_class;
122 GtkWidgetClass *widget_class;
124 object_class = (GtkObjectClass*) class;
125 widget_class = (GtkWidgetClass*) class;
126 parent_class = gtk_type_class (gtk_widget_get_type ());
128 object_class->destroy = gtk_vumeter_destroy;
130 widget_class->realize = gtk_vumeter_realize;
131 widget_class->expose_event = gtk_vumeter_expose;
132 widget_class->size_request = gtk_vumeter_size_request;
133 widget_class->size_allocate = gtk_vumeter_size_allocate;
136 static void gtk_vumeter_destroy (GtkObject *object)
138 GtkVUMeter *vumeter = GTK_VUMETER (object);
140 if(vumeter->peak_timeout) {
141 gtk_timeout_remove(vumeter->peak_timeout);
144 gtk_vumeter_free_colors (vumeter);
146 GTK_OBJECT_CLASS (parent_class)->destroy (object);
149 static void gtk_vumeter_realize (GtkWidget *widget)
152 GdkWindowAttr attributes;
153 gint attributes_mask;
155 g_return_if_fail (widget != NULL);
156 g_return_if_fail (GTK_IS_VUMETER (widget));
158 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
159 vumeter = GTK_VUMETER (widget);
161 attributes.x = widget->allocation.x;
162 attributes.y = widget->allocation.y;
163 attributes.width = widget->allocation.width;
164 attributes.height = widget->allocation.height;
165 attributes.wclass = GDK_INPUT_OUTPUT;
166 attributes.window_type = GDK_WINDOW_CHILD;
167 attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
168 attributes.visual = gtk_widget_get_visual (widget);
169 attributes.colormap = gtk_widget_get_colormap (widget);
170 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
171 widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
173 widget->style = gtk_style_attach (widget->style, widget->window);
175 gdk_window_set_user_data (widget->window, widget);
176 gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
179 vumeter->colormap = gdk_colormap_get_system ();
180 gtk_vumeter_setup_colors (vumeter);
183 static void gtk_vumeter_size_calculate (GtkWidget *widget, GtkRequisition *requisition)
193 g_return_if_fail (GTK_IS_VUMETER (widget));
194 g_return_if_fail (requisition != NULL);
196 vumeter = GTK_VUMETER (widget);
198 if(vumeter->scale_items != NULL) {
199 /* iterate through scale items to get the highest scale item */
200 for (current = vumeter->scale_items; current != NULL; current = g_list_next(current)) {
201 GtkVUMeterScaleItem * item = current->data;
206 PangoLayout * layout = gtk_widget_create_pango_layout (widget, item->label);
207 pango_layout_get_pixel_size(layout, &layout_width, &layout_height);
211 if (vumeter->vertical == TRUE) {
212 max_x = MAX(max_x, item->large ? LARGE_PITCH_LINE : SMALL_PITCH_LINE);
213 max_y = MAX(max_y, 1);
215 max_x = MAX(max_x, LARGE_PITCH_LINE+3+layout_width);
216 max_y = MAX(max_y, layout_height);
219 max_x = MAX(max_x, 1);
220 max_y = MAX(max_y, item->large ? LARGE_PITCH_LINE : SMALL_PITCH_LINE);
222 max_x = MAX(max_x, layout_width);
223 max_y = MAX(max_y, LARGE_PITCH_LINE+/*3+*/layout_height-2);
229 pitches = MAX((vumeter->scale_pitch_holes+1)*pitches-1, MIN_DYNAMIC_SIDE);
231 if (vumeter->vertical == TRUE) {
232 vumeter->padding_left = SPARE_LEFT;
233 vumeter->padding_right = SPARE_RIGHT + max_x;
234 vumeter->padding_top = SPARE_TOP + max_y / 2;
235 vumeter->padding_bottom = SPARE_BOTTOM + max_y / 2;
236 requisition->width = vumeter->padding_left + vumeter->thickness + vumeter->padding_right;
237 requisition->height = vumeter->padding_top + pitches + vumeter->padding_bottom;
239 vumeter->padding_left = SPARE_LEFT + max_x / 2;
240 vumeter->padding_right = SPARE_RIGHT + max_x / 2;
241 vumeter->padding_top = SPARE_TOP;
242 vumeter->padding_bottom = SPARE_BOTTOM + max_y;
243 requisition->width = vumeter->padding_left + pitches + vumeter->padding_right;
244 requisition->height = vumeter->padding_top + vumeter->thickness + vumeter->padding_bottom;
248 static void gtk_vumeter_size_request (GtkWidget *widget, GtkRequisition *requisition)
250 gtk_vumeter_size_calculate(widget, requisition);
253 static void gtk_vumeter_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
256 GtkRequisition requisition;
258 g_return_if_fail (GTK_IS_VUMETER (widget));
259 g_return_if_fail (allocation != NULL);
261 widget->allocation = *allocation;
262 vumeter = GTK_VUMETER (widget);
264 gtk_vumeter_size_calculate(widget, &requisition);
266 if (GTK_WIDGET_REALIZED (widget)) {
267 gdk_window_move_resize (widget->window, allocation->x, allocation->y,
268 MAX (allocation->width, requisition.width),
269 MAX (allocation->height, requisition.height));
271 /* Fix the colours */
272 gtk_vumeter_setup_colors (vumeter);
276 static gint gtk_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
279 gint index, level, peak_level = 0;
284 g_return_val_if_fail (GTK_IS_VUMETER (widget), FALSE);
285 g_return_val_if_fail (event != NULL, FALSE);
287 vumeter = GTK_VUMETER (widget);
289 level = gtk_vumeter_sound_level_to_draw_level (vumeter, vumeter->level);
290 if (vumeter->peak == TRUE) {
291 peak_level = gtk_vumeter_sound_level_to_draw_level (vumeter, vumeter->peak_level);
294 /* the dimentions of the bar (leaving some space for the scale) */
295 width = widget->allocation.width - vumeter->padding_left - vumeter->padding_right;
296 height = widget->allocation.height - vumeter->padding_top - vumeter->padding_bottom;
298 /* clear widget and draw border */
299 gtk_paint_box (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN,
300 NULL, widget, "trough", 0, 0, widget->allocation.width, widget->allocation.height);
304 gtk_paint_box (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_NONE,
305 NULL, widget, "trough", vumeter->padding_left, vumeter->padding_top, width+1, height+1);
308 if (vumeter->vertical == TRUE) {
309 if (vumeter->scale_inverted == TRUE) {
310 h = height + vumeter->padding_top;
313 h = vumeter->padding_top;
318 if(vumeter->scale_items != NULL) {
319 /* iterate through scale items */
320 for (current = vumeter->scale_items; current != NULL; current = g_list_next(current)) {
321 GtkVUMeterScaleItem * item = current->data;
322 int scale_level = gtk_vumeter_sound_level_to_draw_level (vumeter, item->level);
324 /* XXX - use a fixed color for the scale? */
325 gdk_draw_line (widget->window, vumeter->b_gc[scale_level],
326 vumeter->padding_left+width, h + inc*scale_level,
327 vumeter->padding_left+width+(item->large==TRUE ? LARGE_PITCH_LINE : SMALL_PITCH_LINE)-1, h + inc*scale_level);
332 PangoLayout * layout = gtk_widget_create_pango_layout (widget, item->label);
333 pango_layout_get_pixel_size(layout, &layout_width, &layout_height);
334 gdk_draw_layout(widget->window,
335 vumeter->b_gc[scale_level],
336 vumeter->padding_left+width+vumeter->padding_right-1-layout_width,
337 h + inc*scale_level - layout_height/2 - 1,
343 /* draw background gradient */
344 for (index = 0; index < level; index++, h += inc) {
345 gdk_draw_line (widget->window, vumeter->b_gc[index],
346 vumeter->padding_left+vumeter->reduced_thickness, h,
347 vumeter->padding_left+width-1-vumeter->reduced_thickness, h);
349 /* draw foreground gradient */
350 for (index = level; index < height; index++, h += inc) {
351 gdk_draw_line (widget->window, vumeter->f_gc[index],
352 vumeter->padding_left,h,
353 vumeter->padding_left+width-1, h);
356 if (vumeter->peak == TRUE) {
358 index = MAX (peak_level - 1, 0);
359 for (; index < MIN (peak_level + 1, height - 2); index++, h += inc) {
360 h = vumeter->scale_inverted == TRUE ? height +vumeter->padding_top - (index + 2) : vumeter->padding_top + index + 1;
361 gdk_draw_line (widget->window, vumeter->f_gc[index], vumeter->padding_left, h, vumeter->padding_left+width-1, h);
364 } else { /* Horizontal */
365 /* the start point of the bar */
366 if (vumeter->scale_inverted == TRUE) {
367 w = width-1 + vumeter->padding_left;
370 w = vumeter->padding_left;
375 if(vumeter->scale_items != NULL) {
376 /* iterate through scale items */
377 for (current = vumeter->scale_items; current != NULL; current = g_list_next(current)) {
378 GtkVUMeterScaleItem * item = current->data;
379 int scale_level = gtk_vumeter_sound_level_to_draw_level (vumeter, item->level);
381 /* XXX - use a fixed color for the scale? */
382 gdk_draw_line (widget->window, vumeter->b_gc[scale_level],
383 w + inc*scale_level, vumeter->padding_top+height,
384 w + inc*scale_level, vumeter->padding_top+height+(item->large==TRUE ? LARGE_PITCH_LINE : SMALL_PITCH_LINE)-1);
389 PangoLayout * layout = gtk_widget_create_pango_layout (widget, item->label);
390 pango_layout_get_pixel_size(layout, &layout_width, &layout_height);
391 gdk_draw_layout(widget->window, vumeter->b_gc[scale_level],
392 w + inc*scale_level - layout_width/2,
393 height + vumeter->padding_top + 3,
399 /* draw background gradient */
400 for (index = 0; index < level; index++, w += inc) {
401 gdk_draw_line (widget->window, vumeter->b_gc[index],
402 w, vumeter->padding_top+vumeter->reduced_thickness,
403 w, vumeter->padding_top+height-1-vumeter->reduced_thickness);
405 /* draw foreground gradient */
406 for (index = level; index < width; index++, w+= inc) {
407 gdk_draw_line (widget->window, vumeter->f_gc[index],
408 w, vumeter->padding_top,
409 w, vumeter->padding_top+height-1);
413 if (vumeter->peak == TRUE) {
415 index = MAX (peak_level - 1, 0);
416 w = vumeter->scale_inverted == TRUE ? width + vumeter->padding_left - (index + 1) : vumeter->padding_left + index + 1;
417 for (; index <= MIN (peak_level, width - 2); index++, w+= inc) {
418 gdk_draw_line (widget->window, vumeter->f_gc[index], w, vumeter->padding_top, w, vumeter->padding_top+height-1);
426 static void gtk_vumeter_free_colors (GtkVUMeter *vumeter)
430 if (vumeter->colors == 0) { return; }
433 if (vumeter->f_gc && vumeter->b_gc) {
434 for (index = 0; index < vumeter->colors; index++) {
435 if (vumeter->f_gc[index]) {
436 g_object_unref (G_OBJECT(vumeter->f_gc[index]));
438 if (vumeter->b_gc[index]) {
439 g_object_unref (G_OBJECT(vumeter->b_gc[index]));
442 g_free(vumeter->f_gc);
443 g_free(vumeter->b_gc);
444 vumeter->f_gc = NULL;
445 vumeter->b_gc = NULL;
448 /* Free old Colors */
449 if (vumeter->f_colors) {
450 gdk_colormap_free_colors (vumeter->colormap, vumeter->f_colors, vumeter->colors);
451 g_free (vumeter->f_colors);
452 vumeter->f_colors = NULL;
454 if (vumeter->b_colors) {
455 gdk_colormap_free_colors (vumeter->colormap, vumeter->b_colors, vumeter->colors);
456 g_free (vumeter->b_colors);
457 vumeter->b_colors = NULL;
461 static void gtk_vumeter_setup_colors (GtkVUMeter *vumeter)
466 gint max = 0, min = 0, log_max = 0;
468 g_return_if_fail (vumeter->colormap != NULL);
470 gtk_vumeter_free_colors (vumeter);
473 if (vumeter->vertical == TRUE) {
474 vumeter->colors = MAX(GTK_WIDGET(vumeter)->allocation.height - vumeter->padding_top - vumeter->padding_bottom, 0);
476 vumeter->colors = MAX(GTK_WIDGET(vumeter)->allocation.width - vumeter->padding_left - vumeter->padding_right, 0);
479 /* allocate new memory */
480 vumeter->f_colors = g_malloc (vumeter->colors * sizeof(GdkColor));
481 vumeter->b_colors = g_malloc (vumeter->colors * sizeof(GdkColor));
482 vumeter->f_gc = g_malloc (vumeter->colors * sizeof(GdkGC *));
483 vumeter->b_gc = g_malloc (vumeter->colors * sizeof(GdkGC *));
485 /* Initialize stuff */
486 if (vumeter->scaling == GTK_VUMETER_SCALING_LINEAR) {
487 first = 1+gtk_vumeter_sound_level_to_draw_level (vumeter, vumeter->yellow_level);
488 second = vumeter->colors;
490 max = vumeter->level_max;
491 min = vumeter->level_min;
492 log_max = (gint) (- 20.0 * log10(1.0/(max - min + 1.0)));
493 first = (gint)((gdouble)vumeter->colors * 6.0 / log_max);
494 second = (gint)((gdouble)vumeter->colors * 18.0 / log_max);
497 if(vumeter->colors_inverted) {
498 vumeter->f_colors[0].red = 0;
499 vumeter->f_colors[0].green = vumeter->f_brightness;
500 vumeter->f_colors[0].blue = 0;
502 vumeter->b_colors[0].red = 0;
503 vumeter->b_colors[0].green = vumeter->b_brightness;
504 vumeter->b_colors[0].blue = 0;
506 /* Allocate from Green to Yellow */
507 f_step = vumeter->f_brightness / (first - 1);
508 b_step = vumeter->b_brightness / (first - 1);
509 for (index = 1; index < first; index++) {
511 vumeter->f_colors[index].red = vumeter->f_colors[index - 1].red + f_step;
512 vumeter->f_colors[index].green = vumeter->f_brightness;
513 vumeter->f_colors[index].blue = 0;
515 vumeter->b_colors[index].red = vumeter->b_colors[index - 1].red + b_step;
516 vumeter->b_colors[index].green = vumeter->b_brightness;
517 vumeter->b_colors[index].blue = 0;
519 /* Allocate from Yellow to Red */
520 if(second != first) {
521 f_step = vumeter->f_brightness / (second - first);
522 b_step = vumeter->b_brightness / (second - first);
523 for (index = first; index < second; index++) {
525 vumeter->f_colors[index].red = vumeter->f_colors[index - 1].red;
526 vumeter->f_colors[index].green = vumeter->f_colors[index - 1].green - f_step;
527 vumeter->f_colors[index].blue = 0;
529 vumeter->b_colors[index].red = vumeter->b_colors[index - 1].red;
530 vumeter->b_colors[index].green = vumeter->b_colors[index - 1].green - b_step;
531 vumeter->b_colors[index].blue = 0;
535 vumeter->f_colors[0].red = vumeter->f_brightness;
536 vumeter->f_colors[0].green = 0;
537 vumeter->f_colors[0].blue = 0;
539 vumeter->b_colors[0].red = vumeter->b_brightness;
540 vumeter->b_colors[0].green = 0;
541 vumeter->b_colors[0].blue = 0;
543 /* Allocate from Red to Yellow */
544 f_step = vumeter->f_brightness / MAX(first - 1, 1);
545 b_step = vumeter->b_brightness / MAX(first - 1, 1);
546 for (index = 1; index < first; index++) {
548 vumeter->f_colors[index].red = vumeter->f_brightness;
549 vumeter->f_colors[index].green = vumeter->f_colors[index - 1].green + f_step;
550 vumeter->f_colors[index].blue = 0;
552 vumeter->b_colors[index].red = vumeter->b_brightness;
553 vumeter->b_colors[index].green = vumeter->b_colors[index - 1].green + b_step;
554 vumeter->b_colors[index].blue = 0;
556 /* Allocate from Yellow to Green */
557 f_step = vumeter->f_brightness / MAX(second - first, 1);
558 b_step = vumeter->b_brightness / MAX(second - first, 1);
559 for (index = first; index < second; index++) {
561 vumeter->f_colors[index].red = vumeter->f_colors[index - 1].red - f_step;
562 vumeter->f_colors[index].green = vumeter->f_colors[index - 1].green;
563 vumeter->f_colors[index].blue = 0;
565 vumeter->b_colors[index].red = vumeter->b_colors[index - 1].red - b_step;
566 vumeter->b_colors[index].green = vumeter->b_colors[index - 1].green;
567 vumeter->b_colors[index].blue = 0;
569 if (vumeter->scaling == GTK_VUMETER_SCALING_LOG) {
570 /* Allocate from Green to Dark Green */
571 f_step = vumeter->f_brightness / 2 / (vumeter->colors - second);
572 b_step = vumeter->b_brightness / 2 / (vumeter->colors - second);
573 for (index = second; index < vumeter->colors; index++) {
575 vumeter->f_colors[index].red = 0;
576 vumeter->f_colors[index].green = vumeter->f_colors[index - 1].green - f_step;
577 vumeter->f_colors[index].blue = 0;
579 vumeter->b_colors[index].red = 0;
580 vumeter->b_colors[index].green = vumeter->b_colors[index - 1].green - b_step;
581 vumeter->b_colors[index].blue = 0;
584 } /* colors_inverted */
586 /* Allocate the Colours */
587 for (index = 0; index < vumeter->colors; index++) {
589 gdk_colormap_alloc_color (vumeter->colormap, &vumeter->f_colors[index], FALSE, TRUE);
590 vumeter->f_gc[index] = gdk_gc_new(GTK_WIDGET(vumeter)->window);
591 gdk_gc_set_foreground(vumeter->f_gc[index], &vumeter->f_colors[index]);
593 gdk_colormap_alloc_color (vumeter->colormap, &vumeter->b_colors[index], FALSE, TRUE);
594 vumeter->b_gc[index] = gdk_gc_new(GTK_WIDGET(vumeter)->window);
595 gdk_gc_set_foreground(vumeter->b_gc[index], &vumeter->b_colors[index]);
599 static gint gtk_vumeter_sound_level_to_draw_level (GtkVUMeter *vumeter, gint sound_level)
602 gdouble level, min, max, height;
603 gdouble log_level, log_max;
605 level = (gdouble)sound_level;
606 min = (gdouble)vumeter->level_min;
607 max = (gdouble)vumeter->level_max;
608 height = (gdouble)vumeter->colors;
610 if (vumeter->scaling == GTK_VUMETER_SCALING_LINEAR) {
611 draw_level = (1.0 - (level - min)/(max - min)) * (height - 1.0);
612 /* to avoid rounding problems */
615 log_level = log10((level - min + 1)/(max - min + 1));
616 log_max = log10(1/(max - min + 1));
617 draw_level = log_level/log_max * (height - 1.0);
620 return ((gint)draw_level);
623 static gboolean gtk_vumeter_redraw_timeout (GtkVUMeter *vumeter)
625 /* Immediately return if need be */
626 if (!GTK_WIDGET_REALIZED (GTK_WIDGET(vumeter))) { return TRUE; }
627 if (vumeter->peak == FALSE) { return TRUE; }
628 if (vumeter->peak_level == vumeter->level) { return TRUE; }
630 if(vumeter->peak_hold != 0) {
631 vumeter->peak_hold--;
635 /* Drop the peak_level by rate */
636 vumeter->peak_level -= vumeter->peak_falloff_rate;
637 vumeter->peak_level = MAX (vumeter->peak_level, vumeter->level);
639 gtk_widget_queue_draw (GTK_WIDGET(vumeter));
644 static GList *gtk_vumeter_clone_scale_items(GList *scale_items)
646 GList * new_list = NULL;
649 for ( ; scale_items != NULL; scale_items = g_list_next(scale_items)) {
650 GtkVUMeterScaleItem * item = scale_items->data;
651 GtkVUMeterScaleItem * new_item;
653 new_item = g_malloc(sizeof(GtkVUMeterScaleItem));
654 new_item->level = item->level;
655 new_item->large = item->large;
656 new_item->label = g_strdup(item->label);
657 new_list = g_list_append(new_list, new_item);
663 static void gtk_vumeter_setup_scale_items(GtkVUMeter *vumeter, GList *scale_items)
665 GList * new_list = NULL;
666 GList * new_list_item = NULL;
668 /* clone the whole list */
669 new_list = gtk_vumeter_clone_scale_items(scale_items);
671 /* clamp the levels */
672 for (new_list_item = new_list; new_list_item != NULL; new_list_item = g_list_next(new_list_item)) {
673 GtkVUMeterScaleItem * item = new_list_item->data;
675 item->level = CLAMP(item->level, vumeter->level_min, vumeter->level_max);
678 gtk_vumeter_free_scale_items(vumeter->scale_items);
679 vumeter->scale_items = new_list;
682 void gtk_vumeter_free_scale_items(GList *scale_items)
686 if(scale_items == NULL) return;
688 for (current = scale_items; current != NULL; current = g_list_next(current)) {
689 GtkVUMeterScaleItem * item = current->data;
691 if(item->label != NULL) {
692 g_free((void *) item->label);
697 g_list_free(scale_items);
701 * gtk_vumeter_set_orientation:
702 * @vumeter: the vumeter widget
703 * @orientation: the direction in which the graph is going for increasing values
705 void gtk_vumeter_set_orientation (GtkVUMeter *vumeter, GtkVUMeterOrientation orientation)
707 g_return_if_fail (GTK_IS_VUMETER (vumeter));
709 if(orientation == GTK_VUMETER_BOTTOM_TO_TOP || orientation == GTK_VUMETER_TOP_TO_BOTTOM) {
710 vumeter->vertical = TRUE;
712 vumeter->vertical = FALSE;
715 if(orientation == GTK_VUMETER_LEFT_TO_RIGHT || orientation == GTK_VUMETER_BOTTOM_TO_TOP) {
716 vumeter->scale_inverted = TRUE;
718 vumeter->scale_inverted = FALSE;
721 if (GTK_WIDGET_REALIZED (vumeter)) {
722 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
727 * gtk_vumeter_get_orientation:
728 * @vumeter: the vumeter widget
729 * @return: the direction in which the graph is going for increasing values
731 GtkVUMeterOrientation gtk_vumeter_get_orientation (GtkVUMeter *vumeter)
733 if(!GTK_IS_VUMETER (vumeter)) {
734 return GTK_VUMETER_BOTTOM_TO_TOP;
737 /* XXX - might be faster using a lookup table */
738 if(vumeter->vertical == TRUE && vumeter->scale_inverted == TRUE) {
739 return GTK_VUMETER_BOTTOM_TO_TOP;
742 if(vumeter->vertical == TRUE && vumeter->scale_inverted == FALSE) {
743 return GTK_VUMETER_TOP_TO_BOTTOM;
746 if(vumeter->vertical == FALSE && vumeter->scale_inverted == TRUE) {
747 return GTK_VUMETER_LEFT_TO_RIGHT;
750 if(vumeter->vertical == FALSE && vumeter->scale_inverted == FALSE) {
751 return GTK_VUMETER_LEFT_TO_RIGHT;
754 g_assert_not_reached();
755 return GTK_VUMETER_BOTTOM_TO_TOP;
759 * gtk_vumeter_set_thickness:
760 * @vumeter: the vumeter widget
761 * @thickness: gtkvumeter's minimum graph thickness in pixels (default:30)
763 * Allows the user program to change the dimension of the vumeter.
764 * For a vertical meter, this is the width.
765 * Likewise for a horizontal meter, this is the height.
767 void gtk_vumeter_set_thickness (GtkVUMeter *vumeter, gint thickness)
769 g_return_if_fail (GTK_IS_VUMETER (vumeter));
771 if (vumeter->thickness != thickness) {
772 vumeter->thickness = thickness;
773 vumeter->reduced_thickness = MIN(vumeter->reduced_thickness, vumeter->thickness);
774 gtk_widget_queue_resize (GTK_WIDGET (vumeter));
779 * gtk_vumeter_get_thickness:
780 * @vumeter: the vumeter widget
781 * @return: gtkvumeter's minimum graph thickness in pixels (default:30)
783 * For a vertical meter, this is the width.
784 * Likewise for a horizontal meter, this is the height.
786 gint gtk_vumeter_get_thickness (GtkVUMeter *vumeter)
788 if(!GTK_IS_VUMETER (vumeter)) {
791 return vumeter->thickness;
796 * gtk_vumeter_set_thickness_reduction:
797 * @vumeter: the vumeter widget
798 * @reduced_thickness: pixels to reduce the "none active" part of the graph (default:0)
800 * Allows the user program to reduce the thickness of the "background" part of the vumeter graph.
801 * This can be useful to distinguish the border between the foreground and background graph.
803 void gtk_vumeter_set_thickness_reduction (GtkVUMeter *vumeter, gint reduced_thickness)
805 g_return_if_fail (GTK_IS_VUMETER (vumeter));
807 if (vumeter->reduced_thickness != reduced_thickness) {
808 vumeter->reduced_thickness = reduced_thickness;
809 vumeter->reduced_thickness = CLAMP(vumeter->reduced_thickness, 0, vumeter->thickness/2);
810 gtk_widget_queue_resize (GTK_WIDGET (vumeter));
815 * gtk_vumeter_get_thickness_reduction:
816 * @vumeter: the vumeter widget
817 * @return: pixels to reduce the "none active" part of the graph (default:0)
819 * The reduced thickness of the "background" part of the vumeter graph.
821 gint gtk_vumeter_get_thickness_reduction (GtkVUMeter *vumeter)
823 if(!GTK_IS_VUMETER (vumeter)) {
826 return vumeter->reduced_thickness;
831 * gtk_vumeter_set_min_max:
832 * @vumeter: the vumeter widget
833 * @min: the new minimum level shown (default: 0)
834 * @max: the new maximum level shown (default: 32768)
836 * Sets the minimum and maximum of the VU Meters scale.
837 * It will increment max by one if min == max.
838 * And finally it will clamp the relevant levels into the min, max range.
839 * Either value can be NULL, to keep the current value.
841 * Don't forget to call %gtk_vumeter_set_yellow_level() if required!
843 * WARNING: negative values for min or max will currently not work!!!
845 void gtk_vumeter_set_min_max (GtkVUMeter *vumeter, gint *min, gint *max)
849 g_return_if_fail (GTK_IS_VUMETER (vumeter));
851 /* Allow min or max to be NULL */
852 mi = (min != NULL) ? *min : vumeter->level_min;
853 ma = (max != NULL) ? *max : vumeter->level_max;
855 /* Ensure that max > min */
856 vumeter->level_max = MAX(ma, mi);
857 vumeter->level_min = MIN(mi, ma);
858 if (vumeter->level_max == vumeter->level_min) {
859 /* Increment max so we have a range */
860 vumeter->level_max++;
862 /* Clamp the levels to the new range */
863 vumeter->level = CLAMP (vumeter->level, vumeter->level_min, vumeter->level_max);
864 vumeter->peak_level = CLAMP (vumeter->peak_level, vumeter->level, vumeter->level_max);
865 vumeter->yellow_level = CLAMP (vumeter->yellow_level, vumeter->level_min, vumeter->level_max);
867 gtk_widget_queue_draw (GTK_WIDGET(vumeter));
871 * gtk_vumeter_get_min_max:
872 * @vumeter: the vumeter widget
873 * @min: the new minimum level shown (default: 0)
874 * @max: the new maximum level shown (default: 32768)
876 * The minimum and maximum of the VU Meters scale.
878 void gtk_vumeter_get_min_max (GtkVUMeter *vumeter, gint *min, gint *max)
880 if(!GTK_IS_VUMETER (vumeter)) {
884 *min = vumeter->level_min;
885 *max = vumeter->level_max;
890 * gtk_vumeter_set_level:
891 * @vumeter: the vumeter widget
892 * @level: the new level shown (default: 0)
894 * Sets new level value for the vumeter.
895 * The level is clamped to the min max range.
896 * The peak_level will be increased to level if needed.
898 void gtk_vumeter_set_level (GtkVUMeter *vumeter, gint level)
900 g_return_if_fail (GTK_IS_VUMETER (vumeter));
902 if (vumeter->level != level) {
903 vumeter->level = CLAMP (level, vumeter->level_min, vumeter->level_max);
904 if(vumeter->level > vumeter->peak_level) {
905 vumeter->peak_hold = vumeter->peak_hold_factor;
906 vumeter->peak_level = vumeter->level;
908 gtk_widget_queue_draw (GTK_WIDGET(vumeter));
913 * gtk_vumeter_get_level:
914 * @vumeter: the vumeter widget
915 * @return: the level shown (default: 0)
917 * Gets the level value of the vumeter.
919 gint gtk_vumeter_get_level (GtkVUMeter *vumeter)
921 if(!GTK_IS_VUMETER (vumeter)) {
924 return vumeter->level;
929 * gtk_vumeter_set_scaling:
930 * @vumeter: the vumeter widget
931 * @scale: the scaling mode either GTK_VUMETER_SCALING_LINEAR or GTK_VUMETER_SCALING_LOG
933 * Sets the scaling mode of the VU Meter.
934 * It is either log or linear and defaults to linear.
935 * No matter which scale you set the input should always be linear, gtkVUMeter
936 * does the log calculation/display.
938 void gtk_vumeter_set_scaling (GtkVUMeter *vumeter, GtkVUMeterScaling scaling)
940 g_return_if_fail (GTK_IS_VUMETER (vumeter));
942 if (scaling != vumeter->scaling) {
943 vumeter->scaling = CLAMP (scaling, GTK_VUMETER_SCALING_LINEAR, GTK_VUMETER_SCALING_LOG);
944 if (GTK_WIDGET_REALIZED (vumeter)) {
945 gtk_vumeter_setup_colors (vumeter);
946 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
952 * gtk_vumeter_get_scaling:
953 * @vumeter: the vumeter widget
954 * @return: the scaling mode either GTK_VUMETER_SCALING_LINEAR or GTK_VUMETER_SCALING_LOG
956 * Gets the scaling mode of the VU Meter.
957 * It is either log or linear and defaults to linear.
959 GtkVUMeterScaling gtk_vumeter_get_scaling (GtkVUMeter *vumeter)
961 if(!GTK_IS_VUMETER (vumeter)) {
964 return vumeter->scaling;
969 * gtk_vumeter_set_scale_items:
970 * @vumeter: the vumeter widget
971 * @scale_items: a GList of the pitch lines and labels (default:NULL)
973 * Set the scale pitch lines and labels.
974 * Must be NULL or a GList containing filled %GtkVUMeterScaleItem items.
975 * Function will make a deep copy of the GList and it's items,
976 * so the given GList and it's items can be safely thrown away after the call.
977 * A side effect: This also sets the minimum size of the widget.
979 void gtk_vumeter_set_scale_items(GtkVUMeter *vumeter, GList *scale_items)
981 g_return_if_fail (GTK_IS_VUMETER (vumeter));
983 gtk_vumeter_setup_scale_items(vumeter, scale_items);
985 if (GTK_WIDGET_REALIZED (vumeter)) {
986 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
991 * gtk_vumeter_get_scale_items:
992 * @vumeter: the vumeter widget
993 * @return: a GList of the pitch lines and labels (default:NULL)
995 * Get the scale pitch lines and labels, a GList containing %GtkVUMeterScaleItem items.
996 * The returned GList must be freed with gtk_vumeter_free_scale_items() by the user!!!
998 GList *gtk_vumeter_get_scale_items(GtkVUMeter *vumeter)
1000 if(!GTK_IS_VUMETER (vumeter)) {
1003 return gtk_vumeter_clone_scale_items(vumeter->scale_items);
1008 * gtk_vumeter_set_scale_hole_size:
1009 * @vumeter: the vumeter widget
1010 * @hole_size: (default:0)
1012 * Set the size of the "holes" between the pitch lines.
1013 * A side effect: This also sets the minimum size of the widget.
1015 void gtk_vumeter_set_scale_hole_size (GtkVUMeter *vumeter, gint hole_size)
1017 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1019 if (vumeter->scale_pitch_holes != hole_size) {
1020 vumeter->scale_pitch_holes = hole_size;
1021 gtk_widget_queue_resize (GTK_WIDGET (vumeter));
1026 * gtk_vumeter_get_scale_hole_size:
1027 * @vumeter: the vumeter widget
1028 * @return: (default:0)
1030 * Get the size of the "holes" between the pitch lines.
1032 gint gtk_vumeter_get_scale_hole_size (GtkVUMeter *vumeter)
1034 if(!GTK_IS_VUMETER (vumeter)) {
1037 return vumeter->scale_pitch_holes;
1042 * gtk_vumeter_set_peak:
1043 * @vumeter: the vumeter widget
1044 * @peak: whether or not the peak indicator is drawn
1045 * @redraw_rate: the rate (in milliseconds) at which the peak indicator is redrawn
1047 * Enables/Disables the peak meachanism and sets the redraw timeout to redraw_rate milliseconds.
1048 * The redraw operation is intelligent in that the widget is only redrawn
1049 * if the peak_level != level and peak == %TRUE.
1051 * Hint: A good redraw_rate is 200ms (default: 0ms -> off)
1053 void gtk_vumeter_set_peak (GtkVUMeter *vumeter, gboolean peak, guint redraw_rate)
1055 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1057 if (vumeter->peak != peak) {
1058 vumeter->peak = peak;
1059 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1062 vumeter->peak_redraw_rate = redraw_rate;
1064 if(vumeter->peak_timeout) {
1065 gtk_timeout_remove(vumeter->peak_timeout);
1068 if(redraw_rate != 0 && vumeter->peak) {
1069 vumeter->peak_timeout = gtk_timeout_add (redraw_rate, (GtkFunction)gtk_vumeter_redraw_timeout, vumeter);
1074 * gtk_vumeter_get_peak:
1075 * @vumeter: the vumeter widget
1076 * @peak: whether or not the peak indicator is drawn
1077 * @redraw_rate: the rate (in milliseconds) at which the peak indicator is redrawn
1080 void gtk_vumeter_get_peak (GtkVUMeter *vumeter, gboolean *peak, guint *redraw_rate)
1082 if(!GTK_IS_VUMETER (vumeter)) {
1086 *peak = vumeter->peak;
1087 *redraw_rate = vumeter->peak_redraw_rate;
1092 * gtk_vumeter_set_peak_hold_factor:
1093 * @vumeter: the vumeter widget
1094 * @hold_factor: number of redraw_rates to wait until peak indicator is decayed (default:0 -> off)
1096 * Holds the peak indicator for a limited time at it's highest position.
1097 * The actual rate is dependent on the redraw_rate given to %gtk_vumeter_set_peak().
1099 * Hint: For a VU meter, a good hold_factor is 7 with a redraw_rate of 200ms.
1101 void gtk_vumeter_set_peak_hold_factor (GtkVUMeter *vumeter, gint hold_factor)
1103 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1105 if (vumeter->peak_hold_factor != hold_factor) {
1106 vumeter->peak_hold_factor = hold_factor;
1111 * gtk_vumeter_get_peak_hold_factor:
1112 * @vumeter: the vumeter widget
1113 * @return: number of redraw_rates to wait until peak indicator is decayed (default:0 -> off)
1115 gint gtk_vumeter_get_peak_hold_factor (GtkVUMeter *vumeter)
1117 if(!GTK_IS_VUMETER (vumeter)) {
1120 return vumeter->peak_hold_factor;
1125 * gtk_vumeter_set_peak_falloff:
1126 * @vumeter: the vumeter widget
1127 * @peak_falloff: controls the speed to the peak decay
1128 * @user_rate: pixels to reduce the peak level at each redraw_rate in GTK_VUMETER_PEAK_FALLOFF_USER mode, otherwise ignored
1130 * Set the numbers of pixel reduced from the peak indicator each redraw_rate (after the hold period is over).
1131 * The peak_falloff will be around: SLOW:5%, MEDIUM:10%, FAST:20%, USER:user_rate
1132 * of the current range, reduced from peak at each redraw_rate (%gtk_vumeter_set_peak()).
1134 * Hint: a user_rate of 0 can be used to hold the peak indicator at the highest position ever.
1136 void gtk_vumeter_set_peak_falloff (GtkVUMeter *vumeter, GtkVUMeterPeakFalloff peak_falloff, guint user_rate)
1139 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1141 vumeter->peak_falloff_mode = CLAMP(peak_falloff, GTK_VUMETER_PEAK_FALLOFF_SLOW, GTK_VUMETER_PEAK_FALLOFF_USER);
1142 range = vumeter->level_max - vumeter->level_min;
1144 switch (peak_falloff) {
1145 case GTK_VUMETER_PEAK_FALLOFF_SLOW:
1146 vumeter->peak_falloff_rate = range/20;
1149 case GTK_VUMETER_PEAK_FALLOFF_MEDIUM:
1150 vumeter->peak_falloff_rate = range/10;
1152 case GTK_VUMETER_PEAK_FALLOFF_FAST:
1153 vumeter->peak_falloff_rate = range/5;
1155 case GTK_VUMETER_PEAK_FALLOFF_USER:
1156 vumeter->peak_falloff_rate = (gint)user_rate;
1162 * gtk_vumeter_get_peak_falloff:
1163 * @vumeter: the vumeter widget
1164 * @peak_falloff: controls the speed to the peak decay
1165 * @user_rate: pixels to lower the peak level each redraw_rate (value valid in every peak_falloff mode)
1167 void gtk_vumeter_get_peak_falloff (GtkVUMeter *vumeter, GtkVUMeterPeakFalloff *peak_falloff, guint *user_rate)
1169 if(!GTK_IS_VUMETER (vumeter)) {
1173 *peak_falloff = vumeter->peak_falloff_mode;
1174 *user_rate = vumeter->peak_falloff_rate;
1179 * gtk_vumeter_set_colors_inverted:
1180 * @vumeter: the vumeter widget
1181 * @inverted: whether or not the colors are inverted (default:%FALSE)
1183 * Usually the graph will be colored with: 0:green, half:yellow, full:red.
1184 * This is used to display signals that won't "work correct" above a maximum level
1185 * (e.g. audio signals may distort if their amplitude is too high).
1187 * The inverted colors will be: 0:red, half:yellow, full:green.
1188 * This is used to display signals that need a minimum level to work correct
1189 * (e.g. a received antenna signal must have a minimum amplitude to "work correct").
1191 void gtk_vumeter_set_colors_inverted (GtkVUMeter *vumeter, gboolean inverted)
1193 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1195 vumeter->colors_inverted = inverted;
1196 if (GTK_WIDGET_REALIZED (vumeter)) {
1197 gtk_vumeter_setup_colors (vumeter);
1198 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1203 * gtk_vumeter_get_colors_inverted:
1204 * @vumeter: the vumeter widget
1205 * @return: whether or not the colors are inverted (default:%FALSE)
1207 gboolean gtk_vumeter_get_colors_inverted (GtkVUMeter *vumeter)
1209 if(!GTK_IS_VUMETER (vumeter)) {
1212 return vumeter->colors_inverted;
1217 * gtk_vumeter_set_yellow_level:
1218 * @vumeter: the vumeter widget
1219 * @yellow_level: set the position of the yellow area (default:16383)
1221 * Will be clamped between min and max.
1223 void gtk_vumeter_set_yellow_level (GtkVUMeter *vumeter, gint yellow_level)
1225 g_return_if_fail (GTK_IS_VUMETER (vumeter));
1227 vumeter->yellow_level = CLAMP (yellow_level, vumeter->level_min, vumeter->level_max);
1228 if (GTK_WIDGET_REALIZED (vumeter)) {
1229 gtk_vumeter_setup_colors (vumeter);
1230 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1235 * gtk_vumeter_get_yellow_level:
1236 * @vumeter: the vumeter widget
1237 * @return: get the position of the yellow area (default:16383)
1239 gint gtk_vumeter_get_yellow_level (GtkVUMeter *vumeter)
1241 if(!GTK_IS_VUMETER (vumeter)) {
1244 return vumeter->yellow_level;
1249 * gtk_vumeter_set_brightness:
1250 * @vumeter: the vumeter widget
1251 * @foreground: set the brightness of the graphs foreground (default:65535)
1252 * @background: set the brightness of the graphs background (default:49151)
1254 * Hint: don't turn the brightness too low, otherwise you'll only see a black bar :-)
1256 void gtk_vumeter_set_brightness (GtkVUMeter *vumeter, gint foreground, gint background)
1258 vumeter->f_brightness = CLAMP(foreground, 0, 65535);
1259 vumeter->b_brightness = CLAMP(background, 0, vumeter->f_brightness);
1260 if (GTK_WIDGET_REALIZED (vumeter)) {
1261 gtk_vumeter_setup_colors (vumeter);
1262 gtk_widget_queue_draw (GTK_WIDGET (vumeter));
1267 * gtk_vumeter_get_brightness:
1268 * @vumeter: the vumeter widget
1269 * @foreground: get the brightness of the graphs foreground (default:65535)
1270 * @background: get the brightness of the graphs background (default:49151)
1272 void gtk_vumeter_get_brightness (GtkVUMeter *vumeter, gint *foreground, gint *background)
1274 if(!GTK_IS_VUMETER (vumeter)) {
1278 *foreground = vumeter->f_brightness;
1279 *background = vumeter->b_brightness;