/* -*- 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 "gvc-sound-theme-editor.h" #include "sound-theme-file-utils.h" #define GVC_SOUND_THEME_EDITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_SOUND_THEME_EDITOR, GvcSoundThemeEditorPrivate)) struct GvcSoundThemeEditorPrivate { GtkWidget *treeview; GtkWidget *theme_box; GtkWidget *selection_box; GtkWidget *click_feedback_button; GSettings *sound_settings; GSettings *marco_settings; }; static void gvc_sound_theme_editor_class_init (GvcSoundThemeEditorClass *klass); static void gvc_sound_theme_editor_init (GvcSoundThemeEditor *sound_theme_editor); static void gvc_sound_theme_editor_finalize (GObject *object); G_DEFINE_TYPE (GvcSoundThemeEditor, gvc_sound_theme_editor, GTK_TYPE_VBOX) typedef enum { CATEGORY_INVALID, CATEGORY_BELL, CATEGORY_WINDOWS_BUTTONS, CATEGORY_DESKTOP, CATEGORY_ALERTS, NUM_CATEGORIES } CategoryType; typedef enum { SOUND_TYPE_NORMAL, SOUND_TYPE_AUDIO_BELL, SOUND_TYPE_FEEDBACK } SoundType; static struct { CategoryType category; SoundType type; const char *display_name; const char *names[6]; } sounds[20] = { /* Bell */ { CATEGORY_BELL, SOUND_TYPE_AUDIO_BELL, NC_("Sound event", "Alert sound"), { "bell-terminal", "bell-window-system", NULL } }, /* Windows and buttons */ { CATEGORY_WINDOWS_BUTTONS, -1, NC_("Sound event", "Windows and Buttons"), { NULL } }, { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Button clicked"), { "button-pressed", "menu-click", "menu-popup", "menu-popdown", "menu-replace", NULL } }, { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Toggle button clicked"), { "button-toggle-off", "button-toggle-on", NULL } }, { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Window maximized"), { "window-maximized", NULL } }, { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Window unmaximized"), { "window-unmaximized", NULL } }, { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Window minimised"), { "window-minimized", NULL } }, /* Desktop */ { CATEGORY_DESKTOP, -1, NC_("Sound event", "Desktop"), { NULL } }, { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "Login"), { "desktop-login", NULL } }, { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "Logout"), { "desktop-logout", NULL } }, { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "New e-mail"), { "message-new-email", NULL } }, { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "Empty trash"), { "trash-empty", NULL } }, { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "Long action completed (download, CD burning, etc.)"), { "complete-copy", "complete-download", "complete-media-burn", "complete-media-rip", "complete-scan", NULL } }, /* Alerts? */ { CATEGORY_ALERTS, -1, NC_("Sound event", "Alerts"), { NULL } }, { CATEGORY_ALERTS, SOUND_TYPE_NORMAL, NC_("Sound event", "Information or question"), { "dialog-information", "dialog-question", NULL } }, { CATEGORY_ALERTS, SOUND_TYPE_NORMAL, NC_("Sound event", "Warning"), { "dialog-warning", NULL } }, { CATEGORY_ALERTS, SOUND_TYPE_NORMAL, NC_("Sound event", "Error"), { "dialog-error", NULL } }, { CATEGORY_ALERTS, SOUND_TYPE_NORMAL, NC_("Sound event", "Battery warning"), { "power-unplug-battery-low", "battery-low", "battery-caution", NULL } }, /* Finish off */ { -1, -1, NULL, { NULL } } }; #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 KEY_MARCO_SCHEMA "org.mate.Marco.general" #define AUDIO_BELL_KEY "audible-bell" #define CUSTOM_THEME_NAME "__custom" #define NO_SOUNDS_THEME_NAME "__no_sounds" #define PREVIEW_BUTTON_XPAD 5 enum { THEME_DISPLAY_COL, THEME_IDENTIFIER_COL, THEME_PARENT_ID_COL, THEME_NUM_COLS }; enum { SOUND_UNSET, SOUND_OFF, SOUND_BUILTIN, SOUND_CUSTOM, SOUND_CUSTOM_OLD }; enum { DISPLAY_COL, SETTING_COL, TYPE_COL, SENSITIVE_COL, HAS_PREVIEW_COL, FILENAME_COL, SOUND_NAMES_COL, NUM_COLS }; static gboolean theme_changed_custom_reinit (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { int type; gboolean sensitive; gtk_tree_model_get (model, iter, TYPE_COL, &type, SENSITIVE_COL, &sensitive, -1); if (type != -1) { gtk_tree_store_set (GTK_TREE_STORE (model), iter, SETTING_COL, SOUND_BUILTIN, HAS_PREVIEW_COL, sensitive, -1); } return FALSE; } static void on_theme_changed () { /* Don't reinit a custom theme */ if (strcmp (theme_name, CUSTOM_THEME_NAME) != 0) { model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); gtk_tree_model_foreach (model, theme_changed_custom_reinit, NULL); /* Delete the custom dir */ delete_custom_theme_dir (); /* And the combo box entry */ model = gtk_combo_box_get_model (GTK_COMBO_BOX (editor->priv->combo_box)); gtk_tree_model_get_iter_first (model, &iter); do { char *parent; gtk_tree_model_get (model, &iter, THEME_PARENT_ID_COL, &parent, -1); if (parent != NULL && strcmp (parent, CUSTOM_THEME_NAME) != 0) { gtk_list_store_remove (GTK_LIST_STORE (model), &iter); g_free (parent); break; } g_free (parent); } while (gtk_tree_model_iter_next (model, &iter)); } } 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_theme_name (GvcSoundThemeEditor *editor, const char *name) { g_debug ("setting theme %s", name ? name : "(null)"); /* If the name is empty, use "freedesktop" */ if (name == NULL || *name == '\0') { name = "freedesktop"; } g_settings_set_string (editor->priv->sound_settings, SOUND_THEME_KEY, theme_name); } /* Functions to toggle whether the audible bell sound is editable */ static gboolean audible_bell_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { int type; int setting; gboolean enabled = GPOINTER_TO_INT (data); setting = enabled ? SOUND_BUILTIN : SOUND_OFF; gtk_tree_model_get (model, iter, TYPE_COL, &type, -1); if (type == SOUND_TYPE_AUDIO_BELL) { gtk_tree_store_set (GTK_TREE_STORE (model), iter, SETTING_COL, setting, HAS_PREVIEW_COL, enabled, -1); return TRUE; } return FALSE; } static void set_audible_bell_enabled (GvcSoundThemeEditor *editor, gboolean enabled) { GtkTreeModel *model; model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); gtk_tree_model_foreach (model, audible_bell_foreach, GINT_TO_POINTER (enabled)); } /* Functions to toggle whether the Input feedback sounds are editable */ static gboolean input_feedback_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { int type; gboolean enabled = GPOINTER_TO_INT (data); gtk_tree_model_get (model, iter, TYPE_COL, &type, -1); if (type == SOUND_TYPE_FEEDBACK) { gtk_tree_store_set (GTK_TREE_STORE (model), iter, SENSITIVE_COL, enabled, HAS_PREVIEW_COL, enabled, -1); } return FALSE; } static void set_input_feedback_enabled (GvcSoundThemeEditor *editor, gboolean enabled) { GtkTreeModel *model; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (editor->priv->click_feedback_button), enabled); model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); gtk_tree_model_foreach (model, input_feedback_foreach, GINT_TO_POINTER (enabled)); } 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_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_CUSTOM; } g_free (filename); return SOUND_BUILTIN; } static gboolean theme_changed_custom_init (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { char **sound_names; gtk_tree_model_get (model, iter, SOUND_NAMES_COL, &sound_names, -1); if (sound_names != NULL) { char *filename; int type; type = get_file_type (sound_names[0], &filename); gtk_tree_store_set (GTK_TREE_STORE (model), iter, SETTING_COL, type, HAS_PREVIEW_COL, type != SOUND_OFF, FILENAME_COL, filename, -1); g_strfreev (sound_names); g_free (filename); } return FALSE; } static void update_theme (GvcSoundThemeEditor *editor) { char *theme_name; gboolean events_enabled; gboolean bell_enabled; gboolean feedback_enabled; bell_enabled = g_settings_get_boolean (editor->priv->marco_settings, AUDIO_BELL_KEY); set_audible_bell_enabled (editor, bell_enabled); feedback_enabled = g_settings_get_boolean (editor->priv->sound_settings, INPUT_SOUNDS_KEY); set_input_feedback_enabled (editor, feedback_enabled); events_enabled = g_settings_get_boolean (editor->priv->sound_settings, EVENT_SOUNDS_KEY); if (events_enabled) { theme_name = g_settings_get_string (editor->priv->sound_settings, SOUND_THEME_KEY); } else { theme_name = g_strdup (NO_SOUNDS_THEME_NAME); } gtk_widget_set_sensitive (editor->priv->selection_box, events_enabled); set_theme_name (editor, theme_name); /* Setup the default values if we're using the custom theme */ if (theme_name != NULL && strcmp (theme_name, CUSTOM_THEME_NAME) == 0) { GtkTreeModel *model; model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); gtk_tree_model_foreach (model, theme_changed_custom_init, NULL); } g_free (theme_name); } static void setup_theme_selector (GvcSoundThemeEditor *editor) { 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 (editor), 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 (editor->priv->combo_box), GTK_TREE_MODEL (store)); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (editor->priv->combo_box), renderer, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (editor->priv->combo_box), renderer, "text", THEME_DISPLAY_COL, NULL); g_signal_connect (editor->priv->combo_box, "changed", G_CALLBACK (on_combobox_changed), editor); } static void play_sound_preview (GtkFileEditor *editor, gpointer user_data) { char *filename; filename = gtk_file_editor_get_preview_filename (GTK_FILE_EDITOR (editor)); if (filename == NULL) { return; } ca_gtk_play_for_widget (GTK_WIDGET (editor), 0, CA_PROP_APPLICATION_NAME, _("Sound Preferences"), CA_PROP_MEDIA_FILENAME, filename, 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 (filename); } static char * get_sound_filename (GvcSoundThemeEditor *editor) { GtkWidget *file_editor; GtkWidget *toplevel; GtkWindow *parent; int response; char *filename; char *path; const char * const *data_dirs, *data_dir; GtkFileFilter *filter; guint i; /* Try to get the parent window of the widget */ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (editor)); if (gtk_widget_is_toplevel (toplevel) != FALSE) parent = GTK_WINDOW (toplevel); else parent = NULL; file_editor = gtk_file_editor_dialog_new (_("Select Sound File"), parent, GTK_FILE_EDITOR_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_file_editor_set_local_only (GTK_FILE_EDITOR (file_editor), TRUE); gtk_file_editor_set_select_multiple (GTK_FILE_EDITOR (file_editor), FALSE); filter = gtk_file_filter_new (); gtk_file_filter_set_name (filter, _("Sound files")); gtk_file_filter_add_mime_type (filter, "audio/x-vorbis+ogg"); gtk_file_filter_add_mime_type (filter, "audio/x-wav"); gtk_file_editor_add_filter (GTK_FILE_EDITOR (file_editor), filter); gtk_file_editor_set_filter (GTK_FILE_EDITOR (file_editor), filter); g_signal_connect (file_editor, "update-preview", G_CALLBACK (play_sound_preview), NULL); data_dirs = g_get_system_data_dirs (); for (i = 0; data_dirs[i] != NULL; i++) { path = g_build_filename (data_dirs[i], "sounds", NULL); gtk_file_editor_add_shortcut_folder (GTK_FILE_EDITOR (file_editor), path, NULL); g_free (path); } data_dir = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC); if (data_dir != NULL) gtk_file_editor_add_shortcut_folder (GTK_FILE_EDITOR (file_editor), data_dir, NULL); gtk_file_editor_set_current_folder (GTK_FILE_EDITOR (file_editor), SOUND_DATA_DIR); response = gtk_dialog_run (GTK_DIALOG (file_editor)); filename = NULL; if (response == GTK_RESPONSE_ACCEPT) filename = gtk_file_editor_get_filename (GTK_FILE_EDITOR (file_editor)); gtk_widget_destroy (file_editor); return filename; } static gboolean count_customised_sounds (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, int *num_custom) { int type; int setting; gtk_tree_model_get (model, iter, TYPE_COL, &type, SETTING_COL, &setting, -1); if (setting == SOUND_OFF || setting == SOUND_CUSTOM || setting == SOUND_CUSTOM_OLD) { (*num_custom)++; } return FALSE; } static gboolean save_sounds (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { int type; int setting; char *filename; char **sounds; gtk_tree_model_get (model, iter, TYPE_COL, &type, SETTING_COL, &setting, FILENAME_COL, &filename, SOUND_NAMES_COL, &sounds, -1); if (setting == SOUND_BUILTIN) { delete_old_files (sounds); delete_disabled_files (sounds); } else if (setting == SOUND_OFF) { delete_old_files (sounds); add_disabled_file (sounds); } else if (setting == SOUND_CUSTOM || setting == SOUND_CUSTOM_OLD) { delete_old_files (sounds); delete_disabled_files (sounds); add_custom_file (sounds, filename); } g_free (filename); g_strfreev (sounds); return FALSE; } static void save_custom_theme (GtkTreeModel *model, const char *parent) { GKeyFile *keyfile; char *data; char *path; /* Create the custom directory */ path = custom_theme_dir_path (NULL); g_mkdir_with_parents (path, 0755); g_free (path); /* Save the sounds themselves */ gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) save_sounds, NULL); /* Set the data for index.theme */ keyfile = g_key_file_new (); g_key_file_set_string (keyfile, "Sound Theme", "Name", _("Custom")); g_key_file_set_string (keyfile, "Sound Theme", "Inherits", parent); g_key_file_set_string (keyfile, "Sound Theme", "Directories", "."); data = g_key_file_to_data (keyfile, NULL, NULL); g_key_file_free (keyfile); /* Save the index.theme */ path = custom_theme_dir_path ("index.theme"); g_file_set_contents (path, data, -1, NULL); g_free (path); g_free (data); custom_theme_update_time (); } static void dump_theme (GvcSoundThemeEditor *editor) { int num_custom; GtkTreeModel *model; GtkTreeIter iter; char *parent; num_custom = 0; model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) count_customised_sounds, &num_custom); g_debug ("%d customised sounds", num_custom); model = gtk_combo_box_get_model (GTK_COMBO_BOX (editor->priv->combo_box)); /* Get the current theme's name, and set the parent */ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (editor->priv->combo_box), &iter) == FALSE) return; if (num_custom == 0) { gtk_tree_model_get (model, &iter, THEME_PARENT_ID_COL, &parent, -1); if (parent != NULL) { set_theme_name (editor, parent); g_free (parent); } gtk_tree_model_get_iter_first (model, &iter); do { gtk_tree_model_get (model, &iter, THEME_PARENT_ID_COL, &parent, -1); if (parent != NULL && strcmp (parent, CUSTOM_THEME_NAME) != 0) { gtk_list_store_remove (GTK_LIST_STORE (model), &iter); break; } } while (gtk_tree_model_iter_next (model, &iter)); delete_custom_theme_dir (); } else { gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &parent, -1); if (strcmp (parent, CUSTOM_THEME_NAME) != 0) { gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, G_MAXINT, THEME_DISPLAY_COL, _("Custom"), THEME_IDENTIFIER_COL, CUSTOM_THEME_NAME, THEME_PARENT_ID_COL, parent, -1); } else { g_free (parent); gtk_tree_model_get (model, &iter, THEME_PARENT_ID_COL, &parent, -1); } g_debug ("The parent theme is: %s", parent); model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); save_custom_theme (model, parent); g_free (parent); set_theme_name (editor, CUSTOM_THEME_NAME); } } static void on_setting_column_edited (GtkCellRendererText *renderer, char *path, char *new_text, GvcSoundThemeEditor *editor) { GtkTreeModel *model; GtkTreeModel *tree_model; GtkTreeIter iter; GtkTreeIter tree_iter; SoundType type; char *text; char *old_filename; int setting; if (new_text == NULL) { return; } g_object_get (renderer, "model", &model, NULL); tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); if (gtk_tree_model_get_iter_from_string (tree_model, &tree_iter, path) == FALSE) return; gtk_tree_model_get (tree_model, &tree_iter, TYPE_COL, &type, FILENAME_COL, &old_filename, -1); gtk_tree_model_get_iter_first (model, &iter); do { int cmp; gtk_tree_model_get (model, &iter, 0, &text, 1, &setting, -1); cmp = g_utf8_collate (text, new_text); g_free (text); if (cmp != 0) { continue; } if (type == SOUND_TYPE_NORMAL || type == SOUND_TYPE_FEEDBACK || type == SOUND_TYPE_AUDIO_BELL) { if (setting == SOUND_CUSTOM || (setting == SOUND_CUSTOM_OLD && old_filename == NULL)) { char *filename = get_sound_filename (editor); if (filename == NULL) { break; } gtk_tree_store_set (GTK_TREE_STORE (tree_model), &tree_iter, SETTING_COL, setting, HAS_PREVIEW_COL, setting != SOUND_OFF, FILENAME_COL, filename, -1); g_free (filename); } else if (setting == SOUND_CUSTOM_OLD) { gtk_tree_store_set (GTK_TREE_STORE (tree_model), &tree_iter, SETTING_COL, setting, HAS_PREVIEW_COL, setting != SOUND_OFF, FILENAME_COL, old_filename, -1); } else { gtk_tree_store_set (GTK_TREE_STORE (tree_model), &tree_iter, SETTING_COL, setting, HAS_PREVIEW_COL, setting != SOUND_OFF, -1); } g_debug ("Something changed, dump theme"); dump_theme (editor); break; } g_assert_not_reached (); } while (gtk_tree_model_iter_next (model, &iter)); g_free (old_filename); } static void fill_custom_model (GtkListStore *store, const char *prev_filename) { GtkTreeIter iter; gtk_list_store_clear (store); if (prev_filename != NULL) { char *display; display = g_filename_display_basename (prev_filename); gtk_list_store_insert_with_values (store, &iter, G_MAXINT, 0, display, 1, SOUND_CUSTOM_OLD, -1); g_free (display); } gtk_list_store_insert_with_values (store, &iter, G_MAXINT, 0, _("Default"), 1, SOUND_BUILTIN, -1); gtk_list_store_insert_with_values (store, &iter, G_MAXINT, 0, _("Disabled"), 1, SOUND_OFF, -1); gtk_list_store_insert_with_values (store, &iter, G_MAXINT, 0, _("Custom…"), 1, SOUND_CUSTOM, -1); } static void on_combobox_editing_started (GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path, GvcSoundThemeEditor *editor) { GtkTreeModel *model; GtkTreeModel *store; GtkTreeIter iter; SoundType type; char *filename; model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); if (gtk_tree_model_get_iter_from_string (model, &iter, path) == FALSE) { return; } gtk_tree_model_get (model, &iter, TYPE_COL, &type, FILENAME_COL, &filename, -1); g_object_get (renderer, "model", &store, NULL); fill_custom_model (GTK_LIST_STORE (store), filename); g_free (filename); } static gboolean play_sound_at_path (GtkWidget *tree_view, GtkTreePath *path) { GtkTreeModel *model; GtkTreeIter iter; char **sound_names; gboolean sensitive; model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); if (gtk_tree_model_get_iter (model, &iter, path) == FALSE) { return FALSE; } gtk_tree_model_get (model, &iter, SOUND_NAMES_COL, &sound_names, SENSITIVE_COL, &sensitive, -1); if (!sensitive || sound_names == NULL) { return FALSE; } ca_gtk_play_for_widget (GTK_WIDGET (tree_view), 0, CA_PROP_APPLICATION_NAME, _("Sound Preferences"), CA_PROP_EVENT_ID, sound_names[0], 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_strfreev (sound_names); return TRUE; } static void setting_set_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { int setting; char *filename; SoundType type; gtk_tree_model_get (model, iter, SETTING_COL, &setting, FILENAME_COL, &filename, TYPE_COL, &type, -1); if (setting == SOUND_UNSET) { g_object_set (cell, "visible", FALSE, NULL); g_free (filename); return; } if (setting == SOUND_OFF) { g_object_set (cell, "text", _("Disabled"), NULL); } else if (setting == SOUND_BUILTIN) { g_object_set (cell, "text", _("Default"), NULL); } else if (setting == SOUND_CUSTOM || setting == SOUND_CUSTOM_OLD) { char *display; display = g_filename_display_basename (filename); g_object_set (cell, "text", display, NULL); g_free (display); } g_free (filename); } typedef GtkCellRendererPixbuf ActivatableCellRendererPixbuf; typedef GtkCellRendererPixbufClass ActivatableCellRendererPixbufClass; GType activatable_cell_renderer_pixbuf_get_type (void); #define ACTIVATABLE_TYPE_CELL_RENDERER_PIXBUF (activatable_cell_renderer_pixbuf_get_type ()) G_DEFINE_TYPE (ActivatableCellRendererPixbuf, activatable_cell_renderer_pixbuf, GTK_TYPE_CELL_RENDERER_PIXBUF); static gboolean activatable_cell_renderer_pixbuf_activate (GtkCellRenderer *cell, GdkEvent *event, GtkWidget *widget, const gchar *path_string, GdkRectangle *background_area, GdkRectangle *cell_area, GtkCellRendererState flags) { GtkTreePath *path; gboolean res; g_debug ("Activating pixbuf"); path = gtk_tree_path_new_from_string (path_string); res = play_sound_at_path (widget, path); gtk_tree_path_free (path); return res; } static void activatable_cell_renderer_pixbuf_init (ActivatableCellRendererPixbuf *cell) { } static void activatable_cell_renderer_pixbuf_class_init (ActivatableCellRendererPixbufClass *class) { GtkCellRendererClass *cell_class; cell_class = GTK_CELL_RENDERER_CLASS (class); cell_class->activate = activatable_cell_renderer_pixbuf_activate; } static void setup_theme_custom_selector (GvcSoundThemeEditor *editor, gboolean have_xkb ) { GtkTreeStore *store; GtkTreeModel *custom_model; GtkTreeViewColumn *column; GtkCellRenderer *renderer; GtkTreeIter iter; GtkTreeIter parent; CategoryType type; guint i; /* Set up the model for the custom view */ store = gtk_tree_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRV); /* The first column with the categories/sound names */ renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes ("Display", renderer, "text", DISPLAY_COL, "sensitive", SENSITIVE_COL, "ellipsize", PANGO_ELLIPSIZE_START, "ellipsize-set", TRUE, NULL); g_object_set (G_OBJECT (column), "expand", TRUE, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (editor->priv->treeview), column); /* The 2nd column with the sound settings */ renderer = gtk_cell_renderer_combo_new (); g_signal_connect (renderer, "edited", G_CALLBACK (on_setting_column_edited), editor); g_signal_connect (renderer, "editing-started", G_CALLBACK (on_combobox_editing_started), editor); custom_model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT)); fill_custom_model (GTK_LIST_STORE (custom_model), NULL); g_object_set (renderer, "model", custom_model, "has-entry", FALSE, "editable", TRUE, "text-column", 0, NULL); column = gtk_tree_view_column_new_with_attributes ("Setting", renderer, "editable", SENSITIVE_COL, "sensitive", SENSITIVE_COL, "visible", TRUE, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (editor->priv->treeview), column); gtk_tree_view_column_set_cell_data_func (column, renderer, setting_set_func, NULL, NULL); /* The 3rd column with the preview pixbuf */ renderer = g_object_new (ACTIVATABLE_TYPE_CELL_RENDERER_PIXBUF, NULL); g_object_set (renderer, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, "icon-name", "media-playback-start", "stock-size", GTK_ICON_SIZE_MENU, NULL); column = gtk_tree_view_column_new_with_attributes ("Preview", renderer, "visible", HAS_PREVIEW_COL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (editor->priv->treeview), column); g_object_set_data (G_OBJECT (editor->priv->treeview), "preview-column", column); gtk_tree_view_set_model (GTK_TREE_VIEW (editor->priv->treeview), GTK_TREE_MODEL (store)); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (editor->priv->treeview), FALSE); /* Fill in the model */ type = CATEGORY_INVALID; for (i = 0; ; i++) { GtkTreeIter *_parent; if (sounds[i].category == -1) { break; } /* Is it a new type of sound? */ if (sounds[i].category == type && type != CATEGORY_INVALID && type != CATEGORY_BELL) { _parent = &parent; } else { _parent = NULL; } if (sounds[i].type != -1) { gtk_tree_store_insert_with_values (store, &iter, _parent, G_MAXINT, DISPLAY_COL, g_dpgettext2 (NULL, "Sound event", sounds[i].display_name), SETTING_COL, SOUND_BUILTIN, TYPE_COL, sounds[i].type, SOUND_NAMES_COL, sounds[i].names, HAS_PREVIEW_COL, TRUE, SENSITIVE_COL, TRUE, -1); } else { /* Category */ gtk_tree_store_insert_with_values (store, &iter, _parent, G_MAXINT, DISPLAY_COL, g_dpgettext2 (NULL, "Sound event", sounds[i].display_name), SETTING_COL, SOUND_UNSET, TYPE_COL, sounds[i].type, SENSITIVE_COL, TRUE, HAS_PREVIEW_COL, FALSE, -1); } /* If we didn't set a parent already, set one in case we need it later */ if (_parent == NULL) { parent = iter; } type = sounds[i].category; } gtk_tree_view_expand_all (GTK_TREE_VIEW (editor->priv->treeview)); } static GObject * gvc_sound_theme_editor_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params) { GObject *object; GvcSoundThemeEditor *self; object = G_OBJECT_CLASS (gvc_sound_theme_editor_parent_class)->constructor (type, n_construct_properties, construct_params); self = GVC_SOUND_THEME_EDITOR (object); setup_theme_selector (self); setup_theme_custom_selector (self, TRUE); update_theme (self); return object; } static void gvc_sound_theme_editor_class_init (GvcSoundThemeEditorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructor = gvc_sound_theme_editor_constructor; object_class->finalize = gvc_sound_theme_editor_finalize; g_type_class_add_private (klass, sizeof (GvcSoundThemeEditorPrivate)); } static void on_click_feedback_toggled (GtkToggleButton *button, GvcSoundThemeEditor *editor) { gboolean enabled; enabled = gtk_toggle_button_get_active (button); g_settings_set_boolean (editor->priv->sound_settings, INPUT_SOUNDS_KEY, enabled); } static void on_key_changed (GSettings *settings, gchar *key, GvcSoundThemeEditor *editor) { if (strcmp (key, EVENT_SOUNDS_KEY) == 0) { update_theme (editor); } else if (strcmp (key, SOUND_THEME_KEY) == 0) { update_theme (editor); } else if (strcmp (key, INPUT_SOUNDS_KEY) == 0) { update_theme (editor); } else if (strcmp (key, AUDIO_BELL_KEY) == 0) { update_theme (editor); } } static void on_treeview_row_activated (GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, GvcSoundThemeEditor *editor) { g_debug ("row activated"); play_sound_at_path (GTK_WIDGET (treeview), path); } 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); } static void setup_list_size_constraint (GtkWidget *widget, GtkWidget *to_size) { g_signal_connect (widget, "size-request", G_CALLBACK (constrain_list_size), to_size); } static void gvc_sound_theme_editor_init (GvcSoundThemeEditor *editor) { GtkWidget *box; GtkWidget *label; GtkWidget *scrolled_window; editor->priv = GVC_SOUND_THEME_EDITOR_GET_PRIVATE (editor); editor->priv->theme_box = gtk_hbox_new (FALSE, 6); gtk_box_pack_start (GTK_BOX (editor), editor->priv->theme_box, FALSE, FALSE, 0); label = gtk_label_new (_("Sound Theme:")); gtk_box_pack_start (GTK_BOX (editor->priv->theme_box), label, FALSE, FALSE, 6); editor->priv->combo_box = gtk_combo_box_new (); gtk_box_pack_start (GTK_BOX (editor->priv->theme_box), editor->priv->combo_box, FALSE, FALSE, 0); editor->priv->sound_settings = g_settings_new (KEY_SOUNDS_SCHEMA); editor->priv->marco_settings = g_settings_new (KEY_MARCO_SCHEMA); editor->priv->selection_box = box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (editor), box, TRUE, TRUE, 0); editor->priv->treeview = gtk_tree_view_new (); g_signal_connect (editor->priv->treeview, "row-activated", G_CALLBACK (on_treeview_row_activated), editor); scrolled_window = gtk_scrolled_window_new (NULL, NULL); setup_list_size_constraint (scrolled_window, editor->priv->treeview); 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), editor->priv->treeview); gtk_container_add (GTK_CONTAINER (box), scrolled_window); editor->priv->click_feedback_button = gtk_check_button_new_with_mnemonic (_("Enable window and button sounds")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (editor->priv->click_feedback_button), g_settings_get_boolean (editor->priv->sound_settings, INPUT_SOUNDS_KEY)); gtk_box_pack_start (GTK_BOX (box), editor->priv->click_feedback_button, FALSE, FALSE, 0); g_signal_connect (editor->priv->click_feedback_button, "toggled", G_CALLBACK (on_click_feedback_toggled), editor); g_signal_connect (editor->priv->sound_settings, "changed", G_CALLBACK (on_key_changed), editor); g_signal_connect (editor->priv->marco_settings, "changed::" AUDIO_BELL_KEY, G_CALLBACK (on_key_changed), editor); /* FIXME: should accept drag and drop themes. should also add an "Add Theme..." item to the theme combobox */ } static void gvc_sound_theme_editor_finalize (GObject *object) { GvcSoundThemeEditor *sound_theme_editor; g_return_if_fail (object != NULL); g_return_if_fail (GVC_IS_SOUND_THEME_EDITOR (object)); sound_theme_editor = GVC_SOUND_THEME_EDITOR (object); if (sound_theme_editor->priv != NULL) { g_object_unref (sound_theme_editor->priv->sound_settings); sound_theme_editor->priv->sound_settings = NULL; g_object_unref (sound_theme_editor->priv->marco_settings); sound_theme_editor->priv->marco_settings = NULL; } G_OBJECT_CLASS (gvc_sound_theme_editor_parent_class)->finalize (object); } GtkWidget * gvc_sound_theme_editor_new (void) { GObject *editor; editor = g_object_new (GVC_TYPE_SOUND_THEME_EDITOR, "spacing", 6, NULL); return GTK_WIDGET (editor); }