[california/wip/725785-create-recurring] Progress -- can create event every day of week



commit c9bfd1a6d31db21ddbff6bbe32c95f9156868ac2
Author: Jim Nelson <jim yorba org>
Date:   Wed Jun 11 19:17:49 2014 -0700

    Progress -- can create event every day of week
    
    However, when created only the first appears in the calendar.  Must
    restart to see them all.

 src/calendar/calendar-day-of-week.vala       |   15 +++++
 src/collection/collection-iterable.vala      |   12 ++++
 src/component/component-details-parser.vala  |   55 +++++++++++------
 src/component/component-event.vala           |   40 ++++++++-----
 src/component/component-recurrence-rule.vala |   83 +++++++++++++++++++-------
 vapi/libical.vapi                            |   27 +++------
 6 files changed, 158 insertions(+), 74 deletions(-)
---
diff --git a/src/calendar/calendar-day-of-week.vala b/src/calendar/calendar-day-of-week.vala
index 4c2024c..ee29481 100644
--- a/src/calendar/calendar-day-of-week.vala
+++ b/src/calendar/calendar-day-of-week.vala
@@ -52,6 +52,9 @@ public class DayOfWeek : BaseObject, Gee.Hashable<DayOfWeek> {
     public static DayOfWeek SAT;
     public static DayOfWeek SUN;
     
+    public static DayOfWeek[] weekdays;
+    public static DayOfWeek[] weekend_days;
+    
     public const int MIN = 1;
     public const int MAX = 7;
     public const int COUNT = MAX - MIN + 1;
@@ -142,11 +145,23 @@ public class DayOfWeek : BaseObject, Gee.Hashable<DayOfWeek> {
         days_of_week_sunday[4] = THU;
         days_of_week_sunday[5] = FRI;
         days_of_week_sunday[6] = SAT;
+        
+        weekdays = new DayOfWeek[5];
+        weekdays[0] = MON;
+        weekdays[1] = TUE;
+        weekdays[2] = WED;
+        weekdays[3] = THU;
+        weekdays[4] = FRI;
+        
+        weekend_days = new DayOfWeek[2];
+        weekend_days[0] = SAT;
+        weekend_days[1] = SUN;
     }
     
     internal static void terminate() {
         days_of_week_monday = days_of_week_sunday = null;
         MON = TUE = WED = THU = FRI = SAT = SUN = null;
+        weekdays = weekend_days = null;
     }
     
     /**
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index 2276d8e..61e9b32 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -30,6 +30,18 @@ public California.Iterable<G> iterate<G>(G g, ...) {
 }
 
 /**
+ * Take a non-null array of non-null items (all of type G) and return a California.Iterable
+ * for convenience.
+ */
+public California.Iterable<G> from_array<G>(G[] ar) {
+    Gee.ArrayList<G> list = new Gee.ArrayList<G>();
+    foreach (G item in ar)
+        list.add(item);
+    
+    return California.traverse<G>(list);
+}
+
+/**
  * An Iterable that simply wraps an existing Iterator.  You get one iteration,
  * and only one iteration.  Basically every method triggers one iteration and
  * returns a new object.
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index ccea24d..85f7563 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -64,6 +64,7 @@ public class DetailsParser : BaseObject {
     private Calendar.Date? end_date = null;
     private Calendar.Duration? duration = null;
     private bool adding_location = false;
+    private RecurrenceRule? rrule = null;
     
     /**
      * Parses a user-entered string of event details into an { link Event}.
@@ -161,7 +162,7 @@ public class DetailsParser : BaseObject {
             // A recurring preposition suggests a regular occurrance is being described by the next
             // two tokens
             stack.mark();
-            if (token.casefolded in RECURRING_PREPOSITIONS && parse_recurring(stack.pop(), stack.pop()))
+            if (token.casefolded in RECURRING_PREPOSITIONS && parse_recurring(stack.pop()))
                 continue;
             stack.restore();
             
@@ -248,6 +249,10 @@ public class DetailsParser : BaseObject {
             event.set_event_date_span(new Calendar.DateSpan(start_date, end_date));
         }
         
+        // recurrence rule, if specified
+        if (rrule != null)
+            event.make_recurring(rrule);
+        
         // other event details
         if (!String.is_empty(summary.str))
             event.summary = summary.str;
@@ -260,6 +265,8 @@ public class DetailsParser : BaseObject {
             event.description = details;
         else
             event.description += "\n" + details;
+        
+        debug("%s", event.ical_component.as_ical_string());
     }
     
     private bool parse_time(Token? specifier, bool strict) {
@@ -344,30 +351,42 @@ public class DetailsParser : BaseObject {
         return true;
     }
     
-    private bool parse_recurring(Token? amount, Token? unit) {
-        // only one is required
-        if (amount == null && unit == null)
+    private bool parse_recurring(Token? unit) {
+        // if a start date or recurring rule has already been specified, recurring cannot be made
+        if (unit == null || start_date != null || rrule != null)
             return false;
         
         // recurring can be specified with the amount acting as a day specifier, i.e. "every Friday"
         // or a single unit, i.e. "every day" or "every weekday"
-        if (unit == null) {
-            Calendar.DayOfWeek? dow = Calendar.DayOfWeek.parse(amount.casefolded);
-            if (dow != null) {
-                return true;
-            }
+        Calendar.DayOfWeek? dow = Calendar.DayOfWeek.parse(unit.casefolded);
+        if (dow != null) {
+            start_date = Calendar.System.today.upcoming(dow, true);
+            rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.WEEKLY_RECURRENCE);
             
-            if (amount.casefolded == DAY) {
-                return true;
-            }
+            return true;
+        }
+        
+        if (unit.casefolded == DAY) {
+            start_date = Calendar.System.today;
+            rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE);
             
-            if (amount.casefolded == WEEKDAY) {
-                return true;
-            }
+            return true;
+        }
+        
+        if (unit.casefolded == WEEKDAY) {
+            start_date = Calendar.System.today;
+            rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.WEEKLY_RECURRENCE);
+            // TODO: Set start of week
+            Gee.Map<Calendar.DayOfWeek, int> map = new Gee.HashMap<Calendar.DayOfWeek, int>();
+            foreach (Calendar.DayOfWeek weekday in Calendar.DayOfWeek.weekdays)
+                map.set(weekday, 0);
+            rrule.set_by_rule(RecurrenceRule.ByRule.DAY, RecurrenceRule.encode_days(map));
             
-            if (amount.casefolded == WEEKEND) {
-                return true;
-            }
+            return true;
+        }
+        
+        if (unit.casefolded == WEEKEND) {
+            return true;
         }
         
         return false;
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 6ef205f..f309300 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -84,6 +84,10 @@ public class Event : Instance, Gee.Comparable<Event> {
     
     /**
      * { link RecurrenceRule} (RRULE) for { link Event}.
+     *
+     * If the RecurrenceRule is itself altered, that signal is reflected to { link Instance.altered}.
+     *
+     * @see make_recurring
      */
     public RecurrenceRule? rrule { get; private set; default = null; }
     
@@ -227,17 +231,12 @@ public class Event : Instance, Gee.Comparable<Event> {
             break;
             
             case PROP_RRULE:
-                // whether rrule is added or removed (cleared), clear from ical_component
-                unowned iCal.icalproperty? rrule_property = ical_component.get_first_property(
-                    iCal.icalproperty_kind.RRULE_PROPERTY);
-                while (rrule_property != null) {
-                    ical_component.remove_property(rrule_property);
-                    rrule_property = ical_component.get_next_property(iCal.icalproperty_kind.RRULE_PROPERTY);
-                }
+                // always remove existing RRULE
+                remove_all_properties(iCal.icalproperty_kind.RRULE_PROPERTY);
                 
-                // add back if necessary
+                // add new one, if added
                 if (rrule != null)
-                    rrule.to_ical(ical_component);
+                    rrule.add_to_ical(ical_component);
             break;
             
             default:
@@ -348,20 +347,29 @@ public class Event : Instance, Gee.Comparable<Event> {
     /**
      * Add a { link RecurrenceRule} to the { link Event}.
      *
-     * Pass null to make non-recurring.
+     * Pass null to make Event non-recurring.
      */
     public void make_recurring(RecurrenceRule? rrule) {
-        if (this.rrule != null)
-            this.rrule.by_rule_updated.disconnect(on_by_rule_updated);
+        if (this.rrule != null) {
+            this.rrule.notify.disconnect(on_rrule_updated);
+            this.rrule.by_rule_updated.disconnect(on_rrule_updated);
+        }
         
-        if (rrule != null)
-            rrule.by_rule_updated.connect(on_by_rule_updated);
+        if (rrule != null) {
+            rrule.notify.connect(on_rrule_updated);
+            rrule.by_rule_updated.connect(on_rrule_updated);
+        }
         
         this.rrule = rrule;
     }
     
-    private void on_by_rule_updated() {
-        // TODO: Update ical_component with new RRULE
+    private void on_rrule_updated() {
+        // remove old property, replace with new one
+        remove_all_properties(iCal.icalproperty_kind.RRULE_PROPERTY);
+        rrule.add_to_ical(ical_component);
+        
+        // count this as an alteration
+        notify_altered(false);
     }
     
     /**
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
index 4a579d3..52ab290 100644
--- a/src/component/component-recurrence-rule.vala
+++ b/src/component/component-recurrence-rule.vala
@@ -147,7 +147,8 @@ public class RecurrenceRule : BaseObject {
             break;
         }
         
-        fill_by(rrule.by_second, iCal.BY_SECOND_SIZE, by_second);
+        fill_by(rrule.by_second, by_second);
+        /*
         fill_by(rrule.by_minute, iCal.BY_MINUTE_SIZE, by_minute);
         fill_by(rrule.by_hour, iCal.BY_HOUR_SIZE, by_hour);
         fill_by(rrule.by_day, iCal.BY_DAY_SIZE, by_day);
@@ -156,10 +157,11 @@ public class RecurrenceRule : BaseObject {
         fill_by(rrule.by_week_no, iCal.BY_WEEKNO_SIZE, by_week_num);
         fill_by(rrule.by_month, iCal.BY_MONTH_SIZE, by_month);
         fill_by(rrule.by_set_pos, iCal.BY_SETPOS_SIZE, by_set_pos);
+        */
     }
     
-    private void fill_by(short[] ical_by_ar, int ical_by_ar_len, Gee.SortedSet<int> by_set) {
-        for (int ctr = 0; ctr < ical_by_ar_len; ctr++) {
+    private void fill_by(short[] ical_by_ar, Gee.SortedSet<int> by_set) {
+        for (int ctr = 0; ctr < ical_by_ar.length; ctr++) {
             short by = ical_by_ar[ctr];
             if (by == iCal.RECURRENCE_ARRAY_MAX)
                 break;
@@ -193,7 +195,7 @@ public class RecurrenceRule : BaseObject {
     }
     
     /**
-     * Returns a read-only sorted set of BY rule settings.
+     * Returns a read-only sorted set of BY rule settings for the specified { link ByRule}.
      */
     public Gee.SortedSet<int> get_by_rule(ByRule by_rule) {
         switch (by_rule) {
@@ -230,8 +232,34 @@ public class RecurrenceRule : BaseObject {
     }
     
     /**
+     * Encode a Gee.Map of { link Calendar.DayOfWeek} and its position (i.e. Second Thursday of
+     * the month, last Wednesday of the year) into a value for { link set_by_rule} when using
+     * { link ByRule.DAY}.
+     *
+     * Use null for DayOfWeek and zero for position to mean "any" or "every".
+     */
+    public static Gee.Collection<int>? encode_days(Gee.Map<Calendar.DayOfWeek?, int>? day_values) {
+        if (day_values == null || day_values.size == 0)
+            return null;
+        
+        Gee.Collection<int> encoded = new Gee.ArrayList<int>();
+        Gee.MapIterator<Calendar.DayOfWeek?, int> iter = day_values.map_iterator();
+        while (iter.next()) {
+            Calendar.DayOfWeek? dow = iter.get_key();
+            int dow_value = (dow != null) ? dow.ordinal(Calendar.FirstOfWeek.SUNDAY) : 0;
+            int position = iter.get_value().clamp(0, int.MAX);
+            
+            encoded.add((position * 7) + dow_value);
+        }
+        
+        return encoded;
+    }
+    
+    /**
      * Replaces the existing set of values for the BY rules with the supplied values.
      *
+     * Pass null or an empty Collection to clear the by-rules values.
+     *
      * @see by_rule_updated
      */
     public void set_by_rule(ByRule by_rule, Gee.Collection<int>? values) {
@@ -287,13 +315,21 @@ public class RecurrenceRule : BaseObject {
     /**
      * Converts a { link RecurrenceRule} into an iCalendar RRULE property and adds it to the
      * iCal component.
+     *
+     * This call makes no attempt to remove an existing RRULE property; that should be performed by
+     * the caller first.
      */
-    internal void to_ical(iCal.icalcomponent ical_component) {
+    internal void add_to_ical(iCal.icalcomponent ical_component) {
         iCal.icalrecurrencetype rrule = { 0 };
         rrule.freq = freq;
-        rrule.until = until.dt;
-        rrule.count = count;
-        rrule.interval = interval;
+        
+        if (until != null)
+            rrule.until = until.dt;
+        else if (count > 0)
+            rrule.count = count;
+        
+        if (interval > 0)
+            rrule.interval = interval;
         
         if (start_of_week == null)
             rrule.week_start = iCal.icalrecurrencetype_weekday.NO_WEEKDAY;
@@ -314,30 +350,33 @@ public class RecurrenceRule : BaseObject {
         else
             assert_not_reached();
         
-        fill_ical_by(by_second, rrule.by_second, iCal.BY_SECOND_SIZE);
-        fill_ical_by(by_minute, rrule.by_minute, iCal.BY_MINUTE_SIZE);
-        fill_ical_by(by_hour, rrule.by_hour, iCal.BY_HOUR_SIZE);
-        fill_ical_by(by_day, rrule.by_day, iCal.BY_DAY_SIZE);
-        fill_ical_by(by_month_day, rrule.by_month_day, iCal.BY_MONTHDAY_SIZE);
-        fill_ical_by(by_year_day, rrule.by_year_day, iCal.BY_YEARDAY_SIZE);
-        fill_ical_by(by_week_num, rrule.by_week_no, iCal.BY_WEEKNO_SIZE);
-        fill_ical_by(by_month, rrule.by_month, iCal.BY_MONTH_SIZE);
-        fill_ical_by(by_set_pos, rrule.by_set_pos, iCal.BY_SETPOS_SIZE);
+        fill_ical_by(by_second, &rrule.by_second[0], rrule.by_second.length);
+        fill_ical_by(by_minute, &rrule.by_minute[0], rrule.by_minute.length);
+        fill_ical_by(by_hour, &rrule.by_hour[0], rrule.by_hour.length);
+        fill_ical_by(by_day, &rrule.by_day[0], rrule.by_day.length);
+        fill_ical_by(by_month_day, &rrule.by_month_day[0], rrule.by_month_day.length);
+        fill_ical_by(by_year_day, &rrule.by_year_day[0], rrule.by_year_day.length);
+        fill_ical_by(by_week_num, &rrule.by_week_no[0], rrule.by_week_no.length);
+        fill_ical_by(by_month, &rrule.by_month[0], rrule.by_month.length);
+        fill_ical_by(by_set_pos, &rrule.by_set_pos[0], rrule.by_set_pos.length);
         
         iCal.icalproperty rrule_property = new iCal.icalproperty(iCal.icalproperty_kind.RRULE_PROPERTY);
         rrule_property.set_rrule(rrule);
         
-        // TODO: Remove any existing RRULE properties
-        
         ical_component.add_property(rrule_property);
     }
     
-    private void fill_ical_by(Gee.SortedSet<int> by_set, short[] ical_by_ar, int ical_by_ar_len) {
+    private void fill_ical_by(Gee.SortedSet<int> by_set, short *ical_by_ar, int ar_length) {
         int index = 0;
-        foreach (int by in by_set)
+        foreach (int by in by_set) {
             ical_by_ar[index++] = (short) by;
+            
+            // watch for overflow
+            if (index >= ar_length)
+                break;
+        }
         
-        if (index < ical_by_ar_len)
+        if (index < ar_length)
             ical_by_ar[index] = (short) iCal.RECURRENCE_ARRAY_MAX;
     }
     
diff --git a/vapi/libical.vapi b/vapi/libical.vapi
index b1429e7..9d3daad 100644
--- a/vapi/libical.vapi
+++ b/vapi/libical.vapi
@@ -1716,24 +1716,15 @@ namespace iCal {
                public int count;
                public short interval;
                public iCal.icalrecurrencetype_weekday week_start;
-               [CCode (array_length = false)]
-               public weak short[] by_second;
-               [CCode (array_length = false)]
-               public weak short[] by_minute;
-               [CCode (array_length = false)]
-               public weak short[] by_hour;
-               [CCode (array_length = false)]
-               public weak short[] by_day;
-               [CCode (array_length = false)]
-               public weak short[] by_month_day;
-               [CCode (array_length = false)]
-               public weak short[] by_year_day;
-               [CCode (array_length = false)]
-               public weak short[] by_week_no;
-               [CCode (array_length = false)]
-               public weak short[] by_month;
-               [CCode (array_length = false)]
-               public weak short[] by_set_pos;
+               public unowned short by_second[61];
+               public unowned short by_minute[61];
+               public unowned short by_hour[25];
+               public unowned short by_day[364];
+               public unowned short by_month_day[32];
+               public unowned short by_year_day[367];
+               public unowned short by_week_no[54];
+               public unowned short by_month[13];
+               public unowned short by_set_pos[367];
                [CCode (cname = "icalrecurrencetype_as_string")]
                public unowned string as_string ();
                [CCode (cname = "icalrecurrencetype_as_string_r")]


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