You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1144 lines
40 KiB

  1. /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
  2. *
  3. * Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
  4. * Copyright (C) 2008 William Jon McCann
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
  19. *
  20. */
  21. #include "config.h"
  22. #include <stdlib.h>
  23. #include <stdio.h>
  24. #include <unistd.h>
  25. #include <utime.h>
  26. #include <errno.h>
  27. #include <glib.h>
  28. #include <glib/gi18n.h>
  29. #include <glib-object.h>
  30. #include <gio/gio.h>
  31. #include <gtk/gtk.h>
  32. #include <canberra-gtk.h>
  33. #include <libxml/tree.h>
  34. #include "gvc-sound-theme-chooser.h"
  35. #include "sound-theme-file-utils.h"
  36. #define GVC_SOUND_THEME_CHOOSER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_SOUND_THEME_CHOOSER, GvcSoundThemeChooserPrivate))
  37. struct GvcSoundThemeChooserPrivate
  38. {
  39. GtkWidget *combo_box;
  40. GtkWidget *treeview;
  41. GtkWidget *theme_box;
  42. GtkWidget *selection_box;
  43. GtkWidget *click_feedback_button;
  44. GSettings *sound_settings;
  45. };
  46. static void gvc_sound_theme_chooser_class_init (GvcSoundThemeChooserClass *klass);
  47. static void gvc_sound_theme_chooser_init (GvcSoundThemeChooser *sound_theme_chooser);
  48. static void gvc_sound_theme_chooser_dispose (GObject *object);
  49. G_DEFINE_TYPE (GvcSoundThemeChooser, gvc_sound_theme_chooser, GTK_TYPE_BOX)
  50. #define KEY_SOUNDS_SCHEMA "org.mate.sound"
  51. #define EVENT_SOUNDS_KEY "event-sounds"
  52. #define INPUT_SOUNDS_KEY "input-feedback-sounds"
  53. #define SOUND_THEME_KEY "theme-name"
  54. #define DEFAULT_ALERT_ID "__default"
  55. #define CUSTOM_THEME_NAME "__custom"
  56. #define NO_SOUNDS_THEME_NAME "__no_sounds"
  57. enum {
  58. THEME_DISPLAY_COL,
  59. THEME_IDENTIFIER_COL,
  60. THEME_PARENT_ID_COL,
  61. THEME_NUM_COLS
  62. };
  63. enum {
  64. ALERT_DISPLAY_COL,
  65. ALERT_IDENTIFIER_COL,
  66. ALERT_SOUND_TYPE_COL,
  67. ALERT_ACTIVE_COL,
  68. ALERT_NUM_COLS
  69. };
  70. enum {
  71. SOUND_TYPE_UNSET,
  72. SOUND_TYPE_OFF,
  73. SOUND_TYPE_DEFAULT_FROM_THEME,
  74. SOUND_TYPE_BUILTIN,
  75. SOUND_TYPE_CUSTOM
  76. };
  77. static void
  78. on_combobox_changed (GtkComboBox *widget,
  79. GvcSoundThemeChooser *chooser)
  80. {
  81. GtkTreeIter iter;
  82. GtkTreeModel *model;
  83. char *theme_name;
  84. if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter) == FALSE) {
  85. return;
  86. }
  87. model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
  88. gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &theme_name, -1);
  89. g_assert (theme_name != NULL);
  90. /* It is necessary to update the theme name before any other setting as
  91. * the "changed" notification will reload the contents of the widget */
  92. g_settings_set_string (chooser->priv->sound_settings, SOUND_THEME_KEY, theme_name);
  93. /* special case for no sounds */
  94. if (strcmp (theme_name, NO_SOUNDS_THEME_NAME) == 0) {
  95. g_settings_set_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY, FALSE);
  96. return;
  97. } else {
  98. g_settings_set_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY, TRUE);
  99. }
  100. g_free (theme_name);
  101. /* FIXME: reset alert model */
  102. }
  103. static char *
  104. load_index_theme_name (const char *index,
  105. char **parent)
  106. {
  107. GKeyFile *file;
  108. char *indexname = NULL;
  109. gboolean hidden;
  110. file = g_key_file_new ();
  111. if (g_key_file_load_from_file (file, index, G_KEY_FILE_KEEP_TRANSLATIONS, NULL) == FALSE) {
  112. g_key_file_free (file);
  113. return NULL;
  114. }
  115. /* Don't add hidden themes to the list */
  116. hidden = g_key_file_get_boolean (file, "Sound Theme", "Hidden", NULL);
  117. if (!hidden) {
  118. indexname = g_key_file_get_locale_string (file,
  119. "Sound Theme",
  120. "Name",
  121. NULL,
  122. NULL);
  123. /* Save the parent theme, if there's one */
  124. if (parent != NULL) {
  125. *parent = g_key_file_get_string (file,
  126. "Sound Theme",
  127. "Inherits",
  128. NULL);
  129. }
  130. }
  131. g_key_file_free (file);
  132. return indexname;
  133. }
  134. static void
  135. sound_theme_in_dir (GHashTable *hash,
  136. const char *dir)
  137. {
  138. GDir *d;
  139. const char *name;
  140. d = g_dir_open (dir, 0, NULL);
  141. if (d == NULL) {
  142. return;
  143. }
  144. while ((name = g_dir_read_name (d)) != NULL) {
  145. char *dirname, *index, *indexname;
  146. /* Look for directories */
  147. dirname = g_build_filename (dir, name, NULL);
  148. if (g_file_test (dirname, G_FILE_TEST_IS_DIR) == FALSE) {
  149. g_free (dirname);
  150. continue;
  151. }
  152. /* Look for index files */
  153. index = g_build_filename (dirname, "index.theme", NULL);
  154. g_free (dirname);
  155. /* Check the name of the theme in the index.theme file */
  156. indexname = load_index_theme_name (index, NULL);
  157. g_free (index);
  158. if (indexname == NULL) {
  159. continue;
  160. }
  161. g_hash_table_insert (hash, g_strdup (name), indexname);
  162. }
  163. g_dir_close (d);
  164. }
  165. static void
  166. add_theme_to_store (const char *key,
  167. const char *value,
  168. GtkListStore *store)
  169. {
  170. char *parent;
  171. parent = NULL;
  172. /* Get the parent, if we're checking the custom theme */
  173. if (strcmp (key, CUSTOM_THEME_NAME) == 0) {
  174. char *name, *path;
  175. path = custom_theme_dir_path ("index.theme");
  176. name = load_index_theme_name (path, &parent);
  177. g_free (name);
  178. g_free (path);
  179. }
  180. gtk_list_store_insert_with_values (store, NULL, G_MAXINT,
  181. THEME_DISPLAY_COL, value,
  182. THEME_IDENTIFIER_COL, key,
  183. THEME_PARENT_ID_COL, parent,
  184. -1);
  185. g_free (parent);
  186. }
  187. static void
  188. set_combox_for_theme_name (GvcSoundThemeChooser *chooser,
  189. const char *name)
  190. {
  191. GtkTreeIter iter;
  192. GtkTreeModel *model;
  193. gboolean found;
  194. /* If the name is empty, use "freedesktop" */
  195. if (name == NULL || *name == '\0') {
  196. name = "freedesktop";
  197. }
  198. model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
  199. if (gtk_tree_model_get_iter_first (model, &iter) == FALSE) {
  200. return;
  201. }
  202. do {
  203. char *value;
  204. gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &value, -1);
  205. found = (value != NULL && strcmp (value, name) == 0);
  206. g_free (value);
  207. } while (!found && gtk_tree_model_iter_next (model, &iter));
  208. /* When we can't find the theme we need to set, try to set the default
  209. * one "freedesktop" */
  210. if (found) {
  211. gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter);
  212. } else if (strcmp (name, "freedesktop") != 0) {
  213. g_debug ("not found, falling back to fdo");
  214. set_combox_for_theme_name (chooser, "freedesktop");
  215. }
  216. }
  217. static void
  218. set_input_feedback_enabled (GvcSoundThemeChooser *chooser,
  219. gboolean enabled)
  220. {
  221. gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->priv->click_feedback_button),
  222. enabled);
  223. }
  224. static void
  225. setup_theme_selector (GvcSoundThemeChooser *chooser)
  226. {
  227. GHashTable *hash;
  228. GtkListStore *store;
  229. GtkCellRenderer *renderer;
  230. const char * const *data_dirs;
  231. const char *data_dir;
  232. char *dir;
  233. guint i;
  234. /* Add the theme names and their display name to a hash table,
  235. * makes it easy to avoid duplicate themes */
  236. hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
  237. data_dirs = g_get_system_data_dirs ();
  238. for (i = 0; data_dirs[i] != NULL; i++) {
  239. dir = g_build_filename (data_dirs[i], "sounds", NULL);
  240. sound_theme_in_dir (hash, dir);
  241. g_free (dir);
  242. }
  243. data_dir = g_get_user_data_dir ();
  244. dir = g_build_filename (data_dir, "sounds", NULL);
  245. sound_theme_in_dir (hash, dir);
  246. g_free (dir);
  247. /* If there isn't at least one theme, make everything
  248. * insensitive, LAME! */
  249. if (g_hash_table_size (hash) == 0) {
  250. gtk_widget_set_sensitive (GTK_WIDGET (chooser), FALSE);
  251. g_warning ("Bad setup, install the freedesktop sound theme");
  252. g_hash_table_destroy (hash);
  253. return;
  254. }
  255. /* Setup the tree model, 3 columns:
  256. * - internal theme name/directory
  257. * - display theme name
  258. * - the internal id for the parent theme, used for the custom theme */
  259. store = gtk_list_store_new (THEME_NUM_COLS,
  260. G_TYPE_STRING,
  261. G_TYPE_STRING,
  262. G_TYPE_STRING);
  263. /* Add the themes to a combobox */
  264. gtk_list_store_insert_with_values (store,
  265. NULL,
  266. G_MAXINT,
  267. THEME_DISPLAY_COL, _("No sounds"),
  268. THEME_IDENTIFIER_COL, "__no_sounds",
  269. THEME_PARENT_ID_COL, NULL,
  270. -1);
  271. g_hash_table_foreach (hash, (GHFunc) add_theme_to_store, store);
  272. g_hash_table_destroy (hash);
  273. /* Set the display */
  274. gtk_combo_box_set_model (GTK_COMBO_BOX (chooser->priv->combo_box),
  275. GTK_TREE_MODEL (store));
  276. renderer = gtk_cell_renderer_text_new ();
  277. gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->priv->combo_box),
  278. renderer,
  279. TRUE);
  280. gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser->priv->combo_box),
  281. renderer,
  282. "text", THEME_DISPLAY_COL,
  283. NULL);
  284. g_signal_connect (G_OBJECT (chooser->priv->combo_box),
  285. "changed",
  286. G_CALLBACK (on_combobox_changed),
  287. chooser);
  288. }
  289. #define GVC_SOUND_SOUND (xmlChar *) "sound"
  290. #define GVC_SOUND_NAME (xmlChar *) "name"
  291. #define GVC_SOUND_FILENAME (xmlChar *) "filename"
  292. /* Adapted from yelp-toc-pager.c */
  293. static xmlChar *
  294. xml_get_and_trim_names (xmlNodePtr node)
  295. {
  296. xmlNodePtr cur;
  297. xmlChar *keep_lang = NULL;
  298. xmlChar *value;
  299. int j, keep_pri = INT_MAX;
  300. const gchar * const * langs = g_get_language_names ();
  301. value = NULL;
  302. for (cur = node->children; cur; cur = cur->next) {
  303. if (! xmlStrcmp (cur->name, GVC_SOUND_NAME)) {
  304. xmlChar *cur_lang = NULL;
  305. int cur_pri = INT_MAX;
  306. cur_lang = xmlNodeGetLang (cur);
  307. if (cur_lang) {
  308. for (j = 0; langs[j]; j++) {
  309. if (g_str_equal (cur_lang, langs[j])) {
  310. cur_pri = j;
  311. break;
  312. }
  313. }
  314. } else {
  315. cur_pri = INT_MAX - 1;
  316. }
  317. if (cur_pri <= keep_pri) {
  318. if (keep_lang)
  319. xmlFree (keep_lang);
  320. if (value)
  321. xmlFree (value);
  322. value = xmlNodeGetContent (cur);
  323. keep_lang = cur_lang;
  324. keep_pri = cur_pri;
  325. } else {
  326. if (cur_lang)
  327. xmlFree (cur_lang);
  328. }
  329. }
  330. }
  331. /* Delete all GVC_SOUND_NAME nodes */
  332. cur = node->children;
  333. while (cur) {
  334. xmlNodePtr this = cur;
  335. cur = cur->next;
  336. if (! xmlStrcmp (this->name, GVC_SOUND_NAME)) {
  337. xmlUnlinkNode (this);
  338. xmlFreeNode (this);
  339. }
  340. }
  341. return value;
  342. }
  343. static void
  344. populate_model_from_node (GvcSoundThemeChooser *chooser,
  345. GtkTreeModel *model,
  346. xmlNodePtr node)
  347. {
  348. xmlNodePtr child;
  349. xmlChar *filename;
  350. xmlChar *name;
  351. filename = NULL;
  352. name = xml_get_and_trim_names (node);
  353. for (child = node->children; child; child = child->next) {
  354. if (xmlNodeIsText (child)) {
  355. continue;
  356. }
  357. if (xmlStrcmp (child->name, GVC_SOUND_FILENAME) == 0) {
  358. filename = xmlNodeGetContent (child);
  359. } else if (xmlStrcmp (child->name, GVC_SOUND_NAME) == 0) {
  360. /* EH? should have been trimmed */
  361. }
  362. }
  363. if (filename != NULL && name != NULL) {
  364. gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
  365. NULL,
  366. G_MAXINT,
  367. ALERT_IDENTIFIER_COL, filename,
  368. ALERT_DISPLAY_COL, name,
  369. ALERT_SOUND_TYPE_COL, _("Built-in"),
  370. ALERT_ACTIVE_COL, FALSE,
  371. -1);
  372. }
  373. xmlFree (filename);
  374. xmlFree (name);
  375. }
  376. static void
  377. populate_model_from_file (GvcSoundThemeChooser *chooser,
  378. GtkTreeModel *model,
  379. const char *filename)
  380. {
  381. xmlDocPtr doc;
  382. xmlNodePtr root;
  383. xmlNodePtr child;
  384. gboolean exists;
  385. exists = g_file_test (filename, G_FILE_TEST_EXISTS);
  386. if (! exists) {
  387. return;
  388. }
  389. doc = xmlParseFile (filename);
  390. if (doc == NULL) {
  391. return;
  392. }
  393. root = xmlDocGetRootElement (doc);
  394. for (child = root->children; child; child = child->next) {
  395. if (xmlNodeIsText (child)) {
  396. continue;
  397. }
  398. if (xmlStrcmp (child->name, GVC_SOUND_SOUND) != 0) {
  399. continue;
  400. }
  401. populate_model_from_node (chooser, model, child);
  402. }
  403. xmlFreeDoc (doc);
  404. }
  405. static void
  406. populate_model_from_dir (GvcSoundThemeChooser *chooser,
  407. GtkTreeModel *model,
  408. const char *dirname)
  409. {
  410. GDir *d;
  411. const char *name;
  412. d = g_dir_open (dirname, 0, NULL);
  413. if (d == NULL) {
  414. return;
  415. }
  416. while ((name = g_dir_read_name (d)) != NULL) {
  417. char *path;
  418. if (! g_str_has_suffix (name, ".xml")) {
  419. continue;
  420. }
  421. path = g_build_filename (dirname, name, NULL);
  422. populate_model_from_file (chooser, model, path);
  423. g_free (path);
  424. }
  425. g_dir_close (d);
  426. }
  427. static gboolean
  428. save_alert_sounds (GvcSoundThemeChooser *chooser,
  429. const char *id)
  430. {
  431. const char *sounds[3] = { "bell-terminal", "bell-window-system", NULL };
  432. char *path;
  433. if (strcmp (id, DEFAULT_ALERT_ID) == 0) {
  434. delete_old_files (sounds);
  435. delete_disabled_files (sounds);
  436. } else {
  437. delete_old_files (sounds);
  438. delete_disabled_files (sounds);
  439. add_custom_file (sounds, id);
  440. }
  441. /* And poke the directory so the theme gets updated */
  442. path = custom_theme_dir_path (NULL);
  443. if (utime (path, NULL) != 0) {
  444. g_warning ("Failed to update mtime for directory '%s': %s",
  445. path, g_strerror (errno));
  446. }
  447. g_free (path);
  448. return FALSE;
  449. }
  450. static void
  451. update_alert_model (GvcSoundThemeChooser *chooser,
  452. const char *id)
  453. {
  454. GtkTreeModel *model;
  455. GtkTreeIter iter;
  456. model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
  457. gtk_tree_model_get_iter_first (model, &iter);
  458. do {
  459. gboolean toggled;
  460. char *this_id;
  461. gtk_tree_model_get (model, &iter,
  462. ALERT_IDENTIFIER_COL, &this_id,
  463. -1);
  464. if (strcmp (this_id, id) == 0) {
  465. toggled = TRUE;
  466. } else {
  467. toggled = FALSE;
  468. }
  469. g_free (this_id);
  470. gtk_list_store_set (GTK_LIST_STORE (model),
  471. &iter,
  472. ALERT_ACTIVE_COL, toggled,
  473. -1);
  474. } while (gtk_tree_model_iter_next (model, &iter));
  475. }
  476. static void
  477. update_alert (GvcSoundThemeChooser *chooser,
  478. const char *alert_id)
  479. {
  480. GtkTreeModel *theme_model;
  481. GtkTreeIter iter;
  482. char *theme;
  483. char *parent;
  484. gboolean is_custom;
  485. gboolean is_default;
  486. gboolean add_custom;
  487. gboolean remove_custom;
  488. theme_model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
  489. /* Get the current theme's name, and set the parent */
  490. if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter) == FALSE) {
  491. return;
  492. }
  493. gtk_tree_model_get (theme_model, &iter,
  494. THEME_IDENTIFIER_COL, &theme,
  495. THEME_IDENTIFIER_COL, &parent,
  496. -1);
  497. is_custom = strcmp (theme, CUSTOM_THEME_NAME) == 0;
  498. is_default = strcmp (alert_id, DEFAULT_ALERT_ID) == 0;
  499. /* So a few possibilities:
  500. * 1. Named theme, default alert selected: noop
  501. * 2. Named theme, alternate alert selected: create new custom with sound
  502. * 3. Custom theme, default alert selected: remove sound and possibly custom
  503. * 4. Custom theme, alternate alert selected: update custom sound
  504. */
  505. add_custom = FALSE;
  506. remove_custom = FALSE;
  507. if (! is_custom && is_default) {
  508. /* remove custom just in case */
  509. remove_custom = TRUE;
  510. } else if (! is_custom && ! is_default) {
  511. create_custom_theme (parent);
  512. save_alert_sounds (chooser, alert_id);
  513. add_custom = TRUE;
  514. } else if (is_custom && is_default) {
  515. save_alert_sounds (chooser, alert_id);
  516. /* after removing files check if it is empty */
  517. if (custom_theme_dir_is_empty ()) {
  518. remove_custom = TRUE;
  519. }
  520. } else if (is_custom && ! is_default) {
  521. save_alert_sounds (chooser, alert_id);
  522. }
  523. if (add_custom) {
  524. gtk_list_store_insert_with_values (GTK_LIST_STORE (theme_model),
  525. NULL,
  526. G_MAXINT,
  527. THEME_DISPLAY_COL, _("Custom"),
  528. THEME_IDENTIFIER_COL, CUSTOM_THEME_NAME,
  529. THEME_PARENT_ID_COL, theme,
  530. -1);
  531. set_combox_for_theme_name (chooser, CUSTOM_THEME_NAME);
  532. } else if (remove_custom) {
  533. gtk_tree_model_get_iter_first (theme_model, &iter);
  534. do {
  535. char *this_parent;
  536. gtk_tree_model_get (theme_model, &iter,
  537. THEME_PARENT_ID_COL, &this_parent,
  538. -1);
  539. if (this_parent != NULL && strcmp (this_parent, CUSTOM_THEME_NAME) != 0) {
  540. g_free (this_parent);
  541. gtk_list_store_remove (GTK_LIST_STORE (theme_model), &iter);
  542. break;
  543. }
  544. g_free (this_parent);
  545. } while (gtk_tree_model_iter_next (theme_model, &iter));
  546. delete_custom_theme_dir ();
  547. set_combox_for_theme_name (chooser, parent);
  548. }
  549. update_alert_model (chooser, alert_id);
  550. g_free (theme);
  551. g_free (parent);
  552. }
  553. static void
  554. on_alert_toggled (GtkCellRendererToggle *renderer,
  555. char *path_str,
  556. GvcSoundThemeChooser *chooser)
  557. {
  558. GtkTreeModel *model;
  559. GtkTreeIter iter;
  560. GtkTreePath *path;
  561. gboolean toggled;
  562. char *id;
  563. model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
  564. path = gtk_tree_path_new_from_string (path_str);
  565. gtk_tree_model_get_iter (model, &iter, path);
  566. gtk_tree_path_free (path);
  567. id = NULL;
  568. gtk_tree_model_get (model, &iter,
  569. ALERT_IDENTIFIER_COL, &id,
  570. ALERT_ACTIVE_COL, &toggled,
  571. -1);
  572. toggled ^= 1;
  573. if (toggled) {
  574. update_alert (chooser, id);
  575. }
  576. g_free (id);
  577. }
  578. static void
  579. play_preview_for_path (GvcSoundThemeChooser *chooser, GtkTreePath *path)
  580. {
  581. GtkTreeModel *model;
  582. GtkTreeIter iter;
  583. GtkTreeIter theme_iter;
  584. gchar *id = NULL;
  585. gchar *parent_theme = NULL;
  586. model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
  587. if (gtk_tree_model_get_iter (model, &iter, path) == FALSE)
  588. return;
  589. gtk_tree_model_get (model, &iter,
  590. ALERT_IDENTIFIER_COL, &id,
  591. -1);
  592. if (id == NULL)
  593. return;
  594. if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &theme_iter)) {
  595. GtkTreeModel *theme_model;
  596. gchar *theme_id = NULL;
  597. gchar *parent_id = NULL;
  598. theme_model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box));
  599. gtk_tree_model_get (theme_model, &theme_iter,
  600. THEME_IDENTIFIER_COL, &theme_id,
  601. THEME_PARENT_ID_COL, &parent_id, -1);
  602. if (theme_id && strcmp (theme_id, CUSTOM_THEME_NAME) == 0)
  603. parent_theme = g_strdup (parent_id);
  604. g_free (theme_id);
  605. g_free (parent_id);
  606. }
  607. /* special case: for the default item on custom themes
  608. * play the alert for the parent theme */
  609. if (strcmp (id, DEFAULT_ALERT_ID) == 0) {
  610. if (parent_theme != NULL) {
  611. ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
  612. CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
  613. CA_PROP_EVENT_ID, "bell-window-system",
  614. CA_PROP_CANBERRA_XDG_THEME_NAME, parent_theme,
  615. CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
  616. CA_PROP_CANBERRA_CACHE_CONTROL, "never",
  617. CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
  618. #ifdef CA_PROP_CANBERRA_ENABLE
  619. CA_PROP_CANBERRA_ENABLE, "1",
  620. #endif
  621. NULL);
  622. } else {
  623. ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
  624. CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
  625. CA_PROP_EVENT_ID, "bell-window-system",
  626. CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
  627. CA_PROP_CANBERRA_CACHE_CONTROL, "never",
  628. CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
  629. #ifdef CA_PROP_CANBERRA_ENABLE
  630. CA_PROP_CANBERRA_ENABLE, "1",
  631. #endif
  632. NULL);
  633. }
  634. } else {
  635. ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0,
  636. CA_PROP_APPLICATION_NAME, _("Sound Preferences"),
  637. CA_PROP_MEDIA_FILENAME, id,
  638. CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"),
  639. CA_PROP_CANBERRA_CACHE_CONTROL, "never",
  640. CA_PROP_APPLICATION_ID, "org.mate.VolumeControl",
  641. #ifdef CA_PROP_CANBERRA_ENABLE
  642. CA_PROP_CANBERRA_ENABLE, "1",
  643. #endif
  644. NULL);
  645. }
  646. g_free (parent_theme);
  647. g_free (id);
  648. }
  649. static void
  650. on_treeview_row_activated (GtkTreeView *treeview,
  651. GtkTreePath *path,
  652. GtkTreeViewColumn *column,
  653. GvcSoundThemeChooser *chooser)
  654. {
  655. play_preview_for_path (chooser, path);
  656. }
  657. static void
  658. on_treeview_selection_changed (GtkTreeSelection *selection,
  659. GvcSoundThemeChooser *chooser)
  660. {
  661. GList *paths;
  662. GtkTreeModel *model;
  663. GtkTreePath *path;
  664. if (chooser->priv->treeview == NULL)
  665. return;
  666. model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview));
  667. paths = gtk_tree_selection_get_selected_rows (selection, &model);
  668. if (paths == NULL)
  669. return;
  670. path = paths->data;
  671. play_preview_for_path (chooser, path);
  672. g_list_foreach (paths, (GFunc)gtk_tree_path_free, NULL);
  673. g_list_free (paths);
  674. }
  675. static GtkWidget *
  676. create_alert_treeview (GvcSoundThemeChooser *chooser)
  677. {
  678. GtkListStore *store;
  679. GtkWidget *treeview;
  680. GtkCellRenderer *renderer;
  681. GtkTreeViewColumn *column;
  682. GtkTreeSelection *selection;
  683. treeview = gtk_tree_view_new ();
  684. gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
  685. g_signal_connect (G_OBJECT (treeview),
  686. "row-activated",
  687. G_CALLBACK (on_treeview_row_activated),
  688. chooser);
  689. selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
  690. gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
  691. g_signal_connect (G_OBJECT (selection),
  692. "changed",
  693. G_CALLBACK (on_treeview_selection_changed),
  694. chooser);
  695. /* Setup the tree model, 3 columns:
  696. * - display name
  697. * - sound id
  698. * - sound type
  699. */
  700. store = gtk_list_store_new (ALERT_NUM_COLS,
  701. G_TYPE_STRING,
  702. G_TYPE_STRING,
  703. G_TYPE_STRING,
  704. G_TYPE_BOOLEAN);
  705. gtk_list_store_insert_with_values (store,
  706. NULL,
  707. G_MAXINT,
  708. ALERT_IDENTIFIER_COL, DEFAULT_ALERT_ID,
  709. ALERT_DISPLAY_COL, _("Default"),
  710. ALERT_SOUND_TYPE_COL, _("From theme"),
  711. ALERT_ACTIVE_COL, TRUE,
  712. -1);
  713. populate_model_from_dir (chooser, GTK_TREE_MODEL (store), SOUND_SET_DIR);
  714. gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
  715. GTK_TREE_MODEL (store));
  716. renderer = gtk_cell_renderer_toggle_new ();
  717. gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (renderer), TRUE);
  718. column = gtk_tree_view_column_new_with_attributes (NULL,
  719. renderer,
  720. "active", ALERT_ACTIVE_COL,
  721. NULL);
  722. gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
  723. g_signal_connect (renderer,
  724. "toggled",
  725. G_CALLBACK (on_alert_toggled),
  726. chooser);
  727. renderer = gtk_cell_renderer_text_new ();
  728. column = gtk_tree_view_column_new_with_attributes (_("Name"),
  729. renderer,
  730. "text", ALERT_DISPLAY_COL,
  731. NULL);
  732. gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
  733. renderer = gtk_cell_renderer_text_new ();
  734. column = gtk_tree_view_column_new_with_attributes (_("Type"),
  735. renderer,
  736. "text", ALERT_SOUND_TYPE_COL,
  737. NULL);
  738. gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
  739. return treeview;
  740. }
  741. static int
  742. get_file_type (const char *sound_name,
  743. char **linked_name)
  744. {
  745. char *name, *filename;
  746. *linked_name = NULL;
  747. name = g_strdup_printf ("%s.disabled", sound_name);
  748. filename = custom_theme_dir_path (name);
  749. g_free (name);
  750. if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) != FALSE) {
  751. g_free (filename);
  752. return SOUND_TYPE_OFF;
  753. }
  754. g_free (filename);
  755. /* We only check for .ogg files because those are the
  756. * only ones we create */
  757. name = g_strdup_printf ("%s.ogg", sound_name);
  758. filename = custom_theme_dir_path (name);
  759. g_free (name);
  760. if (g_file_test (filename, G_FILE_TEST_IS_SYMLINK) != FALSE) {
  761. *linked_name = g_file_read_link (filename, NULL);
  762. g_free (filename);
  763. return SOUND_TYPE_CUSTOM;
  764. }
  765. g_free (filename);
  766. return SOUND_TYPE_BUILTIN;
  767. }
  768. static void
  769. update_alerts_from_theme_name (GvcSoundThemeChooser *chooser,
  770. const gchar *name)
  771. {
  772. if (strcmp (name, CUSTOM_THEME_NAME) != 0) {
  773. /* reset alert to default */
  774. update_alert (chooser, DEFAULT_ALERT_ID);
  775. } else {
  776. int sound_type;
  777. char *linkname;
  778. linkname = NULL;
  779. sound_type = get_file_type ("bell-terminal", &linkname);
  780. g_debug ("Found link: %s", linkname);
  781. if (sound_type == SOUND_TYPE_CUSTOM) {
  782. update_alert (chooser, linkname);
  783. }
  784. }
  785. }
  786. static void
  787. update_theme (GvcSoundThemeChooser *chooser)
  788. {
  789. char *theme_name;
  790. gboolean events_enabled;
  791. gboolean feedback_enabled;
  792. feedback_enabled = g_settings_get_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY);
  793. set_input_feedback_enabled (chooser, feedback_enabled);
  794. events_enabled = g_settings_get_boolean (chooser->priv->sound_settings, EVENT_SOUNDS_KEY);
  795. if (events_enabled) {
  796. theme_name = g_settings_get_string (chooser->priv->sound_settings, SOUND_THEME_KEY);
  797. } else {
  798. theme_name = g_strdup (NO_SOUNDS_THEME_NAME);
  799. }
  800. gtk_widget_set_sensitive (chooser->priv->selection_box, events_enabled);
  801. gtk_widget_set_sensitive (chooser->priv->click_feedback_button, events_enabled);
  802. set_combox_for_theme_name (chooser, theme_name);
  803. update_alerts_from_theme_name (chooser, theme_name);
  804. g_free (theme_name);
  805. }
  806. static void
  807. gvc_sound_theme_chooser_class_init (GvcSoundThemeChooserClass *klass)
  808. {
  809. GObjectClass *object_class = G_OBJECT_CLASS (klass);
  810. object_class->dispose = gvc_sound_theme_chooser_dispose;
  811. g_type_class_add_private (klass, sizeof (GvcSoundThemeChooserPrivate));
  812. }
  813. static void
  814. on_click_feedback_toggled (GtkToggleButton *button,
  815. GvcSoundThemeChooser *chooser)
  816. {
  817. gboolean enabled;
  818. enabled = gtk_toggle_button_get_active (button);
  819. g_settings_set_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY, enabled);
  820. }
  821. static void
  822. on_key_changed (GSettings *settings,
  823. gchar *key,
  824. GvcSoundThemeChooser *chooser)
  825. {
  826. if (!strcmp (key, EVENT_SOUNDS_KEY) ||
  827. !strcmp (key, SOUND_THEME_KEY) ||
  828. !strcmp (key, INPUT_SOUNDS_KEY))
  829. update_theme (chooser);
  830. }
  831. #if !GTK_CHECK_VERSION (3, 0, 0)
  832. static void
  833. constrain_list_size (GtkWidget *widget,
  834. GtkRequisition *requisition,
  835. GtkWidget *to_size)
  836. {
  837. GtkRequisition req;
  838. int max_height;
  839. /* Constrain height to be the tree height up to a max */
  840. max_height = (gdk_screen_get_height (gtk_widget_get_screen (widget))) / 4;
  841. gtk_widget_size_request (to_size, &req);
  842. requisition->height = MIN (req.height, max_height);
  843. }
  844. #endif
  845. static void
  846. setup_list_size_constraint (GtkWidget *widget,
  847. GtkWidget *to_size)
  848. {
  849. #if GTK_CHECK_VERSION (3, 0, 0)
  850. GtkRequisition req;
  851. int max_height;
  852. /* Constrain height to be the tree height up to a max */
  853. max_height = (gdk_screen_get_height (gtk_widget_get_screen (widget))) / 4;
  854. // XXX this doesn't work
  855. gtk_widget_get_preferred_size (to_size, NULL, &req);
  856. gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (widget),
  857. MIN (req.height, max_height));
  858. #else
  859. g_signal_connect (G_OBJECT (widget),
  860. "size-request",
  861. G_CALLBACK (constrain_list_size),
  862. to_size);
  863. #endif
  864. }
  865. static void
  866. gvc_sound_theme_chooser_init (GvcSoundThemeChooser *chooser)
  867. {
  868. GtkWidget *box;
  869. GtkWidget *label;
  870. GtkWidget *scrolled_window;
  871. GtkWidget *alignment;
  872. gchar *str;
  873. chooser->priv = GVC_SOUND_THEME_CHOOSER_GET_PRIVATE (chooser);
  874. #if GTK_CHECK_VERSION (3, 0, 0)
  875. chooser->priv->theme_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  876. #else
  877. chooser->priv->theme_box = gtk_hbox_new (FALSE, 0);
  878. #endif
  879. gtk_box_pack_start (GTK_BOX (chooser),
  880. chooser->priv->theme_box, FALSE, FALSE, 0);
  881. label = gtk_label_new_with_mnemonic (_("Sound _theme:"));
  882. gtk_box_pack_start (GTK_BOX (chooser->priv->theme_box), label, FALSE, FALSE, 0);
  883. chooser->priv->combo_box = gtk_combo_box_new ();
  884. gtk_box_pack_start (GTK_BOX (chooser->priv->theme_box), chooser->priv->combo_box, FALSE, FALSE, 6);
  885. gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->combo_box);
  886. chooser->priv->sound_settings = g_settings_new (KEY_SOUNDS_SCHEMA);
  887. g_signal_connect (G_OBJECT (chooser->priv->sound_settings),
  888. "changed",
  889. G_CALLBACK (on_key_changed),
  890. chooser);
  891. str = g_strdup_printf ("<b>%s</b>", _("C_hoose an alert sound:"));
  892. chooser->priv->selection_box = box = gtk_frame_new (str);
  893. g_free (str);
  894. label = gtk_frame_get_label_widget (GTK_FRAME (box));
  895. gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
  896. gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
  897. gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
  898. alignment = gtk_alignment_new (0, 0, 1, 1);
  899. gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
  900. gtk_container_add (GTK_CONTAINER (alignment), box);
  901. gtk_box_pack_start (GTK_BOX (chooser), alignment, TRUE, TRUE, 6);
  902. alignment = gtk_alignment_new (0, 0, 1, 1);
  903. gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
  904. gtk_container_add (GTK_CONTAINER (box), alignment);
  905. chooser->priv->treeview = create_alert_treeview (chooser);
  906. gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->treeview);
  907. scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  908. gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
  909. GTK_POLICY_NEVER,
  910. GTK_POLICY_AUTOMATIC);
  911. gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
  912. GTK_SHADOW_IN);
  913. gtk_container_add (GTK_CONTAINER (scrolled_window), chooser->priv->treeview);
  914. gtk_container_add (GTK_CONTAINER (alignment), scrolled_window);
  915. chooser->priv->click_feedback_button = gtk_check_button_new_with_mnemonic (_("Enable _window and button sounds"));
  916. gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->priv->click_feedback_button),
  917. g_settings_get_boolean (chooser->priv->sound_settings, INPUT_SOUNDS_KEY));
  918. gtk_box_pack_start (GTK_BOX (chooser),
  919. chooser->priv->click_feedback_button,
  920. FALSE, FALSE, 0);
  921. g_signal_connect (G_OBJECT (chooser->priv->click_feedback_button),
  922. "toggled",
  923. G_CALLBACK (on_click_feedback_toggled),
  924. chooser);
  925. setup_theme_selector (chooser);
  926. update_theme (chooser);
  927. setup_list_size_constraint (scrolled_window, chooser->priv->treeview);
  928. }
  929. static void
  930. gvc_sound_theme_chooser_dispose (GObject *object)
  931. {
  932. GvcSoundThemeChooser *chooser;
  933. chooser = GVC_SOUND_THEME_CHOOSER (object);
  934. g_clear_object (&chooser->priv->sound_settings);
  935. G_OBJECT_CLASS (gvc_sound_theme_chooser_parent_class)->dispose (object);
  936. }
  937. GtkWidget *
  938. gvc_sound_theme_chooser_new (void)
  939. {
  940. return g_object_new (GVC_TYPE_SOUND_THEME_CHOOSER,
  941. "spacing", 6,
  942. "orientation", GTK_ORIENTATION_VERTICAL,
  943. NULL);
  944. }