aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Hilberg <chilberg@src.gnome.org>2012-11-26 19:15:58 +0800
committerMilan Crha <mcrha@redhat.com>2012-11-26 19:15:58 +0800
commit51ca0952f4b694f25cdfe774dfd8498c916101a7 (patch)
tree67c3255316274f060676dd179372a1514525a213
parent629a2d6a76cc7ae225706f83bd38b353b82239da (diff)
downloadgsoc2013-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.c26
-rw-r--r--calendar/gui/e-meeting-attendee.h4
-rw-r--r--calendar/gui/e-meeting-store.c67
-rw-r--r--calendar/gui/e-meeting-time-sel.c144
-rw-r--r--calendar/gui/e-meeting-types.h24
-rw-r--r--calendar/gui/e-meeting-utils.c122
-rw-r--r--calendar/gui/e-meeting-utils.h14
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_ */
-