[california/wip/725767-week] Flesh out week view, fix layout issues, hours down left-side



commit 6242ef174c18de3e5a8ae7ba05d71f9eb07fe5ac
Author: Jim Nelson <jim yorba org>
Date:   Wed May 7 19:57:25 2014 -0700

    Flesh out week view, fix layout issues, hours down left-side

 src/Makefile.am                      |    2 +
 src/calendar/calendar-wall-time.vala |    5 +-
 src/calendar/calendar.vala           |    4 +-
 src/host/host-main-window.vala       |    3 +-
 src/toolkit/toolkit-stack-model.vala |    2 -
 src/view/month/month-cell.vala       |    4 +-
 src/view/month/month-controller.vala |    2 -
 src/view/view-palette.vala           |    5 +-
 src/view/week/week-controller.vala   |    6 --
 src/view/week/week-day-pane.vala     |   57 ++++++++++++++++++++++
 src/view/week/week-grid.vala         |   86 +++++++++++++++++++++------------
 src/view/week/week-hour-runner.vala  |   56 ++++++++++++++++++++++
 src/view/week/week-pane.vala         |   68 ++++++++++++---------------
 13 files changed, 212 insertions(+), 88 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index a5b5de4..8bf8f9c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -136,7 +136,9 @@ california_VALASOURCES = \
        \
        view/week/week.vala \
        view/week/week-controller.vala \
+       view/week/week-day-pane.vala \
        view/week/week-grid.vala \
+       view/week/week-hour-runner.vala \
        view/week/week-pane.vala \
        \
        $(NULL)
diff --git a/src/calendar/calendar-wall-time.vala b/src/calendar/calendar-wall-time.vala
index 495fafb..b8806be 100644
--- a/src/calendar/calendar-wall-time.vala
+++ b/src/calendar/calendar-wall-time.vala
@@ -361,8 +361,9 @@ public class WallTime : BaseObject, Gee.Comparable<WallTime>, Gee.Hashable<WallT
         
         // Not marked for translation on thw assumption that a 12-hour hour followed by the meridiem
         // isn't something that varies between locales, on the assumption that the user has
