/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2008 William Jon McCann
 * Copyright (C) 2012 Conor Curran
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h>

#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <pulse/pulseaudio.h>


#include "gvc-channel-bar.h"
#include "gvc-balance-bar.h"
#include "gvc-combo-box.h"
#include "gvc-mixer-control.h"
#include "gvc-mixer-card.h"
#include "gvc-mixer-ui-device.h"
#include "gvc-mixer-sink.h"
#include "gvc-mixer-source.h"
#include "gvc-mixer-source-output.h"
#include "gvc-mixer-dialog.h"
#include "gvc-sound-theme-chooser.h"
#include "gvc-level-bar.h"
#include "gvc-speaker-test.h"
#include "gvc-mixer-control-private.h"

#define SCALE_SIZE 128

#define GVC_MIXER_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_DIALOG, GvcMixerDialogPrivate))

struct GvcMixerDialogPrivate
{
        GvcMixerControl *mixer_control;
        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       *no_apps_label;
        GtkWidget       *output_treeview;
        GtkWidget       *output_settings_box;
        GtkWidget       *output_balance_bar;
        GtkWidget       *output_fade_bar;
        GtkWidget       *output_lfe_bar;
        GtkWidget       *output_profile_combo;
        GtkWidget       *input_profile_combo;
        GtkWidget       *input_treeview;
        GtkWidget       *input_settings_box;
        GtkWidget       *sound_theme_chooser;
        GtkWidget       *click_feedback_button;
        GtkWidget       *audible_bell_button;
        GtkSizeGroup    *size_group;
        GtkWidget       *selected_output_label;
        GtkWidget       *selected_input_label;
        GtkWidget       *test_output_button;
        GSettings       *sound_settings;
        GSettings       *indicator_settings;

        gdouble          last_input_peak;
        guint            num_apps;
};

enum {
        NAME_COLUMN,
        DEVICE_COLUMN,
        ACTIVE_COLUMN,
        ID_COLUMN,
        SPEAKERS_COLUMN,
        ICON_COLUMN,
        NUM_COLUMNS
};

enum {
        HW_ID_COLUMN,
        HW_ICON_COLUMN,
        HW_NAME_COLUMN,
        HW_STATUS_COLUMN,
        HW_PROFILE_COLUMN,
        HW_PROFILE_HUMAN_COLUMN,
        HW_SENSITIVE_COLUMN,
        HW_NUM_COLUMNS
};

enum
{
        PROP_0,
        PROP_MIXER_CONTROL
};

static void     gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass);
static void     gvc_mixer_dialog_init       (GvcMixerDialog      *mixer_dialog);
static void     gvc_mixer_dialog_finalize   (GObject             *object);

static void     bar_set_stream              (GvcMixerDialog      *dialog,
                                             GtkWidget           *bar,
                                             GvcMixerStream      *stream);

static void     on_adjustment_value_changed (GtkAdjustment  *adjustment,
                                             GvcMixerDialog *dialog);
static void  	on_control_output_added (GvcMixerControl *control,
                                       guint            id,
                                       GvcMixerDialog  *dialog);
static void   on_control_active_output_update (GvcMixerControl *control,
                                               guint            id,
                                               GvcMixerDialog  *dialog);
                                       
static void   on_control_active_input_update (GvcMixerControl *control,
                                              guint            id,
                                              GvcMixerDialog  *dialog);

G_DEFINE_TYPE (GvcMixerDialog, gvc_mixer_dialog, GTK_TYPE_BOX)


static void
update_description (GvcMixerDialog *dialog,
                    guint column,
                    const char *value,
                    GvcMixerStream *stream)
{
        GtkTreeModel *model;
        GtkTreeIter   iter;
        guint         id;

        if (GVC_IS_MIXER_SOURCE (stream))
                model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
        else if (GVC_IS_MIXER_SINK (stream))
                model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
        else
                g_assert_not_reached ();

        if (gtk_tree_model_get_iter_first (model, &iter) == FALSE){
                g_warning ("The tree is empty => Cannot update the description");
                return;        
        }

        id = gvc_mixer_stream_get_id (stream);
        do {
                guint       current_id;

                gtk_tree_model_get (model, &iter,
                                    ID_COLUMN, &current_id,
                                    -1);
                if (id != current_id)
                        continue;

                gtk_list_store_set (GTK_LIST_STORE (model),
                                    &iter,
                                    column, value,
                                    -1);
                break;
        } while (gtk_tree_model_iter_next (model, &iter));
}

static void
profile_selection_changed (GvcComboBox *combo_box,
                           const char  *profile,
                           GvcMixerDialog *dialog)
{	
        g_debug ("profile_selection_changed - %s", profile);
        GvcMixerUIDevice *out;
        out = g_object_get_data (G_OBJECT (combo_box), "uidevice");
        
        if (out == NULL) {
                g_warning ("Could not find Output for profile combo box");
                return;
        }

        g_debug (" \n on profile selection changed on output with \n description %s \n origin %s \n id %i \n \n",
                gvc_mixer_ui_device_get_description (out),
                gvc_mixer_ui_device_get_origin (out),
                gvc_mixer_ui_device_get_id (out));

        if (gvc_mixer_control_change_profile_on_selected_device (dialog->priv->mixer_control, out, profile) == FALSE) {
                g_warning ("Could not change profile on device %s",
                           gvc_mixer_ui_device_get_description (out));
        }
}

#define DECAY_STEP .15

static void
update_input_peak (GvcMixerDialog *dialog,
                   gdouble         v)
{
        GtkAdjustment *adj;

        if (dialog->priv->last_input_peak >= DECAY_STEP) {
                if (v < dialog->priv->last_input_peak - DECAY_STEP) {
                        v = dialog->priv->last_input_peak - DECAY_STEP;
                }
        }

        dialog->priv->last_input_peak = v;

        adj = gvc_level_bar_get_peak_adjustment (GVC_LEVEL_BAR (dialog->priv->input_level_bar));
        if (v >= 0) {
                gtk_adjustment_set_value (adj, v);
        } else {
                gtk_adjustment_set_value (adj, 0.0);
        }
}

static void
update_input_meter (GvcMixerDialog *dialog,
                    uint32_t        source_index,
                    uint32_t        sink_input_idx,
                    double          v)
{
        update_input_peak (dialog, v);
}

static void
on_monitor_suspended_callback (pa_stream *s,
                               void      *userdata)
{
        GvcMixerDialog *dialog;

        dialog = userdata;

        if (pa_stream_is_suspended (s)) {
                g_debug ("Stream suspended");
                update_input_meter (dialog,
                                    pa_stream_get_device_index (s),
                                    PA_INVALID_INDEX,
                                    -1);
        }
}

static void
on_monitor_read_callback (pa_stream *s,
                          size_t     length,
                          void      *userdata)
{
        GvcMixerDialog *dialog;
        const void     *data;
        double          v;

        dialog = userdata;

        if (pa_stream_peek (s, &data, &length) < 0) {
                g_warning ("Failed to read data from stream");
                return;
        }

        if (!data) {
                pa_stream_drop (s);
                return;
        }

        assert (length > 0);
        assert (length % sizeof (float) == 0);

        v = ((const float *) data)[length / sizeof (float) -1];

        pa_stream_drop (s);

        if (v < 0) {
                v = 0;
        }
        if (v > 1) {
                v = 1;
        }

        update_input_meter (dialog,
                            pa_stream_get_device_index (s),
                            pa_stream_get_monitor_stream (s),
                            v);
}

static void
create_monitor_stream_for_source (GvcMixerDialog *dialog,
                                  GvcMixerStream *stream)
{
        pa_stream     *s;
        char           t[16];
        pa_buffer_attr attr;
        pa_sample_spec ss;
        pa_context    *context;
        int            res;
        pa_proplist   *proplist;
        gboolean       has_monitor;

        if (stream == NULL) {
                g_debug ("\n create_monitor_stream_for_source - stream is null - returning\n");
                return;
        }
        has_monitor = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (stream), "has-monitor"));
        if (has_monitor != FALSE) {
                g_debug ("\n create_monitor_stream_for_source, has monitor is not false - returning \n");
                return;
        }

        g_debug ("Create monitor for %u",
                 gvc_mixer_stream_get_index (stream));

        context = gvc_mixer_control_get_pa_context (dialog->priv->mixer_control);

        if (pa_context_get_server_protocol_version (context) < 13) {
                g_debug ("\n create_monitor_stream_for_source - protocol version is less 13 \n");
                return;
        }

        ss.channels = 1;
        ss.format = PA_SAMPLE_FLOAT32;
        ss.rate = 25;

        memset (&attr, 0, sizeof (attr));
        attr.fragsize = sizeof (float);
        attr.maxlength = (uint32_t) -1;

        snprintf (t, sizeof (t), "%u", gvc_mixer_stream_get_index (stream));

        proplist = pa_proplist_new ();
        pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "org.gnome.VolumeControl");
        s = pa_stream_new_with_proplist (context, _("Peak detect"), &ss, NULL, proplist);
        pa_proplist_free (proplist);
        if (s == NULL) {
                g_warning ("Failed to create monitoring stream");
                return;
        }

        pa_stream_set_read_callback (s, on_monitor_read_callback, dialog);
        pa_stream_set_suspended_callback (s, on_monitor_suspended_callback, dialog);

        res = pa_stream_connect_record (s,
                                        t,
                                        &attr,
                                        (pa_stream_flags_t) (PA_STREAM_DONT_MOVE
                                                             |PA_STREAM_PEAK_DETECT
                                                             |PA_STREAM_ADJUST_LATENCY));
        if (res < 0) {
                g_warning ("Failed to connect monitoring stream");
                pa_stream_unref (s);
        } else {
                g_object_set_data (G_OBJECT (stream), "has-monitor", GINT_TO_POINTER (TRUE));
                g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream", s);
                g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "stream", stream);
        }
}


