[california/wip/725778-allday] Event line numbers in cells are now assigned when added/removed



commit ffdd6e940706008c770626f1e008a2968863dc52
Author: Jim Nelson <jim yorba org>
Date:   Thu Apr 24 15:52:28 2014 -0700

    Event line numbers in cells are now assigned when added/removed
    
    This allows for line numbers of events in cells to be known before
    draw() is called, so any cell can know the assigned line for a
    multi-day event and draw it in its own cell with the same line number.

 src/component/component-event.vala     |    5 +-
 src/view/month/month-cell.vala         |  148 +++++++++++++++++++++++---------
 src/view/month/month-controllable.vala |   18 ++++-
 3 files changed, 126 insertions(+), 45 deletions(-)
---
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index bd68069..3923383 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -347,11 +347,14 @@ public class Event : Instance, Gee.Comparable<Event> {
         }
         
         if (exact_time_span != null
+            && other_event.date_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))) {
+        if (date_span != null
+            && other_event.exact_time_span != null
+            && !date_span.equal_to(new Calendar.DateSpan.from_exact_time_span(other_event.exact_time_span))) 
{
             return false;
         }
         
diff --git a/src/view/month/month-cell.vala b/src/view/month/month-cell.vala
index 4b9a52c..1e3b68b 100644
--- a/src/view/month/month-cell.vala
+++ b/src/view/month/month-cell.vala
@@ -59,7 +59,7 @@ public class Cell : Gtk.EventBox {
         }
     }
     
-    private Gee.TreeSet<Component.Event> days_events = new Gee.TreeSet<Component.Event>(all_day_comparator);
+    private Gee.TreeSet<Component.Event> sorted_events = new Gee.TreeSet<Component.Event>();
     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
@@ -101,28 +101,6 @@ public class Cell : Gtk.EventBox {
         Calendar.System.instance.today_changed.disconnect(on_today_changed);
     }
     
