[california/wip/740088-invite] Better mail body when sending invitation



commit 5d160d6cc118601c364298cf177038feab805c95
Author: Jim Nelson <jim yorba org>
Date:   Thu Nov 20 14:11:40 2014 -0800

    Better mail body when sending invitation

 src/calendar/calendar-exact-time-span.vala   |   60 +++++++++++++++--
 src/component/component-instance.vala        |    7 +-
 src/component/component-recurrence-rule.vala |    2 +
 src/host/host-create-update-event.vala       |   90 +++++++++++++++++++++++++-
 4 files changed, 147 insertions(+), 12 deletions(-)
---
diff --git a/src/calendar/calendar-exact-time-span.vala b/src/calendar/calendar-exact-time-span.vala
index 20ba263..4db2ca1 100644
--- a/src/calendar/calendar-exact-time-span.vala
+++ b/src/calendar/calendar-exact-time-span.vala
@@ -25,7 +25,11 @@ public class ExactTimeSpan : BaseObject, Gee.Comparable<ExactTimeSpan>, Gee.Hash
         /**
          * Use multiple lines to format string if lengthy.
          */
-        ALLOW_MULTILINE
+        ALLOW_MULTILINE,
+        /**
+         * Include timezone information in the string.
+         */
+        INCLUDE_TIMEZONE
     }
     
     /**
@@ -144,6 +148,7 @@ public class ExactTimeSpan : BaseObject, Gee.Comparable<ExactTimeSpan>, Gee.Hash
      */
     public string to_pretty_string(Calendar.Date.PrettyFlag date_flags, PrettyFlag time_flags) {
         bool allow_multiline = (time_flags & PrettyFlag.ALLOW_MULTILINE) != 0;
+        bool include_timezone = (time_flags & PrettyFlag.INCLUDE_TIMEZONE) != 0;
         
         if (!start_date.year.equal_to(Calendar.System.today.year)
             || !end_date.year.equal_to(Calendar.System.today.year)) {
@@ -151,17 +156,32 @@ public class ExactTimeSpan : BaseObject, Gee.Comparable<ExactTimeSpan>, Gee.Hash
         }
         
         if (is_same_day) {
-            // A span of time, i.e. "3:30pm to 4:30pm"
-            string timespan = _("%s to %s").printf(
-                start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
-                end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE));
+            string pretty_start_time = 
start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE);
+            string pretty_end_time = end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE);
+            
+            string timespan;
+            if (!include_timezone) {
+                // A span of time, i.e. "3:30pm to 4:30pm"
+                timespan = _("%s to %s").printf(pretty_start_time, pretty_end_time);
+            } else if (start_exact_time.tzid == end_exact_time.tzid) {
+                // A span of time followed by the timezone, i.e. "3:30pm to 4:30pm EST"
+                timespan = _("%s to %s %s").printf(pretty_start_time, pretty_end_time,
+                    start_exact_time.tzid);
+            } else {
+                // A span of time with each timezone's indicated, i.e.
+                // "12:30AM EDT to 2:30PM EST"
+                timespan = _("%s %s to %s %s").printf(pretty_start_time, start_exact_time.tzid,
+                    pretty_end_time, end_exact_time.tzid);
+            }
             
             // Single-day timed event, print "<full date>, <full start time> to <full end time>",
             // including year if not current year
-            return "%s, %s".printf(start_date.to_pretty_string(date_flags), timespan);
+            
+            // Date and time, i.e. "September 13, 4:30pm"
+            return _("%s, %s").printf(start_date.to_pretty_string(date_flags), timespan);
         }
         
-        if (allow_multiline) {
+        if (allow_multiline && !include_timezone) {
             // Multi-day timed event, print "<full time>, <full date>" on both lines,
             // including year if either not current year
             // Prints two full time and date strings on separate lines, i.e.:
@@ -172,6 +192,32 @@ public class ExactTimeSpan : BaseObject, Gee.Comparable<ExactTimeSpan>, Gee.Hash
                 start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
                 end_exact_time.to_pretty_date_string(date_flags),
                 end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE));
