[california] Display recurring events: Closes bgo#725788
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california] Display recurring events: Closes bgo#725788
- Date: Sat, 8 Mar 2014 03:48:27 +0000 (UTC)
commit db84572eed0a5a3ec2141eb8ecd4cb47975357e2
Author: Jim Nelson <jim yorba org>
Date: Fri Mar 7 19:46:55 2014 -0800
Display recurring events: Closes bgo#725788
Recurring events are now handled properly internally, although
creating/updating/removing will have to wait. Thus, an instance
of a recurring event can only be examined now, cannot be updated.
This also fixes a bug where the month view only displayed events
within the month but not on days outside of the month that were
in view.
src/Makefile.am | 1 +
.../backing-calendar-source-subscription.vala | 152 +++++++++++---------
.../backing-eds-calendar-source-subscription.vala | 8 +-
src/backing/eds/backing-eds-calendar-source.vala | 2 +-
src/calendar/calendar-span.vala | 7 +
src/component/component-date-time.vala | 46 ++++++-
src/component/component-event.vala | 56 +++++++-
src/component/component-instance.vala | 110 +++++++++++++--
src/host/host-show-event.vala | 15 ++
src/util/util-memory.vala | 27 ++++
src/view/month/month-controllable.vala | 43 ++++--
11 files changed, 369 insertions(+), 98 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index e8942d2..fd2c362 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -67,6 +67,7 @@ california_VALASOURCES = \
host/host-popup.vala \
host/host-show-event.vala \
\
+ util/util-memory.vala \
util/util-string.vala \
\
view/view.vala \
diff --git a/src/backing/backing-calendar-source-subscription.vala
b/src/backing/backing-calendar-source-subscription.vala
index cdbac28..90245d5 100644
--- a/src/backing/backing-calendar-source-subscription.vala
+++ b/src/backing/backing-calendar-source-subscription.vala
@@ -41,35 +41,36 @@ public abstract class CalendarSourceSubscription : BaseObject {
public bool active { get; protected set; default = false; }
/**
- * Fired as existing { link Component.Event}s are discovered when starting a subscription.
+ * Fired as existing { link Component.Instance}s are discovered when starting a subscription.
*
* This is fired while { link start} is working, either in the foreground or in the background.
* It won't fire until start() is invoked.
*/
- public signal void event_discovered(Component.Event event);
+ public signal void instance_discovered(Component.Instance instance);
/**
- * Indicates that an event within the { link window} has been added to the calendar.
+ * Indicates that an { link Instance} within the { link window} has been added to the calendar.
*
* The signal is fired for both local additions (added through this interface) and remote
* additions.
*
* This signal won't fire until { link start} is called.
*/
- public signal void event_added(Component.Event event);
+ public signal void instance_added(Component.Instance instance);
/**
- * Indicates that an event within the { link date_window} has been removed from the calendar.
+ * Indicates that an { link Instance} within the { link date_window} has been removed from the
+ * calendar.
*
* The signal is fired for both local removals (added through this interface) and remote
* removals.
*
* This signal won't fire until { link start} is called.
*/
- public signal void event_removed(Component.Event event);
+ public signal void instance_removed(Component.Instance instance);
/**
- * Indicates that an event within the { link date_window} has been altered.
+ * Indicates that an { link Instance} within the { link date_window} has been altered.
*
* This is fired after the alterations have been made. Since the { link Component.Instance}s
* are mutable, it's possible to monitor their properties for changes and be notified that way.
@@ -79,13 +80,13 @@ public abstract class CalendarSourceSubscription : BaseObject {
*
* This signal won't fire until { link start} is called.
*/
- public signal void event_altered(Component.Event event);
+ public signal void instance_altered(Component.Instance instance);
/**
- * Indicates than the event within the { link date_window} has been dropped due to the
- * { link Source} going unavailable.
+ * Indicates than the { link Instance} within the { link date_window} has been dropped due to
+ * the { link Source} going unavailable.
*
- * Generally all the subscription's events will be reported one after another, but this
+ * Generally all the subscription's instances will be reported one after another, but this
* shouldn't be relied upon.
*
* Since the Source is now unavailable, this indicates that the Subscription will not be
@@ -96,7 +97,7 @@ public abstract class CalendarSourceSubscription : BaseObject {
* best course is to call { link Source.set_unavailable} and override
* { link notify_events_dropped} to perform internal bookkeeping.
*/
- public signal void event_dropped(Component.Event event);
+ public signal void instance_dropped(Component.Instance instance);
/**
* Fired if { link start} failed.
@@ -110,8 +111,10 @@ public abstract class CalendarSourceSubscription : BaseObject {
*/
public signal void start_failed(Error err);
- private Gee.HashMap<Component.UID, Component.Event> events = new Gee.HashMap<
- Component.UID, Component.Event>();
+ // Although Component.Instance has no simple notion of one UID for multiple instances, its
+ // subclasses (i.e. Event) do
+ private Gee.HashMultiMap<Component.UID, Component.Instance> instances = new Gee.HashMultiMap<
+ Component.UID, Component.Instance>();
protected CalendarSourceSubscription(CalendarSource calendar, Calendar.ExactTimeSpan window) {
this.calendar = calendar;
@@ -121,7 +124,8 @@ public abstract class CalendarSourceSubscription : BaseObject {
}
/**
- * Add a pre-existing { link Component.Event} to the subscription and notify subscribers.
+ * Add a @link Component.Instance} discovered while starting the subscription to the
+ * internal collection of instances and notify subscribers.
*
* As with the other notify_*() methods, subclasses should invoke this method to fire the
* signal rather than do it directly. This gives { link CalenderSourceSubscription} the
@@ -129,68 +133,89 @@ public abstract class CalendarSourceSubscription : BaseObject {
*
* It can also be overridden by a subclass to take action before or after the signal is fired.
*
- * @see event_discovered
+ * @see instance_discovered
*/
- protected virtual void notify_event_discovered(Component.Event event) {
- if (!events.has_key(event.uid)) {
- events.set(event.uid, event);
- event_discovered(event);
- } else {
- debug("Cannot add discovered event %s to %s: already known", event.to_string(), to_string());
- }
+ protected virtual void notify_instance_discovered(Component.Instance instance) {
+ if (add_instance(instance))
+ instance_discovered(instance);
+ else
+ debug("Cannot add discovered component %s to %s: already known", instance.to_string(),
to_string());
}
/**
- * Add a new { link Component.Event} to the subscription and notify subscribers.
+ * Add a new { link Component.Instance} to the subscription and notify subscribers.
*
- * @see notify_event_discovered
- * @see event_added
+ * @see notify_instance_discovered
+ * @see instance_added
*/
- protected virtual void notify_event_added(Component.Event event) {
- if (!events.has_key(event.uid)) {
- events.set(event.uid, event);
- event_added(event);
- } else {
- debug("Cannot add event %s to %s: already known", event.to_string(), to_string());
- }
+ protected virtual void notify_instance_added(Component.Instance instance) {
+ if (add_instance(instance))
+ instance_added(instance);
+ else
+ debug("Cannot add component %s to %s: already known", instance.to_string(), to_string());
}
/**
- * Remove an { link Component.Event} from the subscription and notify subscribers.
+ * Remove an { link Component.Instance} from the subscription and notify subscribers.
*
- * @see notify_event_discovered
- * @see event_removed
+ * @see notify_instance_discovered
+ * @see instance_removed
*/
- protected virtual void notify_event_removed(Component.UID uid) {
- Component.Event? event;
- if (events.unset(uid, out event))
- event_removed(event);
- else
+ protected virtual void notify_instance_removed(Component.UID uid) {
+ Gee.Collection<Component.Instance> removed_instances;
+ if (remove_instance(uid, out removed_instances)) {
+ foreach (Component.Instance instance in removed_instances)
+ instance_removed(instance);
+ } else {
debug("Cannot remove UID %s from %s: not known", uid.to_string(), to_string());
+ }
}
/**
- * Update an altered { link Component.Event} and notify subscribers.
+ * Update an altered { link Component.Instance} and notify subscribers.
*
- * @see notify_event_discovered
- * @see event_altered
+ * @see notify_instance_discovered
+ * @see instance_altered
*/
- protected virtual void notify_event_altered(Component.Event event) {
- if (events.has_key(event.uid))
- event_altered(event);
+ protected virtual void notify_instance_altered(Component.Instance instance) {
+ if (instances.contains(instance.uid))
+ instance_altered(instance);
else
- debug("Cannot notify altered event %s in %s: not known", event.to_string(), to_string());
+ debug("Cannot notify altered component %s in %s: not known", instance.to_string(), to_string());
}
/**
- * Notify that the { link Component.Event}s have been dropped due to the { link Source} going
+ * Notify that the { link Component.Instance}s have been dropped due to the { link Source} going
* unavailable.
*/
- protected virtual void notify_event_dropped(Component.Event event) {
- if (this.events.unset(event.uid))
- event_dropped(event);
- else
- debug("Cannot notify dropped event %s in %s: not known", event.to_string(), to_string());
+ protected virtual void notify_instance_dropped(Component.Instance instance) {
+ Gee.Collection<Component.Instance> removed_instances;
+ if (remove_instance(instance.uid, out removed_instances)) {
+ foreach (Component.Instance removed_instance in removed_instances)
+ instance_dropped(removed_instance);
+ } else {
+ debug("Cannot notify dropped component %s in %s: not known", instance.to_string(), to_string());
+ }
+ }
+
+ private bool add_instance(Component.Instance instance) {
+ bool already_exists = instances.get(instance.uid).contains(instance);
+ if (!already_exists)
+ instances.set(instance.uid, instance);
+
+ return !already_exists;
+ }
+
+ private bool remove_instance(Component.UID uid, out Gee.Collection<Component.Instance>
removed_instances) {
+ bool removed = instances.contains(uid);
+ if (removed) {
+ removed_instances = instances.get(uid);
+ instances.remove_all(uid);
+ } else {
+ removed_instances = new Gee.ArrayList<Component.Instance>();
+ }
+
+ return removed;
}
/**
@@ -228,24 +253,19 @@ public abstract class CalendarSourceSubscription : BaseObject {
if (calendar.is_available)
return;
- foreach (Component.Event event in events.values.to_array())
- notify_event_dropped(event);
+ // Use to_array() so no iteration troubles when notify_instance_dropped removes it from
+ // the multimap
+ foreach (Component.Instance instance in instances.get_values().to_array())
+ notify_instance_dropped(instance);
}
/**
- * Returns an { link Component.Instance} for the { link Component.UID}.
+ * Returns all { link Component.Instance}s for the { link Component.UID}.
*
* @returns null if the UID has not been seen.
*/
- public Component.Instance? for_uid(Component.UID uid) {
- return events.get(uid);
- }
-
- /**
- * Returns a read-only Map of all known { link Component.Event}s.
- */
- public Gee.Map<Component.UID, Component.Event> get_events() {
- return events.read_only_view;
+ public Gee.Collection<Component.Instance>? for_uid(Component.UID uid) {
+ return instances.contains(uid) ? instances.get(uid) : null;
}
public override string to_string() {
diff --git a/src/backing/eds/backing-eds-calendar-source-subscription.vala
b/src/backing/eds/backing-eds-calendar-source-subscription.vala
index 050b3e8..e4faf20 100644
--- a/src/backing/eds/backing-eds-calendar-source-subscription.vala
+++ b/src/backing/eds/backing-eds-calendar-source-subscription.vala
@@ -97,7 +97,7 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
Component.Event? event = Component.Instance.convert(calendar, eds_component.get_icalcomponent())
as Component.Event;
if (event != null)
- notify_event_discovered(event);
+ notify_instance_discovered(event);
} catch (Error err) {
debug("Unable to generate discovered event for %s: %s", to_string(), err.message);
}
@@ -115,7 +115,7 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
try {
Component.Event? event = Component.Instance.convert(calendar, ical_component) as
Component.Event;
if (event != null)
- notify_event_added(event);
+ notify_instance_added(event);
} catch (Error err) {
debug("Unable to generate added event for %s: %s", to_string(), err.message);
}
@@ -136,13 +136,13 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
debug("Unable to update event %s: %s", event.to_string(), err.message);
}
- notify_event_altered(event);
+ notify_instance_altered(event);
}
}
private void on_objects_removed(SList<weak E.CalComponentId?> ids) {
foreach (weak E.CalComponentId id in ids)
- notify_event_removed(new Component.UID(id.uid));
+ notify_instance_removed(new Component.UID(id.uid));
}
}
diff --git a/src/backing/eds/backing-eds-calendar-source.vala
b/src/backing/eds/backing-eds-calendar-source.vala
index fafb524..d4e6ecc 100644
--- a/src/backing/eds/backing-eds-calendar-source.vala
+++ b/src/backing/eds/backing-eds-calendar-source.vala
@@ -62,7 +62,7 @@ internal class EdsCalendarSource : CalendarSource {
string? uid;
client.create_object_sync(instance.ical_component, out uid, cancellable);
- return (uid != null && uid[0] != '\0') ? new Component.UID(uid) : null;
+ return !String.is_empty(uid) ? new Component.UID(uid) : null;
}
public override async void update_component_async(Component.Instance instance,
diff --git a/src/calendar/calendar-span.vala b/src/calendar/calendar-span.vala
index 187bd88..416ecad 100644
--- a/src/calendar/calendar-span.vala
+++ b/src/calendar/calendar-span.vala
@@ -66,6 +66,13 @@ public interface Span<G> : BaseObject, Collection.SimpleIterable<G> {
}
/**
+ * Converts the { link Span} into a { link DateSpan}.
+ */
+ public DateSpan to_date_span() {
+ return new DateSpan(start_date, end_date);
+ }
+
+ /**
* true if the { link Span} contains the specified { link Date}.
*
* This is named to conform to Vala's rule for automatic syntax support. This allows for the
diff --git a/src/component/component-date-time.vala b/src/component/component-date-time.vala
index 4a23b96..56fcb63 100644
--- a/src/component/component-date-time.vala
+++ b/src/component/component-date-time.vala
@@ -6,7 +6,15 @@
namespace California.Component {
-public class DateTime : BaseObject {
+/**
+ * An immutable representation of iCal's DATE and DATE-TIME property, which are often used
+ * interchangeably.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.3.4]] and
+ * [[https://tools.ietf.org/html/rfc5545#section-3.3.5]]
+ */
+
+public class DateTime : BaseObject, Gee.Hashable<DateTime>, Gee.Comparable<DateTime> {
/**
* The TZID for the iCal component and property kind.
*
@@ -42,10 +50,21 @@ public class DateTime : BaseObject {
public iCal.icaltimetype dt;
/**
+ * The iCal property type ("kind") for { link dt}.
+ */
+ public iCal.icalproperty_kind kind;
+
+ /**
* Creates a new { link DateTime} for the iCal component of the property kind.
*
- * Note that DTSTART_PROPERTY, DTEND_PROPERTY, and DTSTAMP_PROPERTY are the only properties
- * currently supported.
+ * Note that the only properties currently supported are:
+ * * DTSTART_PROPERTY
+ * * DTEND_PROPERTY
+ * * DTSTAMP_PROPERTY
+ * * RECURRENCEID_PROPERTY
+ *
+ * @throws ComponentError.UNAVAILABLE if not found
+ * @throws ComponentError.INVALID if not a valid DATE or DATE-TIME
*/
public DateTime(iCal.icalcomponent ical_component, iCal.icalproperty_kind ical_prop_kind)
throws ComponentError {
@@ -66,6 +85,10 @@ public class DateTime : BaseObject {
dt = prop.get_dtend();
break;
+ case iCal.icalproperty_kind.RECURRENCEID_PROPERTY:
+ dt = prop.get_recurrenceid();
+ break;
+
default:
assert_not_reached();
}
@@ -79,6 +102,8 @@ public class DateTime : BaseObject {
unowned iCal.icalparameter? param = prop.get_first_parameter(iCal.icalparameter_kind.TZID_PARAMETER);
if (param != null)
zone = new Calendar.OlsonZone(param.get_tzid());
+
+ kind = ical_prop_kind;
}
/**
@@ -186,6 +211,21 @@ public class DateTime : BaseObject {
exact_time_span = null;
}
+ public int compare_to(Component.DateTime other) {
+ return (this != other) ? iCal.icaltime_compare(dt, other.dt) : 0;
+ }
+
+ public bool equal_to(Component.DateTime other) {
+ return (this != other) ? iCal.icaltime_compare(dt, other.dt) == 0 : true;
+ }
+
+ public uint hash() {
+ // iCal doesn't supply a hashing function, so here goes
+ iCal.icaltimetype utc = iCal.icaltime_convert_to_zone(dt, iCal.icaltimezone.get_utc_timezone());
+
+ return Memory.hash(&utc, sizeof(iCal.icaltimetype));
+ }
+
public override string to_string() {
try {
if (is_date)
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 7517437..3aea5fd 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -241,12 +241,66 @@ public class Event : Instance, Gee.Comparable<Event> {
if (compare != 0)
return compare;
+ // if recurring, go by sequence number, as the UID and RID are the same for all instances
+ if (is_recurring) {
+ compare = sequence - other.sequence;
+ if (compare != 0)
+ return compare;
+ }
+
// stabilize with UIDs
return uid.compare_to(other.uid);
}
+ public override bool equal_to(Component.Instance other) {
+ Component.Event? other_event = other as Component.Event;
+ if (other_event == null)
+ return false;
+
+ if (this == other_event)
+ return true;
+
+ if (is_recurring != other_event.is_recurring)
+ return false;
+
+ if (is_recurring && !rid.equal_to(other_event.rid))
+ return false;
+
+ if (sequence != other_event.sequence)
+ return false;
+
+ if (exact_time_span != null && other_event.exact_time_span != null
+ && !exact_time_span.equal_to(other_event.exact_time_span)) {
+ return false;
+ }
+
+ if (date_span != null && other_event.date_span != null
+ && !date_span.equal_to(other_event.date_span)) {
+ return false;
+ }
+
+ if (exact_time_span != null
+ && !new Calendar.DateSpan.from_exact_time_span(exact_time_span).equal_to(other_event.date_span))
{
+ return false;
+ }
+
+ if (!date_span.equal_to(new Calendar.DateSpan.from_exact_time_span(other_event.exact_time_span))) {
+ return false;
+ }
+
+ return base.equal_to(other);
+ }
+
+ public override uint hash() {
+ return uid.hash() ^ ((rid != null) ? rid.hash() : 0) ^ sequence;
+ }
+
public override string to_string() {
- return "Event \"%s\" (%s)".printf(summary,
+ return "Event %s/rid=%s/%d \"%s\" (%s)".printf(
+ uid.to_string(),
+ (rid != null) ? rid.to_string() : "(no-recurring)",
+ sequence,
+ summary,
exact_time_span != null ? exact_time_span.to_string() : date_span.to_string());
}
}
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 307af70..f9769d7 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -9,19 +9,17 @@ namespace California.Component {
/**
* A mutable iCalendar component that has a definitive instance within a calendar.
*
- * By "instance", this means { link Event}s, To-Do's, Journals, and Free/Busy components. In other
- * words, components which allocate a specific item within a calendar. Some of thse
- * components may be recurring, in which case any particular instance is merely a generated
- * representation of that recurrance.
+ * By "instance", this means { link Event}s, To-do's, and Journal components. In other words,
+ * components which allocate a specific amount of time within a calendar. (Free/Busy does allow
+ * for time to be published/reserved, but this implementation doesn't deal with that component.)
*
* Mutability is achieved two separate ways. One is to call { link full_update} supplying a new
- * iCal component to update an existing one (verified by UID and RID). This will update all
- * fields.
+ * iCal component to update an existing one (verified by UID). This will update all fields.
*
* The second is to update the mutable properties themselves, which will then update the underlying
* iCal component.
*
- * Alarms are contained within Instance components. Timezones are handled separately.
+ * Alarms will be contained within Instance components. Timezones are handled separately.
*
* Instance also offers a number of methods to convert iCal structures into internal objects.
*/
@@ -31,6 +29,8 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
public const string PROP_DTSTAMP = "dtstamp";
public const string PROP_UID = "uid";
public const string PROP_ICAL_COMPONENT = "ical-component";
+ public const string PROP_RID = "rid";
+ public const string PROP_SEQUENCE = "sequence";
protected const string PROP_IN_FULL_UPDATE = "in-full-update";
@@ -61,6 +61,27 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
public UID uid { get; private set; }
/**
+ * The RECURRENCE-ID of a recurring component.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.8.4.4]]
+ */
+ public Component.DateTime? rid { get; set; default = null; }
+
+ /**
+ * Returns true if the { link Recurrable} is in fact a recurring instance.
+ *
+ * @see rid
+ */
+ public bool is_recurring { get { return rid != null; } }
+
+ /**
+ * The SEQUENCE of a VEVENT, VTODO, or VJOURNAL.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.8.7.4]]
+ */
+ public int sequence { get; set; default = 0; }
+
+ /**
* The iCal component being represented by this { link Instance}.
*/
private iCal.icalcomponent _ical_component;
@@ -110,6 +131,9 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
uid = new UID(_ical_component.get_uid());
full_update(_ical_component);
+
+ // watch for property changes and update ical_component when happens
+ notify.connect(on_notify);
}
/**
@@ -121,6 +145,8 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
_ical_component = new iCal.icalcomponent(kind);
uid = Component.UID.generate();
_ical_component.set_uid(uid.value);
+
+ notify.connect(on_notify);
}
/**
@@ -187,10 +213,50 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
if (!dt_stamp.is_date)
dtstamp = dt_stamp.to_exact_time();
+ try {
+ rid = new DateTime(ical_component, iCal.icalproperty_kind.RECURRENCEID_PROPERTY);
+ } catch (ComponentError comperr) {
+ // ignore if unavailable
+ if (!(comperr is ComponentError.UNAVAILABLE))
+ throw comperr;
+
+ rid = null;
+ }
+
+ sequence = ical_component.get_sequence();
+
+ // save own copy of component; no ownership transferrance w/ current bindings
if (_ical_component != ical_component)
_ical_component = ical_component.clone();
}
+ private void on_notify(ParamSpec pspec) {
+ // don't worry if in full update, that call is supposed to update properties
+ if (in_full_update)
+ return;
+
+ bool altered = true;
+ switch (pspec.name) {
+ case PROP_RID:
+ if (rid == null)
+ remove_all_properties(iCal.icalproperty_kind.RECURRENCEID_PROPERTY);
+ else
+ ical_component.set_recurrenceid(rid.dt);
+ break;
+
+ case PROP_SEQUENCE:
+ ical_component.set_sequence(sequence);
+ break;
+
+ default:
+ altered = false;
+ break;
+ }
+
+ if (altered)
+ notify_altered(false);
+ }
+
/**
* Returns an appropriate { link Component} instance for the iCalendar component.
*
@@ -268,14 +334,36 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
}
/**
- * Equality is defined as { link Component.Instance}s having the same UID (and, when available,
- * RID), nothing more.
+ * Convenience method to remove all instances of a property from { link ical_component}.
+ *
+ * @returns The number of properties found with the specified kind.
*/
- public bool equal_to(Instance other) {
+ protected int remove_all_properties(iCal.icalproperty_kind kind) {
+ int count = 0;
+ unowned iCal.icalproperty? prop;
+ while ((prop = ical_component.get_first_property(kind)) != null) {
+ ical_component.remove_property(prop);
+ count++;
+ }
+
+ return count;
+ }
+
+ /**
+ * Equality is defined as { link Component.Instance}s having the same UID.
+ *
+ * Subclasses should override this and { link hash} if more definite equality is necessary.
+ */
+ public virtual bool equal_to(Instance other) {
return (this != other) ? uid.equal_to(other.uid) : true;
}
- public uint hash() {
+ /**
+ * Hash is calculated using the { link Instance} { link UID}.
+ *
+ * Subclasses should override if they override { link equal_to}.
+ */
+ public virtual uint hash() {
return uid.hash();
}
}
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index afb5367..b55c0ac 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -11,6 +11,12 @@ public class ShowEvent : Gtk.Grid {
[GtkChild]
private Gtk.Label text_label;
+ [GtkChild]
+ private Gtk.Button update_button;
+
+ [GtkChild]
+ private Gtk.Button remove_button;
+
private new Component.Event event;
public signal void remove_event(Component.Event event);
@@ -83,6 +89,15 @@ public class ShowEvent : Gtk.Grid {
add_lf_lf(builder).append_printf("<small>%s</small>", Markup.escape_text(span));
text_label.label = builder.str;
+
+ // don't current support updating or removing recurring events properly; see
+ // https://bugzilla.gnome.org/show_bug.cgi?id=725786
+ // https://bugzilla.gnome.org/show_bug.cgi?id=725787
+ bool visible = !event.is_recurring;
+ update_button.visible = visible;
+ update_button.no_show_all = !visible;
+ remove_button.visible = visible;
+ remove_button.no_show_all = !visible;
}
// Adds two linefeeds if there's existing text
diff --git a/src/util/util-memory.vala b/src/util/util-memory.vala
new file mode 100644
index 0000000..93981f6
--- /dev/null
+++ b/src/util/util-memory.vala
@@ -0,0 +1,27 @@
+/* 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 California.Memory {
+
+/**
+ * A rotating-XOR hash that can be used to hash memory buffers of any size.
+ */
+public uint hash(void *ptr, size_t bytes) {
+ if (bytes == 0)
+ return 0;
+
+ uint8 *u8 = (uint8 *) ptr;
+
+ // initialize hash to first byte value and then rotate-XOR from there
+ uint hash = *u8;
+ for (int ctr = 1; ctr < bytes; ctr++)
+ hash = (hash << 4) ^ (hash >> 28) ^ (*u8++);
+
+ return hash;
+}
+
+}
+
diff --git a/src/view/month/month-controllable.vala b/src/view/month/month-controllable.vala
index 5c7ed5a..a1a0e74 100644
--- a/src/view/month/month-controllable.vala
+++ b/src/view/month/month-controllable.vala
@@ -37,7 +37,7 @@ public class Controllable : Gtk.Grid, View.Controllable {
/**
* @inheritDoc
*/
- public Calendar.FirstOfWeek first_of_week { get; set; }
+ public Calendar.FirstOfWeek first_of_week { get; set; default = Calendar.FirstOfWeek.SUNDAY; }
/**
* Show days outside the current month.
@@ -45,6 +45,11 @@ public class Controllable : Gtk.Grid, View.Controllable {
public bool show_outside_month { get; set; default = true; }
/**
+ * The span of dates being displayed.
+ */
+ public Calendar.DateSpan window { get; private set; }
+
+ /**
* @inheritDoc
*/
public string current_label { get; protected set; }
@@ -109,7 +114,6 @@ public class Controllable : Gtk.Grid, View.Controllable {
// update now that signal handlers are in place
month_of_year = Calendar.System.today.month_of_year();
- first_of_week = Calendar.FirstOfWeek.SUNDAY;
}
/**
@@ -215,6 +219,9 @@ public class Controllable : Gtk.Grid, View.Controllable {
int row = 0;
foreach (Calendar.Week week in span)
update_week(row++, week);
+
+ // update the window being displayed
+ window = span.to_date_span();
}
private void update_first_of_week() {
@@ -227,6 +234,7 @@ public class Controllable : Gtk.Grid, View.Controllable {
// requires updating all the cells as well, since all dates have to be shifted
update_cells();
+ update_subscription();
}
private void on_month_of_year_changed() {
@@ -244,9 +252,12 @@ public class Controllable : Gtk.Grid, View.Controllable {
}
update_cells();
-
- // generate new ExactTimeSpan window for all calendar subscriptions
- Calendar.ExactTimeSpan window = new Calendar.ExactTimeSpan.from_date_span(month_of_year,
+ update_subscription();
+ }
+
+ private void update_subscription() {
+ // convert DateSpan window into an ExactTimeSpan, which is what the subscription wants
+ Calendar.ExactTimeSpan time_window = new Calendar.ExactTimeSpan.from_date_span(window,
Calendar.Timezone.local);
// clear current subscriptions and generate new subscriptions for new window
@@ -254,7 +265,7 @@ public class Controllable : Gtk.Grid, View.Controllable {
foreach (Backing.Store store in Backing.Manager.instance.get_stores()) {
foreach (Backing.Source source in store.get_sources_of_type<Backing.CalendarSource>()) {
Backing.CalendarSource calendar = (Backing.CalendarSource) source;
- calendar.subscribe_async.begin(window, null, on_subscribed);
+ calendar.subscribe_async.begin(time_window, null, on_subscribed);
}
}
}
@@ -266,10 +277,10 @@ public class Controllable : Gtk.Grid, View.Controllable {
Backing.CalendarSourceSubscription subscription = calendar.subscribe_async.end(result);
subscriptions.add(subscription);
- subscription.event_discovered.connect(on_event_added);
- subscription.event_added.connect(on_event_added);
- subscription.event_removed.connect(on_event_removed);
- subscription.event_dropped.connect(on_event_removed);
+ subscription.instance_discovered.connect(on_instance_added);
+ subscription.instance_added.connect(on_instance_added);
+ subscription.instance_removed.connect(on_instance_removed);
+ subscription.instance_dropped.connect(on_instance_removed);
// this will start signals firing for event changes
subscription.start();
@@ -278,7 +289,11 @@ public class Controllable : Gtk.Grid, View.Controllable {
}
}
- private void on_event_added(Component.Event event) {
+ private void on_instance_added(Component.Instance instance) {
+ Component.Event? event = instance as Component.Event;
+ if (event == null)
+ return;
+
// add event to every date it represents
foreach (Calendar.Date date in event.get_event_date_span()) {
Cell? cell = date_to_cell.get(date);
@@ -287,7 +302,11 @@ public class Controllable : Gtk.Grid, View.Controllable {
}
}
- private void on_event_removed(Component.Event event) {
+ private void on_instance_removed(Component.Instance instance) {
+ Component.Event? event = instance as Component.Event;
+ if (event == null)
+ return;
+
foreach (Calendar.Date date in event.get_event_date_span()) {
Cell? cell = date_to_cell.get(date);
if (cell != null)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]