summaryrefslogtreecommitdiff
path: root/mate-volume-control/gvc-sound-theme-chooser.c
diff options
context:
space:
mode:
Diffstat (limited to 'mate-volume-control/gvc-sound-theme-chooser.c')
-rw-r--r--mate-volume-control/gvc-sound-theme-chooser.c1147
1 files changed, 1147 insertions, 0 deletions
diff --git a/mate-volume-control/gvc-sound-theme-chooser.c b/mate-volume-control/gvc-sound-theme-chooser.c
new file mode 100644
index 0000000..303c08b
--- /dev/null
+++ b/mate-volume-control/gvc-sound-theme-chooser.c
@@ -0,0 +1,1147 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Bastien Nocera <[email protected]>
+ * Copyright (C) 2008 William Jon McCann
+ *
+ * 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 "gvc-sound-theme-chooser.h"
+#include "sound-theme-file-utils.h"
+
+#define GVC_SOUND_THEME_CHOOSER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_SOUND_THEME_CHOOSER, GvcSoundThemeChooserPrivate))
+
+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_class_init (GvcSoundThemeChooserClass *klass);
+static void gvc_sound_theme_chooser_init (GvcSoundThemeChooser *sound_theme_chooser);
+static void gvc_sound_theme_chooser_dispose (GObject *object);
+
+#if GTK_CHECK_VERSION (3, 0, 0)
+G_DEFINE_TYPE (GvcSoundThemeChooser, gvc_sound_theme_chooser, GTK_TYPE_BOX)
+#else
+G_DEFINE_TYPE (GvcSoundThemeChooser, gvc_sound_theme_chooser, GTK_TYPE_VBOX)
+#endif
+
+#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, keep = NULL;
+ 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;
+ keep = cur;
+ } 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);
+ }
+}
+
+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;
+
+ g_type_class_add_private (klass, sizeof (GvcSoundThemeChooserPrivate));
+}
+
+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);
+}
+
+#if !GTK_CHECK_VERSION (3, 0, 0)
+static void
+constrain_list_size (GtkWidget *widget,
+ GtkRequisition *requisition,
+ GtkWidget *to_size)
+{
+ GtkRequisition req;
+ int max_height;
+
+ /* Constrain height to be the tree height up to a max */
+ max_height = (gdk_screen_get_height (gtk_widget_get_screen (widget))) / 4;
+
+ gtk_widget_size_request (to_size, &req);
+
+ requisition->height = MIN (req.height, max_height);
+}
+#endif
+
+static void
+setup_list_size_constraint (GtkWidget *widget,
+ GtkWidget *to_size)
+{
+#if GTK_CHECK_VERSION (3, 0, 0)
+ GtkRequisition req;
+ int max_height;
+
+ /* Constrain height to be the tree height up to a max */
+ max_height = (gdk_screen_get_height (gtk_widget_get_screen (widget))) / 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));
+#else
+ g_signal_connect (G_OBJECT (widget),
+ "size-request",
+ G_CALLBACK (constrain_list_size),
+ to_size);
+#endif
+}
+
+static void
+gvc_sound_theme_chooser_init (GvcSoundThemeChooser *chooser)
+{
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *scrolled_window;
+ GtkWidget *alignment;
+ gchar *str;
+
+ chooser->priv = GVC_SOUND_THEME_CHOOSER_GET_PRIVATE (chooser);
+
+#if GTK_CHECK_VERSION (3, 0, 0)
+ chooser->priv->theme_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+#else
+ chooser->priv->theme_box = gtk_hbox_new (FALSE, 0);
+#endif
+
+ 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);
+
+ 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);
+
+ alignment = gtk_alignment_new (0, 0, 1, 1);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
+ gtk_container_add (GTK_CONTAINER (alignment), box);
+ gtk_box_pack_start (GTK_BOX (chooser), alignment, TRUE, TRUE, 6);
+
+ alignment = gtk_alignment_new (0, 0, 1, 1);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
+ gtk_container_add (GTK_CONTAINER (box), alignment);
+
+ 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_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 (alignment), 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);
+ g_signal_connect (G_OBJECT (chooser->priv->sound_settings),
+ "changed",
+ G_CALLBACK (on_key_changed),
+ 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,
+#if GTK_CHECK_VERSION (3, 0, 0)
+ "orientation", GTK_ORIENTATION_VERTICAL,
+#endif
+ NULL);
+}