[evolution] cal-config-weather: Bundle WeatherLocationEntry



commit 8e53d136220ab1967bf6295278845d81239ac250
Author: Milan Crha <mcrha redhat com>
Date:   Thu Nov 11 15:44:16 2021 +0100

    cal-config-weather: Bundle WeatherLocationEntry
    
    The libgweather 4 has the widget removed, thus prepare for it and
    bundle its copy from the libgweather 3 into the sources.

 CMakeLists.txt                                     |   1 +
 po/POTFILES.in                                     |   1 +
 src/modules/cal-config-weather/CMakeLists.txt      |   5 +
 .../cal-config-weather/e-weather-location-entry.c  | 885 +++++++++++++++++++++
 .../cal-config-weather/e-weather-location-entry.h  |  49 ++
 .../evolution-cal-config-weather.c                 |   4 +-
 6 files changed, 944 insertions(+), 1 deletion(-)
---
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 02e9c86d74..45147c544d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -669,6 +669,7 @@ add_printable_option(ENABLE_WEATHER "Enable weather calendars" ON)
 
 if(ENABLE_WEATHER)
        pkg_check_modules_for_option(ENABLE_WEATHER "weather calendar" GWEATHER 
gweather-3.0>=${gweather_minimum_version})
+       pkg_check_modules_for_option(ENABLE_WEATHER "weather calendar" GEO 
geocode-glib-1.0>=${geocode_glib_minimum_version})
 endif(ENABLE_WEATHER)
 
 # maps in Contacts preview
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 741155c552..af0c350485 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -417,6 +417,7 @@ src/modules/cal-config-google/e-cal-config-google.c
 src/modules/cal-config-google/e-cal-config-gtasks.c
 src/modules/cal-config-google/e-google-chooser-button.c
 src/modules/cal-config-local/evolution-cal-config-local.c
+src/modules/cal-config-weather/e-weather-location-entry.c
 src/modules/cal-config-weather/evolution-cal-config-weather.c
 src/modules/cal-config-webcal/evolution-cal-config-webcal.c
 src/modules/cal-config-webdav-notes/evolution-cal-config-webdav-notes.c
diff --git a/src/modules/cal-config-weather/CMakeLists.txt b/src/modules/cal-config-weather/CMakeLists.txt
index b610b0b479..ef6db1720a 100644
--- a/src/modules/cal-config-weather/CMakeLists.txt
+++ b/src/modules/cal-config-weather/CMakeLists.txt
@@ -1,15 +1,20 @@
 set(extra_deps)
 set(sources
+       e-weather-location-entry.h
+       e-weather-location-entry.c
        evolution-cal-config-weather.c
 )
 set(extra_defines)
 set(extra_cflags
+       ${GEO_CFLAGS}
        ${GWEATHER_CFLAGS}
 )
 set(extra_incdirs
+       ${GEO_INCLUDE_DIRS}
        ${GWEATHER_INCLUDE_DIRS}
 )
 set(extra_ldflags
+       ${GEO_LDFLAGS}
        ${GWEATHER_LDFLAGS}
 )
 
