[gnome-clocks/wip/flowbox: 9/9] Port to GtkFlowBox



commit e8a44c8c75f8a9f084e3a48872e2d576762769e0
Author: Paolo Borelli <pborelli gnome org>
Date:   Sun Feb 21 10:32:37 2016 +0100

    Port to GtkFlowBox
    
    Reimplement the ContentView widget using FlowBox instead of IconView.
    Alarm and World now use a template widget for their tiles. A lot of
    code and css is removed and simplified.
    
    This port tries to look and behave exactly the same as the old code:
    dropping IconView and a lot of custom drawing code will allow easier
    design changes, but such changes are not in the scope of this patch.
    However FlowBox behaves slightly differently from IconView when
    resizing (the space among tiles changes and this is especially
    noticeable when the action bar appears)
    
    WIP: still to fix
     - prelight in the world stripe
     - adding the first alarm or world clock does not show the select
       button

 data/css/gnome-clocks.css              |   82 +++---
 data/css/gnome-clocks.highcontrast.css |   70 ++---
 data/gnome-clocks.gresource.xml        |    2 +
 data/ui/alarm.ui                       |    3 +-
 data/ui/alarmtile.ui                   |   45 +++
 data/ui/world.ui                       |    3 +-
 data/ui/worldtile.ui                   |   86 ++++++
 src/alarm.vala                         |  120 ++++++---
 src/widgets.vala                       |  498 +++++++++-----------------------
 src/world.vala                         |   66 +++--
 10 files changed, 479 insertions(+), 496 deletions(-)
---
diff --git a/data/css/gnome-clocks.css b/data/css/gnome-clocks.css
index daec8e6..c6b3dc2 100644
--- a/data/css/gnome-clocks.css
+++ b/data/css/gnome-clocks.css
@@ -8,105 +8,118 @@ window > stack:backdrop {
     box-shadow: none;
 }
 
-.clocks-tiles-view {
-    background-color: transparent;
-}
+/* content view */
 
