/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
 * Copyright (C) 2008 William Jon McCann
 * Copyright (C) 2014-2021 MATE Developers
 *
 * 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 <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <utime.h>
#include <errno.h>

#include <glib.h>
#include <glib/gi18n.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <canberra-gtk.h>
#include <libxml/tree.h>
#include <libxml/parser.h>

#include "gvc-sound-theme-chooser.h"
#include "sound-theme-file-utils.h"

struct GvcSoundThemeChooserPrivate
{
        GtkWidget *combo_box;
        GtkWidget *treeview;
        GtkWidget *theme_box;
        GtkWidget *selection_box;
        GtkWidget *click_feedback_button;
        GSettings *sound_settings;
};

static void     gvc_sound_theme_chooser_dispose   (GObject            *object);

G_DEFINE_TYPE_WITH_PRIVATE (GvcSoundThemeChooser, gvc_sound_theme_chooser, GTK_TYPE_BOX)

#define KEY_SOUNDS_SCHEMA          "org.mate.sound"
#define EVENT_SOUNDS_KEY           "event-sounds"
#define INPUT_SOUNDS_KEY           "input-feedback-sounds"
#define SOUND_THEME_KEY            "theme-name"

#define DEFAULT_ALERT_ID        "__default"
#define CUSTOM_THEME_NAME       "__custom"
#define NO_SOUNDS_THEME_NAME    "__no_sounds"

enum {
        THEME_DISPLAY_COL,
        THEME_IDENTIFIER_COL,
        THEME_PARENT_ID_COL,
        THEME_NUM_COLS
};

enum {
        ALERT_DISPLAY_COL,
        ALERT_IDENTIFIER_COL,
        ALERT_SOUND_TYPE_COL,
        ALERT_ACTIVE_COL,
        ALERT_NUM_COLS
};

enum {
        SOUND_TYPE_UNSET,
        SOUND_TYPE_OFF,
        SOUND_TYPE_DEFAULT_FROM_THEME,
        SOUND_TYPE_BUILTIN,
        SOUND_TYPE_CUSTOM
};

static void
on_combobox_changed (GtkComboBox          *widget,
                     GvcSoundThemeChooser *chooser)
{
        GtkTreeIter   iter;
        GtkTreeModel *model;
        char         *theme_name;

        if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter) == FALSE) {
                return;
        }

        model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
        gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &theme_name, -1);

        g_assert (theme_name != NULL);

        /* It is necessary to update the theme name before any other setting as
         * the "changed" notification will reload the contents of the widget */
        g_settings_set_string (chooser->priv->sound_settings, SOUND_THEME_KEY, theme_name);

        /* special case for no sounds */
        if (strcmp (theme_name, NO_SOUNDS_THEME_NAME) == 0) {
                g_settings_set_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY, FALSE);
                return;
        } else {
                g_settings_set_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY, TRUE);
        }

        g_free (theme_name);

        /* FIXME: reset alert model */
}

static char *
load_index_theme_name (const char *index,
                       char      **parent)
{
        GKeyFile *file;
        char *indexname = NULL;
        gboolean hidden;

        file = g_key_file_new ();
        if (g_key_file_load_from_file (file, index, G_KEY_FILE_KEEP_TRANSLATIONS, NULL) == FALSE) {
                g_key_file_free (file);
                return NULL;
        }
        /* Don't add hidden themes to the list */
        hidden = g_key_file_get_boolean (file, "Sound Theme", "Hidden", NULL);
        if (!hidden) {
                indexname = g_key_file_get_locale_string (file,
                                                          "Sound Theme",
                                                          "Name",
                                                          NULL,
                                                          NULL);

                /* Save the parent theme, if there's one */
                if (parent != NULL) {
                        *parent = g_key_file_get_string (file,
                                                         "Sound Theme",
                                                         "Inherits",
                                                         NULL);
                }
        }

        g_key_file_free (file);
        return indexname;
}

