diff options
Diffstat (limited to 'mate-volume-control/gvc-mixer-dialog.c')
-rw-r--r-- | mate-volume-control/gvc-mixer-dialog.c | 2606 |
1 files changed, 2606 insertions, 0 deletions
diff --git a/mate-volume-control/gvc-mixer-dialog.c b/mate-volume-control/gvc-mixer-dialog.c new file mode 100644 index 0000000..b6525fb --- /dev/null +++ b/mate-volume-control/gvc-mixer-dialog.c @@ -0,0 +1,2606 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <libmatemixer/matemixer.h> + +#include "gvc-channel-bar.h" +#include "gvc-balance-bar.h" +#include "gvc-combo-box.h" +#include "gvc-mixer-dialog.h" +#include "gvc-sound-theme-chooser.h" +#include "gvc-level-bar.h" +#include "gvc-speaker-test.h" +#include "gvc-utils.h" + +#define GVC_MIXER_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_DIALOG, GvcMixerDialogPrivate)) + +struct _GvcMixerDialogPrivate +{ + MateMixerContext *context; + GHashTable *bars; + GtkWidget *notebook; + GtkWidget *output_bar; + GtkWidget *input_bar; + GtkWidget *input_level_bar; + GtkWidget *effects_bar; + GtkWidget *output_stream_box; + GtkWidget *sound_effects_box; + GtkWidget *hw_box; + GtkWidget *hw_treeview; + GtkWidget *hw_settings_box; + GtkWidget *hw_profile_combo; + GtkWidget *input_box; + GtkWidget *output_box; + GtkWidget *applications_box; + GtkWidget *applications_window; + GtkWidget *no_apps_label; + GtkWidget *output_treeview; + GtkWidget *output_settings_frame; + GtkWidget *output_settings_box; + GtkWidget *output_balance_bar; + GtkWidget *output_fade_bar; + GtkWidget *output_lfe_bar; + GtkWidget *output_port_combo; + GtkWidget *input_treeview; + GtkWidget *input_port_combo; + GtkWidget *input_settings_box; + GtkWidget *sound_theme_chooser; + GtkSizeGroup *size_group; + gdouble last_input_peak; + guint num_apps; +}; + +enum { + ICON_COLUMN, + NAME_COLUMN, + LABEL_COLUMN, + ACTIVE_COLUMN, + SPEAKERS_COLUMN, + NUM_COLUMNS +}; + +enum { + HW_ICON_COLUMN, + HW_NAME_COLUMN, + HW_LABEL_COLUMN, + HW_STATUS_COLUMN, + HW_PROFILE_COLUMN, + HW_NUM_COLUMNS +}; + +enum { + PAGE_EFFECTS, + PAGE_HARDWARE, + PAGE_INPUT, + PAGE_OUTPUT, + PAGE_APPLICATIONS +}; + +enum { + PROP_0, + PROP_CONTEXT +}; + +static const guint tab_accel_keys[] = { + GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5 +}; + +static void gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass); +static void gvc_mixer_dialog_init (GvcMixerDialog *dialog); +static void gvc_mixer_dialog_finalize (GObject *object); + +static void add_stream (GvcMixerDialog *dialog, + MateMixerStream *stream); +static void add_application_control (GvcMixerDialog *dialog, + MateMixerStreamControl *control); +static void add_effects_control (GvcMixerDialog *dialog, + MateMixerStreamControl *control); + +static void remove_stream (GvcMixerDialog *dialog, + const gchar *name); +static void remove_application_control (GvcMixerDialog *dialog, + const gchar *name); + +static void bar_set_stream (GvcMixerDialog *dialog, + GtkWidget *bar, + MateMixerStream *stream); +static void bar_set_stream_control (GvcMixerDialog *dialog, + GtkWidget *bar, + MateMixerStreamControl *control); + +G_DEFINE_TYPE (GvcMixerDialog, gvc_mixer_dialog, GTK_TYPE_DIALOG) + +static MateMixerSwitch * +find_stream_port_switch (MateMixerStream *stream) +{ + const GList *switches; + + switches = mate_mixer_stream_list_switches (stream); + while (switches != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (switches->data); + MateMixerSwitchFlags flags; + + flags = mate_mixer_switch_get_flags (swtch); + if ((flags & MATE_MIXER_SWITCH_TOGGLE) == 0 && + mate_mixer_switch_get_role (swtch) == MATE_MIXER_SWITCH_ROLE_PORT) + return swtch; + + switches = switches->next; + } + return NULL; +} + +static MateMixerSwitch * +find_device_profile_switch (MateMixerDevice *device) +{ + const GList *switches; + + switches = mate_mixer_device_list_switches (device); + while (switches != NULL) { + MateMixerSwitch *swtch = MATE_MIXER_SWITCH (switches->data); + + if (mate_mixer_switch_get_role (swtch) == MATE_MIXER_SWITCH_ROLE_DEVICE_PROFILE) + return swtch; + + switches = switches->next; + } + return NULL; +} + +static MateMixerStream * +find_device_test_stream (GvcMixerDialog *dialog, MateMixerDevice *device) +{ + const GList *streams; + + streams = mate_mixer_device_list_streams (device); + while (streams != NULL) { + MateMixerStream *stream; + MateMixerDirection direction; + + stream = MATE_MIXER_STREAM (streams->data); + direction = mate_mixer_stream_get_direction (stream); + + if (direction == MATE_MIXER_DIRECTION_OUTPUT) { + MateMixerStreamControl *control; + + control = mate_mixer_stream_get_default_control (stream); + if (mate_mixer_stream_control_get_num_channels (control) > 0) + return stream; + } + streams = streams->next; + } + return FALSE; +} + +static gboolean +find_tree_item_by_name (GtkTreeModel *model, + const gchar *name, + guint column, + GtkTreeIter *iter) +{ + gboolean found = FALSE; + + if (!gtk_tree_model_get_iter_first (model, iter)) + return FALSE; + + do { + gchar *n; + gtk_tree_model_get (model, iter, column, &n, -1); + + if (!g_strcmp0 (name, n)) + found = TRUE; + + g_free (n); + } while (!found && gtk_tree_model_iter_next (model, iter)); + + return found; +} + +static void +update_default_tree_item (GvcMixerDialog *dialog, + GtkTreeModel *model, + MateMixerStream *stream) +{ + GtkTreeIter iter; + const gchar *name = NULL; + + if (gtk_tree_model_get_iter_first (model, &iter) == FALSE) + return; + + /* The supplied stream is the default, or the selected item. Traverse + * the item list and mark each item as being selected or not. Also do not + * presume some known stream is selected and allow NULL here. */ + if (stream != NULL) + name = mate_mixer_stream_get_name (stream); + + do { + gchar *n; + gtk_tree_model_get (model, &iter, + NAME_COLUMN, &n, + -1); + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + ACTIVE_COLUMN, !g_strcmp0 (name, n), + -1); + g_free (n); + } while (gtk_tree_model_iter_next (model, &iter)); +} + +static void +on_combobox_option_changed (GvcComboBox *combo, + const gchar *name, + GvcMixerDialog *dialog) +{ + MateMixerSwitch *swtch; + MateMixerSwitchOption *option; + const GList *options; + + swtch = g_object_get_data (G_OBJECT (combo), "switch"); + if (G_UNLIKELY (swtch == NULL)) { + g_warn_if_reached (); + return; + } + + options = mate_mixer_switch_list_options (swtch); + while (options != NULL) { + option = MATE_MIXER_SWITCH_OPTION (options->data); + + if (g_strcmp0 (mate_mixer_switch_option_get_name (option), name) == 0) + break; + + option = NULL; + options = options->next; + } + + if (G_UNLIKELY (option == NULL)) { + g_warn_if_reached (); + return; + } + + mate_mixer_switch_set_active_option (swtch, option); +} + +static GtkWidget * +create_port_combo_box (GvcMixerDialog *dialog, + MateMixerSwitch *swtch, + const gchar *name, + const GList *items, + const gchar *active) +{ + GtkWidget *combobox; + + combobox = gvc_combo_box_new (name); + + gvc_combo_box_set_options (GVC_COMBO_BOX (combobox), items); + gvc_combo_box_set_active (GVC_COMBO_BOX (combobox), active); + + gvc_combo_box_set_size_group (GVC_COMBO_BOX (combobox), + dialog->priv->size_group, + FALSE); + + g_object_set_data_full (G_OBJECT (combobox), + "switch", + g_object_ref (swtch), + g_object_unref); + + g_signal_connect (G_OBJECT (combobox), + "changed", + G_CALLBACK (on_combobox_option_changed), + dialog); + return combobox; +} + +static void +update_output_settings (GvcMixerDialog *dialog) +{ + MateMixerStream *stream; + MateMixerStreamControl *control; + MateMixerStreamControlFlags flags; + MateMixerSwitch *port_switch; + gboolean has_settings = FALSE; + + g_debug ("Updating output settings"); + + if (dialog->priv->output_balance_bar != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box), + dialog->priv->output_balance_bar); + + dialog->priv->output_balance_bar = NULL; + } + if (dialog->priv->output_fade_bar != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box), + dialog->priv->output_fade_bar); + + dialog->priv->output_fade_bar = NULL; + } + if (dialog->priv->output_lfe_bar != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box), + dialog->priv->output_lfe_bar); + + dialog->priv->output_lfe_bar = NULL; + } + if (dialog->priv->output_port_combo != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box), + dialog->priv->output_port_combo); + + dialog->priv->output_port_combo = NULL; + } + + /* Get the control currently associated with the output slider */ + control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->output_bar)); + if (control == NULL) { + g_debug ("There is no control for the default output stream"); + gtk_widget_hide (dialog->priv->output_settings_frame); + return; + } + flags = mate_mixer_stream_control_get_flags (control); + + /* Enable balance bar if it is available */ + if (flags & MATE_MIXER_STREAM_CONTROL_CAN_BALANCE) { + dialog->priv->output_balance_bar = + gvc_balance_bar_new (control, BALANCE_TYPE_RL); + + gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_balance_bar), + dialog->priv->size_group, + TRUE); + + gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box), + dialog->priv->output_balance_bar, + FALSE, FALSE, 6); + + gtk_widget_show (dialog->priv->output_balance_bar); + has_settings = TRUE; + } + + /* Enable fade bar if it is available */ + if (flags & MATE_MIXER_STREAM_CONTROL_CAN_FADE) { + dialog->priv->output_fade_bar = + gvc_balance_bar_new (control, BALANCE_TYPE_FR); + + gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_fade_bar), + dialog->priv->size_group, + TRUE); + + gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box), + dialog->priv->output_fade_bar, + FALSE, FALSE, 6); + + gtk_widget_show (dialog->priv->output_fade_bar); + has_settings = TRUE; + } + + /* Enable subwoofer volume bar if subwoofer is available */ + if (mate_mixer_stream_control_has_channel_position (control, MATE_MIXER_CHANNEL_LFE)) { + dialog->priv->output_lfe_bar = + gvc_balance_bar_new (control, BALANCE_TYPE_LFE); + + gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_lfe_bar), + dialog->priv->size_group, + TRUE); + + gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box), + dialog->priv->output_lfe_bar, + FALSE, FALSE, 6); + + gtk_widget_show (dialog->priv->output_lfe_bar); + has_settings = TRUE; + } + + /* Get owning stream of the control */ + stream = mate_mixer_stream_control_get_stream (control); + if (G_UNLIKELY (stream == NULL)) + return; + + /* Enable the port selector if the stream has one */ + port_switch = find_stream_port_switch (stream); + if (port_switch != NULL) { + const GList *options; + const gchar *active_name = NULL; + MateMixerSwitchOption *active; + + options = mate_mixer_switch_list_options (port_switch); + active = mate_mixer_switch_get_active_option (port_switch); + if (active != NULL) + active_name = mate_mixer_switch_option_get_name (active); + + dialog->priv->output_port_combo = + create_port_combo_box (dialog, + port_switch, + _("Co_nnector:"), + options, + active_name); + + gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box), + dialog->priv->output_port_combo, + TRUE, FALSE, 6); + + gtk_widget_show (dialog->priv->output_port_combo); + has_settings = TRUE; + } + + if (has_settings == TRUE) + gtk_widget_show (dialog->priv->output_settings_frame); + else + gtk_widget_hide (dialog->priv->output_settings_frame); +} + +static void +set_output_stream (GvcMixerDialog *dialog, MateMixerStream *stream) +{ + GtkTreeModel *model; + MateMixerSwitch *swtch; + MateMixerStreamControl *control; + + control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->output_bar)); + if (control != NULL) { + /* Disconnect port switch of the previous stream */ + if (dialog->priv->output_port_combo != NULL) { + swtch = g_object_get_data (G_OBJECT (dialog->priv->output_port_combo), + "switch"); + if (swtch != NULL) + g_signal_handlers_disconnect_by_data (G_OBJECT (swtch), + dialog); + } + } + + bar_set_stream (dialog, dialog->priv->output_bar, stream); + + if (stream != NULL) { + const GList *controls; + + controls = mate_mixer_context_list_stored_controls (dialog->priv->context); + + /* Move all stored controls to the newly selected default stream */ + while (controls != NULL) { + MateMixerStream *parent; + MateMixerStreamControl *control; + + control = MATE_MIXER_STREAM_CONTROL (controls->data); + parent = mate_mixer_stream_control_get_stream (control); + + /* Prefer streamless controls to stay the way they are, forcing them to + * a particular owning stream would be wrong for eg. event controls */ + if (parent != NULL && parent != stream) { + MateMixerDirection direction = + mate_mixer_stream_get_direction (parent); + + if (direction == MATE_MIXER_DIRECTION_OUTPUT) + mate_mixer_stream_control_set_stream (control, stream); + } + controls = controls->next; + } + } + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + update_default_tree_item (dialog, model, stream); + + update_output_settings (dialog); +} + +static void +on_context_default_output_stream_notify (MateMixerContext *context, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + MateMixerStream *stream; + + stream = mate_mixer_context_get_default_output_stream (context); + + set_output_stream (dialog, stream); +} + +#define DECAY_STEP .15 + +static void +on_stream_control_monitor_value (MateMixerStream *stream, + gdouble value, + GvcMixerDialog *dialog) +{ + GtkAdjustment *adj; + + if (dialog->priv->last_input_peak >= DECAY_STEP) { + if (value < dialog->priv->last_input_peak - DECAY_STEP) { + value = dialog->priv->last_input_peak - DECAY_STEP; + } + } + + dialog->priv->last_input_peak = value; + + adj = gvc_level_bar_get_peak_adjustment (GVC_LEVEL_BAR (dialog->priv->input_level_bar)); + if (value >= 0) + gtk_adjustment_set_value (adj, value); + else + gtk_adjustment_set_value (adj, 0.0); +} + +static void +update_input_settings (GvcMixerDialog *dialog) +{ + MateMixerStream *stream; + MateMixerStreamControl *control; + MateMixerStreamControlFlags flags; + MateMixerSwitch *port_switch; + + g_debug ("Updating input settings"); + + if (dialog->priv->input_port_combo != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->input_settings_box), + dialog->priv->input_port_combo); + + dialog->priv->input_port_combo = NULL; + } + + /* Get the control currently associated with the input slider */ + control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar)); + if (control == NULL) + return; + + flags = mate_mixer_stream_control_get_flags (control); + + /* Enable level bar only if supported by the control */ + if (flags & MATE_MIXER_STREAM_CONTROL_HAS_MONITOR) + g_signal_connect (G_OBJECT (control), + "monitor-value", + G_CALLBACK (on_stream_control_monitor_value), + dialog); + + /* Get owning stream of the control */ + stream = mate_mixer_stream_control_get_stream (control); + if (G_UNLIKELY (stream == NULL)) + return; + + /* Enable the port selector if the stream has one */ + port_switch = find_stream_port_switch (stream); + if (port_switch != NULL) { + const GList *options; + const gchar *active_name = NULL; + MateMixerSwitchOption *active; + + options = mate_mixer_switch_list_options (port_switch); + active = mate_mixer_switch_get_active_option (port_switch); + if (active != NULL) + active_name = mate_mixer_switch_option_get_name (active); + + dialog->priv->input_port_combo = + create_port_combo_box (dialog, + port_switch, + _("Co_nnector:"), + options, + active_name); + + gtk_box_pack_start (GTK_BOX (dialog->priv->input_settings_box), + dialog->priv->input_port_combo, + TRUE, TRUE, 0); + + gtk_widget_show (dialog->priv->input_port_combo); + } +} + +static void +on_stream_control_mute_notify (MateMixerStreamControl *control, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + /* Stop monitoring the input stream when it gets muted */ + if (mate_mixer_stream_control_get_mute (control) == TRUE) + mate_mixer_stream_control_set_monitor_enabled (control, FALSE); + else + mate_mixer_stream_control_set_monitor_enabled (control, TRUE); +} + +static void +set_input_stream (GvcMixerDialog *dialog, MateMixerStream *stream) +{ + GtkTreeModel *model; + MateMixerSwitch *swtch; + MateMixerStreamControl *control; + + control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar)); + if (control != NULL) { + /* Disconnect port switch of the previous stream */ + if (dialog->priv->input_port_combo != NULL) { + swtch = g_object_get_data (G_OBJECT (dialog->priv->input_port_combo), + "switch"); + if (swtch != NULL) + g_signal_handlers_disconnect_by_data (G_OBJECT (swtch), + dialog); + } + + /* Disable monitoring of the previous control */ + g_signal_handlers_disconnect_by_func (G_OBJECT (control), + G_CALLBACK (on_stream_control_monitor_value), + dialog); + + mate_mixer_stream_control_set_monitor_enabled (control, FALSE); + } + + bar_set_stream (dialog, dialog->priv->input_bar, stream); + + if (stream != NULL) { + const GList *controls; + guint page = gtk_notebook_get_current_page (GTK_NOTEBOOK (dialog->priv->notebook)); + + controls = mate_mixer_context_list_stored_controls (dialog->priv->context); + + /* Move all stored controls to the newly selected default stream */ + while (controls != NULL) { + MateMixerStream *parent; + + control = MATE_MIXER_STREAM_CONTROL (controls->data); + parent = mate_mixer_stream_control_get_stream (control); + + /* Prefer streamless controls to stay the way they are, forcing them to + * a particular owning stream would be wrong for eg. event controls */ + if (parent != NULL && parent != stream) { + MateMixerDirection direction = + mate_mixer_stream_get_direction (parent); + + if (direction == MATE_MIXER_DIRECTION_INPUT) + mate_mixer_stream_control_set_stream (control, stream); + } + controls = controls->next; + } + + if (page == PAGE_INPUT) { + MateMixerStreamControl *control = + gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar)); + + if (G_LIKELY (control != NULL)) + mate_mixer_stream_control_set_monitor_enabled (control, TRUE); + } + + /* Enable/disable the peak level monitor according to mute state */ + g_signal_connect (G_OBJECT (stream), + "notify::mute", + G_CALLBACK (on_stream_control_mute_notify), + dialog); + } + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + update_default_tree_item (dialog, model, stream); + + update_input_settings (dialog); +} + +static void +on_context_default_input_stream_notify (MateMixerContext *context, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + MateMixerStream *stream; + + g_debug ("Default input stream has changed"); + + stream = mate_mixer_context_get_default_input_stream (context); + + set_input_stream (dialog, stream); +} + +static GvcComboBox * +find_combo_box_by_switch (GvcMixerDialog *dialog, MateMixerSwitch *swtch) +{ + MateMixerSwitch *combo_switch; + + if (dialog->priv->output_port_combo != NULL) { + combo_switch = g_object_get_data (G_OBJECT (dialog->priv->output_port_combo), + "switch"); + if (combo_switch == swtch) + return GVC_COMBO_BOX (dialog->priv->output_port_combo); + } + + if (dialog->priv->input_port_combo != NULL) { + combo_switch = g_object_get_data (G_OBJECT (dialog->priv->input_port_combo), + "switch"); + if (combo_switch == swtch) + return GVC_COMBO_BOX (dialog->priv->input_port_combo); + } + return NULL; +} + +static void +on_switch_option_notify (MateMixerSwitch *swtch, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + GvcComboBox *combobox; + MateMixerSwitchOption *port; + + combobox = find_combo_box_by_switch (dialog, swtch); + if (G_UNLIKELY (combobox == NULL)) { + g_warn_if_reached (); + return; + } + + g_signal_handlers_block_by_func (G_OBJECT (combobox), + on_combobox_option_changed, + dialog); + + port = mate_mixer_switch_get_active_option (swtch); + if (G_LIKELY (port != NULL)) + gvc_combo_box_set_active (GVC_COMBO_BOX (combobox), + mate_mixer_switch_option_get_name (port)); + + g_signal_handlers_unblock_by_func (G_OBJECT (combobox), + on_combobox_option_changed, + dialog); +} + +static GtkWidget * +create_bar (GvcMixerDialog *dialog, gboolean use_size_group, gboolean symmetric) +{ + GtkWidget *bar; + + bar = gvc_channel_bar_new (NULL); + + if (use_size_group == TRUE) + gvc_channel_bar_set_size_group (GVC_CHANNEL_BAR (bar), + dialog->priv->size_group, + symmetric); + + g_object_set (G_OBJECT (bar), + "orientation", GTK_ORIENTATION_HORIZONTAL, + "show-mute", TRUE, + "show-icons", TRUE, + "show-marks", TRUE, + "extended", TRUE, NULL); + return bar; +} + +static void +bar_set_stream (GvcMixerDialog *dialog, + GtkWidget *bar, + MateMixerStream *stream) +{ + MateMixerStreamControl *control = NULL; + + if (stream != NULL) { + MateMixerSwitch *port_switch; + + control = mate_mixer_stream_get_default_control (stream); + + port_switch = find_stream_port_switch (stream); + if (port_switch != NULL) + g_signal_connect (G_OBJECT (port_switch), + "notify::active-option", + G_CALLBACK (on_switch_option_notify), + dialog); + } + + bar_set_stream_control (dialog, bar, control); +} + +static void +bar_set_stream_control (GvcMixerDialog *dialog, + GtkWidget *bar, + MateMixerStreamControl *control) +{ + MateMixerStreamControl *previous; + + previous = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (bar)); + if (previous != NULL) { + const gchar *name = mate_mixer_stream_control_get_name (previous); + + g_debug ("Disconnecting old stream control %s", name); + + g_signal_handlers_disconnect_by_data (G_OBJECT (previous), dialog); + + /* This may not do anything because we no longer have the information + * about the owning stream, in case it was an input stream, make + * sure to disconnected from the peak level monitor */ + mate_mixer_stream_control_set_monitor_enabled (previous, FALSE); + + g_hash_table_remove (dialog->priv->bars, name); + } + + gvc_channel_bar_set_control (GVC_CHANNEL_BAR (bar), control); + + if (control != NULL) { + const gchar *name = mate_mixer_stream_control_get_name (control); + + g_hash_table_insert (dialog->priv->bars, + (gpointer) name, + bar); + + gtk_widget_set_sensitive (GTK_WIDGET (bar), TRUE); + } else + gtk_widget_set_sensitive (GTK_WIDGET (bar), TRUE); +} + +static void +add_application_control (GvcMixerDialog *dialog, MateMixerStreamControl *control) +{ + MateMixerStream *stream; + MateMixerStreamControlMediaRole media_role; + MateMixerAppInfo *info; + MateMixerDirection direction = MATE_MIXER_DIRECTION_UNKNOWN; + GtkWidget *bar; + const gchar *app_id; + const gchar *app_name; + const gchar *app_icon; + + media_role = mate_mixer_stream_control_get_media_role (control); + + /* Add stream to the applications page, but make sure the stream qualifies + * for the inclusion */ + info = mate_mixer_stream_control_get_app_info (control); + if (info == NULL) + return; + + /* Skip streams with roles we don't care about */ + if (media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT || + media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_TEST || + media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_ABSTRACT || + media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_FILTER) + return; + + app_id = mate_mixer_app_info_get_id (info); + + /* These applications may have associated streams because they do peak + * level monitoring, skip these too */ + if (!g_strcmp0 (app_id, "org.mate.VolumeControl") || + !g_strcmp0 (app_id, "org.gnome.VolumeControl") || + !g_strcmp0 (app_id, "org.PulseAudio.pavucontrol")) + return; + + app_name = mate_mixer_app_info_get_name (info); + if (app_name == NULL) + app_name = mate_mixer_stream_control_get_label (control); + if (app_name == NULL) + app_name = mate_mixer_stream_control_get_name (control); + if (G_UNLIKELY (app_name == NULL)) + return; + + bar = create_bar (dialog, FALSE, FALSE); + + g_object_set (G_OBJECT (bar), + "show-marks", FALSE, + "extended", FALSE, + NULL); + + /* By default channel bars use speaker icons, use microphone icons + * instead for recording applications */ + stream = mate_mixer_stream_control_get_stream (control); + if (stream != NULL) + direction = mate_mixer_stream_get_direction (stream); + + if (direction == MATE_MIXER_DIRECTION_INPUT) + g_object_set (G_OBJECT (bar), + "low-icon-name", "audio-input-microphone-low", + "high-icon-name", "audio-input-microphone-high", + NULL); + + app_icon = mate_mixer_app_info_get_icon (info); + if (app_icon == NULL) { + if (direction == MATE_MIXER_DIRECTION_INPUT) + app_icon = "audio-input-microphone"; + else + app_icon = "applications-multimedia"; + } + + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (bar), app_name); + gvc_channel_bar_set_icon_name (GVC_CHANNEL_BAR (bar), app_icon); + + gtk_box_pack_start (GTK_BOX (dialog->priv->applications_box), + bar, + FALSE, FALSE, 12); + + bar_set_stream_control (dialog, bar, control); + dialog->priv->num_apps++; + + gtk_widget_hide (dialog->priv->no_apps_label); + gtk_widget_show (bar); +} + +static void +add_effects_control (GvcMixerDialog *dialog, MateMixerStreamControl *control) +{ + MateMixerStreamControl *previous; + const gchar *name; + + /* We use a stored event control for the effects volume slider, + * because regular streams are only created when an event sound + * is played and then immediately destroyed. + * The stored event control should exist all the time. */ + previous = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->effects_bar)); + if (previous != NULL) { + name = mate_mixer_stream_control_get_name (previous); + + g_signal_handlers_disconnect_by_data (G_OBJECT (previous), dialog); + g_hash_table_remove (dialog->priv->bars, name); + } + + gvc_channel_bar_set_control (GVC_CHANNEL_BAR (dialog->priv->effects_bar), control); + + if (control != NULL) { + name = mate_mixer_stream_control_get_name (control); + g_hash_table_insert (dialog->priv->bars, + (gpointer) name, + dialog->priv->effects_bar); + + gtk_widget_set_sensitive (GTK_WIDGET (dialog->priv->effects_bar), TRUE); + } else + gtk_widget_set_sensitive (GTK_WIDGET (dialog->priv->effects_bar), FALSE); +} + +static void +on_stream_control_added (MateMixerStream *stream, + const gchar *name, + GvcMixerDialog *dialog) +{ + MateMixerStreamControl *control; + MateMixerStreamControlRole role; + + control = mate_mixer_stream_get_control (stream, name); + if G_UNLIKELY (control == NULL) + return; + + role = mate_mixer_stream_control_get_role (control); + + if (role == MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION) + add_application_control (dialog, control); +} + +static void +on_stream_control_removed (MateMixerStream *stream, + const gchar *name, + GvcMixerDialog *dialog) +{ + MateMixerStreamControl *control; + + control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar)); + if (control != NULL) { + const gchar *input_name = mate_mixer_stream_control_get_name (control); + + if (strcmp (name, input_name) == 0) { + // XXX probably can't even happen, but handle it somehow + return; + } + } + + control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->output_bar)); + if (control != NULL) { + const gchar *input_name = mate_mixer_stream_control_get_name (control); + + if (strcmp (name, input_name) == 0) { + // XXX probably can't even happen, but handle it somehow + return; + } + } + + /* No way to be sure that it is an application control, but we don't have + * any other than application bars that could match the name */ + remove_application_control (dialog, name); +} + +static void +add_stream (GvcMixerDialog *dialog, MateMixerStream *stream) +{ + GtkTreeModel *model = NULL; + GtkTreeIter iter; + const gchar *speakers = NULL; + const GList *controls; + gboolean is_default = FALSE; + MateMixerDirection direction; + + direction = mate_mixer_stream_get_direction (stream); + + if (direction == MATE_MIXER_DIRECTION_INPUT) { + MateMixerStream *input; + + input = mate_mixer_context_get_default_input_stream (dialog->priv->context); + if (stream == input) { + bar_set_stream (dialog, dialog->priv->input_bar, stream); + + update_input_settings (dialog); + is_default = TRUE; + } + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + } + else if (direction == MATE_MIXER_DIRECTION_OUTPUT) { + MateMixerStream *output; + MateMixerStreamControl *control; + + output = mate_mixer_context_get_default_output_stream (dialog->priv->context); + if (stream == output) { + bar_set_stream (dialog, dialog->priv->output_bar, stream); + + update_output_settings (dialog); + is_default = TRUE; + } + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + + control = mate_mixer_stream_get_default_control (stream); + if (G_LIKELY (control != NULL)) + speakers = gvc_channel_map_to_pretty_string (control); + } + + controls = mate_mixer_stream_list_controls (stream); + while (controls != NULL) { + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (controls->data); + MateMixerStreamControlRole role; + + role = mate_mixer_stream_control_get_role (control); + + if (role == MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION) + add_application_control (dialog, control); + + controls = controls->next; + } + + if (model != NULL) { + const gchar *name; + const gchar *label; + + name = mate_mixer_stream_get_name (stream); + label = mate_mixer_stream_get_label (stream); + + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + NAME_COLUMN, name, + LABEL_COLUMN, label, + ACTIVE_COLUMN, is_default, + SPEAKERS_COLUMN, speakers, + -1); + } + + // XXX find a way to disconnect when removed + g_signal_connect (G_OBJECT (stream), + "control-added", + G_CALLBACK (on_stream_control_added), + dialog); + g_signal_connect (G_OBJECT (stream), + "control-removed", + G_CALLBACK (on_stream_control_removed), + dialog); +} + +static void +update_device_test_visibility (GvcMixerDialog *dialog) +{ + MateMixerDevice *device; + MateMixerStream *stream; + + device = g_object_get_data (G_OBJECT (dialog->priv->hw_profile_combo), "device"); + if (G_UNLIKELY (device == NULL)) { + return; + } + + stream = find_device_test_stream (dialog, device); + + g_object_set (G_OBJECT (dialog->priv->hw_profile_combo), + "show-button", (stream != NULL), + NULL); +} + +static void +on_context_stream_added (MateMixerContext *context, + const gchar *name, + GvcMixerDialog *dialog) +{ + MateMixerStream *stream; + MateMixerDirection direction; + GtkWidget *bar; + + stream = mate_mixer_context_get_stream (context, name); + if (G_UNLIKELY (stream == NULL)) + return; + + direction = mate_mixer_stream_get_direction (stream); + + /* If the newly added stream belongs to the currently selected device and + * the test button is hidden, this stream may be the one to allow the + * sound test and therefore we may need to enable the button */ + if (dialog->priv->hw_profile_combo != NULL && direction == MATE_MIXER_DIRECTION_OUTPUT) { + MateMixerDevice *device1; + MateMixerDevice *device2; + + device1 = mate_mixer_stream_get_device (stream); + device2 = g_object_get_data (G_OBJECT (dialog->priv->hw_profile_combo), + "device"); + + if (device1 == device2) { + gboolean show_button; + + g_object_get (G_OBJECT (dialog->priv->hw_profile_combo), + "show-button", &show_button, + NULL); + + if (show_button == FALSE) + update_device_test_visibility (dialog); + } + } + + bar = g_hash_table_lookup (dialog->priv->bars, name); + if (G_UNLIKELY (bar != NULL)) + return; + + add_stream (dialog, stream); +} + +static void +remove_stream (GvcMixerDialog *dialog, const gchar *name) +{ + GtkWidget *bar; + GtkTreeIter iter; + GtkTreeModel *model; + + bar = g_hash_table_lookup (dialog->priv->bars, name); + + if (bar != NULL) { + g_debug ("Removing stream %s from bar %s", + name, + gvc_channel_bar_get_name (GVC_CHANNEL_BAR (bar))); + + bar_set_stream (dialog, bar, NULL); + } + + /* Remove from any models */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + if (find_tree_item_by_name (GTK_TREE_MODEL (model), + name, + NAME_COLUMN, + &iter) == TRUE) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + if (find_tree_item_by_name (GTK_TREE_MODEL (model), + name, + NAME_COLUMN, + &iter) == TRUE) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); +} + +static void +remove_application_control (GvcMixerDialog *dialog, const gchar *name) +{ + GtkWidget *bar; + + bar = g_hash_table_lookup (dialog->priv->bars, name); + if (G_UNLIKELY (bar == NULL)) + return; + + g_debug ("Removing application stream %s", name); + + /* We could call bar_set_stream_control here, but that would pointlessly + * invalidate the channel bar, so just remove it ourselves */ + g_hash_table_remove (dialog->priv->bars, name); + + gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (bar)), bar); + + if (G_UNLIKELY (dialog->priv->num_apps <= 0)) { + g_warn_if_reached (); + dialog->priv->num_apps = 1; + } + + dialog->priv->num_apps--; + + if (dialog->priv->num_apps == 0) + gtk_widget_show (dialog->priv->no_apps_label); +} + +static void +on_context_stream_removed (MateMixerContext *context, + const gchar *name, + GvcMixerDialog *dialog) +{ + if (dialog->priv->hw_profile_combo != NULL) { + gboolean show_button; + + g_object_get (G_OBJECT (dialog->priv->hw_profile_combo), + "show-button", &show_button, + NULL); + + if (show_button == TRUE) + update_device_test_visibility (dialog); + } + + remove_stream (dialog, name); +} + +static void +on_context_stored_control_added (MateMixerContext *context, + const gchar *name, + GvcMixerDialog *dialog) +{ + MateMixerStreamControl *control; + MateMixerStreamControlMediaRole media_role; + + control = MATE_MIXER_STREAM_CONTROL (mate_mixer_context_get_stored_control (context, name)); + if (G_UNLIKELY (control == NULL)) + return; + + media_role = mate_mixer_stream_control_get_media_role (control); + + if (media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT) + add_effects_control (dialog, control); +} + +static void +on_context_stored_control_removed (MateMixerContext *context, + const gchar *name, + GvcMixerDialog *dialog) +{ + GtkWidget *bar; + + bar = g_hash_table_lookup (dialog->priv->bars, name); + + if (bar != NULL) { + /* We only use a stored control in the effects bar */ + if (G_UNLIKELY (bar != dialog->priv->effects_bar)) { + g_warn_if_reached (); + return; + } + + bar_set_stream (dialog, bar, NULL); + } +} + +static gchar * +device_status (MateMixerDevice *device) +{ + guint inputs = 0; + guint outputs = 0; + gchar *inputs_str = NULL; + gchar *outputs_str = NULL; + const GList *streams; + + /* Get number of input and output streams in the device */ + streams = mate_mixer_device_list_streams (device); + while (streams != NULL) { + MateMixerStream *stream = MATE_MIXER_STREAM (streams->data); + MateMixerDirection direction; + + direction = mate_mixer_stream_get_direction (stream); + + if (direction == MATE_MIXER_DIRECTION_INPUT) + inputs++; + else if (direction == MATE_MIXER_DIRECTION_OUTPUT) + outputs++; + + streams = streams->next; + } + + if (inputs == 0 && outputs == 0) { + /* translators: + * The device has been disabled */ + return g_strdup (_("Disabled")); + } + + if (outputs > 0) { + /* translators: + * The number of sound outputs on a particular device */ + outputs_str = g_strdup_printf (ngettext ("%u Output", + "%u Outputs", + outputs), + outputs); + } + + if (inputs > 0) { + /* translators: + * The number of sound inputs on a particular device */ + inputs_str = g_strdup_printf (ngettext ("%u Input", + "%u Inputs", + inputs), + inputs); + } + + if (inputs_str != NULL && outputs_str != NULL) { + gchar *ret = g_strdup_printf ("%s / %s", + outputs_str, + inputs_str); + g_free (outputs_str); + g_free (inputs_str); + return ret; + } + + if (inputs_str != NULL) + return inputs_str; + + return outputs_str; +} + +static void +update_device_info (GvcMixerDialog *dialog, MateMixerDevice *device) +{ + GtkTreeModel *model = NULL; + GtkTreeIter iter; + const gchar *label; + const gchar *profile_label = NULL; + gchar *status; + MateMixerSwitch *profile_switch; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview)); + + if (find_tree_item_by_name (model, + mate_mixer_device_get_name (device), + HW_NAME_COLUMN, + &iter) == FALSE) + return; + + label = mate_mixer_device_get_label (device); + + profile_switch = find_device_profile_switch (device); + if (profile_switch != NULL) { + MateMixerSwitchOption *active; + + active = mate_mixer_switch_get_active_option (profile_switch); + if (G_LIKELY (active != NULL)) + profile_label = mate_mixer_switch_option_get_label (active); + } + + status = device_status (device); + + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + HW_LABEL_COLUMN, label, + HW_PROFILE_COLUMN, profile_label, + HW_STATUS_COLUMN, status, + -1); + g_free (status); +} + +static void +on_device_profile_notify (MateMixerSwitch *swtch, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + MateMixerSwitchOption *active; + + g_signal_handlers_block_by_func (G_OBJECT (dialog->priv->hw_profile_combo), + G_CALLBACK (on_combobox_option_changed), + dialog); + + active = mate_mixer_switch_get_active_option (swtch); + if (G_LIKELY (active != NULL)) { + const gchar *name = mate_mixer_switch_option_get_name (active); + + gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->hw_profile_combo), + name); + } + + g_signal_handlers_unblock_by_func (G_OBJECT (dialog->priv->hw_profile_combo), + G_CALLBACK (on_combobox_option_changed), + dialog); + + // XXX find device + // update_device_info (dialog, device); +} + +static void +add_device (GvcMixerDialog *dialog, MateMixerDevice *device) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GIcon *icon; + const gchar *name; + const gchar *label; + gchar *status; + const gchar *profile_label = NULL; + MateMixerSwitch *profile_switch; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview)); + + name = mate_mixer_device_get_name (device); + label = mate_mixer_device_get_label (device); + + if (find_tree_item_by_name (GTK_TREE_MODEL (model), + name, + HW_NAME_COLUMN, + &iter) == FALSE) + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + + icon = g_themed_icon_new_with_default_fallbacks (mate_mixer_device_get_icon (device)); + + profile_switch = find_device_profile_switch (device); + if (profile_switch != NULL) { + MateMixerSwitchOption *active; + + active = mate_mixer_switch_get_active_option (profile_switch); + if (G_LIKELY (active != NULL)) + profile_label = mate_mixer_switch_option_get_label (active); + + g_signal_connect (G_OBJECT (device), + "notify::active-option", + G_CALLBACK (on_device_profile_notify), + dialog); + } + + status = device_status (device); + + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + HW_NAME_COLUMN, name, + HW_LABEL_COLUMN, label, + HW_ICON_COLUMN, icon, + HW_PROFILE_COLUMN, profile_label, + HW_STATUS_COLUMN, status, + -1); + g_free (status); + +} + +static void +on_context_device_added (MateMixerContext *context, const gchar *name, GvcMixerDialog *dialog) +{ + MateMixerDevice *device; + + device = mate_mixer_context_get_device (context, name); + if (G_UNLIKELY (device == NULL)) + return; + + add_device (dialog, device); +} + +static void +on_context_device_removed (MateMixerContext *context, + const gchar *name, + GvcMixerDialog *dialog) +{ + GtkTreeIter iter; + GtkTreeModel *model; + + /* Remove from the device model */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview)); + + if (find_tree_item_by_name (GTK_TREE_MODEL (model), + name, + HW_NAME_COLUMN, + &iter) == TRUE) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); +} + +static void +make_label_bold (GtkLabel *label) +{ + PangoFontDescription *font_desc; + + font_desc = pango_font_description_new (); + + pango_font_description_set_weight (font_desc, PANGO_WEIGHT_BOLD); + + /* This will only affect the weight of the font, the rest is + * from the current state of the widget, which comes from the + * theme or user prefs, since the font desc only has the + * weight flag turned on. */ +#if GTK_CHECK_VERSION (3, 0, 0) + gtk_widget_override_font (GTK_WIDGET (label), font_desc); +#else + gtk_widget_modify_font (GTK_WIDGET (label), font_desc); +#endif + pango_font_description_free (font_desc); +} + +static void +on_input_radio_toggled (GtkCellRendererToggle *renderer, + gchar *path_str, + GvcMixerDialog *dialog) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + gboolean toggled = FALSE; + gchar *name = NULL; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + path = gtk_tree_path_new_from_string (path_str); + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + NAME_COLUMN, &name, + ACTIVE_COLUMN, &toggled, + -1); + if (toggled ^ 1) { + MateMixerStream *stream; + MateMixerBackendFlags flags; + + stream = mate_mixer_context_get_stream (dialog->priv->context, name); + if (G_UNLIKELY (stream == NULL)) { + g_warn_if_reached (); + g_free (name); + return; + } + + g_debug ("Default input stream selection changed to %s", name); + + // XXX cache this + flags = mate_mixer_context_get_backend_flags (dialog->priv->context); + + if (flags & MATE_MIXER_BACKEND_CAN_SET_DEFAULT_INPUT_STREAM) + mate_mixer_context_set_default_input_stream (dialog->priv->context, stream); + else + set_input_stream (dialog, stream); + } + g_free (name); +} + +static void +on_output_radio_toggled (GtkCellRendererToggle *renderer, + gchar *path_str, + GvcMixerDialog *dialog) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + gboolean toggled = FALSE; + gchar *name = NULL; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + path = gtk_tree_path_new_from_string (path_str); + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + NAME_COLUMN, &name, + ACTIVE_COLUMN, &toggled, + -1); + if (toggled ^ 1) { + MateMixerStream *stream; + MateMixerBackendFlags flags; + + stream = mate_mixer_context_get_stream (dialog->priv->context, name); + if (G_UNLIKELY (stream == NULL)) { + g_warn_if_reached (); + g_free (name); + return; + } + + g_debug ("Default output stream selection changed to %s", name); + + // XXX cache this + flags = mate_mixer_context_get_backend_flags (dialog->priv->context); + + if (flags & MATE_MIXER_BACKEND_CAN_SET_DEFAULT_OUTPUT_STREAM) + mate_mixer_context_set_default_output_stream (dialog->priv->context, stream); + else + set_output_stream (dialog, stream); + } + g_free (name); +} + +static void +stream_name_to_text (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + gchar *label; + gchar *speakers; + + gtk_tree_model_get (model, iter, + LABEL_COLUMN, &label, + SPEAKERS_COLUMN, &speakers, + -1); + + if (speakers != NULL) { + gchar *str = g_strdup_printf ("%s\n<i>%s</i>", + label, + speakers); + + g_object_set (cell, "markup", str, NULL); + g_free (str); + g_free (speakers); + } else { + g_object_set (cell, "text", label, NULL); + } + + g_free (label); +} + +static gint +compare_stream_treeview_items (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + gchar *desc_a = NULL; + gchar *desc_b = NULL; + gint result; + + gtk_tree_model_get (model, a, + LABEL_COLUMN, &desc_a, + -1); + gtk_tree_model_get (model, b, + LABEL_COLUMN, &desc_b, + -1); + + if (desc_a == NULL) { + g_free (desc_b); + return -1; + } + if (desc_b == NULL) { + g_free (desc_a); + return 1; + } + + result = g_ascii_strcasecmp (desc_a, desc_b); + + g_free (desc_a); + g_free (desc_b); + return result; +} + +static GtkWidget * +create_stream_treeview (GvcMixerDialog *dialog, GCallback on_toggled) +{ + GtkWidget *treeview; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + treeview = gtk_tree_view_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + + store = gtk_list_store_new (NUM_COLUMNS, + G_TYPE_ICON, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_BOOLEAN, + G_TYPE_STRING); + + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), + GTK_TREE_MODEL (store)); + + renderer = gtk_cell_renderer_toggle_new (); + gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (renderer), TRUE); + + column = gtk_tree_view_column_new_with_attributes (NULL, + renderer, + "active", ACTIVE_COLUMN, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + g_signal_connect (G_OBJECT (renderer), + "toggled", + G_CALLBACK (on_toggled), + dialog); + + gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (treeview), -1, + _("Name"), + gtk_cell_renderer_text_new (), + stream_name_to_text, + NULL, NULL); + + /* Keep the list of streams sorted by the name */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + LABEL_COLUMN, + GTK_SORT_ASCENDING); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), + LABEL_COLUMN, + compare_stream_treeview_items, + NULL, NULL); + return treeview; +} + +static void +on_test_speakers_clicked (GvcComboBox *widget, GvcMixerDialog *dialog) +{ + GtkWidget *d, + *test, + *container; + gchar *title; + MateMixerDevice *device; + MateMixerStream *stream; + + device = g_object_get_data (G_OBJECT (widget), "device"); + if (G_UNLIKELY (device == NULL)) { + g_warn_if_reached (); + return; + } + + stream = find_device_test_stream (dialog, device); + if (G_UNLIKELY (stream == NULL)) { + g_warn_if_reached (); + return; + } + + title = g_strdup_printf (_("Speaker Testing for %s"), + mate_mixer_device_get_label (device)); + + d = gtk_dialog_new_with_buttons (title, + GTK_WINDOW (dialog), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + "gtk-close", + GTK_RESPONSE_CLOSE, + NULL); + g_free (title); + + gtk_window_set_resizable (GTK_WINDOW (d), FALSE); + + test = gvc_speaker_test_new (stream); + gtk_widget_show (test); + + container = gtk_dialog_get_content_area (GTK_DIALOG (d)); + gtk_container_add (GTK_CONTAINER (container), test); + + gtk_dialog_run (GTK_DIALOG (d)); + gtk_widget_destroy (d); +} + +static GtkWidget * +create_profile_combo_box (GvcMixerDialog *dialog, + MateMixerSwitch *swtch, + const gchar *name, + const GList *items, + const gchar *active) +{ + GtkWidget *combobox; + + combobox = gvc_combo_box_new (name); + + gvc_combo_box_set_options (GVC_COMBO_BOX (combobox), items); + gvc_combo_box_set_active (GVC_COMBO_BOX (combobox), active); + + gvc_combo_box_set_size_group (GVC_COMBO_BOX (combobox), + dialog->priv->size_group, + FALSE); + + g_object_set (G_OBJECT (combobox), + "button-label", _("Test Speakers"), + NULL); + + g_object_set_data_full (G_OBJECT (combobox), + "switch", + g_object_ref (swtch), + g_object_unref); + + g_signal_connect (G_OBJECT (combobox), + "changed", + G_CALLBACK (on_combobox_option_changed), + dialog); + + g_signal_connect (G_OBJECT (combobox), + "button-clicked", + G_CALLBACK (on_test_speakers_clicked), + dialog); + + return combobox; +} + +static void +on_device_selection_changed (GtkTreeSelection *selection, GvcMixerDialog *dialog) +{ + GtkTreeIter iter; + gchar *name; + MateMixerDevice *device; + MateMixerSwitch *profile_switch; + + g_debug ("Device selection changed"); + + if (dialog->priv->hw_profile_combo != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->hw_settings_box), + dialog->priv->hw_profile_combo); + + dialog->priv->hw_profile_combo = NULL; + } + + if (gtk_tree_selection_get_selected (selection, NULL, &iter) == FALSE) + return; + + gtk_tree_model_get (gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview)), + &iter, + HW_NAME_COLUMN, &name, + -1); + + device = mate_mixer_context_get_device (dialog->priv->context, name); + if (G_UNLIKELY (device == NULL)) { + g_warn_if_reached (); + g_free (name); + return; + } + g_free (name); + + /* Profile/speaker test combo */ + profile_switch = find_device_profile_switch (device); + if (profile_switch != NULL) { + const GList *options; + const gchar *active_name = NULL; + MateMixerSwitchOption *active; + + options = mate_mixer_switch_list_options (profile_switch); + active = mate_mixer_switch_get_active_option (profile_switch); + if (active != NULL) + active_name = mate_mixer_switch_option_get_name (active); + + dialog->priv->hw_profile_combo = + create_profile_combo_box (dialog, + profile_switch, + _("_Profile:"), + options, + active_name); + + g_object_set_data_full (G_OBJECT (dialog->priv->hw_profile_combo), + "device", + g_object_ref (device), + g_object_unref); + + gtk_box_pack_start (GTK_BOX (dialog->priv->hw_settings_box), + dialog->priv->hw_profile_combo, + TRUE, TRUE, 6); + + /* Enable the speaker test button if the selected device + * is capable of sound output */ + update_device_test_visibility (dialog); + + gtk_widget_show (dialog->priv->hw_profile_combo); + } +} + +static void +on_notebook_switch_page (GtkNotebook *notebook, + GtkWidget *page, + guint page_num, + GvcMixerDialog *dialog) +{ + MateMixerStreamControl *control; + + // XXX because this is called too early in constructor + if (G_UNLIKELY (dialog->priv->input_bar == NULL)) + return; + + control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar)); + if (control == NULL) + return; + + if (page_num == PAGE_INPUT) + mate_mixer_stream_control_set_monitor_enabled (control, TRUE); + else + mate_mixer_stream_control_set_monitor_enabled (control, FALSE); +} + +static void +device_name_to_text (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + gchar *label = NULL; + gchar *profile = NULL; + gchar *status = NULL; + + gtk_tree_model_get (model, iter, + HW_LABEL_COLUMN, &label, + HW_PROFILE_COLUMN, &profile, + HW_STATUS_COLUMN, &status, + -1); + + if (G_LIKELY (status != NULL)) { + gchar *str; + + if (profile != NULL) + str = g_strdup_printf ("%s\n<i>%s</i>\n<i>%s</i>", + label, + status, + profile); + else + str = g_strdup_printf ("%s\n<i>%s</i>", + label, + status); + + g_object_set (cell, "markup", str, NULL); + g_free (str); + g_free (profile); + g_free (status); + } else + g_object_set (cell, "text", label, NULL); + + g_free (label); +} + +static gint +compare_device_treeview_items (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + gchar *desc_a = NULL; + gchar *desc_b = NULL; + gint result; + + gtk_tree_model_get (model, a, + HW_LABEL_COLUMN, &desc_a, + -1); + gtk_tree_model_get (model, b, + HW_LABEL_COLUMN, &desc_b, + -1); + + result = g_ascii_strcasecmp (desc_a, desc_b); + + g_free (desc_a); + g_free (desc_b); + return result; +} + +static GtkWidget * +create_device_treeview (GvcMixerDialog *dialog, GCallback on_changed) +{ + GtkWidget *treeview; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + + treeview = gtk_tree_view_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); + g_signal_connect (G_OBJECT (selection), + "changed", + on_changed, + dialog); + + store = gtk_list_store_new (HW_NUM_COLUMNS, + G_TYPE_ICON, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), + GTK_TREE_MODEL (store)); + + renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (G_OBJECT (renderer), + "stock-size", + GTK_ICON_SIZE_DIALOG, + NULL); + + column = gtk_tree_view_column_new_with_attributes (NULL, + renderer, + "gicon", HW_ICON_COLUMN, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (treeview), -1, + _("Name"), + gtk_cell_renderer_text_new (), + device_name_to_text, + NULL, NULL); + + /* Keep the list of streams sorted by the name */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + HW_LABEL_COLUMN, + GTK_SORT_ASCENDING); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), + HW_LABEL_COLUMN, + compare_device_treeview_items, + NULL, NULL); + return treeview; +} + +static void +dialog_accel_cb (GtkAccelGroup *accelgroup, + GObject *object, + guint key, + GdkModifierType mod, + GvcMixerDialog *self) +{ + gint num = -1; + gint i; + + for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) { + if (tab_accel_keys[i] == key) { + num = i; + break; + } + } + + if (num != -1) + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num); +} + +static GObject * +gvc_mixer_dialog_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerDialog *self; + GtkWidget *main_vbox; + GtkWidget *label; + GtkWidget *alignment; + GtkWidget *box; + GtkWidget *sbox; + GtkWidget *ebox; + GtkTreeSelection *selection; + GtkAccelGroup *accel_group; + GClosure *closure; + GtkTreeIter iter; + gint i; + const GList *list; + + object = G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->constructor (type, + n_construct_properties, + construct_params); + + self = GVC_MIXER_DIALOG (object); + + gtk_dialog_add_button (GTK_DIALOG (self), "gtk-close", GTK_RESPONSE_OK); + + main_vbox = gtk_dialog_get_content_area (GTK_DIALOG (self)); + gtk_box_set_spacing (GTK_BOX (main_vbox), 2); + + gtk_container_set_border_width (GTK_CONTAINER (self), 6); + +#if GTK_CHECK_VERSION (3, 0, 0) + self->priv->output_stream_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); +#else + self->priv->output_stream_box = gtk_hbox_new (FALSE, 12); +#endif + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 0, 0, 0); + gtk_container_add (GTK_CONTAINER (alignment), self->priv->output_stream_box); + gtk_box_pack_start (GTK_BOX (main_vbox), + alignment, + FALSE, FALSE, 0); + + self->priv->output_bar = create_bar (self, TRUE, TRUE); + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->output_bar), + _("_Output volume: ")); + + gtk_widget_show (self->priv->output_bar); + gtk_widget_set_sensitive (self->priv->output_bar, FALSE); + + gtk_box_pack_start (GTK_BOX (self->priv->output_stream_box), + self->priv->output_bar, TRUE, TRUE, 12); + + self->priv->notebook = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (main_vbox), + self->priv->notebook, + TRUE, TRUE, 0); + + g_signal_connect (G_OBJECT (self->priv->notebook), + "switch-page", + G_CALLBACK (on_notebook_switch_page), + self); + + gtk_container_set_border_width (GTK_CONTAINER (self->priv->notebook), 5); + + /* Set up accels (borrowed from Empathy) */ + accel_group = gtk_accel_group_new (); + gtk_window_add_accel_group (GTK_WINDOW (self), accel_group); + + for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) { + closure = g_cclosure_new (G_CALLBACK (dialog_accel_cb), + self, + NULL); + gtk_accel_group_connect (accel_group, + tab_accel_keys[i], + GDK_MOD1_MASK, + 0, + closure); + } + + g_object_unref (accel_group); + + /* Create notebook pages */ +#if GTK_CHECK_VERSION (3, 0, 0) + self->priv->sound_effects_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); +#else + self->priv->sound_effects_box = gtk_vbox_new (FALSE, 6); +#endif + gtk_container_set_border_width (GTK_CONTAINER (self->priv->sound_effects_box), 12); + + label = gtk_label_new (_("Sound Effects")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->sound_effects_box, + label); + + self->priv->effects_bar = create_bar (self, TRUE, TRUE); + gtk_box_pack_start (GTK_BOX (self->priv->sound_effects_box), + self->priv->effects_bar, FALSE, FALSE, 0); + + gvc_channel_bar_set_show_marks (GVC_CHANNEL_BAR (self->priv->effects_bar), FALSE); + gvc_channel_bar_set_extended (GVC_CHANNEL_BAR (self->priv->effects_bar), FALSE); + + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->effects_bar), + _("_Alert volume: ")); + + gtk_widget_show (self->priv->effects_bar); + gtk_widget_set_sensitive (self->priv->effects_bar, FALSE); + + self->priv->sound_theme_chooser = gvc_sound_theme_chooser_new (); + + gtk_box_pack_start (GTK_BOX (self->priv->sound_effects_box), + self->priv->sound_theme_chooser, + TRUE, TRUE, 6); + +#if GTK_CHECK_VERSION (3, 0, 0) + self->priv->hw_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); +#else + self->priv->hw_box = gtk_vbox_new (FALSE, 12); +#endif + gtk_container_set_border_width (GTK_CONTAINER (self->priv->hw_box), 12); + + label = gtk_label_new (_("Hardware")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->hw_box, + label); + + box = gtk_frame_new (_("C_hoose a device to configure:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + make_label_bold (GTK_LABEL (label)); + gtk_label_set_use_underline (GTK_LABEL (label), TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->hw_box), box, TRUE, TRUE, 0); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_container_add (GTK_CONTAINER (box), alignment); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + + self->priv->hw_treeview = create_device_treeview (self, + G_CALLBACK (on_device_selection_changed)); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->hw_treeview); + + box = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (box), self->priv->hw_treeview); + gtk_container_add (GTK_CONTAINER (alignment), box); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->hw_treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + box = gtk_frame_new (_("Settings for the selected device:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + make_label_bold (GTK_LABEL (label)); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->hw_box), box, FALSE, TRUE, 12); + +#if GTK_CHECK_VERSION (3, 0, 0) + self->priv->hw_settings_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); +#else + self->priv->hw_settings_box = gtk_vbox_new (FALSE, 12); +#endif + + gtk_container_add (GTK_CONTAINER (box), self->priv->hw_settings_box); + +#if GTK_CHECK_VERSION (3, 0, 0) + self->priv->input_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); +#else + self->priv->input_box = gtk_vbox_new (FALSE, 12); +#endif + + gtk_container_set_border_width (GTK_CONTAINER (self->priv->input_box), 12); + + label = gtk_label_new (_("Input")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->input_box, + label); + + self->priv->input_bar = create_bar (self, TRUE, TRUE); + + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->input_bar), + _("_Input volume: ")); + + gvc_channel_bar_set_low_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar), + "audio-input-microphone-low"); + gvc_channel_bar_set_high_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar), + "audio-input-microphone-high"); + + gtk_widget_show (self->priv->input_bar); + gtk_widget_set_sensitive (self->priv->input_bar, FALSE); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + gtk_container_add (GTK_CONTAINER (alignment), self->priv->input_bar); + gtk_box_pack_start (GTK_BOX (self->priv->input_box), + alignment, + FALSE, FALSE, 0); + +#if GTK_CHECK_VERSION (3, 0, 0) + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + sbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + ebox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); +#else + box = gtk_hbox_new (FALSE, 6); + sbox = gtk_hbox_new (FALSE, 6); + ebox = gtk_hbox_new (FALSE, 6); +#endif + + gtk_box_pack_start (GTK_BOX (self->priv->input_box), + box, + FALSE, FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), + sbox, + FALSE, FALSE, 0); + + label = gtk_label_new (_("Input level:")); + gtk_box_pack_start (GTK_BOX (sbox), + label, + FALSE, FALSE, 0); + gtk_size_group_add_widget (self->priv->size_group, sbox); + + self->priv->input_level_bar = gvc_level_bar_new (); + gvc_level_bar_set_orientation (GVC_LEVEL_BAR (self->priv->input_level_bar), + GTK_ORIENTATION_HORIZONTAL); + gvc_level_bar_set_scale (GVC_LEVEL_BAR (self->priv->input_level_bar), + GVC_LEVEL_SCALE_LINEAR); + gtk_box_pack_start (GTK_BOX (box), + self->priv->input_level_bar, + TRUE, TRUE, 6); + + gtk_box_pack_start (GTK_BOX (box), + ebox, + FALSE, FALSE, 0); + gtk_size_group_add_widget (self->priv->size_group, ebox); + +#if GTK_CHECK_VERSION (3, 0, 0) + self->priv->input_settings_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); +#else + self->priv->input_settings_box = gtk_hbox_new (FALSE, 6); +#endif + gtk_box_pack_start (GTK_BOX (self->priv->input_box), + self->priv->input_settings_box, + FALSE, FALSE, 0); + + box = gtk_frame_new (_("C_hoose a device for sound input:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + make_label_bold (GTK_LABEL (label)); + gtk_label_set_use_underline (GTK_LABEL (label), TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->input_box), box, TRUE, TRUE, 0); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_container_add (GTK_CONTAINER (box), alignment); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + + self->priv->input_treeview = + create_stream_treeview (self, G_CALLBACK (on_input_radio_toggled)); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->input_treeview); + + box = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (box), self->priv->input_treeview); + gtk_container_add (GTK_CONTAINER (alignment), box); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->input_treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + /* Output page */ + self->priv->output_box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (self->priv->output_box), 12); + label = gtk_label_new (_("Output")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->output_box, + label); + + box = gtk_frame_new (_("C_hoose a device for sound output:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + make_label_bold (GTK_LABEL (label)); + gtk_label_set_use_underline (GTK_LABEL (label), TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->output_box), box, TRUE, TRUE, 0); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_container_add (GTK_CONTAINER (box), alignment); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + + self->priv->output_treeview = create_stream_treeview (self, + G_CALLBACK (on_output_radio_toggled)); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->output_treeview); + + box = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (box), self->priv->output_treeview); + gtk_container_add (GTK_CONTAINER (alignment), box); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->output_treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + box = gtk_frame_new (_("Settings for the selected device:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + make_label_bold (GTK_LABEL (label)); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->output_box), box, FALSE, FALSE, 12); + self->priv->output_settings_box = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (box), self->priv->output_settings_box); + + self->priv->output_settings_frame = box; + + /* Applications */ + self->priv->applications_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (self->priv->applications_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (self->priv->applications_window), + GTK_SHADOW_IN); + +#if GTK_CHECK_VERSION (3, 0, 0) + self->priv->applications_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); +#else + self->priv->applications_box = gtk_vbox_new (FALSE, 12); +#endif + gtk_container_set_border_width (GTK_CONTAINER (self->priv->applications_box), 12); + +#if GTK_CHECK_VERSION (3, 8, 0) + gtk_container_add (GTK_CONTAINER (self->priv->applications_window), + self->priv->applications_box); +#else + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (self->priv->applications_window), + self->priv->applications_box); +#endif + + label = gtk_label_new (_("Applications")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->applications_window, + label); + + self->priv->no_apps_label = gtk_label_new (_("No application is currently playing or recording audio.")); + gtk_box_pack_start (GTK_BOX (self->priv->applications_box), + self->priv->no_apps_label, + TRUE, TRUE, 0); + + gtk_widget_show_all (main_vbox); + + list = mate_mixer_context_list_streams (self->priv->context); + while (list != NULL) { + add_stream (self, MATE_MIXER_STREAM (list->data)); + list = list->next; + } + + list = mate_mixer_context_list_devices (self->priv->context); + while (list != NULL) { + add_device (self, MATE_MIXER_DEVICE (list->data)); + list = list->next; + } + + /* Find an event role stream */ + list = mate_mixer_context_list_stored_controls (self->priv->context); + while (list != NULL) { + MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (list->data); + MateMixerStreamControlMediaRole media_role; + + media_role = mate_mixer_stream_control_get_media_role (control); + + if (media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT) { + add_effects_control (self, control); + break; + } + list = list->next; + } + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->hw_treeview)); + + /* Select the first device in the list */ + // XXX handle no devices + if (gtk_tree_selection_get_selected (selection, NULL, NULL) == FALSE) { + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->hw_treeview)); + + if (gtk_tree_model_get_iter_first (model, &iter)) + gtk_tree_selection_select_iter (selection, &iter); + } + + return object; +} + +static MateMixerContext * +gvc_mixer_dialog_get_context (GvcMixerDialog *dialog) +{ + return dialog->priv->context; +} + +static void +gvc_mixer_dialog_set_context (GvcMixerDialog *dialog, MateMixerContext *context) +{ + dialog->priv->context = g_object_ref (context); + + g_signal_connect (G_OBJECT (dialog->priv->context), + "stream-added", + G_CALLBACK (on_context_stream_added), + dialog); + g_signal_connect (G_OBJECT (dialog->priv->context), + "stream-removed", + G_CALLBACK (on_context_stream_removed), + dialog); + + g_signal_connect (G_OBJECT (dialog->priv->context), + "device-added", + G_CALLBACK (on_context_device_added), + dialog); + g_signal_connect (G_OBJECT (dialog->priv->context), + "device-removed", + G_CALLBACK (on_context_device_removed), + dialog); + + g_signal_connect (G_OBJECT (dialog->priv->context), + "notify::default-input-stream", + G_CALLBACK (on_context_default_input_stream_notify), + dialog); + g_signal_connect (G_OBJECT (dialog->priv->context), + "notify::default-output-stream", + G_CALLBACK (on_context_default_output_stream_notify), + dialog); + + g_signal_connect (G_OBJECT (dialog->priv->context), + "stored-control-added", + G_CALLBACK (on_context_stored_control_added), + dialog); + g_signal_connect (G_OBJECT (dialog->priv->context), + "stored-control-removed", + G_CALLBACK (on_context_stored_control_removed), + dialog); + + g_object_notify (G_OBJECT (dialog), "context"); +} + +static void +gvc_mixer_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerDialog *self = GVC_MIXER_DIALOG (object); + + switch (prop_id) { + case PROP_CONTEXT: + gvc_mixer_dialog_set_context (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerDialog *self = GVC_MIXER_DIALOG (object); + + switch (prop_id) { + case PROP_CONTEXT: + g_value_set_object (value, gvc_mixer_dialog_get_context (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_dialog_dispose (GObject *object) +{ + GvcMixerDialog *dialog = GVC_MIXER_DIALOG (object); + + if (dialog->priv->context != NULL) { + g_signal_handlers_disconnect_by_data (G_OBJECT (dialog->priv->context), + dialog); + + g_clear_object (&dialog->priv->context); + } + + G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->dispose (object); +} + +static void +gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_mixer_dialog_constructor; + object_class->dispose = gvc_mixer_dialog_dispose; + object_class->finalize = gvc_mixer_dialog_finalize; + object_class->set_property = gvc_mixer_dialog_set_property; + object_class->get_property = gvc_mixer_dialog_get_property; + + g_object_class_install_property (object_class, + PROP_CONTEXT, + g_param_spec_object ("context", + "Context", + "MateMixer context", + MATE_MIXER_TYPE_CONTEXT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (klass, sizeof (GvcMixerDialogPrivate)); +} + +static void +gvc_mixer_dialog_init (GvcMixerDialog *dialog) +{ + dialog->priv = GVC_MIXER_DIALOG_GET_PRIVATE (dialog); + + dialog->priv->bars = g_hash_table_new (g_str_hash, g_str_equal); + dialog->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); +} + +static void +gvc_mixer_dialog_finalize (GObject *object) +{ + GvcMixerDialog *dialog; + + dialog = GVC_MIXER_DIALOG (object); + + g_hash_table_destroy (dialog->priv->bars); + + G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->finalize (object); +} + +GvcMixerDialog * +gvc_mixer_dialog_new (MateMixerContext *context) +{ + return g_object_new (GVC_TYPE_MIXER_DIALOG, + "icon-name", "multimedia-volume-control", + "title", _("Sound Preferences"), + "context", context, + NULL); +} + +gboolean +gvc_mixer_dialog_set_page (GvcMixerDialog *self, const gchar *page) +{ + guint num = 0; + + g_return_val_if_fail (GVC_IS_MIXER_DIALOG (self), FALSE); + + if (page != NULL) { + if (g_str_equal (page, "effects")) + num = PAGE_EFFECTS; + else if (g_str_equal (page, "hardware")) + num = PAGE_HARDWARE; + else if (g_str_equal (page, "input")) + num = PAGE_INPUT; + else if (g_str_equal (page, "output")) + num = PAGE_OUTPUT; + else if (g_str_equal (page, "applications")) + num = PAGE_APPLICATIONS; + } + + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num); + + return TRUE; +} |