[california/wip/734698-agenda] Move start date backwards and end date forwards



commit 436e3ce976fa09bb77eb29345ef98d717031a3ee
Author: Jim Nelson <jim yorba org>
Date:   Fri Dec 5 15:51:15 2014 -0800

    Move start date backwards and end date forwards
    
    Previous and Load More, but not Next quite yet.

 po/POTFILES.in                                     |    3 +
 po/POTFILES.skip                                   |    1 +
 src/Makefile.am                                    |    2 +
 .../backing-calendar-subscription-manager.vala     |   55 +++++++++++++++++-
 src/calendar/calendar-date.vala                    |   24 ++++++++
 src/calendar/calendar-exact-time-span.vala         |   19 ++++++
 src/calendar/calendar-span.vala                    |   14 +++++
 src/calendar/calendar.vala                         |    6 ++-
 src/california-resources.xml                       |    3 +
 src/rc/view-agenda-event-row.ui                    |    1 +
 src/rc/view-agenda-load-more-row.ui                |   42 ++++++++++++++
 src/view/agenda/agenda-controller.vala             |   60 +++++++++++++++++---
 src/view/agenda/agenda-date-row.vala               |   25 ++++++--
 src/view/agenda/agenda-load-more-row.vala          |   48 ++++++++++++++++
 14 files changed, 283 insertions(+), 20 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 72e528f..15a5e19 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -28,6 +28,7 @@ src/manager/manager-calendar-list-item.vala
 src/manager/manager-remove-calendar.vala
 src/view/agenda/agenda-controller.vala
 src/view/agenda/agenda-event-row.vala
+src/view/agenda/agenda-load-more-row.vala
 src/view/month/month-controller.vala
 src/view/week/week-controller.vala
 [type: gettext/glade]src/rc/activator-generic-subscribe.ui
@@ -49,5 +50,7 @@ src/view/week/week-controller.vala
 [type: gettext/glade]src/rc/manager-calendar-list.ui
 [type: gettext/glade]src/rc/manager-calendar-list-item.ui
 [type: gettext/glade]src/rc/manager-remove-calendar.ui
+[type: gettext/glade]src/rc/view-agenda-date-row.ui
 [type: gettext/glade]src/rc/view-agenda-event-row.ui
+[type: gettext/glade]src/rc/view-agenda-load-more-row.ui
 [type: gettext/glade]src/rc/window-menu.interface
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 22513c9..8afa4c4 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -26,6 +26,7 @@ src/manager/manager-calendar-list-item.c
 src/manager/manager-remove-calendar.c
 src/view/agenda/agenda-controller.c
 src/view/agenda/agenda-event-row.c
+src/view/agenda/agenda-load-more-row.c
 src/view/month/month-controller.c
 src/view/week/week-controller.c
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 8e8f029..070b25c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -174,6 +174,7 @@ california_VALASOURCES = \
        view/agenda/agenda-controller.vala \
        view/agenda/agenda-date-row.vala \
        view/agenda/agenda-event-row.vala \
+       view/agenda/agenda-load-more-row.vala \
        \
        view/common/common.vala \
        view/common/common-events-cell.vala \
@@ -221,6 +222,7 @@ california_RC = \
        rc/manager-remove-calendar.ui \
        rc/view-agenda-date-row.ui \
        rc/view-agenda-event-row.ui \
+       rc/view-agenda-load-more-row.ui \
        rc/window-menu.interface \
        $(NULL)
 
diff --git a/src/backing/backing-calendar-subscription-manager.vala 
b/src/backing/backing-calendar-subscription-manager.vala
index 71da1b4..b86c78d 100644
--- a/src/backing/backing-calendar-subscription-manager.vala
+++ b/src/backing/backing-calendar-subscription-manager.vala
@@ -115,6 +115,50 @@ public class CalendarSubscriptionManager : BaseObject {
         }
     }
     
