[california/wip/725783-time] Further refinements, including clamping



commit a322d663c0e2ba8cb5aa391f28321b40d3a1714c
Author: Jim Nelson <jim yorba org>
Date:   Mon Aug 4 17:24:58 2014 -0700

    Further refinements, including clamping

 src/Makefile.am                          |    1 +
 src/calendar/calendar-date.vala          |    2 +
 src/calendar/calendar-exact-time.vala    |   21 ++++++++++
 src/host/host-date-time-widget.vala      |   29 +++++++++++--
 src/host/host-event-time-settings.vala   |   42 ++++++++++++++++----
 src/tests/tests-calendar-exact-time.vala |   63 ++++++++++++++++++++++++++++++
 src/tests/tests.vala                     |    1 +
 7 files changed, 146 insertions(+), 13 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 95d1833..a858b9a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -107,6 +107,7 @@ california_VALASOURCES = \
        \
        tests/tests.vala \
        tests/tests-calendar-date.vala \
+       tests/tests-calendar-exact-time.vala \
        tests/tests-calendar-month-of-year.vala \
        tests/tests-calendar-month-span.vala \
        tests/tests-calendar-wall-time.vala \
diff --git a/src/calendar/calendar-date.vala b/src/calendar/calendar-date.vala
index 9f0e71f..2f8da5c 100644
--- a/src/calendar/calendar-date.vala
+++ b/src/calendar/calendar-date.vala
@@ -296,6 +296,8 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
     
     /**
      * Returns a { link Date} clamped between the two supplied Dates, inclusive.
+     *
+     * @see Span.clamp_between
      */
     public Date clamp(Date min, Date max) {
         GLib.Date clone = gdate;
diff --git a/src/calendar/calendar-exact-time.vala b/src/calendar/calendar-exact-time.vala
index 49ff335..d508dae 100644
--- a/src/calendar/calendar-exact-time.vala
+++ b/src/calendar/calendar-exact-time.vala
@@ -162,6 +162,27 @@ public class ExactTime : BaseObject, Gee.Comparable<ExactTime>, Gee.Hashable<Exa
     }
     
     /**
+     * Clamp the { link ExactTime} between a supplied floor and ceiling ExactTime.
+     *
+     * If null is passed for either value, it will be ignored (effectively making clamp() work like
+     * a floor() or ceiling() method).  If null is passed for both, the current ExactTime is
+     * returned.
+     *
+     * Results are indeterminate if a floor chronologically later than a ceiling is passed in.
+     */
+    public ExactTime clamp(ExactTime? floor, ExactTime? ceiling) {
+        ExactTime clamped = this;
+        
+        if (floor != null && clamped.compare_to(floor) < 0)
+            clamped = floor;
+        
+        if (ceiling != null && clamped.compare_to(ceiling) > 0)
+            clamped = ceiling;
+        
+        return clamped;
+    }
+    
+    /**
      * See DateTime.to_unix_time.
      */
     public time_t to_time_t() {
diff --git a/src/host/host-date-time-widget.vala b/src/host/host-date-time-widget.vala
index b12859f..4dd0c20 100644
--- a/src/host/host-date-time-widget.vala
+++ b/src/host/host-date-time-widget.vala
@@ -12,6 +12,8 @@ public class DateTimeWidget : Gtk.Box {
     public const string PROP_ENABLE_DATE = "enable-date";
     public const string PROP_DATE = "date";
     public const string PROP_WALL_TIME = "wall-time";
+    public const string PROP_FLOOR = "floor";
+    public const string PROP_CEILING = "ceiling";
     
     public bool enable_time { get; set; default = true; }
     
@@ -21,6 +23,10 @@ public class DateTimeWidget : Gtk.Box {
     
     public Calendar.WallTime wall_time { get; set; default = Calendar.System.now.to_wall_time(); }
     
+    public Calendar.ExactTime? floor { get; set; default = null; }
+    
+    public Calendar.ExactTime? ceiling { get; set; default = null; }
+    
     [GtkChild]
     private Gtk.Calendar calendar;
     
@@ -133,8 +139,9 @@ public class DateTimeWidget : Gtk.Box {
         if (!adjust_time_controls(details, out amount, out time_unit))
             return Toolkit.PROPAGATE;
         
+        // adjust wall time and ensure it's clamped
         // this will update the entry fields, so don't disconnect widget signals
-        wall_time = wall_time.adjust(amount, time_unit, null);
+        wall_time = get_clamped(date, wall_time.adjust(amount, time_unit, null)).to_wall_time();
         
         return Toolkit.STOP;
     }
@@ -147,10 +154,10 @@ public class DateTimeWidget : Gtk.Box {
             amount = -1;
             time_unit = Calendar.TimeUnit.HOUR;
         } else if (details.widget == minutes_up) {
-            amount = 1;
+            amount = 5;
             time_unit = Calendar.TimeUnit.MINUTE;
         } else if (details.widget == minutes_down) {
-            amount = -1;
+            amount = -5;
             time_unit = Calendar.TimeUnit.MINUTE;
         } else if (details.widget == meridiem_up) {
             amount = 12;
@@ -168,6 +175,13 @@ public class DateTimeWidget : Gtk.Box {
         return true;
     }
     
+    private Calendar.ExactTime get_clamped(Calendar.Date proposed_date, Calendar.WallTime proposed_time) {
+        Calendar.ExactTime exact_time = new Calendar.ExactTime(Calendar.Timezone.local, proposed_date,
+            proposed_time);
+        
+        return exact_time.clamp(floor, ceiling);
+    }
+    
     private Calendar.Date? get_selected_date() {
         if (calendar.day == 0)
             return null;
@@ -189,8 +203,13 @@ public class DateTimeWidget : Gtk.Box {
         disconnect_property_signals();
         
         Calendar.Date? selected = get_selected_date();
-        if (selected != null && !selected.equal_to(date))
-            date = selected;
+        if (selected != null) {
+            // clamp selected date; if not the same as selected, select it (in effect, prevents the
+            // user from selecting a date on the GtkCalendar outside of range)
+            date = new Calendar.Date.from_exact_time(get_clamped(selected, wall_time));
+            if (!date.equal_to(selected))
+                on_date_changed();
+        }
         
         connect_property_signals();
     }
diff --git a/src/host/host-event-time-settings.vala b/src/host/host-event-time-settings.vala
index 885a7d6..5253cc6 100644
--- a/src/host/host-event-time-settings.vala
+++ b/src/host/host-event-time-settings.vala
@@ -77,10 +77,10 @@ public class EventTimeSettings : Gtk.Box, Toolkit.Card {
         from_box.pack_start(from_widget);
         to_box.pack_start(to_widget);
         
-        from_widget.notify[DateTimeWidget.PROP_DATE].connect(on_update_summary);
-        from_widget.notify[DateTimeWidget.PROP_WALL_TIME].connect(on_update_summary);
-        to_widget.notify[DateTimeWidget.PROP_DATE].connect(on_update_summary);
-        to_widget.notify[DateTimeWidget.PROP_WALL_TIME].connect(on_update_summary);
+        from_widget.notify[DateTimeWidget.PROP_DATE].connect(on_from_changed);
+        from_widget.notify[DateTimeWidget.PROP_WALL_TIME].connect(on_from_changed);
+        to_widget.notify[DateTimeWidget.PROP_DATE].connect(on_to_changed);
+        to_widget.notify[DateTimeWidget.PROP_WALL_TIME].connect(on_to_changed);
         all_day_checkbutton.notify["active"].connect(on_update_summary);
         
         all_day_checkbutton.bind_property("active", from_widget, DateTimeWidget.PROP_ENABLE_TIME,
@@ -98,18 +98,28 @@ public class EventTimeSettings : Gtk.Box, Toolkit.Card {
     public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message_value) {
         message = (Message) message_value;
         
+        Calendar.DateSpan date_span = message.get_event_date_span(Calendar.Timezone.local);
+        from_widget.date = date_span.start_date;
+        to_widget.date = date_span.end_date;
+        
         // only set wall time if not all day; let old wall times float so user can return to them
         // later while Deck is active
         if (message.exact_time_span != null) {
             Calendar.ExactTimeSpan time_span = message.exact_time_span.to_timezone(Calendar.Timezone.local);
             from_widget.wall_time = time_span.start_exact_time.to_wall_time();
             to_widget.wall_time = time_span.end_exact_time.to_wall_time();
+        } else {
+            // set to defaults in case user wants to change from all-day to timed event
+            from_widget.wall_time = Calendar.System.now.to_wall_time().round_down(15, 
Calendar.TimeUnit.MINUTE);
+            if (date_span.is_same_day) {
+                // one-hour event is default
+                to_widget.wall_time = from_widget.wall_time.adjust(1, Calendar.TimeUnit.HOUR, null);
+            } else {
+                // different days, same time on each day
+                to_widget.wall_time = from_widget.wall_time;
+            }
         }
         
-        Calendar.DateSpan date_span = message.get_event_date_span(Calendar.Timezone.local);
-        from_widget.date = date_span.start_date;
-        to_widget.date = date_span.end_date;
-        
         all_day_checkbutton.active = (message.exact_time_span == null);
     }
     
@@ -150,6 +160,22 @@ public class EventTimeSettings : Gtk.Box, Toolkit.Card {
         else
             summary_label.label = get_exact_time_span().to_pretty_string(date_flags, time_flags);
     }
+    
+    private void on_from_changed() {
+        // clamp to_widget to not allow earlier date/times than from_widget
+        to_widget.floor = new Calendar.ExactTime(Calendar.System.timezone, from_widget.date,
+            from_widget.wall_time);
+        
+        on_update_summary();
+    }
+    
+    private void on_to_changed() {
+        // clamp from_widget to not allow later date/times than to_widget
+        from_widget.ceiling = new Calendar.ExactTime(Calendar.System.timezone, to_widget.date,
+            to_widget.wall_time);
+        
+        on_update_summary();
+    }
 }
 
 }
diff --git a/src/tests/tests-calendar-exact-time.vala b/src/tests/tests-calendar-exact-time.vala
new file mode 100644
index 0000000..7d4b9e5
--- /dev/null
+++ b/src/tests/tests-calendar-exact-time.vala
@@ -0,0 +1,63 @@
+/* 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 {
+
+internal class CalendarExactTime : UnitTest.Harness {
+    private Calendar.ExactTime? now;
+    private Calendar.ExactTime? past;
+    private Calendar.ExactTime? future;
+    
+    public CalendarExactTime() {
+        add_case("clamp-floor-unaltered", clamp_floor_unaltered);
+        add_case("clamp-floor-altered", clamp_floor_altered);
+        add_case("clamp-ceiling-unaltered", clamp_ceiling_unaltered);
+        add_case("clamp-ceiling-altered", clamp_ceiling_altered);
+        add_case("clamp-both-unaltered", clamp_both_unaltered);
+        add_case("clamp-both-altered", clamp_both_altered);
+    }
+    
+    protected override void setup() throws Error {
+        Calendar.init();
+        
+        now = Calendar.System.now;
+        past = now.adjust_time(-1, Calendar.TimeUnit.MINUTE);
+        future = now.adjust_time(1, Calendar.TimeUnit.MINUTE);
+    }
+    
+    protected override void teardown() {
+        now = past = future = null;
+        
+        Calendar.terminate();
+    }
+    
+    private bool clamp_floor_unaltered() throws Error {
+        return now.clamp(past, null).equal_to(now);
+    }
+    
+    private bool clamp_floor_altered() throws Error {
+        return now.clamp(future, null).equal_to(future);
+    }
+    
+    private bool clamp_ceiling_unaltered() throws Error {
+        return now.clamp(null, future).equal_to(now);
+    }
+    
+    private bool clamp_ceiling_altered() throws Error {
+        return now.clamp(null, past).equal_to(past);
+    }
+    
+    private bool clamp_both_unaltered() throws Error {
+        return now.clamp(past, future).equal_to(now);
+    }
+    
+    private bool clamp_both_altered() throws Error {
+        return now.clamp(past, past).equal_to(past);
+    }
+}
+
+}
+
diff --git a/src/tests/tests.vala b/src/tests/tests.vala
index 24c171e..11a3a47 100644
--- a/src/tests/tests.vala
+++ b/src/tests/tests.vala
@@ -17,6 +17,7 @@ public int run(string[] args) {
     UnitTest.Harness.register(new CalendarMonthSpan());
     UnitTest.Harness.register(new CalendarMonthOfYear());
     UnitTest.Harness.register(new CalendarWallTime());
+    UnitTest.Harness.register(new CalendarExactTime());
     UnitTest.Harness.register(new QuickAdd());
     UnitTest.Harness.register(new QuickAddRecurring());
     


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