[gtk/wip/chergert/spellcheck: 1/9] spellcheck: stub out spellcheck API




commit 89fe3888e4a8073569414e399f5f4177b7fb231a
Author: Christian Hergert <chergert redhat com>
Date:   Thu Mar 11 15:29:36 2021 -0800

    spellcheck: stub out spellcheck API

 config.h.meson                |   3 +
 gdk/gdkkeys.c                 |   2 +-
 gdk/wayland/gdkkeys-wayland.c |   2 +
 gtk/gtk.h                     |   1 +
 gtk/gtkspellcheck.c           | 463 ++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkspellcheck.h           |  69 +++++++
 gtk/gtkspellcheckprivate.h    |  93 +++++++++
 gtk/meson.build               |   3 +
 meson.build                   |  12 ++
 meson_options.txt             |   7 +
 tests/meson.build             |   1 +
 tests/testspelling.c          |  68 +++++++
 12 files changed, 723 insertions(+), 1 deletion(-)
---
diff --git a/config.h.meson b/config.h.meson
index 549a677379..c490d4e25c 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -30,6 +30,9 @@
 
 /* Define to 1 if you have the <dlfcn.h> header file. */
 #mesondefine HAVE_DLFCN_H
+#
+/* define if we have enchant */
+#mesondefine HAVE_ENCHANT
 
 /* Have the ffmpeg library */
 #mesondefine HAVE_FFMPEG
diff --git a/gdk/gdkkeys.c b/gdk/gdkkeys.c
index 90f42c5472..7c86715089 100644
--- a/gdk/gdkkeys.c
+++ b/gdk/gdkkeys.c
@@ -587,7 +587,7 @@ gdk_keymap_lookup_key (GdkKeymap          *keymap,
  *     (state & ~consumed & ALL_ACCELS_MASK) == GDK_CONTROL_MASK)
  *   // Control was pressed
  * ]|
- * 
+ *
  * An older interpretation @consumed_modifiers was that it contained
  * all modifiers that might affect the translation of the key;
  * this allowed accelerators to be stored with irrelevant consumed
diff --git a/gdk/wayland/gdkkeys-wayland.c b/gdk/wayland/gdkkeys-wayland.c
index 38f346c75d..ac8e133756 100644
--- a/gdk/wayland/gdkkeys-wayland.c
+++ b/gdk/wayland/gdkkeys-wayland.c
@@ -53,6 +53,8 @@ struct _GdkWaylandKeymap
 
   PangoDirection *direction;
   gboolean bidi;
+
+  char **languages;
 };
 
 struct _GdkWaylandKeymapClass
diff --git a/gtk/gtk.h b/gtk/gtk.h
index d5f50a86b3..ddd63a7a23 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -236,6 +236,7 @@
 #include <gtk/gtkstacksidebar.h>
 #include <gtk/gtksizegroup.h>
 #include <gtk/gtksizerequest.h>
+#include <gtk/gtkspellcheck.h>
 #include <gtk/gtkspinbutton.h>
 #include <gtk/gtkspinner.h>
 #include <gtk/gtkstack.h>
