[california/wip/725778-allday] Ensure that if a multi-day event changes lines, other affected cells are notified



commit 777906ce5de7b333422d680a9663d31231df977c
Author: Jim Nelson <jim yorba org>
Date:   Thu Apr 24 18:29:25 2014 -0700

    Ensure that if a multi-day event changes lines, other affected cells are notified

 src/Makefile.am                         |    1 +
 src/calendar/calendar-date-span.vala    |   15 +++++
 src/collection/collection-iterable.vala |    9 +++
 src/tests/tests-calendar-date.vala      |   67 ++++++++++++++++++++++++
 src/tests/tests.vala                    |    1 +
 src/view/month/month-cell.vala          |   86 +++++++++++++++++++++++++++----
 6 files changed, 169 insertions(+), 10 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 57105f0..1e8ebda 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -99,6 +99,7 @@ california_VALASOURCES = \
        manager/manager-window.vala \
        \
        tests/tests.vala \
+       tests/tests-calendar-date.vala \
        tests/tests-quick-add.vala \
        \
        toolkit/toolkit.vala \
diff --git a/src/calendar/calendar-date-span.vala b/src/calendar/calendar-date-span.vala
index 8bc1336..a7ffc29 100644
--- a/src/calendar/calendar-date-span.vala
+++ b/src/calendar/calendar-date-span.vala
@@ -188,6 +188,21 @@ public class DateSpan : BaseObject, Collection.SimpleIterable<Date>, Span<Date>,
     }
     
     /**
+     * Returns a { link DateSpan} with starting and ending points within the boundary specified
+     * (inclusive).
+     *
+     * If this DateSpan is within the clamped dates, this object may be returned.
+     *
+     * This method will not expand a DateSpan to meet the clamp range.
+     */
+    public DateSpan clamp(DateSpan span) {
+        Date new_start = (start_date.compare_to(span.start_date) < 0) ? span.start_date : start_date;
+        Date new_end = (end_date.compare_to(span.end_date) > 0) ? span.end_date : end_date;
+        
+        return new DateSpan(new_start, new_end);
+    }
+    
+    /**
      * Compares two { link DateSpan}s by their { link start_date}.
      */
     public int compare_to(DateSpan other) {
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index 1bd9cbf..2276d8e 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -131,6 +131,15 @@ public class Iterable<G> : BaseObject {
         return false;
     }
     
+    public bool contains_any(Gee.Collection<G> c) {
+        foreach (G g in this) {
+            if (c.contains(g))
+                return true;
+        }
+        
+        return false;
+    }
+    
     public bool all(owned Gee.Predicate<G> f) {
         foreach (G g in this) {
             if (!f(g))
diff --git a/src/tests/tests-calendar-date.vala b/src/tests/tests-calendar-date.vala
new file mode 100644
index 0000000..2620dd9
--- /dev/null
+++ b/src/tests/tests-calendar-date.vala
@@ -0,0 +1,67 @@
+/* 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.Tests {
+
+private class CalendarDate : UnitTest.Harness {
+    public CalendarDate() {
+        add_case("clamp-start", clamp_start);
+        add_case("clamp-end", clamp_end);
+        add_case("clamp-both", clamp_both);
+        add_case("clamp-neither", clamp_neither);
+    }
+    
+    protected override void setup() throws Error {
+        Calendar.init();
+    }
+    
+    protected override void teardown() {
+        Calendar.terminate();
+    }
+    
+    private Calendar.Date from_today(int days) {
+        return Calendar.System.today.adjust(days, Calendar.DateUnit.DAY);
+    }
+    
+    private Calendar.DateSpan span_from_today(int start_days, int end_days) {
+        return new Calendar.DateSpan(from_today(start_days), from_today(end_days));
+    }
+    
+    private bool clamp_start() throws Error {
+        Calendar.DateSpan span = span_from_today(0, 5);
+        Calendar.DateSpan clamp = span_from_today(1, 5);
+        Calendar.DateSpan adj = span.clamp(clamp);
+        
+        return adj.start_date.equal_to(clamp.start_date) && adj.end_date.equal_to(span.end_date);
+    }
+    
+    private bool clamp_end() throws Error {
+        Calendar.DateSpan span = span_from_today(0, 5);
+        Calendar.DateSpan clamp = span_from_today(0, 4);
+        Calendar.DateSpan adj = span.clamp(clamp);
+        
+        return adj.start_date.equal_to(span.start_date) && adj.end_date.equal_to(clamp.end_date);
+    }
+    
+    private bool clamp_both() throws Error {
+        Calendar.DateSpan span = span_from_today(0, 5);
+        Calendar.DateSpan clamp = span_from_today(1, 4);
+        Calendar.DateSpan adj = span.clamp(clamp);
+        
+        return adj.start_date.equal_to(clamp.start_date) && adj.end_date.equal_to(clamp.end_date);
+    }
+    
+    private bool clamp_neither() throws Error {
+        Calendar.DateSpan span = span_from_today(0, 5);
+        Calendar.DateSpan clamp = span_from_today(-1, 6);
+        Calendar.DateSpan adj = span.clamp(clamp);
+        
+        return adj.start_date.equal_to(span.start_date) && adj.end_date.equal_to(span.end_date);
+    }
+}
+
+}
+
diff --git a/src/tests/tests.vala b/src/tests/tests.vala
index 327609f..0684618 100644
--- a/src/tests/tests.vala
+++ b/src/tests/tests.vala
@@ -8,6 +8,7 @@ namespace California.Tests {
 
 public int run(string[] args) {
     UnitTest.Harness.register(new QuickAdd());
+    UnitTest.Harness.register(new CalendarDate());
     
     return UnitTest.Harness.exec_all();
 }
diff --git a/src/view/month/month-cell.vala b/src/view/month/month-cell.vala
index 5ac7f3e..2ee1959 100644
--- a/src/view/month/month-cell.vala
+++ b/src/view/month/month-cell.vala
@@ -71,7 +71,7 @@ public class Cell : Gtk.EventBox {
         }
     }
     
-    private Gee.TreeSet<Component.Event> sorted_events = new Gee.TreeSet<Component.Event>();
+    private Gee.TreeSet<Component.Event> sorted_events = new 
Gee.TreeSet<Component.Event>(all_day_comparator);
     private Gee.HashMap<int, Component.Event> line_to_event = new Gee.HashMap<int, Component.Event>();
     
     // TODO: We may need to get these colors from the theme
@@ -129,6 +129,28 @@ public class Cell : Gtk.EventBox {
         line_font = null;
     }
     
+    // this comparator uses the standard Event comparator with one exception: if both Events are
+    // all-day, it sorts the one(s) with the furthest out end dates to the top, to ensure they are
+    // at the top of the drawn lines and prevent gaps and skips in the connected bars
+    private static int all_day_comparator(Component.Event a, Component.Event b) {
+        if (a == b)
+            return 0;
+        
+        if (!a.is_all_day || !b.is_all_day)
+            return a.compare_to(b);
+        
+        int compare = a.date_span.start_date.compare_to(b.date_span.start_date);
+        if (compare != 0)
+            return compare;
+        
+        compare = b.date_span.end_date.compare_to(a.date_span.end_date);
+        if (compare != 0)
+            return compare;
+        
+        // to stabilize
+        return a.compare_to(b);
+    }
+    
     /**
      * Returns true if the point at x,y is within the { link Cell}'s width and height.
      */
@@ -182,14 +204,29 @@ public class Cell : Gtk.EventBox {
         queue_draw();
     }
     
+    /**
+     * Called by { link Controllable} when a calendar's visibility has changed.
+     *
+     * This causes event line numbers to be reassigned and thie { link Cell} redrawn, if the
+     * calendar in question has any events in this date.
+     */
     public void notify_calendar_visibility_changed(Backing.CalendarSource calendar_source) {
-        if (traverse<Component.Event>(sorted_events).any((event) => event.calendar_source == 
calendar_source)) {
-            // found one
-            assign_line_numbers();
-            queue_draw();
-            
+        if (!traverse<Component.Event>(sorted_events).any((event) => event.calendar_source == 
calendar_source))
             return;
-        }
+        
+        // found one
+        assign_line_numbers();
+        queue_draw();
+    }
+    
+    // Called internally by other Cells when (a) they're in charge of assigning a multi-day event
+    // its line number for the week and (b) that line number has changed.
+    private void notify_assigned_line_number_changed(Gee.Collection<Component.Event> events) {
+        if (!traverse<Component.Event>(sorted_events).contains_any(events))
+            return;
+        
+        assign_line_numbers();
+        queue_draw();
     }
     
     // each event gets a line of the cell to draw in; this clears all assigned line numbers and
@@ -198,12 +235,18 @@ public class Cell : Gtk.EventBox {
     // this cell is responsible for assigning a line number to it, which the other cells of the
     // same week will honor (so a continuous line can be drawn)
     private void assign_line_numbers() {
-        line_to_event.clear();
+        Gee.HashMap<int, Component.Event> old_line_to_event = line_to_event;
+        line_to_event = new Gee.HashMap<int, Component.Event>();
+        
+        // track each event whose line number this cell is responsible for assigning that gets
+        // reassigned because of this
+        Gee.ArrayList<Component.Event> reassigned = new Gee.ArrayList<Component.Event>();
         
         foreach (Component.Event event in sorted_events) {
             if (!event.calendar_source.visible)
                 continue;
             
+            bool all_day_assigned_here = false;
             if (event.is_all_day) {
                 // get the first day of this week the event exists in ... if not the current cell's
                 // date, get the assigned line number from the first day of this week the event
@@ -220,16 +263,37 @@ public class Cell : Gtk.EventBox {
                         
                         continue;
                     }
+                } else {
+                    // only worried about multi-day events being reassigned, as that's what effects
+                    // other cells
+                    all_day_assigned_here = event.date_span.duration() > 1;
                 }
             }
             
             // otherwise, a timed event, a single-day event, or a multi-day event which starts here,
             // so assign
-            assign_line_number(-1, event);
+            int assigned = assign_line_number(-1, event);
+            
+            // if this cell assigns the line number and the event is not new and the number has changed,
+            // inform all the other cells following this day's in the current week
+            if (all_day_assigned_here && old_line_to_event.values.contains(event) && 
old_line_to_event.get(assigned) != event)
+                reassigned.add(event);
+        }
+        
+        if (reassigned.size > 0) {
+            // only need to tell cells following this day's in the current week about the reassignment
+            Calendar.Week this_week = date.week_of(owner.first_of_week);
+            Calendar.DateSpan span = new Calendar.DateSpan(date.next(), this_week.end_date).clamp(this_week);
+            
+            foreach (Calendar.Date span_date in span) {
+                Cell? cell = owner.get_cell_for_date(span_date);
+                if (cell != null && cell != this)
+                    cell.notify_assigned_line_number_changed(reassigned);
+            }
         }
     }
     
-    private void assign_line_number(int force_line_number, Component.Event event) {
+    private int assign_line_number(int force_line_number, Component.Event event) {
         // kinda dumb, but this prevents holes appearing in lines where, due to the shape of the
         // all-day events, could be filled
         int line_number = 0;
@@ -241,6 +305,8 @@ public class Cell : Gtk.EventBox {
         }
         
         line_to_event.set(line_number, event);
+        
+        return line_number;
     }
     
     public bool has_events() {


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