[gnome-clocks] Redesign the Add World Clock dialog



commit a912edfb7be9cc91239777aa9fcaaad9823bf023
Author: matyas5 <hronekmatyas gmail com>
Date:   Tue Jul 7 21:44:11 2020 +0200

    Redesign the Add World Clock dialog
    
    Fixes #71

 data/gnome-clocks.gresource.xml      |   1 +
 data/ui/world-location-dialog-row.ui | 108 ++++++++++++++++++++++++
 data/ui/world-location-dialog.ui     | 154 ++++++++++++++++++++++++++---------
 po/POTFILES.in                       |   1 +
 src/meson.build                      |   1 +
 src/utils.vala                       |  31 +++++++
 src/world-face.vala                  |   8 +-
 src/world-location-dialog-row.vala   |  77 ++++++++++++++++++
 src/world-location-dialog.vala       | 139 ++++++++++++++++++++++++++-----
 src/world-row.vala                   |  28 +------
 10 files changed, 461 insertions(+), 87 deletions(-)
---
diff --git a/data/gnome-clocks.gresource.xml b/data/gnome-clocks.gresource.xml
index 9b73df8..1538975 100644
--- a/data/gnome-clocks.gresource.xml
+++ b/data/gnome-clocks.gresource.xml
@@ -18,6 +18,7 @@
     <file preprocess="xml-stripblanks">ui/window.ui</file>
     <file preprocess="xml-stripblanks">ui/world-face.ui</file>
     <file preprocess="xml-stripblanks">ui/world-location-dialog.ui</file>
+    <file preprocess="xml-stripblanks">ui/world-location-dialog-row.ui</file>
     <file preprocess="xml-stripblanks">ui/world-row.ui</file>
     <file preprocess="xml-stripblanks">ui/world-standalone.ui</file>
   </gresource>
diff --git a/data/ui/world-location-dialog-row.ui b/data/ui/world-location-dialog-row.ui
new file mode 100644
index 0000000..757a30c
--- /dev/null
+++ b/data/ui/world-location-dialog-row.ui
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.36.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="ClocksWorldAddClockRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="selectable">False</property>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="valign">start</property>
+        <property name="margin_start">12</property>
+        <property name="margin_end">12</property>
+        <property name="margin_top">12</property>
+        <property name="margin_bottom">12</property>
+        <property name="hexpand">True</property>
+        <property name="row_spacing">6</property>
+        <property name="column_spacing">12</property>
+        <child>
+          <object class="GtkLabel" id="desc">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="label">label</property>
+            <property name="ellipsize">end</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <style>
+              <class name="dim-label"/>
+              <class name="clock-desc"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkLabel" id="name_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label">label</property>
+                <property name="ellipsize">end</property>
+                <property name="xalign">0</property>
+                <property name="yalign">1</property>
+                <style>
+                  <class name="clock-title"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="country_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="valign">center</property>
+                <property name="label">label</property>
+                <style>
+                  <class name="caption-heading"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkImage" id="checkmark">
+            <property name="can_focus">False</property>
+            <property name="icon_name">object-select-symbolic</property>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">0</property>
+            <property name="height">2</property>
+          </packing>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+    </child>
+    <style>
+      <class name="clock"/>
+    </style>
+  </template>
+</interface>
diff --git a/data/ui/world-location-dialog.ui b/data/ui/world-location-dialog.ui
index 3c69861..5f0e869 100644
--- a/data/ui/world-location-dialog.ui
+++ b/data/ui/world-location-dialog.ui
@@ -1,11 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.36.0 -->
 <interface>
-  <!-- interface-requires libgweather 3.0 -->
-  <!-- interface-requires gtk+ 3.12 -->
+  <requires lib="gtk+" version="3.22"/>
+  <requires lib="libhandy" version="0.0"/>
   <template class="ClocksWorldLocationDialog" parent="GtkDialog">
     <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Add World Clock</property>
     <property name="modal">True</property>
     <property name="window_position">center-on-parent</property>
