/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2008 Bastien Nocera * 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 #include #include #include #include #include #include #include #include #include #include #include #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); G_DEFINE_TYPE (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; 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); } static void setup_list_size_constraint (GtkWidget *widget, 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; // 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; GtkWidget *alignment; gchar *str; chooser->priv = GVC_SOUND_THEME_CHOOSER_GET_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 ("%s", _("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); 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); }