diff options
Diffstat (limited to 'gst-mixer/src/volume.c')
-rw-r--r-- | gst-mixer/src/volume.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/gst-mixer/src/volume.c b/gst-mixer/src/volume.c new file mode 100644 index 0000000..a3386f1 --- /dev/null +++ b/gst-mixer/src/volume.c @@ -0,0 +1,552 @@ +/* MATE Volume Control + * Copyright (C) 2003-2004 Ronald Bultje <[email protected]> + * + * volume.c: representation of a track's volume channels + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _ISOC99_SOURCE + +#include <math.h> +#include <glib/gi18n.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +#include "volume.h" +#include "button.h" + +G_DEFINE_TYPE (MateVolumeControlVolume, mate_volume_control_volume, GTK_TYPE_FIXED) + + +static void mate_volume_control_volume_class_init (MateVolumeControlVolumeClass *klass); +static void mate_volume_control_volume_init (MateVolumeControlVolume *el); +static void mate_volume_control_volume_dispose (GObject *object); + +static void mate_volume_control_volume_size_req (GtkWidget *widget, + GtkRequisition *req); +static void mate_volume_control_volume_size_alloc (GtkWidget *widget, + GtkAllocation *alloc); +static gboolean mate_volume_control_volume_expose (GtkWidget *widget, + GdkEventExpose *expose); + +static void cb_volume_changed (GtkAdjustment *adj, + gpointer data); +static void cb_lock_toggled (GtkToggleButton *button, + gpointer data); + +static gboolean cb_check (gpointer data); + + +static void +mate_volume_control_volume_class_init (MateVolumeControlVolumeClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->dispose = mate_volume_control_volume_dispose; + gtkwidget_class->size_allocate = mate_volume_control_volume_size_alloc; + gtkwidget_class->size_request = mate_volume_control_volume_size_req; + gtkwidget_class->expose_event = mate_volume_control_volume_expose; +} + +static void +mate_volume_control_volume_init (MateVolumeControlVolume *vol) +{ + #if GTK_CHECK_VERSION(2,18,0) + gtk_widget_set_has_window (GTK_WIDGET (vol), TRUE); + #else + gtk_fixed_set_has_window (GTK_FIXED (vol), TRUE); + #endif + + vol->mixer = NULL; + vol->track = NULL; + vol->padding = 6; + vol->scales = NULL; + vol->button = NULL; + vol->locked = FALSE; + vol->id = 0; +} + +static GtkWidget * +get_scale (MateVolumeControlVolume *vol, + gint num_chan, + gint volume) +{ + GtkWidget *slider; + GtkObject *adj; + AtkObject *accessible; + gchar *accessible_name; + + adj = gtk_adjustment_new (volume, + vol->track->min_volume, vol->track->max_volume, + (vol->track->max_volume - vol->track->min_volume) / 100.0, + (vol->track->max_volume - vol->track->min_volume) / 10.0, 0.0); + g_signal_connect (adj, "value_changed", + G_CALLBACK (cb_volume_changed), vol); + slider = gtk_vscale_new (GTK_ADJUSTMENT (adj)); + gtk_scale_set_draw_value (GTK_SCALE (slider), FALSE); + gtk_range_set_inverted (GTK_RANGE (slider), TRUE); + + /* a11y */ + accessible = gtk_widget_get_accessible (slider); + if (GTK_IS_ACCESSIBLE (accessible)) { + if (vol->track->num_channels == 1) { + accessible_name = g_strdup_printf (_("Track %s"), + vol->track->label); + } else { + gchar *accessible_desc = g_strdup_printf (_("Channel %d of track %s"), + num_chan + 1, + vol->track->label); + accessible_name = g_strdup_printf (_("Track %s, channel %d"), + vol->track->label, num_chan + 1); + atk_object_set_description (accessible, accessible_desc); + g_free (accessible_desc); + } + atk_object_set_name (accessible, accessible_name); + g_free (accessible_name); + } + + return slider; +} + +static void +get_button (MateVolumeControlVolume *vol, + gint *volumes) +{ + AtkObject *accessible; + gchar *accessible_name, *msg; + gint n; + + msg = g_strdup_printf (_("Lock channels for %s together"), vol->track->label); + vol->button = mate_volume_control_button_new ("chain.png", + "chain-broken.png", + msg); + g_free (msg); + g_signal_connect (vol->button, "clicked", + G_CALLBACK (cb_lock_toggled), vol); + for (n = 1; n < vol->track->num_channels; n++) { + /* default, unlocked */ + if (volumes[n] != volumes[0]) + break; + } + mate_volume_control_button_set_active (MATE_VOLUME_CONTROL_BUTTON (vol->button), + n == vol->track->num_channels); + + /* a11y */ + accessible = gtk_widget_get_accessible (vol->button); + if (GTK_IS_ACCESSIBLE (accessible)) { + accessible_name = g_strdup_printf (_("Track %s: lock channels together"), + vol->track->label); + atk_object_set_name (accessible, accessible_name); + g_free (accessible_name); + } +} + +GtkWidget * +mate_volume_control_volume_new (GstMixer *mixer, + GstMixerTrack *track, + gint padding) +{ + MateVolumeControlVolume *vol; + gint *volumes, n; + gchar *msg, *chan; + gboolean need_timeout = TRUE; + + need_timeout = ((gst_mixer_get_mixer_flags (GST_MIXER (mixer)) & + GST_MIXER_FLAG_AUTO_NOTIFICATIONS) == 0); + + /* volume */ + vol = g_object_new (MATE_VOLUME_CONTROL_TYPE_VOLUME, NULL); + gst_object_ref (GST_OBJECT (mixer)); + vol->mixer = mixer; + vol->track = g_object_ref (G_OBJECT (track)); + if (padding >= 0) + vol->padding = padding; + + /* sliders */ + volumes = g_new0 (gint, track->num_channels); + gst_mixer_get_volume (mixer, track, volumes); + for (n = 0; n < track->num_channels; n++) { + GtkWidget *slider; + + /* we will reposition the widget once we're drawing up */ + slider = get_scale (vol, n, volumes[n]); + gtk_fixed_put (GTK_FIXED (vol), slider, 0, 0); + gtk_widget_show (slider); + vol->scales = g_list_append (vol->scales, slider); + + /* somewhat dirty hack that will suffice for now. 1 chan + * means mono, two means stereo (left/right) and > 2 means + * alsa, where channel order is front, rear, center, lfe, + * side. */ + if (vol->track->num_channels == 1) { + chan = _("mono"); + } else if (vol->track->num_channels == 2) { + chan = (n == 0) ? _("left") : _("right"); + } else { + switch (n) { + case 0: chan = _("front left"); break; + case 1: chan = _("front right"); break; + case 2: chan = _("rear left"); break; + case 3: chan = _("rear right"); break; + case 4: chan = _("front center"); break; + /* Translators: This is the name of a surround sound channel. It + * stands for "Low-Frequency Effects". If you're not sure that + * this has an established and different translation in your + * language, leave it unchanged. */ + case 5: chan = _("LFE"); break; + case 6: chan = _("side left"); break; + case 7: chan = _("side right"); break; + default: chan = _("unknown"); break; + } + } + + /* Here, we can actually tell people that this + * is a slider that will change channel X. */ + msg = g_strdup_printf (_("Volume of %s channel on %s"), + chan, vol->track->label); + gtk_widget_set_tooltip_text (slider, msg); + g_free (msg); + } + + /* chainbutton */ + get_button (vol, volumes); + if (track->num_channels > 1) { + gtk_fixed_put (GTK_FIXED (vol), vol->button, 0, 0); + gtk_widget_show (vol->button); + } + + g_free (volumes); + + /* GStreamer signals */ + if (need_timeout) + vol->id = g_timeout_add (100, cb_check, vol); + + return GTK_WIDGET (vol); +} + +static void +mate_volume_control_volume_dispose (GObject *object) +{ + MateVolumeControlVolume *vol = MATE_VOLUME_CONTROL_VOLUME (object); + + if (vol->id != 0) { + g_source_remove (vol->id); + vol->id = 0; + } + + if (vol->track) { + g_object_unref (G_OBJECT (vol->track)); + vol->track = NULL; + } + + if (vol->mixer) { + gst_object_unref (GST_OBJECT (vol->mixer)); + vol->mixer = NULL; + } + + if (vol->scales) { + g_list_free (vol->scales); + vol->scales = NULL; + } + + G_OBJECT_CLASS (mate_volume_control_volume_parent_class)->dispose (object); +} + +/* + * Gtk/GDK virtual functions for size negotiation. + */ + +static void +mate_volume_control_volume_size_req (GtkWidget *widget, + GtkRequisition *req) +{ + MateVolumeControlVolume *vol = MATE_VOLUME_CONTROL_VOLUME (widget); + GtkRequisition but_req, scale_req; + + /* request size of kids */ + GTK_WIDGET_GET_CLASS (vol->button)->size_request (vol->button, &but_req); + GTK_WIDGET_GET_CLASS (vol->scales->data)->size_request (vol->scales->data, + &scale_req); + if (scale_req.height < 100) + scale_req.height = 100; + + /* calculate our own size from that */ + req->width = scale_req.width * vol->track->num_channels + + vol->padding * (vol->track->num_channels - 1); + req->height = scale_req.height + but_req.height /*+ vol->padding*/; +} + +static void +mate_volume_control_volume_size_alloc (GtkWidget *widget, + GtkAllocation *alloc) +{ + MateVolumeControlVolume *vol = MATE_VOLUME_CONTROL_VOLUME (widget); + GtkRequisition but_req, scale_req; + GtkAllocation but_all, scale_all; + gint x_offset, but_deco_width, n = 0; + GList *scales; + GtkAllocation allocation; + + /* loop? */ + gtk_widget_get_allocation (widget, &allocation); + if (alloc->x == allocation.x && + alloc->y == allocation.y && + alloc->width == allocation.width && + alloc->height == allocation.height) + return; + + /* request size of kids */ + GTK_WIDGET_GET_CLASS (vol->button)->size_request (vol->button, &but_req); + GTK_WIDGET_GET_CLASS (vol->scales->data)->size_request (vol->scales->data, + &scale_req); + + /* calculate */ + x_offset = (alloc->width - ((vol->track->num_channels * scale_req.width) + + (vol->track->num_channels - 1) * vol->padding)) / 2; + scale_all.width = scale_req.width; + scale_all.height = alloc->height - but_req.height; + scale_all.y = 0; + but_deco_width = alloc->width - (2 * x_offset); + but_all.width = but_req.width; + but_all.height = but_req.height; + but_all.x = x_offset + (but_deco_width - but_req.width) / 2; + but_all.y = alloc->height - but_req.height; + + /* tell sliders */ + for (scales = vol->scales; scales != NULL; scales = scales->next, n++) { + scale_all.x = x_offset + n * (scale_req.width + vol->padding); + gtk_fixed_move (GTK_FIXED (vol), scales->data, scale_all.x, scale_all.y); + gtk_widget_set_size_request (scales->data, scale_all.width, scale_all.height); + } + + /* tell button */ + if (vol->track->num_channels > 1) { + gtk_fixed_move (GTK_FIXED (vol), vol->button, but_all.x, but_all.y); + gtk_widget_set_size_request (vol->button, but_all.width, but_all.height); + } + + /* parent will resize window */ + GTK_WIDGET_CLASS (mate_volume_control_volume_parent_class)->size_allocate (widget, alloc); +} + +static gboolean +mate_volume_control_volume_expose (GtkWidget *widget, + GdkEventExpose *expose) +{ + GtkAllocation allocation; + MateVolumeControlVolume *vol = MATE_VOLUME_CONTROL_VOLUME (widget); + + /* clear background */ + gtk_widget_get_allocation (widget, &allocation); + gdk_window_clear_area (gtk_widget_get_window (widget), 0, 0, + allocation.width, + allocation.height); + + if (vol->track->num_channels > 1) { + gint x_offset, y_offset, height, width; + GtkRequisition scale_req, but_req; + GdkPoint points[3]; + GtkStyle *style; + GtkStateType state; + + /* request size of kids */ + GTK_WIDGET_GET_CLASS (vol->button)->size_request (vol->button, &but_req); + GTK_WIDGET_GET_CLASS (vol->scales->data)->size_request (vol->scales->data, + &scale_req); + + /* calculate */ + gtk_widget_get_allocation (widget, &allocation); + x_offset = (allocation.width - + ((vol->track->num_channels * scale_req.width) + + (vol->track->num_channels - 1) * vol->padding)) / 2; + y_offset = allocation.height - but_req.height; + width = allocation.width - (2 * x_offset + but_req.width); + height = but_req.height / 2; + points[0].y = y_offset + 3; + points[1].y = points[2].y = points[0].y + height - 3; + + /* draw chainbutton decorations */ + style = gtk_widget_get_style (widget); + state = gtk_widget_get_state (widget); + + points[0].x = points[1].x = x_offset + 3; + points[2].x = points[0].x + width - 6; + gtk_paint_polygon (style, gtk_widget_get_window (widget), + state, + GTK_SHADOW_ETCHED_IN, + &expose->area, widget, "hseparator", + points, 3, FALSE); + + points[0].x = points[1].x = allocation.width - x_offset - 3; + points[2].x = points[0].x - width + 6; + gtk_paint_polygon (style, gtk_widget_get_window (widget), + state, + GTK_SHADOW_ETCHED_IN, + &expose->area, widget, "hseparator", + points, 3, FALSE); + } + + /* take care of redrawing the kids */ + return GTK_WIDGET_CLASS (mate_volume_control_volume_parent_class)->expose_event (widget, expose); +} + +/* + * Signals handlers. + */ + +static void +cb_volume_changed (GtkAdjustment *_adj, + gpointer data) +{ + MateVolumeControlVolume *vol = data; + gint *volumes, i = 0; + GList *scales; + + if (vol->locked) + return; + vol->locked = TRUE; + volumes = g_new (gint, vol->track->num_channels); + + for (scales = vol->scales; scales != NULL; scales = scales->next) { + GtkAdjustment *adj = gtk_range_get_adjustment (scales->data); + + if (mate_volume_control_button_get_active ( + MATE_VOLUME_CONTROL_BUTTON (vol->button))) { + gtk_adjustment_set_value (adj, gtk_adjustment_get_value (_adj)); + volumes[i++] = rint (gtk_adjustment_get_value (_adj)); + } else { + volumes[i++] = rint (gtk_adjustment_get_value (adj)); + } + } + + gst_mixer_set_volume (vol->mixer, vol->track, volumes); + + g_free (volumes); + vol->locked = FALSE; +} + +static void +cb_lock_toggled (GtkToggleButton *button, + gpointer data) +{ + MateVolumeControlVolume *vol = data; + + if (mate_volume_control_button_get_active ( + MATE_VOLUME_CONTROL_BUTTON (vol->button))) { + /* get the mean value, and set it on the first adjustment. + * the cb_volume_changed () callback will take care of the + * rest. */ + gint volume = 0, num = 0; + GList *scales; + + for (scales = vol->scales ; scales != NULL; scales = scales->next) { + GtkAdjustment *adj = gtk_range_get_adjustment (scales->data); + + num++; + volume += gtk_adjustment_get_value (adj); + } + + /* safety check */ + if (vol->scales != NULL) { + gtk_adjustment_set_value (gtk_range_get_adjustment (vol->scales->data), + volume / num); + } + } +} + +/* + * See if our volume is zero. + */ + +void +mate_volume_control_volume_ask (MateVolumeControlVolume * vol, + gboolean *real_zero, gboolean *slider_zero) +{ + GList *scales; + gint *volumes, n, tot = 0; + + volumes = g_new (gint, vol->track->num_channels); + gst_mixer_get_volume (vol->mixer, vol->track, volumes); + for (n = 0; n < vol->track->num_channels; n++) + tot += volumes[n]; + g_free (volumes); + *real_zero = (tot == 0); + + *slider_zero = TRUE; + for (n = 0, scales = vol->scales; + scales != NULL; scales = scales->next, n++) { + GtkAdjustment *adj = gtk_range_get_adjustment (scales->data); + + if (rint (gtk_adjustment_get_value (adj)) != 0) { + *slider_zero = FALSE; + break; + } + } +} + + +void +mate_volume_control_volume_update (MateVolumeControlVolume *vol) +{ + gint *volumes, n; + GList *scales; + + /* don't do callbacks */ + if (vol->locked) + return; + + vol->locked = TRUE; + + volumes = g_new (gint, vol->track->num_channels); + gst_mixer_get_volume (vol->mixer, vol->track, volumes); + + /* did we change? */ + for (n = 0, scales = vol->scales; + scales != NULL; scales = scales->next, n++) { + GtkAdjustment *adj = gtk_range_get_adjustment (scales->data); + + if ((gint) gtk_adjustment_get_value (adj) != volumes[n]) { + gtk_range_set_value (scales->data, volumes[n]); + } + + /* should we release lock? */ + if (volumes[n] != volumes[0]) { + mate_volume_control_button_set_active ( + MATE_VOLUME_CONTROL_BUTTON (vol->button), FALSE); + } + } + + g_free (volumes); + vol->locked = FALSE; +} + +/* + * Timeout to check for volume changes. + */ + +static gboolean +cb_check (gpointer data) +{ + mate_volume_control_volume_update (data); + + return TRUE; +} |