Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / comp-util.c
blobe66eaf37c2b34f9090c95d87d2dfc5e2e5ce5555
1 /*
2 * Evolution calendar - Utilities for manipulating ECalComponent objects
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 * Authors:
18 * Federico Mena-Quintero <federico@ximian.com>
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
24 #include "evolution-config.h"
26 #include <glib/gi18n-lib.h>
28 #include <string.h>
29 #include <time.h>
31 #include "calendar-config.h"
32 #include "comp-util.h"
33 #include "e-calendar-view.h"
34 #include "itip-utils.h"
36 #include "shell/e-shell-window.h"
37 #include "shell/e-shell-view.h"
39 /**
40 * cal_comp_util_add_exdate:
41 * @comp: A calendar component object.
42 * @itt: Time for the exception.
44 * Adds an exception date to the current list of EXDATE properties in a calendar
45 * component object.
46 **/
47 void
48 cal_comp_util_add_exdate (ECalComponent *comp,
49 time_t t,
50 icaltimezone *zone)
52 GSList *list;
53 ECalComponentDateTime *cdt;
55 g_return_if_fail (comp != NULL);
56 g_return_if_fail (E_IS_CAL_COMPONENT (comp));
58 e_cal_component_get_exdate_list (comp, &list);
60 cdt = g_new (ECalComponentDateTime, 1);
61 cdt->value = g_new (struct icaltimetype, 1);
62 *cdt->value = icaltime_from_timet_with_zone (t, FALSE, zone);
63 cdt->tzid = g_strdup (icaltimezone_get_tzid (zone));
65 list = g_slist_append (list, cdt);
66 e_cal_component_set_exdate_list (comp, list);
67 e_cal_component_free_exdate_list (list);
70 /* Returns TRUE if the TZIDs are equivalent, i.e. both NULL or the same. */
71 static gboolean
72 e_cal_component_compare_tzid (const gchar *tzid1,
73 const gchar *tzid2)
75 gboolean retval = TRUE;
77 if (tzid1) {
78 if (!tzid2 || strcmp (tzid1, tzid2))
79 retval = FALSE;
80 } else {
81 if (tzid2)
82 retval = FALSE;
85 return retval;
88 /**
89 * cal_comp_util_compare_event_timezones:
90 * @comp: A calendar component object.
91 * @client: A #ECalClient.
93 * Checks if the component uses the given timezone for both the start and
94 * the end time, or if the UTC offsets of the start and end times are the same
95 * as in the given zone.
97 * Returns: TRUE if the component's start and end time are at the same UTC
98 * offset in the given timezone.
99 **/
100 gboolean
101 cal_comp_util_compare_event_timezones (ECalComponent *comp,
102 ECalClient *client,
103 icaltimezone *zone)
105 ECalComponentDateTime start_datetime, end_datetime;
106 const gchar *tzid;
107 gboolean retval = FALSE;
108 icaltimezone *start_zone = NULL;
109 icaltimezone *end_zone = NULL;
110 gint offset1, offset2;
112 tzid = icaltimezone_get_tzid (zone);
114 e_cal_component_get_dtstart (comp, &start_datetime);
115 e_cal_component_get_dtend (comp, &end_datetime);
117 /* If either the DTSTART or the DTEND is a DATE value, we return TRUE.
118 * Maybe if one was a DATE-TIME we should check that, but that should
119 * not happen often. */
120 if ((start_datetime.value && start_datetime.value->is_date)
121 || (end_datetime.value && end_datetime.value->is_date)) {
122 retval = TRUE;
123 goto out;
126 /* If the event uses UTC for DTSTART & DTEND, return TRUE. Outlook
127 * will send single events as UTC, so we don't want to mark all of
128 * these. */
129 if ((!start_datetime.value || icaltime_is_utc (*start_datetime.value))
130 && (!end_datetime.value || icaltime_is_utc (*end_datetime.value))) {
131 retval = TRUE;
132 goto out;
135 /* If the event uses floating time for DTSTART & DTEND, return TRUE.
136 * Imported vCalendar files will use floating times, so we don't want
137 * to mark all of these. */
138 if (!start_datetime.tzid && !end_datetime.tzid) {
139 retval = TRUE;
140 goto out;
143 /* FIXME: DURATION may be used instead. */
144 if (e_cal_component_compare_tzid (tzid, start_datetime.tzid)
145 && e_cal_component_compare_tzid (tzid, end_datetime.tzid)) {
146 /* If both TZIDs are the same as the given zone's TZID, then
147 * we know the timezones are the same so we return TRUE. */
148 retval = TRUE;
149 } else {
150 /* If the TZIDs differ, we have to compare the UTC offsets
151 * of the start and end times, using their own timezones and
152 * the given timezone. */
153 if (start_datetime.tzid)
154 e_cal_client_get_timezone_sync (client, start_datetime.tzid, &start_zone, NULL, NULL);
155 else
156 start_zone = NULL;
158 if (start_zone == NULL)
159 goto out;
161 if (start_datetime.value) {
162 offset1 = icaltimezone_get_utc_offset (
163 start_zone,
164 start_datetime.value,
165 NULL);
166 offset2 = icaltimezone_get_utc_offset (
167 zone,
168 start_datetime.value,
169 NULL);
170 if (offset1 != offset2)
171 goto out;
174 if (end_datetime.tzid)
175 e_cal_client_get_timezone_sync (client, end_datetime.tzid, &end_zone, NULL, NULL);
176 else
177 end_zone = NULL;
179 if (end_zone == NULL)
180 goto out;
182 if (end_datetime.value) {
183 offset1 = icaltimezone_get_utc_offset (
184 end_zone,
185 end_datetime.value,
186 NULL);
187 offset2 = icaltimezone_get_utc_offset (
188 zone,
189 end_datetime.value,
190 NULL);
191 if (offset1 != offset2)
192 goto out;
195 retval = TRUE;
198 out:
200 e_cal_component_free_datetime (&start_datetime);
201 e_cal_component_free_datetime (&end_datetime);
203 return retval;
207 * cal_comp_is_on_server_sync:
208 * @comp: an #ECalComponent
209 * @client: an #ECalClient
210 * @cancellable: (allow none): a #GCancellable
211 * @error: (out): (allow none): a #GError
213 * Checks whether @client contains @comp. A "No Such Object" error is not
214 * propagated to the caller, any other errors are.
216 * Returns: #TRUE, when the @client contains @comp, #FALSE when not or on error.
217 * The @error is not set when the @client doesn't contain the @comp.
219 gboolean
220 cal_comp_is_on_server_sync (ECalComponent *comp,
221 ECalClient *client,
222 GCancellable *cancellable,
223 GError **error)
225 const gchar *uid;
226 gchar *rid = NULL;
227 icalcomponent *icalcomp = NULL;
228 GError *local_error = NULL;
230 g_return_val_if_fail (comp != NULL, FALSE);
231 g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
232 g_return_val_if_fail (client != NULL, FALSE);
233 g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
235 /* See if the component is on the server. If it is not, then it likely
236 * means that the appointment is new, only in the day view, and we
237 * haven't added it yet to the server. In that case, we don't need to
238 * confirm and we can just delete the event. Otherwise, we ask
239 * the user.
241 e_cal_component_get_uid (comp, &uid);
243 /* TODO We should not be checking for this here. But since
244 * e_cal_util_construct_instance does not create the instances
245 * of all day events, so we default to old behaviour. */
246 if (e_cal_client_check_recurrences_no_master (client)) {
247 rid = e_cal_component_get_recurid_as_string (comp);
250 if (e_cal_client_get_object_sync (client, uid, rid, &icalcomp, cancellable, &local_error) &&
251 icalcomp != NULL) {
252 icalcomponent_free (icalcomp);
253 g_free (rid);
255 return TRUE;
258 if (g_error_matches (local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND))
259 g_clear_error (&local_error);
260 else
261 g_propagate_error (error, local_error);
263 g_free (rid);
265 return FALSE;
269 * cal_comp_is_icalcomp_on_server_sync:
270 * The same as cal_comp_is_on_server_sync(), only the component parameter is
271 * icalcomponent, not the ECalComponent.
273 gboolean
274 cal_comp_is_icalcomp_on_server_sync (icalcomponent *icalcomp,
275 ECalClient *client,
276 GCancellable *cancellable,
277 GError **error)
279 gboolean on_server;
280 ECalComponent *comp;
282 if (!icalcomp || !client || !icalcomponent_get_uid (icalcomp))
283 return FALSE;
285 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (icalcomp));
286 if (!comp)
287 return FALSE;
289 on_server = cal_comp_is_on_server_sync (comp, client, cancellable, error);
291 g_object_unref (comp);
293 return on_server;
297 * cal_comp_event_new_with_defaults_sync:
299 * Creates a new VEVENT component and adds any default alarms to it as set in
300 * the program's configuration values, but only if not the all_day event.
302 * Return value: A newly-created calendar component.
304 ECalComponent *
305 cal_comp_event_new_with_defaults_sync (ECalClient *client,
306 gboolean all_day,
307 gboolean use_default_reminder,
308 gint default_reminder_interval,
309 EDurationType default_reminder_units,
310 GCancellable *cancellable,
311 GError **error)
313 icalcomponent *icalcomp = NULL;
314 ECalComponent *comp;
315 ECalComponentAlarm *alarm;
316 icalproperty *icalprop;
317 ECalComponentAlarmTrigger trigger;
319 if (client && !e_cal_client_get_default_object_sync (client, &icalcomp, cancellable, error))
320 return NULL;
322 if (icalcomp == NULL)
323 icalcomp = icalcomponent_new (ICAL_VEVENT_COMPONENT);
325 comp = e_cal_component_new ();
326 if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
327 icalcomponent_free (icalcomp);
329 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
332 if (all_day || !use_default_reminder)
333 return comp;
335 alarm = e_cal_component_alarm_new ();
337 /* We don't set the description of the alarm; we'll copy it from the
338 * summary when it gets committed to the server. For that, we add a
339 * X-EVOLUTION-NEEDS-DESCRIPTION property to the alarm's component.
341 icalcomp = e_cal_component_alarm_get_icalcomponent (alarm);
342 icalprop = icalproperty_new_x ("1");
343 icalproperty_set_x_name (icalprop, "X-EVOLUTION-NEEDS-DESCRIPTION");
344 icalcomponent_add_property (icalcomp, icalprop);
346 e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY);
348 trigger.type = E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START;
350 memset (&trigger.u.rel_duration, 0, sizeof (trigger.u.rel_duration));
352 trigger.u.rel_duration.is_neg = TRUE;
354 switch (default_reminder_units) {
355 case E_DURATION_MINUTES:
356 trigger.u.rel_duration.minutes = default_reminder_interval;
357 break;
359 case E_DURATION_HOURS:
360 trigger.u.rel_duration.hours = default_reminder_interval;
361 break;
363 case E_DURATION_DAYS:
364 trigger.u.rel_duration.days = default_reminder_interval;
365 break;
367 default:
368 g_warning ("wrong units %d\n", default_reminder_units);
371 e_cal_component_alarm_set_trigger (alarm, trigger);
373 e_cal_component_add_alarm (comp, alarm);
374 e_cal_component_alarm_free (alarm);
376 return comp;
379 ECalComponent *
380 cal_comp_event_new_with_current_time_sync (ECalClient *client,
381 gboolean all_day,
382 gboolean use_default_reminder,
383 gint default_reminder_interval,
384 EDurationType default_reminder_units,
385 GCancellable *cancellable,
386 GError **error)
388 ECalComponent *comp;
389 struct icaltimetype itt;
390 ECalComponentDateTime dt;
391 icaltimezone *zone;
393 comp = cal_comp_event_new_with_defaults_sync (
394 client, all_day, use_default_reminder,
395 default_reminder_interval, default_reminder_units,
396 cancellable, error);
397 if (!comp)
398 return NULL;
400 zone = calendar_config_get_icaltimezone ();
402 if (all_day) {
403 itt = icaltime_from_timet_with_zone (time (NULL), 1, zone);
405 dt.value = &itt;
406 dt.tzid = icaltimezone_get_tzid (zone);
408 e_cal_component_set_dtstart (comp, &dt);
409 e_cal_component_set_dtend (comp, &dt);
410 } else {
411 itt = icaltime_current_time_with_zone (zone);
412 icaltime_adjust (&itt, 0, 1, -itt.minute, -itt.second);
414 dt.value = &itt;
415 dt.tzid = icaltimezone_get_tzid (zone);
417 e_cal_component_set_dtstart (comp, &dt);
418 icaltime_adjust (&itt, 0, 1, 0, 0);
419 e_cal_component_set_dtend (comp, &dt);
422 return comp;
425 ECalComponent *
426 cal_comp_task_new_with_defaults_sync (ECalClient *client,
427 GCancellable *cancellable,
428 GError **error)
430 ECalComponent *comp;
431 icalcomponent *icalcomp = NULL;
433 if (client && !e_cal_client_get_default_object_sync (client, &icalcomp, cancellable, error))
434 return NULL;
436 if (icalcomp == NULL)
437 icalcomp = icalcomponent_new (ICAL_VTODO_COMPONENT);
439 comp = e_cal_component_new ();
440 if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
441 icalcomponent_free (icalcomp);
443 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
446 return comp;
449 ECalComponent *
450 cal_comp_memo_new_with_defaults_sync (ECalClient *client,
451 GCancellable *cancellable,
452 GError **error)
454 ECalComponent *comp;
455 icalcomponent *icalcomp = NULL;
457 if (client && !e_cal_client_get_default_object_sync (client, &icalcomp, cancellable, error))
458 return NULL;
460 if (icalcomp == NULL)
461 icalcomp = icalcomponent_new (ICAL_VJOURNAL_COMPONENT);
463 comp = e_cal_component_new ();
464 if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
465 icalcomponent_free (icalcomp);
467 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
470 return comp;
473 void
474 cal_comp_update_time_by_active_window (ECalComponent *comp,
475 EShell *shell)
477 GtkWindow *window;
479 g_return_if_fail (comp != NULL);
480 g_return_if_fail (shell != NULL);
482 window = e_shell_get_active_window (shell);
484 if (E_IS_SHELL_WINDOW (window)) {
485 EShellWindow *shell_window;
486 const gchar *active_view;
488 shell_window = E_SHELL_WINDOW (window);
489 active_view = e_shell_window_get_active_view (shell_window);
491 if (g_strcmp0 (active_view, "calendar") == 0) {
492 EShellContent *shell_content;
493 EShellView *shell_view;
494 ECalendarView *cal_view;
495 time_t start = 0, end = 0;
496 icaltimezone *zone;
497 struct icaltimetype itt;
498 icalcomponent *icalcomp;
499 icalproperty *prop;
501 shell_view = e_shell_window_peek_shell_view (shell_window, "calendar");
502 g_return_if_fail (shell_view != NULL);
504 cal_view = NULL;
505 shell_content = e_shell_view_get_shell_content (shell_view);
506 g_object_get (shell_content, "current-view", &cal_view, NULL);
507 g_return_if_fail (cal_view != NULL);
508 g_return_if_fail (e_calendar_view_get_visible_time_range (cal_view, &start, &end));
510 zone = e_cal_model_get_timezone (e_calendar_view_get_model (cal_view));
511 itt = icaltime_from_timet_with_zone (start, FALSE, zone);
513 icalcomp = e_cal_component_get_icalcomponent (comp);
514 prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY);
515 if (prop) {
516 icalproperty_set_dtstart (prop, itt);
517 } else {
518 prop = icalproperty_new_dtstart (itt);
519 icalcomponent_add_property (icalcomp, prop);
522 e_cal_component_rescan (comp);
524 g_clear_object (&cal_view);
530 * cal_comp_util_get_n_icons:
531 * @comp: A calendar component object.
532 * @pixbufs: List of pixbufs to use. Can be NULL.
534 * Get the number of icons owned by the component.
535 * Each member of pixmaps should be freed with g_object_unref
536 * and the list itself should be freed too.
538 * Returns: the number of icons owned by the component.
540 gint
541 cal_comp_util_get_n_icons (ECalComponent *comp,
542 GSList **pixbufs)
544 GSList *categories_list, *elem;
545 gint num_icons = 0;
547 g_return_val_if_fail (comp != NULL, 0);
548 g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), 0);
550 e_cal_component_get_categories_list (comp, &categories_list);
551 for (elem = categories_list; elem; elem = elem->next) {
552 const gchar *category;
553 GdkPixbuf *pixbuf = NULL;
555 category = elem->data;
556 if (e_categories_config_get_icon_for (category, &pixbuf)) {
557 if (!pixbuf)
558 continue;
560 num_icons++;
562 if (pixbufs) {
563 *pixbufs = g_slist_append (*pixbufs, pixbuf);
564 } else {
565 g_object_unref (pixbuf);
569 e_cal_component_free_categories_list (categories_list);
571 return num_icons;
575 * cal_comp_selection_set_string_list
576 * @data: Selection data, where to put list of strings.
577 * @str_list: List of strings. (Each element is of type const gchar *.)
579 * Stores list of strings into selection target data. Use
580 * cal_comp_selection_get_string_list() to get this list from target data.
582 void
583 cal_comp_selection_set_string_list (GtkSelectionData *data,
584 GSList *str_list)
586 /* format is "str1\0str2\0...strN\0" */
587 GSList *p;
588 GByteArray *array;
589 GdkAtom target;
591 g_return_if_fail (data != NULL);
593 if (!str_list)
594 return;
596 array = g_byte_array_new ();
597 for (p = str_list; p; p = p->next) {
598 const guint8 *c = p->data;
600 if (c)
601 g_byte_array_append (array, c, strlen ((const gchar *) c) + 1);
604 target = gtk_selection_data_get_target (data);
605 gtk_selection_data_set (data, target, 8, array->data, array->len);
606 g_byte_array_free (array, TRUE);
610 * cal_comp_selection_get_string_list
611 * @data: Selection data, where to put list of strings.
613 * Converts data from selection to list of strings. Data should be assigned
614 * to selection data with cal_comp_selection_set_string_list().
615 * Each string in newly created list should be freed by g_free().
616 * List itself should be freed by g_slist_free().
618 * Returns: Newly allocated #GSList of strings.
620 GSList *
621 cal_comp_selection_get_string_list (GtkSelectionData *selection_data)
623 /* format is "str1\0str2\0...strN\0" */
624 gchar *inptr, *inend;
625 GSList *list;
626 const guchar *data;
627 gint length;
629 g_return_val_if_fail (selection_data != NULL, NULL);
631 data = gtk_selection_data_get_data (selection_data);
632 length = gtk_selection_data_get_length (selection_data);
634 list = NULL;
635 inptr = (gchar *) data;
636 inend = (gchar *) (data + length);
638 while (inptr < inend) {
639 gchar *start = inptr;
641 while (inptr < inend && *inptr)
642 inptr++;
644 list = g_slist_prepend (list, g_strndup (start, inptr - start));
646 inptr++;
649 return list;
652 static void
653 datetime_to_zone (ECalClient *client,
654 ECalComponentDateTime *date,
655 const gchar *tzid)
657 icaltimezone *from, *to;
659 g_return_if_fail (date != NULL);
661 if (date->tzid == NULL || tzid == NULL ||
662 date->tzid == tzid || g_str_equal (date->tzid, tzid))
663 return;
665 from = icaltimezone_get_builtin_timezone_from_tzid (date->tzid);
666 if (!from) {
667 GError *error = NULL;
669 e_cal_client_get_timezone_sync (
670 client, date->tzid, &from, NULL, &error);
672 if (error != NULL) {
673 g_warning (
674 "%s: Could not get timezone '%s' from server: %s",
675 G_STRFUNC, date->tzid ? date->tzid : "",
676 error->message);
677 g_error_free (error);
681 to = icaltimezone_get_builtin_timezone_from_tzid (tzid);
682 if (!to) {
683 /* do not check failure here, maybe the zone is not available there */
684 e_cal_client_get_timezone_sync (client, tzid, &to, NULL, NULL);
687 icaltimezone_convert_time (date->value, from, to);
688 date->tzid = tzid;
692 * cal_comp_set_dtstart_with_oldzone:
693 * @client: ECalClient structure, to retrieve timezone from, when required.
694 * @comp: Component, where make the change.
695 * @pdate: Value, to change to.
697 * Changes 'dtstart' of the component, but converts time to the old timezone.
699 void
700 cal_comp_set_dtstart_with_oldzone (ECalClient *client,
701 ECalComponent *comp,
702 const ECalComponentDateTime *pdate)
704 ECalComponentDateTime olddate, date;
706 g_return_if_fail (comp != NULL);
707 g_return_if_fail (pdate != NULL);
709 e_cal_component_get_dtstart (comp, &olddate);
711 date = *pdate;
713 datetime_to_zone (client, &date, olddate.tzid);
714 e_cal_component_set_dtstart (comp, &date);
716 e_cal_component_free_datetime (&olddate);
720 * cal_comp_set_dtend_with_oldzone:
721 * @client: ECalClient structure, to retrieve timezone from, when required.
722 * @comp: Component, where make the change.
723 * @pdate: Value, to change to.
725 * Changes 'dtend' of the component, but converts time to the old timezone.
727 void
728 cal_comp_set_dtend_with_oldzone (ECalClient *client,
729 ECalComponent *comp,
730 const ECalComponentDateTime *pdate)
732 ECalComponentDateTime olddate, date;
734 g_return_if_fail (comp != NULL);
735 g_return_if_fail (pdate != NULL);
737 e_cal_component_get_dtend (comp, &olddate);
739 date = *pdate;
741 datetime_to_zone (client, &date, olddate.tzid);
742 e_cal_component_set_dtend (comp, &date);
744 e_cal_component_free_datetime (&olddate);
747 gboolean
748 comp_util_sanitize_recurrence_master_sync (ECalComponent *comp,
749 ECalClient *client,
750 GCancellable *cancellable,
751 GError **error)
753 ECalComponent *master = NULL;
754 icalcomponent *icalcomp = NULL;
755 ECalComponentRange rid;
756 ECalComponentDateTime sdt;
757 const gchar *uid;
759 /* Get the master component */
760 e_cal_component_get_uid (comp, &uid);
762 if (!e_cal_client_get_object_sync (client, uid, NULL, &icalcomp, cancellable, error))
763 return FALSE;
765 master = e_cal_component_new_from_icalcomponent (icalcomp);
766 if (!master) {
767 g_warn_if_reached ();
768 return FALSE;
771 /* Compare recur id and start date */
772 e_cal_component_get_recurid (comp, &rid);
773 e_cal_component_get_dtstart (comp, &sdt);
775 if (rid.datetime.value && sdt.value &&
776 icaltime_compare_date_only (
777 *rid.datetime.value, *sdt.value) == 0) {
778 ECalComponentDateTime msdt, medt, edt;
779 gint *sequence;
781 e_cal_component_get_dtstart (master, &msdt);
782 e_cal_component_get_dtend (master, &medt);
784 e_cal_component_get_dtend (comp, &edt);
786 if (!msdt.value || !medt.value || !edt.value) {
787 g_warn_if_reached ();
788 e_cal_component_free_datetime (&msdt);
789 e_cal_component_free_datetime (&medt);
790 e_cal_component_free_datetime (&edt);
791 e_cal_component_free_datetime (&sdt);
792 e_cal_component_free_range (&rid);
793 g_object_unref (master);
794 return FALSE;
797 sdt.value->year = msdt.value->year;
798 sdt.value->month = msdt.value->month;
799 sdt.value->day = msdt.value->day;
801 edt.value->year = medt.value->year;
802 edt.value->month = medt.value->month;
803 edt.value->day = medt.value->day;
805 e_cal_component_set_dtstart (comp, &sdt);
806 e_cal_component_set_dtend (comp, &edt);
808 e_cal_component_get_sequence (master, &sequence);
809 e_cal_component_set_sequence (comp, sequence);
811 e_cal_component_free_datetime (&msdt);
812 e_cal_component_free_datetime (&medt);
813 e_cal_component_free_datetime (&edt);
816 e_cal_component_free_datetime (&sdt);
817 e_cal_component_free_range (&rid);
818 e_cal_component_set_recurid (comp, NULL);
820 g_object_unref (master);
822 return TRUE;
825 gchar *
826 icalcomp_suggest_filename (icalcomponent *icalcomp,
827 const gchar *default_name)
829 icalproperty *prop;
830 const gchar *summary = NULL;
832 if (!icalcomp)
833 return g_strconcat (default_name, ".ics", NULL);
835 prop = icalcomponent_get_first_property (icalcomp, ICAL_SUMMARY_PROPERTY);
836 if (prop)
837 summary = icalproperty_get_summary (prop);
839 if (!summary || !*summary)
840 summary = default_name;
842 return g_strconcat (summary, ".ics", NULL);
845 void
846 cal_comp_get_instance_times (ECalClient *client,
847 icalcomponent *icalcomp,
848 const icaltimezone *default_zone,
849 time_t *instance_start,
850 gboolean *start_is_date,
851 time_t *instance_end,
852 gboolean *end_is_date,
853 GCancellable *cancellable)
855 struct icaltimetype start_time, end_time;
856 const icaltimezone *zone = default_zone;
858 g_return_if_fail (E_IS_CAL_CLIENT (client));
859 g_return_if_fail (icalcomp != NULL);
860 g_return_if_fail (instance_start != NULL);
861 g_return_if_fail (instance_end != NULL);
863 start_time = icalcomponent_get_dtstart (icalcomp);
864 end_time = icalcomponent_get_dtend (icalcomp);
866 /* Some event can have missing DTEND, then use the start_time for them */
867 if (icaltime_is_null_time (end_time))
868 end_time = start_time;
870 if (start_time.zone) {
871 zone = start_time.zone;
872 } else {
873 icalparameter *param = NULL;
874 icalproperty *prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY);
876 if (prop) {
877 param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
879 if (param) {
880 const gchar *tzid = NULL;
881 icaltimezone *st_zone = NULL;
883 tzid = icalparameter_get_tzid (param);
884 if (tzid)
885 e_cal_client_get_timezone_sync (client, tzid, &st_zone, cancellable, NULL);
887 if (st_zone)
888 zone = st_zone;
893 *instance_start = icaltime_as_timet_with_zone (start_time, zone);
894 if (start_is_date)
895 *start_is_date = start_time.is_date;
897 if (end_time.zone) {
898 zone = end_time.zone;
899 } else {
900 icalparameter *param = NULL;
901 icalproperty *prop = icalcomponent_get_first_property (icalcomp, ICAL_DTSTART_PROPERTY);
903 if (prop) {
904 param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
906 if (param) {
907 const gchar *tzid = NULL;
908 icaltimezone *end_zone = NULL;
910 tzid = icalparameter_get_tzid (param);
911 if (tzid)
912 e_cal_client_get_timezone_sync (client, tzid, &end_zone, cancellable, NULL);
914 if (end_zone)
915 zone = end_zone;
920 *instance_end = icaltime_as_timet_with_zone (end_time, zone);
921 if (end_is_date)
922 *end_is_date = end_time.is_date;
925 time_t
926 cal_comp_gdate_to_timet (const GDate *date,
927 const icaltimezone *with_zone)
929 struct tm tm;
930 struct icaltimetype tt;
932 g_return_val_if_fail (date != NULL, (time_t) -1);
933 g_return_val_if_fail (g_date_valid (date), (time_t) -1);
935 g_date_to_struct_tm (date, &tm);
937 tt = tm_to_icaltimetype (&tm, TRUE);
938 if (with_zone)
939 return icaltime_as_timet_with_zone (tt, with_zone);
941 return icaltime_as_timet (tt);
944 typedef struct _AsyncContext {
945 ECalClient *src_client;
946 icalcomponent *icalcomp_clone;
947 gboolean do_copy;
948 } AsyncContext;
950 struct ForeachTzidData
952 ECalClient *source_client;
953 ECalClient *destination_client;
954 GCancellable *cancellable;
955 GError **error;
956 gboolean success;
959 static void
960 async_context_free (AsyncContext *async_context)
962 if (async_context->src_client)
963 g_object_unref (async_context->src_client);
965 if (async_context->icalcomp_clone)
966 icalcomponent_free (async_context->icalcomp_clone);
968 g_slice_free (AsyncContext, async_context);
971 static void
972 add_timezone_to_cal_cb (icalparameter *param,
973 gpointer data)
975 struct ForeachTzidData *ftd = data;
976 icaltimezone *tz = NULL;
977 const gchar *tzid;
979 g_return_if_fail (ftd != NULL);
980 g_return_if_fail (ftd->source_client != NULL);
981 g_return_if_fail (ftd->destination_client != NULL);
983 if (!ftd->success)
984 return;
986 if (ftd->cancellable && g_cancellable_is_cancelled (ftd->cancellable)) {
987 ftd->success = FALSE;
988 return;
991 tzid = icalparameter_get_tzid (param);
992 if (!tzid || !*tzid)
993 return;
995 if (e_cal_client_get_timezone_sync (ftd->source_client, tzid, &tz, ftd->cancellable, NULL) && tz)
996 ftd->success = e_cal_client_add_timezone_sync (
997 ftd->destination_client, tz, ftd->cancellable, ftd->error);
1000 /* Helper for cal_comp_transfer_item_to() */
1001 static void
1002 cal_comp_transfer_item_to_thread (GSimpleAsyncResult *simple,
1003 GObject *source_object,
1004 GCancellable *cancellable)
1006 AsyncContext *async_context;
1007 GError *local_error = NULL;
1009 async_context = g_simple_async_result_get_op_res_gpointer (simple);
1011 cal_comp_transfer_item_to_sync (
1012 async_context->src_client,
1013 E_CAL_CLIENT (source_object),
1014 async_context->icalcomp_clone,
1015 async_context->do_copy,
1016 cancellable, &local_error);
1018 if (local_error != NULL)
1019 g_simple_async_result_take_error (simple, local_error);
1022 void
1023 cal_comp_transfer_item_to (ECalClient *src_client,
1024 ECalClient *dest_client,
1025 icalcomponent *icalcomp_vcal,
1026 gboolean do_copy,
1027 GCancellable *cancellable,
1028 GAsyncReadyCallback callback,
1029 gpointer user_data)
1031 GSimpleAsyncResult *simple;
1032 AsyncContext *async_context;
1034 g_return_if_fail (E_IS_CAL_CLIENT (src_client));
1035 g_return_if_fail (E_IS_CAL_CLIENT (dest_client));
1036 g_return_if_fail (icalcomp_vcal != NULL);
1038 async_context = g_slice_new0 (AsyncContext);
1039 async_context->src_client = g_object_ref (src_client);
1040 async_context->icalcomp_clone = icalcomponent_new_clone (icalcomp_vcal);
1041 async_context->do_copy = do_copy;
1043 simple = g_simple_async_result_new (
1044 G_OBJECT (dest_client), callback, user_data,
1045 cal_comp_transfer_item_to);
1047 g_simple_async_result_set_check_cancellable (simple, cancellable);
1049 g_simple_async_result_set_op_res_gpointer (
1050 simple, async_context, (GDestroyNotify) async_context_free);
1052 g_simple_async_result_run_in_thread (
1053 simple, cal_comp_transfer_item_to_thread,
1054 G_PRIORITY_DEFAULT, cancellable);
1056 g_object_unref (simple);
1059 gboolean
1060 cal_comp_transfer_item_to_finish (ECalClient *client,
1061 GAsyncResult *result,
1062 GError **error)
1064 GSimpleAsyncResult *simple;
1066 g_return_val_if_fail (
1067 g_simple_async_result_is_valid (result, G_OBJECT (client), cal_comp_transfer_item_to),
1068 FALSE);
1070 simple = G_SIMPLE_ASYNC_RESULT (result);
1072 if (g_simple_async_result_propagate_error (simple, error))
1073 return FALSE;
1075 return TRUE;
1078 gboolean
1079 cal_comp_transfer_item_to_sync (ECalClient *src_client,
1080 ECalClient *dest_client,
1081 icalcomponent *icalcomp_vcal,
1082 gboolean do_copy,
1083 GCancellable *cancellable,
1084 GError **error)
1086 icalcomponent *icalcomp;
1087 icalcomponent *icalcomp_event, *subcomp;
1088 icalcomponent_kind icalcomp_kind;
1089 const gchar *uid;
1090 gchar *new_uid = NULL;
1091 struct ForeachTzidData ftd;
1092 ECalClientSourceType source_type;
1093 GHashTable *processed_uids;
1094 gboolean same_client;
1095 gboolean success = FALSE;
1097 g_return_val_if_fail (E_IS_CAL_CLIENT (src_client), FALSE);
1098 g_return_val_if_fail (E_IS_CAL_CLIENT (dest_client), FALSE);
1099 g_return_val_if_fail (icalcomp_vcal != NULL, FALSE);
1101 icalcomp_event = icalcomponent_get_inner (icalcomp_vcal);
1102 g_return_val_if_fail (icalcomp_event != NULL, FALSE);
1104 source_type = e_cal_client_get_source_type (src_client);
1105 switch (source_type) {
1106 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1107 icalcomp_kind = ICAL_VEVENT_COMPONENT;
1108 break;
1109 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1110 icalcomp_kind = ICAL_VTODO_COMPONENT;
1111 break;
1112 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1113 icalcomp_kind = ICAL_VJOURNAL_COMPONENT;
1114 break;
1115 default:
1116 g_return_val_if_reached (FALSE);
1119 same_client = src_client == dest_client || e_source_equal (
1120 e_client_get_source (E_CLIENT (src_client)), e_client_get_source (E_CLIENT (dest_client)));
1121 processed_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1123 icalcomp_event = icalcomponent_get_first_component (icalcomp_vcal, icalcomp_kind);
1125 * This check should be removed in the near future.
1126 * We should be able to work properly with multiselection, which means that we always
1127 * will receive a component with subcomponents.
1129 if (icalcomp_event == NULL)
1130 icalcomp_event = icalcomp_vcal;
1131 for (;
1132 icalcomp_event;
1133 icalcomp_event = icalcomponent_get_next_component (icalcomp_vcal, icalcomp_kind)) {
1134 GError *local_error = NULL;
1136 uid = icalcomponent_get_uid (icalcomp_event);
1138 if (g_hash_table_lookup (processed_uids, uid))
1139 continue;
1141 if (do_copy && same_client)
1142 success = FALSE;
1143 else
1144 success = e_cal_client_get_object_sync (dest_client, uid, NULL, &icalcomp, cancellable, &local_error);
1145 if (success) {
1146 success = e_cal_client_modify_object_sync (
1147 dest_client, icalcomp_event, E_CAL_OBJ_MOD_ALL, cancellable, error);
1149 icalcomponent_free (icalcomp);
1150 if (!success)
1151 goto exit;
1153 if (!do_copy) {
1154 ECalObjModType mod_type = E_CAL_OBJ_MOD_THIS;
1156 /* Remove the item from the source calendar. */
1157 if (e_cal_util_component_is_instance (icalcomp_event) ||
1158 e_cal_util_component_has_recurrences (icalcomp_event))
1159 mod_type = E_CAL_OBJ_MOD_ALL;
1161 success = e_cal_client_remove_object_sync (
1162 src_client, uid, NULL, mod_type, cancellable, error);
1163 if (!success)
1164 goto exit;
1167 continue;
1168 } else if (local_error != NULL && !g_error_matches (
1169 local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND)) {
1170 g_propagate_error (error, local_error);
1171 goto exit;
1172 } else {
1173 g_clear_error (&local_error);
1176 if (e_cal_util_component_is_instance (icalcomp_event)) {
1177 GSList *ecalcomps = NULL, *eiter;
1178 ECalComponent *comp;
1180 success = e_cal_client_get_objects_for_uid_sync (src_client, uid, &ecalcomps, cancellable, error);
1181 if (!success)
1182 goto exit;
1184 if (ecalcomps && !ecalcomps->next) {
1185 /* only one component, no need for a vCalendar list */
1186 comp = ecalcomps->data;
1187 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
1188 } else {
1189 icalcomp = icalcomponent_new (ICAL_VCALENDAR_COMPONENT);
1190 for (eiter = ecalcomps; eiter; eiter = g_slist_next (eiter)) {
1191 comp = eiter->data;
1193 icalcomponent_add_component (
1194 icalcomp,
1195 icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
1199 e_cal_client_free_ecalcomp_slist (ecalcomps);
1200 } else {
1201 icalcomp = icalcomponent_new_clone (icalcomp_event);
1204 if (do_copy) {
1205 /* Change the UID to avoid problems with duplicated UID */
1206 new_uid = e_util_generate_uid ();
1207 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
1208 /* in case of a vCalendar, the component might have detached instances,
1209 * thus change the UID on all of the subcomponents of it */
1210 for (subcomp = icalcomponent_get_first_component (icalcomp, icalcomp_kind);
1211 subcomp;
1212 subcomp = icalcomponent_get_next_component (icalcomp, icalcomp_kind)) {
1213 icalcomponent_set_uid (subcomp, new_uid);
1215 } else {
1216 icalcomponent_set_uid (icalcomp, new_uid);
1218 g_free (new_uid);
1219 new_uid = NULL;
1222 ftd.source_client = src_client;
1223 ftd.destination_client = dest_client;
1224 ftd.cancellable = cancellable;
1225 ftd.error = error;
1226 ftd.success = TRUE;
1228 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
1229 /* in case of a vCalendar, the component might have detached instances,
1230 * thus check timezones on all of the subcomponents of it */
1231 for (subcomp = icalcomponent_get_first_component (icalcomp, icalcomp_kind);
1232 subcomp && ftd.success;
1233 subcomp = icalcomponent_get_next_component (icalcomp, icalcomp_kind)) {
1234 icalcomponent_foreach_tzid (subcomp, add_timezone_to_cal_cb, &ftd);
1236 } else {
1237 icalcomponent_foreach_tzid (icalcomp, add_timezone_to_cal_cb, &ftd);
1240 if (!ftd.success) {
1241 success = FALSE;
1242 goto exit;
1245 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
1246 gboolean did_add = FALSE;
1248 /* in case of a vCalendar, the component might have detached instances,
1249 * thus add the master object first, and then all of the subcomponents of it */
1250 for (subcomp = icalcomponent_get_first_component (icalcomp, icalcomp_kind);
1251 subcomp && !did_add;
1252 subcomp = icalcomponent_get_next_component (icalcomp, icalcomp_kind)) {
1253 if (icaltime_is_null_time (icalcomponent_get_recurrenceid (subcomp))) {
1254 did_add = TRUE;
1255 success = e_cal_client_create_object_sync (
1256 dest_client, subcomp,
1257 &new_uid, cancellable, error);
1258 g_free (new_uid);
1262 if (!success) {
1263 icalcomponent_free (icalcomp);
1264 goto exit;
1267 /* deal with detached instances */
1268 for (subcomp = icalcomponent_get_first_component (icalcomp, icalcomp_kind);
1269 subcomp && success;
1270 subcomp = icalcomponent_get_next_component (icalcomp, icalcomp_kind)) {
1271 if (!icaltime_is_null_time (icalcomponent_get_recurrenceid (subcomp))) {
1272 if (did_add) {
1273 success = e_cal_client_modify_object_sync (
1274 dest_client, subcomp,
1275 E_CAL_OBJ_MOD_THIS, cancellable, error);
1276 } else {
1277 /* just in case there are only detached instances and no master object */
1278 did_add = TRUE;
1279 success = e_cal_client_create_object_sync (
1280 dest_client, subcomp,
1281 &new_uid, cancellable, error);
1282 g_free (new_uid);
1286 } else {
1287 success = e_cal_client_create_object_sync (dest_client, icalcomp, &new_uid, cancellable, error);
1288 g_free (new_uid);
1291 icalcomponent_free (icalcomp);
1292 if (!success)
1293 goto exit;
1295 if (!do_copy) {
1296 ECalObjModType mod_type = E_CAL_OBJ_MOD_THIS;
1298 /* Remove the item from the source calendar. */
1299 if (e_cal_util_component_is_instance (icalcomp_event) ||
1300 e_cal_util_component_has_recurrences (icalcomp_event))
1301 mod_type = E_CAL_OBJ_MOD_ALL;
1303 success = e_cal_client_remove_object_sync (src_client, uid, NULL, mod_type, cancellable, error);
1304 if (!success)
1305 goto exit;
1308 g_hash_table_insert (processed_uids, g_strdup (uid), GINT_TO_POINTER (1));
1311 exit:
1312 g_hash_table_destroy (processed_uids);
1314 return success;
1317 void
1318 cal_comp_util_update_tzid_parameter (icalproperty *prop,
1319 const struct icaltimetype tt)
1321 icalparameter *param;
1322 const gchar *tzid = NULL;
1324 g_return_if_fail (prop != NULL);
1326 if (!icaltime_is_valid_time (tt) ||
1327 icaltime_is_null_time (tt))
1328 return;
1330 param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
1331 if (tt.zone)
1332 tzid = icaltimezone_get_tzid ((icaltimezone *) tt.zone);
1334 if (tt.zone && tzid && *tzid && !icaltime_is_utc (tt) && !tt.is_date) {
1335 if (param) {
1336 icalparameter_set_tzid (param, (gchar *) tzid);
1337 } else {
1338 param = icalparameter_new_tzid ((gchar *) tzid);
1339 icalproperty_add_parameter (prop, param);
1341 } else if (param) {
1342 icalproperty_remove_parameter_by_kind (prop, ICAL_TZID_PARAMETER);
1346 /* Returns <0 for time before today, 0 for today, >0 for after today (future) */
1347 gint
1348 cal_comp_util_compare_time_with_today (const struct icaltimetype time_tt)
1350 struct icaltimetype now_tt;
1352 if (icaltime_is_null_time (time_tt))
1353 return 0;
1355 if (time_tt.is_date) {
1356 now_tt = icaltime_today ();
1357 return icaltime_compare_date_only (time_tt, now_tt);
1358 } else {
1359 now_tt = icaltime_current_time_with_zone (time_tt.zone);
1360 now_tt.zone = time_tt.zone;
1363 return icaltime_compare (time_tt, now_tt);
1366 /* Returns whether removed any */
1367 gboolean
1368 cal_comp_util_remove_all_properties (icalcomponent *component,
1369 icalproperty_kind kind)
1371 icalproperty *prop;
1372 gboolean removed_any = FALSE;
1374 g_return_val_if_fail (component != NULL, FALSE);
1376 while (prop = icalcomponent_get_first_property (component, kind), prop) {
1377 icalcomponent_remove_property (component, prop);
1378 icalproperty_free (prop);
1380 removed_any = TRUE;
1383 return removed_any;
1386 gboolean
1387 cal_comp_util_have_in_new_attendees (const GSList *new_attendees_mails,
1388 const gchar *eml)
1390 const GSList *link;
1392 if (!eml)
1393 return FALSE;
1395 for (link = new_attendees_mails; link; link = g_slist_next (link)) {
1396 if (link->data && g_ascii_strcasecmp (eml, link->data) == 0)
1397 return TRUE;
1400 return FALSE;
1403 static void
1404 free_slist_strs (gpointer data)
1406 GSList *lst = data;
1408 if (lst) {
1409 g_slist_foreach (lst, (GFunc) g_free, NULL);
1410 g_slist_free (lst);
1415 * cal_comp_util_copy_new_attendees:
1416 * @des: Component, to copy to.
1417 * @src: Component, to copy from.
1419 * Copies "new-attendees" information from @src to @des component.
1421 void
1422 cal_comp_util_copy_new_attendees (ECalComponent *des,
1423 ECalComponent *src)
1425 GSList *copy = NULL, *l;
1427 g_return_if_fail (src != NULL);
1428 g_return_if_fail (des != NULL);
1430 for (l = g_object_get_data (G_OBJECT (src), "new-attendees"); l; l = l->next) {
1431 copy = g_slist_append (copy, g_strdup (l->data));
1434 g_object_set_data_full (G_OBJECT (des), "new-attendees", copy, free_slist_strs);
1437 /* Takes ownership of the 'emails' */
1438 void
1439 cal_comp_util_set_added_attendees_mails (ECalComponent *comp,
1440 GSList *emails)
1442 g_return_if_fail (E_IS_CAL_COMPONENT (comp));
1444 g_object_set_data_full (G_OBJECT (comp), "new-attendees", emails, free_slist_strs);
1447 const gchar *
1448 cal_comp_util_find_parameter_xvalue (icalproperty *prop,
1449 const gchar *name)
1451 icalparameter *param;
1453 if (!prop || !name || !*name)
1454 return NULL;
1456 for (param = icalproperty_get_first_parameter (prop, ICAL_X_PARAMETER);
1457 param;
1458 param = icalproperty_get_next_parameter (prop, ICAL_X_PARAMETER)) {
1459 const gchar *xname = icalparameter_get_xname (param);
1461 if (xname && g_ascii_strcasecmp (xname, name) == 0)
1462 return icalparameter_get_xvalue (param);
1465 return NULL;
1468 gchar *
1469 cal_comp_util_get_attendee_comments (icalcomponent *icalcomp)
1471 GString *comments = NULL;
1472 icalproperty *prop;
1474 g_return_val_if_fail (icalcomp != NULL, NULL);
1476 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY);
1477 prop != NULL;
1478 prop = icalcomponent_get_next_property (icalcomp, ICAL_ATTENDEE_PROPERTY)) {
1479 gchar *guests_str = NULL;
1480 guint32 num_guests = 0;
1481 const gchar *value;
1483 value = cal_comp_util_find_parameter_xvalue (prop, "X-NUM-GUESTS");
1484 if (value && *value)
1485 num_guests = atoi (value);
1487 value = cal_comp_util_find_parameter_xvalue (prop, "X-RESPONSE-COMMENT");
1489 if (num_guests)
1490 guests_str = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "with one guest", "with %d guests", num_guests), num_guests);
1492 if (guests_str || (value && *value)) {
1493 const gchar *email = icalproperty_get_attendee (prop);
1494 const gchar *cn = NULL;
1495 icalparameter *cnparam;
1497 cnparam = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER);
1498 if (cnparam) {
1499 cn = icalparameter_get_cn (cnparam);
1500 if (!cn || !*cn)
1501 cn = NULL;
1504 email = itip_strip_mailto (email);
1506 if ((email && *email) || (cn && *cn)) {
1507 if (!comments)
1508 comments = g_string_new ("");
1509 else
1510 g_string_append (comments, "\n ");
1512 if (cn && *cn) {
1513 g_string_append (comments, cn);
1515 if (g_strcmp0 (email, cn) == 0)
1516 email = NULL;
1519 if (email && *email) {
1520 if (cn && *cn)
1521 g_string_append_printf (comments, " <%s>", email);
1522 else
1523 g_string_append (comments, email);
1526 g_string_append (comments, ": ");
1528 if (guests_str) {
1529 g_string_append (comments, guests_str);
1531 if (value && *value)
1532 g_string_append (comments, "; ");
1535 if (value && *value)
1536 g_string_append (comments, value);
1540 g_free (guests_str);
1543 if (comments) {
1544 gchar *str;
1546 str = g_strdup_printf (_("Comments: %s"), comments->str);
1547 g_string_free (comments, TRUE);
1549 return str;
1552 return NULL;