static void
sound_theme_in_dir (GHashTable *hash,
                    const char *dir)
{
        GDir *d;
        const char *name;

        d = g_dir_open (dir, 0, NULL);
        if (d == NULL) {
                return;
        }

        while ((name = g_dir_read_name (d)) != NULL) {
                char *dirname, *index, *indexname;

                /* Look for directories */
                dirname = g_build_filename (dir, name, NULL);
                if (g_file_test (dirname, G_FILE_TEST_IS_DIR) == FALSE) {
                        g_free (dirname);
                        continue;
                }

                /* Look for index files */
                index = g_build_filename (dirname, "index.theme", NULL);
                g_free (dirname);

                /* Check the name of the theme in the index.theme file */
                indexname = load_index_theme_name (index, NULL);
                g_free (index);
                if (indexname == NULL) {
                        continue;
                }

                g_hash_table_insert (hash, g_strdup (name), indexname);
        }

        g_dir_close (d);
}

static void
add_theme_to_store (const char   *key,
                    const char   *value,
                    GtkListStore *store)
{
        char *parent;

        parent = NULL;

        /* Get the parent, if we're checking the custom theme */
        if (strcmp (key, CUSTOM_THEME_NAME) == 0) {
                char *name, *path;

                path = custom_theme_dir_path ("index.theme");
                name = load_index_theme_name (path, &parent);
                g_free (name);
                g_free (path);
        }
        gtk_list_store_insert_with_values (store, NULL, G_MAXINT,
                                           THEME_DISPLAY_COL, value,
                                           THEME_IDENTIFIER_COL, key,
                                           THEME_PARENT_ID_COL, parent,
                                           -1);
        g_free (parent);
}

static void
set_combox_for_theme_name (GvcSoundThemeChooser *chooser,
                           const char           *name)
{
        GtkTreeIter   iter;
        GtkTreeModel *model;
        gboolean      found;

        /* If the name is empty, use "freedesktop" */
        if (name == NULL || *name == '\0') {
                name = "freedesktop";
        }

        model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));

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

        do {
                char *value;

                gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &value, -1);
                found = (value != NULL && strcmp (value, name) == 0);
                g_free (value);

        } while (!found && gtk_tree_model_iter_next (model, &iter));

        /* When we can't find the theme we need to set, try to set the default
         * one "freedesktop" */
        if (found) {
                gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter);
        } else if (strcmp (name, "freedesktop") != 0) {
                g_debug ("not found, falling back to fdo");
                set_combox_for_theme_name (chooser, "freedesktop");
        }
}

static void
set_input_feedback_enabled (GvcSoundThemeChooser *chooser,
                            gboolean              enabled)
{
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->priv->click_feedback_button),
                                      enabled);
}

static void
setup_theme_selector (GvcSoundThemeChooser *chooser)
{
        GHashTable           *hash;
        GtkListStore         *store;
        GtkCellRenderer      *renderer;
        const char * const   *data_dirs;
        const char           *data_dir;
        char                 *dir;
        guint                 i;

        /* Add the theme names and their display name to a hash table,
         * makes it easy to avoid duplicate themes */
        hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

        data_dirs = g_get_system_data_dirs ();
        for (i = 0; data_dirs[i] != NULL; i++) {
                dir = g_build_filename (data_dirs[i], "sounds", NULL);
                sound_theme_in_dir (hash, dir);
                g_free (dir);
        }

        data_dir = g_get_user_data_dir ();
        dir = g_build_filename (data_dir, "sounds", NULL);
        sound_theme_in_dir (hash, dir);
        g_free (dir);

        /* If there isn't at least one theme, make everything
         * insensitive, LAME! */
        if (g_hash_table_size (hash) == 0) {
                gtk_widget_set_sensitive (GTK_WIDGET (chooser), FALSE);
                g_warning ("Bad setup, install the freedesktop sound theme");
                g_hash_table_destroy (hash);
                return;
        }

        /* Setup the tree model, 3 columns:
         * - internal theme name/directory
         * - display theme name
         * - the internal id for the parent theme, used for the custom theme */
        store = gtk_list_store_new (THEME_NUM_COLS,
                                    G_TYPE_STRING,
                                    G_TYPE_STRING,
                                    G_TYPE_STRING);

        /* Add the themes to a combobox */
        gtk_list_store_insert_with_values (store,
                                           NULL,
                                           G_MAXINT,
                                           THEME_DISPLAY_COL, _("No sounds"),
                                           THEME_IDENTIFIER_COL, "__no_sounds",
                                           THEME_PARENT_ID_COL, NULL,
                                           -1);
        g_hash_table_foreach (hash, (GHFunc) add_theme_to_store, store);
        g_hash_table_destroy (hash);

        /* Set the display */
        gtk_combo_box_set_model (GTK_COMBO_BOX (chooser->priv->combo_box),
                                 GTK_TREE_MODEL (store));

        renderer = gtk_cell_renderer_text_new ();
        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->priv->combo_box),
                                    renderer,
                                    TRUE);
        gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser->priv->combo_box),
                                        renderer,
                                        "text", THEME_DISPLAY_COL,
                                        NULL);

        g_signal_connect (G_OBJECT (chooser->priv->combo_box),
                          "changed",
                          G_CALLBACK (on_combobox_changed),
                          chooser);
}