diff --git a/gtk/gtkspellcheck.c b/gtk/gtkspellcheck.c
new file mode 100644
index 0000000000..deb39d4fc6
--- /dev/null
+++ b/gtk/gtkspellcheck.c
@@ -0,0 +1,463 @@
+/*
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * 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 of the
+ * licence 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#ifdef HAVE_ENCHANT
+# include <enchant.h>
+#endif
+
+#include "gtkflattenlistmodel.h"
+#include "gtkspellcheckprivate.h"
+#include "gtkstringlist.h"
+
+#ifdef HAVE_ENCHANT
+static char       **gtk_enchant_list_languages   (void);
+static gboolean     gtk_enchant_supports         (const char       *code);
+static gboolean     gtk_enchant_contains_word    (GtkSpellLanguage *language,
+                                                  const char       *word,
+                                                  gssize            word_length);
+static void         gtk_enchant_init_language    (GtkSpellLanguage *language);
+static void         gtk_enchant_fini_language    (GtkSpellLanguage *language);
+static GListModel  *gtk_enchant_list_corrections (GtkSpellLanguage *language,
+                                                  const char       *word,
+                                                  gssize            word_length);
+#endif
+
+G_DEFINE_TYPE (GtkSpellChecker, gtk_spell_checker, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_LANGUAGES,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+static const GtkSpellProvider providers[] = {
+#ifdef HAVE_ENCHANT
+  {
+    .name             = "enchant",
+    .supports         = gtk_enchant_supports,
+    .contains_word    = gtk_enchant_contains_word,
+    .list_languages   = gtk_enchant_list_languages,
+    .init_language    = gtk_enchant_init_language,
+    .fini_language    = gtk_enchant_fini_language,
+    .list_corrections = gtk_enchant_list_corrections,
+  },
+#endif
+};
+
+static gboolean
+gtk_spell_provider_supports (const GtkSpellProvider *provider,
+                             const char             *code)
+{
+  char **codes;
+  gboolean ret = FALSE;
+
+  if (provider->supports != NULL)
+    return provider->supports (code);
+
+  /* Fallback based on listing languages */
+  if ((codes = provider->list_languages ()))
+    {
+      for (guint i = 0; codes[i]; i++)
+        {
+          if (g_strcmp0 (codes[i], code) == 0)
+            {
+              ret = TRUE;
+              break;
+            }
+        }
+    }
+
+  g_strfreev (codes);
+
+  return ret;
+}
+
+static void
+_gtk_source_language_free (GtkSpellLanguage *language)
+{
+  if (language->provider->fini_language)
+    language->provider->fini_language (language);
+
+  g_clear_pointer (&language->code, g_free);
+
+  language->provider = NULL;
+  language->native = NULL;
+
+  g_free (language);
+}
+
+static GtkSpellLanguage *
+_gtk_spell_language_new (const GtkSpellProvider *provider,
+                         const char             *code)
+{
+  GtkSpellLanguage *language;
+
+  g_assert (provider != NULL);
+  g_assert (code != NULL);
+
+  language = g_new0 (GtkSpellLanguage, 1);
+  language->provider = provider;
+  language->code = g_strdup (code);
+
+  if (provider->init_language != NULL)
+    provider->init_language (language);
+
+  return language;
+}
+
+static char **
+gtk_spell_checker_get_languages (GtkSpellChecker *self)
+{
+  GArray *ar = g_array_new (TRUE, FALSE, sizeof (char *));
+
+  for (guint i = 0; i < self->languages->len; i++)
+    {
+      GtkSpellLanguage *language = g_ptr_array_index (self->languages, i);
+      char *code = g_strdup (language->code);
+      g_array_append_val (ar, code);
+    }
+
+  return (char **)(gpointer)g_array_free (ar, FALSE);
+}
+
+static void
+gtk_spell_checker_set_languages (GtkSpellChecker    *self,
+                                 const char * const *languages)
+{
+  for (guint i = 0; languages[i]; i++)
+    {
+      const char *code = languages[i];
+
+      for (guint j = 0; j < G_N_ELEMENTS (providers); j++)
+        {
+          const GtkSpellProvider *provider = &providers[j];
+
+          if (gtk_spell_provider_supports (provider, code))
+            {
+              g_ptr_array_add (self->languages, _gtk_spell_language_new (provider, code));
+              break;
+            }
+        }
+    }
+}
+
+static void
+gtk_spell_checker_finalize (GObject *object)
+{
+  GtkSpellChecker *self = (GtkSpellChecker *)object;
+
+  g_clear_pointer (&self->languages, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (gtk_spell_checker_parent_class)->finalize (object);
+}
+
+static void
+gtk_spell_checker_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  GtkSpellChecker *self = GTK_SPELL_CHECKER (object);
+
+  switch (prop_id)
+    {
+    case PROP_LANGUAGES:
+      g_value_take_boxed (value, gtk_spell_checker_get_languages (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_spell_checker_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GtkSpellChecker *self = GTK_SPELL_CHECKER (object);
+
+  switch (prop_id)
+    {
+    case PROP_LANGUAGES:
+      gtk_spell_checker_set_languages (self, g_value_get_boxed (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_spell_checker_class_init (GtkSpellCheckerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_spell_checker_finalize;
+  object_class->get_property = gtk_spell_checker_get_property;
+  object_class->set_property = gtk_spell_checker_set_property;
+
+  properties [PROP_LANGUAGES] =
+    g_param_spec_boxed ("languages",
+                        "Languages",
+                        "The language codes to support",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE |
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gtk_spell_checker_init (GtkSpellChecker *self)
+{
+  self->languages = g_ptr_array_new_with_free_func ((GDestroyNotify)_gtk_source_language_free);
+}
+
+/**
+ * gtk_spell_checker_new_for_language:
+ * @language: (nullable): the language to support
+ *
+ * Creates a new #GtkSpellChecker which uses a dictionary available based
+ * on @language.
+ *
+ * Returns: (transfer full): a #GtkSpellChecker
+ *
+ * Since: 4.2
+ */
+GtkSpellChecker *
+gtk_spell_checker_new_for_language (const char *language)
+{
+  const char *languages[] = { language, NULL };
+
+  return g_object_new (GTK_TYPE_SPELL_CHECKER,
+                       "languages", languages,
+                       NULL);
+}
+
+/**
+ * gtk_spell_checker_new_for_languages:
+ * @languages: (nullable) (element-type utf8): the language codes to support
+ *
+ * Creates a new #GtkSpellChecker which uses dictionaries available based
+ * on @languages.
+ *
+ * Returns: (transfer full): a #GtkSpellChecker
+ *
+ * Since: 4.2
+ */
+GtkSpellChecker *
+gtk_spell_checker_new_for_languages (const char * const * languages)
+{
+  return g_object_new (GTK_TYPE_SPELL_CHECKER,
+                       "languages", languages,
+                       NULL);
+}
+
+const char * const *
+gtk_spell_checker_list_languages (void)
+{
+  static char **languages;
+
+  if (languages == NULL)
+    {
+      GHashTable *seen = g_hash_table_new (g_str_hash, g_str_equal);
+      GArray *ar = g_array_new (TRUE, FALSE, sizeof (char *));
+
+      for (guint i = 0; i < G_N_ELEMENTS (providers); i++)
+        {
+          const GtkSpellProvider *provider = &providers[i];
+          char **found = provider->list_languages ();
+
+          if (found == NULL)
+            continue;
+
+          for (guint j = 0; found[j]; j++)
+            {
+              if (!g_hash_table_contains (seen, found[j]))
+                {
+                  char *copy = g_strdup (found[j]);
+                  g_array_append_val (ar, copy);
+                  g_hash_table_add (seen, copy);
+                }
+            }
+
+          g_strfreev (found);
+        }
+
+      languages = (char **)(gpointer)g_array_free (ar, FALSE);
+      g_hash_table_unref (seen);
+    }
+
+  return (const char * const *)languages;
+}
+
+gboolean
+gtk_spell_checker_contains_word (GtkSpellChecker *self,
+                                 const char      *word,
+                                 gssize           word_length)
+{
+  g_return_val_if_fail (GTK_IS_SPELL_CHECKER (self), FALSE);
+
+  return _gtk_spell_checker_contains_word (self, word, word_length);
+}
+
+GListModel *
+gtk_spell_checker_list_corrections (GtkSpellChecker *self,
+                                    const char      *word,
+                                    gssize           word_length)
+{
+  GtkFlattenListModel *ret;
+  GListStore *store;
+
+  g_return_val_if_fail (GTK_IS_SPELL_CHECKER (self), NULL);
+  g_return_val_if_fail (word != NULL, NULL);
+
+  if (word_length < 0)
+    word_length = strlen (word);
+
+  if (self->languages->len == 0)
+    return G_LIST_MODEL (gtk_string_list_new (NULL));
+
+  store = g_list_store_new (G_TYPE_LIST_MODEL);
+
+  if (self->languages->len == 1)
+    {
+      GtkSpellLanguage *language = g_ptr_array_index (self->languages, 0);
+      return language->provider->list_corrections (language, word, word_length);
+    }
+
+  for (guint i = 0; i < self->languages->len; i++)
+    {
+      GtkSpellLanguage *language = g_ptr_array_index (self->languages, i);
+      GListModel *model = language->provider->list_corrections (language, word, word_length);
+
+      if (model != NULL)
+        {
+          g_list_store_append (store, model);
+          g_object_unref (model);
+        }
+    }
+
+  ret = gtk_flatten_list_model_new (G_LIST_MODEL (store));
+
+  g_object_unref (store);
+
+  return G_LIST_MODEL (ret);
+}
+
+GtkSpellChecker *
+gtk_spell_checker_get_default (void)
+{
+  static GtkSpellChecker *instance;
+
+  if (instance == NULL)
+    {
+      instance = gtk_spell_checker_new_for_language ("en_US");
+      g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
+    }
+
+  return instance;
+}
+
+#ifdef HAVE_ENCHANT
+static EnchantBroker *
+gtk_enchant_get_broker (void)
+{
+  static EnchantBroker *broker;
+
+  if (broker == NULL)
+    broker = enchant_broker_init ();
+
+  return broker;
+}
+
+static gboolean
+gtk_enchant_supports (const char *code)
+{
+  return enchant_broker_dict_exists (gtk_enchant_get_broker (), code);
+}
+
+static void
+gtk_enchant_list_languages_cb (const char * const  lang_tag,
+                               const char * const  provider_name,
+                               const char * const  provider_desc,
+                               const char * const  provider_file,
+                               void               *user_data)
+{
+  GArray *ar = user_data;
+  char *code = g_strdup (lang_tag);
+  g_array_append_val (ar, code);
+}
+
+static char **
+gtk_enchant_list_languages (void)
+{
+  EnchantBroker *broker = gtk_enchant_get_broker ();
+  GArray *ar = g_array_new (TRUE, FALSE, sizeof (char *));
+
+  enchant_broker_list_dicts (broker,
+                             gtk_enchant_list_languages_cb,
+                             ar);
+
+  return (char **)(gpointer)g_array_free (ar, FALSE);
+}
+
+static gboolean
+gtk_enchant_contains_word (GtkSpellLanguage *language,
+                           const char       *word,
+                           gssize            word_length)
+{
+  return enchant_dict_check (language->native, word, word_length) == 0;
+}
+
+static GListModel *
+gtk_enchant_list_corrections (GtkSpellLanguage *language,
+                              const char       *word,
+                              gssize            word_length)
+{
+  size_t count = 0;
+  char **ret = enchant_dict_suggest (language->native, word, word_length, &count);
+  GtkStringList *model = gtk_string_list_new ((const char * const *)ret);
+  enchant_dict_free_string_list (language->native, ret);
+  return G_LIST_MODEL (model);
+}
+
+static void
+gtk_enchant_init_language (GtkSpellLanguage *language)
+{
+  EnchantBroker *broker = gtk_enchant_get_broker ();
+  language->native = enchant_broker_request_dict (broker, language->code);
+}
+
+static void
+gtk_enchant_fini_language (GtkSpellLanguage *language)
+{
+  if (language->native != NULL)
+    {
+      enchant_broker_free_dict (gtk_enchant_get_broker (), language->native);
+      language->native = NULL;
+    }
+}
+#endif
diff --git a/gtk/gtkspellcheck.h b/gtk/gtkspellcheck.h
new file mode 100644
index 0000000000..5bfffcda47
--- /dev/null
+++ b/gtk/gtkspellcheck.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * 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 of the
+ * licence 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GTK_SPELL_CHECK_H__
+#define __GTK_SPELL_CHECK_H__
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SPELL_CHECKER (gtk_spell_checker_get_type())
+
+typedef enum _GtkSpellDictionary
+{
+  GTK_SPELL_DICTIONARY_SESSION,
+  GTK_SPELL_DICTIONARY_PERSONAL,
+} GtkSpellDictionary;
+
+GDK_AVAILABLE_IN_4_2
+G_DECLARE_FINAL_TYPE (GtkSpellChecker, gtk_spell_checker, GTK, SPELL_CHECKER, GObject)
+
+GDK_AVAILABLE_IN_4_2
+GtkSpellChecker    *gtk_spell_checker_get_default       (void);
+GDK_AVAILABLE_IN_4_2
+GtkSpellChecker    *gtk_spell_checker_new_for_language  (const char         *language);
+GDK_AVAILABLE_IN_4_2
+GtkSpellChecker    *gtk_spell_checker_new_for_languages (const char * const *languages);
+GDK_AVAILABLE_IN_4_2
+const char * const *gtk_spell_checker_list_languages    (void);
+GDK_AVAILABLE_IN_4_2
+gboolean            gtk_spell_checker_contains_word     (GtkSpellChecker    *self,
+                                                         const char         *word,
+                                                         gssize              word_length);
+GDK_AVAILABLE_IN_4_2
+GListModel         *gtk_spell_checker_list_corrections  (GtkSpellChecker    *self,
+                                                         const char         *word,
+                                                         gssize              word_length);
+GDK_AVAILABLE_IN_4_2
+void                gtk_spell_checker_add_word          (GtkSpellChecker    *self,
+                                                         GtkSpellDictionary  dictionary,
+                                                         const char         *word,
+                                                         gssize              word_length);
+GDK_AVAILABLE_IN_4_2
+void                gtk_spell_checker_set_correction    (GtkSpellChecker    *self,
+                                                         GtkSpellDictionary  dictionary,
+                                                         const char         *word,
+                                                         gssize              word_length,
+                                                         const char         *correction,
+                                                         gssize              correction_length);
+
+G_END_DECLS
+
+#endif /* __GTK_SPELL_CHECK_H__ */
diff --git a/gtk/gtkspellcheckprivate.h b/gtk/gtkspellcheckprivate.h
new file mode 100644
index 0000000000..71e4806e13
--- /dev/null
+++ b/gtk/gtkspellcheckprivate.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * 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 of the
+ * licence 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GTK_SPELL_CHECK_PRIVATE_H__
+#define __GTK_SPELL_CHECK_PRIVATE_H__
+
+#include "gtkspellcheck.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GtkSpellProvider GtkSpellProvider;
+typedef struct _GtkSpellLanguage GtkSpellLanguage;
+
+struct _GtkSpellProvider
+{
+  const char    *name;
+  gboolean     (*supports)         (const char       *code);
+  char       **(*list_languages)   (void);
+  GListModel  *(*list_corrections) (GtkSpellLanguage *language,
+                                    const char       *word,
+                                    gssize            word_length);
+  void         (*init_language)    (GtkSpellLanguage *language);
+  void         (*fini_language)    (GtkSpellLanguage *language);
+  gboolean     (*contains_word)    (GtkSpellLanguage *language,
+                                    const char       *word,
+                                    gssize            word_length);
+};
+
+struct _GtkSpellLanguage
+{
+  const GtkSpellProvider *provider;
+  char *code;
+  gpointer native;
+};
+
+struct _GtkSpellChecker
+{
+  GObject    parent_instance;
+  GPtrArray *languages;
+};
+
+struct _GtkSpellCorrection
+{
+  GObject parent_instance;
+  char *text;
+};
+
+static inline gboolean
+_gtk_spell_language_contains_word (GtkSpellLanguage *language,
+                                   const char       *word,
+                                   gssize            word_length)
+{
+  return language->provider->contains_word (language, word, word_length);
+}
+
+static inline gboolean
+_gtk_spell_checker_contains_word (GtkSpellChecker *checker,
+                                  const char      *word,
+                                  gssize           word_length)
+{
+  if (word_length < 0)
+    word_length = strlen (word);
+
+  for (guint i = 0; i < checker->languages->len; i++)
+    {
+      GtkSpellLanguage *language = g_ptr_array_index (checker->languages, i);
+
+      if (_gtk_spell_language_contains_word (language, word, word_length))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+G_END_DECLS
+
+#endif /* __GTK_SPELL_CHECKER_PRIVATE_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index fe0ec456d9..4cf4c253a6 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -377,6 +377,7 @@ gtk_public_sources = files([
   'gtksnapshot.c',
   'gtksorter.c',
   'gtksortlistmodel.c',
+  'gtkspellcheck.c',
   'gtkspinbutton.c',
   'gtkspinner.c',
   'gtkstack.c',
@@ -648,6 +649,7 @@ gtk_public_headers = files([
   'gtksnapshot.h',
   'gtksorter.h',
   'gtksortlistmodel.h',
+  'gtkspellcheck.h',
   'gtkspinbutton.h',
   'gtkspinner.h',
   'gtkstack.h',
@@ -1001,6 +1003,7 @@ gtk_deps = [
   epoxy_dep,
   libm,
   graphene_dep,
+  libenchant_dep,
 ]
 
 if harfbuzz_dep.found() and pangoft_dep.found()
diff --git a/meson.build b/meson.build
index 5e505a9b94..67039d1508 100644
--- a/meson.build
+++ b/meson.build
@@ -23,6 +23,7 @@ epoxy_req          = '>= 1.4'
 cloudproviders_req = '>= 0.3.1'
 xkbcommon_req      = '>= 0.2.0'
 sysprof_req        = '>= 3.38.0'
+enchant_req        = '>= 2.2.12'
 
 gnome = import('gnome')
 pkg_config = import('pkgconfig')
@@ -667,6 +668,16 @@ else
   profiler_enabled = false
 endif
 
+# enchant spellcheck support
+spellcheck_backends = []
+if not get_option('spell-enchant').disabled()
+  libenchant_dep = dependency('enchant-2', version: enchant_req, required: get_option('spell-enchant'))
+  if libenchant_dep.found()
+    spellcheck_backends += ['enchant']
+    cdata.set10('HAVE_ENCHANT', true)
+  endif
+endif
+
 graphene_dep_type = graphene_dep.type_name()
 if graphene_dep_type == 'pkgconfig'
   graphene_has_sse2 = graphene_dep.get_pkgconfig_variable('graphene_has_sse2') == '1'
@@ -827,6 +838,7 @@ endif
 summary('Display backends', display_backends)
 summary('Print backends', print_backends)
 summary('Media backends', media_backends)
+summary('Spell backends', spellcheck_backends)
 
 summary('Vulkan support', vulkan_dep.found(), section: 'Features')
 summary('Cloud support', cloudproviders_dep.found(), section: 'Features')
diff --git a/meson_options.txt b/meson_options.txt
index 16a1cbd9fe..c749529be1 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -49,6 +49,13 @@ option('print-cloudprint',
        value: 'auto',
        description : 'Build the cloudprint print backend')
 
+# Spell Check Providers
+
+option('spell-enchant',
+       type: 'feature',
+       value: 'auto',
+       description : 'Build the enchant spellcheck backend')
+
 # Optional features
 
 option('vulkan',
diff --git a/tests/meson.build b/tests/meson.build
index c0e5f836b0..dbe4d62e19 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -68,6 +68,7 @@ gtk_tests = [
   ['testscale'],
   ['testselectionmode'],
   ['testsounds'],
+  ['testspelling'],
   ['testspinbutton'],
   ['testtreechanging'],
   ['testtreednd'],
diff --git a/tests/testspelling.c b/tests/testspelling.c
new file mode 100644
index 0000000000..4159e5ddec
--- /dev/null
+++ b/tests/testspelling.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2021 Christian Hergert <chergert redhat com>
+ *
+ * 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 of the
+ * licence 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <gtk/gtk.h>
+
+int
+main (int argc, char *argv[])
+{
+  GtkSpellChecker *checker;
+
+  gtk_init ();
+
+  checker = gtk_spell_checker_get_default ();
+
+  for (guint i = 1; i < argc; i++)
+    {
+      const char *word = argv[i];
+      GListModel *corrections;
+      guint n_items = 0;
+
+      if (gtk_spell_checker_contains_word (checker, word, -1))
+        {
+          g_print ("Dictionary contains the word “%s”\n", word);
+          continue;
+        }
+
+      if ((corrections = gtk_spell_checker_list_corrections (checker, word, -1)))
+        {
+          n_items = g_list_model_get_n_items (G_LIST_MODEL (corrections));
+
+          if (n_items > 0)
+            g_print ("Corrections for “%s”:\n", word);
+
+          for (guint j = 0; j < n_items; j++)
+            {
+              GtkStringObject *correction = g_list_model_get_item (corrections, j);
+              const char *text = gtk_string_object_get_string (correction);
+
+              g_print ("  %s\n", text);
+            }
+
+          g_object_unref (corrections);
+        }
+
+      if (n_items == 0)
+        g_print ("No corrections for “%s” were found.\n", word);
+    }
+
+  g_assert_finalize_object (checker);
+
+  return 0;
+}


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