[california] Remove recurring events: Bug #725787



commit 48054dd30cd1f00f1b604b119745951780040298
Author: Jim Nelson <jim yorba org>
Date:   Thu Jul 3 15:43:57 2014 -0700

    Remove recurring events: Bug #725787
    
    User can now remove an instance, all instances, or an instance and
    all its future instances of a recurring event.
    
    This also includes some fixes to the EDS CalendarSourceSubscription
    class.  Investigating this ticket showed some problems with how we
    were dealing with signals from the EDS CalClientView that needed to
    be corrected in order to properly deal with particular instances of
    a recurring event going away.

 .../backing-calendar-source-subscription.vala      |    7 +
 src/backing/backing-calendar-source.vala           |   53 +++-
 .../backing-eds-calendar-source-subscription.vala  |   89 ++++--
 src/backing/eds/backing-eds-calendar-source.vala   |   68 +++++-
 src/component/component-date-time.vala             |   10 +-
 src/host/host-show-event.vala                      |   76 ++++-
 src/rc/show-event.ui                               |  305 ++++++++++++++------
 vapi/libecal-1.2.vapi                              |   32 ++-
 vapi/libecal-1.2/libecal-1.2-custom.vala           |   18 ++
 vapi/libecal-1.2/libecal-1.2.metadata              |   11 +-
 vapi/libical.vapi                                  |    6 +-
 11 files changed, 518 insertions(+), 157 deletions(-)