#define GVC_SOUND_SOUND    (xmlChar *) "sound"
#define GVC_SOUND_NAME     (xmlChar *) "name"
#define GVC_SOUND_FILENAME (xmlChar *) "filename"

/* Adapted from yelp-toc-pager.c */
static xmlChar *
xml_get_and_trim_names (xmlNodePtr node)
{
        xmlNodePtr cur;
        xmlChar *keep_lang = NULL;
        xmlChar *value;
        int j, keep_pri = INT_MAX;

        const gchar * const * langs = g_get_language_names ();

        value = NULL;

        for (cur = node->children; cur; cur = cur->next) {
                if (! xmlStrcmp (cur->name, GVC_SOUND_NAME)) {
                        xmlChar *cur_lang = NULL;
                        int cur_pri = INT_MAX;

                        cur_lang = xmlNodeGetLang (cur);

                        if (cur_lang) {
                                for (j = 0; langs[j]; j++) {
                                        if (g_str_equal (cur_lang, langs[j])) {
                                                cur_pri = j;
                                                break;
                                        }
                                }
                        } else {
                                cur_pri = INT_MAX - 1;
                        }

                        if (cur_pri <= keep_pri) {
                                if (keep_lang)
                                        xmlFree (keep_lang);
                                if (value)
                                        xmlFree (value);

                                value = xmlNodeGetContent (cur);

                                keep_lang = cur_lang;
                                keep_pri = cur_pri;
                        } else {
                                if (cur_lang)
                                        xmlFree (cur_lang);
                        }
                }
        }

        /* Delete all GVC_SOUND_NAME nodes */
        cur = node->children;
        while (cur) {
                xmlNodePtr this = cur;
                cur = cur->next;
                if (! xmlStrcmp (this->name, GVC_SOUND_NAME)) {
                        xmlUnlinkNode (this);
                        xmlFreeNode (this);
                }
        }

        return value;
}

static void
populate_model_from_node (GvcSoundThemeChooser *chooser,
                          GtkTreeModel         *model,
                          xmlNodePtr            node)
{
        xmlNodePtr child;
        xmlChar   *filename;
        xmlChar   *name;

        filename = NULL;
        name = xml_get_and_trim_names (node);
        for (child = node->children; child; child = child->next) {
                if (xmlNodeIsText (child)) {
                        continue;
                }

                if (xmlStrcmp (child->name, GVC_SOUND_FILENAME) == 0) {
                        filename = xmlNodeGetContent (child);
                } else if (xmlStrcmp (child->name, GVC_SOUND_NAME) == 0) {
                        /* EH? should have been trimmed */
                }
        }

        if (filename != NULL && name != NULL) {
                gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
                                                   NULL,
                                                   G_MAXINT,
                                                   ALERT_IDENTIFIER_COL, filename,
                                                   ALERT_DISPLAY_COL, name,
                                                   ALERT_SOUND_TYPE_COL, _("Built-in"),
                                                   ALERT_ACTIVE_COL, FALSE,
                                                   -1);
        }

        xmlFree (filename);
        xmlFree (name);
}