static void
stop_monitor_stream_for_source (GvcMixerDialog *dialog)
{
        pa_stream      *s;
        pa_context     *context;
        int             res = 0;
        GvcMixerStream *stream;

        stream = g_object_get_data (G_OBJECT (dialog->priv->input_level_bar), "stream");

        if (stream == NULL){
                g_debug ("\n stop_monitor_stream_for_source - gvcstream is null - returning \n");                
                return;
        }
        else{
                g_debug ("\n stop_monitor_stream_for_source - gvcstream is not null - continue \n");                                
        }

        s = g_object_get_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream");

        if (s != NULL){
                res = pa_stream_disconnect (s);
                if (res == 0) {
                        g_debug("stream has been disconnected");
                }
                else {
                        g_warning ("pa_stream_disconnect failed, res = %d", res);
                        /* If disconnect failed, at least make sure we don't show the data */       
                        pa_stream_set_read_callback (s, NULL, NULL);
                }
                pa_stream_unref (s);
                g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream", NULL);        
        }
                
        context = gvc_mixer_control_get_pa_context (dialog->priv->mixer_control);

        if (pa_context_get_server_protocol_version (context) < 13) {
                return;
        }
        g_object_set_data (G_OBJECT (stream), "has-monitor", GINT_TO_POINTER (FALSE));
        g_debug ("Stopping monitor");
        g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "stream", NULL);
}

static void
gvc_mixer_dialog_set_mixer_control (GvcMixerDialog  *dialog,
                                    GvcMixerControl *control)
{
        g_return_if_fail (GVC_MIXER_DIALOG (dialog));
        g_return_if_fail (GVC_IS_MIXER_CONTROL (control));

        g_object_ref (control);

        if (dialog->priv->mixer_control != NULL) {
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      G_CALLBACK (on_control_active_output_update),
                                                      dialog);
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      G_CALLBACK (on_control_active_input_update),
                                                      dialog);
                g_object_unref (dialog->priv->mixer_control);
        }

        dialog->priv->mixer_control = control;

        g_signal_connect (dialog->priv->mixer_control,
                          "active-output-update",
                          G_CALLBACK (on_control_active_output_update),
                          dialog);
        g_signal_connect (dialog->priv->mixer_control,
                          "active-input-update",
                          G_CALLBACK (on_control_active_input_update),
                          dialog);

        g_object_notify (G_OBJECT (dialog), "mixer-control");
}

static GvcMixerControl *
gvc_mixer_dialog_get_mixer_control (GvcMixerDialog *dialog)
{
        g_return_val_if_fail (GVC_IS_MIXER_DIALOG (dialog), NULL);

        return dialog->priv->mixer_control;
}

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_MIXER_CONTROL:
                gvc_mixer_dialog_set_mixer_control (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_MIXER_CONTROL:
                g_value_set_object (value, gvc_mixer_dialog_get_mixer_control (self));
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
        }
}

static void
on_adjustment_value_changed (GtkAdjustment  *adjustment,
                             GvcMixerDialog *dialog)
{
        GvcMixerStream *stream;

        stream = g_object_get_data (G_OBJECT (adjustment), "gvc-mixer-dialog-stream");
        if (stream != NULL) {
                GObject *bar;
                gdouble volume, rounded;
                char *name;

                volume = gtk_adjustment_get_value (adjustment);
                rounded = round (volume);

                bar = g_object_get_data (G_OBJECT (adjustment), "gvc-mixer-dialog-bar");
                g_object_get (bar, "name", &name, NULL);
                g_debug ("Setting stream volume %lf (rounded: %lf) for bar '%s'", volume, rounded, name);
                g_free (name);

                /* FIXME would need to do that in the balance bar really... */
                /* Make sure we do not unmute muted streams, there's a button for that */
                if (volume == 0.0)
                        gvc_mixer_stream_set_is_muted (stream, TRUE);
                /* Only push the volume if it's actually changed */
                if (gvc_mixer_stream_set_volume(stream, (pa_volume_t) rounded) != FALSE)
                        gvc_mixer_stream_push_volume (stream);
        }
}

static void
on_bar_is_muted_notify (GObject        *object,
                        GParamSpec     *pspec,
                        GvcMixerDialog *dialog)
{
        gboolean        is_muted;
        GvcMixerStream *stream;

        is_muted = gvc_channel_bar_get_is_muted (GVC_CHANNEL_BAR (object));

        stream = g_object_get_data (object, "gvc-mixer-dialog-stream");
        if (stream != NULL) {
                gvc_mixer_stream_change_is_muted (stream, is_muted);
        } else {
                char *name;
                g_object_get (object, "name", &name, NULL);
                g_warning ("Unable to find stream for bar '%s'", name);
                g_free (name);
        }
}

static GtkWidget *
lookup_bar_for_stream (GvcMixerDialog *dialog,
                       GvcMixerStream *stream)
{
        GtkWidget *bar;

        bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)));

        return bar;
}

// TODO
// Do we need this ?
// UI devices now pull description material mainly for the card ports.
// Therefore the need for stream description dynamic changes more than ever seems unneccessary. 
static void
on_stream_description_notify (GvcMixerStream *stream,
                              GParamSpec     *pspec,
                              GvcMixerDialog *dialog)
{
        update_description (dialog, NAME_COLUMN,
                            gvc_mixer_stream_get_description (stream),
                            stream);
}

static void
on_stream_volume_notify (GObject        *object,
                         GParamSpec     *pspec,
                         GvcMixerDialog *dialog)
{
        GvcMixerStream *stream;
        GtkWidget      *bar;
        GtkAdjustment  *adj;
        stream = GVC_MIXER_STREAM (object);

        bar = lookup_bar_for_stream (dialog, stream);

        if (bar == NULL) {
                if (stream == gvc_mixer_control_get_default_sink(dialog->priv->mixer_control)) {
                        bar = dialog->priv->output_bar;
                }
                else if(stream == gvc_mixer_control_get_default_source(dialog->priv->mixer_control)) {
                        bar = dialog->priv->input_bar;
                }
                else{
                        g_warning ("Unable to find bar for stream %s in on_stream_volume_notify()",
                                   gvc_mixer_stream_get_name (stream));
                        return;
                }
        }

        adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (bar)));

        g_signal_handlers_block_by_func (adj,
                                         on_adjustment_value_changed,
                                         dialog);

        gtk_adjustment_set_value (adj,
                                  gvc_mixer_stream_get_volume (stream));

        g_signal_handlers_unblock_by_func (adj,
                                           on_adjustment_value_changed,
                                           dialog);
}

static void
on_stream_is_muted_notify (GObject        *object,
                           GParamSpec     *pspec,
                           GvcMixerDialog *dialog)
{
        GvcMixerStream *stream;
        GtkWidget      *bar;
        gboolean        is_muted;

        stream = GVC_MIXER_STREAM (object);

        bar = lookup_bar_for_stream (dialog, stream);

        if (bar == NULL) {
                if (stream == gvc_mixer_control_get_default_sink(dialog->priv->mixer_control)) {
                        bar = dialog->priv->output_bar;
                }
                else if(stream == gvc_mixer_control_get_default_source(dialog->priv->mixer_control)) {
                        bar = dialog->priv->input_bar;
                }                
                else{
                        g_warning ("Unable to find bar for stream %s in on_stream_muted_notify()",
                                   gvc_mixer_stream_get_name (stream));
                        return;
                }
        }


        is_muted = gvc_mixer_stream_get_is_muted (stream);
        gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (bar),
                                      is_muted);

        if (stream == gvc_mixer_control_get_default_sink (dialog->priv->mixer_control)) {
                gtk_widget_set_sensitive (dialog->priv->applications_box,
                                          !is_muted);
        }

}