+    <property name="default_width">400</property>
+    <property name="default_height">540</property>
     <property name="destroy_with_parent">True</property>
     <property name="type_hint">dialog</property>
     <property name="gravity">center</property>
@@ -32,7 +36,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkButton" id="button2">
+          <object class="GtkButton" id="button_add">
             <property name="label" translatable="yes">_Add</property>
             <property name="visible">True</property>
             <property name="sensitive">False</property>
@@ -58,48 +62,124 @@
         <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <property name="spacing">2</property>
-        <property name="valign">center</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
         <child>
-          <object class="GtkGrid" id="location_dialog_content">
+          <object class="GtkScrolledWindow">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="border_width">6</property>
-            <property name="row_spacing">6</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="height_request">250</property>
             <child>
-              <object class="GtkLabel" id="label2">
+              <object class="HdyClamp">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">Search for a city:</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GWeatherLocationEntry" id="location_entry">
-                <property name="show-named-timezones">True</property>
-                <property name="visible">True</property>
-                <property name="hexpand">True</property>
-                <property name="can_focus">True</property>
-                <property name="activates_default">True</property>
-                <signal name="activate" handler="location_changed" object="ClocksWorldLocationDialog" 
swapped="no"/>
-                <signal name="changed" handler="location_changed" object="ClocksWorldLocationDialog" 
swapped="no"/>
-                <signal name="icon-release" handler="icon_released" object="ClocksWorldLocationDialog" 
swapped="no"/>
+                <property name="width_request">300</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkSearchEntry" id="location_entry">
+                        <property name="visible">True</property>
+                        <property name="margin_top">18</property>
+                        <property name="margin_start">50</property>
+                        <property name="margin_end">50</property>
+                        <property name="can_focus">True</property>
+                        <signal name="search-changed" handler="on_search_changed" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkStack" id="stack">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <property name="vexpand">True</property>
+                        <property name="transition_duration">100</property>
+                        <property name="transition_type">crossfade</property>
+                        <child>
+                          <object class="GtkBox" id="empty_search_box">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="valign">center</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="pixel_size">128</property>
+                                <property name="icon_name">edit-find-symbolic</property>
+                                <style>
+                                  <class name="dim-label"/>
+                                </style>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">Search for a city</property>
+                                <style>
+                                  <class name="dim-label"/>
+                                  <class name="title-4"/>
+                                </style>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkListBox" id="listbox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="valign">start</property>
+                            <property name="selection_mode">none</property>
+                            <style>
+                              <class name="clocks-list"/>
+                              <class name="preferences"/>
+                            </style>
+                            <signal name="row-activated" handler="item_activated" swapped="no"/>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
             </child>
           </object>
           <packing>
-            <property name="expand">False</property>
+            <property name="expand">True</property>
             <property name="fill">True</property>
             <property name="position">1</property>
           </packing>
@@ -108,7 +188,7 @@
     </child>
     <action-widgets>
       <action-widget response="0">button1</action-widget>
-      <action-widget response="1">button2</action-widget>
+      <action-widget response="1">button_add</action-widget>
     </action-widgets>
   </template>
 </interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b78700c..ba47283 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -18,6 +18,7 @@ data/ui/timer-setup.ui
 data/ui/window.ui
 data/ui/world-face.ui
 data/ui/world-location-dialog.ui
+data/ui/world-location-dialog-row.ui
 data/ui/world-standalone.ui
 src/alarm-face.vala
 src/alarm-item.vala