+    /**
+     * Expand the { link CalendarSubscriptionManager}'s { link window} of time.
+     *
+     * expand_window() will increase the contiguous span of time being monitored for changes by the
+     * subscription manager.  There is no provision for managing multiple ''fragments'' of time,
+     * only expanding the window.
+     *
+     * expand_window() should ''not'' be called until { link start_async} has completed.  Results
+     * are unguaranteed if called while start_async() is executing.
+     *
+     * If expanded_time is within the current window, nothing happens.
+     */
+    public async void expand_window_async(Calendar.ExactTime expanded_time) {
+        if (expanded_time in window)
+            return;
+        
+        // and create a new subscription window to cover the new span of time without overlapping
+        // existing subscription(s)
+        Calendar.ExactTimeSpan subscription_window;
+        if (expanded_time.compare_to(window.start_exact_time) < 0) {
+            subscription_window = new Calendar.ExactTimeSpan(
+                expanded_time,
+                window.start_exact_time.adjust_time(-1, Calendar.TimeUnit.SECOND)
+            );
+        } else {
+            assert(expanded_time.compare_to(window.end_exact_time) > 0);
+            
+            subscription_window = new Calendar.ExactTimeSpan(
+                window.end_exact_time.adjust_time(1, Calendar.TimeUnit.SECOND),
+                expanded_time
+            );
+        }
+        
+        // expand the current window ... do this before adding subscriptions so if new calendars
+        // are reported during async calls, they use the full expanded window
+        window = window.expand(expanded_time);
+        
+        // create new subscriptions for the expanded span only
+        foreach (Backing.Store store in Backing.Manager.instance.get_stores()) {
+            foreach (Backing.CalendarSource calendar in store.get_sources_of_type<Backing.CalendarSource>())
+                yield add_subscription_async(calendar, subscription_window, null);
+        }
+    }
+    
     private void on_source_added(Backing.Source source) {
         Backing.CalendarSource? calendar = source as Backing.CalendarSource;
         if (calendar != null)
@@ -125,12 +169,17 @@ public class CalendarSubscriptionManager : BaseObject {
         // report calendar as added to subscription
         calendar_added(calendar);
         
-        // start generating instances on this calendar
+        // add a subscription for the new calendar with existing window
+        yield add_subscription_async(calendar, window, cancellable);
+    }
+    
+    private async void add_subscription_async(Backing.CalendarSource calendar,
+        Calendar.ExactTimeSpan subscription_window, Cancellable? cancellable) {
         try {
             // Since this might be called after the dtor has finished (cancelling the operation), don't
             // touch the "this" ref unless the Error is shown not to be a cancellation
-            Backing.CalendarSourceSubscription subscription = yield calendar.subscribe_async(window,
-                cancellable);
+            Backing.CalendarSourceSubscription subscription = yield calendar.subscribe_async(
+                subscription_window, cancellable);
             
             // okay to use "this" ref
             subscriptions.add(subscription);
diff --git a/src/calendar/calendar-date.vala b/src/calendar/calendar-date.vala
index 1bf41ce..4ba9e43 100644
--- a/src/calendar/calendar-date.vala
+++ b/src/calendar/calendar-date.vala
@@ -62,6 +62,15 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
         NO_DAY_OF_WEEK
     }
     
+    /**
+     * The practical earliest representable { link Date} in time.
+     */
+    public static Date earliest { get; private set; }
+    
+    /**
+     * The practical latest representable { link Date} in time.
+     */
+    public static Date latest { get; private set; }
     
     /**
      * @inheritDoc
@@ -145,6 +154,21 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
         year = new Year.from_gdate(gdate);
     }
     
+    internal static void init() throws CalendarError {
+        GLib.Date earliest_gdate = GLib.Date();
+        earliest_gdate.set_julian(1);
+        earliest = new Date.from_gdate(earliest_gdate);
+        
+        // GLib.Date.set_julian(uint.MAX) causes strange assertions inside of GLib, so just jimmying
+        // together a date far in the future for now
+        latest = new Date(DayOfMonth.for(31), Month.DEC, new Year(100000));
+    }
+    
+    internal static void terminate() {
+        earliest = null;
+        latest = null;
+    }
+    
     /**
      * Returns the { link Week} the { link Date} falls in.
      */
diff --git a/src/calendar/calendar-exact-time-span.vala b/src/calendar/calendar-exact-time-span.vala
index 4db2ca1..6e4a0b2 100644
--- a/src/calendar/calendar-exact-time-span.vala
+++ b/src/calendar/calendar-exact-time-span.vala
@@ -140,6 +140,25 @@ public class ExactTimeSpan : BaseObject, Gee.Comparable<ExactTimeSpan>, Gee.Hash
     }
     
     /**
+     * Returns an { link ExactTimeSpan} expanded to include the supplied { link ExactTime}.
+     *
+     * If the expanded_time is within this ExactTimeSpan, this object is returned.
+     */
+    public ExactTimeSpan expand(ExactTime expanded_time) {
+        if (contains(expanded_time))
+            return this;
+        
+        // if supplied time before start of span, that becomes the new start time
+        if (expanded_time.compare_to(start_exact_time) < 0)
+            return new ExactTimeSpan(expanded_time, end_exact_time);
+        
+        // prior tests guarantee supplied time is after end of this span
+        assert(expanded_time.compare_to(end_exact_time) > 0);
+        
+        return new ExactTimeSpan(start_exact_time, expanded_time);
+    }
+    
+    /**
      * Returns a prettified string describing the { link Event}'s time span in as concise and
      * economical manner possible.
      *
diff --git a/src/calendar/calendar-span.vala b/src/calendar/calendar-span.vala
index 2f7eaeb..30d2997 100644
--- a/src/calendar/calendar-span.vala
+++ b/src/calendar/calendar-span.vala
@@ -180,6 +180,20 @@ public abstract class Span : BaseObject {
     }
     
     /**
+     * Returns a { link DateSpan} that covers the time of this { link Span} and the supplied
+     * { link Date}.
+     *
+     * If the Date is within the existing Span, a DateSpan for this Span is returned, i.e. this
+     * is just like calling { link to_date_span}.
+     */
+    public DateSpan expand(Calendar.Date expansion) {
+        Date new_start = (expansion.compare_to(start_date) < 0) ? expansion : start_date;
+        Date new_end = (expansion.compare_to(end_date) > 0) ? expansion : end_date;
+        
+        return new DateSpan(new_start, new_end);
+    }
+    
+    /**
      * True if the { link Span} contains the specified { link Date}.
      */
     public bool has_date(Date date) {
diff --git a/src/calendar/calendar.vala b/src/calendar/calendar.vala
index 75cc426..09f7a83 100644
--- a/src/calendar/calendar.vala
+++ b/src/calendar/calendar.vala
@@ -233,13 +233,15 @@ public void init() throws Error {
     // This init() throws an IOError, so perform before others to prevent unnecessary unwinding
     System.preinit();
     
-    // internal initialization
     Collection.init();
+    
+    // internal initialization
     OlsonZone.init();
     DayOfWeek.init();
     DayOfMonth.init();
     Month.init();
     WallTime.init();
+    Date.init();
     System.init();
     Timezone.init();
 }
@@ -250,11 +252,13 @@ public void terminate() {
     
     Timezone.terminate();
     System.terminate();
+    Date.terminate();
     WallTime.terminate();
     Month.terminate();
     DayOfMonth.terminate();
     DayOfWeek.terminate();
     OlsonZone.terminate();
+    
     Collection.terminate();
 }
 
diff --git a/src/california-resources.xml b/src/california-resources.xml
index 5a72f09..707ebd1 100644
--- a/src/california-resources.xml
+++ b/src/california-resources.xml
@@ -64,6 +64,9 @@
         <file compressed="false">rc/view-agenda-event-row.ui</file>
     </gresource>
     <gresource prefix="/org/yorba/california">
+        <file compressed="true">rc/view-agenda-load-more-row.ui</file>
+    </gresource>
+    <gresource prefix="/org/yorba/california">
         <file compressed="true">rc/window-menu.interface</file>
     </gresource>
 </gresources>
diff --git a/src/rc/view-agenda-event-row.ui b/src/rc/view-agenda-event-row.ui
index e8b0d0c..c4d05ed 100644
--- a/src/rc/view-agenda-event-row.ui
+++ b/src/rc/view-agenda-event-row.ui
@@ -38,6 +38,7 @@
             <property name="can_focus">False</property>
             <property name="label">(summary)</property>
             <property name="use_markup">True</property>
+            <property name="ellipsize">end</property>
           </object>
         </child>
       </object>
diff --git a/src/rc/view-agenda-load-more-row.ui b/src/rc/view-agenda-load-more-row.ui
new file mode 100644
index 0000000..b360c96
--- /dev/null
+++ b/src/rc/view-agenda-load-more-row.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <template class="CaliforniaViewAgendaLoadMoreRow" parent="GtkBox">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="halign">center</property>
+    <property name="hexpand">True</property>
+    <property name="spacing">8</property>
+    <child>
+      <object class="GtkLabel" id="showing_until_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label">Showing events until Month, Day, Year.</property>
+        <attributes>
+          <attribute name="style" value="italic"/>
+        </attributes>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="load_more_button">
+        <property name="label" translatable="yes" comments="As in, &quot;Load more events&quot;">Load 
_More</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_load_more_button_clicked" 
object="CaliforniaViewAgendaLoadMoreRow" swapped="no"/>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/src/view/agenda/agenda-controller.vala b/src/view/agenda/agenda-controller.vala
index c1de5a5..66e0aba 100644
--- a/src/view/agenda/agenda-controller.vala
+++ b/src/view/agenda/agenda-controller.vala
@@ -7,6 +7,8 @@
 namespace California.View.Agenda {
 
 public class Controller : BaseObject, View.Controllable {
+    public const string PROP_CURRENT_SPAN = "current-span";
+    
     public const string VIEW_ID = "agenda";
     
     private class Container : Gtk.ScrolledWindow, View.Container {
@@ -55,18 +57,21 @@ public class Controller : BaseObject, View.Controllable {
      */
     public View.Palette palette { get; private set; }
     
+    /**
+     * Current { link Calendar.DateSpan} being displayed.
+     */
+    public Calendar.DateSpan current_span { get; private set; }
+    
     private Container container;
-    private Calendar.DateSpan current_span;
     private Backing.CalendarSubscriptionManager? subscriptions = null;
     private Gtk.ListBox listbox = new Gtk.ListBox();
     private Toolkit.ListBoxModel<Calendar.Date> listbox_model;
+    private LoadMoreRow load_more_row;
     
     public Controller(View.Palette palette) {
         this.palette = palette;
         
         container = new Container(this, listbox);
-        current_span = new Calendar.DateSpan(Calendar.System.today,
-            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
@@ -77,7 +82,19 @@ public class Controller : BaseObject, View.Controllable {
         listbox.selection_mode = Gtk.SelectionMode.NONE;
         listbox.activate_on_single_click = false;
         
-        update_subscriptions();
+        // this will initialize current_span
+        reset_subscriptions(
+            new Calendar.DateSpan(
+                Calendar.System.today,
+                Calendar.System.today.adjust_by(2, Calendar.DateUnit.MONTH)
+            )
+        );
+        
+        // LoadMoreRow is persistent and always sorts to the end of the list (see model_presentation)
+        // (need to add after setting current_span in reset_subscriptions)
+        load_more_row = new LoadMoreRow(this);
+        load_more_row.load_more.connect(on_load_more);
+        listbox_model.add(Calendar.Date.latest);
     }
     
     /**
@@ -97,12 +114,14 @@ public class Controller : BaseObject, View.Controllable {
      * @inheritDoc
      */
     public void previous() {
+        expand_subscriptions(current_span.start_date.adjust(-1));
     }
     
     /**
      * @inheritDoc
      */
     public void today() {
+        reset_subscriptions(new Calendar.DateSpan(Calendar.System.today, current_span.end_date));
     }
     
     /**
@@ -120,6 +139,9 @@ public class Controller : BaseObject, View.Controllable {
     }
     
     private Gtk.Widget model_presentation(Calendar.Date date) {
+        if (date.equal_to(Calendar.Date.latest))
+            return load_more_row;
+        
         DateRow date_row = new DateRow(this, date);
         date_row.empty.connect(on_date_row_empty);
         
@@ -130,7 +152,15 @@ public class Controller : BaseObject, View.Controllable {
         listbox_model.remove(date_row.date);
     }
     
-    private void update_subscriptions() {
+    private void on_load_more() {
+        expand_subscriptions(current_span.end_date.adjust_by(1, Calendar.DateUnit.MONTH));
+    }
+    
+    private void reset_subscriptions(Calendar.DateSpan new_span) {
+        current_span = new_span;
+        
+        listbox_model.clear();
+        
         subscriptions = new Backing.CalendarSubscriptionManager(
             current_span.to_exact_time_span(Calendar.Timezone.local));
         
@@ -141,13 +171,25 @@ public class Controller : BaseObject, View.Controllable {
         
         subscriptions.start_async.begin();
         
-        current_label = current_span.to_pretty_string(
-            Calendar.Date.PrettyFlag.COMPACT
+        update_view_details();
+    }
+    
+    private void expand_subscriptions(Calendar.Date expansion) {
+        current_span = current_span.expand(expansion);
+        
+        subscriptions.expand_window_async.begin(
+            expansion.to_exact_time_span(Calendar.Timezone.local).start_exact_time);
+        
+        update_view_details();
+    }
+    
+    private void update_view_details() {
+        current_label = current_span.start_date.to_pretty_string(
+            Calendar.Date.PrettyFlag.ABBREV
             | Calendar.Date.PrettyFlag.INCLUDE_YEAR
-            | Calendar.Date.PrettyFlag.NO_DAY_OF_WEEK
             | Calendar.Date.PrettyFlag.NO_TODAY
         );
-        is_viewing_today = Calendar.System.today in current_span;
+        is_viewing_today = current_span.start_date.equal_to(Calendar.System.today);
     }
     
     private void on_calendar_added(Backing.CalendarSource calendar) {
diff --git a/src/view/agenda/agenda-date-row.vala b/src/view/agenda/agenda-date-row.vala
index cdc73b4..18781c3 100644
--- a/src/view/agenda/agenda-date-row.vala
+++ b/src/view/agenda/agenda-date-row.vala
@@ -41,10 +41,17 @@ private class DateRow : Gtk.Box {
             Toolkit.prevent_prelight(row);
         });
         
-        date_label.label = date.to_pretty_string(DATE_PRETTY_FLAGS);
-        
         // all date labels are same width
         date_label_size_group.add_widget(date_label);
+        
+        // Because some date text labels are relative (i.e. "Today"), refresh when the date changes
+        Calendar.System.instance.today_changed.connect(update_ui);
+        
+        update_ui();
+    }
+    
+    ~DateRow() {
+        Calendar.System.instance.today_changed.disconnect(update_ui);
     }
     
     internal static void init() {
@@ -55,6 +62,10 @@ private class DateRow : Gtk.Box {
         date_label_size_group = null;
     }
     
+    private void update_ui() {
+        date_label.label = date.to_pretty_string(DATE_PRETTY_FLAGS);
+    }
+    
     private void on_listbox_model_size_changed() {
         if (listbox_model.size == 0)
             empty();
@@ -65,19 +76,19 @@ private class DateRow : Gtk.Box {
             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);
+        event.notify[Component.Event.PROP_DATE_SPAN].connect(on_event_date_changed);
+        event.notify[Component.Event.PROP_EXACT_TIME_SPAN].connect(on_event_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);
+        event.notify[Component.Event.PROP_DATE_SPAN].disconnect(on_event_date_changed);
+        event.notify[Component.Event.PROP_EXACT_TIME_SPAN].disconnect(on_event_date_changed);
     }
     
-    private void on_date_changed(Object o, ParamSpec pspec) {
+    private void on_event_date_changed(Object o, ParamSpec pspec) {
         Component.Event event = (Component.Event) o;
         
         if (!(date in event.get_event_date_span(Calendar.Timezone.local)))
diff --git a/src/view/agenda/agenda-load-more-row.vala b/src/view/agenda/agenda-load-more-row.vala
new file mode 100644
index 0000000..5c1d65a
--- /dev/null
+++ b/src/view/agenda/agenda-load-more-row.vala
@@ -0,0 +1,48 @@
+/* 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.View.Agenda {
+
+[GtkTemplate (ui = "/org/yorba/california/rc/view-agenda-load-more-row.ui")]
+private class LoadMoreRow : Gtk.Box {
+    private unowned Controller owner;
+    
+    [GtkChild]
+    private Gtk.Label showing_until_label;
+    
+    public signal void load_more();
+    
+    public LoadMoreRow(Controller owner) {
+        this.owner = owner;
+        
+        owner.notify[Controller.PROP_CURRENT_SPAN].connect(update_ui);
+        
+        update_ui();
+    }
+    
+    ~LoadMoreRow() {
+        owner.notify[Controller.PROP_CURRENT_SPAN].disconnect(update_ui);
+    }
+    
+    private void update_ui() {
+        string date_str = owner.current_span.end_date.to_pretty_string(
+            Calendar.Date.PrettyFlag.INCLUDE_YEAR
+            | Calendar.Date.PrettyFlag.NO_DAY_OF_WEEK
+            | Calendar.Date.PrettyFlag.NO_TODAY
+        );
+        
+        // %s is a date, i.e. "Showing events until December 5, 2014"
+        showing_until_label.label = _("Showing events until %s").printf(date_str);
+    }
+    
+    [GtkCallback]
+    private void on_load_more_button_clicked() {
+        load_more();
+    }
+}
+
+}
+


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