static void
save_bar_for_stream (GvcMixerDialog *dialog,
                     GvcMixerStream *stream,
                     GtkWidget      *bar)
{
        g_debug ("\n saving bar for stream %s",
                 gvc_mixer_stream_get_name (stream));
        g_hash_table_insert (dialog->priv->bars,
                             GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)),
                             bar);
}

static GtkWidget *
create_bar (GvcMixerDialog *dialog,
            gboolean        add_to_size_group,
            gboolean        symmetric)
{
        GtkWidget *bar;

        bar = gvc_channel_bar_new ();
        gtk_widget_set_sensitive (bar, FALSE);
        if (add_to_size_group && dialog->priv->size_group != NULL) {
                gvc_channel_bar_set_size_group (GVC_CHANNEL_BAR (bar),
                                                dialog->priv->size_group,
                                                symmetric);
        }
        gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (bar),
                                         GTK_ORIENTATION_HORIZONTAL);
        gvc_channel_bar_set_show_mute (GVC_CHANNEL_BAR (bar),
                                       TRUE);
        g_signal_connect (bar,
                          "notify::is-muted",
                          G_CALLBACK (on_bar_is_muted_notify),
                          dialog);
        return bar;
}

static GtkWidget *
create_app_bar (GvcMixerDialog *dialog,
                const char     *name,
                const char     *icon_name)
{
        GtkWidget *bar;

        bar = create_bar (dialog, FALSE, FALSE);
        gvc_channel_bar_set_ellipsize (GVC_CHANNEL_BAR (bar), TRUE);
        gvc_channel_bar_set_icon_name (GVC_CHANNEL_BAR (bar), icon_name);
        if (name == NULL || strchr (name, '_') == NULL) {
                gvc_channel_bar_set_name (GVC_CHANNEL_BAR (bar), name);
        } else {
                char **tokens, *escaped;

                tokens = g_strsplit (name, "_", -1);
                escaped = g_strjoinv ("__", tokens);
                g_strfreev (tokens);
                gvc_channel_bar_set_name (GVC_CHANNEL_BAR (bar), escaped);
                g_free (escaped);
        }

        return bar;
}

static gint test_it = 0;

/* active_input_update
 * Handle input update change from the backend (control). 
 * Trust the backend whole-heartedly to deliver the correct input
 * i.e. keep it MVC.
 */
static void
active_input_update (GvcMixerDialog *dialog,
                     GvcMixerUIDevice *active_input)
{         
        g_debug ("\n active_input_update %s \n", gvc_mixer_ui_device_get_description (active_input));
        // First make sure the correct UI device is selected.
        GtkTreeModel *model;
        GtkTreeIter   iter;

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));

        if (gtk_tree_model_get_iter_first (model, &iter) == FALSE){
                /* When there are no device we need disable the input bar and set the original name */
                gtk_label_set_label (GTK_LABEL(dialog->priv->selected_input_label),
                                     _("Settings for the selected device"));
                gtk_widget_set_sensitive (dialog->priv->input_bar, FALSE);
                g_debug ("The tree is empty => we have no devices so cannot set the active input");
                return;        
        }
        
        do {
                gboolean         is_selected = FALSE;
                gint             id;
                        
                gtk_tree_model_get (model, &iter,
                                    ID_COLUMN, &id,
                                    -1);

                is_selected = id == gvc_mixer_ui_device_get_id (active_input);
                
                gtk_list_store_set (GTK_LIST_STORE (model),
                                    &iter,
                                    ACTIVE_COLUMN, is_selected,
                                    -1);

                if (is_selected) {
                        GtkTreeSelection *selection;
                        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->input_treeview));
                        gtk_tree_selection_select_iter (selection, &iter);
                }
                
        }while (gtk_tree_model_iter_next (model, &iter));


          // Not ideal but for now destroy the combo and recreate below.
        if (dialog->priv->input_profile_combo != NULL) {
                gtk_container_remove (GTK_CONTAINER (dialog->priv->input_settings_box),
                                      dialog->priv->input_profile_combo);
                dialog->priv->input_profile_combo = NULL;
        }

        GvcMixerStream       *stream;  
        GtkAdjustment        *adj;
  
        stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control, 
                                                           active_input); 
        if (stream == NULL) {
                g_warning ("active_input_update - couldn't find a stream from the supposed active input");
                gtk_widget_set_sensitive (dialog->priv->input_bar, 
                                          FALSE);                
                return;
        }

        // Set the label accordingly
        adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (dialog->priv->input_bar)));
        g_signal_handlers_disconnect_by_func(adj, on_adjustment_value_changed, dialog);

        gtk_label_set_label (GTK_LABEL(dialog->priv->selected_input_label),
                             g_strdup_printf(_("Settings for %s"),
                                             gvc_mixer_ui_device_get_description (active_input)));  

        gvc_channel_bar_set_base_volume (GVC_CHANNEL_BAR (dialog->priv->input_bar),
                                         gvc_mixer_stream_get_base_volume (stream));
        gvc_channel_bar_set_is_amplified (GVC_CHANNEL_BAR (dialog->priv->input_bar),
                                          gvc_mixer_stream_get_can_decibel (stream));
        /* Update the adjustment in case the previous bar wasn't decibel
         * capable, and we clipped it */
        adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (dialog->priv->input_bar)));
        gtk_adjustment_set_value (adj,
                                  gvc_mixer_stream_get_volume (stream));        
        
        stop_monitor_stream_for_source (dialog);
        //if (test_it < 6){
        create_monitor_stream_for_source (dialog, stream);
        test_it += 1;
        //}
        bar_set_stream (dialog, dialog->priv->input_bar, stream);   
        // remove any previous stream that might have been pointed at 
        // the static input bar and connect new signals from new stream.

        const GList* profiles = gvc_mixer_ui_device_get_profiles (active_input);

        if (profiles != NULL && !gvc_mixer_ui_device_should_profiles_be_hidden (active_input)){
                const gchar *active_profile;
                dialog->priv->input_profile_combo = gvc_combo_box_new (_("Mode:"));                
                gvc_combo_box_set_profiles (GVC_COMBO_BOX (dialog->priv->input_profile_combo),
                                            profiles);
                                
                active_profile = gvc_mixer_ui_device_get_active_profile (active_input);
                if (active_profile)
                        gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->input_profile_combo), active_profile);

                g_object_set_data (G_OBJECT (dialog->priv->input_profile_combo),
                                   "uidevice",
                                   active_input);

                g_signal_connect (G_OBJECT (dialog->priv->input_profile_combo), "changed",
                                  G_CALLBACK (profile_selection_changed), dialog);

                gtk_box_pack_start (GTK_BOX (dialog->priv->input_settings_box),
                                    dialog->priv->input_profile_combo,
                                    TRUE, FALSE, 0);

                if (dialog->priv->size_group != NULL) {
                        gvc_combo_box_set_size_group (GVC_COMBO_BOX (dialog->priv->input_profile_combo),
                                                      dialog->priv->size_group, FALSE);
                }
                gtk_widget_show (dialog->priv->input_profile_combo);                
        }
}

/* active_output_update
 * Handle output update change from the backend (control). 
 * Trust the backend whole heartedly to deliver the correct output
 * i.e. keep it MVC.
 */