diff --git a/src/meson.build b/src/meson.build
index 0c5b2d2..3b33ab9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -29,6 +29,7 @@ clocks_vala_sources = [
   'world-face.vala',
   'world-item.vala',
   'world-location-dialog.vala',
+  'world-location-dialog-row.vala',
   'world-row.vala',
   'world-shell-world-clocks.vala',
   'world-standalone.vala',
diff --git a/src/utils.vala b/src/utils.vala
index 79c2e20..eebad84 100644
--- a/src/utils.vala
+++ b/src/utils.vala
@@ -60,6 +60,37 @@ public void time_to_hms (double t, out int h, out int m, out int s, out double r
     remainder = t - s;
 }
 
+public string get_time_difference_message (double offset) {
+    var diff = (double) offset / (double) TimeSpan.HOUR;
+    var diff_string = "%.0f".printf (diff.abs ());
+
+    if (diff != Math.round (diff)) {
+        if (diff * 2 != Math.round (diff * 2)) {
+            diff_string = "%.2f".printf (diff.abs ());
+        } else {
+            diff_string = "%.1f".printf (diff.abs ());
+        }
+    }
+
+    // Translators: The time is the same as the local time
+    var message = _("Current timezone");
+
+    if (diff > 0) {
+        // Translators: The (possibly fractical) number hours in the past
+        // (relative to local) the clock/location is
+        message = ngettext ("%s hour earlier",
+                            "%s hours earlier",
+                            ((int) diff).abs ()).printf (diff_string);
+    } else if (diff < 0) {
+        // Translators: The (possibly fractical) number hours in the
+        // future (relative to local) the clock/location is
+        message = ngettext ("%s hour later",
+                            "%s hours later",
+                            ((int) diff).abs ()).printf (diff_string);
+    }
+    return message;
+}
+
 // TODO: For now we are wrapping Gnome's clock, but we should probably
 // implement our own class, maybe using gnome-datetime-source
 // Especially if we want to try to use CLOCK_REALTIME_ALARM
diff --git a/src/world-face.vala b/src/world-face.vala
index c84f822..30f8cba 100644
--- a/src/world-face.vala
+++ b/src/world-face.vala
@@ -148,11 +148,13 @@ public class Face : Gtk.Stack, Clocks.Clock {
     public void activate_new () {
         var dialog = new LocationDialog ((Gtk.Window) get_toplevel (), this);
 
-        dialog.response.connect ((dialog, response) => {
+        dialog.response.connect ((_, response) => {
             if (response == 1) {
-                var location = ((LocationDialog) dialog).get_location ();
-                add_location_item ((Item) location);
+                var location = dialog.get_selected_location ();
+                if (location != null)
+                    add_location ((GWeather.Location) location);
             }
+
             dialog.destroy ();
         });
         dialog.show ();
diff --git a/src/world-location-dialog-row.vala b/src/world-location-dialog-row.vala
new file mode 100644
index 0000000..0a4cd2d
--- /dev/null
+++ b/src/world-location-dialog-row.vala
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020  Matyáš Hronek <saytamkenorh seznam cz>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+namespace World {
+
+[GtkTemplate (ui = "/org/gnome/clocks/ui/world-location-dialog-row.ui")]
+private class AddClockRow : Gtk.ListBoxRow {
+    public ClockLocation data { get; construct set; }
+
+    [GtkChild]
+    private Gtk.Label name_label;
+    [GtkChild]
+    private Gtk.Label country_label;
+    [GtkChild]
+    private Gtk.Label desc;
+    [GtkChild]
+    private Gtk.Widget checkmark;
+
+    public AddClockRow (ClockLocation data) {
+        Object (data: data);
+
+        name_label.label = data.location.get_name ();
+
+        var country_name = data.location.get_country_name ();
+        if (country_name != null) {
+            country_label.label = (string) country_name;
+        }
+
+        var wallclock = Utils.WallClock.get_default ();
+        var local_time = wallclock.date_time;
+        var weather_time_zone = data.location.get_timezone ();
+        if (weather_time_zone != null) {
+            var time_zone = new TimeZone (((GWeather.Timezone) weather_time_zone).get_tzid ());
+            var date_time = local_time.to_timezone (time_zone);
+            var local_offset = local_time.get_utc_offset () - date_time.get_utc_offset ();
+            var time_diff_message = Utils.get_time_difference_message (local_offset);
+            var time_zone_name = ((GWeather.Timezone) weather_time_zone).get_name ();
+
+            if ((string?) time_zone_name != null) {
+                desc.label = "%s • %s".printf (time_zone_name, time_diff_message);
+            } else {
+                desc.label = "%s".printf (time_diff_message);
+            }
+
+
+        } else {
+            desc.hide ();
+        }
+
+        sensitive = !data.selected;
+
+        data.notify["selected"].connect (on_selected_changed);
+    }
+
+    private void on_selected_changed () {
+        checkmark.visible = data.selected;
+    }
+}
+
+} // namespace World
+} // namespace Clocks
diff --git a/src/world-location-dialog.vala b/src/world-location-dialog.vala
index 44bdeef..63907ca 100644
--- a/src/world-location-dialog.vala
+++ b/src/world-location-dialog.vala
@@ -19,49 +19,148 @@
 namespace Clocks {
 namespace World {
 
+private class ClockLocation : Object {
+    public GWeather.Location location { get; construct set; }
+    public bool selected { get; set; }
+
+    public ClockLocation (GWeather.Location location, bool selected) {
+        Object (location: location, selected: selected);
+    }
+}
+
 [GtkTemplate (ui = "/org/gnome/clocks/ui/world-location-dialog.ui")]
 private class LocationDialog : Gtk.Dialog {
     [GtkChild]
-    private GWeather.LocationEntry location_entry;
+    private Gtk.Stack stack;
+    [GtkChild]
+    private Gtk.Box empty_search_box;
+    [GtkChild]
+    private Gtk.SearchEntry location_entry;
+    [GtkChild]
+    private Gtk.ListBox listbox;
+    [GtkChild]
+    private Gtk.Button button_add;
+
     private Face world;
+    private ListStore locations;
+    private AddClockRow? _selected_row = null;
+    private AddClockRow? selected_row {
+        get {
+            return _selected_row;
+        } set {
+            _selected_row = value;
+            button_add.sensitive = _selected_row != null;
+        }
+    }
+
+    private const int RESULT_COUNT_LIMIT = 12;
 
     public LocationDialog (Gtk.Window parent, Face world_face) {
         Object (transient_for: parent, use_header_bar: 1);
 
+        key_press_event.connect ((event) => {
+            if (event.keyval == Gdk.Key.Tab)
+                return Gdk.EVENT_PROPAGATE;
+            return location_entry.handle_event (event);
+        });
+
         world = world_face;
+
+        locations = new ListStore (typeof (ClockLocation));
+        listbox.bind_model (locations, (data) => {
+            return new AddClockRow ((ClockLocation) data);
+        });
+    }
+
+    public GWeather.Location? get_selected_location () {
+        if (selected_row == null)
+            return null;
+        return ((AddClockRow) selected_row).data.location;
     }
 
     [GtkCallback]
-    private void icon_released () {
-        if (location_entry.secondary_icon_name == "edit-clear-symbolic") {
-            location_entry.set_text ("");
+    private void item_activated (Gtk.ListBoxRow listbox_row) {
+        var row = (AddClockRow) listbox_row;
+
+        if (selected_row != null && selected_row != row) {
+            ((AddClockRow) selected_row).data.selected = false;
+        }
+
+        row.data.selected = !row.data.selected;
+        if (row.data.selected) {
+            selected_row = row;
+        } else {
+            selected_row = null;
         }
     }
 
     [GtkCallback]
-    private void location_changed () {
-        GWeather.Location? l = null;
-        GWeather.Timezone? t = null;
+    private void on_search_changed () {
+        selected_row = null;
 
-        if (location_entry.get_text () != "") {
-            l = location_entry.get_location ();
+        // Remove old results
+        locations.remove_all ();
 
-            if (l != null && !world.location_exists ((GWeather.Location) l)) {
-                t = ((GWeather.Location) l).get_timezone ();
+        if (location_entry.text == "") {
+            stack.visible_child = empty_search_box;
+            return;
+        }
 
-                if (t == null) {
-                    warning ("Timezone not defined for %s. This is a bug in libgweather database",
-                             (string) ((GWeather.Location) l).get_city_name ());
-                }
-            }
+        string search = location_entry.text.normalize ().casefold ();
+        var world_location = GWeather.Location.get_world ();
+        if (world_location == null) {
+            return;
         }
 
-        set_response_sensitive (1, l != null && t != null);
+        query_locations ((GWeather.Location) world_location, search);
+
+        if (locations.get_n_items () == 0) {
+            stack.visible_child = empty_search_box;
+            return;
+        }
+        locations.sort ((a, b) => {
+            var name_a = ((ClockLocation) a).location.get_sort_name ();
+            var name_b = ((ClockLocation) b).location.get_sort_name ();
+            return strcmp (name_a, name_b);
+        });
+
+        stack.visible_child = listbox;
     }
 
-    public Item? get_location () {
-        var location = location_entry.get_location ();
-        return location != null ? (Item?) new Item ((GWeather.Location) location) : null;
+    private void query_locations (GWeather.Location location, string search) {
+        if (locations.get_n_items () >= RESULT_COUNT_LIMIT) return;
+
+        if (location.get_level () == GWeather.LocationLevel.CITY) {
+            var contains_name = location.get_sort_name ().contains (search);
+
+            var country_name = location.get_country_name ();
+            if (country_name != null) {
+                country_name = ((string) country_name).normalize ().casefold ();
+            }
+            var contains_country_name = country_name != null && ((string) country_name).contains (search);
+
+            string? timezone_name = null;
+            var timezone = location.get_timezone ();
+            if (timezone != null) {
+                timezone_name = ((GWeather.Timezone) timezone).get_name ();
+                if (timezone_name != null) {
+                    timezone_name = ((string) timezone_name).normalize ().casefold ();
+                }
+            }
+            var contains_timezone_name = timezone_name != null && ((string) timezone_name).contains (search);
+
+            if (contains_name || contains_country_name || contains_timezone_name) {
+                bool selected = world.location_exists (location);
+                locations.append (new ClockLocation (location, selected));
+            }
+            return;
+        }
+        foreach (var child in location.get_children ()) {
+            query_locations (child, search);
+            if (locations.get_n_items () >= RESULT_COUNT_LIMIT) {
+                return;
+            }
+        }
     }
 }
 
diff --git a/src/world-row.vala b/src/world-row.vala
index a8ceb45..c0ed307 100644
--- a/src/world-row.vala
+++ b/src/world-row.vala
@@ -56,33 +56,7 @@ private class Row : Gtk.ListBoxRow {
         ctx.remove_class ("day");
         ctx.add_class (location.state_class);
 
-        var diff = ((double) location.local_offset / (double) TimeSpan.HOUR);
-        var diff_string = "%.0f".printf (diff.abs ());
-
-        if (diff != Math.round (diff)) {
-            if (diff * 2 != Math.round (diff * 2)) {
-                diff_string = "%.2f".printf (diff.abs ());
-            } else {
-                diff_string = "%.1f".printf (diff.abs ());
-            }
-        }
-
-        // Translators: The time is the same as the local time
-        var message = _("Current timezone");
-
-        if (diff > 0) {
-            // Translators: The (possibly fractical) number hours in the past
-            // (relative to local) the clock/location is
-            message = ngettext ("%s hour earlier",
-                                "%s hours earlier",
-                                ((int) diff).abs ()).printf (diff_string);
-        } else if (diff < 0) {
-            // Translators: The (possibly fractical) number hours in the
-            // future (relative to local) the clock/location is
-            message = ngettext ("%s hour later",
-                                "%s hours later",
-                                ((int) diff).abs ()).printf (diff_string);
-        }
+        var message = Utils.get_time_difference_message ((double) location.local_offset);
 
         if (location.day_label != null && location.day_label != "") {
             desc.label = "%s • %s".printf ((string) location.day_label, message);


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