summaryrefslogtreecommitdiff
path: root/gst-mixer/src/volume.c
diff options
context:
space:
mode:
Diffstat (limited to 'gst-mixer/src/volume.c')
-rw-r--r--gst-mixer/src/volume.c552
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;
+}