---
diff --git a/src/backing/backing-calendar-source-subscription.vala 
b/src/backing/backing-calendar-source-subscription.vala
index 71216fd..12a1342 100644
--- a/src/backing/backing-calendar-source-subscription.vala
+++ b/src/backing/backing-calendar-source-subscription.vala
@@ -9,6 +9,13 @@ namespace California.Backing {
 /**
  * A subscription to an active timespan of interest of a calendar.
  *
+ * CalendarSourceSubscription generates { link Component.Instance}s of the various events in a
+ * window of time in a calendar.  Note that for recurring events, there is no interface to directly
+ * edit or remove the "original" iCalendar source.  Rather, the caller should update or remove
+ * generated instances of that recurring event with flags to indicate how the original is to be
+ * altered to match these changes.  See { link CalendarSource} for the interface to make these
+ * alterations.
+ *
  * The subscription can notify of calendar event updates and list a complete or partial collections
  * of the same.
  */
diff --git a/src/backing/backing-calendar-source.vala b/src/backing/backing-calendar-source.vala
index 1cd6cde..4e6589b 100644
--- a/src/backing/backing-calendar-source.vala
+++ b/src/backing/backing-calendar-source.vala
@@ -9,11 +9,36 @@ namespace California.Backing {
 /**
  * An abstract representation of a backing source of calendar information.
  *
+ * CalendarSource provides information about the calendar and an interface for operating on
+ * { link Component.Instance}s in the calendar.  Use { link CalendarSourceSubscription} to generate
+ * those instances for specific windows of time.
+ *
  * @see Manager
  * @see Source
  */
 
 public abstract class CalendarSource : Source {
+    /**
+     * The affected range of a modification or removal operation.
+     *
+     * Note that zero (0) does ''not'' mean "none", it means { link AffectedInstances.THIS}.  The
+     * additional enums merely expand the scope of the default, which is the supplied instance.
+     */
+    public enum AffectedInstances {
+        /**
+         * Include the supplied { link Component.Instance} in the affected instances.
+         */
+        THIS = 0,
+        /**
+         * Include all future { link Component.Instance}s in the affected instances.
+         */
+        THIS_AND_FUTURE,
+        /**
+         * Indicating all { link Component.Instance}s should be affected.
+         */
+        ALL
+    }
+    
     protected CalendarSource(string id, string title) {
         base (id, title);
     }
@@ -28,9 +53,10 @@ public abstract class CalendarSource : Source {
      * Creates a new { link Component} instance on the backing { link CalendarSource}.
      *
      * Outstanding { link CalendarSourceSubscriptions} will eventually report the generated
-     * instance when it's available.
+     * instance when it's available.  If the supplied instance includes an RRULE (i.e.
+     * { link Component.Instance.rrule}), one or more instances will be generated.
      *
-     * @returns The { link Component.UID}.of the generated instance, if available.
+     * @returns The { link Component.UID}.keyed to all instances, if available.
      */
     public abstract async Component.UID? create_component_async(Component.Instance instance,
         Cancellable? cancellable = null) throws Error;
@@ -45,15 +71,30 @@ public abstract class CalendarSource : Source {
         Cancellable? cancellable = null) throws Error;
     
     /**
-     * Destroys (removes) a { link Component} instance on the backing { link CalendarSource}.
+     * Destroys (removes) all { link Component.Instance}s on the backing { link CalendarSource}
+     * keyed to the supplied { link Component.UID}.
      *
-     * Outstanding { link CalendarSourceSubscriptions} will eventually report the instance as
-     * removed.
+     * Outstanding { link CalendarSourceSubscriptions} will eventually report all affected instances
+     * as removed.
      */
-    public abstract async void remove_component_async(Component.UID uid,
+    public abstract async void remove_all_instances_async(Component.UID uid,
         Cancellable? cancellable = null) throws Error;
     
     /**
+     * Destroys (removes) some or all { link Component.Instance}s on the backing
+     * { link CalendarSource} keyed to the supplied { link Component.UID}, the supplied RID,
+     * and the { link AffectedInstances}.
+     *
+     * If { link AffectedInstances.ALL} is passed, the RID is ignored.  This is operationally the
+     * same as calling { link remove_all_instances_async}.
+     *
+     * Outstanding { link CalendarSourceSubscriptions} will eventually report all affected instances
+     * as removed.
+     */
+    public abstract async void remove_instances_async(Component.UID uid, Component.DateTime rid,
+        AffectedInstances affected, Cancellable? cancellable = null) throws Error;
+    
+    /**
      * Imports a { link Component.iCalendar} into the { link CalendarSource}.
      */
     public abstract async void import_icalendar_async(Component.iCalendar ical, Cancellable? cancellable = 
null)
diff --git a/src/backing/eds/backing-eds-calendar-source-subscription.vala 
b/src/backing/eds/backing-eds-calendar-source-subscription.vala
index 0da9af6..b3003a6 100644
--- a/src/backing/eds/backing-eds-calendar-source-subscription.vala
+++ b/src/backing/eds/backing-eds-calendar-source-subscription.vala
@@ -112,6 +112,23 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
     
     private void on_objects_added(SList<weak iCal.icalcomponent> objects) {
         foreach (weak iCal.icalcomponent ical_component in objects) {
+            if (String.is_empty(ical_component.get_uid()))
+                continue;
+            
+            Component.UID uid = new Component.UID(ical_component.get_uid());
+            
+            // remove all existing components with this UID
+            if (has_uid(uid))
+                notify_instance_removed(uid);
+            
+            // if no recurrences, add this alone
+            if (!E.Util.component_has_recurrences(ical_component)) {
+                add_instance(ical_component);
+                
+                continue;
+            }
+            
+            // generate recurring instances
             view.client.generate_instances_for_object(
                 ical_component,
                 window.start_exact_time.to_time_t(),
@@ -124,56 +141,70 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
     
     private bool on_instance_added(E.CalComponent eds_component, time_t instance_start,
         time_t instance_end) {
-        unowned iCal.icalcomponent ical_component = eds_component.get_icalcomponent();
+        add_instance(eds_component.get_icalcomponent());
         
+        return true;
+    }
+    
+    // Assumes all existing events with UID/RID have been removed already
+    private void add_instance(iCal.icalcomponent ical_component) {
         // convert the added component into a new Event
         Component.Event? added_event;
         try {
             added_event = Component.Instance.convert(calendar, ical_component) as Component.Event;
-            if (added_event == null)
-                return true;
+            if (added_event != null)
+                notify_instance_added(added_event);
         } catch (Error err) {
             debug("Unable to process added event: %s", err.message);
-            
-            return true;
         }
-        
-        // see if this was seen before
-        Component.Event? seen_event = has_instance(added_event) as Component.Event;
-        if (seen_event != null)
-            return true;
-        
-        // nope, it's a new one
-        notify_instance_added(added_event);
-        
-        return true;
     }
     
     private void on_objects_modified(SList<weak iCal.icalcomponent> objects) {
+        SList<weak iCal.icalcomponent> add_list = new SList<weak iCal.icalcomponent>();
         foreach (weak iCal.icalcomponent ical_component in objects) {
-            // convert the modified event source into an orphaned event (for comparison purposes)
-            Component.Event modified_event;
-            try {
-                modified_event = new Component.Event(null, ical_component);
-            } catch (Error err) {
-                debug("Unable to process modified event: %s", err.message);
+            // if not an instance and has recurring, treat as an add (which removes and adds generated
+            // instances)
+            if (!E.Util.component_is_instance(ical_component) && 
E.Util.component_has_recurrences(ical_component)) {
+                add_list.append(ical_component);
                 
                 continue;
             }
             
-            // find original event instance for this one
-            Component.Event? seen_event = has_instance(modified_event) as Component.Event;
-            if (seen_event == null)
+            if (String.is_empty(ical_component.get_uid()))
+                continue;
+            
+            // if none present, skip
+            Component.UID uid = new Component.UID(ical_component.get_uid());
+            if (!has_uid(uid))
                 continue;
             
-            try {
-                seen_event.full_update(ical_component, null);
-            } catch (Error err) {
-                debug("Unable to update event %s: %s", seen_event.to_string(), err.message);
+            // find original for this one
+            Gee.Collection<Component.Instance>? instances = for_uid(uid);
+            if (instances == null || instances.size == 0)
+                continue;
+            
+            foreach (Component.Instance instance in instances) {
+                Component.Event? known_event = instance as Component.Event;
+                if (known_event == null)
+                    continue;
+                
+                try {
+                    known_event.full_update(ical_component, null);
+                } catch (Error err) {
+                    debug("Unable to update event %s: %s", known_event.to_string(), err.message);
+                    
+                    continue;
+                }
+                
+                notify_instance_altered(known_event);
             }
             
-            notify_instance_altered(seen_event);
+            if (instances.size > 1)
+                debug("Warning: updated %d modified events, expecting only 1", instances.size);
         }
+        
+        // add any recurring events
+        on_objects_added(add_list);
     }
     
     private void on_objects_removed(SList<weak E.CalComponentId?> ids) {
diff --git a/src/backing/eds/backing-eds-calendar-source.vala 
b/src/backing/eds/backing-eds-calendar-source.vala
index 984f778..440327e 100644
--- a/src/backing/eds/backing-eds-calendar-source.vala
+++ b/src/backing/eds/backing-eds-calendar-source.vala
@@ -155,11 +155,75 @@ internal class EdsCalendarSource : CalendarSource {
         yield client.modify_object(instance.ical_component, E.CalObjModType.THIS, cancellable);
     }
     
-    public override async void remove_component_async(Component.UID uid,
+    public override async void remove_all_instances_async(Component.UID uid,
         Cancellable? cancellable = null) throws Error {
         check_open();
         
-        yield client.remove_object(uid.value, null, E.CalObjModType.THIS, cancellable);
+        yield client.remove_object(uid.value, null, E.CalObjModType.ALL, cancellable);
+    }
+    
+    public override async void remove_instances_async(Component.UID uid, Component.DateTime rid,
+        CalendarSource.AffectedInstances affected, Cancellable? cancellable = null) throws Error {
+        check_open();
+        
+        // Note that E.CalObjModType.ONLY_THIS is *never* used ... examining EDS source code,
+        // it appears in e-cal-backend-file.c that ONLY_THIS merely removes the instance but does not
+        // include an EXDATE in the original iCal source ... I don't quite understand the benefit of
+        // this, as this suggests (a) other calendar clients won't learn of the removal and (b) the
+        // instance will be re-generated the next time the user runs an EDS calendar client.  In
+        // either case, ONLY maps to our desired effect by adding an EXDATE to the iCal source.
+        switch (affected) {
+            case CalendarSource.AffectedInstances.THIS:
+                yield client.remove_object(uid.value, rid.value, E.CalObjModType.THIS, cancellable);
+            break;
+            
+            case CalendarSource.AffectedInstances.THIS_AND_FUTURE:
+                yield remove_this_and_future_async(uid, rid, cancellable);
+            break;
+            
+            case CalendarSource.AffectedInstances.ALL:
+                yield remove_all_instances_async(uid, cancellable);
+            break;
+            
+            default:
+                assert_not_reached();
+        }
+    }
+    
+    private async void remove_this_and_future_async(Component.UID uid, Component.DateTime rid,
+        Cancellable? cancellable) throws Error {
+        // get the master instance ... remember that the Backing.CalendarSource only stores generated
+        // instances
+        iCal.icalcomponent ical_component;
+        yield client.get_object(uid.value, null, cancellable, out ical_component);
+        
+        // change the RRULE's UNTIL indicating the end of the recurring set (which is, handily enough,
+        // the RID)
+        unowned iCal.icalproperty? rrule_property = ical_component.get_first_property(
+            iCal.icalproperty_kind.RRULE_PROPERTY);
+        if (rrule_property == null)
+            return;
+        
+        iCal.icalrecurrencetype rrule = rrule_property.get_rrule();
+        
+        // In order to be inclusive, need to set UNTIL one tick earlier to ensure the supplied RID
+        // is now excluded
+        if (rid.is_date) {
+            Component.date_to_ical(rid.to_date().previous(), &rrule.until);
+        } else {
+            Component.exact_time_to_ical(rid.to_exact_time().adjust_time(-1, Calendar.TimeUnit.SECOND),
+                &rrule.until);
+        }
+        
+        // COUNT and UNTIL are mutually exclusive in an RRULE ... COUNT can be reliably reset
+        // because the RID enforces a new de facto COUNT (assuming the RID originated from the UID's
+        // recurring instance; if not, the user has screwed up)
+        rrule.count = 0;
+        
+        rrule_property.set_rrule(rrule);
+        
+        // write it out ... essentially, this style of remove is actually an update
+        yield client.modify_object(ical_component, E.CalObjModType.THIS, cancellable);
     }
     
     public override async void import_icalendar_async(Component.iCalendar ical, Cancellable? cancellable = 
null)
diff --git a/src/component/component-date-time.vala b/src/component/component-date-time.vala
index d398f59..d52bd00 100644
--- a/src/component/component-date-time.vala
+++ b/src/component/component-date-time.vala
@@ -45,6 +45,13 @@ public class DateTime : BaseObject, Gee.Hashable<DateTime>, Gee.Comparable<DateT
     public bool is_date { get { return iCal.icaltime_is_date(dt) != 0; } }
     
     /**
+     * Returns the original iCalendar string representing the DATE/DATE-TIME property value.
+     *
+     * This does not include the iCal key string preceding the value, i.e. "DTSTAMP:"
+     */
+    public string value { get; private set; }
+    
+    /**
      * The DATE-TIME for the iCal component and property kind.
      */
     public iCal.icaltimetype dt;
@@ -114,6 +121,7 @@ public class DateTime : BaseObject, Gee.Hashable<DateTime>, Gee.Comparable<DateT
         }
         
         kind = ical_prop_kind;
+        value = prop.get_value_as_string();
     }
     
     /**
@@ -161,7 +169,7 @@ public class DateTime : BaseObject, Gee.Hashable<DateTime>, Gee.Comparable<DateT
      *
      * Returns null if { link is_date} is true.
      */
-    public Calendar.ExactTime? to_exact_time() throws CalendarError{
+    public Calendar.ExactTime? to_exact_time() throws CalendarError {
         if (is_date)
             return null;
         
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 9f0cf08..d16301f 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -45,6 +45,12 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
     [GtkChild]
     private Gtk.Button close_button;
     
+    [GtkChild]
+    private Gtk.Revealer button_box_revealer;
+    
+    [GtkChild]
+    private Gtk.Revealer remove_recurring_revealer;
+    
     private new Component.Event event;
     
     public ShowEvent() {
@@ -80,15 +86,16 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
         // description
         set_label(null, description_text, Markup.linkify(escape(event.description), linkify_delegate));
         
-        // don't current support updating or removing recurring events properly; see
+        // don't current support updating recurring events properly; see
         // https://bugzilla.gnome.org/show_bug.cgi?id=725786
-        // https://bugzilla.gnome.org/show_bug.cgi?id=725787
         bool read_only = event.calendar_source != null && event.calendar_source.read_only;
-        bool visible = !event.is_recurring && !read_only;
-        update_button.visible = visible;
-        update_button.no_show_all = !visible;
-        remove_button.visible = visible;
-        remove_button.no_show_all = !visible;
+        
+        bool updatable = !event.is_recurring && !read_only;
+        update_button.visible = updatable;
+        update_button.no_show_all = updatable;
+        
+        remove_button.visible = !read_only;
+        remove_button.no_show_all = !read_only;
     }
     
     private string? escape(string? plain) {
@@ -130,7 +137,40 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
     
     [GtkCallback]
     private void on_remove_button_clicked() {
-        remove_event_async.begin();
+        // If recurring (and so this is a generated instance of the VEVENT, not the VEVENT itself),
+        // reveal additional remove buttons
+        //
+        // TODO: Gtk.Stack would be a better widget for this animation, but it's unavailable in
+        // Glade as of GTK+ 3.12.
+        if (event.is_recurring) {
+            button_box_revealer.reveal_child = false;
+            remove_recurring_revealer.reveal_child = true;
+            
+            return;
+        }
+        
+        remove_events_async.begin(null, Backing.CalendarSource.AffectedInstances.ALL);
+    }
+    
+    [GtkCallback]
+    private void on_cancel_remove_recurring_button_clicked() {
+        button_box_revealer.reveal_child = true;
+        remove_recurring_revealer.reveal_child = false;
+    }
+    
+    [GtkCallback]
+    private void on_remove_this_button_clicked() {
+        remove_events_async.begin(event.rid, Backing.CalendarSource.AffectedInstances.THIS);
+    }
+    
+    [GtkCallback]
+    private void on_remove_future_button_clicked() {
+        remove_events_async.begin(event.rid, Backing.CalendarSource.AffectedInstances.THIS_AND_FUTURE);
+    }
+    
+    [GtkCallback]
+    private void on_remove_all_button_clicked() {
+        remove_events_async.begin(null, Backing.CalendarSource.AffectedInstances.ALL);
     }
     
     [GtkCallback]
@@ -143,22 +183,32 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
         notify_user_closed();
     }
     
-    private async void remove_event_async() {
+    private async void remove_events_async(Component.DateTime? rid,
+        Backing.CalendarSource.AffectedInstances affected) {
         Gdk.Cursor? cursor = Toolkit.set_busy(this);
         
         Error? remove_err = null;
         try {
-            yield event.calendar_source.remove_component_async(event.uid, null);
+            if (rid == null || affected == Backing.CalendarSource.AffectedInstances.ALL)
+                yield event.calendar_source.remove_all_instances_async(event.uid, null);
+            else
+                yield event.calendar_source.remove_instances_async(event.uid, rid, affected, null);
         } catch (Error err) {
             remove_err = err;
         }
         
         Toolkit.set_unbusy(this, cursor);
         
-        if (remove_err == null)
+        if (remove_err == null) {
             notify_success();
-        else
-            notify_failure(_("Unable to remove event: %s").printf(remove_err.message));
+        } else {
+            bool multiple = (rid != null) || (affected != Backing.CalendarSource.AffectedInstances.THIS);
+            
+            // No number is supplied because the number of events removed is indefinite in certain
+            // situations ... plural text should simply be for "more than one"
+            notify_failure(ngettext("Unable to remove event: %s", "Unable to remove events: %s",
+                !multiple ? 1 : 2).printf(remove_err.message));
+        }
     }
 }
 
diff --git a/src/rc/show-event.ui b/src/rc/show-event.ui
index 0763e8e..a164ec2 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">
@@ -26,8 +26,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>
@@ -68,83 +66,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>
-      <object class="GtkButtonBox" id="button_box">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">end</property>
-        <property name="margin_top">8</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <property name="spacing">8</property>
-        <property name="homogeneous">True</property>
-        <property name="baseline_position">bottom</property>
-        <property name="layout_style">end</property>
-        <child>
-          <object class="GtkButton" id="remove_button">
-            <property name="label" translatable="yes">_Remove</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">False</property>
-            <property name="use_underline">True</property>
-            <property name="image_position">bottom</property>
-            <signal name="clicked" handler="on_remove_button_clicked" object="CaliforniaHostShowEvent" 
swapped="no"/>
-            <style>
-              <class name="destructive-action"/>
-            </style>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkButton" id="update_button">
-            <property name="label" translatable="yes">_Update</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="can_default">True</property>
-            <property name="receives_default">True</property>
-            <property name="use_underline">True</property>
-            <property name="image_position">bottom</property>
-            <signal name="clicked" handler="on_update_button_clicked" object="CaliforniaHostShowEvent" 
swapped="no"/>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkButton" id="close_button">
-            <property name="label" translatable="yes">_Close</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="can_default">True</property>
-            <property name="has_default">True</property>
-            <property name="receives_default">True</property>
-            <property name="use_underline">True</property>
-            <property name="image_position">bottom</property>
-            <signal name="clicked" handler="on_close_button_clicked" object="CaliforniaHostShowEvent" 
swapped="no"/>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="pack_type">end</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
-      </object>
-      <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>
     <child>
@@ -167,8 +88,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>
@@ -185,8 +104,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>
@@ -201,8 +118,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>
@@ -217,17 +132,227 @@
           <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>
       </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>
+      <object class="GtkBox" id="box2">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkRevealer" id="remove_recurring_revealer">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="transition_type">slide-right</property>
+            <property name="transition_duration">500</property>
+            <child>
+              <object class="GtkBox" id="box1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">4</property>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Remove in this series:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButtonBox" id="remove_recurring_button_box">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">8</property>
+                    <property name="baseline_position">bottom</property>
+                    <property name="layout_style">end</property>
+                    <child>
+                      <object class="GtkButton" id="remove_this_button">
+                        <property name="label" translatable="yes">_This Event</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="use_underline">True</property>
+                        <signal name="clicked" handler="on_remove_this_button_clicked" 
object="CaliforniaHostShowEvent" swapped="no"/>
+                        <style>
+                          <class name="destructive-action"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="remove_future_button">
+                        <property name="label" translatable="yes">This and _Future Events</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="use_underline">True</property>
+                        <signal name="clicked" handler="on_remove_future_button_clicked" 
object="CaliforniaHostShowEvent" swapped="no"/>
+                        <style>
+                          <class name="destructive-action"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="remove_all_button">
+                        <property name="label" translatable="yes">_All Events</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="use_underline">True</property>
+                        <property name="xalign">0.56000000238418579</property>
+                        <property name="yalign">0.51999998092651367</property>
+                        <signal name="clicked" handler="on_remove_all_button_clicked" 
object="CaliforniaHostShowEvent" swapped="no"/>
+                        <style>
+                          <class name="destructive-action"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="cancel_remove_recurring_button">
+                        <property name="label" translatable="yes">_Cancel</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="use_underline">True</property>
+                        <signal name="clicked" handler="on_cancel_remove_recurring_button_clicked" 
object="CaliforniaHostShowEvent" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">4</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkRevealer" id="button_box_revealer">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="transition_type">slide-left</property>
+            <property name="transition_duration">500</property>
+            <property name="reveal_child">True</property>
+            <child>
+              <object class="GtkButtonBox" id="button_box">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="valign">end</property>
+                <property name="margin_top">8</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="spacing">8</property>
+                <property name="baseline_position">bottom</property>
+                <property name="layout_style">end</property>
+                <child>
+                  <object class="GtkButton" id="remove_button">
+                    <property name="label" translatable="yes">_Remove</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="image_position">bottom</property>
+                    <signal name="clicked" handler="on_remove_button_clicked" 
object="CaliforniaHostShowEvent" swapped="no"/>
+                    <style>
+                      <class name="destructive-action"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="update_button">
+                    <property name="label" translatable="yes">_Update</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="can_default">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_underline">True</property>
+                    <property name="image_position">bottom</property>
+                    <signal name="clicked" handler="on_update_button_clicked" 
object="CaliforniaHostShowEvent" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="close_button">
+                    <property name="label" translatable="yes">_Close</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="can_default">True</property>
+                    <property name="has_default">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_underline">True</property>
+                    <property name="image_position">bottom</property>
+                    <signal name="clicked" handler="on_close_button_clicked" 
object="CaliforniaHostShowEvent" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="pack_type">end</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">3</property>
+      </packing>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
   </template>
 </interface>
diff --git a/vapi/libecal-1.2.vapi b/vapi/libecal-1.2.vapi
index e414c46..4d9a21a 100644
--- a/vapi/libecal-1.2.vapi
+++ b/vapi/libecal-1.2.vapi
@@ -2,6 +2,14 @@
 
 [CCode (cprefix = "E", gir_namespace = "ECalendar", gir_version = "1.2", lower_case_cprefix = "e_")]
 namespace E {
+       namespace Util {
+               [CCode (cheader_filename = "libecal/libecal.h", cname = 
"e_cal_util_component_has_recurrences")]
+               public static bool component_has_recurrences (iCal.icalcomponent ical_component);
+               [CCode (cheader_filename = "libecal/libecal.h", cname = "e_cal_util_component_is_instance")]
+               public static bool component_is_instance (iCal.icalcomponent ical_component);
+               [CCode (cheader_filename = "libecal/libecal.h", cname = "e_cal_util_remove_instances")]
+               public static bool remove_instances (iCal.icalcomponent ical_component, iCal.icaltimetype 
rid, E.CalObjModType mod);
+       }
        [CCode (cheader_filename = "libecal/libecal.h", type_id = "e_cal_client_get_type ()")]
        public class CalClient : E.Client, GLib.Initable, GLib.AsyncInitable, E.TimezoneCache {
                [CCode (has_construct_function = false)]
@@ -43,7 +51,8 @@ namespace E {
                public async bool get_free_busy (ulong start, ulong end, GLib.SList users, GLib.Cancellable? 
cancellable) throws GLib.Error;
                public bool get_free_busy_sync (ulong start, ulong end, GLib.SList users, GLib.Cancellable? 
cancellable) throws GLib.Error;
                public unowned string get_local_attachment_store ();
-               public async bool get_object (string uid, string rid, GLib.Cancellable? cancellable, out 
unowned iCal.icalcomponent out_icalcomp) throws GLib.Error;
+               [CCode (finish_name = "e_cal_client_get_object_finish")]
+               public async void get_object (string uid, string? rid, GLib.Cancellable? cancellable, out 
iCal.icalcomponent out_icalcomp) throws GLib.Error;
                public async bool get_object_list (string sexp, GLib.Cancellable? cancellable) throws 
GLib.Error;
                public async bool get_object_list_as_comps (string sexp, GLib.Cancellable? cancellable) 
throws GLib.Error;
                public bool get_object_list_as_comps_sync (string sexp, GLib.SList out_ecalcomps, 
GLib.Cancellable? cancellable) throws GLib.Error;
@@ -248,6 +257,17 @@ namespace E {
                public void set_repeat (E.CalComponentAlarmRepeat repeat);
                public void set_trigger (E.CalComponentAlarmTrigger trigger);
        }
+       [CCode (cheader_filename = "libecal/libecal.h", copy_function = "e_cal_component_id_copy", 
free_function = "e_cal_component_free_id")]
+       [Compact]
+       public class CalComponentId {
+               public weak string rid;
+               public weak string uid;
+               [CCode (has_construct_function = false)]
+               public CalComponentId (string uid, string rid);
+               public E.CalComponentId copy ();
+               public bool equal (E.CalComponentId id2);
+               public uint hash ();
+       }
        [CCode (cheader_filename = "libecal/libecal.h")]
        public interface TimezoneCache : GLib.Object {
                public abstract unowned GLib.List list_timezones ();
@@ -301,16 +321,6 @@ namespace E {
                public weak string tzid;
        }
        [CCode (cheader_filename = "libecal/libecal.h")]
-       public struct CalComponentId {
-               public weak string uid;
-               public weak string rid;
-               [CCode (has_construct_function = false)]
-               public CalComponentId (string uid, string rid);
-               public E.CalComponentId copy ();
-               public bool equal (E.CalComponentId id2);
-               public uint hash ();
-       }
-       [CCode (cheader_filename = "libecal/libecal.h")]
        public struct CalComponentOrganizer {
                public weak string value;
                public weak string sentby;
diff --git a/vapi/libecal-1.2/libecal-1.2-custom.vala b/vapi/libecal-1.2/libecal-1.2-custom.vala
index e69de29..791263b 100644
--- a/vapi/libecal-1.2/libecal-1.2-custom.vala
+++ b/vapi/libecal-1.2/libecal-1.2-custom.vala
@@ -0,0 +1,18 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace E.Util {
+
+[CCode (cheader_filename="libecal/libecal.h", cname="e_cal_util_component_has_recurrences")]
+public bool component_has_recurrences(iCal.icalcomponent ical_component);
+
+[CCode (cheader_filename="libecal/libecal.h", cname="e_cal_util_component_is_instance")]
+public bool component_is_instance(iCal.icalcomponent ical_component);
+
+[CCode (cheader_filename="libecal/libecal.h", cname="e_cal_util_remove_instances")]
+public bool remove_instances(iCal.icalcomponent ical_component, iCal.icaltimetype rid, E.CalObjModType mod);
+
+}
diff --git a/vapi/libecal-1.2/libecal-1.2.metadata b/vapi/libecal-1.2/libecal-1.2.metadata
index 0d82150..e93a428 100644
--- a/vapi/libecal-1.2/libecal-1.2.metadata
+++ b/vapi/libecal-1.2/libecal-1.2.metadata
@@ -82,9 +82,12 @@ e_cal_client_get_free_busy async="1"
 e_cal_client_get_free_busy.cancellable nullable="1"
 e_cal_client_get_free_busy_sync.cancellable nullable="1"
 
-e_cal_client_get_object async="1"
+e_cal_client_get_object async="1" finish_name="e_cal_client_get_object_finish"
+e_cal_client_get_object.rid nullable="1"
 e_cal_client_get_object.cancellable nullable="1"
-e_cal_client_get_object_finish.icalcomp is_out="1" value_owned="1"
+
+e_cal_client_get_object_finish type_name="void"
+e_cal_client_get_object_finish.out_icalcomp is_out="1" transfer_ownership="1"
 
 e_cal_client_get_object_list async="1"
 e_cal_client_get_object_list.cancellable nullable="1"
@@ -234,7 +237,9 @@ ECalComponentAttendee is_value_type="1"
 ECalComponentDateTime is_value_type="1" has_destroy_function="0"
 ECalComponentDateTime.value type_name="iCal.icaltimetype*"
 
-ECalComponentId is_value_type="1"
+ECalComponentId is_value_type="0" free_function="e_cal_component_free_id"
+e_cal_component_id_copy transfer_ownership="1"
+
 ECalComponentOrganizer is_value_type="1"
 ECalComponentPeriod is_value_type="1"
 ECalComponentPeriod.start type_name="iCal.icaltimetype"
diff --git a/vapi/libical.vapi b/vapi/libical.vapi
index 2bf00a2..9b50119 100644
--- a/vapi/libical.vapi
+++ b/vapi/libical.vapi
@@ -1678,7 +1678,8 @@ namespace iCal {
        [Compact]
        public class sspm_minor_type {
        }
-       [CCode (cheader_filename = "libical/ical.h")]
+       [CCode (cheader_filename = "libical/ical.h", cname="struct icaldatetimeperiodtype")]
+       [SimpleType]
        public struct icaldatetimeperiodtype {
                public iCal.icaltimetype time;
                public iCal.icalperiodtype period;
@@ -1688,7 +1689,8 @@ namespace iCal {
                public float lat;
                public float lon;
        }
-       [CCode (cheader_filename = "libical/ical.h")]
+       [CCode (cheader_filename = "libical/ical.h", cname="struct icalperiodtype")]
+       [SimpleType]
        public struct icalperiodtype {
                public iCal.icaltimetype start;
                public iCal.icaltimetype end;



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