1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 #include "evolution-data-server-config.h"
20
21 #include <errno.h>
22 #include <string.h>
23 #include <libxml/parser.h>
24 #include <glib/gstdio.h>
25 #include <glib/gi18n-lib.h>
26 #include "e-data-server-util.h"
27 #include "e-categories.h"
28
29 #include "libedataserver-private.h"
30
31 #define d(x)
32
33 typedef struct {
34 gchar *display_name; /* localized category name */
35 gchar *clocale_name; /* only for default categories */
36 gchar *icon_file;
37 gboolean is_default;
38 gboolean is_searchable;
39 } CategoryInfo;
40
41 typedef struct {
42 const gchar *category;
43 const gchar *icon_file;
44 } DefaultCategory;
45
46 static DefaultCategory default_categories[] = {
47 { NC_("CategoryName", "Anniversary") },
48 { NC_("CategoryName", "Birthday"), "category_birthday_16.png" },
49 { NC_("CategoryName", "Business"), "category_business_16.png" },
50 { NC_("CategoryName", "Competition") },
51 { NC_("CategoryName", "Favorites"), "category_favorites_16.png" },
52 { NC_("CategoryName", "Gifts"), "category_gifts_16.png" },
53 { NC_("CategoryName", "Goals/Objectives"), "category_goals_16.png" },
54 { NC_("CategoryName", "Holiday"), "category_holiday_16.png" },
55 { NC_("CategoryName", "Holiday Cards"), "category_holiday-cards_16.png" },
56 /* important people (e.g. new business partners) */
57 { NC_("CategoryName", "Hot Contacts"), "category_hot-contacts_16.png" },
58 { NC_("CategoryName", "Ideas"), "category_ideas_16.png" },
59 { NC_("CategoryName", "International"), "category_international_16.png" },
60 { NC_("CategoryName", "Key Customer"), "category_key-customer_16.png" },
61 { NC_("CategoryName", "Miscellaneous"), "category_miscellaneous_16.png" },
62 { NC_("CategoryName", "Personal"), "category_personal_16.png" },
63 { NC_("CategoryName", "Phone Calls"), "category_phonecalls_16.png" },
64 /* Translators: "Status" is a category name; it can mean anything user wants to */
65 { NC_("CategoryName", "Status"), "category_status_16.png" },
66 { NC_("CategoryName", "Strategies"), "category_strategies_16.png" },
67 { NC_("CategoryName", "Suppliers"), "category_suppliers_16.png" },
68 { NC_("CategoryName", "Time & Expenses"), "category_time-and-expenses_16.png" },
69 { NC_("CategoryName", "VIP") },
70 { NC_("CategoryName", "Waiting") },
71 { NULL }
72 };
73
74 /* ------------------------------------------------------------------------- */
75
76 typedef struct {
77 GObject object;
78 } EChangedListener;
79
80 typedef struct {
81 GObjectClass parent_class;
82
83 void (* changed) (void);
84 } EChangedListenerClass;
85
86 static GType e_changed_listener_get_type (void);
87
88 G_DEFINE_TYPE (EChangedListener, e_changed_listener, G_TYPE_OBJECT)
89
90 enum {
91 CHANGED,
92 LAST_SIGNAL
93 };
94
95 static guint changed_listener_signals[LAST_SIGNAL];
96
97 static void
e_changed_listener_class_init(EChangedListenerClass * class)98 e_changed_listener_class_init (EChangedListenerClass *class)
99 {
100 changed_listener_signals[CHANGED] = g_signal_new (
101 "changed",
102 G_TYPE_FROM_CLASS (class),
103 G_SIGNAL_RUN_FIRST,
104 G_STRUCT_OFFSET (EChangedListenerClass, changed),
105 NULL, NULL, NULL,
106 G_TYPE_NONE, 0);
107 }
108
109 static void
e_changed_listener_init(EChangedListener * listener)110 e_changed_listener_init (EChangedListener *listener)
111 {
112 }
113
114 /* ------------------------------------------------------------------------- */
115
116 /* All the static variables below are protected by a global categories lock. */
117 G_LOCK_DEFINE_STATIC (categories);
118
119 static gboolean initialized = FALSE;
120 static GHashTable *categories_table = NULL;
121 static gboolean save_is_pending = FALSE;
122 static guint idle_id = 0;
123 static EChangedListener *listeners = NULL;
124 static gboolean changed = FALSE;
125
126 static gchar *
build_categories_filename(void)127 build_categories_filename (void)
128 {
129 const gchar *user_data_dir;
130 gchar *filename;
131
132 user_data_dir = e_get_user_data_dir ();
133 filename = g_build_filename (user_data_dir, "categories.xml", NULL);
134
135 if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
136 gchar *old_filename;
137
138 /* Try moving the file from its old 2.x location.
139 * This is best effort; don't worry about errors. */
140 old_filename = g_build_filename (
141 g_get_home_dir (), ".evolution",
142 "categories.xml", NULL);
143 if (g_rename (old_filename, filename) == -1 && errno != ENOENT) {
144 g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, old_filename, filename, g_strerror (errno));
145 }
146 g_free (old_filename);
147 }
148
149 return filename;
150 }
151
152 static void
free_category_info(CategoryInfo * cat_info)153 free_category_info (CategoryInfo *cat_info)
154 {
155 g_free (cat_info->display_name);
156 g_free (cat_info->clocale_name);
157 g_free (cat_info->icon_file);
158
159 g_slice_free (CategoryInfo, cat_info);
160 }
161
162 static gboolean
category_info_equal(const CategoryInfo * cat_info1,const CategoryInfo * cat_info2)163 category_info_equal (const CategoryInfo *cat_info1,
164 const CategoryInfo *cat_info2)
165 {
166 if (!cat_info1 || !cat_info2 || cat_info1 == cat_info2)
167 return cat_info1 == cat_info2;
168
169 return g_strcmp0 (cat_info1->display_name, cat_info2->display_name) == 0 &&
170 g_strcmp0 (cat_info1->clocale_name, cat_info2->clocale_name) == 0 &&
171 g_strcmp0 (cat_info1->icon_file, cat_info2->icon_file) == 0 &&
172 (cat_info1->is_default ? 1 : 0) == (cat_info2->is_default ? 1 : 0) &&
173 (cat_info1->is_searchable ? 1 : 0) == (cat_info2->is_searchable ? 1 : 0);
174 }
175
176 static gchar *
escape_string(const gchar * source)177 escape_string (const gchar *source)
178 {
179 GString *buffer;
180
181 buffer = g_string_sized_new (strlen (source));
182
183 while (*source) {
184 switch (*source) {
185 case '<':
186 g_string_append_len (buffer, "<", 4);
187 break;
188 case '>':
189 g_string_append_len (buffer, ">", 4);
190 break;
191 case '&':
192 g_string_append_len (buffer, "&", 5);
193 break;
194 case '"':
195 g_string_append_len (buffer, """, 6);
196 break;
197 default:
198 g_string_append_c (buffer, *source);
199 break;
200 }
201 source++;
202 }
203
204 return g_string_free (buffer, FALSE);
205 }
206
207 /* This must be called with the @categories lock held. */
208 static void
hash_to_xml_string(gpointer key,gpointer value,gpointer user_data)209 hash_to_xml_string (gpointer key,
210 gpointer value,
211 gpointer user_data)
212 {
213 CategoryInfo *cat_info = value;
214 GString *string = user_data;
215 gchar *category;
216
217 g_string_append_len (string, " <category", 11);
218
219 if (cat_info->is_default && cat_info->clocale_name && *cat_info->clocale_name)
220 category = escape_string (cat_info->clocale_name);
221 else
222 category = escape_string (cat_info->display_name);
223 g_string_append_printf (string, " a=\"%s\"", category);
224 g_free (category);
225
226 if (cat_info->icon_file != NULL)
227 g_string_append_printf (
228 string, " icon=\"%s\"", cat_info->icon_file);
229
230 g_string_append_printf (
231 string, " default=\"%d\"", cat_info->is_default ? 1 : 0);
232
233 g_string_append_printf (
234 string, " searchable=\"%d\"", cat_info->is_searchable ? 1 : 0);
235
236 g_string_append_len (string, "/>\n", 3);
237 }
238
239 /* Called with the @categories lock locked */
240 static void
idle_saver_save(void)241 idle_saver_save (void)
242 {
243 GString *buffer;
244 gchar *contents;
245 gchar *filename;
246 gchar *pathname;
247 EChangedListener *emit_listeners = NULL; /* owned */
248 GError *error = NULL;
249
250 if (!save_is_pending)
251 goto exit;
252
253 filename = build_categories_filename ();
254
255 d (g_debug ("Saving categories to \"%s\"", filename));
256
257 /* Build the file contents. */
258 buffer = g_string_new ("<categories>\n");
259 g_hash_table_foreach (categories_table, hash_to_xml_string, buffer);
260 g_string_append_len (buffer, "</categories>\n", 14);
261 contents = g_string_free (buffer, FALSE);
262
263 pathname = g_path_get_dirname (filename);
264 g_mkdir_with_parents (pathname, 0700);
265
266 if (!g_file_set_contents (filename, contents, -1, &error)) {
267 g_warning ("Unable to save categories: %s", error->message);
268 g_error_free (error);
269 }
270
271 g_free (pathname);
272 g_free (contents);
273 g_free (filename);
274 save_is_pending = FALSE;
275
276 if (changed)
277 emit_listeners = g_object_ref (listeners);
278
279 changed = FALSE;
280 exit:
281 idle_id = 0;
282
283 /* Emit the signal with the lock released to avoid re-entrancy
284 * deadlocks. Hold a reference to @listeners until this is complete. */
285 if (emit_listeners) {
286 G_UNLOCK (categories);
287
288 g_signal_emit_by_name (emit_listeners, "changed");
289 g_object_unref (emit_listeners);
290
291 G_LOCK (categories);
292 }
293 }
294
295 static gboolean
idle_saver_cb(gpointer user_data)296 idle_saver_cb (gpointer user_data)
297 {
298 G_LOCK (categories);
299
300 idle_saver_save ();
301
302 G_UNLOCK (categories);
303
304 return FALSE;
305 }
306
307 /* This must be called with the @categories lock held. */
308 static void
save_categories(void)309 save_categories (void)
310 {
311 save_is_pending = TRUE;
312
313 if (idle_id == 0)
314 idle_id = g_idle_add (idle_saver_cb, NULL);
315 }
316
317 static gchar *
get_collation_key(const gchar * category)318 get_collation_key (const gchar *category)
319 {
320 gchar *casefolded, *key;
321
322 g_return_val_if_fail (category != NULL, NULL);
323
324 casefolded = g_utf8_casefold (category, -1);
325 g_return_val_if_fail (casefolded != NULL, NULL);
326
327 key = g_utf8_collate_key (casefolded, -1);
328 g_free (casefolded);
329
330 return key;
331 }
332
333 /* This must be called with the @categories lock held. */
334 static void
categories_add_full(const gchar * category,const gchar * icon_file,gboolean is_default,gboolean is_searchable)335 categories_add_full (const gchar *category,
336 const gchar *icon_file,
337 gboolean is_default,
338 gboolean is_searchable)
339 {
340 CategoryInfo *cat_info, *existing_cat_info;
341 gchar *collation_key;
342
343 cat_info = g_slice_new (CategoryInfo);
344 if (is_default) {
345 const gchar *display_name;
346 display_name = g_dpgettext2 (
347 GETTEXT_PACKAGE, "CategoryName", category);
348 cat_info->display_name = g_strdup (display_name);
349 cat_info->clocale_name = g_strdup (category);
350 } else {
351 cat_info->display_name = g_strdup (category);
352 cat_info->clocale_name = NULL;
353 }
354 cat_info->icon_file = g_strdup (icon_file);
355 cat_info->is_default = is_default;
356 cat_info->is_searchable = is_default || is_searchable;
357
358 collation_key = get_collation_key (cat_info->display_name);
359 existing_cat_info = g_hash_table_lookup (categories_table, collation_key);
360 if (category_info_equal (existing_cat_info, cat_info)) {
361 free_category_info (cat_info);
362 g_free (collation_key);
363 } else {
364 g_hash_table_insert (categories_table, collation_key, cat_info);
365 changed = TRUE;
366 save_categories ();
367 }
368 }
369
370 /* This must be called with the @categories lock held. */
371 static CategoryInfo *
categories_lookup(const gchar * category)372 categories_lookup (const gchar *category)
373 {
374 CategoryInfo *cat_info;
375 gchar *collation_key;
376
377 collation_key = get_collation_key (category);
378 cat_info = g_hash_table_lookup (categories_table, collation_key);
379 g_free (collation_key);
380
381 return cat_info;
382 }
383
384 /* This must be called with the @categories lock held. */
385 static gint
parse_categories(const gchar * contents,gsize length)386 parse_categories (const gchar *contents,
387 gsize length)
388 {
389 xmlDocPtr doc;
390 xmlNodePtr node;
391 gint n_added = 0;
392
393 doc = xmlParseMemory (contents, length);
394 if (doc == NULL) {
395 g_warning ("Unable to parse categories");
396 return 0;
397 }
398
399 node = xmlDocGetRootElement (doc);
400 if (node == NULL) {
401 g_warning ("Unable to parse categories");
402 xmlFreeDoc (doc);
403 return 0;
404 }
405
406 for (node = node->xmlChildrenNode; node != NULL; node = node->next) {
407 xmlChar *category, *icon_file, *is_default, *is_searchable;
408
409 category = xmlGetProp (node, (xmlChar *) "a");
410 icon_file = xmlGetProp (node, (xmlChar *) "icon");
411 is_default = xmlGetProp (node, (xmlChar *) "default");
412 is_searchable = xmlGetProp (node, (xmlChar *) "searchable");
413
414 if (category != NULL && *category) {
415 categories_add_full (
416 (gchar *) category, (gchar *) icon_file,
417 g_strcmp0 ((gchar *) is_default, "1") == 0,
418 g_strcmp0 ((gchar *) is_searchable, "1") == 0);
419 n_added++;
420 }
421
422 xmlFree (category);
423 xmlFree (icon_file);
424 xmlFree (is_default);
425 xmlFree (is_searchable);
426 }
427
428 xmlFreeDoc (doc);
429
430 return n_added;
431 }
432
433 /* This must be called with the @categories lock held. */
434 static gint
load_categories(void)435 load_categories (void)
436 {
437 gchar *contents;
438 gchar *filename;
439 gsize length;
440 gint n_added = 0;
441 GError *error = NULL;
442
443 contents = NULL;
444 filename = build_categories_filename ();
445
446 if (!g_file_test (filename, G_FILE_TEST_EXISTS))
447 goto exit;
448
449 d (g_debug ("Loading categories from \"%s\"", filename));
450
451 if (!g_file_get_contents (filename, &contents, &length, &error)) {
452 g_warning ("Unable to load categories: %s", error->message);
453 g_error_free (error);
454 goto exit;
455 }
456
457 n_added = parse_categories (contents, length);
458
459 exit:
460 g_free (contents);
461 g_free (filename);
462
463 return n_added;
464 }
465
466 /* This must be called with the @categories lock held. */
467 static void
load_default_categories(void)468 load_default_categories (void)
469 {
470 DefaultCategory *cat_info = default_categories;
471
472 while (cat_info->category != NULL) {
473 gchar *icon_file = NULL;
474
475 if (cat_info->icon_file != NULL)
476 icon_file = g_build_filename (
477 E_DATA_SERVER_IMAGESDIR,
478 cat_info->icon_file, NULL);
479
480 categories_add_full (cat_info->category, icon_file, TRUE, TRUE);
481
482 g_free (icon_file);
483 cat_info++;
484 }
485 }
486
487 static void
finalize_categories(void)488 finalize_categories (void)
489 {
490 G_LOCK (categories);
491
492 if (save_is_pending)
493 idle_saver_save ();
494
495 if (idle_id > 0) {
496 g_source_remove (idle_id);
497 idle_id = 0;
498 }
499
500 g_clear_pointer (&categories_table, g_hash_table_destroy);
501 g_clear_object (&listeners);
502
503 initialized = FALSE;
504
505 G_UNLOCK (categories);
506 }
507
508 /* This must be called with the @categories lock held. */
509 static void
initialize_categories(void)510 initialize_categories (void)
511 {
512 gint n_added;
513
514 if (initialized)
515 return;
516
517 initialized = TRUE;
518
519 bindtextdomain (GETTEXT_PACKAGE, E_DATA_SERVER_LOCALEDIR);
520
521 categories_table = g_hash_table_new_full (
522 g_str_hash, g_str_equal,
523 (GDestroyNotify) g_free,
524 (GDestroyNotify) free_category_info);
525
526 listeners = g_object_new (e_changed_listener_get_type (), NULL);
527
528 atexit (finalize_categories);
529
530 n_added = load_categories ();
531 if (n_added > 0) {
532 d (g_debug ("Loaded %d categories", n_added));
533 save_is_pending = FALSE;
534 return;
535 }
536
537 load_default_categories ();
538 d (g_debug ("Loaded default categories"));
539 save_categories ();
540 }
541
542 /**
543 * e_categories_get_list:
544 *
545 * Returns a sorted list of all the category names currently configured.
546 *
547 * This function is mostly thread safe, but as the category names are not
548 * copied, they may be freed by another thread after being returned by this
549 * function. Use e_categories_dup_list() instead.
550 *
551 * Returns: (transfer container) (element-type utf8): a sorted GList containing
552 * the names of the categories. The list should be freed using g_list_free(),
553 * but the names of the categories should not be touched at all, they are
554 * internal strings.
555 *
556 * Deprecated: 3.16: This function is not entirely thread safe. Use
557 * e_categories_dup_list() instead.
558 */
559 GList *
e_categories_get_list(void)560 e_categories_get_list (void)
561 {
562 GHashTableIter iter;
563 GList *list = NULL;
564 gpointer key, value;
565
566 G_LOCK (categories);
567
568 if (!initialized)
569 initialize_categories ();
570
571 g_hash_table_iter_init (&iter, categories_table);
572
573 while (g_hash_table_iter_next (&iter, &key, &value)) {
574 CategoryInfo *cat_info = value;
575 list = g_list_prepend (list, cat_info->display_name);
576 }
577
578 G_UNLOCK (categories);
579
580 return g_list_sort (list, (GCompareFunc) g_utf8_collate);
581 }
582
583 /**
584 * e_categories_dup_list:
585 *
586 * Returns a sorted list of all the category names currently configured.
587 *
588 * This function is thread safe.
589 *
590 * Returns: (transfer full) (element-type utf8): a sorted #GList containing
591 * the names of the categories. The list should be freed using g_list_free(),
592 * and the names of the categories should be freed using g_free(). Everything
593 * can be freed simultaneously using g_list_free_full().
594 *
595 * Since: 3.16
596 */
597 GList *
e_categories_dup_list(void)598 e_categories_dup_list (void)
599 {
600 GHashTableIter iter;
601 GList *list = NULL;
602 gpointer key, value;
603
604 G_LOCK (categories);
605
606 if (!initialized)
607 initialize_categories ();
608
609 g_hash_table_iter_init (&iter, categories_table);
610
611 while (g_hash_table_iter_next (&iter, &key, &value)) {
612 CategoryInfo *cat_info = value;
613 list = g_list_prepend (list, g_strdup (cat_info->display_name));
614 }
615
616 G_UNLOCK (categories);
617
618 return g_list_sort (list, (GCompareFunc) g_utf8_collate);
619 }
620
621 /**
622 * e_categories_add:
623 * @category: name of category to add.
624 * @unused: DEPRECATED! associated color. DEPRECATED!
625 * @icon_file: full path of the icon associated to the category.
626 * @searchable: whether the category can be used for searching in the GUI.
627 *
628 * Adds a new category, with its corresponding icon, to the
629 * configuration database.
630 *
631 * This function is thread safe.
632 */
633 void
e_categories_add(const gchar * category,const gchar * unused,const gchar * icon_file,gboolean searchable)634 e_categories_add (const gchar *category,
635 const gchar *unused,
636 const gchar *icon_file,
637 gboolean searchable)
638 {
639 g_return_if_fail (category != NULL);
640 g_return_if_fail (*category);
641
642 G_LOCK (categories);
643
644 if (!initialized)
645 initialize_categories ();
646
647 categories_add_full (category, icon_file, FALSE, searchable);
648
649 G_UNLOCK (categories);
650 }
651
652 /**
653 * e_categories_remove:
654 * @category: category to be removed.
655 *
656 * Removes the given category from the configuration.
657 *
658 * This function is thread safe.
659 */
660 void
e_categories_remove(const gchar * category)661 e_categories_remove (const gchar *category)
662 {
663 gchar *collation_key;
664
665 g_return_if_fail (category != NULL);
666
667 G_LOCK (categories);
668
669 if (!initialized)
670 initialize_categories ();
671
672 collation_key = get_collation_key (category);
673
674 if (g_hash_table_remove (categories_table, collation_key)) {
675 changed = TRUE;
676 save_categories ();
677 }
678
679 g_free (collation_key);
680
681 G_UNLOCK (categories);
682 }
683
684 /**
685 * e_categories_exist:
686 * @category: category to be searched.
687 *
688 * Checks whether the given category is available in the configuration.
689 *
690 * This function is thread safe.
691 *
692 * Returns: %TRUE if the category is available, %FALSE otherwise.
693 */
694 gboolean
e_categories_exist(const gchar * category)695 e_categories_exist (const gchar *category)
696 {
697 gboolean exists;
698
699 g_return_val_if_fail (category != NULL, FALSE);
700
701 G_LOCK (categories);
702
703 if (!initialized)
704 initialize_categories ();
705
706 exists = (!*category) || (categories_lookup (category) != NULL);
707
708 G_UNLOCK (categories);
709
710 return exists;
711 }
712
713 /**
714 * e_categories_get_icon_file_for:
715 * @category: category to retrieve the icon file for.
716 *
717 * Gets the icon file associated with the given category.
718 *
719 * This function is mostly thread safe, but as the icon file name is not
720 * copied, it may be freed by another thread after being returned by this
721 * function. Use e_categories_dup_icon_file_for() instead.
722 *
723 * Deprecated: 3.16: This function is not entirely thread safe. Use
724 * e_categories_dup_icon_file_for() instead.
725 *
726 * Returns: icon file name.
727 */
728 const gchar *
e_categories_get_icon_file_for(const gchar * category)729 e_categories_get_icon_file_for (const gchar *category)
730 {
731 CategoryInfo *cat_info;
732
733 g_return_val_if_fail (category != NULL, NULL);
734
735 G_LOCK (categories);
736
737 if (!initialized)
738 initialize_categories ();
739
740 cat_info = categories_lookup (category);
741
742 G_UNLOCK (categories);
743
744 if (cat_info == NULL)
745 return NULL;
746
747 return cat_info->icon_file;
748 }
749
750 /**
751 * e_categories_dup_icon_file_for:
752 * @category: category to retrieve the icon file for.
753 *
754 * Gets the icon file associated with the given category and returns a copy of
755 * it.
756 *
757 * This function is thread safe.
758 *
759 * Returns: (transfer full): icon file name; free with g_free().
760 *
761 * Since: 3.16
762 */
763 gchar *
e_categories_dup_icon_file_for(const gchar * category)764 e_categories_dup_icon_file_for (const gchar *category)
765 {
766 CategoryInfo *cat_info;
767 gchar *icon_file = NULL;
768
769 g_return_val_if_fail (category != NULL, NULL);
770
771 G_LOCK (categories);
772
773 if (!initialized)
774 initialize_categories ();
775
776 cat_info = categories_lookup (category);
777
778 if (cat_info != NULL)
779 icon_file = g_strdup (cat_info->icon_file);
780
781 G_UNLOCK (categories);
782
783 return icon_file;
784 }
785
786 /**
787 * e_categories_set_icon_file_for:
788 * @category: category to set the icon file for.
789 * @icon_file: icon file.
790 *
791 * Sets the icon file associated with the given category.
792 *
793 * This function is thread safe.
794 */
795 void
e_categories_set_icon_file_for(const gchar * category,const gchar * icon_file)796 e_categories_set_icon_file_for (const gchar *category,
797 const gchar *icon_file)
798 {
799 CategoryInfo *cat_info;
800
801 g_return_if_fail (category != NULL);
802
803 G_LOCK (categories);
804
805 if (!initialized)
806 initialize_categories ();
807
808 cat_info = categories_lookup (category);
809 g_return_if_fail (cat_info != NULL);
810
811 g_free (cat_info->icon_file);
812 cat_info->icon_file = g_strdup (icon_file);
813
814 changed = TRUE;
815 save_categories ();
816
817 G_UNLOCK (categories);
818 }
819
820 /**
821 * e_categories_is_searchable:
822 * @category: category name.
823 *
824 * Gets whether the given calendar is to be used for searches in the GUI.
825 *
826 * This function is thread safe.
827 *
828 * Return value; %TRUE% if the category is searchable, %FALSE% if not.
829 */
830 gboolean
e_categories_is_searchable(const gchar * category)831 e_categories_is_searchable (const gchar *category)
832 {
833 CategoryInfo *cat_info;
834 gboolean is_searchable = FALSE;
835
836 g_return_val_if_fail (category != NULL, FALSE);
837
838 G_LOCK (categories);
839
840 if (!initialized)
841 initialize_categories ();
842
843 cat_info = categories_lookup (category);
844
845 if (cat_info != NULL)
846 is_searchable = cat_info->is_searchable;
847
848 G_UNLOCK (categories);
849
850 return is_searchable;
851 }
852
853 /**
854 * e_categories_register_change_listener:
855 * @listener: (scope async): the callback to be called on any category change.
856 * @user_data: used data passed to the @listener when called.
857 *
858 * Registers callback to be called on change of any category.
859 * Pair listener and user_data is used to distinguish between listeners.
860 * Listeners can be unregistered with @e_categories_unregister_change_listener.
861 *
862 * This function is thread safe.
863 *
864 * Since: 2.24
865 **/
866 void
e_categories_register_change_listener(GCallback listener,gpointer user_data)867 e_categories_register_change_listener (GCallback listener,
868 gpointer user_data)
869 {
870 G_LOCK (categories);
871
872 if (!initialized)
873 initialize_categories ();
874
875 g_signal_connect (listeners, "changed", listener, user_data);
876
877 G_UNLOCK (categories);
878 }
879
880 /**
881 * e_categories_unregister_change_listener:
882 * @listener: (scope async): Callback to be removed.
883 * @user_data: User data as passed with call to @e_categories_register_change_listener.
884 *
885 * Removes previously registered callback from the list of listeners on changes.
886 * If it was not registered, then does nothing.
887 *
888 * This function is thread safe.
889 *
890 * Since: 2.24
891 **/
892 void
e_categories_unregister_change_listener(GCallback listener,gpointer user_data)893 e_categories_unregister_change_listener (GCallback listener,
894 gpointer user_data)
895 {
896 G_LOCK (categories);
897
898 if (initialized)
899 g_signal_handlers_disconnect_by_func (listeners, listener, user_data);
900
901 G_UNLOCK (categories);
902 }
903