static void
active_output_update (GvcMixerDialog *dialog,
                      GvcMixerUIDevice *active_output)
{                 
        // First make sure the correct UI device is selected.
        GtkTreeModel *model;
        GtkTreeIter   iter;
        g_debug ("\n\n active output update - device id = %i \n\n",
                 gvc_mixer_ui_device_get_id (active_output));

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));

        if (gtk_tree_model_get_iter_first (model, &iter) == FALSE){
                /* When there are no device we need disable the output bar and set the original name */
                gtk_label_set_label (GTK_LABEL(dialog->priv->selected_output_label),
                                     _("Settings for the selected device"));
                gtk_widget_set_sensitive (dialog->priv->output_bar, FALSE);
                g_debug ("The tree is empty => we have no devices in the tree => cannot set the active output");
                return;        
        }
        
        do {
                gboolean         is_selected;
                gint             id;
                        
                gtk_tree_model_get (model, &iter,
                                    ID_COLUMN, &id,
                                    ACTIVE_COLUMN, &is_selected,                  
                                    -1);

                if (is_selected && id == gvc_mixer_ui_device_get_id (active_output)) {
                        g_debug ("\n\n unneccessary active output update unless it was a profile change on the same device ? \n\n");
                }

                is_selected = id == gvc_mixer_ui_device_get_id (active_output);
                
                gtk_list_store_set (GTK_LIST_STORE (model),
                                    &iter,
                                    ACTIVE_COLUMN, is_selected,
                                    -1);

                if (is_selected) {
                        GtkTreeSelection *selection;
                        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->output_treeview));
                        gtk_tree_selection_select_iter (selection, &iter);
                }
                
        }while (gtk_tree_model_iter_next (model, &iter));
        
          // Not ideal but for now destroy the combo and recreate below.
        if (dialog->priv->output_profile_combo != NULL) {
                gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
                                      dialog->priv->output_profile_combo);
                dialog->priv->output_profile_combo = NULL;
        }

        GvcMixerStream       *stream;  
        const GvcChannelMap  *map;
        GtkAdjustment        *adj;
  
        stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control, 
                                                           active_output); 

        if (stream == NULL) {
                g_warning ("active_output_update - couldn't find a stream from the supposed active output");
                return;
        }

        gboolean is_muted = gvc_mixer_stream_get_is_muted (stream);
        gtk_widget_set_sensitive (dialog->priv->applications_box,
                                  !is_muted);
        adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (dialog->priv->output_bar)));
        g_signal_handlers_disconnect_by_func(adj, on_adjustment_value_changed, dialog);

        bar_set_stream (dialog, dialog->priv->output_bar, stream);
        gvc_channel_bar_set_base_volume (GVC_CHANNEL_BAR (dialog->priv->output_bar),
                                         gvc_mixer_stream_get_base_volume (stream));
        /* Update the adjustment in case the previous bar wasn't decibel
         * capable, and we clipped it */
        adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (dialog->priv->output_bar)));
        gtk_adjustment_set_value (adj,
                                  gvc_mixer_stream_get_volume (stream));

        map = gvc_mixer_stream_get_channel_map (stream);

        if (map == NULL) {
                g_warning ("Active output stream has no channel map");
                gtk_widget_set_sensitive (dialog->priv->output_bar, FALSE);
                gtk_widget_set_sensitive (dialog->priv->output_balance_bar, FALSE);
                gtk_widget_set_sensitive (dialog->priv->output_lfe_bar, FALSE);
                gtk_widget_set_sensitive (dialog->priv->output_fade_bar, FALSE);                
                return;
        }


        // Swap bars to the active map
        gvc_balance_bar_set_map (GVC_BALANCE_BAR (dialog->priv->output_balance_bar), 
                                 map);  
        gvc_balance_bar_set_map (GVC_BALANCE_BAR (dialog->priv->output_fade_bar), 
                                 map);
        gvc_balance_bar_set_map (GVC_BALANCE_BAR (dialog->priv->output_lfe_bar), 
                                 map);
                           
        // Set sensitivities accordingly.
        gtk_widget_set_sensitive (dialog->priv->output_balance_bar,
                                  gvc_channel_map_can_balance (map));                                    
        gtk_widget_set_sensitive (dialog->priv->output_fade_bar, 
                                  gvc_channel_map_can_fade (map));
        gtk_widget_set_sensitive (dialog->priv->output_lfe_bar, 
                                  gvc_channel_map_has_lfe (map));
        gtk_widget_set_sensitive (dialog->priv->output_bar, 
                                  TRUE);
        // Set the label accordingly
	gtk_label_set_label (GTK_LABEL(dialog->priv->selected_output_label),
			     g_strdup_printf(_("Settings for %s"),
                                             gvc_mixer_ui_device_get_description (active_output)));
        g_debug ("\n active_output_update %s \n", gvc_mixer_ui_device_get_description (active_output));

        GList* profiles = gvc_mixer_ui_device_get_profiles (active_output);

        if (profiles != NULL && !gvc_mixer_ui_device_should_profiles_be_hidden (active_output)) {
                const gchar *active_profile;

                dialog->priv->output_profile_combo = gvc_combo_box_new (_("Mode:"));                
                gvc_combo_box_set_profiles (GVC_COMBO_BOX (dialog->priv->output_profile_combo),
                                            profiles);
                                
                gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
                                    dialog->priv->output_profile_combo,
                                    FALSE, FALSE, 3);

                if (dialog->priv->size_group != NULL) {
                        gvc_combo_box_set_size_group (GVC_COMBO_BOX (dialog->priv->output_profile_combo),
                                                      dialog->priv->size_group, TRUE);
                }

                active_profile = gvc_mixer_ui_device_get_active_profile (active_output);
                if (active_profile)
                        gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->output_profile_combo), active_profile);
                                          
                
                g_object_set_data (G_OBJECT (dialog->priv->output_profile_combo),
                                   "uidevice",
                                   active_output);
                g_signal_connect (G_OBJECT (dialog->priv->output_profile_combo), "changed",
                                  G_CALLBACK (profile_selection_changed), dialog);

                gtk_widget_show (dialog->priv->output_profile_combo);                
        }

}

static void
bar_set_stream (GvcMixerDialog *dialog,
                GtkWidget      *bar,
                GvcMixerStream *stream)
{
        GtkAdjustment  *adj;

        g_assert (bar != NULL);

        gtk_widget_set_sensitive (bar, (stream != NULL));

        adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (bar)));
        
        g_signal_handlers_disconnect_by_func (adj, on_adjustment_value_changed, dialog);

        g_object_set_data (G_OBJECT (bar), "gvc-mixer-dialog-stream", stream);
        g_object_set_data (G_OBJECT (adj), "gvc-mixer-dialog-stream", stream);
        g_object_set_data (G_OBJECT (adj), "gvc-mixer-dialog-bar", bar);

        if (stream != NULL) {
                gboolean is_muted;

                is_muted = gvc_mixer_stream_get_is_muted (stream);
                gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (bar), is_muted);

                gtk_adjustment_set_value (adj,
                                          gvc_mixer_stream_get_volume (stream));
                g_signal_connect (stream,
                                  "notify::is-muted",
                                  G_CALLBACK (on_stream_is_muted_notify),
                                  dialog);
                g_signal_connect (stream,
                                  "notify::volume",
                                  G_CALLBACK (on_stream_volume_notify),
                                  dialog);
                g_signal_connect (adj,
                                  "value-changed",
                                  G_CALLBACK (on_adjustment_value_changed),
                                  dialog);
        }
}

/**
* This method handles all streams that are not an input or output
* i.e. effects streams and application streams
* TODO rename to truly reflect its usage. 
**/
static void
add_stream (GvcMixerDialog *dialog,
            GvcMixerStream *stream)
{
  
        GtkWidget     *bar;
        bar = NULL;

        if (stream == gvc_mixer_control_get_event_sink_input (dialog->priv->mixer_control)) {
                bar = dialog->priv->effects_bar;
                g_debug ("Adding effects stream");
        } else {
                // Must be a sink/source input/output
                const char *name;
                name = gvc_mixer_stream_get_name (stream);
                g_debug ("\n Add bar for application stream : %s",
                             name);

                bar = create_app_bar (dialog, name,
                                      gvc_mixer_stream_get_icon_name (stream));

                gtk_box_pack_start (GTK_BOX (dialog->priv->applications_box), bar, FALSE, FALSE, 12);
                dialog->priv->num_apps++;
                gtk_widget_hide (dialog->priv->no_apps_label);
        }
        // We should have a bar by now.
        g_assert (bar != NULL);
        GvcMixerStream *old_stream;

        if (bar != NULL) {
                old_stream = g_object_get_data (G_OBJECT (bar), "gvc-mixer-dialog-stream");
                if (old_stream != NULL) {
                        char *name;
                        g_object_get (bar, "name", &name, NULL);
                        g_debug ("Disconnecting old stream '%s' from bar '%s'",
                                 gvc_mixer_stream_get_name (old_stream), name);
                        g_free (name);

                        g_signal_handlers_disconnect_by_func (old_stream, on_stream_is_muted_notify, dialog);
                        g_signal_handlers_disconnect_by_func (old_stream, on_stream_volume_notify, dialog);
                        g_print ("\n in add stream \n");
                        g_hash_table_remove (dialog->priv->bars, GUINT_TO_POINTER (gvc_mixer_stream_get_id (old_stream)));
                }
                save_bar_for_stream (dialog, stream, bar);
                bar_set_stream (dialog, bar, stream);
                gtk_widget_show (bar);
        }
}

static void
remove_stream (GvcMixerDialog  *dialog,
               guint            id)
{        
        GtkWidget *bar;
        bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (id));
        if (bar != NULL) {
                g_hash_table_remove (dialog->priv->bars, GUINT_TO_POINTER (id));
                gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (bar)),
                                      bar);
                dialog->priv->num_apps--;
                if (dialog->priv->num_apps == 0) {
                        gtk_widget_show (dialog->priv->no_apps_label);
                }
        }

}