static void
populate_model_from_file (GvcSoundThemeChooser *chooser,
                          GtkTreeModel         *model,
                          const char           *filename)
{
        xmlDocPtr  doc;
        xmlNodePtr root;
        xmlNodePtr child;
        gboolean   exists;

        exists = g_file_test (filename, G_FILE_TEST_EXISTS);
        if (! exists) {
                return;
        }

        doc = xmlParseFile (filename);
        if (doc == NULL) {
                return;
        }

        root = xmlDocGetRootElement (doc);

        for (child = root->children; child; child = child->next) {
                if (xmlNodeIsText (child)) {
                        continue;
                }
                if (xmlStrcmp (child->name, GVC_SOUND_SOUND) != 0) {
                        continue;
                }

                populate_model_from_node (chooser, model, child);
        }

        xmlFreeDoc (doc);
}

static void
populate_model_from_dir (GvcSoundThemeChooser *chooser,
                         GtkTreeModel         *model,
                         const char           *dirname)
{
        GDir       *d;
        const char *name;

        d = g_dir_open (dirname, 0, NULL);
        if (d == NULL) {
                return;
        }

        while ((name = g_dir_read_name (d)) != NULL) {
                char *path;

                if (! g_str_has_suffix (name, ".xml")) {
                        continue;
                }

                path = g_build_filename (dirname, name, NULL);
                populate_model_from_file (chooser, model, path);
                g_free (path);
        }

        g_dir_close (d);
}

static gboolean
save_alert_sounds (GvcSoundThemeChooser  *chooser,
                   const char            *id)
{
        const char *sounds[3] = { "bell-terminal", "bell-window-system", NULL };
        char *path;

        if (strcmp (id, DEFAULT_ALERT_ID) == 0) {
                delete_old_files (sounds);
                delete_disabled_files (sounds);
        } else {
                delete_old_files (sounds);
                delete_disabled_files (sounds);
                add_custom_file (sounds, id);
        }

        /* And poke the directory so the theme gets updated */
        path = custom_theme_dir_path (NULL);
        if (utime (path, NULL) != 0) {
                g_warning ("Failed to update mtime for directory '%s': %s",
                           path, g_strerror (errno));
        }
        g_free (path);

        return FALSE;
}

static void
update_alert_model (GvcSoundThemeChooser  *chooser,
                    const char            *id)
{
        GtkTreeModel *model;
        GtkTreeIter   iter;

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
        gtk_tree_model_get_iter_first (model, &iter);
        do {
                gboolean toggled;
                char    *this_id;

                gtk_tree_model_get (model, &iter,
                                    ALERT_IDENTIFIER_COL, &this_id,
                                    -1);

                if (strcmp (this_id, id) == 0) {
                        toggled = TRUE;
                } else {
                        toggled = FALSE;
                }
                g_free (this_id);

                gtk_list_store_set (GTK_LIST_STORE (model),
                                    &iter,
                                    ALERT_ACTIVE_COL, toggled,
                                    -1);
        } while (gtk_tree_model_iter_next (model, &iter));
}

