diff options
Diffstat (limited to 'mate-volume-control/gvc-sound-theme-chooser.c')
-rw-r--r-- | mate-volume-control/gvc-sound-theme-chooser.c | 1147 |
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); +} |