static void
on_control_stream_added (GvcMixerControl *control,
                         guint            id,
                         GvcMixerDialog  *dialog)
{
        GvcMixerStream *stream;
        stream = gvc_mixer_control_lookup_stream_id (control, id);
        
        if (stream == NULL) 
                return;
        
        const char    *app_id;
        app_id = gvc_mixer_stream_get_application_id (stream);

        if (stream == gvc_mixer_control_get_event_sink_input (dialog->priv->mixer_control) || (!GVC_IS_MIXER_SOURCE (stream) &&
                !GVC_IS_MIXER_SINK (stream)
                && !gvc_mixer_stream_is_virtual (stream)
                && g_strcmp0 (app_id, "org.gnome.VolumeControl") != 0
                && g_strcmp0 (app_id, "org.PulseAudio.pavucontrol") != 0)) {

                GtkWidget      *bar;

                bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (id));
                if (bar != NULL) {
                        g_debug ("GvcMixerDialog: Stream %u already added", id);
                        return;
                }
                add_stream (dialog, stream);
        } 
}

static void
on_control_stream_removed (GvcMixerControl *control,
                           guint            id,
                           GvcMixerDialog  *dialog)
{
        remove_stream (dialog, id);
}

static gboolean
find_item_by_id (GtkTreeModel *model,
                 guint         id,
                 guint         column,
                 GtkTreeIter  *iter)
{
        gboolean found_item;

        found_item = FALSE;

        if (!gtk_tree_model_get_iter_first (model, iter)) {
                return FALSE;
        }

        do {
                guint t_id;

                gtk_tree_model_get (model, iter,
                                    column, &t_id, -1);

                if (id == t_id) {
                        found_item = TRUE;
                }
        } while (!found_item && gtk_tree_model_iter_next (model, iter));

        return found_item;
}

static void
add_input_ui_entry (GvcMixerDialog *dialog,
                    GvcMixerUIDevice *input)
{
        g_debug ("\n Add input ui entry with id : %u \n",
                  gvc_mixer_ui_device_get_id (input));

        gchar    *port_name;
        gchar    *origin;
        gchar    *description;
        gboolean active;
        gboolean available;
        gint     stream_id;
        GvcMixerCard  *card;

        g_object_get (G_OBJECT (input),
                     "stream-id", &stream_id,
                     "card", &card,
                     "origin", &origin,
                     "description", &description,
                     "port-name", &port_name,
                     "port-available", &available,
                      NULL);
        GtkTreeModel        *model;
        GtkTreeIter          iter;
        const GvcChannelMap *map;
        GIcon               *icon;

        if (card == NULL) {
                GvcMixerStream *stream;
                g_debug ("just detected a network source");
                stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control, input);
                if (stream == NULL) {
                        g_warning ("tried to add the network source but the stream was null - fail ?!");
                        g_free (port_name);					
                        g_free (origin);							
                        g_free (description);		  				
                        return;
                }
                icon = gvc_mixer_stream_get_gicon (stream);			
        }
        else
                icon = gvc_mixer_card_get_gicon (card);                 

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
        gtk_list_store_append (GTK_LIST_STORE (model), &iter);

        gtk_list_store_set (GTK_LIST_STORE (model),
                            &iter,
                            NAME_COLUMN, description,
                            DEVICE_COLUMN, origin,
                            ACTIVE_COLUMN, FALSE,
                            ICON_COLUMN, icon,
                            ID_COLUMN, gvc_mixer_ui_device_get_id (input),
                            SPEAKERS_COLUMN,origin,
                            -1);

        if (icon != NULL)
                g_object_unref (icon);

        // TODO check this.
        /*g_signal_connect (output,
                          "notify::description",
                          G_CALLBACK (on_output_description_notify),
                          dialog);*/
              
        g_free (port_name);                                        
        g_free (origin);                                                        
        g_free (description);                   
}                                   

static void
add_output_ui_entry (GvcMixerDialog *dialog,
                     GvcMixerUIDevice *output)
{
        g_debug ("\n Add output ui entry with id : %u \n",
                  gvc_mixer_ui_device_get_id (output));

        gchar    *sink_port_name;
        gchar    *origin;
        gchar    *description;
        gboolean active;
        gboolean available;
        gint     sink_stream_id;
        GvcMixerCard  *card;

        g_object_get (G_OBJECT (output),
                     "stream-id", &sink_stream_id,
                     "card", &card,
                     "origin", &origin,
                     "description", &description,
                     "port-name", &sink_port_name,
                     "port-available", &available,
                      NULL);
	
        GtkTreeModel        *model;
        GtkTreeIter          iter;
        const GvcChannelMap *map;
        GIcon               *icon;

        if (card == NULL) {
                g_debug ("just detected a network sink");
                
                GvcMixerStream *stream;
                stream = gvc_mixer_control_get_stream_from_device (dialog->priv->mixer_control, output);
                if (stream == NULL) {
                        g_warning ("tried to add the network sink but the stream was null - fail ?!");
                        g_free (sink_port_name);					
                        g_free (origin);							
                        g_free (description);		  				
                        return;
                }
                icon = gvc_mixer_stream_get_gicon (stream);			
        }
        else
                icon = gvc_mixer_card_get_gicon (card);			


        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
        gtk_list_store_append (GTK_LIST_STORE (model), &iter);

        gtk_list_store_set (GTK_LIST_STORE (model),
                            &iter,
                            NAME_COLUMN, description,
                            DEVICE_COLUMN, origin,
                            ACTIVE_COLUMN, FALSE,
                            ICON_COLUMN, icon,
                            ID_COLUMN, gvc_mixer_ui_device_get_id (output),
                            SPEAKERS_COLUMN,origin,
                            -1);

        if (icon != NULL)
                g_object_unref (icon);

        // TODO check this.
        /*g_signal_connect (output,
                          "notify::description",
                          G_CALLBACK (on_output_description_notify),
                          dialog);*/
              
        g_free (sink_port_name);					
        g_free (origin);							
        g_free (description);		  	
}				    

static void
on_control_output_added (GvcMixerControl *control,
                         guint            id,
                         GvcMixerDialog  *dialog)
{
        gboolean          is_first_device;
        GtkTreeModel     *model;
        GtkTreeIter       iter;
        GvcMixerUIDevice *out = NULL;

	out = gvc_mixer_control_lookup_output_id (control, id);

	if (out == NULL) {
		g_warning ("on_control_output_added - tried to fetch an output of id %u but got nothing", id);
		return;
	}

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
        is_first_device = !gtk_tree_model_get_iter_first (model, &iter);

	add_output_ui_entry (dialog, out);
        if (is_first_device)
                active_output_update (dialog, out);

}

static void
on_control_active_output_update (GvcMixerControl *control,
                                 guint            id,
                                 GvcMixerDialog  *dialog)
{
	GvcMixerUIDevice* out = NULL;
	out = gvc_mixer_control_lookup_output_id (control, id);

	if (out == NULL) {
		g_warning ("\n on_control_active_output_update - tried to fetch an output of id %u but got nothing", id);
		return;
	}
        active_output_update (dialog, out);
}

static void
on_control_active_input_update (GvcMixerControl *control,
                                 guint            id,
                                 GvcMixerDialog  *dialog)
{
	GvcMixerUIDevice* in = NULL;
	in = gvc_mixer_control_lookup_input_id (control, id);

	if (in == NULL) {
		g_warning ("on_control_active_input_update - tried to fetch an input of id %u but got nothing", id);
		return;
	}
        active_input_update (dialog, in);
}

static void
on_control_input_added (GvcMixerControl *control,
                        guint            id,
                        GvcMixerDialog  *dialog)
{
        gboolean          is_first_device;
        GtkTreeModel     *model;
        GtkTreeIter       iter;
        GvcMixerUIDevice *in = NULL;

	in = gvc_mixer_control_lookup_input_id (control, id);

	if (in == NULL) {
		g_warning ("on_control_input_added - tried to fetch an input of id %u but got nothing", id);
		return;
	}

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
        is_first_device = !gtk_tree_model_get_iter_first (model, &iter);

	add_input_ui_entry (dialog, in);
        if (is_first_device)
                active_input_update (dialog, in);
}

