[california/wip/731543-attendees] Parse and display organizers in Show Event
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/731543-attendees] Parse and display organizers in Show Event
- Date: Thu, 6 Nov 2014 00:35:49 +0000 (UTC)
commit 643d089ad435a04a213544842927d0977dbabb7d
Author: Jim Nelson <jim yorba org>
Date: Wed Nov 5 16:23:44 2014 -0800
Parse and display organizers in Show Event
src/backing/backing-source.vala | 8 ++-
src/backing/eds/backing-eds-calendar-source.vala | 14 +++
src/collection/collection-iterable.vala | 12 +++
src/component/component-instance.vala | 104 ++++++++++++++++------
src/component/component-person.vala | 20 ++++-
src/host/host-show-event.vala | 36 ++++++--
src/rc/show-event.ui | 49 ++++++++--
7 files changed, 196 insertions(+), 47 deletions(-)
---
diff --git a/src/backing/backing-source.vala b/src/backing/backing-source.vala
index 26a889f..9269e0e 100644
--- a/src/backing/backing-source.vala
+++ b/src/backing/backing-source.vala
@@ -22,6 +22,7 @@ public abstract class Source : BaseObject, Gee.Comparable<Source> {
public const string PROP_VISIBLE = "visible";
public const string PROP_READONLY = "read-only";
public const string PROP_COLOR = "color";
+ public const string PROP_OWNER_MAILBOX = "owner-mailbox";
/**
* A unique identifier for the { link Source}.
@@ -52,6 +53,11 @@ public abstract class Source : BaseObject, Gee.Comparable<Source> {
public string title { get; set; }
/**
+ * The owner of this calendar's mailbox (email address), i.e. "bob example com".
+ */
+ public string? owner_mailbox { get; protected set; default = null; }
+
+ /**
* Whether the { link Source} should be visible to the user.
*
* The caller should monitor this setting to decide whether or not to display the Source's
@@ -151,7 +157,7 @@ public abstract class Source : BaseObject, Gee.Comparable<Source> {
}
public override string to_string() {
- return title;
+ return !String.is_empty(owner_mailbox) ? "%s (%s)".printf(title, owner_mailbox) : title;
}
}
diff --git a/src/backing/eds/backing-eds-calendar-source.vala
b/src/backing/eds/backing-eds-calendar-source.vala
index d3219fb..9f6407f 100644
--- a/src/backing/eds/backing-eds-calendar-source.vala
+++ b/src/backing/eds/backing-eds-calendar-source.vala
@@ -171,6 +171,20 @@ internal class EdsCalendarSource : CalendarSource {
client.notify["readonly"].connect(() => {
debug("%s readonly: %s", to_string(), client.readonly.to_string());
});
+
+ // TODO: This does not work for some reason, although it appears to work in Evolution ...
+ // this will be important for determining if the ORGANIZER is the calendar owner, i.e. user
+ try {
+ string prop_value;
+ if (yield client.get_backend_property("cal-email-address", cancellable, out prop_value))
+ owner_mailbox = !String.is_empty(prop_value) ? prop_value : null;
+ else
+ debug("Unable to fetch %s email address", to_string());
+ } catch (Error err) {
+ client = null;
+
+ throw err;
+ }
}
// Invoked by EdsStore when closing and dropping all its refs
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index b5695bb..9a67a6d 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -183,6 +183,18 @@ public class Iterable<G> : Object {
return new Iterable<G>(list.iterator());
}
+ /**
+ * Sorts the elements of the { link Iterable} so that the next iteration they are in the
+ * comparator's order.
+ */
+ public Iterable<G> sort(owned Gee.EqualDataFunc<G>? equal_func = null,
+ owned CompareDataFunc<G>? compare_func = null) {
+ Gee.ArrayList<G> sorted = to_array_list(equal_func);
+ sorted.sort(compare_func);
+
+ return new Iterable<G>(sorted.iterator());
+ }
+
public Iterable<A> map<A>(Gee.MapFunc<A, G> f) {
return new Iterable<A>(i.map<A>(f));
}
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 0a12044..2bd0f30 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -39,6 +39,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
public const string PROP_RDATES = "rdates";
public const string PROP_SEQUENCE = "sequence";
public const string PROP_MASTER = "master";
+ public const string PROP_ORGANIZERS = "organizers";
public const string PROP_ATTENDEES = "attendees";
protected const string PROP_IN_FULL_UPDATE = "in-full-update";
@@ -172,6 +173,19 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
public int sequence { get; set; default = 0; }
/**
+ * ORGANIZERs of a VEVENT, VTODO, or VJOURNAL.
+ *
+ * To add or remove organizers, use { link add_organizers}, { link remove_organizers}, and
+ * { link clear_organizers}. Because those methods always update the property itself and not
+ * merely modify the list, the property can be watched for changes with the "notify" and/or
+ * "altered" signals.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.8.4.3]] In particular, note that the
+ * { link organizer} must be specified in group-scheduled calendar entity.
+ */
+ public Gee.Set<Person> organizers { get; private set; default = new Gee.HashSet<Person>(); }
+
+ /**
* ATTENDEEs for a VEVENT, VTODO, or VJOURNAL.
*
* To add or remove attendees, use { link add_attendees}, { link remove_attendees}, and
@@ -179,8 +193,6 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
* merely modify the list, the property can be watched for changes with the "notify" and/or
* "altered" signals.
*
- * No validity checking is performed on attendee mailto's.
- *
* See [[https://tools.ietf.org/html/rfc5545#section-3.8.4.1]]
*/
public Gee.Set<Person> attendees { get; private set; default = new Gee.HashSet<Person>(); }
@@ -367,24 +379,30 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
exdates = get_multiple_date_times(iCal.icalproperty_kind.EXDATE_PROPERTY);
rdates = get_multiple_date_times(iCal.icalproperty_kind.RDATE_PROPERTY);
- // Currently only storing mailto's for attendees for now
- unowned iCal.icalproperty? attendee_prop = ical_component.get_first_property(
- iCal.icalproperty_kind.ATTENDEE_PROPERTY);
- while (attendee_prop != null) {
- try {
- attendees.add(new Person.from_property(attendee_prop));
- } catch (ComponentError comperr) {
- debug("Unable to parse ATTENDEE for %s: %s", to_string(), comperr.message);
- }
-
- attendee_prop = ical_component.get_next_property(iCal.icalproperty_kind.ATTENDEE_PROPERTY);
- }
+ persons_from_component(ical_component, organizers, iCal.icalproperty_kind.ORGANIZER_PROPERTY);
+ persons_from_component(ical_component, attendees, iCal.icalproperty_kind.ATTENDEE_PROPERTY);
// save own copy of component; no ownership transferrance w/ current bindings
if (_ical_component != ical_component)
_ical_component = ical_component.clone();
}
+ private void persons_from_component(iCal.icalcomponent ical_component, Gee.Set<Person> persons,
+ iCal.icalproperty_kind kind) {
+ persons.clear();
+
+ unowned iCal.icalproperty? prop = ical_component.get_first_property(kind);
+ while (prop != null) {
+ try {
+ persons.add(new Person.from_property(prop));
+ } catch (Error err) {
+ debug("Unable to parse %s for %s: %s", kind.to_string(), to_string(), err.message);
+ }
+
+ prop = ical_component.get_next_property(kind);
+ }
+ }
+
private void on_notify(ParamSpec pspec) {
// don't worry if in full update, that call is supposed to update properties
if (in_full_update)
@@ -426,6 +444,12 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
set_multiple_date_times(iCal.icalproperty_kind.RDATE_PROPERTY, rdates);
break;
+ case PROP_ORGANIZERS:
+ remove_all_properties(iCal.icalproperty_kind.ORGANIZER_PROPERTY);
+ foreach (Person organizer in organizers)
+ ical_component.add_property(organizer.as_ical_property());
+ break;
+
case PROP_ATTENDEES:
remove_all_properties(iCal.icalproperty_kind.ATTENDEE_PROPERTY);
foreach (Person attendee in attendees)
@@ -483,27 +507,38 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
}
/**
- * Add one or more mailto: URIs as { link attendees}.
- *
- * No URI-format checking is performed here; it is up to the caller to convert and deal with
- * formatting issues. Also note that duplicates are allowed in the attendees list.
+ * Add one or more { link Person}s as { link organizers}.
+ */
+ public void add_organizers(Gee.Collection<Person> to_add) {
+ organizers = add_persons(organizers, to_add);
+ }
+
+ /**
+ * Remove one or more { link Person}s as (@link organizers}.
+ */
+ public void remove_organizers(Gee.Collection<Person> to_remove) {
+ organizers = remove_persons(organizers, to_remove);
+ }
+
+ /*
+ * Removes all { link organizers}.
+ */
+ public void clear_organizers() {
+ organizers = new Gee.HashSet<Person>();
+ }
+
+ /**
+ * Add one or more { link Person}s as { link attendees}.
*/
public void add_attendees(Gee.Collection<Person> to_add) {
- Gee.Set<Person> copy = traverse<Person>(attendees).to_hash_set();
- copy.add_all(to_add);
-
- attendees = copy;
+ attendees = add_persons(attendees, to_add);
}
/**
- * Remove one or more mailto: URIs as (@link attendees}.
- *
- * See { link add_attendees} for notes about data validity and checking.
+ * Remove one or more { link Person}s as (@link attendees}.
*/
public void remove_attendees(Gee.Collection<Person> to_remove) {
- attendees = traverse<Person>(attendees)
- .filter(attendee => !to_remove.contains(attendee))
- .to_hash_set();
+ attendees = remove_persons(attendees, to_remove);
}
/*
@@ -513,6 +548,19 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
attendees = new Gee.HashSet<Person>();
}
+ private Gee.Set<Person> add_persons(Gee.Set<Person> existing, Gee.Collection<Person> to_add) {
+ Gee.Set<Person> copy = traverse<Person>(attendees).to_hash_set();
+ copy.add_all(to_add);
+
+ return copy;
+ }
+
+ private Gee.Set<Person> remove_persons(Gee.Set<Person> existing, Gee.Collection<Person> to_remove) {
+ return traverse<Person>(existing)
+ .filter(person => !to_remove.contains(person))
+ .to_hash_set();
+ }
+
/**
* Returns a single comma-delimited string for all attendees.
*
diff --git a/src/component/component-person.vala b/src/component/component-person.vala
index dce480c..bf66cde 100644
--- a/src/component/component-person.vala
+++ b/src/component/component-person.vala
@@ -21,7 +21,7 @@ namespace California.Component {
* [[https://tools.ietf.org/html/rfc5545#section-3.2.2]], and more.
*/
-public class Person : BaseObject, Gee.Hashable<Person> {
+public class Person : BaseObject, Gee.Hashable<Person>, Gee.Comparable<Person> {
/**
* The mailto: of the { link Person}, the only required value for the property.
*/
@@ -67,6 +67,10 @@ public class Person : BaseObject, Gee.Hashable<Person> {
this.mailto = mailto;
this.common_name = common_name;
full_mailbox = make_full_address(mailto, common_name);
+
+ // store in parameters in case object is serialized as an iCal property.
+ if (!String.is_empty(common_name))
+ parameters.add(new iCal.icalparameter.cn(common_name).as_ical_string());
}
internal Person.from_property(iCal.icalproperty prop) throws Error {
@@ -139,6 +143,20 @@ public class Person : BaseObject, Gee.Hashable<Person> {
return (this != other) ? mailto.equal(other.mailto) : true;
}
+ public int compare_to(Person other) {
+ if (this == other)
+ return 0;
+
+ // if a common name is supplied, use that first, but need to stabilize sort
+ if (!String.is_empty(common_name) && !String.is_empty(other.common_name)) {
+ int compare = String.stricmp(common_name, other.common_name);
+ if (compare != 0)
+ return compare;
+ }
+
+ return String.stricmp(mailbox, other.mailbox);
+ }
+
public override string to_string() {
return mailto_text;
}
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 4c985b4..d19931a 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -45,10 +45,16 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
private Gtk.Label where_text;
[GtkChild]
- private Gtk.Label who_label;
+ private Gtk.Label organizers_label;
[GtkChild]
- private Gtk.Label who_text;
+ private Gtk.Label organizers_text;
+
+ [GtkChild]
+ private Gtk.Label attendees_label;
+
+ [GtkChild]
+ private Gtk.Label attendees_text;
[GtkChild]
private Gtk.Label calendar_label;
@@ -133,7 +139,8 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
summary_text.draw.connect(on_label_drawn);
when_text.draw.connect(on_label_drawn);
where_text.draw.connect(on_label_drawn);
- who_text.draw.connect(on_label_drawn);
+ attendees_text.draw.connect(on_label_drawn);
+ organizers_text.draw.connect(on_label_drawn);
}
~ShowEvent() {
@@ -170,8 +177,23 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
set_label(when_label, when_text, event.get_event_time_pretty_string(Calendar.Date.PrettyFlag.NONE,
Calendar.ExactTimeSpan.PrettyFlag.NONE, Calendar.Timezone.local));
- // attendees
- set_label(who_label, who_text,
traverse<Component.Person>(event.attendees).to_string(stringify_attendee));
+ // organizers and attendees
+ // remove organizers from attendee list
+ Gee.Set<Component.Person> attendee_list = traverse<Component.Person>(event.attendees)
+ .filter(person => !event.organizers.contains(person))
+ .to_hash_set();
+
+ // convert to sorted LF-delimited strings
+ string organizers = traverse<Component.Person>(event.organizers)
+ .sort()
+ .to_string(stringify_person) ?? "";
+
+ string attendees = traverse<Component.Person>(attendee_list)
+ .sort()
+ .to_string(stringify_person) ?? "";
+
+ set_label(organizers_label, organizers_text, organizers);
+ set_label(attendees_label, attendees_text, attendees);
// calendar
set_label(calendar_label, calendar_text, event.calendar_source != null ? event.calendar_source.title
: null);
@@ -215,8 +237,8 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
return true;
}
- private string? stringify_attendee(Component.Person attendee, bool is_first, bool is_last) {
- return is_first ? attendee.mailbox : "%s%s".printf(EMAIL_SEPARATOR, attendee.full_mailbox);
+ private string? stringify_person(Component.Person person, bool is_first, bool is_last) {
+ return "%s%s".printf(person.full_mailbox, is_last ? "" : "\n");
}
// Note that text is not escaped, up to caller to determine if necessary or not.
diff --git a/src/rc/show-event.ui b/src/rc/show-event.ui
index 24b2063..310627e 100644
--- a/src/rc/show-event.ui
+++ b/src/rc/show-event.ui
@@ -134,6 +134,37 @@
</packing>
</child>
<child>
+ <object class="GtkLabel" id="organizers_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Organizers</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="organizers_text">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label">(empty)</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkLabel" id="calendar_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -145,7 +176,7 @@
</object>
<packing>
<property name="left_attach">0</property>
- <property name="top_attach">3</property>
+ <property name="top_attach">4</property>
</packing>
</child>
<child>
@@ -159,39 +190,37 @@
</object>
<packing>
<property name="left_attach">1</property>
- <property name="top_attach">3</property>
+ <property name="top_attach">4</property>
</packing>
</child>
<child>
- <object class="GtkLabel" id="who_label">
+ <object class="GtkLabel" id="attendees_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="yalign">0</property>
- <property name="label" translatable="yes" comments="Label for presenting the list of attendees
to an event.">Who</property>
+ <property name="label" translatable="yes">Attendees</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
- <property name="top_attach">2</property>
+ <property name="top_attach">3</property>
</packing>
</child>
<child>
- <object class="GtkLabel" id="who_text">
+ <object class="GtkLabel" id="attendees_text">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
+ <property name="yalign">0</property>
<property name="label">(empty)</property>
<property name="selectable">True</property>
- <property name="ellipsize">end</property>
- <property name="single_line_mode">True</property>
- <property name="max_width_chars">60</property>
</object>
<packing>
<property name="left_attach">1</property>
- <property name="top_attach">2</property>
+ <property name="top_attach">3</property>
</packing>
</child>
</object>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]