-        // specified 12-hour time to begin with
-        if (optional_min && minute == 0)
+        // specified 12-hour time to begin with ... don't allow for 24-hour time because it doesn't
+        // look right (especially early hours, i.e. "0", "2")
+        if (optional_min && minute == 0 && !is_24hr)
             return "%d%s".printf(is_24hr ? hour : 12hour, meridiem);
         
         if (!include_sec) {
diff --git a/src/calendar/calendar.vala b/src/calendar/calendar.vala
index db3cc06..4d6d705 100644
--- a/src/calendar/calendar.vala
+++ b/src/calendar/calendar.vala
@@ -156,10 +156,10 @@ public void init() throws Error {
     FMT_12HOUR_MIN_SEC_MERIDIEM = _("%d:%02d:%02d%s");
     
     /// The 24-hour time with minutes, i.e. "17:06"
-    FMT_24HOUR_MIN = _("%d:%02d");
+    FMT_24HOUR_MIN = _("%02d:%02d");
     
     /// The 24-hour time with minutes and seconds, i.e. "17:06:31"
-    FMT_24HOUR_MIN_SEC = _("%d:%02d:%02d");
+    FMT_24HOUR_MIN_SEC = _("%02d:%02d:%02d");
     
     // Used by quick-add to convert a user's day unit into an internal value.  Common abbreviations
     // (without punctuation) should be included.  Each word must be separated by semi-colons.
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index 64c61b6..f629565 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -69,8 +69,9 @@ public class MainWindow : Gtk.ApplicationWindow {
         view_stack.notify["visible-child"].connect(on_view_changed);
         
         // add views to view stack, first added is first shown
-        add_controller(month_view);
+        // TODO: Switch back to month_view first
         add_controller(week_view);
+        add_controller(month_view);
         
         // if not on Unity, use headerbar as the titlebar (removes window chrome) and provide close
         // button for users who might have trouble finding it otherwise
diff --git a/src/toolkit/toolkit-stack-model.vala b/src/toolkit/toolkit-stack-model.vala
index c63f615..cbd72be 100644
--- a/src/toolkit/toolkit-stack-model.vala
+++ b/src/toolkit/toolkit-stack-model.vala
@@ -293,7 +293,6 @@ public class StackModel<G> : BaseObject {
         if (stack_destroyed || in_balance_cache)
             return;
         
-        debug("%s: balance cache: %s", to_string(), why);
         in_balance_cache = true;
         
         // trim existing widgets from cache
@@ -319,7 +318,6 @@ public class StackModel<G> : BaseObject {
             }
         }
         
-        debug("%s: cache balanced: %s", to_string(), why);
         in_balance_cache = false;
     }
     
diff --git a/src/view/month/month-cell.vala b/src/view/month/month-cell.vala
index cc3d773..7518af4 100644
--- a/src/view/month/month-cell.vala
+++ b/src/view/month/month-cell.vala
@@ -470,10 +470,10 @@ private class Cell : Gtk.EventBox {
             y = Palette.TEXT_MARGIN_PX;
         } else {
             // starting y of "regular" lines
-            y = Palette.TEXT_MARGIN_PX + Palette.instance.normal_font_height_px + Palette.LINE_SPACING_PX;
+            y = Palette.TEXT_MARGIN_PX + Palette.instance.normal_font_height_px + Palette.LINE_PADDING_PX;
             
             // add additional lines
-            y += line_number * (Palette.instance.small_font_height_px + Palette.LINE_SPACING_PX);
+            y += line_number * (Palette.instance.small_font_height_px + Palette.LINE_PADDING_PX);
         }
         
         return y;
diff --git a/src/view/month/month-controller.vala b/src/view/month/month-controller.vala
index c01c24c..b40a2df 100644
--- a/src/view/month/month-controller.vala
+++ b/src/view/month/month-controller.vala
@@ -121,8 +121,6 @@ public class Controller : BaseObject, View.Controllable {
     }
     
     private Gtk.Widget model_presentation(Calendar.MonthOfYear moy, out string? id) {
-        debug("Creating month grid for %s", moy.to_string());
-        
         Grid grid = new Grid(this, moy);
         id = grid.id;
         
diff --git a/src/view/view-palette.vala b/src/view/view-palette.vala
index a3dc4ee..ebdbf6e 100644
--- a/src/view/view-palette.vala
+++ b/src/view/view-palette.vala
@@ -20,9 +20,9 @@ public class Palette : BaseObject {
     public const int TEXT_MARGIN_PX = 2;
     
     /**
-     * Line spacing when painting text (in pixels).
+     * Line padding when painting text (in pixels).
      */
-    public const int LINE_SPACING_PX = 4;
+    public const int LINE_PADDING_PX = 4;
     
     /**
      * Hairline line width.
@@ -174,6 +174,7 @@ public class Palette : BaseObject {
     public static Cairo.Context prepare_hairline_border(Cairo.Context ctx) {
         Gdk.cairo_set_source_rgba(ctx, instance.border);
         ctx.set_line_width(HAIRLINE_WIDTH);
+        ctx.set_dash(null, 0);
         
         return ctx;
     }
diff --git a/src/view/week/week-controller.vala b/src/view/week/week-controller.vala
index 0be5d54..e76f559 100644
--- a/src/view/week/week-controller.vala
+++ b/src/view/week/week-controller.vala
@@ -111,8 +111,6 @@ public class Controller : BaseObject, View.Controllable {
     }
     
     private Gtk.Widget model_presentation(Calendar.Week week, out string? id) {
-        debug("Creating Grid for %s", week.to_string());
-        
         Grid week_grid = new Grid(week);
         id = week_grid.id;
         
@@ -124,8 +122,6 @@ public class Controller : BaseObject, View.Controllable {
         if (week.equal_to(Calendar.System.today.week_of(first_of_week)))
             return false;
         
-        debug("Trim %s: %s", week.to_string(), (!cache_span.has(week)).to_string());
-        
         // otherwise only keep weeks that are in the current cache span
         return !cache_span.has(week);
     }
@@ -137,8 +133,6 @@ public class Controller : BaseObject, View.Controllable {
         // add today's week to the mix
         weeks.add(Calendar.System.today.week_of(first_of_week));
         
-        debug("ensuring %d weeks", weeks.size);
-        
         return weeks;
     }
     
diff --git a/src/view/week/week-day-pane.vala b/src/view/week/week-day-pane.vala
new file mode 100644
index 0000000..714def6
--- /dev/null
+++ b/src/view/week/week-day-pane.vala
@@ -0,0 +1,57 @@
+/* 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.Week {
+
+internal class DayPane : Pane {
+    public const string PROP_OWNER = "owner";
+    public const string PROP_DATE = "date";
+    public const string PROP_SELECTED = "selected";
+    
+    public Calendar.Date date { get; set; }
+    
+    public bool selected { get; set; default = false; }
+    
+    public DayPane(Grid owner, Calendar.Date date) {
+        base (owner, -1);
+        
+        this.date = date;
+        
+        notify[PROP_DATE].connect(queue_draw);
+        notify[PROP_SELECTED].connect(queue_draw);
+        Calendar.System.instance.is_24hr_changed.connect(queue_draw);
+        Calendar.System.instance.today_changed.connect(on_today_changed);
+    }
+    
+    ~DayPane() {
+        Calendar.System.instance.is_24hr_changed.disconnect(queue_draw);
+        Calendar.System.instance.today_changed.disconnect(on_today_changed);
+    }
+    
+    private void on_today_changed(Calendar.Date old_today, Calendar.Date new_today) {
+        // need to know re: redrawing background color to indicate current day
+        if (date.equal_to(old_today) || date.equal_to(new_today))
+            queue_draw();
+    }
+    
+    // note that a painter's algorithm should be used here: background should be painted before
+    // calling base method, and foreground afterward
+    protected override bool on_draw(Cairo.Context ctx) {
+        // shade background color if this is current day or selected
+        if (selected) {
+            Gdk.cairo_set_source_rgba(ctx, Palette.instance.selection);
+            ctx.paint();
+        } else if (date.equal_to(Calendar.System.today)) {
+            Gdk.cairo_set_source_rgba(ctx, Palette.instance.current_day);
+            ctx.paint();
+        }
+        
+        return base.on_draw(ctx);
+    }
+}
+
+}
+
diff --git a/src/view/week/week-grid.vala b/src/view/week/week-grid.vala
index a4a2eec..a68c24d 100644
--- a/src/view/week/week-grid.vala
+++ b/src/view/week/week-grid.vala
@@ -7,10 +7,22 @@
 namespace California.View.Week {
 
 /**
- * A Gtk.Grid that holds the various { link Pane}s for each day of thw week.
+ * A GTK container that holds the various { link Pane}s for each day of thw week.
+ *
+ * Although this looks to be the perfect use of Gtk.Grid, some serious limitations with that widget
+ * forced this implementation to fall back on the old "boxes within boxes" of GTK 2.0.
+ * Specifically, the top-left cell in this widget must be a fixed width (the same as
+ * { link HourRunner}'s) and Gtk.Grid wouldn't let that occur, always giving it more space than it
+ * needed (although, strangely, always honoring the requested width for HourRunner).  This ruined
+ * the effect of an "empty" box in the top left corner where the date labels met the hour runner.
+ *
+ * The basic layout is a top row of date labels (with a spacer at the beginning, as mentioned)
+ * with a scrollable box of { link DayPane}s with an HourRunner on the left side which scrolls
+ * as well.  This layout ensures the date labels are always visible as the user scrolls down the
+ * time of day for all the panes.
  */
 
-internal class Grid : Gtk.Grid {
+internal class Grid : Gtk.Box {
     public const string PROP_WEEK = "week";
     
     /**
@@ -25,56 +37,68 @@ internal class Grid : Gtk.Grid {
      */
     public string id { owned get { return week.to_string(); } }
     
-    private Gtk.Grid pane_grid = new Gtk.Grid();
-    private Gtk.ScrolledWindow scrolled_panes = new Gtk.ScrolledWindow(null, null);
-    
     public Grid(Calendar.Week week) {
+        Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
+        
         this.week = week;
         
-        column_homogeneous = true;
-        column_spacing = 0;
-        row_homogeneous = false;
-        row_spacing = 0;
+        // hold date labels in a horizontal box
+        Gtk.Box label_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
+        pack_start(label_box, false, true, 0);
         
-        pane_grid.column_homogeneous = true;
+        // fixed size space in top left corner of "grid"
+        Gtk.DrawingArea spacer = new Gtk.DrawingArea();
+        spacer.set_size_request(HourRunner.REQUESTED_WIDTH, -1);
+        spacer.draw.connect(on_draw_bottom_line);
+        label_box.pack_start(spacer, false, false, 0);
+        
+        // hold Panes (DayPanes and HourRunner) in a scrolling Gtk.Grid
+        Gtk.Grid pane_grid = new Gtk.Grid();
+        pane_grid.column_homogeneous = false;
         pane_grid.column_spacing = 0;
-        pane_grid.row_homogeneous = true;
+        pane_grid.row_homogeneous = false;
         pane_grid.row_spacing = 0;
         
-        scrolled_panes.hscrollbar_policy = Gtk.PolicyType.NEVER;
-        scrolled_panes.vscrollbar_policy = Gtk.PolicyType.ALWAYS;
-        scrolled_panes.add(pane_grid);
+        // attach an HourRunner to the left side of the Panes grid
+        pane_grid.attach(new HourRunner(this), 0, 0, 1, 1);
         
-        // date labels across the top, week panes extending across the bottom, all stored inside
-        // a scrolled window
-        int col = 0;
+        // date labels across the top, week panes extending across the bottom ... start col at one
+        // to account for spacer/HourRunner
+        int col = 1;
         foreach (Calendar.Date date in week) {
             Gtk.Label date_label = new Gtk.Label("%s %d/%d".printf(date.day_of_week.abbrev_name,
                 date.month_of_year().month.value, date.day_of_month.value));
             
             // draw a line along the bottom of the label
-            date_label.draw.connect((ctx) => {
-                int width = date_label.get_allocated_width();
-                int height = date_label.get_allocated_height();
-                
-                Palette.prepare_hairline_border(ctx);
-                ctx.move_to(0, height);
-                ctx.line_to(width, height);
-                ctx.stroke();
-                
-                return false;
-            });
+            date_label.draw.connect(on_draw_bottom_line);
             
-            attach(date_label, col, 0, 1, 1);
+            label_box.pack_start(date_label, true, true, 0);
             
-            Pane pane = new Pane(this, date);
+            DayPane pane = new DayPane(this, date);
             pane.expand = true;
             pane_grid.attach(pane, col, 0, 1, 1);
             
             col++;
         }
         
-        attach(scrolled_panes, 0, 1, col, 1);
+        // place Panes grid into a GtkScrolledWindow
+        Gtk.ScrolledWindow scrolled_panes = new Gtk.ScrolledWindow(null, null);
+        scrolled_panes.hscrollbar_policy = Gtk.PolicyType.NEVER;
+        scrolled_panes.vscrollbar_policy = Gtk.PolicyType.ALWAYS;
+        scrolled_panes.add(pane_grid);
+        pack_end(scrolled_panes, true, true, 0);
+    }
+    
+    private bool on_draw_bottom_line(Gtk.Widget widget, Cairo.Context ctx) {
+        int width = widget.get_allocated_width();
+        int height = widget.get_allocated_height();
+        
+        Palette.prepare_hairline_border(ctx);
+        ctx.move_to(0, height);
+        ctx.line_to(width, height);
+        ctx.stroke();
+        
+        return false;
     }
 }
 
diff --git a/src/view/week/week-hour-runner.vala b/src/view/week/week-hour-runner.vala
new file mode 100644
index 0000000..1bcbcf1
--- /dev/null
+++ b/src/view/week/week-hour-runner.vala
@@ -0,0 +1,56 @@
+/* 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.Week {
+
+internal class HourRunner : Pane {
+    public const int REQUESTED_WIDTH = 50;
+    
+    private const Calendar.WallTime.PrettyFlag TIME_FLAGS =
+        Calendar.WallTime.PrettyFlag.OPTIONAL_MINUTES;
+    
+    public HourRunner(Grid owner) {
+        base (owner, REQUESTED_WIDTH);
+        
+        Calendar.System.instance.is_24hr_changed.connect(queue_draw);
+    }
+    
+    ~HourRunner() {
+        Calendar.System.instance.is_24hr_changed.disconnect(queue_draw);
+    }
+    
+    // note that a painter's algorithm should be used here: background should be painted before
+    // calling base method, and foreground afterward
+    protected override bool on_draw(Cairo.Context ctx) {
+        if (!base.on_draw(ctx))
+            return false;
+        
+        int right_justify_px = get_allocated_width() - Palette.TEXT_MARGIN_PX;
+        
+        // draw time-of-day down right-hand side of HourRunner pane, which acts as tick marks for
+        // the rest of the week view
+        Calendar.WallTime wall_time = Calendar.WallTime.earliest;
+        for (;;) {
+            Pango.Layout layout = create_pango_layout(wall_time.to_pretty_string(TIME_FLAGS));
+            layout.set_font_description(Palette.instance.small_font);
+            layout.set_width(right_justify_px);
+            layout.set_alignment(Pango.Alignment.RIGHT);
+            
+            ctx.move_to(right_justify_px, get_text_y(wall_time));
+            Pango.cairo_show_layout(ctx, layout);
+            
+            bool rollover;
+            wall_time = wall_time.adjust(1, Calendar.TimeUnit.HOUR, out rollover);
+            if (rollover)
+                break;
+        }
+        
+        return true;
+    }
+}
+
+}
+
diff --git a/src/view/week/week-pane.vala b/src/view/week/week-pane.vala
index b52f05a..5dc53a1 100644
--- a/src/view/week/week-pane.vala
+++ b/src/view/week/week-pane.vala
@@ -6,70 +6,48 @@
 
 namespace California.View.Week {
 
-internal class Pane : Gtk.EventBox {
-    public const string PROP_OWNER = "owner";
-    public const string PROP_DATE = "date";
-    public const string PROP_SELECTED = "selected";
-    
+internal abstract class Pane : Gtk.EventBox {
     public weak Grid owner { get; private set; }
     
-    public Calendar.Date date { get; set; }
-    
-    public bool selected { get; set; default = false; }
+    // The height of each "line" of text, including top and bottom padding
+    protected int line_height_px { get; private set; default = 0; }
     
+    private int requested_width;
     private Gtk.DrawingArea canvas = new Gtk.DrawingArea();
-    private int line_height_px = 0;
     
-    public Pane(Grid owner, Calendar.Date initial_date) {
+    public Pane(Grid owner, int requested_width) {
         this.owner = owner;
-        date = initial_date;
+        this.requested_width = requested_width;
         
         margin = 0;
         
         add(canvas);
         
-        notify[PROP_DATE].connect(queue_draw);
-        notify[PROP_SELECTED].connect(queue_draw);
+        update_palette_metrics();
         Palette.instance.palette_changed.connect(on_palette_changed);
-        Calendar.System.instance.is_24hr_changed.connect(queue_draw);
-        Calendar.System.instance.today_changed.connect(on_today_changed);
         
         canvas.draw.connect(on_draw);
     }
     
     ~Pane() {
         Palette.instance.palette_changed.disconnect(on_palette_changed);
-        Calendar.System.instance.is_24hr_changed.disconnect(queue_draw);
-        Calendar.System.instance.today_changed.disconnect(on_today_changed);
     }
     
-    private void on_palette_changed() {
-        // calculcate the amount of space each "line" gets when drawing (normal font height plus
+    private void update_palette_metrics() {
+        // calculate the amount of space each "line" gets when drawing (normal font height plus
         // padding on top and bottom)
-        line_height_px = Palette.instance.normal_font_height_px + (Palette.LINE_SPACING_PX * 2);
+        line_height_px = Palette.instance.normal_font_height_px + (Palette.LINE_PADDING_PX * 2);
         
         // update the height request based on the number of lines needed to show the entire day
-        canvas.set_size_request(-1, get_line_y(Calendar.WallTime.latest) + line_height_px);
-        
-        queue_draw();
+        canvas.set_size_request(requested_width, get_line_y(Calendar.WallTime.latest));
     }
     
-    private void on_today_changed(Calendar.Date old_today, Calendar.Date new_today) {
-        // need to know re: redrawing background color to indicate current day
-        if (date.equal_to(old_today) || date.equal_to(new_today))
-            queue_draw();
+    private void on_palette_changed() {
+        update_palette_metrics();
+        queue_draw();
     }
     
-    private bool on_draw(Cairo.Context ctx) {
-        // shade background color if this is current day or selected
-        if (selected) {
-            Gdk.cairo_set_source_rgba(ctx, Palette.instance.selection);
-            ctx.paint();
-        } else if (date.equal_to(Calendar.System.today)) {
-            Gdk.cairo_set_source_rgba(ctx, Palette.instance.current_day);
-            ctx.paint();
-        }
-        
+    protected virtual bool on_draw(Cairo.Context ctx) {
         int width = get_allocated_width();
         int height = get_allocated_height();
         
@@ -104,7 +82,12 @@ internal class Pane : Gtk.EventBox {
         return true;
     }
     
-    private int get_line_y(Calendar.WallTime wall_time) {
+    /**
+     * Returns the y (in pixels) for a particular line of text for the { link Calendar.WallTime}.
+     *
+     * If displaying text, use { link get_text_y}, as that will deduct padding.
+     */
+    protected int get_line_y(Calendar.WallTime wall_time) {
         // every hour gets two "lines" of text
         int line_y = line_height_px * 2 * wall_time.hour;
         
@@ -118,6 +101,15 @@ internal class Pane : Gtk.EventBox {
         
         return line_y;
     }
+    
+    /**
+     * Returns the y (in pixels) for the top of a line of text at { link Calendar.WallTime}.
+     *
+     * Use this when displaying text.  Drawing lines, borders, etc. should use { link get_line_y}.
+     */
+    protected int get_text_y(Calendar.WallTime wall_time) {
+        return get_line_y(wall_time) + Palette.LINE_PADDING_PX;
+    }
 }
 
 }


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