static void
on_control_output_removed (GvcMixerControl *control,
                           guint	    id,
                           GvcMixerDialog  *dialog)
{
        GtkWidget    *bar;
        gboolean      is_last_device;
        gboolean      found;
        GtkTreeIter   iter;
        GtkTreeModel *model;

        GvcMixerUIDevice* out = NULL;
        out = gvc_mixer_control_lookup_output_id (control, id);
        
        gint sink_stream_id;
        
        g_object_get (G_OBJECT (out),
                     "stream-id", &sink_stream_id,
                      NULL);
		      
        g_debug ("Remove output from dialog \n id : %u \n sink stream id : %i \n",
                  id,
                  sink_stream_id);

        /* remove from any models */
        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
        found = find_item_by_id (GTK_TREE_MODEL (model), id, ID_COLUMN, &iter);
        if (found) {
                gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
        }

        /* Disable output bar when all output devices removed */
        is_last_device = !gtk_tree_model_get_iter_first (model, &iter);
        if (is_last_device)
                active_output_update (dialog, out);
}     


                 
static void
on_control_input_removed (GvcMixerControl *control,
			   guint	    id,
			   GvcMixerDialog  *dialog)
{
        GtkWidget    *bar;
        gboolean      found;
        gboolean      is_last_device;
        GtkTreeIter   iter;
        GtkTreeModel *model;

        GvcMixerUIDevice* in = NULL;
        in = gvc_mixer_control_lookup_input_id (control, id);
        
        gint stream_id;
        
        g_object_get (G_OBJECT (in),
                     "stream-id", &stream_id,
                      NULL);
                      
        g_debug ("Remove input from dialog \n id : %u \n stream id : %i \n",
                  id,
                  stream_id);

        /* remove from any models */
        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
        found = find_item_by_id (GTK_TREE_MODEL (model), id, ID_COLUMN, &iter);
        if (found) {
                gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
        }        

        /* Disable input bar when all input devices removed */
        is_last_device = !gtk_tree_model_get_iter_first (model, &iter);
        if (is_last_device)
                active_input_update (dialog, in);
}

static void
_gtk_label_make_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.
         */
        gtk_widget_modify_font (GTK_WIDGET (label), font_desc);

        pango_font_description_free (font_desc);
}


static void
on_input_selection_changed (GtkTreeSelection *selection,
                             GvcMixerDialog   *dialog)
{
        GtkTreeModel *model;
        GtkTreeIter   iter;
        gboolean      toggled;
        guint         id;

        if (gtk_tree_selection_get_selected (selection, &model, &iter) == FALSE) {
                g_debug ("Could not get default input from selection");
                return;
        }

        gtk_tree_model_get (model, &iter,
                            ID_COLUMN, &id,
                            ACTIVE_COLUMN, &toggled,
                            -1);

        toggled ^= 1;
        GvcMixerUIDevice *input;
        //g_debug ("on_input_selection_changed - try swap to input with id %u", id); 
        input = gvc_mixer_control_lookup_input_id (dialog->priv->mixer_control, id);
        
        if (input == NULL) {
                g_warning ("on_input_selection_changed - Unable to find input with id: %u", id);
                return;
        }

        gvc_mixer_control_change_input (dialog->priv->mixer_control, input);
}

static void
on_output_selection_changed (GtkTreeSelection *selection,
                             GvcMixerDialog   *dialog)
{
        GtkTreeModel *model;
        GtkTreeIter   iter;
        gboolean      active;
        guint         id;

        if (gtk_tree_selection_get_selected (selection, &model, &iter) == FALSE) {
                g_debug ("Could not get default output from selection");
                return;
        }

        gtk_tree_model_get (model, &iter,
                            ID_COLUMN, &id,
                            ACTIVE_COLUMN, &active,
                            -1);
        
        g_debug ("\n\n on_output_selection_changed - active %i \n\n", active); 
        if (active){
                return;
        }

        GvcMixerUIDevice *output;
        g_debug ("\n on_output_selection_changed - try swap to output with id %u", id); 
	output = gvc_mixer_control_lookup_output_id (dialog->priv->mixer_control, id);
        
	if (output == NULL) {
		g_warning ("on_output_selection_changed - Unable to find output with id: %u", id);
		return;
	}

        gvc_mixer_control_change_output (dialog->priv->mixer_control, output);
}

static void
name_to_text (GtkTreeViewColumn *column,
              GtkCellRenderer *cell,
              GtkTreeModel *model,
              GtkTreeIter *iter,
              gpointer user_data)
{
        char *name, *mapping;

        gtk_tree_model_get(model, iter,
                           NAME_COLUMN, &name,
                           SPEAKERS_COLUMN, &mapping,
                           -1);

        if (mapping == NULL) {
                g_object_set (cell, "text", name, NULL);
        } else {
                char *str;

                str = g_strdup_printf ("%s\n<i>%s</i>",
                                       name, mapping);
                g_object_set (cell, "markup", str, NULL);
                g_free (str);
        }

        g_free (name);
        g_free (mapping);
}

static GtkWidget *
create_stream_treeview (GvcMixerDialog *dialog,
                        GCallback       on_selection_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);

        store = gtk_list_store_new (NUM_COLUMNS,
                                    G_TYPE_STRING,
                                    G_TYPE_STRING,
                                    G_TYPE_BOOLEAN,
                                    G_TYPE_UINT,
                                    G_TYPE_STRING,
                                    G_TYPE_ICON);
        gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
                                 GTK_TREE_MODEL (store));

        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
        gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);

        column = gtk_tree_view_column_new ();
        gtk_tree_view_column_set_title (column, _("Name"));
        renderer = gtk_cell_renderer_pixbuf_new ();
        gtk_tree_view_column_pack_start (column, renderer, FALSE);
        g_object_set (G_OBJECT (renderer), "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
        gtk_tree_view_column_set_attributes (column, renderer,
                                             "gicon", ICON_COLUMN,
                                             NULL);

        renderer = gtk_cell_renderer_text_new ();
        gtk_tree_view_column_pack_start (column, renderer, TRUE);
        gtk_tree_view_column_set_cell_data_func (column, renderer,
                                                 name_to_text, NULL, NULL);
        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);

        g_signal_connect ( selection, "changed",
                          on_selection_changed, dialog);
#if 0
        renderer = gtk_cell_renderer_text_new ();
        column = gtk_tree_view_column_new_with_attributes (_("Device"),
                                                           renderer,
                                                           "text", DEVICE_COLUMN,
                                                           NULL);
        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
#endif
        return treeview;
}

static void
on_profile_changed (GvcComboBox *widget,
                    const char  *profile,
                    gpointer     user_data)
{
        GvcMixerCard        *card;

        card = g_object_get_data (G_OBJECT (widget), "card");
        if (card == NULL) {
                g_warning ("Could not find card for combobox");
                return;
        }

        g_debug ("Profile changed to %s for card %s", profile,
                 gvc_mixer_card_get_name (card));

        gvc_mixer_card_change_profile (card, profile);
}

static void
on_test_speakers_clicked (GtkButton *widget,
                          gpointer  user_data)
{
        GvcMixerDialog      *dialog = GVC_MIXER_DIALOG (user_data);
        GtkTreeModel        *model;
        GtkTreeIter         iter;
        gint                active_output = GVC_MIXER_UI_DEVICE_INVALID;

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
        
        if (gtk_tree_model_get_iter_first (model, &iter) == FALSE){
                g_warning ("The tree is empty => we have no device to test speakers with return");
                return;        
        }
                
        do {
                gboolean         is_selected = FALSE ;
                gint             id;
                        
                gtk_tree_model_get (model, &iter,
                                    ID_COLUMN, &id,
                                    ACTIVE_COLUMN, &is_selected,
                                    -1);
                
                if (is_selected) {
                        active_output = id;
                        break;
                }
                
        }while (gtk_tree_model_iter_next (model, &iter));
        
        if (active_output == GVC_MIXER_UI_DEVICE_INVALID) {
                g_warning ("Cant find the active output from the UI");
                return;
        }        

        GvcMixerUIDevice *output;
        output = gvc_mixer_control_lookup_output_id (dialog->priv->mixer_control, (guint)active_output);
        gint stream_id = gvc_mixer_ui_device_get_stream_id(output);

        if (stream_id == GVC_MIXER_UI_DEVICE_INVALID)
                return;

        g_debug ("Test the speakers on the %s", gvc_mixer_ui_device_get_description (output));
        
        GvcMixerStream        *stream;
        GvcMixerCardProfile *profile;
        GtkWidget           *d, *speaker_test, *container;
        char                *title;

        stream = gvc_mixer_control_lookup_stream_id (dialog->priv->mixer_control, stream_id);
        if (stream == NULL) {
                g_debug ("Stream/sink not found");
                return;
        }
        title = g_strdup_printf (_("Speaker Testing for %s"), gvc_mixer_ui_device_get_description (output));
        d = gtk_dialog_new_with_buttons (title,
                                         GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (widget))),
                                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                                         _("_Close"), GTK_RESPONSE_CLOSE,
                                         NULL);
        gtk_window_set_has_resize_grip (GTK_WINDOW (d), FALSE);

        g_free (title);
        speaker_test = gvc_speaker_test_new (dialog->priv->mixer_control,
                                             stream);
        gtk_widget_show (speaker_test);
        container = gtk_dialog_get_content_area (GTK_DIALOG (d));
        gtk_container_add (GTK_CONTAINER (container), speaker_test);

        gtk_dialog_run (GTK_DIALOG (d));
        gtk_widget_destroy (d);
}

