1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3
* Copyright (C) 2008 William Jon McCann <william.jon.mccann@gmail.com>
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29
#include <glib/gi18n-lib.h>
32
#include "gvc-level-bar.h"
36
#define GVC_LEVEL_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_LEVEL_BAR, GvcLevelBarPrivate))
38
#define MIN_HORIZONTAL_BAR_WIDTH 150
39
#define HORIZONTAL_BAR_HEIGHT 6
40
#define VERTICAL_BAR_WIDTH 6
41
#define MIN_VERTICAL_BAR_HEIGHT 400
63
struct GvcLevelBarPrivate
65
GtkOrientation orientation;
66
GtkAdjustment *peak_adjustment;
67
GtkAdjustment *rms_adjustment;
69
gdouble peak_fraction;
73
LevelBarLayout layout;
85
static void gvc_level_bar_class_init (GvcLevelBarClass *klass);
86
static void gvc_level_bar_init (GvcLevelBar *level_bar);
87
static void gvc_level_bar_finalize (GObject *object);
89
G_DEFINE_TYPE (GvcLevelBar, gvc_level_bar, GTK_TYPE_WIDGET)
91
#define check_rectangle(rectangle1, rectangle2) \
93
/* .x and .y are always 0 */ \
94
if (rectangle1.width != rectangle2.width) return TRUE; \
95
if (rectangle1.height != rectangle2.height) return TRUE; \
99
layout_changed (LevelBarLayout *layout1,
100
LevelBarLayout *layout2)
102
check_rectangle (layout1->area, layout2->area);
103
if (layout1->delta != layout2->delta) return TRUE;
104
if (layout1->peak_num != layout2->peak_num) return TRUE;
105
if (layout1->max_peak_num != layout2->max_peak_num) return TRUE;
106
if (layout1->bg_r != layout2->bg_r
107
|| layout1->bg_g != layout2->bg_g
108
|| layout1->bg_b != layout2->bg_b)
110
if (layout1->bdr_r != layout2->bdr_r
111
|| layout1->bdr_g != layout2->bdr_g
112
|| layout1->bdr_b != layout2->bdr_b)
114
if (layout1->fl_r != layout2->fl_r
115
|| layout1->fl_g != layout2->fl_g
116
|| layout1->fl_b != layout2->fl_b)
123
fraction_from_adjustment (GvcLevelBar *bar,
124
GtkAdjustment *adjustment)
131
level = gtk_adjustment_get_value (adjustment);
133
min = gtk_adjustment_get_lower (adjustment);
134
max = gtk_adjustment_get_upper (adjustment);
136
switch (bar->priv->scale) {
137
case GVC_LEVEL_SCALE_LINEAR:
138
fraction = (level - min) / (max - min);
140
case GVC_LEVEL_SCALE_LOG:
141
fraction = log10 ((level - min + 1) / (max - min + 1));
144
g_assert_not_reached ();
151
reset_max_peak (GvcLevelBar *bar)
155
min = gtk_adjustment_get_lower (bar->priv->peak_adjustment);
156
bar->priv->max_peak = min;
157
bar->priv->layout.max_peak_num = 0;
158
gtk_widget_queue_draw (GTK_WIDGET (bar));
159
bar->priv->max_peak_id = 0;
164
bar_calc_layout (GvcLevelBar *bar)
169
GtkAllocation allocation;
172
gtk_widget_get_allocation (GTK_WIDGET (bar), &allocation);
173
bar->priv->layout.area.width = allocation.width - 2;
174
bar->priv->layout.area.height = allocation.height - 2;
176
style = gtk_widget_get_style (GTK_WIDGET (bar));
177
color = style->bg [GTK_STATE_NORMAL];
178
bar->priv->layout.bg_r = (float)color.red / 65535.0;
179
bar->priv->layout.bg_g = (float)color.green / 65535.0;
180
bar->priv->layout.bg_b = (float)color.blue / 65535.0;
181
color = style->dark [GTK_STATE_NORMAL];
182
bar->priv->layout.bdr_r = (float)color.red / 65535.0;
183
bar->priv->layout.bdr_g = (float)color.green / 65535.0;
184
bar->priv->layout.bdr_b = (float)color.blue / 65535.0;
185
color = style->bg [GTK_STATE_SELECTED];
186
bar->priv->layout.fl_r = (float)color.red / 65535.0;
187
bar->priv->layout.fl_g = (float)color.green / 65535.0;
188
bar->priv->layout.fl_b = (float)color.blue / 65535.0;
190
if (bar->priv->orientation == GTK_ORIENTATION_VERTICAL) {
191
peak_level = bar->priv->peak_fraction * bar->priv->layout.area.height;
192
max_peak_level = bar->priv->max_peak * bar->priv->layout.area.height;
194
bar->priv->layout.delta = bar->priv->layout.area.height / NUM_BOXES;
195
bar->priv->layout.area.x = 0;
196
bar->priv->layout.area.y = 0;
197
bar->priv->layout.box_height = bar->priv->layout.delta / 2;
198
bar->priv->layout.box_width = bar->priv->layout.area.width;
199
bar->priv->layout.box_radius = bar->priv->layout.box_width / 2;
201
peak_level = bar->priv->peak_fraction * bar->priv->layout.area.width;
202
max_peak_level = bar->priv->max_peak * bar->priv->layout.area.width;
204
bar->priv->layout.delta = bar->priv->layout.area.width / NUM_BOXES;
205
bar->priv->layout.area.x = 0;
206
bar->priv->layout.area.y = 0;
207
bar->priv->layout.box_width = bar->priv->layout.delta / 2;
208
bar->priv->layout.box_height = bar->priv->layout.area.height;
209
bar->priv->layout.box_radius = bar->priv->layout.box_height / 2;
212
/* This can happen if the level bar isn't realized */
213
if (bar->priv->layout.delta == 0)
216
bar->priv->layout.peak_num = peak_level / bar->priv->layout.delta;
217
bar->priv->layout.max_peak_num = max_peak_level / bar->priv->layout.delta;
221
update_peak_value (GvcLevelBar *bar)
224
LevelBarLayout layout;
226
layout = bar->priv->layout;
228
val = fraction_from_adjustment (bar, bar->priv->peak_adjustment);
229
bar->priv->peak_fraction = val;
231
if (val > bar->priv->max_peak) {
232
if (bar->priv->max_peak_id > 0) {
233
g_source_remove (bar->priv->max_peak_id);
235
bar->priv->max_peak_id = g_timeout_add_seconds (1, (GSourceFunc)reset_max_peak, bar);
236
bar->priv->max_peak = val;
239
bar_calc_layout (bar);
241
if (layout_changed (&bar->priv->layout, &layout)) {
242
gtk_widget_queue_draw (GTK_WIDGET (bar));
247
update_rms_value (GvcLevelBar *bar)
251
val = fraction_from_adjustment (bar, bar->priv->rms_adjustment);
252
bar->priv->rms_fraction = val;
256
gvc_level_bar_get_orientation (GvcLevelBar *bar)
258
g_return_val_if_fail (GVC_IS_LEVEL_BAR (bar), 0);
259
return bar->priv->orientation;
263
gvc_level_bar_set_orientation (GvcLevelBar *bar,
264
GtkOrientation orientation)
266
g_return_if_fail (GVC_IS_LEVEL_BAR (bar));
268
if (orientation != bar->priv->orientation) {
269
bar->priv->orientation = orientation;
270
gtk_widget_queue_draw (GTK_WIDGET (bar));
271
g_object_notify (G_OBJECT (bar), "orientation");
276
on_peak_adjustment_value_changed (GtkAdjustment *adjustment,
279
update_peak_value (bar);
283
on_rms_adjustment_value_changed (GtkAdjustment *adjustment,
286
update_rms_value (bar);
290
gvc_level_bar_set_peak_adjustment (GvcLevelBar *bar,
291
GtkAdjustment *adjustment)
293
g_return_if_fail (GVC_LEVEL_BAR (bar));
294
g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
296
if (bar->priv->peak_adjustment != NULL) {
297
g_signal_handlers_disconnect_by_func (bar->priv->peak_adjustment,
298
G_CALLBACK (on_peak_adjustment_value_changed),
300
g_object_unref (bar->priv->peak_adjustment);
303
bar->priv->peak_adjustment = g_object_ref_sink (adjustment);
305
g_signal_connect (bar->priv->peak_adjustment,
307
G_CALLBACK (on_peak_adjustment_value_changed),
310
update_peak_value (bar);
312
g_object_notify (G_OBJECT (bar), "peak-adjustment");
316
gvc_level_bar_set_rms_adjustment (GvcLevelBar *bar,
317
GtkAdjustment *adjustment)
319
g_return_if_fail (GVC_LEVEL_BAR (bar));
320
g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
322
if (bar->priv->rms_adjustment != NULL) {
323
g_signal_handlers_disconnect_by_func (bar->priv->peak_adjustment,
324
G_CALLBACK (on_rms_adjustment_value_changed),
326
g_object_unref (bar->priv->rms_adjustment);
329
bar->priv->rms_adjustment = g_object_ref_sink (adjustment);
332
g_signal_connect (bar->priv->peak_adjustment,
334
G_CALLBACK (on_peak_adjustment_value_changed),
337
update_rms_value (bar);
339
g_object_notify (G_OBJECT (bar), "rms-adjustment");
343
gvc_level_bar_get_peak_adjustment (GvcLevelBar *bar)
345
g_return_val_if_fail (GVC_IS_LEVEL_BAR (bar), NULL);
347
return bar->priv->peak_adjustment;
351
gvc_level_bar_get_rms_adjustment (GvcLevelBar *bar)
353
g_return_val_if_fail (GVC_IS_LEVEL_BAR (bar), NULL);
355
return bar->priv->rms_adjustment;
359
gvc_level_bar_set_scale (GvcLevelBar *bar,
362
g_return_if_fail (GVC_IS_LEVEL_BAR (bar));
364
if (scale != bar->priv->scale) {
365
bar->priv->scale = scale;
367
update_peak_value (bar);
368
update_rms_value (bar);
370
g_object_notify (G_OBJECT (bar), "scale");
375
gvc_level_bar_set_property (GObject *object,
380
GvcLevelBar *self = GVC_LEVEL_BAR (object);
384
gvc_level_bar_set_scale (self, g_value_get_int (value));
386
case PROP_ORIENTATION:
387
gvc_level_bar_set_orientation (self, g_value_get_enum (value));
389
case PROP_PEAK_ADJUSTMENT:
390
gvc_level_bar_set_peak_adjustment (self, g_value_get_object (value));
392
case PROP_RMS_ADJUSTMENT:
393
gvc_level_bar_set_rms_adjustment (self, g_value_get_object (value));
396
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
402
gvc_level_bar_get_property (GObject *object,
407
GvcLevelBar *self = GVC_LEVEL_BAR (object);
411
g_value_set_int (value, self->priv->scale);
413
case PROP_ORIENTATION:
414
g_value_set_enum (value, self->priv->orientation);
416
case PROP_PEAK_ADJUSTMENT:
417
g_value_set_object (value, self->priv->peak_adjustment);
419
case PROP_RMS_ADJUSTMENT:
420
g_value_set_object (value, self->priv->rms_adjustment);
423
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
429
gvc_level_bar_constructor (GType type,
430
guint n_construct_properties,
431
GObjectConstructParam *construct_params)
433
return G_OBJECT_CLASS (gvc_level_bar_parent_class)->constructor (type, n_construct_properties, construct_params);
437
gvc_level_bar_size_request (GtkWidget *widget,
438
GtkRequisition *requisition)
440
GvcLevelBar *bar = GVC_LEVEL_BAR (widget);
442
switch (bar->priv->orientation) {
443
case GTK_ORIENTATION_VERTICAL:
444
requisition->width = VERTICAL_BAR_WIDTH;
445
requisition->height = MIN_VERTICAL_BAR_HEIGHT;
447
case GTK_ORIENTATION_HORIZONTAL:
448
requisition->width = MIN_HORIZONTAL_BAR_WIDTH;
449
requisition->height = HORIZONTAL_BAR_HEIGHT;
452
g_assert_not_reached ();
458
gvc_level_bar_get_preferred_width (GtkWidget *widget,
462
GtkRequisition requisition;
464
gvc_level_bar_size_request (widget, &requisition);
466
if (minimum != NULL) {
467
*minimum = requisition.width;
469
if (natural != NULL) {
470
*natural = requisition.width;
475
gvc_level_bar_get_preferred_height (GtkWidget *widget,
479
GtkRequisition requisition;
481
gvc_level_bar_size_request (widget, &requisition);
483
if (minimum != NULL) {
484
*minimum = requisition.height;
486
if (natural != NULL) {
487
*natural = requisition.height;
492
gvc_level_bar_size_allocate (GtkWidget *widget,
493
GtkAllocation *allocation)
497
g_return_if_fail (GVC_IS_LEVEL_BAR (widget));
498
g_return_if_fail (allocation != NULL);
500
bar = GVC_LEVEL_BAR (widget);
502
/* FIXME: add height property, labels, etc */
503
GTK_WIDGET_CLASS (gvc_level_bar_parent_class)->size_allocate (widget, allocation);
505
gtk_widget_set_allocation (widget, allocation);
506
gtk_widget_get_allocation (widget, allocation);
508
if (bar->priv->orientation == GTK_ORIENTATION_VERTICAL) {
509
allocation->height = MIN (allocation->height, MIN_VERTICAL_BAR_HEIGHT);
510
allocation->width = MAX (allocation->width, VERTICAL_BAR_WIDTH);
512
allocation->width = MIN (allocation->width, MIN_HORIZONTAL_BAR_WIDTH);
513
allocation->height = MAX (allocation->height, HORIZONTAL_BAR_HEIGHT);
516
bar_calc_layout (bar);
520
curved_rectangle (cairo_t *cr,
533
if (!width || !height) {
537
if (width / 2 < radius) {
538
if (height / 2 < radius) {
539
cairo_move_to (cr, x0, (y0 + y1) / 2);
540
cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
541
cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
542
cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
543
cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
545
cairo_move_to (cr, x0, y0 + radius);
546
cairo_curve_to (cr, x0, y0, x0, y0, (x0 + x1) / 2, y0);
547
cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
548
cairo_line_to (cr, x1, y1 - radius);
549
cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
550
cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius);
553
if (height / 2 < radius) {
554
cairo_move_to (cr, x0, (y0 + y1) / 2);
555
cairo_curve_to (cr, x0, y0, x0 , y0, x0 + radius, y0);
556
cairo_line_to (cr, x1 - radius, y0);
557
cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
558
cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
559
cairo_line_to (cr, x0 + radius, y1);
560
cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
562
cairo_move_to (cr, x0, y0 + radius);
563
cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
564
cairo_line_to (cr, x1 - radius, y0);
565
cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
566
cairo_line_to (cr, x1, y1 - radius);
567
cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
568
cairo_line_to (cr, x0 + radius, y1);
569
cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius);
573
cairo_close_path (cr);
577
gvc_level_bar_draw (GtkWidget *widget,
582
g_return_val_if_fail (GVC_IS_LEVEL_BAR (widget), FALSE);
584
bar = GVC_LEVEL_BAR (widget);
586
if (bar->priv->orientation == GTK_ORIENTATION_VERTICAL) {
590
for (i = 0; i < NUM_BOXES; i++) {
591
by = i * bar->priv->layout.delta;
592
curved_rectangle (cr,
593
bar->priv->layout.area.x + 0.5,
595
bar->priv->layout.box_width - 1,
596
bar->priv->layout.box_height - 1,
597
bar->priv->layout.box_radius);
598
if ((bar->priv->layout.max_peak_num - 1) == i) {
599
/* fill peak foreground */
600
cairo_set_source_rgb (cr, bar->priv->layout.fl_r, bar->priv->layout.fl_g, bar->priv->layout.fl_b);
601
cairo_fill_preserve (cr);
602
} else if ((bar->priv->layout.peak_num - 1) >= i) {
603
/* fill background */
604
cairo_set_source_rgb (cr, bar->priv->layout.bg_r, bar->priv->layout.bg_g, bar->priv->layout.bg_b);
605
cairo_fill_preserve (cr);
606
/* fill foreground */
607
cairo_set_source_rgba (cr, bar->priv->layout.fl_r, bar->priv->layout.fl_g, bar->priv->layout.fl_b, 0.5);
608
cairo_fill_preserve (cr);
610
/* fill background */
611
cairo_set_source_rgb (cr, bar->priv->layout.bg_r, bar->priv->layout.bg_g, bar->priv->layout.bg_b);
612
cairo_fill_preserve (cr);
616
cairo_set_source_rgb (cr, bar->priv->layout.bdr_r, bar->priv->layout.bdr_g, bar->priv->layout.bdr_b);
617
cairo_set_line_width (cr, 1);
625
for (i = 0; i < NUM_BOXES; i++) {
626
bx = i * bar->priv->layout.delta;
627
curved_rectangle (cr,
629
bar->priv->layout.area.y + 0.5,
630
bar->priv->layout.box_width - 1,
631
bar->priv->layout.box_height - 1,
632
bar->priv->layout.box_radius);
634
if ((bar->priv->layout.max_peak_num - 1) == i) {
635
/* fill peak foreground */
636
cairo_set_source_rgb (cr, bar->priv->layout.fl_r, bar->priv->layout.fl_g, bar->priv->layout.fl_b);
637
cairo_fill_preserve (cr);
638
} else if ((bar->priv->layout.peak_num - 1) >= i) {
639
/* fill background */
640
cairo_set_source_rgb (cr, bar->priv->layout.bg_r, bar->priv->layout.bg_g, bar->priv->layout.bg_b);
641
cairo_fill_preserve (cr);
642
/* fill foreground */
643
cairo_set_source_rgba (cr, bar->priv->layout.fl_r, bar->priv->layout.fl_g, bar->priv->layout.fl_b, 0.5);
644
cairo_fill_preserve (cr);
646
/* fill background */
647
cairo_set_source_rgb (cr, bar->priv->layout.bg_r, bar->priv->layout.bg_g, bar->priv->layout.bg_b);
648
cairo_fill_preserve (cr);
652
cairo_set_source_rgb (cr, bar->priv->layout.bdr_r, bar->priv->layout.bdr_g, bar->priv->layout.bdr_b);
653
cairo_set_line_width (cr, 1);
662
gvc_level_bar_class_init (GvcLevelBarClass *klass)
664
GObjectClass *object_class = G_OBJECT_CLASS (klass);
665
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
667
object_class->constructor = gvc_level_bar_constructor;
668
object_class->finalize = gvc_level_bar_finalize;
669
object_class->set_property = gvc_level_bar_set_property;
670
object_class->get_property = gvc_level_bar_get_property;
672
widget_class->draw = gvc_level_bar_draw;
673
widget_class->get_preferred_width = gvc_level_bar_get_preferred_width;
674
widget_class->get_preferred_height = gvc_level_bar_get_preferred_height;
675
widget_class->size_allocate = gvc_level_bar_size_allocate;
677
g_object_class_install_property (object_class,
679
g_param_spec_enum ("orientation",
681
"The orientation of the bar",
682
GTK_TYPE_ORIENTATION,
683
GTK_ORIENTATION_HORIZONTAL,
685
g_object_class_install_property (object_class,
686
PROP_PEAK_ADJUSTMENT,
687
g_param_spec_object ("peak-adjustment",
689
"The GtkAdjustment that contains the current peak value",
692
g_object_class_install_property (object_class,
694
g_param_spec_object ("rms-adjustment",
696
"The GtkAdjustment that contains the current rms value",
699
g_object_class_install_property (object_class,
701
g_param_spec_int ("scale",
706
GVC_LEVEL_SCALE_LINEAR,
707
G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
709
g_type_class_add_private (klass, sizeof (GvcLevelBarPrivate));
713
gvc_level_bar_init (GvcLevelBar *bar)
715
bar->priv = GVC_LEVEL_BAR_GET_PRIVATE (bar);
717
bar->priv->peak_adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0,
723
g_object_ref_sink (bar->priv->peak_adjustment);
724
g_signal_connect (bar->priv->peak_adjustment,
726
G_CALLBACK (on_peak_adjustment_value_changed),
729
bar->priv->rms_adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0,
735
g_object_ref_sink (bar->priv->rms_adjustment);
736
g_signal_connect (bar->priv->rms_adjustment,
738
G_CALLBACK (on_rms_adjustment_value_changed),
741
gtk_widget_set_has_window (GTK_WIDGET (bar), FALSE);
745
gvc_level_bar_finalize (GObject *object)
749
g_return_if_fail (object != NULL);
750
g_return_if_fail (GVC_IS_LEVEL_BAR (object));
752
bar = GVC_LEVEL_BAR (object);
754
if (bar->priv->max_peak_id > 0) {
755
g_source_remove (bar->priv->max_peak_id);
758
g_return_if_fail (bar->priv != NULL);
760
G_OBJECT_CLASS (gvc_level_bar_parent_class)->finalize (object);
764
gvc_level_bar_new (void)
767
bar = g_object_new (GVC_TYPE_LEVEL_BAR,
769
return GTK_WIDGET (bar);