[evolution/gnome-3-6] Bug #687974 - No displaying of extended free/busy (XFB) information



commit 014787726484baff39dca4f8a3c5d71b72dcee9a
Author: Christian Hilberg <chilberg src gnome org>
Date:   Mon Nov 26 12:16:42 2012 +0100

    Bug #687974 - No displaying of extended free/busy (XFB) information

 calendar/gui/e-meeting-attendee.c |   26 +++++++-
 calendar/gui/e-meeting-attendee.h |    4 +-
 calendar/gui/e-meeting-store.c    |   67 ++++++++++++++----
 calendar/gui/e-meeting-time-sel.c |  139 +++++++++++++++++++++++++++++++++++++
 calendar/gui/e-meeting-types.h    |   24 +++++++
 calendar/gui/e-meeting-utils.c    |  122 ++++++++++++++++++++++++++++++++
 calendar/gui/e-meeting-utils.h    |   14 ++++-
 7 files changed, 380 insertions(+), 16 deletions(-)
---
diff --git a/calendar/gui/e-meeting-attendee.c b/calendar/gui/e-meeting-attendee.c
index 1059537..3d27f67 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 27e3f35..12ab658 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 b7ab18d..f7bb717 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 69d9e27..066af3f 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,130 @@ 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)
+	 */
+	tt_text = g_strdup_printf ("%s\n\n%s",
+	                           (xfb->summary == NULL) ? "" : xfb->summary,
+	                           (xfb->location == NULL) ? "" : xfb->location);
+
+	/* 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 b8f1057..689fe9c 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 89149e7..2bab129 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 7c275b1..0de391d 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_ */
-



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]