static void
allow_amplify_check_changed (GObject    *object,
                             GParamSpec *pspec,
                             gpointer    user_data)
{
        const gchar *label;
        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (object)))
                label = (_("Allow louder than 100% (may distort sound)"));
        else
                label = (_("Allow louder than 100%"));

        gtk_button_set_label (GTK_BUTTON (object), label);
}

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        *alignment_settings_box;
        GtkWidget        *settings_box;
        GtkWidget        *box;
        GtkWidget        *sbox;
        GtkWidget        *ebox;
        GtkWidget        *test_sound_box;
        GSList           *streams;
        GSList           *cards;
        GSList           *l;
        GvcMixerStream   *stream;
        GvcMixerCard     *card;
        GtkTreeSelection *selection;
        GtkWidget        *mute_check;
        GtkWidget        *allow_amplify_check;
        GtkWidget        *indicator_visible_check;
        AtkObject        *output_bar_atk;
        AtkObject        *output_bar_label_atk;
        AtkObject        *output_bar_mute_atk;

        object = G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->constructor (type, n_construct_properties, construct_params);

        self = GVC_MIXER_DIALOG (object);

        main_vbox = GTK_WIDGET (self);
        gtk_box_set_spacing (GTK_BOX (main_vbox), 6);

        gtk_container_set_border_width (GTK_CONTAINER (self), 6);

        // Output volume
        self->priv->output_bar = create_bar (self, FALSE, TRUE);
        gvc_channel_bar_set_show_mute (GVC_CHANNEL_BAR (self->priv->output_bar), FALSE);
        gvc_channel_bar_set_mark_position (GVC_CHANNEL_BAR (self->priv->output_bar), GTK_POS_TOP);
        gtk_widget_set_sensitive (self->priv->output_bar, FALSE);
        gtk_widget_set_valign (self->priv->output_bar, GTK_ALIGN_END);
        gtk_widget_set_size_request (self->priv->output_bar, 400, -1);
        g_settings_bind (self->priv->sound_settings, "allow-amplified-volume",
                         self->priv->output_bar, "is-amplified", G_SETTINGS_BIND_DEFAULT);

        label = gtk_label_new_with_mnemonic (_("_Output volume:"));
        gtk_widget_set_valign (label, GTK_ALIGN_END);
        gtk_widget_set_margin_top (label, 30);

        mute_check = gtk_check_button_new_with_label (_("Mute"));
        gtk_widget_set_halign (mute_check, GTK_ALIGN_START);
        g_object_bind_property (mute_check, "active", self->priv->output_bar, "is-muted",
                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);

        output_bar_atk = gtk_widget_get_accessible (self->priv->output_bar);
        output_bar_label_atk = gtk_widget_get_accessible (label);
        output_bar_mute_atk = gtk_widget_get_accessible (mute_check);
        atk_object_add_relationship (output_bar_label_atk, ATK_RELATION_LABEL_FOR,
                                     output_bar_atk);
        atk_object_add_relationship (output_bar_label_atk, ATK_RELATION_LABEL_FOR,
                                     output_bar_mute_atk);

        /* TRANSLATORS: This label is used in a checkbox close to volume
         * slider. Please keep it brief. */
        allow_amplify_check = gtk_check_button_new_with_label (_("Allow louder than 100%"));
        gtk_widget_set_halign (allow_amplify_check, GTK_ALIGN_START);
        gtk_widget_set_hexpand (allow_amplify_check, TRUE);
        g_signal_connect (allow_amplify_check, "notify::active",
                          G_CALLBACK (allow_amplify_check_changed), NULL);
        g_settings_bind (self->priv->sound_settings, "allow-amplified-volume",
                         allow_amplify_check, "active", G_SETTINGS_BIND_DEFAULT);

        self->priv->output_stream_box = gtk_grid_new ();
        gtk_widget_set_halign (self->priv->output_stream_box, GTK_ALIGN_CENTER);
        gtk_grid_set_column_spacing (GTK_GRID (self->priv->output_stream_box), 6);
        gtk_grid_set_row_spacing (GTK_GRID (self->priv->output_stream_box), 6);
        gtk_grid_attach (GTK_GRID (self->priv->output_stream_box),
                         label, 0, 0, 1, 1);
        gtk_grid_attach (GTK_GRID (self->priv->output_stream_box),
                         self->priv->output_bar, 1, 0, 2, 1);
        gtk_grid_attach (GTK_GRID (self->priv->output_stream_box),
                         mute_check, 1, 1, 1, 1);
        gtk_grid_attach (GTK_GRID (self->priv->output_stream_box),
                         allow_amplify_check, 2, 1, 1, 1);

        gtk_box_pack_start (GTK_BOX (main_vbox),
                            self->priv->output_stream_box,
                            FALSE, TRUE, 0);

        self->priv->notebook = gtk_notebook_new ();
        gtk_box_pack_start (GTK_BOX (main_vbox),
                            self->priv->notebook,
                            TRUE, TRUE, 0);
        gtk_container_set_border_width (GTK_CONTAINER (self->priv->notebook), 5);

        /* Output page */
        self->priv->output_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 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 (_("Play sound through"));
        gtk_widget_set_size_request (GTK_WIDGET (box), 310, -1);        
        label = gtk_frame_get_label_widget (GTK_FRAME (box));
        _gtk_label_make_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, FALSE, TRUE, 0);

        alignment = gtk_alignment_new (0, 0, 1, 1);
        gtk_container_add (GTK_CONTAINER (box), alignment);
        gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 10, 5, 0, 15);

        self->priv->output_treeview = create_stream_treeview (self,
                                                              G_CALLBACK (on_output_selection_changed));
        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);
        
        self->priv->selected_output_label = gtk_label_new (_("Settings for the selected device"));
        gtk_widget_set_halign (self->priv->selected_output_label, GTK_ALIGN_START);
        gtk_widget_set_valign (self->priv->selected_output_label, GTK_ALIGN_START);       
        gtk_misc_set_padding (GTK_MISC (self->priv->selected_output_label), 0, 0);
        _gtk_label_make_bold (GTK_LABEL (self->priv->selected_output_label));
        settings_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
        alignment_settings_box = gtk_alignment_new (0, 0, 1, 1);
        gtk_alignment_set_padding (GTK_ALIGNMENT (alignment_settings_box), 7, 0, 0, 0);
        self->priv->output_settings_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
        gtk_container_add (GTK_CONTAINER (alignment_settings_box), self->priv->output_settings_box);

        gtk_box_pack_start (GTK_BOX (self->priv->output_box),
                            settings_box,
                            TRUE,
                            TRUE,
                            0);
        gtk_box_pack_start (GTK_BOX (settings_box),
                            self->priv->selected_output_label,
                            FALSE,
                            FALSE,
                            0);
        gtk_box_pack_start (GTK_BOX (settings_box),
                            alignment_settings_box,
                            FALSE,
                            FALSE,
                            0);

        self->priv->output_balance_bar = gvc_balance_bar_new (BALANCE_TYPE_RL);
        if (self->priv->size_group != NULL) {
                gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (self->priv->output_balance_bar),
                                                self->priv->size_group,
                                                FALSE);
        }
        gtk_box_pack_start (GTK_BOX (self->priv->output_settings_box),
                            self->priv->output_balance_bar,
                            FALSE, FALSE, 3);
        gtk_widget_show (self->priv->output_balance_bar);

        self->priv->output_fade_bar = gvc_balance_bar_new (BALANCE_TYPE_FR);
        if (self->priv->size_group != NULL) {
                gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (self->priv->output_fade_bar),
                                                self->priv->size_group,
                                                FALSE);
        }
        gtk_box_pack_start (GTK_BOX (self->priv->output_settings_box),
                            self->priv->output_fade_bar,
                            FALSE, FALSE, 3);
        gtk_widget_show (self->priv->output_fade_bar);

        self->priv->output_lfe_bar = gvc_balance_bar_new (BALANCE_TYPE_LFE);
        if (self->priv->size_group != NULL) {
                gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (self->priv->output_lfe_bar),
                                                self->priv->size_group,
                                                FALSE);
        }
        gtk_box_pack_start (GTK_BOX (self->priv->output_settings_box),
                            self->priv->output_lfe_bar,
                            FALSE, FALSE, 3);
        gtk_widget_show (self->priv->output_lfe_bar);
                
        /* Creating a box and try to deal using the same size group. */
        test_sound_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
        gtk_box_pack_end (GTK_BOX (self->priv->output_settings_box),
                          test_sound_box,
                          FALSE, 
                          FALSE,
                          5);

        sbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
        gtk_box_pack_start (GTK_BOX (test_sound_box),
                            sbox,
                            FALSE, FALSE, 0);

        if (self->priv->size_group != NULL)
                gtk_size_group_add_widget (self->priv->size_group, sbox);

        self->priv->test_output_button = gtk_button_new_with_label (_("Test Sound"));
        
        /* FIXME: I am getting mental with all these hardcoded padding values,
         * Here 8 works fine, not sure why. */
        gtk_box_pack_start (GTK_BOX (test_sound_box),
                            self->priv->test_output_button,
                            FALSE, FALSE, 8);

        /* Is this needed */
        if (self->priv->size_group != NULL)
                gtk_size_group_add_widget (self->priv->size_group, self->priv->test_output_button);

        gtk_widget_show (test_sound_box);

        //gtk_container_add (GTK_CONTAINER (box), self->priv->output_settings_box);
	g_signal_connect (self->priv->test_output_button,
                          "released",
                           G_CALLBACK (on_test_speakers_clicked),
                           self);

        /* Input page */
        self->priv->input_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
        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);
        box = gtk_frame_new (_("Record sound from"));
        gtk_widget_set_size_request (GTK_WIDGET (box), 310, -1);        
        label = gtk_frame_get_label_widget (GTK_FRAME (box));
        _gtk_label_make_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, FALSE, TRUE, 0);

        alignment = gtk_alignment_new (0, 0, 1, 1);
        gtk_container_add (GTK_CONTAINER (box), alignment);
        gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 10, 5, 0, 15);

        self->priv->input_treeview = create_stream_treeview (self,
                                                             G_CALLBACK (on_input_selection_changed));
        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);

        self->priv->selected_input_label = gtk_label_new (_("Settings for the selected device"));
        gtk_widget_set_halign (self->priv->selected_input_label, GTK_ALIGN_START);
        _gtk_label_make_bold (GTK_LABEL (self->priv->selected_input_label));
        
        self->priv->input_settings_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
        gtk_box_pack_start (GTK_BOX (self->priv->input_box),
                            self->priv->input_settings_box,
                            TRUE,
                            TRUE,
                            0);
        gtk_box_pack_start (GTK_BOX (self->priv->input_settings_box),
                            self->priv->selected_input_label,
                            FALSE,
                            FALSE,
                            0);     

        self->priv->input_bar = create_bar (self, FALSE, 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-symbolic");
        gvc_channel_bar_set_high_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar),
                                            "audio-input-microphone-high-symbolic");
        gtk_widget_set_sensitive (self->priv->input_bar, FALSE);

        if (self->priv->size_group != NULL) {
                gvc_channel_bar_set_size_group (GVC_CHANNEL_BAR (self->priv->input_bar),
                                                self->priv->size_group,
                                                FALSE);                
        }

        gtk_box_pack_start (GTK_BOX (self->priv->input_settings_box),
                            self->priv->input_bar,
                            FALSE, FALSE, 15);
        gtk_widget_show (self->priv->input_bar);
        

        /* Creating a box and try to deal using the same size group. */
        GtkWidget *input_level_box;
        input_level_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
        gtk_box_pack_start (GTK_BOX (self->priv->input_settings_box),
                            input_level_box,
                            FALSE,
                            FALSE,
                            5);

        sbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
        gtk_box_pack_start (GTK_BOX (input_level_box),
                            sbox,
                            FALSE, FALSE, 0);
        
        label = gtk_label_new (_("Input level:"));
        gtk_box_pack_start (GTK_BOX (sbox),
                            label,
                            FALSE, FALSE, 0);
        if (self->priv->size_group != NULL)
                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 (input_level_box),
                            self->priv->input_level_bar,
                            TRUE, TRUE, 0);        
        	
        /* Effects page */
        self->priv->sound_effects_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
        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, FALSE, TRUE);
        gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->effects_bar),
                                  _("_Alert volume:"));
        gtk_widget_set_sensitive (self->priv->effects_bar, FALSE);
        gtk_box_pack_start (GTK_BOX (self->priv->sound_effects_box),
                            self->priv->effects_bar, FALSE, FALSE, 0);

        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);

        /* Applications */
        self->priv->applications_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
        gtk_container_set_border_width (GTK_CONTAINER (self->priv->applications_box), 12);
        label = gtk_label_new (_("Applications"));
        gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
                                  self->priv->applications_box,
                                  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);

        indicator_visible_check = gtk_check_button_new_with_label (_("Show sound volume in the menu bar"));
        g_settings_bind (self->priv->indicator_settings, "visible",
                         indicator_visible_check, "active", G_SETTINGS_BIND_DEFAULT);
        gtk_box_pack_start (GTK_BOX (main_vbox), indicator_visible_check, FALSE, TRUE, 0);

        gtk_widget_show_all (main_vbox);

        g_signal_connect (self->priv->mixer_control,
                          "stream-added",
                          G_CALLBACK (on_control_stream_added),
                          self);
        g_signal_connect (self->priv->mixer_control,
                          "stream-removed",
                          G_CALLBACK (on_control_stream_removed),
                          self);
        g_signal_connect (self->priv->mixer_control,
                          "output-added",
                          G_CALLBACK (on_control_output_added),
                          self);
        g_signal_connect (self->priv->mixer_control,
                          "output-removed",
                          G_CALLBACK (on_control_output_removed),
                          self);
        g_signal_connect (self->priv->mixer_control,
                          "input-added",
                          G_CALLBACK (on_control_input_added),
                          self);
        g_signal_connect (self->priv->mixer_control,
                          "input-removed",
                          G_CALLBACK (on_control_input_removed),
                          self);        
        return object;
}