static void
update_alert (GvcSoundThemeChooser *chooser,
              const char           *alert_id)
{
        GtkTreeModel *theme_model;
        GtkTreeIter   iter;
        char         *theme;
        char         *parent;
        gboolean      is_custom;
        gboolean      is_default;
        gboolean      add_custom;
        gboolean      remove_custom;

        theme_model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
        /* Get the current theme's name, and set the parent */
        if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter) == FALSE) {
                return;
        }

        gtk_tree_model_get (theme_model, &iter,
                            THEME_IDENTIFIER_COL, &theme,
                            THEME_IDENTIFIER_COL, &parent,
                            -1);
        is_custom = strcmp (theme, CUSTOM_THEME_NAME) == 0;
        is_default = strcmp (alert_id, DEFAULT_ALERT_ID) == 0;

        /* So a few possibilities:
         * 1. Named theme, default alert selected: noop
         * 2. Named theme, alternate alert selected: create new custom with sound
         * 3. Custom theme, default alert selected: remove sound and possibly custom
         * 4. Custom theme, alternate alert selected: update custom sound
         */
        add_custom = FALSE;
        remove_custom = FALSE;
        if (! is_custom && is_default) {
                /* remove custom just in case */
                remove_custom = TRUE;
        } else if (! is_custom && ! is_default) {
                create_custom_theme (parent);
                save_alert_sounds (chooser, alert_id);
                add_custom = TRUE;
        } else if (is_custom && is_default) {
                save_alert_sounds (chooser, alert_id);
                /* after removing files check if it is empty */
                if (custom_theme_dir_is_empty ()) {
                        remove_custom = TRUE;
                }
        } else if (is_custom && ! is_default) {
                save_alert_sounds (chooser, alert_id);
        }

        if (add_custom) {
                gtk_list_store_insert_with_values (GTK_LIST_STORE (theme_model),
                                                   NULL,
                                                   G_MAXINT,
                                                   THEME_DISPLAY_COL, _("Custom"),
                                                   THEME_IDENTIFIER_COL, CUSTOM_THEME_NAME,
                                                   THEME_PARENT_ID_COL, theme,
                                                   -1);
                set_combox_for_theme_name (chooser, CUSTOM_THEME_NAME);
        } else if (remove_custom) {
                gtk_tree_model_get_iter_first (theme_model, &iter);
                do {
                        char *this_parent;

                        gtk_tree_model_get (theme_model, &iter,
                                            THEME_PARENT_ID_COL, &this_parent,
                                            -1);
                        if (this_parent != NULL && strcmp (this_parent, CUSTOM_THEME_NAME) != 0) {
                                g_free (this_parent);
                                gtk_list_store_remove (GTK_LIST_STORE (theme_model), &iter);
                                break;
                        }
                        g_free (this_parent);
                } while (gtk_tree_model_iter_next (theme_model, &iter));

                delete_custom_theme_dir ();

                set_combox_for_theme_name (chooser, parent);
        }

        update_alert_model (chooser, alert_id);

        g_free (theme);
        g_free (parent);
}

static void
on_alert_toggled (GtkCellRendererToggle *renderer,
                  char                  *path_str,
                  GvcSoundThemeChooser  *chooser)
{
        GtkTreeModel *model;
        GtkTreeIter   iter;
        GtkTreePath  *path;
        gboolean      toggled;
        char         *id;

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));

        path = gtk_tree_path_new_from_string (path_str);
        gtk_tree_model_get_iter (model, &iter, path);
        gtk_tree_path_free (path);

        id = NULL;
        gtk_tree_model_get (model, &iter,
                            ALERT_IDENTIFIER_COL, &id,
                            ALERT_ACTIVE_COL, &toggled,
                            -1);

        toggled ^= 1;
        if (toggled) {
                update_alert (chooser, id);
        }

        g_free (id);
}

