[california/wip/734698-agenda] Deal with events/calendars removing, changing
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/734698-agenda] Deal with events/calendars removing, changing
- Date: Fri, 5 Dec 2014 03:22:29 +0000 (UTC)
commit 27fb97cd66c89bd9bc760732ab3267befddecb80
Author: Jim Nelson <jim yorba org>
Date: Thu Dec 4 19:22:17 2014 -0800
Deal with events/calendars removing, changing
src/collection/collection-iterable.vala | 24 +++++++++
src/component/component-instance.vala | 5 ++-
src/rc/view-agenda-date-row.ui | 2 +-
src/rc/view-agenda-event-row.ui | 39 +++++++++++++--
src/toolkit/toolkit-listbox-model.vala | 10 ++++
src/view/agenda/agenda-controller.vala | 53 ++++++++++++++++-----
src/view/agenda/agenda-date-row.vala | 46 +++++++++++++++++-
src/view/agenda/agenda-event-row.vala | 79 ++++++++++++++++++++++++++----
8 files changed, 226 insertions(+), 32 deletions(-)
---
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index cd1afbd..5c26149 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -307,6 +307,30 @@ public class Iterable<G> : Object {
}
/**
+ * Returns true if the { link Iterable} is empty.
+ *
+ * This is more efficient than checking if { link} count is zero when the Iterable is holding
+ * items.
+ *
+ * @see is_nonempty
+ */
+ public bool is_empty() {
+ return !iterator().has_next();
+ }
+
+ /**
+ * Returns true if the { link Iterable} is non-empty.
+ *
+ * This is more efficient than checking if { link} count is non-zero when the Iterable is
+ * holding items.
+ *
+ * @see is_empty
+ */
+ public bool is_nonempty() {
+ return iterator().has_next();
+ }
+
+ /**
* The resulting Gee.Iterable comes with the same caveat that you may only
* iterate over it once.
*/
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index b65602b..258b6a7 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -29,7 +29,6 @@ namespace California.Component {
*/
public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
- public const string PROP_CALENDAR_SOURCE = "calendar-source";
public const string PROP_DTSTAMP = "dtstamp";
public const string PROP_UID = "uid";
public const string PROP_ICAL_COMPONENT = "ical-component";
@@ -47,6 +46,10 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
/**
* The { link Backing.CalendarSource} this { link Instance} originated from.
*
+ * This field is immutable for the lifetime of the { link Instance}. If an Instance is moved
+ * to another calendar, this instance will be destroyed and a new one reported from the
+ * appropriate { link Backing.CalendarSourceSubscription}.
+ *
* This will initialize as null if created as a { link blank} Instance.
*/
public Backing.CalendarSource? calendar_source { get; private set; default = null; }
diff --git a/src/rc/view-agenda-date-row.ui b/src/rc/view-agenda-date-row.ui
index 88fee77..ca1709b 100644
--- a/src/rc/view-agenda-date-row.ui
+++ b/src/rc/view-agenda-date-row.ui
@@ -9,7 +9,7 @@
<object class="GtkLabel" id="date_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_right">8</property>
+ <property name="margin_right">16</property>
<property name="margin_top">3</property>
<property name="xalign">1</property>
<property name="yalign">0</property>
diff --git a/src/rc/view-agenda-event-row.ui b/src/rc/view-agenda-event-row.ui
index a80fc07..e8b0d0c 100644
--- a/src/rc/view-agenda-event-row.ui
+++ b/src/rc/view-agenda-event-row.ui
@@ -14,9 +14,8 @@
<object class="GtkLabel" id="time_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_right">8</property>
+ <property name="margin_right">16</property>
<property name="xalign">0</property>
- <property name="yalign">0</property>
<property name="label">(time)</property>
<property name="use_markup">True</property>
</object>
@@ -37,18 +36,48 @@
<object class="GtkLabel" id="summary_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="yalign">0</property>
<property name="label">(summary)</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
- <property name="expand">True</property>
+ <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
+ <child>
+ <object class="GtkImage" id="guests_icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="tooltip_text" translatable="yes">Event has guests</property>
+ <property name="xpad">4</property>
+ <property name="pixel_size">12</property>
+ <property name="icon_name">system-users-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="recurring_icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="tooltip_text" translatable="yes">Event is recurring</property>
+ <property name="xpad">4</property>
+ <property name="pixel_size">12</property>
+ <property name="icon_name">rotation-allowed-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
</template>
</interface>
diff --git a/src/toolkit/toolkit-listbox-model.vala b/src/toolkit/toolkit-listbox-model.vala
index 4fb36ae..8595bd6 100644
--- a/src/toolkit/toolkit-listbox-model.vala
+++ b/src/toolkit/toolkit-listbox-model.vala
@@ -255,6 +255,16 @@ public class ListBoxModel<G> : BaseObject {
}
row.changed();
+
+ // reset size as filter could have changed contents of list
+ int count = 0;
+ foreach (Gtk.Widget widget in listbox.get_children()) {
+ Gtk.ListBoxRow child = (Gtk.ListBoxRow) widget;
+ if (model_filter == null || model_filter(child.get_data<G>(KEY)))
+ count++;
+ }
+
+ size = count;
}
/**
diff --git a/src/view/agenda/agenda-controller.vala b/src/view/agenda/agenda-controller.vala
index 94166c0..c1de5a5 100644
--- a/src/view/agenda/agenda-controller.vala
+++ b/src/view/agenda/agenda-controller.vala
@@ -66,7 +66,7 @@ public class Controller : BaseObject, View.Controllable {
container = new Container(this, listbox);
current_span = new Calendar.DateSpan(Calendar.System.today,
- Calendar.System.today.adjust_by(1, Calendar.DateUnit.MONTH));
+ Calendar.System.today.adjust_by(2, Calendar.DateUnit.MONTH));
listbox_model = new Toolkit.ListBoxModel<Calendar.Date>(listbox, model_presentation);
// Don't prelight the DateRows, as they can't be selected or activated
@@ -109,25 +109,34 @@ public class Controller : BaseObject, View.Controllable {
* @inheritDoc
*/
public void unselect_all() {
+ // no notion of selection in Agenda view
}
/**
* @inheritDoc
*/
public Gtk.Widget? get_widget_for_date(Calendar.Date date) {
- return null;
+ return listbox_model.get_widget_for_item(date);
}
private Gtk.Widget model_presentation(Calendar.Date date) {
- return new DateRow(this, date);
+ DateRow date_row = new DateRow(this, date);
+ date_row.empty.connect(on_date_row_empty);
+
+ return date_row;
+ }
+
+ private void on_date_row_empty(DateRow date_row) {
+ listbox_model.remove(date_row.date);
}
private void update_subscriptions() {
subscriptions = new Backing.CalendarSubscriptionManager(
current_span.to_exact_time_span(Calendar.Timezone.local));
- subscriptions.instance_added.connect(on_instance_added);
- subscriptions.instance_altered.connect(on_instance_altered);
+ subscriptions.calendar_added.connect(on_calendar_added);
+ subscriptions.calendar_removed.connect(on_calendar_removed);
+ subscriptions.instance_added.connect(on_instance_added_or_altered);
subscriptions.instance_removed.connect(on_instance_removed);
subscriptions.start_async.begin();
@@ -141,12 +150,30 @@ public class Controller : BaseObject, View.Controllable {
is_viewing_today = Calendar.System.today in current_span;
}
- private void on_instance_added(Component.Instance instance) {
+ private void on_calendar_added(Backing.CalendarSource calendar) {
+ calendar.notify[Backing.Source.PROP_VISIBLE].connect(on_calendar_visibility_changed);
+ }
+
+ private void on_calendar_removed(Backing.CalendarSource calendar) {
+ calendar.notify[Backing.Source.PROP_VISIBLE].disconnect(on_calendar_visibility_changed);
+ }
+
+ private void on_calendar_visibility_changed(Object o, ParamSpec pspec) {
+ Backing.CalendarSource calendar = (Backing.CalendarSource) o;
+
+ foreach (Calendar.Date date in listbox_model.all()) {
+ DateRow date_row = (DateRow) listbox_model.get_widget_for_item(date);
+ date_row.notify_calendar_visibility_changed(calendar);
+ }
+ }
+
+ private void on_instance_added_or_altered(Component.Instance instance) {
Component.Event? event = instance as Component.Event;
if (event == null)
return;
foreach (Calendar.Date date in event.get_event_date_span(Calendar.Timezone.local)) {
+ // Add dates on-demand; not all dates are listed in Agenda view
if (!listbox_model.contains(date))
listbox_model.add(date);
@@ -155,16 +182,18 @@ public class Controller : BaseObject, View.Controllable {
}
}
- private void on_instance_altered(Component.Instance instance) {
- Component.Event? event = instance as Component.Event;
- if (event == null)
- return;
- }
-
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(Calendar.Timezone.local)) {
+ if (!listbox_model.contains(date))
+ continue;
+
+ DateRow date_row = (DateRow) listbox_model.get_widget_for_item(date);
+ date_row.remove_event(event);
+ }
}
public override string to_string() {
diff --git a/src/view/agenda/agenda-date-row.vala b/src/view/agenda/agenda-date-row.vala
index 7d8ad5a..cdc73b4 100644
--- a/src/view/agenda/agenda-date-row.vala
+++ b/src/view/agenda/agenda-date-row.vala
@@ -15,6 +15,8 @@ private class DateRow : Gtk.Box {
public Calendar.Date date { get; private set; }
+ public int size { get { return listbox_model.size; } }
+
[GtkChild]
private Gtk.Label date_label;
@@ -24,11 +26,15 @@ private class DateRow : Gtk.Box {
private unowned Controller owner;
private Toolkit.ListBoxModel<Component.Event> listbox_model;
+ public signal void empty();
+
public DateRow(Controller owner, Calendar.Date date) {
this.owner = owner;
this.date = date;
- listbox_model = new Toolkit.ListBoxModel<Component.Event>(event_listbox, model_presentation);
+ listbox_model = new Toolkit.ListBoxModel<Component.Event>(event_listbox, model_presentation,
+ model_filter);
+ listbox_model.notify[Toolkit.ListBoxModel.PROP_SIZE].connect(on_listbox_model_size_changed);
// Don't prelight the DateRows, as they can't be selected or activated
listbox_model.row_added.connect((row, item) => {
@@ -49,13 +55,49 @@ private class DateRow : Gtk.Box {
date_label_size_group = null;
}
+ private void on_listbox_model_size_changed() {
+ if (listbox_model.size == 0)
+ empty();
+ }
+
public void add_event(Component.Event event) {
- listbox_model.add(event);
+ if (!listbox_model.add(event))
+ return;
+
+ // watch for date changes, which affect if the event is represented here
+ event.notify[Component.Event.PROP_DATE_SPAN].connect(on_date_changed);
+ event.notify[Component.Event.PROP_EXACT_TIME_SPAN].connect(on_date_changed);
+ }
+
+ public void remove_event(Component.Event event) {
+ if (!listbox_model.remove(event))
+ return;
+
+ event.notify[Component.Event.PROP_DATE_SPAN].disconnect(on_date_changed);
+ event.notify[Component.Event.PROP_EXACT_TIME_SPAN].disconnect(on_date_changed);
+ }
+
+ private void on_date_changed(Object o, ParamSpec pspec) {
+ Component.Event event = (Component.Event) o;
+
+ if (!(date in event.get_event_date_span(Calendar.Timezone.local)))
+ remove_event(event);
+ }
+
+ public void notify_calendar_visibility_changed(Backing.CalendarSource calendar_source) {
+ foreach (Component.Event event in listbox_model.all()) {
+ if (event.calendar_source == calendar_source)
+ listbox_model.mutated(event);
+ }
}
private Gtk.Widget model_presentation(Component.Event event) {
return new EventRow(owner, event);
}
+
+ private bool model_filter(Component.Event event) {
+ return event.calendar_source.visible;
+ }
}
}
diff --git a/src/view/agenda/agenda-event-row.vala b/src/view/agenda/agenda-event-row.vala
index 2dc33ca..6580510 100644
--- a/src/view/agenda/agenda-event-row.vala
+++ b/src/view/agenda/agenda-event-row.vala
@@ -10,8 +10,6 @@ namespace California.View.Agenda {
private class EventRow : Gtk.Box {
private const Calendar.WallTime.PrettyFlag TIME_PRETTY_FLAGS = Calendar.WallTime.PrettyFlag.NONE;
- private const string COLOR_PRINTF = "<span color=\"%s\">%s</span>";
-
private static Gtk.SizeGroup time_label_size_group;
public new Component.Event event { get; private set; }
@@ -28,6 +26,12 @@ private class EventRow : Gtk.Box {
[GtkChild]
private Gtk.Label summary_label;
+ [GtkChild]
+ private Gtk.Image guests_icon;
+
+ [GtkChild]
+ private Gtk.Image recurring_icon;
+
private Controller owner;
private Toolkit.ButtonConnector button_connector = new Toolkit.ButtonConnector();
private Toolkit.MotionConnector motion_connector = new Toolkit.MotionConnector();
@@ -46,13 +50,48 @@ private class EventRow : Gtk.Box {
motion_connector.connect_to(summary_eventbox);
button_connector.clicked.connect(on_event_clicked);
+ button_connector.double_clicked.connect(on_event_double_clicked);
motion_connector.entered.connect(on_event_entered_exited);
motion_connector.exited.connect(on_event_entered_exited);
motion_connector.motion.connect(on_event_motion);
+ // watch for changes to the event
+ event.notify[Component.Event.PROP_SUMMARY].connect(update_ui);
+ event.notify[Component.Event.PROP_DATE_SPAN].connect(update_ui);
+ event.notify[Component.Event.PROP_EXACT_TIME_SPAN].connect(update_ui);
+ event.notify[Component.Event.PROP_LOCATION].connect(update_ui);
+ event.notify[Component.Instance.PROP_ATTENDEES].connect(update_ui);
+ event.notify[Component.Instance.PROP_ORGANIZERS].connect(update_ui);
+ event.notify[Component.Instance.PROP_RRULE].connect(update_ui);
+
+ // watch for changes to the calendar (which is immutable for the lifetime of the Event
+ // instances)
+ event.calendar_source.notify[Backing.Source.PROP_COLOR].connect(update_ui);
+
+ // .. date formatting changes
+ Calendar.System.instance.is_24hr_changed.connect(update_ui);
+ Calendar.System.instance.zone_changed.connect(update_ui);
+ Calendar.System.instance.timezone_changed.connect(update_ui);
+
update_ui();
}
+ ~EventRow() {
+ event.notify[Component.Event.PROP_SUMMARY].disconnect(update_ui);
+ event.notify[Component.Event.PROP_DATE_SPAN].disconnect(update_ui);
+ event.notify[Component.Event.PROP_EXACT_TIME_SPAN].disconnect(update_ui);
+ event.notify[Component.Event.PROP_LOCATION].disconnect(update_ui);
+ event.notify[Component.Instance.PROP_ATTENDEES].disconnect(update_ui);
+ event.notify[Component.Instance.PROP_ORGANIZERS].disconnect(update_ui);
+ event.notify[Component.Instance.PROP_RRULE].disconnect(update_ui);
+
+ event.calendar_source.notify[Backing.Source.PROP_COLOR].disconnect(update_ui);
+
+ Calendar.System.instance.is_24hr_changed.disconnect(update_ui);
+ Calendar.System.instance.zone_changed.disconnect(update_ui);
+ Calendar.System.instance.timezone_changed.disconnect(update_ui);
+ }
+
internal static void init() {
time_label_size_group = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL);
}
@@ -62,26 +101,44 @@ private class EventRow : Gtk.Box {
}
private void update_ui() {
- summary_label.label = COLOR_PRINTF.printf(event.calendar_source.color, event.summary);
-
- string label;
if (event.is_all_day) {
- label = _("All day");
+ time_label.label = _("All day");
} else {
+ Calendar.ExactTimeSpan time_span = event.exact_time_span.to_timezone(Calendar.Timezone.local);
+
// hex value is an endash
- label = "%s – %s".printf(
- event.exact_time_span.start_exact_time.to_wall_time().to_pretty_string(TIME_PRETTY_FLAGS),
- event.exact_time_span.end_exact_time.to_wall_time().to_pretty_string(TIME_PRETTY_FLAGS)
+ time_label.label = "%s – %s".printf(
+ time_span.start_exact_time.to_wall_time().to_pretty_string(TIME_PRETTY_FLAGS),
+ time_span.end_exact_time.to_wall_time().to_pretty_string(TIME_PRETTY_FLAGS)
);
}
- time_label.label = COLOR_PRINTF.printf(event.calendar_source.color, label);
+ if (!String.is_empty(event.location)) {
+ // hex value is an endash
+ summary_label.label = "<span color=\"%s\">%s</span> – %s".printf(
+ event.calendar_source.color, event.summary, event.location);
+ } else {
+ summary_label.label = "<span color=\"%s\">%s</span>".printf(
+ event.calendar_source.color, event.summary);
+ }
+
+ // only show guests icon if attendees include someone not an organizer
+ guests_icon.visible = traverse<Component.Person>(event.attendees)
+ .filter(person => !event.organizers.contains(person))
+ .is_nonempty();
+ recurring_icon.visible = event.rrule != null;
}
private bool on_event_clicked(Toolkit.ButtonEvent details) {
owner.request_display_event(event, details.widget, details.press_point);
- return Toolkit.PROPAGATE;
+ return Toolkit.STOP;
+ }
+
+ private bool on_event_double_clicked(Toolkit.ButtonEvent details) {
+ owner.request_edit_event(event, details.widget, details.press_point);
+
+ return Toolkit.STOP;
}
private void on_event_entered_exited(Toolkit.MotionEvent details) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]