static void
gvc_mixer_dialog_dispose (GObject *object)
{
        GvcMixerDialog *dialog = GVC_MIXER_DIALOG (object);

        g_clear_object (&dialog->priv->sound_settings);
        g_clear_object (&dialog->priv->indicator_settings);

        if (dialog->priv->mixer_control != NULL) {
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      on_control_output_added,
                                                      dialog);
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      on_control_output_removed,
                                                      dialog);
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      on_control_active_input_update,
                                                      dialog);
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      on_control_active_output_update,
                                                      dialog);                                            
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      on_control_input_added,
                                                      dialog);
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      on_control_input_removed,
                                                      dialog);
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      on_control_stream_added,
                                                      dialog);
                g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control,
                                                      on_control_stream_removed,
                                                      dialog);
                g_object_unref (dialog->priv->mixer_control);
                dialog->priv->mixer_control = NULL;
        }

        if (dialog->priv->bars != NULL) {
                g_hash_table_destroy (dialog->priv->bars);
                dialog->priv->bars = NULL;
        }

        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_MIXER_CONTROL,
                                         g_param_spec_object ("mixer-control",
                                                              "mixer control",
                                                              "mixer control",
                                                              GVC_TYPE_MIXER_CONTROL,
                                                              G_PARAM_READWRITE|G_PARAM_CONSTRUCT));

        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 (NULL, NULL);
        dialog->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
        dialog->priv->sound_settings = g_settings_new ("com.ubuntu.sound");
        dialog->priv->indicator_settings = g_settings_new ("com.canonical.indicator.sound");
}

static void
gvc_mixer_dialog_finalize (GObject *object)
{
        GvcMixerDialog *mixer_dialog;

        g_return_if_fail (object != NULL);
        g_return_if_fail (GVC_IS_MIXER_DIALOG (object));

        mixer_dialog = GVC_MIXER_DIALOG (object);

        g_return_if_fail (mixer_dialog->priv != NULL);
        G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->finalize (object);
}

GvcMixerDialog *
gvc_mixer_dialog_new (GvcMixerControl *control)
{
        GObject *dialog;
        dialog = g_object_new (GVC_TYPE_MIXER_DIALOG,
                               "mixer-control", control,
                               "orientation", GTK_ORIENTATION_VERTICAL,
                               NULL);
        return GVC_MIXER_DIALOG (dialog);
}

enum {
        PAGE_OUTPUT,
        PAGE_INPUT,
        PAGE_EVENTS,
        PAGE_APPLICATIONS
};

gboolean
gvc_mixer_dialog_set_page (GvcMixerDialog *self,
                           const char     *page)
{
        guint num;

        g_return_val_if_fail (self != NULL, FALSE);

        num = PAGE_OUTPUT;

        if (g_str_equal (page, "effects"))
                num = PAGE_EVENTS;
        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;
}