static void
play_preview_for_path (GvcSoundThemeChooser *chooser, GtkTreePath *path)
{
        GtkTreeModel *model;
        GtkTreeIter   iter;
        GtkTreeIter   theme_iter;
        gchar        *id = NULL;
        gchar        *parent_theme = NULL;

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
        if (gtk_tree_model_get_iter (model, &iter, path) == FALSE)
                return;

        gtk_tree_model_get (model, &iter,
                            ALERT_IDENTIFIER_COL, &id,
                            -1);
        if (id == NULL)
                return;

        if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &theme_iter)) {
                GtkTreeModel *theme_model;
                gchar        *theme_id = NULL;
                gchar        *parent_id = NULL;

                theme_model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));

                gtk_tree_model_get (theme_model, &theme_iter,
                                    THEME_IDENTIFIER_COL, &theme_id,
                                    THEME_PARENT_ID_COL, &parent_id, -1);
                if (theme_id && strcmp (theme_id, CUSTOM_THEME_NAME) == 0)
                        parent_theme = g_strdup (parent_id);

                g_free (theme_id);
                g_free (parent_id);
        }

        /* special case: for the default item on custom themes
         * play the alert for the parent theme */
        if (strcmp (id, DEFAULT_ALERT_ID) == 0) {
                if (parent_theme != NULL) {
                        ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
                                                CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
                                                CA_PROP_EVENT_ID, "bell-window-system",
                                                CA_PROP_CANBERRA_XDG_THEME_NAME, parent_theme,
                                                CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
                                                CA_PROP_CANBERRA_CACHE_CONTROL, "never",
                                                CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
#ifdef CA_PROP_CANBERRA_ENABLE
                                                CA_PROP_CANBERRA_ENABLE, "1",
#endif
                                                NULL);
                } else {
                        ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
                                                CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
                                                CA_PROP_EVENT_ID, "bell-window-system",
                                                CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
                                                CA_PROP_CANBERRA_CACHE_CONTROL, "never",
                                                CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
#ifdef CA_PROP_CANBERRA_ENABLE
                                                CA_PROP_CANBERRA_ENABLE, "1",
#endif
                                                NULL);
                }
        } else {
                ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
                                        CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
                                        CA_PROP_MEDIA_FILENAME, id,
                                        CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
                                        CA_PROP_CANBERRA_CACHE_CONTROL, "never",
                                        CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
#ifdef CA_PROP_CANBERRA_ENABLE
                                        CA_PROP_CANBERRA_ENABLE, "1",
#endif
                                        NULL);

        }
        g_free (parent_theme);
        g_free (id);
}

static void
on_treeview_row_activated (GtkTreeView          *treeview,
                           GtkTreePath          *path,
                           GtkTreeViewColumn    *column,
                           GvcSoundThemeChooser *chooser)
{
        play_preview_for_path (chooser, path);
}

static void
on_treeview_selection_changed (GtkTreeSelection     *selection,
                               GvcSoundThemeChooser *chooser)
{
        GList        *paths;
        GtkTreeModel *model;
        GtkTreePath  *path;

        if (chooser->priv->treeview == NULL)
                return;

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));

        paths = gtk_tree_selection_get_selected_rows (selection, &model);
        if (paths == NULL)
                return;

        path = paths->data;
        play_preview_for_path (chooser, path);

        g_list_foreach (paths, (GFunc)gtk_tree_path_free, NULL);
        g_list_free (paths);
}

static GtkWidget *
create_alert_treeview (GvcSoundThemeChooser *chooser)
{
        GtkListStore         *store;
        GtkWidget            *treeview;
        GtkCellRenderer      *renderer;
        GtkTreeViewColumn    *column;
        GtkTreeSelection     *selection;

        treeview = gtk_tree_view_new ();

        gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
        g_signal_connect (G_OBJECT (treeview),
                          "row-activated",
                          G_CALLBACK (on_treeview_row_activated),
                          chooser);

        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));

        gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
        g_signal_connect (G_OBJECT (selection),
                          "changed",
                          G_CALLBACK (on_treeview_selection_changed),
                          chooser);

        /* Setup the tree model, 3 columns:
         * - display name
         * - sound id
         * - sound type
         */
        store = gtk_list_store_new (ALERT_NUM_COLS,
                                    G_TYPE_STRING,
                                    G_TYPE_STRING,
                                    G_TYPE_STRING,
                                    G_TYPE_BOOLEAN);

        gtk_list_store_insert_with_values (store,
                                           NULL,
                                           G_MAXINT,
                                           ALERT_IDENTIFIER_COL, DEFAULT_ALERT_ID,
                                           ALERT_DISPLAY_COL, _("Default"),
                                           ALERT_SOUND_TYPE_COL, _("From theme"),
                                           ALERT_ACTIVE_COL, TRUE,
                                           -1);

        populate_model_from_dir (chooser, GTK_TREE_MODEL (store), SOUND_SET_DIR);

        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", ALERT_ACTIVE_COL,
                                                           NULL);
        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
        g_signal_connect (renderer,
                          "toggled",
                          G_CALLBACK (on_alert_toggled),
                          chooser);

        renderer = gtk_cell_renderer_text_new ();
        column = gtk_tree_view_column_new_with_attributes (_("Name"),
                                                           renderer,
                                                           "text", ALERT_DISPLAY_COL,
                                                           NULL);
        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);

        renderer = gtk_cell_renderer_text_new ();
        column = gtk_tree_view_column_new_with_attributes (_("Type"),
                                                           renderer,
                                                           "text", ALERT_SOUND_TYPE_COL,
                                                           NULL);

        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);

        return treeview;
}

