[california/wip/731543-attendees: 4/5] Display attendees in event viewer, editor



commit 21359d9cc7afc0db2683973a2dc58aa1b87ed7a4
Author: Jim Nelson <jim yorba org>
Date:   Tue Nov 4 18:28:54 2014 -0800

    Display attendees in event viewer, editor

 src/collection/collection-iterable.vala |   13 +++-
 src/component/component-instance.vala   |   93 +++++++++++++++++++++++++++++++
 src/host/host-create-update-event.vala  |    1 +
 src/host/host-show-event.vala           |   31 ++++++++++
 src/rc/show-event.ui                    |   58 +++++++++++--------
 5 files changed, 169 insertions(+), 27 deletions(-)
---
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index 3ee25ba..b5695bb 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -103,7 +103,7 @@ public class Iterable<G> : Object {
     /**
      * For { link to_string}.
      */
-    public delegate string? ToString<G>(G element);
+    public delegate string? ToString<G>(G element, bool is_first, bool is_last);
     
     /**
      * For simple iteration of the { link Iterable}.
@@ -382,12 +382,19 @@ public class Iterable<G> : Object {
      *
      * If { link ToString} returns null or an empty string, nothing is appended to the final string.
      *
+     * is_first is passed true to ToString if the string is the first element of the Iterable.  If
+     * prior elements resulted in null being returned, then is_first will continue to be true.  In
+     * other words, is_first is true if the built string so far is empty.
+     *
+     * is_last is only true when the last element of the Iterable has been reached.
+     *
      * If the final string is empty, null is returned instead.
      */
     public string? to_string(ToString<G> string_cb) {
         StringBuilder builder = new StringBuilder();
-        foreach (G element in this) {
-            string? str = string_cb(element);
+        Gee.Iterator<G> iter = iterator();
+        while (iter.next()) {
+            string? str = string_cb(iter.get(), String.is_empty(builder.str), !iter.has_next());
             if (!String.is_empty(str))
                 builder.append(str);
         }
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 89cc3c2..acdb8b3 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_ATTENDEES = "attendees";
     
     protected const string PROP_IN_FULL_UPDATE = "in-full-update";
     
@@ -171,6 +172,20 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
     public int sequence { get; set; default = 0; }
     
     /**
+     * ATTENDEEs for a VEVENT, VTODO, or VJOURNAL.
+     *
+     * This property returns a read-only view of the list of attendees.  To add or remove attendees,
+     * use { link add_attendees}, { link remove_attendees}, and { link clear_attendees}.
+     * 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.
+     *
+     * No validity checking is performed on attendee mailto's.
+     *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.8.4.1]]
+     */
+    public Gee.List<string> attendees { get; private set; default = new Gee.ArrayList<string>(); }
+    
+    /**
      * The iCal component being represented by this { link Instance}.
      */
     private iCal.icalcomponent _ical_component;
@@ -352,6 +367,17 @@ 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) {
+            unowned string mailto = attendee_prop.get_attendee();
+            if (!String.is_empty(mailto))
+                attendees.add(mailto);
+            
+            attendee_prop = ical_component.get_next_property(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();
@@ -398,6 +424,17 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
                     set_multiple_date_times(iCal.icalproperty_kind.RDATE_PROPERTY, rdates);
             break;
             
+            case PROP_ATTENDEES:
+                // TODO: Need to update iCal component with adjusted set of attendees, whether
+                // removed or appended
+                /*
+                unowned iCal.icalproperty? attendee_prop = ical_component.get_first_property(
+                    iCal.icalproperty_kind.ATTENDEE_PROPERTY);
+                while (attendee_prop != null) {
+                }
+                */
+            break;
+            
             default:
                 altered = false;
             break;
@@ -449,6 +486,62 @@ 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.
+     */
+    public void add_attendees(Gee.Collection<string> mailtos) {
+        Gee.List<string> copy = traverse<string>(attendees).to_array_list();
+        copy.add_all(mailtos);
+        
+        attendees = copy;
+    }
+    
+    /**
+     * Remove one or more mailto: URIs as (@link attendees}.
+     *
+     * See { link add_attendees} for notes about data validity and checking.
+     */
+    public void remove_attendees(Gee.Collection<string> mailtos) {
+        attendees = traverse<string>(attendees)
+            .filter(attendee => mailtos.contains(attendee))
+            .to_array_list();
+    }
+    
+    /*
+     * Removes all { link attendees}.
+     */
+    public void clear_attendees() {
+        attendees = new Gee.ArrayList<string>();
+    }
+    
+    /**
+     * Returns a single comma-delimited string for all attendees.
+     *
+     * Returns null if no attendees are associated with this { link Instance}.
+     */
+    public string? attendees_to_string() {
+        return traverse<string>(attendees).to_string(stringify_attendee);
+    }
+    
+    private static string? stringify_attendee(string attendee, bool is_first, bool is_last) {
+        // Must parse correctly into URI
+        Soup.URI mailto;
+        try {
+            mailto = URI.parse(attendee);
+        } catch (Error err) {
+            return null;
+        }
+        
+        // Must be a non-empty mailto:
+        if (mailto.scheme != "mailto" || String.is_empty(mailto.path))
+            return null;
+        
+        return is_first ? mailto.path : ", %s".printf(mailto.path);
+    }
+    
+    /**
      * Returns an appropriate { link Component} instance for the iCalendar component.
      *
      * VCALENDARs should use { link Component.iCalendar}.
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 922d8c6..1e678f8 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -170,6 +170,7 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         
         location_entry.text = event.location ?? "";
         description_textview.buffer.text = event.description ?? "";
+        attendees_entry.text = event.attendees_to_string() ?? "";
         
         Component.Event master = event.is_master_instance ? event : (Component.Event) event.master;
         
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 640c72d..439e1c7 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -17,6 +17,8 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
     private const string FAMILY_NORMAL = "normal";
     private const string FAMILY_REMOVING = "removing";
     
+    private const string DATA_TEXT = "california.text";
+    
     public string card_id { get { return ID; } }
     
     public string? title { get { return null; } }
@@ -43,6 +45,12 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
     private Gtk.Label where_text;
     
     [GtkChild]
+    private Gtk.Label who_label;
+    
+    [GtkChild]
+    private Gtk.Label who_text;
+    
+    [GtkChild]
     private Gtk.Label calendar_label;
     
     [GtkChild]
@@ -119,6 +127,13 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
         rotating_button_box.vexpand = true;
         rotating_button_box.valign = Gtk.Align.END;
         rotating_button_box_container.add(rotating_button_box);
+        
+        // use this to setup tooltips for text labels that have been ellipsized ... this only works
+        // for GtkLabel's initialized with set_label(), so beware
+        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);
     }
     
     ~ShowEvent() {
@@ -155,6 +170,9 @@ 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, event.attendees_to_string());
+        
         // calendar
         set_label(calendar_label, calendar_text, event.calendar_source != null ? event.calendar_source.title 
: null);
         
@@ -211,6 +229,19 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
                 label.no_show_all = true;
             }
         }
+        
+        text.set_data(DATA_TEXT, str);
+    }
+    
+    private bool on_label_drawn(Gtk.Widget widget, Cairo.Context ctx) {
+        // if ellipsized, add a tooltip, otherwise remove it
+        Gtk.Label label = (Gtk.Label) widget;
+        if (label.get_layout().is_ellipsized())
+            label.set_tooltip_text(label.get_data(DATA_TEXT));
+        else
+            label.set_tooltip_text("");
+        
+        return false;
     }
     
     private void on_remove_button_clicked() {
diff --git a/src/rc/show-event.ui b/src/rc/show-event.ui
index f09531f..24b2063 100644
--- a/src/rc/show-event.ui
+++ b/src/rc/show-event.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.16.1 -->
+<!-- Generated with glade 3.18.3 -->
 <interface>
   <requires lib="gtk+" version="3.10"/>
   <template class="CaliforniaHostShowEvent" parent="GtkGrid">
@@ -24,8 +24,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">0</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -67,8 +65,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">2</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -91,8 +87,6 @@
           <packing>
             <property name="left_attach">0</property>
             <property name="top_attach">0</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
           </packing>
         </child>
         <child>
@@ -109,8 +103,6 @@
           <packing>
             <property name="left_attach">0</property>
             <property name="top_attach">1</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
           </packing>
         </child>
         <child>
@@ -125,8 +117,6 @@
           <packing>
             <property name="left_attach">1</property>
             <property name="top_attach">0</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
           </packing>
         </child>
         <child>
@@ -141,8 +131,6 @@
           <packing>
             <property name="left_attach">1</property>
             <property name="top_attach">1</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
           </packing>
         </child>
         <child>
@@ -157,9 +145,7 @@
           </object>
           <packing>
             <property name="left_attach">0</property>
-            <property name="top_attach">2</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
+            <property name="top_attach">3</property>
           </packing>
         </child>
         <child>
@@ -173,17 +159,45 @@
           </object>
           <packing>
             <property name="left_attach">1</property>
+            <property name="top_attach">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="who_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>
+            <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="who_text">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">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="width">1</property>
-            <property name="height">1</property>
           </packing>
         </child>
       </object>
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">1</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -201,8 +215,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">4</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -219,8 +231,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">3</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
   </template>


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