-.clocks-digital-renderer {
+.tile {
+    padding: 12px;
     background-color: transparent;
 }
 
-.clocks-digital-renderer.stripe {
+.tile .tile-label {
     font-size: 32pt;
 }
 
+.tile .name-label {
+    padding-top: 0.5em;
+}
+
+.tile .name-icon {
+    padding-top: 0.5em;
+    padding-right: 8px;
+}
+
 /* world */
 
-.clocks-digital-renderer.light.stripe {
+.world-tile .stripe,
+.world-tile .stripe:backdrop {
     background-color: rgba(255, 255, 255, 0.4);
     color: black;
 }
 
-.clocks-digital-renderer.light.stripe:hover {
+.tile.prelight .world-tile .stripe,
+.tile.prelight .world-tile .stripe:backdrop {
     background-color: rgba(255, 255, 255, 0.6);
 }
 
-.clocks-digital-renderer.dark.stripe {
+.night .world-tile .stripe,
+.night .world-tile .stripe:backdrop {
     background-color: rgba(0, 0, 0, 0.4);
     color: white;
 }
 
-.clocks-digital-renderer.dark.stripe:hover {
+.tile.prelight .night .world-tile .stripe,
+.tile.prelight .night .world-tile .stripe:backdrop {
     background-color: rgba(0, 0, 0, 0.2);
 }
 
 /* alarms */
 
-.clocks-digital-renderer.active {
+.alarm-tile,
+.alarm-tile:backdrop {
+    color: shade(@insensitive_fg_color,0.9);
     background-color: transparent;
     background-image: -gtk-gradient(radial,
                                     center center, 0,
                                     center bottom, 1.0,
-                                    from(shade(@theme_selected_bg_color,1.2)), to(@theme_selected_bg_color));
-    color: white;
+                                    from(shade(@insensitive_bg_color,0.9)), 
to(shade(@insensitive_bg_color,0.85)));
 }
 
-.clocks-digital-renderer.active:hover {
+.tile.prelight .alarm-tile,
+.tile.prelight .alarm-tile:backdrop {
+    color: @insensitive_fg_color;
     background-color: transparent;
     background-image: -gtk-gradient(radial,
                                     center center, 0,
                                     center bottom, 1.0,
-                                    from(shade(@theme_selected_bg_color, 1.4)), 
to(@theme_selected_bg_color));
-    text-shadow: 0 2px 2px rgba(0,0,0,0.5);
+                                    from(shade(@insensitive_bg_color,0.99)), 
to(shade(@insensitive_bg_color,0.9)));
 }
 
-.clocks-digital-renderer.snoozing {
+.active .alarm-tile,
+.active .alarm-tile:backdrop {
+    color: white;
     background-color: transparent;
     background-image: -gtk-gradient(radial,
                                     center center, 0,
                                     center bottom, 1.0,
-                                    from(@warning_color), to(shade(@warning_color,0.9)));
-    color: white;
+                                    from(shade(@theme_selected_bg_color,1.2)), to(@theme_selected_bg_color));
 }
 
-.clocks-digital-renderer.snoozing:hover {
+.tile.prelight .active .alarm-tile,
+.tile.prelight .active .alarm-tile:backdrop {
+    color: white;
+    text-shadow: 0 2px 2px rgba(0,0,0,0.5);
     background-color: transparent;
     background-image: -gtk-gradient(radial,
                                     center center, 0,
                                     center bottom, 1.0,
-                                    from(shade(@warning_color,1.1)), to(shade(@warning_color,0.99)));
-    text-shadow: 0 2px 2px rgba(0,0,0,0.5);
+                                    from(shade(@theme_selected_bg_color, 1.4)), 
to(@theme_selected_bg_color));
 }
 
-.clocks-digital-renderer.inactive {
+.snoozing .alarm-tile,
+.snoozing .alarm-tile:backdrop {
+    color: white;
     background-color: transparent;
     background-image: -gtk-gradient(radial,
                                     center center, 0,
                                     center bottom, 1.0,
-                                    from(shade(@insensitive_bg_color,0.9)), 
to(shade(@insensitive_bg_color,0.85)));
-    color: shade(@insensitive_fg_color,0.9);
+                                    from(@warning_color), to(shade(@warning_color,0.9)));
 }
 
-.clocks-digital-renderer.inactive:hover {
+.tile.prelight .snoozing .alarm-tile,
+.tile.prelight .snoozing .alarm-tile:backdrop {
+    color: white;
+    text-shadow: 0 2px 2px rgba(0,0,0,0.5);
     background-color: transparent;
     background-image: -gtk-gradient(radial,
                                     center center, 0,
                                     center bottom, 1.0,
-                                    from(shade(@insensitive_bg_color,0.99)), 
to(shade(@insensitive_bg_color,0.9)));
-    color: @insensitive_fg_color;
+                                    from(shade(@warning_color,1.1)), to(shade(@warning_color,0.99)));
 }
 
 .clocks-ampm-toggle-button {
     font-size: 18pt;
 }
 
-.clocks-digital-renderer.active.stripe,
-.clocks-digital-renderer.snoozing.stripe,
-.clocks-digital-renderer.inactive.stripe {
-    background-color: transparent;
-    background-image: none;
-}
-
 .clocks-standalone-label,
 .clocks-ringing-label {
     font-size: 64pt;
@@ -213,4 +226,3 @@ spinbutton.clocks-timer-label button {
     animation-timing-function: cubic-bezier(1.0,0,0,1.0);
     animation-duration: 1s; 
 }
-
diff --git a/data/css/gnome-clocks.highcontrast.css b/data/css/gnome-clocks.highcontrast.css
index c91b399..ebcb83a 100644
--- a/data/css/gnome-clocks.highcontrast.css
+++ b/data/css/gnome-clocks.highcontrast.css
@@ -8,66 +8,56 @@ window > stack:backdrop {
     box-shadow: none;
 }
 
-.clocks-digital-renderer {
-    border: 2px solid rgb(141, 141, 141);
-}
+/* world */
 
-.clocks-digital-renderer.stripe {
-    background-color: transparent;
-    border: none;
+.world-tile image,
+.world-tile image:backdrop {
+    border: 2px solid rgb(141, 141, 141);
 }
 
-/* world */
-
-.clocks-digital-renderer.light.stripe,
-.clocks-digital-renderer.light.stripe:hover {
+.world-tile .stripe,
+.world-tile .stripe:backdrop,
+.tile.prelight .world-tile .stripe,
+.tile.prelight .world-tile .stripe:backdrop {
     background-color: white;
     color: black;
 }
 
-.clocks-digital-renderer.dark.stripe,
-.clocks-digital-renderer.dark.stripe:hover {
+.night .world-tile .stripe,
+.night .world-tile .stripe:backdrop,
+.tile.prelight .night .world-tile .stripe,
+.tile.prelight .night .world-tile .stripe:backdrop {
     background-color: black;
     color: white;
 }
 
-.check.clocks-digital-renderer-check.light {
-    color: black;
-}
-
-.check.clocks-digital-renderer-check.dark {
-    color: white;
-}
-
 /* alarms */
 
-.clocks-digital-renderer.active,
-.clocks-digital-renderer.active:hover {
-    background-image: none;
-    background-color: @theme_selected_bg_color;
-    color: @theme_selected_fg_color;
-}
-
-.clocks-digital-renderer.snoozing,
-.clocks-digital-renderer.snoozing:hover {
-    background-image: none;
-    background-color: @warning_color;
-    color: @theme_fg_color;
-    text-shadow: none;
-}
-
-.clocks-digital-renderer.inactive,
-.clocks-digital-renderer.inactive:hover {
+.alarm-tile,
+.alarm-tile:backdrop,
+.tile.prelight .alarm-tile,
+.tile.prelight .alarm-tile:backdrop {
     background-image: none;
     background-color: @theme_bg_color;
     color: @theme_fg_color;
+    border: 2px solid rgb(141, 141, 141);
 }
 
-.check.clocks-digital-renderer-check.active {
+.active .alarm-tile,
+.active .alarm-tile:backdrop,
+.tile.prelight .active .alarm-tile,
+.tile.prelight .active .alarm-tile:backdrop {
     color: @theme_selected_fg_color;
+    background-image: none;
+    background-color: @theme_selected_bg_color;
 }
 
-.check.clocks-digital-renderer-check.inactive,
-.check.clocks-digital-renderer-check.snoozing {
+.snoozing .alarm-tile,
+.snoozing .alarm-tile:backdrop,
+.tile.prelight .snoozing .alarm-tile,
+.tile.prelight .snoozing .alarm-tile:backdrop {
+    background-image: none;
+    background-color: @warning_color;
     color: @theme_fg_color;
+    text-shadow: none;
 }
diff --git a/data/gnome-clocks.gresource.xml b/data/gnome-clocks.gresource.xml
index c2390e8..4c1aac3 100644
--- a/data/gnome-clocks.gresource.xml
+++ b/data/gnome-clocks.gresource.xml
@@ -8,7 +8,9 @@
     <file preprocess="xml-stripblanks">ui/window.ui</file>
     <file preprocess="xml-stripblanks">ui/worldlocationdialog.ui</file>
     <file preprocess="xml-stripblanks">ui/world.ui</file>
+    <file preprocess="xml-stripblanks">ui/worldtile.ui</file>
     <file preprocess="xml-stripblanks">ui/alarmringing.ui</file>
+    <file preprocess="xml-stripblanks">ui/alarmtile.ui</file>
     <file preprocess="xml-stripblanks">ui/alarmsetupdialog.ui</file>
     <file preprocess="xml-stripblanks">ui/alarm.ui</file>
     <file preprocess="xml-stripblanks">ui/stopwatch.ui</file>
diff --git a/data/ui/alarm.ui b/data/ui/alarm.ui
index 99c7bf4..88c629f 100644
--- a/data/ui/alarm.ui
+++ b/data/ui/alarm.ui
@@ -1,8 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.6 -->
+  <requires lib="gtk+" version="3.18"/>
   <template class="ClocksAlarmFace" parent="GtkStack">
     <property name="visible">True</property>
+    <property name="can_focus">False</property>
     <signal name="notify::visible-child" handler="visible_child_changed" swapped="no"/>
     <child>
       <object class="GtkGrid" id="empty_view">
diff --git a/data/ui/alarmtile.ui b/data/ui/alarmtile.ui
new file mode 100644
index 0000000..3cf8a80
--- /dev/null
+++ b/data/ui/alarmtile.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ClocksAlarmTile" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="halign">start</property>
+    <property name="valign">start</property>
+    <child>
+      <object class="GtkLabel" id="time_label">
+        <property name="width_request">256</property>
+        <property name="height_request">256</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <property name="valign">end</property>
+        <property name="use-markup">True</property>
+        <property name="justify">center</property>
+        <property name="wrap">True</property>
+        <property name="max-width-chars">7</property>
+        <style>
+          <class name="tile-label"/>
+          <class name="alarm-tile"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="name_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <style>
+          <class name="name-label"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/world.ui b/data/ui/world.ui
index 90b9965..c004c58 100644
--- a/data/ui/world.ui
+++ b/data/ui/world.ui
@@ -1,8 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.0 -->
+  <requires lib="gtk+" version="3.18"/>
   <template class="ClocksWorldFace" parent="GtkStack">
     <property name="visible">True</property>
+    <property name="can_focus">False</property>
     <signal name="notify::visible-child" handler="visible_child_changed" swapped="no"/>
     <child>
       <object class="GtkGrid" id="empty_view">
diff --git a/data/ui/worldtile.ui b/data/ui/worldtile.ui
new file mode 100644
index 0000000..052183d
--- /dev/null
+++ b/data/ui/worldtile.ui
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="ClocksWorldTile" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="halign">start</property>
+    <property name="valign">start</property>
+    <child>
+      <object class="GtkOverlay" id="tile_overlay">
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="width_request">256</property>
+            <property name="height_request">256</property>
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+          </object>
+        </child>
+        <child type="overlay">
+          <object class="GtkLabel" id="time_label">
+            <property name="width_request">256</property>
+            <property name="height_request">128</property>
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="use-markup">True</property>
+            <property name="justify">center</property>
+            <style>
+              <class name="stripe"/>
+              <class name="tile-label"/>
+            </style>
+          </object>
+        </child>
+        <style>
+          <class name="world-tile"/>
+        </style>
+      </object>
+    </child>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">center</property>
+        <child>
+          <object class="GtkImage" id="name_icon">
+            <property name="icon_name">find-location-symbolic</property>
+            <property name="pixel_size">16</property>
+            <property name="visible">False</property>
+            <property name="no_show_all">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <style>
+              <class name="dim-label"/>
+              <class name="name-icon"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="name_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <style>
+              <class name="name-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/src/alarm.vala b/src/alarm.vala
index a7fd1a4..82444f3 100644
--- a/src/alarm.vala
+++ b/src/alarm.vala
@@ -24,7 +24,7 @@ private struct AlarmTime {
     public int minute;
 }
 
-private class Item : Object, ContentItem, ContentThumb {
+private class Item : Object, ContentItem {
     const int SNOOZE_MINUTES = 9;
     const int RING_MINUTES = 3;
 
@@ -57,7 +57,7 @@ private class Item : Object, ContentItem, ContentThumb {
 
     public AlarmTime time { get; set; }
 
-    public Utils.Weekdays days { get; construct set; }
+    public Utils.Weekdays days { get; set; }
 
     public State state { get; private set; }
 
@@ -73,6 +73,12 @@ private class Item : Object, ContentItem, ContentThumb {
          }
     }
 
+    public string days_label {
+         owned get {
+            return days != null ? days.get_label () : null;
+         }
+    }
+
     [CCode (notify = false)]
     public bool active {
         get {
@@ -100,17 +106,9 @@ private class Item : Object, ContentItem, ContentThumb {
     private Utils.Bell bell;
     private GLib.Notification notification;
 
-    public Item () {
-        id = GLib.DBus.generate_guid ();
-        days = new Utils.Weekdays ();
-    }
-
-    public Item.with_data (string? id, string name, bool active, AlarmTime time, Utils.Weekdays days) {
-        var _id = id != null ? id : GLib.DBus.generate_guid();
-        Object (id: _id, name: name, active: active, time: time, days: days);
-
-        setup_bell ();
-        reset ();
+    public Item (string? id = null) {
+        var guid = id != null ? id : GLib.DBus.generate_guid ();
+        Object (id: guid);
     }
 
     private void setup_bell () {
@@ -138,7 +136,7 @@ private class Item : Object, ContentItem, ContentThumb {
                                     time.minute,
                                     0);
 
-        if (days.empty) {
+        if (days == null || days.empty) {
             // Alarm without days.
             if (dt.compare (now) <= 0) {
                 // Time already passed, ring tomorrow.
@@ -244,7 +242,7 @@ private class Item : Object, ContentItem, ContentThumb {
         bool active = true;
         int hour = -1;
         int minute = -1;
-        Utils.Weekdays days = new Utils.Weekdays ();
+        Utils.Weekdays days = null;
         foreach (var v in alarm_variant) {
             var key = v.get_child_value (0).get_string ();
             if (key == "name") {
@@ -262,28 +260,66 @@ private class Item : Object, ContentItem, ContentThumb {
             }
         }
         if (name != null && hour >= 0 && minute >= 0) {
-            AlarmTime time = { hour, minute };
-            return new Item.with_data (id, name, active, time, days);
+            Item alarm = new Item (id);
+            alarm.name = name;
+            alarm.active = active;
+            alarm.time = { hour, minute };
+            alarm.days = days;
+            alarm.reset ();
+            return alarm;
         } else {
             warning ("Invalid alarm %s", name != null ? name : "name missing");
         }
         return null;
     }
+}
+
+[GtkTemplate (ui = "/org/gnome/clocks/ui/alarmtile.ui")]
+private class Tile : Gtk.Grid {
+    public Item alarm { get; construct set; }
+
+    [GtkChild]
+    private Gtk.Label time_label;
+    [GtkChild]
+    private Gtk.Widget name_label;
+
+    public Tile (Item alarm) {
+        Object (alarm: alarm);
+
+        alarm.bind_property ("name", name_label, "label", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE);
+
+        alarm.notify["active"].connect (update);
+        alarm.notify["state"].connect (update);
+        alarm.notify["time"].connect (update);
+        alarm.notify["days"].connect (update);
+
+        update ();
+    }
 
-    public void get_thumb_properties (out string text,
-                                      out string subtext,
-                                      out Gdk.Pixbuf? pixbuf,
-                                      out string css_class) {
-        if (state == State.SNOOZING) {
-            text = snooze_time_label;
-            subtext = "(%s)".printf(time_label);
-            css_class = "snoozing";
+    private void update () {
+        string text, sub_text;
+
+        if (alarm.active) {
+            get_style_context ().add_class ("active");
+        } else {
+            get_style_context ().remove_class ("active");
+        }
+
+        if (alarm.state == Item.State.SNOOZING) {
+            get_style_context ().add_class ("snoozing");
+            text = alarm.snooze_time_label;
+            sub_text = "(%s)".printf (alarm.time_label);
         } else {
-            text = time_label;
-            subtext = days.get_label ();;
-            css_class = active ? "active" : "inactive";
+            get_style_context ().remove_class ("snoozing");
+            text = alarm.time_label;
+            sub_text = alarm.days_label;
+        }
+
+        if (sub_text != null && sub_text != "") {
+            time_label.label = "%s\n<span size='xx-small'>%s</span>".printf (text, sub_text);
+        } else {
+            time_label.label = text;
         }
-        pixbuf = null;
     }
 }
 
@@ -437,13 +473,22 @@ private class SetupDialog : Gtk.Dialog {
 
         AlarmTime time = { hour, minute };
 
+        Utils.Weekdays days = new Utils.Weekdays ();
+        for (int i = 0; i < 7; i++) {
+            days.set ((Utils.Weekdays.Day) i, day_buttons[i].active);
+        }
+
+        alarm.freeze_notify ();
+
         alarm.name = name;
         alarm.active = active;
         alarm.time = time;
+        alarm.days = days;
 
-        for (int i = 0; i < 7; i++) {
-            alarm.days.set ((Utils.Weekdays.Day) i, day_buttons[i].active);
-        }
+        // Force update of alarm_time before notifying the changes
+        alarm.reset ();
+
+        alarm.thaw_notify ();
     }
 
     private void avoid_duplicate_alarm () {
@@ -522,9 +567,9 @@ private class RingingPanel : Gtk.Grid {
     public void update () {
         if (alarm != null) {
             if (alarm.state == Item.State.SNOOZING) {
-                time_label.set_text (alarm.snooze_time_label);
+                time_label.label = alarm.snooze_time_label;
             } else {
-                time_label.set_text (alarm.time_label);
+                time_label.label = alarm.time_label;
             }
         }
     }
@@ -582,7 +627,10 @@ public class Face : Gtk.Stack, Clocks.Clock {
         new_button.action_name = "win.new";
         header_bar.pack_start (new_button);
 
-        content_view.bind_model (alarms);
+        content_view.bind_model (alarms, (item) => {
+            return new Tile ((Item)item);
+        });
+
         content_view.set_header_bar (header_bar);
 
         load ();
@@ -655,7 +703,6 @@ public class Face : Gtk.Stack, Clocks.Clock {
         dialog.response.connect ((dialog, response) => {
             if (response == 1) {
                 ((SetupDialog) dialog).apply_to_alarm (alarm);
-                alarm.reset ();
                 save ();
             } else {
                 alarm.active = saved_active;
@@ -682,7 +729,6 @@ public class Face : Gtk.Stack, Clocks.Clock {
                 var alarm = new Item ();
                 ((SetupDialog) dialog).apply_to_alarm (alarm);
                 alarms.add (alarm);
-                alarm.reset();
                 save ();
             }
             dialog.destroy ();
diff --git a/src/widgets.vala b/src/widgets.vala
index 31d0d72..26e1ad5 100644
--- a/src/widgets.vala
+++ b/src/widgets.vala
@@ -56,220 +56,13 @@ public class HeaderBar : Gtk.HeaderBar {
     }
 }
 
-private class TitleRenderer : Gtk.CellRendererText {
-    private int ICON_XOFF;
-    private int ICON_YOFF;
-    private int ICON_SIZE;
-
-    public string title {
-        get {
-            return _title;
-        }
-        set {
-            markup = _title = value;
-        }
-    }
-
-    public string title_icon { get; set; default = null; }
-
-    private string _title;
-
-    public TitleRenderer () {
-        ICON_YOFF = 5;
-        ICON_XOFF = 25;
-        ICON_SIZE = 18;
-    }
-
-    public override void render (Cairo.Context cr,
-                                 Gtk.Widget widget,
-                                 Gdk.Rectangle background_area,
-                                 Gdk.Rectangle cell_area,
-                                 Gtk.CellRendererState flags) {
-        base.render (cr, widget, cell_area, cell_area, flags);
-
-        if (title_icon != null) {
-            var context = widget.get_style_context ();
-            context.save ();
-
-            cr.save ();
-            Gdk.cairo_rectangle (cr, cell_area);
-            cr.clip ();
-
-            cr.translate (cell_area.x, cell_area.y);
-
-            // create the layouts so that we can measure them
-            var layout = widget.create_pango_layout ("");
-            layout.set_markup (title, -1);
-            layout.set_alignment (Pango.Alignment.CENTER);
-            int text_w, text_h;
-            layout.get_pixel_size (out text_w, out text_h);
-
-            int x = (cell_area.width - text_w) / 2 - ICON_XOFF, y = ICON_YOFF;
-
-            if (widget.get_direction () == Gtk.TextDirection.RTL) {
-                x = (cell_area.width + text_w) / 2 + ICON_XOFF - ICON_SIZE;
-            }
-
-            Gtk.IconTheme icon_theme = Gtk.IconTheme.get_for_screen (Gdk.Screen.get_default ());
-            try {
-                Gtk.IconInfo? icon_info = icon_theme.lookup_icon_for_scale (title_icon, ICON_SIZE, 
widget.scale_factor, 0);
-                assert (icon_info != null);
-
-                Cairo.Surface surface = icon_info.load_surface (widget.get_window ());
-                Gtk.render_icon_surface (context, cr, surface, x, y);
-            } catch (Error e) {
-                warning (e.message);
-            }
-
-            context.restore ();
-            cr.restore ();
-        }
-    }
-}
-
-private class DigitalClockRenderer : Gtk.CellRendererPixbuf {
-    public const int TILE_SIZE = 256;
-    public const int CHECK_ICON_SIZE = 40;
-    public const int TILE_MARGIN = CHECK_ICON_SIZE / 4;
-    public const int TILE_MARGIN_BOTTOM = CHECK_ICON_SIZE / 8; // less margin, the text label is below
-
-    public string text { get; set; }
-    public string subtext { get; set; }
-    public string css_class { get; set; }
-    public bool checked { get; set; default = false; }
-    public bool toggle_visible { get; set; default = false; }
-    public bool selectable { get; set; default = true; }
-
-    public DigitalClockRenderer () {
-    }
-
-    public override void render (Cairo.Context cr,
-                                 Gtk.Widget widget,
-                                 Gdk.Rectangle background_area,
-                                 Gdk.Rectangle cell_area,
-                                 Gtk.CellRendererState flags) {
-        var context = widget.get_style_context ();
-
-        context.save ();
-        context.add_class ("clocks-digital-renderer");
-        context.add_class (css_class);
-
-        cr.save ();
-        Gdk.cairo_rectangle (cr, cell_area);
-        cr.clip ();
-
-        cr.translate (cell_area.x, cell_area.y);
-
-        // the width of the cell which may be larger in case of long city names
-        int margin = int.max (TILE_MARGIN, (int) ((cell_area.width - TILE_SIZE) / 2));
-
-        // draw the tile
-        if (pixbuf != null) {
-            Gdk.Rectangle area = {margin, margin, TILE_SIZE, TILE_SIZE};
-            base.render (cr, widget, area, area, flags);
-        } else {
-            context.render_background (cr, margin, margin, TILE_SIZE, TILE_SIZE);
-        }
-        context.render_frame (cr, margin, margin, TILE_SIZE, TILE_SIZE);
-
-        var border = context.get_border(context.get_state ());
-        int w = cell_area.width - 2 * margin - border.left - border.right;
-
-        // create the layouts so that we can measure them
-        var layout = widget.create_pango_layout ("");
-        layout.set_markup ("<span font_desc=\"32.0\">%s</span>".printf (text), -1);
-        layout.set_width (w * Pango.SCALE);
-        layout.set_alignment (Pango.Alignment.CENTER);
-        int text_w, text_h;
-        layout.get_pixel_size (out text_w, out text_h);
-
-        Pango.Layout? layout_subtext = null;
-        int subtext_w = 0;
-        int subtext_h = 0;
-        int subtext_pad = 0;
-        if (subtext != null) {
-            layout_subtext = widget.create_pango_layout ("");
-            layout_subtext.set_markup ("<span font_desc=\"14.0\">%s</span>".printf (subtext), -1);
-            layout_subtext.set_width (w * Pango.SCALE);
-            layout_subtext.set_alignment (Pango.Alignment.CENTER);
-            layout_subtext.get_pixel_size (out subtext_w, out subtext_h);
-            subtext_pad = 4;
-            // We just assume the first line is the longest
-            var line = layout_subtext.get_line (0);
-            Pango.Rectangle ink_rect, log_rect;
-            line.get_pixel_extents (out ink_rect, out log_rect);
-            subtext_w = log_rect.width;
-        }
-
-        // draw the stripe background
-        int stripe_h = 128;
-        int x = margin + border.left;
-        int y = (cell_area.height - stripe_h) / 2;
-
-        context.add_class ("stripe");
-        context.render_frame (cr, x, y, w, stripe_h);
-        context.render_background (cr, x, y, w, stripe_h);
-
-        // draw text centered on the stripe
-        y += (stripe_h - text_h - subtext_h - subtext_pad) / 2;
-        context.render_layout (cr, x, y, layout);
-        if (subtext != null) {
-            y += text_h + subtext_pad;
-            context.render_layout (cr, x, y, layout_subtext);
-        }
-
-        context.restore ();
-
-        // draw the overlayed checkbox
-        if (selectable && toggle_visible) {
-            int xpad, ypad, x_offset;
-            get_padding (out xpad, out ypad);
-
-            if (widget.get_direction () == Gtk.TextDirection.RTL) {
-                x_offset = xpad;
-            } else {
-                x_offset = cell_area.width - CHECK_ICON_SIZE - xpad;
-            }
-
-            int check_x = x_offset;
-            int check_y = cell_area.height - CHECK_ICON_SIZE - ypad;
-
-            context.save ();
-            context.add_class (Gtk.STYLE_CLASS_CHECK);
-            context.add_class ("clocks-digital-renderer-check");
-            context.add_class (css_class);
-
-            if (checked) {
-                context.set_state (Gtk.StateFlags.CHECKED);
-            }
-
-            context.render_background (cr, check_x, check_y, CHECK_ICON_SIZE, CHECK_ICON_SIZE);
-            context.render_frame (cr, check_x, check_y, CHECK_ICON_SIZE, CHECK_ICON_SIZE);
-            context.render_check (cr, check_x, check_y, CHECK_ICON_SIZE, CHECK_ICON_SIZE);
-
-            context.restore ();
-        }
-
-        cr.restore ();
-    }
-}
-
 public interface ContentItem : GLib.Object {
     public abstract string name { get; set; }
-    public abstract string title_icon { get; set; default = null; }
     public abstract bool selectable { get; set; default = true; }
     public abstract bool selected { get; set; default = false; }
-
     public abstract void serialize (GLib.VariantBuilder builder);
 }
 
-public interface ContentThumb : GLib.Object {
-    public abstract void get_thumb_properties (out string text,
-                                               out string subtext,
-                                               out Gdk.Pixbuf? pixbuf,
-                                               out string css_class);
-}
-
 public class ContentStore : GLib.Object, GLib.ListModel {
     private ListStore store;
     private CompareDataFunc sort_func;
@@ -367,7 +160,7 @@ public class ContentStore : GLib.Object, GLib.ListModel {
         }
 
         if (n_deleted > 0) {
-            store.splice(0, n, not_selected);
+            store.splice (0, n, not_selected);
             if (sort_func != null) {
                 store.sort (sort_func);
             }
@@ -426,111 +219,6 @@ public class ContentStore : GLib.Object, GLib.ListModel {
     }
 }
 
-private class IconView : Gtk.IconView {
-    public enum Mode {
-        NORMAL,
-        SELECTION
-    }
-
-    public Mode mode { get; set; }
-
-    public IconView () {
-        Object (selection_mode: Gtk.SelectionMode.NONE, mode: Mode.NORMAL);
-
-        model = new Gtk.ListStore (1, typeof (ContentItem));
-
-        get_style_context ().add_class ("clocks-tiles-view");
-        get_style_context ().add_class ("content-view");
-        set_item_padding (0);
-        set_margin (12);
-
-        var tile_width = DigitalClockRenderer.TILE_SIZE + 2 * DigitalClockRenderer.TILE_MARGIN;
-        var tile_height = DigitalClockRenderer.TILE_SIZE +
-                          DigitalClockRenderer.TILE_MARGIN +
-                          DigitalClockRenderer.TILE_MARGIN_BOTTOM;
-
-        var thumb_renderer = new DigitalClockRenderer ();
-        thumb_renderer.set_alignment (0.5f, 0.5f);
-        thumb_renderer.set_fixed_size (tile_width, tile_height);
-        pack_start (thumb_renderer, false);
-        set_cell_data_func (thumb_renderer, (column, cell, model, iter) => {
-            ContentItem? item;
-            model.get (iter, 0, out item);
-            if (item != null) {
-                var renderer = (DigitalClockRenderer) cell;
-                string text;
-                string subtext;
-                Gdk.Pixbuf? pixbuf;
-                string css_class;
-                ((ContentThumb)item).get_thumb_properties (out text, out subtext, out pixbuf, out css_class);
-                renderer.selectable = item.selectable;
-                renderer.toggle_visible = (mode == Mode.SELECTION);
-                renderer.checked = item.selected;
-                renderer.text = text;
-                renderer.subtext = subtext;
-                renderer.pixbuf = pixbuf;
-                renderer.css_class = css_class;
-            }
-        });
-
-        var title_renderer = new TitleRenderer ();
-        title_renderer.set_alignment (0.5f, 0.5f);
-        title_renderer.set_fixed_size (tile_width, -1);
-        title_renderer.alignment = Pango.Alignment.CENTER;
-        title_renderer.wrap_width = 220;
-        title_renderer.wrap_mode = Pango.WrapMode.WORD_CHAR;
-        pack_start (title_renderer, true);
-        set_cell_data_func (title_renderer, (column, cell, model, iter) => {
-            ContentItem? item;
-            model.get (iter, 0, out item);
-            if (item != null) {
-                var renderer = (TitleRenderer) cell;
-                renderer.title = GLib.Markup.escape_text (item.name);
-                renderer.title_icon = item.title_icon;
-            }
-        });
-    }
-
-    public override bool button_press_event (Gdk.EventButton event) {
-        var path = get_path_at_pos ((int) event.x, (int) event.y);
-        if (path != null) {
-            var store = (Gtk.ListStore) model;
-            Gtk.TreeIter i;
-            if (store.get_iter (out i, path)) {
-                ContentItem item;
-                store.get (i, 0, out item);
-                if (item != null) {
-                    // On right click, swicth to selection mode automatically
-                    if (item.selectable && event.button == Gdk.BUTTON_SECONDARY) {
-                        mode = Mode.SELECTION;
-                    }
-
-                    if (item.selectable && mode == Mode.SELECTION) {
-                        item.selected = !item.selected;
-                        selection_changed ();
-                        queue_draw ();
-                    } else if (event.button == Gdk.BUTTON_PRIMARY) {
-                        item_activated (path);
-                    }
-                }
-            }
-        }
-
-        return false;
-    }
-
-    public void add_item (Object item) {
-        var store = (Gtk.ListStore) model;
-        Gtk.TreeIter i;
-        store.append (out i);
-        store.set (i, 0, item);
-    }
-
-    public new void clear () {
-        ((Gtk.ListStore) model).clear ();
-    }
-}
-
 private class SelectionMenuButton : Gtk.MenuButton {
     public uint n_items {
         get {
@@ -570,6 +258,40 @@ private class SelectionMenuButton : Gtk.MenuButton {
 }
 
 public class ContentView : Gtk.Bin {
+    public enum Mode {
+        NORMAL,
+        SELECTION
+    }
+
+    public Mode mode {
+        get {
+            return _mode;
+        }
+
+        private set {
+            if (_mode != value) {
+                _mode = value;
+
+                switch (_mode) {
+                case Mode.SELECTION:
+                    header_bar.mode = HeaderBar.Mode.SELECTION;
+                    action_bar.show ();
+                    break;
+                case Mode.NORMAL:
+                    header_bar.mode = HeaderBar.Mode.NORMAL;
+                    action_bar.hide ();
+                    // clear current selection
+                    model.unselect_all ();
+                    break;
+                default:
+                    assert_not_reached ();
+                }
+
+                update_header_bar ();
+            }
+        }
+    }
+
     private bool can_select {
         get {
             return _can_select;
@@ -587,21 +309,34 @@ public class ContentView : Gtk.Bin {
         }
     }
 
+    private Mode _mode;
     private bool _can_select;
     private ContentStore model;
-    private IconView icon_view;
+    private Gtk.FlowBox flow_box;
     private Gtk.Button select_button;
     private Gtk.Button cancel_button;
     private SelectionMenuButton selection_menubutton;
     private Gtk.Grid grid;
+    private Gtk.ActionBar action_bar;
     private Gtk.Button delete_button;
     private HeaderBar? header_bar;
 
     construct {
-        icon_view = new IconView ();
+        get_style_context ().add_class ("content-view");
+
+        flow_box = new Gtk.FlowBox ();
+        flow_box.selection_mode = Gtk.SelectionMode.NONE;
+        flow_box.min_children_per_line = 3;
+
+        flow_box.child_activated.connect ((child) => {
+            var item = model.get_item (child.get_index ()) as ContentItem;
+            if (item != null) {
+                item_activated (item);
+            }
+        });
 
         var scrolled_window = new Gtk.ScrolledWindow (null, null);
-        scrolled_window.add (icon_view);
+        scrolled_window.add (flow_box);
         scrolled_window.hexpand = true;
         scrolled_window.vexpand = true;
         scrolled_window.halign = Gtk.Align.FILL;
@@ -610,7 +345,7 @@ public class ContentView : Gtk.Bin {
         grid = new Gtk.Grid ();
         grid.attach (scrolled_window, 0, 0, 1, 1);
 
-        var action_bar = new Gtk.ActionBar ();
+        action_bar = new Gtk.ActionBar ();
         action_bar.no_show_all = true;
         grid.attach (action_bar, 0, 1, 1, 1);
 
@@ -622,38 +357,20 @@ public class ContentView : Gtk.Bin {
         delete_button.hexpand = true;
         delete_button.clicked.connect (() => {
             model.delete_selected ();
-            icon_view.mode = IconView.Mode.NORMAL;
+            mode = Mode.NORMAL;
         });
 
         action_bar.pack_end (delete_button);
 
-        icon_view.notify["mode"].connect (() => {
-            if (icon_view.mode == IconView.Mode.SELECTION) {
-                action_bar.show ();
-            } else if (icon_view.mode == IconView.Mode.NORMAL) {
-                action_bar.hide ();
-            }
-        });
-
-        icon_view.item_activated.connect ((path) => {
-            var store = (Gtk.ListStore) icon_view.model;
-            Gtk.TreeIter iter;
-            if (store.get_iter (out iter, path)) {
-                ContentItem? item;
-                store.get (iter, 0, out item);
-                if (item != null) {
-                    item_activated (item);
-                }
-            }
-        });
-
         add (grid);
         grid.show_all ();
     }
 
     public signal void item_activated (ContentItem item);
 
-    public void bind_model (ContentStore store) {
+    public delegate Gtk.Widget ContentViewCreateWidgetFunc (ContentItem item);
+
+    public void bind_model (ContentStore store, owned ContentViewCreateWidgetFunc create_func) {
         model = store;
         model.items_changed.connect ((position, removed, added) => {
             var first_selectable = model.find ((i) => {
@@ -661,14 +378,6 @@ public class ContentView : Gtk.Bin {
             });
 
             can_select = first_selectable != null;
-
-            // Just clear and repopulate the GtkTreeModel...
-            // it sucks, but there is no easy way to sync to a GListMode
-            // and we always have few items.
-            icon_view.clear ();
-            model.foreach ((item) => {
-                icon_view.add_item (item);
-            });
         });
 
         model.selection_changed.connect (() => {
@@ -681,10 +390,85 @@ public class ContentView : Gtk.Bin {
                 delete_button.sensitive = false;
             }
         });
+
+        flow_box.bind_model (model, (object) => {
+            var item = (ContentItem) object;
+            var inner = create_func (item);
+
+            // wrap the widget in an event box to handle righ-click
+            var event_box = new Gtk.EventBox ();
+            event_box.add (inner);
+            event_box.button_press_event.connect ((event) => {
+                // On right click, swicth to selection mode automatically
+                if (item.selectable && event.button == Gdk.BUTTON_SECONDARY) {
+                    mode = Mode.SELECTION;
+                }
+
+                if (item.selectable && mode == Mode.SELECTION) {
+                    item.selected = !item.selected;
+                    return true;
+                } else if (event.button == Gdk.BUTTON_PRIMARY) {
+                    item_activated (item);
+                    return true;
+                }
+
+                return false;
+            });
+
+            // wrap the widget in overlay for the selection check box
+            var overlay = new Gtk.Overlay ();
+            overlay.halign = Gtk.Align.START;
+            overlay.valign = Gtk.Align.START;
+            overlay.add (event_box);
+
+            var check = new Gtk.CheckButton ();
+            check.no_show_all = true;
+            check.halign = Gtk.Align.END;
+            check.valign = Gtk.Align.END;
+            check.margin_bottom = 8;
+            check.margin_end = 8;
+
+            item.bind_property ("selected", check, "active", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE 
| BindingFlags.BIDIRECTIONAL);
+            item.bind_property ("selectable", check, "visible", BindingFlags.DEFAULT | 
BindingFlags.SYNC_CREATE,
+                                 (binding, selectable, ref visible) => {
+                visible = this.mode == Mode.SELECTION && (item).selectable;
+                return true;
+            });
+
+            bind_property ("mode", check, "visible", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE,
+                           (binding, mode, ref visible) => {
+                visible = mode == Mode.SELECTION && (item).selectable;
+                return true;
+            });
+
+            overlay.add_overlay (check);
+
+            // manually wrap in flowboxchild ourselves since we want to set alignment
+            var flow_box_child = new Gtk.FlowBoxChild ();
+            flow_box_child.halign = Gtk.Align.START;
+            flow_box_child.valign = Gtk.Align.START;
+            flow_box_child.add (overlay);
+            flow_box_child.get_style_context ().add_class ("tile");
+
+            // flowbox does not handle :hover, emulate it ourselves with css classes :(
+            event_box.enter_notify_event.connect ((event) => {
+                flow_box_child.get_style_context ().add_class ("prelight");
+                return false;
+            });
+
+            event_box.leave_notify_event.connect ((event) => {
+                flow_box_child.get_style_context ().remove_class ("prelight");
+                return false;
+            });
+
+            flow_box_child.show_all ();
+
+            return flow_box_child;
+        });
     }
 
     public void select_all () {
-        icon_view.mode = IconView.Mode.SELECTION;
+        mode = Mode.SELECTION;
         model.select_all ();
     }
 
@@ -693,8 +477,8 @@ public class ContentView : Gtk.Bin {
     }
 
     public bool escape_pressed () {
-        if (icon_view.mode == IconView.Mode.SELECTION) {
-            icon_view.mode = IconView.Mode.NORMAL;
+        if (mode == Mode.SELECTION) {
+            mode = Mode.NORMAL;
             return true;
         }
         return false;
@@ -709,7 +493,7 @@ public class ContentView : Gtk.Bin {
         select_button.valign = Gtk.Align.CENTER;
         select_button.no_show_all = true;
         select_button.clicked.connect (() => {
-            icon_view.mode = IconView.Mode.SELECTION;
+            mode = Mode.SELECTION;
         });
         header_bar.pack_end (select_button);
 
@@ -717,21 +501,11 @@ public class ContentView : Gtk.Bin {
         cancel_button.no_show_all = true;
         cancel_button.valign = Gtk.Align.CENTER;
         cancel_button.clicked.connect (() => {
-            icon_view.mode = IconView.Mode.NORMAL;
+            mode = Mode.NORMAL;
         });
         header_bar.pack_end (cancel_button);
 
         selection_menubutton = new SelectionMenuButton ();
-
-        icon_view.notify["mode"].connect (() => {
-            if (icon_view.mode == IconView.Mode.SELECTION) {
-                header_bar.mode = HeaderBar.Mode.SELECTION;
-            } else if (icon_view.mode == IconView.Mode.NORMAL) {
-                // clear current selection
-                icon_view.unselect_all ();
-                header_bar.mode = HeaderBar.Mode.NORMAL;
-            }
-        });
     }
 
     public void update_header_bar () {
diff --git a/src/world.vala b/src/world.vala
index a484fd0..bccc9fa 100644
--- a/src/world.vala
+++ b/src/world.vala
@@ -19,16 +19,11 @@
 namespace Clocks {
 namespace World {
 
-public class Item : Object, ContentItem, ContentThumb {
-    private static Gdk.Pixbuf? day_pixbuf = Utils.load_image ("day.png");
-    private static Gdk.Pixbuf? night_pixbuf = Utils.load_image ("night.png");
-
+public class Item : Object, ContentItem {
     public GWeather.Location location { get; set; }
 
     public bool automatic { get; set; default = false; }
 
-    public string title_icon { get; set; default = null; }
-
     public bool selectable { get; set; default = true; }
 
     public bool selected { get; set; default = false; }
@@ -150,7 +145,8 @@ public class Item : Object, ContentItem, ContentThumb {
         tick ();
     }
 
-    public void tick () {
+    [Signal (run = "first")]
+    public virtual signal void tick () {
         var wallclock = Utils.WallClock.get_default ();
         local_time = wallclock.date_time;
         date_time = local_time.to_timezone (time_zone);
@@ -183,19 +179,47 @@ public class Item : Object, ContentItem, ContentThumb {
         }
         return location != null ? new Item (location) : null;
     }
+}
+
+[GtkTemplate (ui = "/org/gnome/clocks/ui/worldtile.ui")]
+private class Tile : Gtk.Grid {
+    private static Gdk.Pixbuf? day_pixbuf = Utils.load_image ("day.png");
+    private static Gdk.Pixbuf? night_pixbuf = Utils.load_image ("night.png");
+
+    public Item location { get; construct set; }
+
+    [GtkChild]
+    private Gtk.Image image;
+    [GtkChild]
+    private Gtk.Label time_label;
+    [GtkChild]
+    private Gtk.Widget name_icon;
+    [GtkChild]
+    private Gtk.Widget name_label;
+
+    public Tile (Item location) {
+        Object (location: location);
+
+        location.bind_property ("automatic", name_icon, "visible", BindingFlags.DEFAULT | 
BindingFlags.SYNC_CREATE);
+        location.bind_property ("name", name_label, "label", BindingFlags.DEFAULT | 
BindingFlags.SYNC_CREATE);
+        location.tick.connect (update);
+
+        update ();
+    }
+
+    private void update () {
+        if (location.is_daytime) {
+            get_style_context ().remove_class ("night");
+            image.pixbuf = day_pixbuf;
+        } else {
+            get_style_context ().add_class ("night");
+            image.pixbuf = night_pixbuf;
+        }
 
-    public void get_thumb_properties (out string text,
-                                      out string subtext,
-                                      out Gdk.Pixbuf? pixbuf,
-                                      out string css_class) {
-        text = time_label;
-        subtext = day_label;
-        if (is_daytime) {
-            pixbuf = day_pixbuf;
-            css_class = "light";
+        if (location.day_label != null && location.day_label != "") {
+            time_label.label = "%s\n<span size='xx-small'>%s</span>".printf (location.time_label, 
location.day_label);
         } else {
-            pixbuf = night_pixbuf;
-            css_class = "dark";
+            time_label.label = location.time_label;
         }
     }
 }
@@ -308,7 +332,10 @@ public class Face : Gtk.Stack, Clocks.Clock {
         });
         header_bar.pack_start (back_button);
 
-        content_view.bind_model (locations);
+        content_view.bind_model (locations, (item) => {
+            return new Tile ((Item)item);
+        });
+
         content_view.set_header_bar (header_bar);
 
         load ();
@@ -389,7 +416,6 @@ public class Face : Gtk.Stack, Clocks.Clock {
             item = new Item (found_location);
             item.automatic = true;
             item.selectable = false;
-            item.title_icon = "find-location-symbolic";
             locations.add (item);
         });
 


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