static int
get_file_type (const char *sound_name,
               char      **linked_name)
{
        char *name, *filename;

        *linked_name = NULL;

        name = g_strdup_printf ("%s.disabled", sound_name);
        filename = custom_theme_dir_path (name);
        g_free (name);

        if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) != FALSE) {
                g_free (filename);
                return SOUND_TYPE_OFF;
        }
        g_free (filename);

        /* We only check for .ogg files because those are the
         * only ones we create */
        name = g_strdup_printf ("%s.ogg", sound_name);
        filename = custom_theme_dir_path (name);
        g_free (name);

        if (g_file_test (filename, G_FILE_TEST_IS_SYMLINK) != FALSE) {
                *linked_name = g_file_read_link (filename, NULL);
                g_free (filename);
                return SOUND_TYPE_CUSTOM;
        }
        g_free (filename);

        return SOUND_TYPE_BUILTIN;
}

static void
update_alerts_from_theme_name (GvcSoundThemeChooser *chooser,
                               const gchar          *name)
{
        if (strcmp (name, CUSTOM_THEME_NAME) != 0) {
                /* reset alert to default */
                update_alert (chooser, DEFAULT_ALERT_ID);
        } else {
                int   sound_type;
                char *linkname;

                linkname = NULL;
                sound_type = get_file_type ("bell-terminal", &linkname);
                g_debug ("Found link: %s", linkname);
                if (sound_type == SOUND_TYPE_CUSTOM) {
                        update_alert (chooser, linkname);
                }
        }
}

static void
update_theme (GvcSoundThemeChooser *chooser)
{
        char        *theme_name;
        gboolean     events_enabled;
        gboolean     feedback_enabled;

        feedback_enabled = g_settings_get_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY);
        set_input_feedback_enabled (chooser, feedback_enabled);

        events_enabled = g_settings_get_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY);
        if (events_enabled) {
                theme_name = g_settings_get_string (chooser->priv->sound_settings, SOUND_THEME_KEY);
        } else {
                theme_name = g_strdup (NO_SOUNDS_THEME_NAME);
        }

        gtk_widget_set_sensitive (chooser->priv->selection_box, events_enabled);
        gtk_widget_set_sensitive (chooser->priv->click_feedback_button, events_enabled);

        set_combox_for_theme_name (chooser, theme_name);

        update_alerts_from_theme_name (chooser, theme_name);

        g_free (theme_name);
}

static void
gvc_sound_theme_chooser_class_init (GvcSoundThemeChooserClass *klass)
{
        GObjectClass *object_class = G_OBJECT_CLASS (klass);

        object_class->dispose = gvc_sound_theme_chooser_dispose;
}

static void
on_click_feedback_toggled (GtkToggleButton      *button,
                           GvcSoundThemeChooser *chooser)
{
        gboolean enabled;

        enabled = gtk_toggle_button_get_active (button);

        g_settings_set_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY, enabled);
}

static void
on_key_changed (GSettings            *settings,
                gchar                *key,
                GvcSoundThemeChooser *chooser)
{
        if (!strcmp (key, EVENT_SOUNDS_KEY) ||
            !strcmp (key, SOUND_THEME_KEY) ||
            !strcmp (key, INPUT_SOUNDS_KEY))
                update_theme (chooser);
}