diff --git a/src/modules/cal-config-weather/e-weather-location-entry.c 
b/src/modules/cal-config-weather/e-weather-location-entry.c
new file mode 100644
index 0000000000..f4fb48c403
--- /dev/null
+++ b/src/modules/cal-config-weather/e-weather-location-entry.c
@@ -0,0 +1,885 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Taken from libgweather 3, due to it being gone in the libgweather 4
+ *
+ * SPDX-FileCopyrightText: (C) 2008 Red Hat, Inc. (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-config.h"
+
+#include <string.h>
+#include <geocode-glib/geocode-glib.h>
+#include <gio/gio.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "e-weather-location-entry.h"
+
+/*
+ * SECTION:gweatherlocationentry
+ * @Title: EWeatherLocationEntry
+ *
+ * A subclass of #GtkSearchEntry that provides autocompletion on
+ * #GWeatherLocation<!-- -->s
+ */
+
+struct _EWeatherLocationEntryPrivate {
+       GWeatherLocation *location;
+       GWeatherLocation *top;
+       gboolean          show_named_timezones;
+       gboolean          custom_text;
+       GCancellable     *cancellable;
+       GtkTreeModel     *model;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (EWeatherLocationEntry, e_weather_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 (EWeatherLocationEntry *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_E_WEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME = 0,
+       LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCATION,
+       LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME,
+       LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME,
+       LOC_E_WEATHER_LOCATION_ENTRY_COL_ENGLISH_COMPARE_NAME,
+       LOC_E_WEATHER_LOCATION_ENTRY_NUM_COLUMNS
+};
+
+enum PLACE
+{
+       PLACE_E_WEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME = 0,
+       PLACE_E_WEATHER_LOCATION_ENTRY_COL_PLACE,
+       PLACE_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME,
+       PLACE_E_WEATHER_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 (EWeatherLocationEntry *entry);
+static void _no_matches (GtkEntryCompletion *completion, EWeatherLocationEntry *entry);
+
+static void
+e_weather_location_entry_init (EWeatherLocationEntry *entry)
+{
+       GtkEntryCompletion *completion;
+       EWeatherLocationEntryPrivate *priv;
+
+       priv = entry->priv = e_weather_location_entry_get_instance_private (entry);
+
+       completion = gtk_entry_completion_new ();
+
+       gtk_entry_completion_set_popup_set_width (completion, FALSE);
+       gtk_entry_completion_set_text_column (completion, LOC_E_WEATHER_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)
+{
+       EWeatherLocationEntry *entry;
+       EWeatherLocationEntryPrivate *priv;
+
+       entry = E_WEATHER_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 (e_weather_location_entry_parent_class)->finalize (object);
+}
+
+static void
+dispose (GObject *object)
+{
+       EWeatherLocationEntry *entry;
+       EWeatherLocationEntryPrivate *priv;
+
+       entry = E_WEATHER_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 (e_weather_location_entry_parent_class)->dispose (object);
+}
+
+static gint
+tree_compare_local_name (GtkTreeModel *model,
+                        GtkTreeIter  *a,
+                        GtkTreeIter  *b,
+                        gpointer      user_data)
+{
+       gchar *name_a = NULL, *name_b = NULL;
+       gint res;
+
+       gtk_tree_model_get (model, a,
+                           LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, &name_a,
+                           -1);
+       gtk_tree_model_get (model, b,
+                           LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, &name_b,
+                           -1);
+
+       res = g_utf8_collate (name_a, name_b);
+
+       g_free (name_a);
+       g_free (name_b);
+
+       return res;
+}
+
+
+static void
+constructed (GObject *object)
+{
+       EWeatherLocationEntry *entry;
+       GtkListStore *store = NULL;
+       GtkEntryCompletion *completion;
+
+       entry = E_WEATHER_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 (e_weather_location_entry_parent_class)->constructed (object);
+}
+
+static void
+e_weather_location_entry_class_init (EWeatherLocationEntryClass *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;
+
+       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));
+}
+
+static void
+set_property (GObject *object,
+             guint prop_id,
+             const GValue *value,
+             GParamSpec *pspec)
+{
+       EWeatherLocationEntry *entry = E_WEATHER_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:
+               e_weather_location_entry_set_location (E_WEATHER_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)
+{
+       EWeatherLocationEntry *entry = E_WEATHER_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 (EWeatherLocationEntry *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 (EWeatherLocationEntry *entry,
+                      GtkTreeModel          *model,
+                      GtkTreeIter           *iter,
+                      GWeatherLocation      *loc)
+{
+       EWeatherLocationEntryPrivate *priv;
+       gchar *name;
+
+       priv = entry->priv;
+
+       if (priv->location)
+               gweather_location_unref (priv->location);
+
+       g_return_if_fail (iter == NULL || loc == NULL);
+
+       if (iter) {
+               gtk_tree_model_get (model, iter,
+                                   LOC_E_WEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, &name,
+                                   LOC_E_WEATHER_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");
+}
+
+/*
+ * e_weather_location_entry_set_location:
+ * @entry: a #EWeatherLocationEntry
+ * @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
+e_weather_location_entry_set_location (EWeatherLocationEntry *entry,
+                                      GWeatherLocation *loc)
+{
+       GtkEntryCompletion *completion;
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GWeatherLocation *cmploc;
+
+       g_return_if_fail (E_WEATHER_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_E_WEATHER_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);
+}
+
+/*
+ * e_weather_location_entry_get_location:
+ * @entry: a #EWeatherLocationEntry
+ *
+ * Gets the location that was set by a previous call to
+ * e_weather_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 *
+e_weather_location_entry_get_location (EWeatherLocationEntry *entry)
+{
+       g_return_val_if_fail (E_WEATHER_IS_LOCATION_ENTRY (entry), NULL);
+
+       if (entry->priv->location)
+               return gweather_location_ref (entry->priv->location);
+       else
+               return NULL;
+}
+
+/*
+ * e_weather_location_entry_has_custom_text:
+ * @entry: a #EWeatherLocationEntry
+ *
+ * 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.
+ * e_weather_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
+e_weather_location_entry_has_custom_text (EWeatherLocationEntry *entry)
+{
+       g_return_val_if_fail (E_WEATHER_IS_LOCATION_ENTRY (entry), FALSE);
+
+       return entry->priv->custom_text;
+}
+
+/*
+ * e_weather_location_entry_set_city:
+ * @entry: a #EWeatherLocationEntry
+ * @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
+e_weather_location_entry_set_city (EWeatherLocationEntry *entry,
+                                  const gchar *city_name,
+                                  const gchar *code)
+{
+       GtkEntryCompletion *completion;
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GWeatherLocation *cmploc;
+       const gchar *cmpcode;
+       gchar *cmpname;
+
+       g_return_val_if_fail (E_WEATHER_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_E_WEATHER_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 gchar *parent_display_name,
+                          const gchar *parent_sort_local_name,
+                          const gchar *parent_compare_local_name,
+                          const gchar *parent_compare_english_name,
+                          gboolean show_named_timezones)
+{
+       GWeatherLocation *child = NULL;
+       gchar *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_E_WEATHER_LOCATION_ENTRY_COL_LOCATION, loc,
+                       LOC_E_WEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, display_name,
+                       LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, local_sort_name,
+                       LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME, local_compare_name,
+                       LOC_E_WEATHER_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_E_WEATHER_LOCATION_ENTRY_COL_LOCATION, loc,
+                               LOC_E_WEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, gweather_location_get_name 
(loc),
+                               LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, 
gweather_location_get_sort_name (loc),
+                               LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME, 
gweather_location_get_sort_name (loc),
+                               LOC_E_WEATHER_LOCATION_ENTRY_COL_ENGLISH_COMPARE_NAME, 
gweather_location_get_english_sort_name (loc),
+                               -1);
+               }
+               break;
+
+       case GWEATHER_LOCATION_DETACHED:
+               g_warn_if_reached ();
+               break;
+       }
+
+       g_clear_pointer (&child, gweather_location_unref);
+}
+
+static gchar *
+find_word (const gchar *full_name,
+          const gchar *word,
+          gint word_len,
+          gboolean whole_word,
+          gboolean is_first_word)
+{
+       gchar *p;
+
+       if (word == NULL || *word == '\0')
+               return NULL;
+
+       p = (gchar *) full_name - 1;
+       while ((p = strchr (p + 1, *word))) {
+               if (strncmp (p, word, word_len) != 0)
+                       continue;
+
+               if (p > (gchar *) full_name) {
+                       gchar *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 == (gchar *) 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 gchar *key,
+                   const gchar *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_warn_if_fail (len == strlen (key));
+               return find_word (name, key, len, FALSE, is_first_word) != NULL;
+       }
+}
+
+static gboolean
+matcher (GtkEntryCompletion *completion,
+        const gchar *key,
+        GtkTreeIter *iter,
+        gpointer user_data)
+{
+       gchar *local_compare_name, *english_compare_name;
+       gboolean match;
+
+       gtk_tree_model_get (gtk_entry_completion_get_model (completion), iter,
+               LOC_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_COMPARE_NAME, &local_compare_name,
+               LOC_E_WEATHER_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)
+{
+       EWeatherLocationEntryPrivate *priv;
+
+       priv = ((EWeatherLocationEntry *)entry)->priv;
+
+       if (model != priv->model) {
+               GeocodePlace *place;
+               gchar *display_name;
+               GeocodeLocation *loc;
+               GWeatherLocation *location;
+               GWeatherLocation *scope = NULL;
+               const gchar *country_code;
+
+               gtk_tree_model_get (model, iter,
+                       PLACE_E_WEATHER_LOCATION_ENTRY_COL_PLACE, &place,
+                       PLACE_E_WEATHER_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 (display_name, NULL,
+                       geocode_location_get_latitude (loc),
+                       geocode_location_get_longitude (loc));
+
+               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 gchar *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 gchar *display_name;
+       gchar *normalized;
+       gchar *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_E_WEATHER_LOCATION_ENTRY_COL_PLACE, place,
+               PLACE_E_WEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, display_name,
+               PLACE_E_WEATHER_LOCATION_ENTRY_COL_LOCAL_SORT_NAME, compare_name,
+               PLACE_E_WEATHER_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;
+       EWeatherLocationEntry *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);
+       } else {
+               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);
+       }
+
+       gtk_entry_completion_delete_action (completion, 0);
+       g_clear_object (&self->priv->cancellable);
+}
+
+static void
+_no_matches (GtkEntryCompletion *completion,
+            EWeatherLocationEntry *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);
+}
+
+/*
+ * e_weather_location_entry_new:
+ * @top: the top-level location for the entry.
+ *
+ * Creates a new #EWeatherLocationEntry.
+ *
+ * @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 #EWeatherLocationEntry
+ **/
+GtkWidget *
+e_weather_location_entry_new (GWeatherLocation *top)
+{
+       return g_object_new (E_WEATHER_TYPE_LOCATION_ENTRY,
+                            "top", top,
+                            NULL);
+}
diff --git a/src/modules/cal-config-weather/e-weather-location-entry.h 
b/src/modules/cal-config-weather/e-weather-location-entry.h
new file mode 100644
index 0000000000..dd6a15875d
--- /dev/null
+++ b/src/modules/cal-config-weather/e-weather-location-entry.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Taken from libgweather 3, due to it being gone in the libgweather 4
+ *
+ * SPDX-FileCopyrightText: (C) 2008 Red Hat, Inc. (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_WEATHER_LOCATION_ENTRY_H
+#define E_WEATHER_LOCATION_ENTRY_H 1
+
+#include <gtk/gtk.h>
+#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
+#include <libgweather/gweather.h>
+#undef GWEATHER_I_KNOW_THIS_IS_UNSTABLE
+
+typedef struct _EWeatherLocationEntry EWeatherLocationEntry;
+typedef struct _EWeatherLocationEntryClass EWeatherLocationEntryClass;
+typedef struct _EWeatherLocationEntryPrivate EWeatherLocationEntryPrivate;
+
+#define E_WEATHER_TYPE_LOCATION_ENTRY            (e_weather_location_entry_get_type ())
+#define E_WEATHER_LOCATION_ENTRY(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), 
E_WEATHER_TYPE_LOCATION_ENTRY, EWeatherLocationEntry))
+#define E_WEATHER_LOCATION_ENTRY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
E_WEATHER_TYPE_LOCATION_ENTRY, EWeatherLocationEntryClass))
+#define E_WEATHER_IS_LOCATION_ENTRY(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), 
E_WEATHER_TYPE_LOCATION_ENTRY))
+#define E_WEATHER_IS_LOCATION_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
E_WEATHER_TYPE_LOCATION_ENTRY))
+#define E_WEATHER_LOCATION_ENTRY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
E_WEATHER_TYPE_LOCATION_ENTRY, EWeatherLocationEntryClass))
+
+struct _EWeatherLocationEntry {
+       GtkSearchEntry parent;
+
+       /*< private >*/
+       EWeatherLocationEntryPrivate *priv;
+};
+
+struct _EWeatherLocationEntryClass {
+       GtkSearchEntryClass parent_class;
+};
+
+GType             e_weather_location_entry_get_type     (void);
+GtkWidget        *e_weather_location_entry_new          (GWeatherLocation      *top);
+void              e_weather_location_entry_set_location (EWeatherLocationEntry *entry,
+                                                        GWeatherLocation *loc);
+GWeatherLocation *e_weather_location_entry_get_location (EWeatherLocationEntry *entry);
+gboolean          e_weather_location_entry_has_custom_text
+                                                       (EWeatherLocationEntry *entry);
+gboolean          e_weather_location_entry_set_city     (EWeatherLocationEntry *entry,
+                                                        const gchar *city_name,
+                                                        const gchar *code);
+
+#endif
diff --git a/src/modules/cal-config-weather/evolution-cal-config-weather.c 
b/src/modules/cal-config-weather/evolution-cal-config-weather.c
index 1b8a2cfcbd..423d6e2357 100644
--- a/src/modules/cal-config-weather/evolution-cal-config-weather.c
+++ b/src/modules/cal-config-weather/evolution-cal-config-weather.c
@@ -26,6 +26,8 @@
 #include <libgweather/gweather.h>
 #undef GWEATHER_I_KNOW_THIS_IS_UNSTABLE
 
+#include "e-weather-location-entry.h"
+
 #include <e-util/e-util.h>
 
 #if defined(HAVE_NL_LANGINFO)
@@ -275,7 +277,7 @@ cal_config_weather_insert_widgets (ESourceConfigBackend *backend,
 
        world = gweather_location_get_world ();
 
-       widget = gweather_location_entry_new (world);
+       widget = e_weather_location_entry_new (world);
        e_source_config_insert_widget (
                config, scratch_source, _("Location:"), widget);
        context->location_entry = g_object_ref (widget);


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