diff options
author | Christian Hilberg <chilberg@src.gnome.org> | 2012-11-26 19:15:58 +0800 |
---|---|---|
committer | Milan Crha <mcrha@redhat.com> | 2012-11-26 19:15:58 +0800 |
commit | 51ca0952f4b694f25cdfe774dfd8498c916101a7 (patch) | |
tree | 67c3255316274f060676dd179372a1514525a213 | |
parent | 629a2d6a76cc7ae225706f83bd38b353b82239da (diff) | |
download | gsoc2013-evolution-51ca0952f4b694f25cdfe774dfd8498c916101a7.tar.gz gsoc2013-evolution-51ca0952f4b694f25cdfe774dfd8498c916101a7.tar.zst gsoc2013-evolution-51ca0952f4b694f25cdfe774dfd8498c916101a7.zip |
Bug #687974 - No displaying of extended free/busy (XFB) information
-rw-r--r-- | calendar/gui/e-meeting-attendee.c | 26 | ||||
-rw-r--r-- | calendar/gui/e-meeting-attendee.h | 4 | ||||
-rw-r--r-- | calendar/gui/e-meeting-store.c | 67 | ||||
-rw-r--r-- | calendar/gui/e-meeting-time-sel.c | 144 | ||||
-rw-r--r-- | calendar/gui/e-meeting-types.h | 24 | ||||
-rw-r--r-- | calendar/gui/e-meeting-utils.c | 122 | ||||
-rw-r--r-- | calendar/gui/e-meeting-utils.h | 14 |
7 files changed, 385 insertions, 16 deletions
diff --git a/calendar/gui/e-meeting-attendee.c b/calendar/gui/e-meeting-attendee.c index 1059537b84..3d27f676c0 100644 --- a/calendar/gui/e-meeting-attendee.c +++ b/calendar/gui/e-meeting-attendee.c @@ -26,6 +26,7 @@ #include <stdlib.h> #include <gtk/gtk.h> +#include "e-meeting-utils.h" #include "e-meeting-attendee.h" #define E_MEETING_ATTENDEE_GET_PRIVATE(obj) \ @@ -92,6 +93,20 @@ string_is_set (gchar *string) } static void +busy_periods_array_clear_func (gpointer data) +{ + EMeetingFreeBusyPeriod *period = (EMeetingFreeBusyPeriod *) data; + + /* We're expected to clear the data segment, + * but not deallocate the segment itself. The + * XFB data possibly attached to the + * EMeetingFreeBusyPeriod requires special + * care when removing elements from the GArray + */ + e_meeting_xfb_data_clear (&(period->xfb)); +} + +static void notify_changed (EMeetingAttendee *ia) { g_signal_emit_by_name (ia, "changed"); @@ -167,6 +182,7 @@ e_meeting_attendee_init (EMeetingAttendee *ia) ia->priv->has_calendar_info = FALSE; ia->priv->busy_periods = g_array_new (FALSE, FALSE, sizeof (EMeetingFreeBusyPeriod)); + g_array_set_clear_func (ia->priv->busy_periods, busy_periods_array_clear_func); ia->priv->busy_periods_sorted = FALSE; g_date_clear (&ia->priv->busy_periods_start.date, 1); @@ -816,7 +832,9 @@ e_meeting_attendee_add_busy_period (EMeetingAttendee *ia, gint end_day, gint end_hour, gint end_minute, - EMeetingFreeBusyType busy_type) + EMeetingFreeBusyType busy_type, + const gchar *summary, + const gchar *location) { EMeetingAttendeePrivate *priv; EMeetingFreeBusyPeriod period; @@ -825,6 +843,8 @@ e_meeting_attendee_add_busy_period (EMeetingAttendee *ia, g_return_val_if_fail (ia != NULL, FALSE); g_return_val_if_fail (E_IS_MEETING_ATTENDEE (ia), FALSE); g_return_val_if_fail (busy_type < E_MEETING_FREE_BUSY_LAST, FALSE); + /* summary may be NULL (optional XFB data) */ + /* location may be NULL (optional XFB data) */ priv = ia->priv; @@ -925,6 +945,10 @@ e_meeting_attendee_add_busy_period (EMeetingAttendee *ia, } } + /* Setting of extended free/busy (XFB) data, if we have any. */ + e_meeting_xfb_data_init (&(period.xfb)); + e_meeting_xfb_data_set (&(period.xfb), summary, location); + g_array_append_val (priv->busy_periods, period); period_in_days = diff --git a/calendar/gui/e-meeting-attendee.h b/calendar/gui/e-meeting-attendee.h index 27e3f3518b..12ab65826a 100644 --- a/calendar/gui/e-meeting-attendee.h +++ b/calendar/gui/e-meeting-attendee.h @@ -142,7 +142,9 @@ gboolean e_meeting_attendee_add_busy_period (EMeetingAttendee *ia, gint end_day, gint end_hour, gint end_minute, - EMeetingFreeBusyType busy_type); + EMeetingFreeBusyType busy_type, + const gchar *summary, + const gchar *location); EMeetingTime e_meeting_attendee_get_start_busy_range (EMeetingAttendee *ia); EMeetingTime e_meeting_attendee_get_end_busy_range (EMeetingAttendee *ia); diff --git a/calendar/gui/e-meeting-store.c b/calendar/gui/e-meeting-store.c index d215b2decc..b8e065e2dd 100644 --- a/calendar/gui/e-meeting-store.c +++ b/calendar/gui/e-meeting-store.c @@ -1340,6 +1340,31 @@ process_callbacks (EMeetingStoreQueueData *qdata) } static void +process_free_busy_comp_get_xfb (icalproperty *ip, + gchar **summary, + gchar **location) +{ + const gchar *tmp = NULL; + + g_return_if_fail (ip != NULL); + g_return_if_fail (summary != NULL && *summary == NULL); + g_return_if_fail (location != NULL && *location == NULL); + + /* We extract extended free/busy information from the icalproperty + * here (X-SUMMARY and X-LOCATION). If the property carries such, + * it will be displayed as a tooltip for the busy period. Otherwise, + * nothing will happen (*summary and/or *location will be NULL) + */ + + tmp = icalproperty_get_parameter_as_string (ip, E_MEETING_FREE_BUSY_XPROP_SUMMARY); + *summary = e_meeting_xfb_utf8_string_new_from_ical (tmp, + E_MEETING_FREE_BUSY_XPROP_MAXLEN); + tmp = icalproperty_get_parameter_as_string (ip, E_MEETING_FREE_BUSY_XPROP_LOCATION); + *location = e_meeting_xfb_utf8_string_new_from_ical (tmp, + E_MEETING_FREE_BUSY_XPROP_MAXLEN); +} + +static void process_free_busy_comp (EMeetingAttendee *attendee, icalcomponent *fb_comp, icaltimezone *zone, @@ -1422,22 +1447,38 @@ process_free_busy_comp (EMeetingAttendee *attendee, if (busy_type != E_MEETING_FREE_BUSY_LAST) { icaltimezone *utc_zone = icaltimezone_get_utc_timezone (); + gchar *summary = NULL; + gchar *location = NULL; icaltimezone_convert_time (&fb.start, utc_zone, zone); icaltimezone_convert_time (&fb.end, utc_zone, zone); - e_meeting_attendee_add_busy_period ( - attendee, - fb.start.year, - fb.start.month, - fb.start.day, - fb.start.hour, - fb.start.minute, - fb.end.year, - fb.end.month, - fb.end.day, - fb.end.hour, - fb.end.minute, - busy_type); + + /* Extract extended free/busy (XFB) information from + * the icalproperty, if it carries such. + * See the comment for the EMeetingXfbData structure + * for a reference. + */ + process_free_busy_comp_get_xfb (ip, &summary, &location); + + e_meeting_attendee_add_busy_period (attendee, + fb.start.year, + fb.start.month, + fb.start.day, + fb.start.hour, + fb.start.minute, + fb.end.year, + fb.end.month, + fb.end.day, + fb.end.hour, + fb.end.minute, + busy_type, + summary, + location); + + if (summary != NULL) + g_free (summary); + if (location != NULL) + g_free (location); } ip = icalcomponent_get_next_property (fb_comp, ICAL_FREEBUSY_PROPERTY); diff --git a/calendar/gui/e-meeting-time-sel.c b/calendar/gui/e-meeting-time-sel.c index 69d9e27ffb..3747a7be1d 100644 --- a/calendar/gui/e-meeting-time-sel.c +++ b/calendar/gui/e-meeting-time-sel.c @@ -196,6 +196,12 @@ static void e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector * static void e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts); static void e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts); static gboolean e_meeting_time_selector_on_canvas_scroll_event (GtkWidget *widget, GdkEventScroll *event, EMeetingTimeSelector *mts); +static gboolean e_meeting_time_selector_on_canvas_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data); static void row_inserted_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data); static void row_changed_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data); @@ -522,6 +528,15 @@ e_meeting_time_selector_construct (EMeetingTimeSelector *mts, g_signal_connect ( mts->display_main, "scroll-event", G_CALLBACK (e_meeting_time_selector_on_canvas_scroll_event), mts); + /* used for displaying extended free/busy (XFB) display when hovering + * over a busy period which carries XFB information */ + g_signal_connect (mts->display_main, + "query-tooltip", + G_CALLBACK (e_meeting_time_selector_on_canvas_query_tooltip), + mts); + g_object_set (G_OBJECT (mts->display_main), + "has-tooltip", TRUE, + NULL); scrollable = GTK_SCROLLABLE (mts->display_main); @@ -2660,6 +2675,135 @@ e_meeting_time_selector_on_canvas_scroll_event (GtkWidget *widget, return return_val; } +/* Sets a tooltip for the busy periods canvas. If the mouse pointer + * hovers over a busy period for which extended free/busy (XFB) data + * could be extracted from the vfreebusy calendar object, the tooltip + * will be shown (currently displays the summary and the location of + * for the busy period, if available). See EMeetingXfbData for a reference. + * + */ +static gboolean +e_meeting_time_selector_on_canvas_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data) +{ + EMeetingTimeSelector *mts = NULL; + EMeetingAttendee *attendee = NULL; + EMeetingFreeBusyPeriod *period = NULL; + EMeetingXfbData *xfb = NULL; + GtkScrollable *scrollable = NULL; + GtkAdjustment *adjustment = NULL; + const GArray *periods = NULL; + gint scroll_x = 0; + gint scroll_y = 0; + gint mouse_x = 0; + gint row = 0; + gint first_idx = 0; + gint ii = 0; + gchar *tt_text = NULL; + + g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (GTK_IS_TOOLTIP (tooltip), FALSE); + g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (user_data), FALSE); + + mts = E_MEETING_TIME_SELECTOR (user_data); + + scrollable = GTK_SCROLLABLE (widget); + adjustment = gtk_scrollable_get_hadjustment (scrollable); + scroll_x = (gint) gtk_adjustment_get_value (adjustment); + adjustment = gtk_scrollable_get_vadjustment (scrollable); + scroll_y = (gint) gtk_adjustment_get_value (adjustment); + + /* calculate the attendee index (row) we're at */ + row = (scroll_y + y) / mts->row_height; + + /* no tooltip if we have no attendee in the row */ + if (row > e_meeting_store_count_actual_attendees (mts->model) - 1) + return FALSE; + + /* no tooltip if attendee has no calendar info */ + attendee = e_meeting_store_find_attendee_at_row (mts->model, row); + g_return_val_if_fail (E_IS_MEETING_ATTENDEE (attendee), FALSE); + if (!e_meeting_attendee_get_has_calendar_info (attendee)) + return FALSE; + + /* get the attendee's busy times array */ + periods = e_meeting_attendee_get_busy_periods (attendee); + g_return_val_if_fail (periods != NULL, FALSE); + g_return_val_if_fail (periods->len > 0, FALSE); + + /* no tooltip if no busy period reaches into the current canvas area */ + first_idx = e_meeting_attendee_find_first_busy_period (attendee, + &(mts->first_date_shown)); + if (first_idx < 0) + return FALSE; + + /* calculate the mouse tip x position in the canvas area */ + mouse_x = x + scroll_x; + + /* find out whether mouse_x lies inside a busy + * period (we start with the index of the first + * one reaching into the current canvas area) + */ + for (ii = first_idx; ii < periods->len; ii++) { + EMeetingFreeBusyPeriod *p = NULL; + gint sx = 0; + gint ex = 0; + + p = &(g_array_index (periods, + EMeetingFreeBusyPeriod, + ii)); + /* meeting start time x position */ + sx = e_meeting_time_selector_calculate_time_position (mts, + &(p->start)); + /* meeting end time x position */ + ex = e_meeting_time_selector_calculate_time_position (mts, + &(p->end)); + if ((mouse_x >= sx) && (mouse_x <= ex)) { + /* found busy period the mouse tip is over */ + period = p; + break; + } + } + + /* no tooltip if we did not find a busy period under + * the mouse pointer + */ + if (period == NULL) + return FALSE; + + /* get the extended free/busy data + * (no tooltip if none available) + */ + xfb = &(period->xfb); + if ((xfb->summary == NULL) && (xfb->location == NULL)) + return FALSE; + + /* Create the tooltip text. The data sent by the server will + * have been validated for UTF-8 conformance (and possibly + * forced into) as well as length-limited by a call to the + * e_meeting_xfb_utf8_string_new_from_ical() function in + * process_free_busy_comp_get_xfb() (e-meeting-store.c) + */ + if (xfb->summary && xfb->location) + tt_text = g_strdup_printf (_("Summary: %s\nLocation: %s"), xfb->summary, xfb->location); + else if (xfb->summary) + tt_text = g_strdup_printf (_("Summary: %s"), xfb->summary); + else if (xfb->location) + tt_text = g_strdup_printf (_("Location: %s"), xfb->location); + else + g_return_val_if_reached (FALSE); + + /* set XFB information as tooltip text */ + gtk_tooltip_set_text (tooltip, tt_text); + g_free (tt_text); + + return TRUE; +} + /* This updates the canvas scroll regions according to the number of attendees. * If the total height needed is less than the height of the canvas, we must * use the height of the canvas, or it causes problems. */ diff --git a/calendar/gui/e-meeting-types.h b/calendar/gui/e-meeting-types.h index b8f1057962..689fe9cd30 100644 --- a/calendar/gui/e-meeting-types.h +++ b/calendar/gui/e-meeting-types.h @@ -26,10 +26,17 @@ #include <glib.h> +/* Extended free/busy (XFB) vfreebusy properties */ +#define E_MEETING_FREE_BUSY_XPROP_SUMMARY "X-SUMMARY" +#define E_MEETING_FREE_BUSY_XPROP_LOCATION "X-LOCATION" +/* Maximum string length displayed in the XFB tooltip */ +#define E_MEETING_FREE_BUSY_XPROP_MAXLEN 200 + G_BEGIN_DECLS typedef struct _EMeetingTime EMeetingTime; typedef struct _EMeetingFreeBusyPeriod EMeetingFreeBusyPeriod; +typedef struct _EMeetingXfbData EMeetingXfbData; /* These are used to specify whether an attendee is free or busy at a * particular time. We'll probably replace this with a global calendar type. @@ -55,12 +62,29 @@ struct _EMeetingTime guint8 minute; }; +/* This represents extended free/busy data (XFB) associated + * with a busy period (optional). Groupware servers like Kolab + * may send it as X-SUMMARY and X-LOCATION properties of vfreebusy + * calendar objects. + * See http://wiki.kolab.org/Free_Busy#Kolab_Object_Storage_Format + * for a reference. If we find that a vfreebusy object carries + * such information, we extract it and display it as a tooltip + * for the busy period in the meeting time selector scheduling page. + */ +struct _EMeetingXfbData +{ + /* if adding more items, adapt e_meeting_xfb_data_clear() */ + gchar *summary; + gchar *location; +}; + /* This represents a busy period. */ struct _EMeetingFreeBusyPeriod { EMeetingTime start; EMeetingTime end; EMeetingFreeBusyType busy_type; + EMeetingXfbData xfb; }; G_END_DECLS diff --git a/calendar/gui/e-meeting-utils.c b/calendar/gui/e-meeting-utils.c index 89149e77e9..2bab129b1f 100644 --- a/calendar/gui/e-meeting-utils.c +++ b/calendar/gui/e-meeting-utils.c @@ -25,6 +25,9 @@ #include <config.h> #endif +#include <string.h> +#include <libedataserver/libedataserver.h> + #include "e-meeting-utils.h" gint @@ -51,3 +54,122 @@ e_meeting_time_compare_times (EMeetingTime *time1, /* The start times are exactly the same. */ return 0; } + +void +e_meeting_xfb_data_init (EMeetingXfbData *xfb) +{ + g_return_if_fail (xfb != NULL); + + xfb->summary = NULL; + xfb->location = NULL; +} + +void +e_meeting_xfb_data_set (EMeetingXfbData *xfb, + const gchar *summary, + const gchar *location) +{ + g_return_if_fail (xfb != NULL); + + e_meeting_xfb_data_clear (xfb); + xfb->summary = g_strdup (summary); + xfb->location = g_strdup (location); +} + +void +e_meeting_xfb_data_clear (EMeetingXfbData *xfb) +{ + g_return_if_fail (xfb != NULL); + + /* clearing the contents of xfb, + * but not the xfb structure itself + */ + + if (xfb->summary != NULL) { + g_free (xfb->summary); + xfb->summary = NULL; + } + if (xfb->location != NULL) { + g_free (xfb->location); + xfb->location = NULL; + } +} + +/* Creates an XFB string from a string property of a vfreebusy + * icalproperty. The ical string we read may be base64 encoded, but + * we get no reliable indication whether it really is. So we + * try to base64-decode, and failing that, assume the string + * is plain. The result is validated for UTF-8. We try to convert + * to UTF-8 from locale if the input is no valid UTF-8, and failing + * that, force the result into valid UTF-8. We also limit the + * length of the resulting string, since it gets displayed as a + * tooltip text in the meeting time selector. + */ +gchar* +e_meeting_xfb_utf8_string_new_from_ical (const gchar *icalstring, + gsize max_len) +{ + gchar *tmp = NULL; + gchar *utf8s = NULL; + gsize in_len = 0; + gsize out_len = 0; + GError *tmp_err = NULL; + + g_return_val_if_fail (max_len > 4, NULL); + + if (icalstring == NULL) + return NULL; + + /* ical does not carry charset hints, so we + * try UTF-8 first, then conversion using + * system locale info. + */ + + /* if we have valid UTF-8, we're done converting */ + if (g_utf8_validate (icalstring, -1, NULL)) + goto valid; + + /* no valid UTF-8, trying to convert to it + * according to system locale + */ + tmp = g_locale_to_utf8 (icalstring, + -1, + &in_len, + &out_len, + &tmp_err); + + if (tmp_err == NULL) + goto valid; + + g_warning ("%s: %s", G_STRFUNC, tmp_err->message); + g_error_free (tmp_err); + g_free (tmp); + + /* still no success, forcing it into UTF-8, using + * replacement chars to replace invalid ones + */ + tmp = e_util_utf8_data_make_valid (icalstring, + strlen (icalstring)); + valid: + if (tmp == NULL) + tmp = g_strdup (icalstring); + + /* now that we're (forcibly) valid UTF-8, we can + * limit the size of the UTF-8 string for display + */ + + if (g_utf8_strlen (tmp, -1) > (glong) max_len) { + /* insert NULL termination to where we want to + * clip, take care to hit UTF-8 character boundary + */ + utf8s = g_utf8_offset_to_pointer (tmp, (glong) max_len - 4); + *utf8s = '\0'; + /* create shortened UTF-8 string */ + utf8s = g_strdup_printf ("%s ...", tmp); + g_free (tmp); + } else { + utf8s = tmp; + } + + return utf8s; +} diff --git a/calendar/gui/e-meeting-utils.h b/calendar/gui/e-meeting-utils.h index 7c275b19d0..0de391d815 100644 --- a/calendar/gui/e-meeting-utils.h +++ b/calendar/gui/e-meeting-utils.h @@ -33,7 +33,19 @@ G_BEGIN_DECLS gint e_meeting_time_compare_times (EMeetingTime *time1, EMeetingTime *time2); +/* Extended free/busy (XFB) helpers */ + +void e_meeting_xfb_data_init (EMeetingXfbData *xfb); + +void e_meeting_xfb_data_set (EMeetingXfbData *xfb, + const gchar *summary, + const gchar *location); + +void e_meeting_xfb_data_clear (EMeetingXfbData *xfb); + +gchar * e_meeting_xfb_utf8_string_new_from_ical (const gchar *icalstring, + gsize max_len); + G_END_DECLS #endif /* _E_MEETING_UTILS_H_ */ - |