+        } else if (allow_multiline && include_timezone) {
+            // Multi-day timed event, print "<full time>, <full date>" on both lines,
+            // including year if either not current year,
+            // *and* including timezone
+            // Prints two full time and date strings on separate lines, i.e.:
+            // 12 January 2012, 3:30pm PST
+            // 13 January 2013, 6:30am PST
+            return _("%s, %s %s\n%s, %s %s").printf(
+                start_exact_time.to_pretty_date_string(date_flags),
+                start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
+                start_exact_time.tzid,
+                end_exact_time.to_pretty_date_string(date_flags),
+                end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
+                end_exact_time.tzid);
+        }
+        
+        if (include_timezone) {
+            // Prints full time and date strings on a single line with timezone, i.e.:
+            // 12 January 2012, 3:30pm PST to 13 January 2013, 6:30am PST
+            return _("%s, %s %s to %s, %s %s").printf(
+                    start_exact_time.to_pretty_date_string(date_flags),
+                    start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
+                    start_exact_time.tzid,
+                    end_exact_time.to_pretty_date_string(date_flags),
+                    end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
+                    end_exact_time.tzid);
         }
         
         // Prints full time and date strings on a single line, i.e.:
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 2c534f2..b65602b 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -581,14 +581,15 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
     }
     
     /**
-     * Export this { link Instance}'s master as an iCalendar.
+     * Export this { link Instance}'s { link master} as an iCalendar.
+     *
+     * If this Instance is the master, this is functionally the same as { link export}.
      *
-     * @see export
      * @see is_master
      */
     public iCalendar export_master(iCal.icalproperty_method method) {
         return new iCalendar(method, ICAL_PRODID, ICAL_VERSION, null,
-            iterate<Instance>(master).to_array_list());
+            iterate<Instance>(master ?? this).to_array_list());
     }
     
     /**
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
index abfffed..cd94c8b 100644
--- a/src/component/component-recurrence-rule.vala
+++ b/src/component/component-recurrence-rule.vala
@@ -589,6 +589,8 @@ public class RecurrenceRule : BaseObject {
     /**
      * Returns a natural-language string explaining the { link RecurrenceRule} for the user.
      *
+     * The start_date should be the starting date of the associated { link Instance}.
+     *
      * Returns null if the RRULE is beyond the comprehension of this parser.
      */
     public string? explain(Calendar.Date start_date) {
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 5cb62c5..27d9af5 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -480,11 +480,18 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
             argv += invitee.mailbox;
         
         argv += "--subject";
-        argv += String.is_empty(event.summary) ? _("Event invitation") : _("Invitation: 
%s").printf(event.summary);
+        if (String.is_empty(event.summary)) {
+            argv += _("Event invitation");
+        } else if (String.is_empty(event.location)) {
+            argv += _("Invitation: %s").printf(event.summary);
+        } else {
+            // Invitation: <summary> at <location>
+            argv += _("Invitation: %s at %s").printf(event.summary, event.location);
+        }
         
         // TODO: Generate a better body for the email (w/ event summary)
         argv += "--body";
-        argv += _("Attached is an invitation to a new event.");
+        argv += generate_invite_body(event);
         
         argv += "--attach";
         argv += temporary_filename;
@@ -498,6 +505,85 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
                 _("Unable to launch mail client: %s").printf(err.message));
         }
     }
+    
+    private static string generate_invite_body(Component.Event event) {
+        StringBuilder builder = new StringBuilder();
+        
+        // Salutations for an email
+        append_line(builder, _("Hello,"));
+        append_line(builder);
+        append_line(builder, _("Attached is an invitation to a new event:"));
+        append_line(builder);
+        
+        // Summary
+        if (!String.is_empty(event.summary))
+            append_line(builder, event.summary);
+        
+        // Date/Time span
+        string? pretty_time = event.get_event_time_pretty_string(
+            Calendar.Date.PrettyFlag.NO_TODAY | Calendar.Date.PrettyFlag.INCLUDE_OTHER_YEAR,
+            Calendar.ExactTimeSpan.PrettyFlag.INCLUDE_TIMEZONE,
+            Calendar.Timezone.local
+        );
+        if (!String.is_empty(pretty_time)) {
+            // Date/time of an event
+            append_line(builder, _("When: %s").printf(pretty_time));
+        }
+        
+        // Recurrences
+        if (event.rrule != null) {
+            string? rrule_explanation = 
event.rrule.explain(event.get_event_date_span(Calendar.Timezone.local).start_date);
+            if (!String.is_empty(rrule_explanation))
+                append_line(builder, rrule_explanation);
+        }
+        
+        // Location
+        if (!String.is_empty(event.location)) {
+            // Location of an event
+            append_line(builder, _("Where: %s").printf(event.location));
+        }
+        
+        // Organizer (only list one)
+        Component.Person? organizer = null;
+        if (!event.organizers.is_empty) {
+            organizer = traverse<Component.Person>(event.organizers)
+                .sort()
+                .first();
+            // Who organized (scheduled or planned) the event
+            append_line(builder, _("Organizer: %s").printf(organizer.full_mailbox));
+        }
+        
+        // Attendees (strip Organizer from list)
+        Gee.List<Component.Person> attendees = traverse<Component.Person>(event.attendees)
+            .filter(person => organizer == null || !person.equal_to(organizer))
+            .sort()
+            .to_array_list();
+        if (attendees.size > 0) {
+            // People attending event
+            append_line(builder, ngettext("Guest: %s", "Guests: %s", attendees.size).printf(
+                traverse<Component.Person>(attendees).to_string(stringify_people)));
+        }
+        
+        // Description
+        if (!String.is_empty(event.description)) {
+            append_line(builder);
+            append_line(builder, event.description);
+        }
+        
+        return builder.str;
+    }
+    
+    private static void append_line(StringBuilder builder, string? str = null) {
+        if (!String.is_empty(str))
+            builder.append(str);
+        
+        builder.append("\n");
+    }
+    
+    private static string? stringify_people(Component.Person person, bool is_first, bool is_last) {
+        // Email separator, i.e. "alice example com, bob example com"
+        return !is_last ? _("%s, ").printf(person.full_mailbox) : person.full_mailbox;
+    }
 }
 
 }


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