static void
setup_list_size_constraint (GtkWidget *widget,
                            GtkWidget *to_size)
{
        GtkRequisition req;
        gint           sc_height;
        int            max_height;

        /* Constrain height to be the tree height up to a max */
        gdk_window_get_geometry (gdk_screen_get_root_window (gtk_widget_get_screen (widget)),
                                 NULL, NULL, NULL, &sc_height);

        max_height = sc_height / 4;

        // XXX this doesn't work
        gtk_widget_get_preferred_size (to_size, NULL, &req);

        gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (widget),
                                                    MIN (req.height, max_height));
}

static void
gvc_sound_theme_chooser_init (GvcSoundThemeChooser *chooser)
{
        GtkWidget   *box;
        GtkWidget   *label;
        GtkWidget   *scrolled_window;
        gchar       *str;

        chooser->priv = gvc_sound_theme_chooser_get_instance_private (chooser);

        chooser->priv->theme_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);

        gtk_box_pack_start (GTK_BOX (chooser),
                            chooser->priv->theme_box, FALSE, FALSE, 0);

        label = gtk_label_new_with_mnemonic (_("Sound _theme:"));
        gtk_box_pack_start (GTK_BOX (chooser->priv->theme_box), label, FALSE, FALSE, 0);
        chooser->priv->combo_box = gtk_combo_box_new ();
        gtk_box_pack_start (GTK_BOX (chooser->priv->theme_box), chooser->priv->combo_box, FALSE, FALSE, 6);
        gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->combo_box);

        chooser->priv->sound_settings = g_settings_new (KEY_SOUNDS_SCHEMA);

        g_signal_connect (G_OBJECT (chooser->priv->sound_settings),
                          "changed",
                          G_CALLBACK (on_key_changed),
                          chooser);

        str = g_strdup_printf ("<b>%s</b>", _("C_hoose an alert sound:"));
        chooser->priv->selection_box = box = gtk_frame_new (str);
        g_free (str);

        label = gtk_frame_get_label_widget (GTK_FRAME (box));
        gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
        gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
        gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);

        gtk_box_pack_start (GTK_BOX (chooser), box, TRUE, TRUE, 6);

        chooser->priv->treeview = create_alert_treeview (chooser);
        gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->treeview);

        scrolled_window = gtk_scrolled_window_new (NULL, NULL);
        gtk_widget_set_hexpand (scrolled_window, TRUE);
        gtk_widget_set_vexpand (scrolled_window, TRUE);
        gtk_widget_set_margin_top (scrolled_window, 6);

        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
                                        GTK_POLICY_NEVER,
                                        GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
                                             GTK_SHADOW_IN);

        gtk_container_add (GTK_CONTAINER (scrolled_window), chooser->priv->treeview);
        gtk_container_add (GTK_CONTAINER (box), scrolled_window);

        chooser->priv->click_feedback_button = gtk_check_button_new_with_mnemonic (_("Enable _window and button sounds"));
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->priv->click_feedback_button),
                                      g_settings_get_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY));

        gtk_box_pack_start (GTK_BOX (chooser),
                            chooser->priv->click_feedback_button,
                            FALSE, FALSE, 0);

        g_signal_connect (G_OBJECT (chooser->priv->click_feedback_button),
                          "toggled",
                          G_CALLBACK (on_click_feedback_toggled),
                          chooser);

        setup_theme_selector (chooser);
        update_theme (chooser);

        setup_list_size_constraint (scrolled_window, chooser->priv->treeview);
}

static void
gvc_sound_theme_chooser_dispose (GObject *object)
{
        GvcSoundThemeChooser *chooser;

        chooser = GVC_SOUND_THEME_CHOOSER (object);

        g_clear_object (&chooser->priv->sound_settings);

        G_OBJECT_CLASS (gvc_sound_theme_chooser_parent_class)->dispose (object);
}

GtkWidget *
gvc_sound_theme_chooser_new (void)
{
        return g_object_new (GVC_TYPE_SOUND_THEME_CHOOSER,
                                "spacing", 6,
                                "orientation", GTK_ORIENTATION_VERTICAL,
                                NULL);
}
