[gnome-panel/wip/muktupavels/gweather-4] clock: add GWeatherLocationEntry and GWeatherTimezoneMenu



commit cf56a55d34d2b6f143ce406540c3d822a1920b58
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Sun Feb 27 21:13:17 2022 +0200

    clock: add GWeatherLocationEntry and GWeatherTimezoneMenu
    
    GWeatherLocationEntry and GWeatherTimezoneMenu are removed from
    libgweather 4. This commit adds unmodified copies of these classes
    from libgweather.

 modules/clock/gweather-location-entry.c | 880 ++++++++++++++++++++++++++++++++
 modules/clock/gweather-location-entry.h |  63 +++
 modules/clock/gweather-timezone-menu.c  | 420 +++++++++++++++
 modules/clock/gweather-timezone-menu.h  |  59 +++
 4 files changed, 1422 insertions(+)
---
diff --git a/modules/clock/gweather-location-entry.c b/modules/clock/gweather-location-entry.c
new file mode 100644
index 000000000..7b63f995c
--- /dev/null
+++ b/modules/clock/gweather-location-entry.c
@@ -0,0 +1,880 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* location-entry.c - Location-selecting text entry
+ *
+ * Copyright 2008, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <geocode-glib/geocode-glib.h>
+#include <gio/gio.h>
+
+#include "gweather-location-entry.h"
+#include "gweather-private.h"
+
+/**
+ * SECTION:gweatherlocationentry
+ * @Title: GWeatherLocationEntry
+ *
+ * A subclass of #GtkSearchEntry that provides autocompletion on
+ * #GWeatherLocation<!-- -->s
+ */
+
+struct _GWeatherLocationEntryPrivate {
+    GWeatherLocation *location;
+    GWeatherLocation *top;
+    gboolean          show_named_timezones;
+    gboolean          custom_text;
+    GCancellable     *cancellable;
+    GtkTreeModel     *model;
+};
+
+G_DEFINE_TYPE (GWeatherLocationEntry, gweather_location_entry, GTK_TYPE_SEARCH_ENTRY)
+
+enum {
+    PROP_0,
+
+    PROP_TOP,
+    PROP_SHOW_NAMED_TIMEZONES,
+    PROP_LOCATION,
+
+    LAST_PROP
+};
+
+static void set_property (GObject *object, guint prop_id,
+                         const GValue *value, GParamSpec *pspec);
+static void get_property (GObject *object, guint prop_id,
+                         GValue *value, GParamSpec *pspec);
+
+static void set_location_internal (GWeatherLocationEntry *entry,
+                                  GtkTreeModel          *model,
+                                  GtkTreeIter           *iter,
+                                  GWeatherLocation      *loc);
+static void
+fill_location_entry_model (GtkListStore *store, GWeatherLocation *loc,
+                          const char *parent_display_name,
+                          const char *parent_sort_local_name,
+                          const char *parent_compare_local_name,
+                          const char *parent_compare_english_name,
+                          gboolean show_named_timezones);
+
+enum LOC
+{
+    LOC_GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME = 0,
+    LOC_GWEATHER_LOCATION_ENTRY_COL_LOCATION,
+    LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME,
+    LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME,
+    LOC_GWEATHER_LOCATION_ENTRY_COL_ENGLISH_COMPARE_NAME,
+    LOC_GWEATHER_LOCATION_ENTRY_NUM_COLUMNS
+};
+
+enum PLACE
+{
+    PLACE_GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME = 0,
+    PLACE_GWEATHER_LOCATION_ENTRY_COL_PLACE,
+    PLACE_GWEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME,
+    PLACE_GWEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME
+};
+
+static gboolean matcher (GtkEntryCompletion *completion, const char *key,
+                        GtkTreeIter *iter, gpointer user_data);
+static gboolean match_selected (GtkEntryCompletion *completion,
+                               GtkTreeModel       *model,
+                               GtkTreeIter        *iter,
+                               gpointer            entry);
+static void     entry_changed (GWeatherLocationEntry *entry);
+static void _no_matches (GtkEntryCompletion *completion, GWeatherLocationEntry *entry);
+
+static void
+gweather_location_entry_init (GWeatherLocationEntry *entry)
+{
+    GtkEntryCompletion *completion;
+    GWeatherLocationEntryPrivate *priv;
+
+    priv = entry->priv = G_TYPE_INSTANCE_GET_PRIVATE (entry, GWEATHER_TYPE_LOCATION_ENTRY, 
GWeatherLocationEntryPrivate);
+
+    completion = gtk_entry_completion_new ();
+
+    gtk_entry_completion_set_popup_set_width (completion, FALSE);
+    gtk_entry_completion_set_text_column (completion, LOC_GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME);
+    gtk_entry_completion_set_match_func (completion, matcher, NULL, NULL);
+    gtk_entry_completion_set_inline_completion (completion, TRUE);
+
+    g_signal_connect (completion, "match-selected",
+                     G_CALLBACK (match_selected), entry);
+
+    g_signal_connect (completion, "no-matches",
+                      G_CALLBACK (_no_matches), entry);
+
+    gtk_entry_set_completion (GTK_ENTRY (entry), completion);
+    g_object_unref (completion);
+
+    priv->custom_text = FALSE;
+    g_signal_connect (entry, "changed",
+                     G_CALLBACK (entry_changed), NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+    GWeatherLocationEntry *entry;
+    GWeatherLocationEntryPrivate *priv;
+
+    entry = GWEATHER_LOCATION_ENTRY (object);
+    priv = entry->priv;
+
+    if (priv->location)
+       gweather_location_unref (priv->location);
+    if (priv->top)
+       gweather_location_unref (priv->top);
+    if (priv->model)
+        g_object_unref (priv->model);
+
+    G_OBJECT_CLASS (gweather_location_entry_parent_class)->finalize (object);
+}
+
+static void
+dispose (GObject *object)
+{
+    GWeatherLocationEntry *entry;
+    GWeatherLocationEntryPrivate *priv;
+
+    entry = GWEATHER_LOCATION_ENTRY (object);
+    priv = entry->priv;
+
+    if (priv->cancellable) {
+        g_cancellable_cancel (priv->cancellable);
+        g_object_unref (priv->cancellable);
+        priv->cancellable = NULL;
+    }
+
+    G_OBJECT_CLASS (gweather_location_entry_parent_class)->dispose (object);
+}
+
+static int
+tree_compare_local_name (GtkTreeModel *model,
+                        GtkTreeIter  *a,
+                        GtkTreeIter  *b,
+                        gpointer      user_data)
+{
+    g_autofree gchar *name_a = NULL, *name_b = NULL;
+
+    gtk_tree_model_get (model, a,
+                       LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, &name_a,
+                       -1);
+    gtk_tree_model_get (model, b,
+                       LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, &name_b,
+                       -1);
+
+    return g_utf8_collate (name_a, name_b);
+}
+
+
+static void
+constructed (GObject *object)
+{
+    GWeatherLocationEntry *entry;
+    GtkListStore *store = NULL;
+    GtkEntryCompletion *completion;
+
+    entry = GWEATHER_LOCATION_ENTRY (object);
+
+    if (!entry->priv->top)
+       entry->priv->top = gweather_location_get_world ();
+
+    store = gtk_list_store_new (5, G_TYPE_STRING, GWEATHER_TYPE_LOCATION, G_TYPE_STRING, G_TYPE_STRING, 
G_TYPE_STRING);
+    gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+                                             tree_compare_local_name, NULL, NULL);
+    fill_location_entry_model (store, entry->priv->top, NULL, NULL, NULL, NULL, 
entry->priv->show_named_timezones);
+
+    entry->priv->model = GTK_TREE_MODEL (store);
+    completion = gtk_entry_get_completion (GTK_ENTRY (entry));
+    gtk_entry_completion_set_match_func (completion, matcher, NULL, NULL);
+    gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (store));
+
+    G_OBJECT_CLASS (gweather_location_entry_parent_class)->constructed (object);
+}
+
+static void
+gweather_location_entry_class_init (GWeatherLocationEntryClass *location_entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (location_entry_class);
+
+    object_class->constructed = constructed;
+    object_class->finalize = finalize;
+    object_class->set_property = set_property;
+    object_class->get_property = get_property;
+    object_class->dispose = dispose;
+
+    /* properties */
+    g_object_class_install_property (
+       object_class, PROP_TOP,
+       g_param_spec_boxed ("top",
+                           "Top Location",
+                           "The GWeatherLocation whose children will be used to fill in the entry",
+                           GWEATHER_TYPE_LOCATION,
+                           G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+    g_object_class_install_property (
+       object_class, PROP_SHOW_NAMED_TIMEZONES,
+       g_param_spec_boolean ("show-named-timezones",
+                             "Show named timezones",
+                             "Whether UTC and other named timezones are shown in the list of locations",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+    g_object_class_install_property (
+       object_class, PROP_LOCATION,
+       g_param_spec_boxed ("location",
+                           "Location",
+                           "The selected GWeatherLocation",
+                           GWEATHER_TYPE_LOCATION,
+                           G_PARAM_READWRITE));
+
+    g_type_class_add_private (location_entry_class, sizeof (GWeatherLocationEntryPrivate));
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+             const GValue *value, GParamSpec *pspec)
+{
+    GWeatherLocationEntry *entry = GWEATHER_LOCATION_ENTRY (object);
+
+    switch (prop_id) {
+    case PROP_TOP:
+        entry->priv->top = g_value_dup_boxed (value);
+       break;
+    case PROP_SHOW_NAMED_TIMEZONES:
+       entry->priv->show_named_timezones = g_value_get_boolean (value);
+       break;
+    case PROP_LOCATION:
+       gweather_location_entry_set_location (GWEATHER_LOCATION_ENTRY (object),
+                                             g_value_get_boxed (value));
+       break;
+    default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       break;
+    }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+             GValue *value, GParamSpec *pspec)
+{
+    GWeatherLocationEntry *entry = GWEATHER_LOCATION_ENTRY (object);
+
+    switch (prop_id) {
+    case PROP_SHOW_NAMED_TIMEZONES:
+       g_value_set_boolean (value, entry->priv->show_named_timezones);
+       break;
+    case PROP_LOCATION:
+       g_value_set_boxed (value, entry->priv->location);
+       break;
+    default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       break;
+    }
+}
+
+static void
+entry_changed (GWeatherLocationEntry *entry)
+{
+    GtkEntryCompletion *completion;
+    const gchar *text;
+
+    completion = gtk_entry_get_completion (GTK_ENTRY (entry));
+
+    if (entry->priv->cancellable) {
+        g_cancellable_cancel (entry->priv->cancellable);
+        g_object_unref (entry->priv->cancellable);
+        entry->priv->cancellable = NULL;
+        gtk_entry_completion_delete_action (completion, 0);
+    }
+
+    gtk_entry_completion_set_match_func (gtk_entry_get_completion (GTK_ENTRY (entry)), matcher, NULL, NULL);
+    gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (entry)), entry->priv->model);
+
+    text = gtk_entry_get_text (GTK_ENTRY (entry));
+
+    if (text && *text)
+       entry->priv->custom_text = TRUE;
+    else
+       set_location_internal (entry, NULL, NULL, NULL);
+}
+
+static void
+set_location_internal (GWeatherLocationEntry *entry,
+                      GtkTreeModel          *model,
+                      GtkTreeIter           *iter,
+                      GWeatherLocation      *loc)
+{
+    GWeatherLocationEntryPrivate *priv;
+    char *name;
+
+    priv = entry->priv;
+
+    if (priv->location)
+       gweather_location_unref (priv->location);
+
+    g_assert (iter == NULL || loc == NULL);
+
+    if (iter) {
+       gtk_tree_model_get (model, iter,
+                           LOC_GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, &name,
+                           LOC_GWEATHER_LOCATION_ENTRY_COL_LOCATION, &priv->location,
+                           -1);
+       gtk_entry_set_text (GTK_ENTRY (entry), name);
+       priv->custom_text = FALSE;
+       g_free (name);
+    } else if (loc) {
+       priv->location = gweather_location_ref (loc);
+       gtk_entry_set_text (GTK_ENTRY (entry), gweather_location_get_name (loc));
+       priv->custom_text = FALSE;
+    } else {
+       priv->location = NULL;
+       gtk_entry_set_text (GTK_ENTRY (entry), "");
+       priv->custom_text = TRUE;
+    }
+
+    gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+    g_object_notify (G_OBJECT (entry), "location");
+}
+
+/**
+ * gweather_location_entry_set_location:
+ * @entry: a #GWeatherLocationEntry
+ * @loc: (allow-none): a #GWeatherLocation in @entry, or %NULL to
+ * clear @entry
+ *
+ * Sets @entry's location to @loc, and updates the text of the
+ * entry accordingly.
+ * Note that if the database contains a location that compares
+ * equal to @loc, that will be chosen in place of @loc.
+ **/
+void
+gweather_location_entry_set_location (GWeatherLocationEntry *entry,
+                                     GWeatherLocation      *loc)
+{
+    GtkEntryCompletion *completion;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    GWeatherLocation *cmploc;
+
+    g_return_if_fail (GWEATHER_IS_LOCATION_ENTRY (entry));
+
+    completion = gtk_entry_get_completion (GTK_ENTRY (entry));
+    model = gtk_entry_completion_get_model (completion);
+
+    if (loc == NULL) {
+       set_location_internal (entry, model, NULL, NULL);
+       return;
+    }
+
+    gtk_tree_model_get_iter_first (model, &iter);
+    do {
+       gtk_tree_model_get (model, &iter,
+                           LOC_GWEATHER_LOCATION_ENTRY_COL_LOCATION, &cmploc,
+                           -1);
+       if (gweather_location_equal (loc, cmploc)) {
+           set_location_internal (entry, model, &iter, NULL);
+           gweather_location_unref (cmploc);
+           return;
+       }
+
+       gweather_location_unref (cmploc);
+    } while (gtk_tree_model_iter_next (model, &iter));
+
+    set_location_internal (entry, model, NULL, loc);
+}
+
+/**
+ * gweather_location_entry_get_location:
+ * @entry: a #GWeatherLocationEntry
+ *
+ * Gets the location that was set by a previous call to
+ * gweather_location_entry_set_location() or was selected by the user.
+ *
+ * Return value: (transfer full) (allow-none): the selected location
+ * (which you must unref when you are done with it), or %NULL if no
+ * location is selected.
+ **/
+GWeatherLocation *
+gweather_location_entry_get_location (GWeatherLocationEntry *entry)
+{
+    g_return_val_if_fail (GWEATHER_IS_LOCATION_ENTRY (entry), NULL);
+
+    if (entry->priv->location)
+       return gweather_location_ref (entry->priv->location);
+    else
+       return NULL;
+}
+
+/**
+ * gweather_location_entry_has_custom_text:
+ * @entry: a #GWeatherLocationEntry
+ *
+ * Checks whether or not @entry's text has been modified by the user.
+ * Note that this does not mean that no location is associated with @entry.
+ * gweather_location_entry_get_location() should be used for this.
+ *
+ * Return value: %TRUE if @entry's text was modified by the user, or %FALSE if
+ * it's set to the default text of a location.
+ **/
+gboolean
+gweather_location_entry_has_custom_text (GWeatherLocationEntry *entry)
+{
+    g_return_val_if_fail (GWEATHER_IS_LOCATION_ENTRY (entry), FALSE);
+
+    return entry->priv->custom_text;
+}
+
+/**
+ * gweather_location_entry_set_city:
+ * @entry: a #GWeatherLocationEntry
+ * @city_name: (allow-none): the city name, or %NULL
+ * @code: the METAR station code
+ *
+ * Sets @entry's location to a city with the given @code, and given
+ * @city_name, if non-%NULL. If there is no matching city, sets
+ * @entry's location to %NULL.
+ *
+ * Return value: %TRUE if @entry's location could be set to a matching city,
+ * %FALSE otherwise.
+ **/
+gboolean
+gweather_location_entry_set_city (GWeatherLocationEntry *entry,
+                                 const char            *city_name,
+                                 const char            *code)
+{
+    GtkEntryCompletion *completion;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    GWeatherLocation *cmploc;
+    const char *cmpcode;
+    char *cmpname;
+
+    g_return_val_if_fail (GWEATHER_IS_LOCATION_ENTRY (entry), FALSE);
+    g_return_val_if_fail (code != NULL, FALSE);
+
+    completion = gtk_entry_get_completion (GTK_ENTRY (entry));
+    model = gtk_entry_completion_get_model (completion);
+
+    gtk_tree_model_get_iter_first (model, &iter);
+    do {
+       gtk_tree_model_get (model, &iter,
+                           LOC_GWEATHER_LOCATION_ENTRY_COL_LOCATION, &cmploc,
+                           -1);
+
+       cmpcode = gweather_location_get_code (cmploc);
+       if (!cmpcode || strcmp (cmpcode, code) != 0) {
+           gweather_location_unref (cmploc);
+           continue;
+       }
+
+       if (city_name) {
+           cmpname = gweather_location_get_city_name (cmploc);
+           if (!cmpname || strcmp (cmpname, city_name) != 0) {
+               gweather_location_unref (cmploc);
+               g_free (cmpname);
+               continue;
+           }
+           g_free (cmpname);
+       }
+
+       set_location_internal (entry, model, &iter, NULL);
+       gweather_location_unref (cmploc);
+       return TRUE;
+    } while (gtk_tree_model_iter_next (model, &iter));
+
+    set_location_internal (entry, model, NULL, NULL);
+
+    return FALSE;
+}
+
+static void
+fill_location_entry_model (GtkListStore *store, GWeatherLocation *loc,
+                          const char *parent_display_name,
+                          const char *parent_sort_local_name,
+                          const char *parent_compare_local_name,
+                          const char *parent_compare_english_name,
+                          gboolean show_named_timezones)
+{
+    g_autoptr(GWeatherLocation) child = NULL;
+    char *display_name, *local_sort_name, *local_compare_name, *english_compare_name;
+
+    switch (gweather_location_get_level (loc)) {
+    case GWEATHER_LOCATION_WORLD:
+    case GWEATHER_LOCATION_REGION:
+       /* Ignore these levels of hierarchy; just recurse, passing on
+        * the names from the parent node.
+        */
+       while ((child = gweather_location_next_child (loc, child)))
+           fill_location_entry_model (store, child,
+                                      parent_display_name,
+                                      parent_sort_local_name,
+                                      parent_compare_local_name,
+                                      parent_compare_english_name,
+                                      show_named_timezones);
+       break;
+
+    case GWEATHER_LOCATION_COUNTRY:
+       /* Recurse, initializing the names to the country name */
+       while ((child = gweather_location_next_child (loc, child)))
+           fill_location_entry_model (store, child,
+                                      gweather_location_get_name (loc),
+                                      gweather_location_get_sort_name (loc),
+                                      gweather_location_get_sort_name (loc),
+                                      gweather_location_get_english_sort_name (loc),
+                                      show_named_timezones);
+       break;
+
+    case GWEATHER_LOCATION_ADM1:
+       /* Recurse, adding the ADM1 name to the country name */
+       /* Translators: this is the name of a location followed by a region, for example:
+        * 'London, United Kingdom'
+        * You shouldn't need to translate this string unless the language has a different comma.
+        */
+       display_name = g_strdup_printf (_("%s, %s"), gweather_location_get_name (loc), parent_display_name);
+       local_sort_name = g_strdup_printf ("%s, %s", parent_sort_local_name, gweather_location_get_sort_name 
(loc));
+       local_compare_name = g_strdup_printf ("%s, %s", gweather_location_get_sort_name (loc), 
parent_compare_local_name);
+       english_compare_name = g_strdup_printf ("%s, %s", gweather_location_get_english_sort_name (loc), 
parent_compare_english_name);
+
+       while ((child = gweather_location_next_child (loc, child)))
+           fill_location_entry_model (store, child,
+                                      display_name, local_sort_name, local_compare_name, 
english_compare_name,
+                                      show_named_timezones);
+
+       g_free (display_name);
+       g_free (local_sort_name);
+       g_free (local_compare_name);
+       g_free (english_compare_name);
+       break;
+
+    case GWEATHER_LOCATION_CITY:
+       /* If there are multiple (<location>) children, we use the one
+        * closest to the city center.
+        *
+        * Locations are already sorted by increasing distance from
+        * the city.
+        */
+    case GWEATHER_LOCATION_WEATHER_STATION:
+       /* <location> with no parent <city> */
+       /* Translators: this is the name of a location followed by a region, for example:
+        * 'London, United Kingdom'
+        * You shouldn't need to translate this string unless the language has a different comma.
+        */
+       display_name = g_strdup_printf (_("%s, %s"),
+                                       gweather_location_get_name (loc), parent_display_name);
+       local_sort_name = g_strdup_printf ("%s, %s",
+                                          parent_sort_local_name, gweather_location_get_sort_name (loc));
+       local_compare_name = g_strdup_printf ("%s, %s",
+                                             gweather_location_get_sort_name (loc), 
parent_compare_local_name);
+       english_compare_name = g_strdup_printf ("%s, %s",
+                                               gweather_location_get_english_sort_name (loc), 
parent_compare_english_name);
+
+       gtk_list_store_insert_with_values (store, NULL, -1,
+                                          LOC_GWEATHER_LOCATION_ENTRY_COL_LOCATION, loc,
+                                          LOC_GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, display_name,
+                                          LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, local_sort_name,
+                                          LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME, 
local_compare_name,
+                                          LOC_GWEATHER_LOCATION_ENTRY_COL_ENGLISH_COMPARE_NAME, 
english_compare_name,
+                                          -1);
+
+       g_free (display_name);
+       g_free (local_sort_name);
+       g_free (local_compare_name);
+       g_free (english_compare_name);
+       break;
+
+    case GWEATHER_LOCATION_NAMED_TIMEZONE:
+       if (show_named_timezones) {
+           gtk_list_store_insert_with_values (store, NULL, -1,
+                                              LOC_GWEATHER_LOCATION_ENTRY_COL_LOCATION, loc,
+                                              LOC_GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, 
gweather_location_get_name (loc),
+                                              LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, 
gweather_location_get_sort_name (loc),
+                                              LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME, 
gweather_location_get_sort_name (loc),
+                                              LOC_GWEATHER_LOCATION_ENTRY_COL_ENGLISH_COMPARE_NAME, 
gweather_location_get_english_sort_name (loc),
+                                              -1);
+       }
+       break;
+
+    case GWEATHER_LOCATION_DETACHED:
+       g_assert_not_reached ();
+    }
+}
+
+static char *
+find_word (const char *full_name, const char *word, int word_len,
+          gboolean whole_word, gboolean is_first_word)
+{
+    char *p;
+
+    if (word == NULL || *word == '\0')
+        return NULL;
+
+    p = (char *)full_name - 1;
+    while ((p = strchr (p + 1, *word))) {
+       if (strncmp (p, word, word_len) != 0)
+           continue;
+
+       if (p > (char *)full_name) {
+           char *prev = g_utf8_prev_char (p);
+
+           /* Make sure p points to the start of a word */
+           if (g_unichar_isalpha (g_utf8_get_char (prev)))
+               continue;
+
+           /* If we're matching the first word of the key, it has to
+            * match the first word of the location, city, state, or
+            * country, or the abbreviation (in parenthesis).
+            * Eg, it either matches the start of the string
+            * (which we already know it doesn't at this point) or
+            * it is preceded by the string ", " or "(" (which isn't actually
+            * a perfect test. FIXME)
+            */
+           if (is_first_word) {
+               if (prev == (char *)full_name ||
+                   ((prev - 1 <= full_name && strncmp (prev - 1, ", ", 2) != 0)
+                     && *prev != '('))
+                   continue;
+           }
+       }
+
+       if (whole_word && g_unichar_isalpha (g_utf8_get_char (p + word_len)))
+           continue;
+
+       return p;
+    }
+    return NULL;
+}
+
+static gboolean
+match_compare_name (const char *key, const char *name)
+{
+    gboolean is_first_word = TRUE;
+    size_t len;
+
+    /* Ignore whitespace before the string */
+    key += strspn (key, " ");
+
+    /* All but the last word in KEY must match a full word from NAME,
+     * in order (but possibly skipping some words from NAME).
+     */
+    len = strcspn (key, " ");
+    while (key[len]) {
+       name = find_word (name, key, len, TRUE, is_first_word);
+       if (!name)
+           return FALSE;
+
+       key += len;
+       while (*key && !g_unichar_isalpha (g_utf8_get_char (key)))
+           key = g_utf8_next_char (key);
+       while (*name && !g_unichar_isalpha (g_utf8_get_char (name)))
+           name = g_utf8_next_char (name);
+
+       len = strcspn (key, " ");
+       is_first_word = FALSE;
+    }
+
+    /* The last word in KEY must match a prefix of a following word in NAME */
+    if (len == 0) {
+       return TRUE;
+    } else {
+       // if we get here, key[len] == 0, so...
+       g_assert (len == strlen(key));
+       return find_word (name, key, len, FALSE, is_first_word) != NULL;
+    }
+}
+
+static gboolean
+matcher (GtkEntryCompletion *completion, const char *key,
+        GtkTreeIter *iter, gpointer user_data)
+{
+    char *local_compare_name, *english_compare_name;
+    gboolean match;
+
+    gtk_tree_model_get (gtk_entry_completion_get_model (completion), iter,
+                       LOC_GWEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME, &local_compare_name,
+                       LOC_GWEATHER_LOCATION_ENTRY_COL_ENGLISH_COMPARE_NAME, &english_compare_name,
+                       -1);
+
+    match = match_compare_name (key, local_compare_name) ||
+           match_compare_name (key, english_compare_name) ||
+           g_ascii_strcasecmp (key, english_compare_name) == 0;
+
+    g_free (local_compare_name);
+    g_free (english_compare_name);
+    return match;
+}
+
+static gboolean
+match_selected (GtkEntryCompletion *completion,
+               GtkTreeModel       *model,
+               GtkTreeIter        *iter,
+               gpointer            entry)
+{
+    GWeatherLocationEntryPrivate *priv;
+
+    priv = ((GWeatherLocationEntry *)entry)->priv;
+
+    if (model != priv->model) {
+       GeocodePlace *place;
+       char *display_name;
+       GeocodeLocation *loc;
+       GWeatherLocation *location;
+       GWeatherLocation *scope = NULL;
+       const char* country_code;
+
+       gtk_tree_model_get (model, iter,
+                           PLACE_GWEATHER_LOCATION_ENTRY_COL_PLACE, &place,
+                           PLACE_GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, &display_name,
+                           -1);
+
+       country_code = geocode_place_get_country_code (place);
+       if (country_code != NULL && gweather_location_get_level (priv->top) == GWEATHER_LOCATION_WORLD)
+           scope = gweather_location_find_by_country_code (priv->top, country_code);
+       if (!scope)
+           scope = priv->top;
+
+       loc = geocode_place_get_location (place);
+       location = gweather_location_find_nearest_city (scope, geocode_location_get_latitude (loc), 
geocode_location_get_longitude (loc));
+
+       location = _gweather_location_new_detached (location, display_name, TRUE,
+                                                    geocode_location_get_latitude (loc) * M_PI / 180.0,
+                                                    geocode_location_get_longitude (loc) * M_PI / 180.0);
+
+       set_location_internal (entry, model, NULL, location);
+
+       g_object_unref (place);
+       g_free (display_name);
+    } else {
+       set_location_internal (entry, model, iter, NULL);
+    }
+    return TRUE;
+}
+
+static gboolean
+new_matcher (GtkEntryCompletion *completion, const char *key,
+             GtkTreeIter        *iter,       gpointer    user_data)
+{
+    return TRUE;
+}
+
+static void
+fill_store (gpointer data, gpointer user_data)
+{
+    GeocodePlace *place = GEOCODE_PLACE (data);
+    GeocodeLocation *loc = geocode_place_get_location (place);
+    const char *display_name;
+    char *normalized;
+    char *compare_name;
+
+    display_name = geocode_location_get_description (loc);
+    normalized = g_utf8_normalize (display_name, -1, G_NORMALIZE_ALL);
+    compare_name = g_utf8_casefold (normalized, -1);
+
+    gtk_list_store_insert_with_values (user_data, NULL, -1,
+                                      PLACE_GWEATHER_LOCATION_ENTRY_COL_PLACE, place,
+                                      PLACE_GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, display_name,
+                                      PLACE_GWEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, compare_name,
+                                      PLACE_GWEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME, compare_name,
+                                      -1);
+
+    g_free (normalized);
+    g_free (compare_name);
+}
+
+static void
+_got_places (GObject      *source_object,
+             GAsyncResult *result,
+             gpointer      user_data)
+{
+    GList *places;
+    GWeatherLocationEntry *self = user_data;
+    GError *error = NULL;
+    GtkListStore *store = NULL;
+    GtkEntryCompletion *completion;
+
+    places = geocode_forward_search_finish (GEOCODE_FORWARD (source_object), result, &error);
+    if (places == NULL) {
+        /* return without touching anything if cancelled (the entry might have been disposed) */
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+            g_clear_error (&error);
+            return;
+        }
+
+        g_clear_error (&error);
+        completion = gtk_entry_get_completion (user_data);
+        gtk_entry_completion_set_match_func (completion, matcher, NULL, NULL);
+        gtk_entry_completion_set_model (completion, self->priv->model);
+        goto out;
+    }
+
+    completion = gtk_entry_get_completion (user_data);
+    store = gtk_list_store_new (5, G_TYPE_STRING, GEOCODE_TYPE_PLACE, G_TYPE_STRING, G_TYPE_STRING, 
G_TYPE_STRING);
+    gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+                                             tree_compare_local_name, NULL, NULL);
+    g_list_foreach (places, fill_store, store);
+    g_list_free (places);
+    gtk_entry_completion_set_match_func (completion, new_matcher, NULL, NULL);
+    gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (store));
+    g_object_unref (store);
+
+ out:
+    gtk_entry_completion_delete_action (completion, 0);
+    g_clear_object (&self->priv->cancellable);
+}
+
+static void
+_no_matches (GtkEntryCompletion *completion, GWeatherLocationEntry *entry) {
+    const gchar *key = gtk_entry_get_text(GTK_ENTRY (entry));
+    GeocodeForward *forward;
+
+    if (entry->priv->cancellable) {
+        g_cancellable_cancel (entry->priv->cancellable);
+        g_object_unref (entry->priv->cancellable);
+        entry->priv->cancellable = NULL;
+    } else {
+        gtk_entry_completion_insert_action_text (completion, 0, _("Loading…"));
+    }
+
+    entry->priv->cancellable = g_cancellable_new ();
+
+    forward = geocode_forward_new_for_string(key);
+    geocode_forward_search_async (forward, entry->priv->cancellable, _got_places, entry);
+}
+
+/**
+ * gweather_location_entry_new:
+ * @top: the top-level location for the entry.
+ *
+ * Creates a new #GWeatherLocationEntry.
+ *
+ * @top will normally be the location returned from
+ * gweather_location_get_world(), but you can create an entry that
+ * only accepts a smaller set of locations if you want.
+ *
+ * Return value: the new #GWeatherLocationEntry
+ **/
+GtkWidget *
+gweather_location_entry_new (GWeatherLocation *top)
+{
+    return g_object_new (GWEATHER_TYPE_LOCATION_ENTRY,
+                        "top", top,
+                        NULL);
+}
diff --git a/modules/clock/gweather-location-entry.h b/modules/clock/gweather-location-entry.h
new file mode 100644
index 000000000..5d7ab63d9
--- /dev/null
+++ b/modules/clock/gweather-location-entry.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* location-entry.h - Location-selecting text entry
+ *
+ * Copyright 2008, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef GWEATHER_LOCATION_ENTRY_H
+#define GWEATHER_LOCATION_ENTRY_H 1
+
+#if !(defined(IN_GWEATHER_H) || defined(GWEATHER_COMPILATION))
+#error "gweather-location-entry.h must not be included individually, include gweather.h instead"
+#endif
+
+#include <gtk/gtk.h>
+#include <libgweather/gweather-location.h>
+
+typedef struct _GWeatherLocationEntry GWeatherLocationEntry;
+typedef struct _GWeatherLocationEntryClass GWeatherLocationEntryClass;
+typedef struct _GWeatherLocationEntryPrivate GWeatherLocationEntryPrivate;
+
+#define GWEATHER_TYPE_LOCATION_ENTRY            (gweather_location_entry_get_type ())
+#define GWEATHER_LOCATION_ENTRY(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), 
GWEATHER_TYPE_LOCATION_ENTRY, GWeatherLocationEntry))
+#define GWEATHER_LOCATION_ENTRY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
GWEATHER_TYPE_LOCATION_ENTRY, GWeatherLocationEntryClass))
+#define GWEATHER_IS_LOCATION_ENTRY(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
GWEATHER_TYPE_LOCATION_ENTRY))
+#define GWEATHER_IS_LOCATION_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GWEATHER_TYPE_LOCATION_ENTRY))
+#define GWEATHER_LOCATION_ENTRY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GWEATHER_TYPE_LOCATION_ENTRY, GWeatherLocationEntryClass))
+
+struct _GWeatherLocationEntry {
+    GtkSearchEntry parent;
+
+    /*< private >*/
+    GWeatherLocationEntryPrivate *priv;
+};
+
+struct _GWeatherLocationEntryClass {
+    GtkSearchEntryClass parent_class;
+};
+
+GType             gweather_location_entry_get_type     (void);
+GtkWidget        *gweather_location_entry_new          (GWeatherLocation      *top);
+void              gweather_location_entry_set_location (GWeatherLocationEntry *entry,
+                                                       GWeatherLocation      *loc);
+GWeatherLocation *gweather_location_entry_get_location (GWeatherLocationEntry *entry);
+gboolean          gweather_location_entry_has_custom_text (GWeatherLocationEntry *entry);
+gboolean          gweather_location_entry_set_city     (GWeatherLocationEntry *entry,
+                                                       const char            *city_name,
+                                                       const char            *code);
+
+#endif
diff --git a/modules/clock/gweather-timezone-menu.c b/modules/clock/gweather-timezone-menu.c
new file mode 100644
index 000000000..c4559bbac
--- /dev/null
+++ b/modules/clock/gweather-timezone-menu.c
@@ -0,0 +1,420 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* timezone-menu.c - Timezone-selecting menu
+ *
+ * Copyright 2008, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gweather-timezone-menu.h"
+#include "gweather-private.h"
+
+#include <string.h>
+
+/**
+ * SECTION:gweathertimezonemenu
+ * @Title: GWeatherTimezoneMenu
+ *
+ * A #GtkComboBox subclass for choosing a #GWeatherTimezone
+ */
+
+G_DEFINE_TYPE (GWeatherTimezoneMenu, gweather_timezone_menu, GTK_TYPE_COMBO_BOX)
+
+enum {
+    PROP_0,
+
+    PROP_TOP,
+    PROP_TZID,
+
+    LAST_PROP
+};
+
+static void set_property (GObject *object, guint prop_id,
+                         const GValue *value, GParamSpec *pspec);
+static void get_property (GObject *object, guint prop_id,
+                         GValue *value, GParamSpec *pspec);
+
+static void changed      (GtkComboBox *combo);
+
+static GtkTreeModel *gweather_timezone_model_new (GWeatherLocation *top);
+static gboolean row_separator_func (GtkTreeModel *model, GtkTreeIter *iter,
+                                   gpointer data);
+static void is_sensitive (GtkCellLayout *cell_layout, GtkCellRenderer *cell,
+                         GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data);
+
+static void
+gweather_timezone_menu_init (GWeatherTimezoneMenu *menu)
+{
+    GtkCellRenderer *renderer;
+
+    gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (menu),
+                                         row_separator_func, NULL, NULL);
+
+    renderer = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (menu), renderer, TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (menu), renderer,
+                                   "markup", 0,
+                                   NULL);
+    gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (menu),
+                                       renderer, is_sensitive, NULL, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+    GWeatherTimezoneMenu *menu = GWEATHER_TIMEZONE_MENU (object);
+
+    if (menu->zone)
+       gweather_timezone_unref (menu->zone);
+
+    G_OBJECT_CLASS (gweather_timezone_menu_parent_class)->finalize (object);
+}
+
+static void
+gweather_timezone_menu_class_init (GWeatherTimezoneMenuClass *timezone_menu_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (timezone_menu_class);
+    GtkComboBoxClass *combo_class = GTK_COMBO_BOX_CLASS (timezone_menu_class);
+
+    object_class->finalize = finalize;
+    object_class->set_property = set_property;
+    object_class->get_property = get_property;
+
+    combo_class->changed = changed;
+
+    /* properties */
+    g_object_class_install_property (
+       object_class, PROP_TOP,
+       g_param_spec_boxed ("top",
+                           "Top Location",
+                           "The GWeatherLocation whose children will be used to fill in the menu",
+                           GWEATHER_TYPE_LOCATION,
+                           G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+    g_object_class_install_property (
+       object_class, PROP_TZID,
+       g_param_spec_string ("tzid",
+                            "TZID",
+                            "The selected TZID",
+                            NULL,
+                            G_PARAM_READWRITE));
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+             const GValue *value, GParamSpec *pspec)
+{
+    GtkTreeModel *model;
+
+    switch (prop_id) {
+    case PROP_TOP:
+       model = gweather_timezone_model_new (g_value_get_boxed (value));
+       gtk_combo_box_set_model (GTK_COMBO_BOX (object), model);
+       g_object_unref (model);
+       gtk_combo_box_set_active (GTK_COMBO_BOX (object), 0);
+       break;
+
+    case PROP_TZID:
+       gweather_timezone_menu_set_tzid (GWEATHER_TIMEZONE_MENU (object),
+                                        g_value_get_string (value));
+       break;
+    default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       break;
+    }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+             GValue *value, GParamSpec *pspec)
+{
+    GWeatherTimezoneMenu *menu = GWEATHER_TIMEZONE_MENU (object);
+
+    switch (prop_id) {
+    case PROP_TZID:
+       g_value_set_string (value, gweather_timezone_menu_get_tzid (menu));
+       break;
+    default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       break;
+    }
+}
+
+enum {
+    GWEATHER_TIMEZONE_MENU_NAME,
+    GWEATHER_TIMEZONE_MENU_ZONE
+};
+
+static void
+changed (GtkComboBox *combo)
+{
+    GWeatherTimezoneMenu *menu = GWEATHER_TIMEZONE_MENU (combo);
+    GtkTreeIter iter;
+
+    if (menu->zone)
+       gweather_timezone_unref (menu->zone);
+
+    gtk_combo_box_get_active_iter (combo, &iter);
+    gtk_tree_model_get (gtk_combo_box_get_model (combo), &iter,
+                       GWEATHER_TIMEZONE_MENU_ZONE, &menu->zone,
+                       -1);
+
+    g_object_notify (G_OBJECT (combo), "tzid");
+}
+
+static void
+append_offset (GString *desc, int offset)
+{
+    int hours, minutes;
+
+    hours = offset / 60;
+    minutes = (offset > 0) ? offset % 60 : -offset % 60;
+
+    if (minutes)
+       g_string_append_printf (desc, "GMT%+d:%02d", hours, minutes);
+    else if (hours)
+       g_string_append_printf (desc, "GMT%+d", hours);
+    else
+       g_string_append (desc, "GMT");
+}
+
+static char *
+get_offset (GWeatherTimezone *zone)
+{
+    GString *desc;
+
+    desc = g_string_new (NULL);
+    append_offset (desc, gweather_timezone_get_offset (zone));
+    if (gweather_timezone_has_dst (zone)) {
+       g_string_append (desc, " / ");
+       append_offset (desc, gweather_timezone_get_dst_offset (zone));
+    }
+    return g_string_free (desc, FALSE);
+}
+
+static void
+insert_location (GtkTreeStore *store, GWeatherTimezone *zone, const char *loc_name, GtkTreeIter *parent)
+{
+    GtkTreeIter iter;
+    char *name, *offset;
+
+    offset = get_offset (zone);
+    name = g_strdup_printf ("%s <small>(%s)</small>",
+                            loc_name ? loc_name : gweather_timezone_get_name (zone),
+                            offset);
+    gtk_tree_store_append (store, &iter, parent);
+    gtk_tree_store_set (store, &iter,
+                        GWEATHER_TIMEZONE_MENU_NAME, name,
+                        GWEATHER_TIMEZONE_MENU_ZONE, zone,
+                        -1);
+    g_free (name);
+    g_free (offset);
+}
+
+static void
+insert_locations (GtkTreeStore *store, GWeatherLocation *loc)
+{
+    int i;
+
+    if (gweather_location_get_level (loc) < GWEATHER_LOCATION_COUNTRY) {
+       GWeatherLocation **children;
+
+       children = gweather_location_get_children (loc);
+       for (i = 0; children[i]; i++)
+           insert_locations (store, children[i]);
+    } else {
+       GWeatherTimezone **zones;
+       GtkTreeIter iter;
+
+       zones = gweather_location_get_timezones (loc);
+       if (zones[1]) {
+           gtk_tree_store_append (store, &iter, NULL);
+           gtk_tree_store_set (store, &iter,
+                               GWEATHER_TIMEZONE_MENU_NAME, gweather_location_get_name (loc),
+                               -1);
+
+           for (i = 0; zones[i]; i++) {
+                insert_location (store, zones[i], NULL, &iter);
+           }
+       } else if (zones[0]) {
+            insert_location (store, zones[0], gweather_location_get_name (loc), NULL);
+       }
+
+       gweather_location_free_timezones (loc, zones);
+    }
+}
+
+static GtkTreeModel *
+gweather_timezone_model_new (GWeatherLocation *top)
+{
+    g_autoptr(GWeatherLocation) world = NULL;
+    GtkTreeStore *store;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    char *unknown;
+    GWeatherTimezone *utc;
+
+    store = gtk_tree_store_new (2, G_TYPE_STRING, GWEATHER_TYPE_TIMEZONE);
+    model = GTK_TREE_MODEL (store);
+
+    unknown = g_markup_printf_escaped ("<i>%s</i>", C_("timezone", "Unknown"));
+
+    gtk_tree_store_append (store, &iter, NULL);
+    gtk_tree_store_set (store, &iter,
+                       GWEATHER_TIMEZONE_MENU_NAME, unknown,
+                       GWEATHER_TIMEZONE_MENU_ZONE, NULL,
+                       -1);
+
+    utc = gweather_timezone_get_utc ();
+    if (utc) {
+        insert_location (store, utc, NULL, NULL);
+        gweather_timezone_unref (utc);
+    }
+
+    gtk_tree_store_append (store, &iter, NULL);
+
+    g_free (unknown);
+
+    if (!top)
+       top = world = gweather_location_get_world ();
+
+    insert_locations (store, top);
+
+    return model;
+}
+
+static gboolean
+row_separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+    char *name;
+
+    gtk_tree_model_get (model, iter,
+                       GWEATHER_TIMEZONE_MENU_NAME, &name,
+                       -1);
+    if (name) {
+       g_free (name);
+       return FALSE;
+    } else
+       return TRUE;
+}
+
+static void
+is_sensitive (GtkCellLayout *cell_layout, GtkCellRenderer *cell,
+             GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
+{
+    gboolean sensitive;
+
+    sensitive = !gtk_tree_model_iter_has_child (tree_model, iter);
+    g_object_set (cell, "sensitive", sensitive, NULL);
+}
+
+/**
+ * gweather_timezone_menu_new:
+ * @top: the top-level location for the menu.
+ *
+ * Creates a new #GWeatherTimezoneMenu.
+ *
+ * @top will normally be the location returned from
+ * gweather_location_get_world(), but you can create a menu that
+ * contains the timezones from a smaller set of locations if you want.
+ *
+ * Return value: the new #GWeatherTimezoneMenu
+ **/
+GtkWidget *
+gweather_timezone_menu_new (GWeatherLocation *top)
+{
+    return g_object_new (GWEATHER_TYPE_TIMEZONE_MENU,
+                        "top", top,
+                        NULL);
+}
+
+typedef struct {
+    GtkComboBox *combo;
+    const char  *tzid;
+} SetTimezoneData;
+
+static gboolean
+check_tzid (GtkTreeModel *model, GtkTreePath *path,
+           GtkTreeIter *iter, gpointer data)
+{
+    SetTimezoneData *tzd = data;
+    GWeatherTimezone *zone;
+    gboolean ok;
+
+    gtk_tree_model_get (model, iter,
+                       GWEATHER_TIMEZONE_MENU_ZONE, &zone,
+                       -1);
+    if (!zone)
+       return FALSE;
+
+    if (!strcmp (gweather_timezone_get_tzid (zone), tzd->tzid)) {
+       gtk_combo_box_set_active_iter (tzd->combo, iter);
+       ok = TRUE;
+    } else
+       ok = FALSE;
+
+    gweather_timezone_unref (zone);
+    return ok;
+}
+
+/**
+ * gweather_timezone_menu_set_tzid:
+ * @menu: a #GWeatherTimezoneMenu
+ * @tzid: (allow-none): a tzdata id (eg, "America/New_York")
+ *
+ * Sets @menu to the given @tzid. If @tzid is %NULL, sets @menu to
+ * "Unknown".
+ **/
+void
+gweather_timezone_menu_set_tzid (GWeatherTimezoneMenu *menu,
+                                const char           *tzid)
+{
+    SetTimezoneData tzd;
+
+    g_return_if_fail (GWEATHER_IS_TIMEZONE_MENU (menu));
+
+    if (!tzid) {
+       gtk_combo_box_set_active (GTK_COMBO_BOX (menu), 0);
+       return;
+    }
+
+    tzd.combo = GTK_COMBO_BOX (menu);
+    tzd.tzid = tzid;
+    gtk_tree_model_foreach (gtk_combo_box_get_model (tzd.combo),
+                           check_tzid, &tzd);
+}
+
+/**
+ * gweather_timezone_menu_get_tzid:
+ * @menu: a #GWeatherTimezoneMenu
+ *
+ * Gets @menu's timezone id.
+ *
+ * Return value: (allow-none): @menu's tzid, or %NULL if no timezone
+ * is selected.
+ **/
+const char *
+gweather_timezone_menu_get_tzid (GWeatherTimezoneMenu *menu)
+{
+    g_return_val_if_fail (GWEATHER_IS_TIMEZONE_MENU (menu), NULL);
+
+    if (!menu->zone)
+       return NULL;
+    return gweather_timezone_get_tzid (menu->zone);
+}
+
diff --git a/modules/clock/gweather-timezone-menu.h b/modules/clock/gweather-timezone-menu.h
new file mode 100644
index 000000000..b5c4cc558
--- /dev/null
+++ b/modules/clock/gweather-timezone-menu.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* timezone-menu.h - Timezone-selecting menu
+ *
+ * Copyright 2008, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef GWEATHER_TIMEZONE_MENU_H
+#define GWEATHER_TIMEZONE_MENU_H 1
+
+#if !(defined(IN_GWEATHER_H) || defined(GWEATHER_COMPILATION))
+#error "gweather-timezone-menu.h must not be included individually, include gweather.h instead"
+#endif
+
+#include <gtk/gtk.h>
+#include <libgweather/gweather-location.h>
+
+typedef struct _GWeatherTimezoneMenu GWeatherTimezoneMenu;
+typedef struct _GWeatherTimezoneMenuClass GWeatherTimezoneMenuClass;
+
+#define GWEATHER_TYPE_TIMEZONE_MENU            (gweather_timezone_menu_get_type ())
+#define GWEATHER_TIMEZONE_MENU(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), 
GWEATHER_TYPE_TIMEZONE_MENU, GWeatherTimezoneMenu))
+#define GWEATHER_TIMEZONE_MENU_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
GWEATHER_TYPE_TIMEZONE_MENU, GWeatherTimezoneMenuClass))
+#define GWEATHER_IS_TIMEZONE_MENU(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
GWEATHER_TYPE_TIMEZONE_MENU))
+#define GWEATHER_IS_TIMEZONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GWEATHER_TYPE_TIMEZONE_MENU))
+#define GWEATHER_TIMEZONE_MENU_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GWEATHER_TYPE_TIMEZONE_MENU, GWeatherTimezoneMenuClass))
+
+struct _GWeatherTimezoneMenu {
+    GtkComboBox parent;
+
+    /*< private >*/
+    GWeatherTimezone *zone;
+};
+
+struct _GWeatherTimezoneMenuClass {
+    GtkComboBoxClass parent_class;
+
+};
+
+GType       gweather_timezone_menu_get_type         (void);
+GtkWidget  *gweather_timezone_menu_new              (GWeatherLocation     *top);
+void        gweather_timezone_menu_set_tzid         (GWeatherTimezoneMenu *menu,
+                                                    const char           *tzid);
+const char *gweather_timezone_menu_get_tzid         (GWeatherTimezoneMenu *menu);
+
+#endif


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