-    // 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);
-    }
-    
     internal static void init() {
         top_line_font = new Pango.FontDescription();
         top_line_font.set_size(TOP_LINE_FONT_SIZE_PT * Pango.SCALE);
@@ -146,13 +124,27 @@ public class Cell : Gtk.EventBox {
         return x >= 0 && x < get_allocated_width() && y >= 0 && y < get_allocated_height();
     }
     
+    /**
+     * Returns the assigned line number for the event, -1 if not found in { link Cell}.
+     */
+    public int get_line_for_event(Component.Event event) {
+        Gee.MapIterator<int, Component.Event> iter = line_to_event.map_iterator();
+        while (iter.next()) {
+            if (iter.get_value().equal_to(event))
+                return iter.get_key();
+        }
+        
+        return -1;
+    }
+    
     public void clear() {
         date = null;
-        days_events.clear();
+        sorted_events.clear();
+        line_to_event.clear();
     }
     
     public void add_event(Component.Event event) {
-        if (!days_events.add(event))
+        if (!sorted_events.add(event))
             return;
         
         // subscribe to interesting mutable properties
@@ -160,22 +152,87 @@ public class Cell : Gtk.EventBox {
         event.notify[Component.Event.PROP_DATE_SPAN].connect(on_span_updated);
         event.notify[Component.Event.PROP_EXACT_TIME_SPAN].connect(on_span_updated);
         
+        assign_line_numbers();
+        
         queue_draw();
     }
     
     public void remove_event(Component.Event event) {
-        if (!days_events.remove(event))
+        if (!sorted_events.remove(event))
             return;
         
         event.notify[Component.Event.PROP_SUMMARY].disconnect(queue_draw);
         event.notify[Component.Event.PROP_DATE_SPAN].disconnect(on_span_updated);
         event.notify[Component.Event.PROP_EXACT_TIME_SPAN].disconnect(on_span_updated);
         
+        assign_line_numbers();
+        
         queue_draw();
     }
     
+    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();
+            
+            return;
+        }
+    }
+    
+    // each event gets a line of the cell to draw in; this clears all assigned line numbers and
+    // re-assigns from the sorted set of events, making sure holes are filled where possible ...
+    // if an event starts in this cell or this cell is the first day of a week an event is in,
+    // 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();
+        
+        foreach (Component.Event event in sorted_events) {
+            if (!event.calendar_source.visible)
+                continue;
+            
+            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
+                // exists in
+                Calendar.Date first_date = get_event_first_day_this_week(event);
+                if (!date.equal_to(first_date)) {
+                    int event_line = -1;
+                    Cell? cell = owner.get_cell_for_date(first_date);
+                    if (cell != null)
+                        event_line = cell.get_line_for_event(event);
+                    
+                    if (event_line >= 0) {
+                        assign_line_number(event_line, event);
+                        
+                        continue;
+                    }
+                }
+            }
+            
+            // otherwise, a timed event, a single-day event, or a multi-day event which starts here,
+            // so assign
+            assign_line_number(-1, event);
+        }
+    }
+    
+    private void 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;
+        if (force_line_number < 0) {
+            while (line_to_event.has_key(line_number))
+                line_number++;
+        } else {
+            line_number = force_line_number;
+        }
+        
+        line_to_event.set(line_number, event);
+    }
+    
     public bool has_events() {
-        return days_events.size > 0;
+        return sorted_events.size > 0;
     }
     
     private void on_24hr_changed() {
@@ -195,12 +252,14 @@ public class Cell : Gtk.EventBox {
         
         Component.Event event = (Component.Event) object;
         
-        // remove from cell if no longer in this day, otherwise remove and add again to days_events
+        // remove from cell if no longer in this day, otherwise remove and add again to sorted_events
         // to re-sort
-        if (!(date in event.get_event_date_span(Calendar.Timezone.local)))
+        if (!(date in event.get_event_date_span(Calendar.Timezone.local))) {
             remove_event(event);
-        else if (days_events.remove(event))
-            days_events.add(event);
+        } else if (sorted_events.remove(event)) {
+            sorted_events.add(event);
+            assign_line_numbers();
+        }
         
         queue_draw();
     }
@@ -219,6 +278,17 @@ public class Cell : Gtk.EventBox {
         return true;
     }
     
+    // Returns the first day of this cell's calendar week that the event is in ... this could be
+    // the event's starting day or the first day of this week (i.e. Monday or Sunday)
+    private Calendar.Date get_event_first_day_this_week(Component.Event event) {
+        Calendar.Date event_start_date = event.get_event_date_span(Calendar.Timezone.local).start_date;
+        
+        Calendar.Week cell_week = date.week_of(owner.first_of_week);
+        Calendar.Week event_start_week = event_start_date.week_of(owner.first_of_week);
+        
+        return cell_week.equal_to(event_start_week) ? event_start_date : cell_week.start_date;
+    }
+    
     private bool on_draw(Cairo.Context ctx) {
         // calculate extents if not already calculated;
         if (line_height_px < 0 || top_line_height_px < 0)
@@ -266,15 +336,10 @@ public class Cell : Gtk.EventBox {
             draw_line_of_text(ctx, -1, color, date.day_of_month.informal_number, false);
         }
         
-        // represents the line number being drawn (zero-based for remaining lines)
-        int line_number = 0;
-        line_to_event.clear();
-        
-        // draw all events in chronological order, all-day events first, storing lookup data
-        // as the "lines" are drawn ... make sure to convert them all to local timezone
-        foreach (Component.Event event in days_events) {
-            if (!event.calendar_source.visible)
-                continue;
+        // walk the assigned line numbers for each event and draw
+        Gee.MapIterator<int, Component.Event> iter = line_to_event.map_iterator();
+        while (iter.next()) {
+            Component.Event event = iter.get_value();
             
             string text, tooltip_text;
             if (event.is_all_day) {
@@ -292,9 +357,8 @@ public class Cell : Gtk.EventBox {
                 tooltip_text = text;
             }
             
-            Pango.Layout layout = draw_line_of_text(ctx, line_number, event.calendar_source.color_as_rgba(),
+            Pango.Layout layout = draw_line_of_text(ctx, iter.get_key(), 
event.calendar_source.color_as_rgba(),
                 text, event.is_all_day);
-            line_to_event.set(line_number++, event);
             event.set_data<string?>(KEY_TOOLTIP, layout.is_ellipsized() ? tooltip_text : null);
         }
         
diff --git a/src/view/month/month-controllable.vala b/src/view/month/month-controllable.vala
index 3707df8..cf5b34f 100644
--- a/src/view/month/month-controllable.vala
+++ b/src/view/month/month-controllable.vala
@@ -168,6 +168,10 @@ public class Controllable : Gtk.Grid, View.Controllable {
         return (Cell) get_child_at(col, row);
     }
     
+    internal Cell? get_cell_for_date(Calendar.Date date) {
+        return date_to_cell.get(date);
+    }
+    
     private void foreach_cell(CellCallback callback) {
         foreach (unowned Gtk.Widget widget in get_children()) {
             // watch for Gtk.Labels across the top
@@ -286,15 +290,25 @@ public class Controllable : Gtk.Grid, View.Controllable {
     }
     
     private void on_calendar_added(Backing.CalendarSource calendar) {
-        calendar.notify[Backing.Source.PROP_VISIBLE].connect(queue_draw);
+        calendar.notify[Backing.Source.PROP_VISIBLE].connect(on_calendar_visibility_changed);
         calendar.notify[Backing.Source.PROP_COLOR].connect(queue_draw);
     }
     
     private void on_calendar_removed(Backing.CalendarSource calendar) {
-        calendar.notify[Backing.Source.PROP_VISIBLE].disconnect(queue_draw);
+        calendar.notify[Backing.Source.PROP_VISIBLE].disconnect(on_calendar_visibility_changed);
         calendar.notify[Backing.Source.PROP_COLOR].disconnect(queue_draw);
     }
     
+    private void on_calendar_visibility_changed(Object o, ParamSpec pspec) {
+        Backing.CalendarSource calendar = (Backing.CalendarSource) o;
+        
+        foreach_cell((cell) => {
+            cell.notify_calendar_visibility_changed(calendar);
+            
+            return true;
+        });
+    }
+    
     private void on_instance_added(Component.Instance instance) {
         Component.Event? event = instance as Component.Event;
         if (event == null)


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