[gtk/wip/otte/listview: 1/139] Add GtkFontList



commit e2db67efefd6da7f8c564b2e6485c35fcbf53cfe
Author: Benjamin Otte <otte redhat com>
Date:   Sun Nov 10 00:06:44 2019 +0100

    Add GtkFontList

 docs/reference/gtk/gtk4-docs.xml     |   1 +
 docs/reference/gtk/gtk4-sections.txt |  22 ++
 gtk/gtk.h                            |   1 +
 gtk/gtkfontlist.c                    | 518 +++++++++++++++++++++++++++++++++++
 gtk/gtkfontlist.h                    |  60 ++++
 gtk/meson.build                      |   2 +
 testsuite/gtk/fontlist.c             | 196 +++++++++++++
 testsuite/gtk/meson.build            |   1 +
 8 files changed, 801 insertions(+)
---
diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml
index 016a5263ed..8c0ef6580f 100644
--- a/docs/reference/gtk/gtk4-docs.xml
+++ b/docs/reference/gtk/gtk4-docs.xml
@@ -54,6 +54,7 @@
       <xi:include href="xml/gtknoselection.xml" />
       <xi:include href="xml/gtksingleselection.xml" />
       <xi:include href="xml/gtkdirectorylist.xml" />
+      <xi:include href="xml/gtkfontlist.xml" />
     </chapter>
 
     <chapter id="Application">
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index ad128daf2b..0ca38e2601 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -1347,6 +1347,28 @@ GTK_DIRECTORY_LIST_GET_CLASS
 gtk_directory_list_get_type
 </SECTION>
 
+<SECTION>
+<FILE>gtkfontlist</FILE>
+<TITLE>GtkFontList</TITLE>
+GtkFontList
+gtk_font_list_new
+gtk_font_list_get_display
+gtk_font_list_set_display
+gtk_font_list_get_font_map
+gtk_font_list_set_font_map
+gtk_font_list_get_families_only
+gtk_font_list_set_families_only
+<SUBSECTION Standard>
+GTK_FONT_LIST
+GTK_IS_FONT_LIST
+GTK_TYPE_FONT_LIST
+GTK_FONT_LIST_CLASS
+GTK_IS_FONT_LIST_CLASS
+GTK_FONT_LIST_GET_CLASS
+<SUBSECTION Private>
+gtk_font_list_get_type
+</SECTION>
+
 <SECTION>
 <FILE>gtkfilterlistmodel</FILE>
 <TITLE>GtkFilterListModel</TITLE>
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 70cc4a40b8..6c4d9ea147 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -121,6 +121,7 @@
 #include <gtk/gtkfontchooser.h>
 #include <gtk/gtkfontchooserdialog.h>
 #include <gtk/gtkfontchooserwidget.h>
+#include <gtk/gtkfontlist.h>
 #include <gtk/gtkframe.h>
 #include <gtk/gtkgesture.h>
 #include <gtk/gtkgestureclick.h>
diff --git a/gtk/gtkfontlist.c b/gtk/gtkfontlist.c
new file mode 100644
index 0000000000..f3b735b3c6
--- /dev/null
+++ b/gtk/gtkfontlist.c
@@ -0,0 +1,518 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkfontlist.h"
+
+#include "gtkintl.h"
+#include "gtkprivate.h"
+#include "gtksettings.h"
+
+/**
+ * SECTION:gtkfontlist
+ * @title: GtkFontList
+ * @short_description: A list model for all installed fonts
+ * @see_also: #GListModel, #GtkFontChooser
+ *
+ * #GtkFontList is a list model of #PangoFontFace that contains the current
+ * fonts installed on the system. It updates itself automatically when new
+ * fonts get installed.
+ *
+ * This list is used by GTK's #GtkFontChooser implementations, so the fonts
+ * listed by either do match.
+ */
+
+enum {
+  PROP_0,
+  PROP_DISPLAY,
+  PROP_FAMILIES_ONLY,
+  PROP_FONT_MAP,
+  NUM_PROPERTIES
+};
+
+struct _GtkFontList
+{
+  GObject parent_instance;
+
+  GdkDisplay *display;
+  PangoFontMap *font_map; /* may be NULL */
+  gboolean families_only;
+
+  GSequence *faces; /* Use GPtrArray or GListStore here? */
+};
+
+struct _GtkFontListClass
+{
+  GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static GType
+gtk_font_list_get_item_type (GListModel *list)
+{
+  return PANGO_TYPE_FONT_FACE;
+}
+
+static guint
+gtk_font_list_get_n_items (GListModel *list)
+{
+  GtkFontList *self = GTK_FONT_LIST (list);
+
+  return g_sequence_get_length (self->faces);
+}
+
+static gpointer
+gtk_font_list_get_item (GListModel *list,
+                        guint       position)
+{
+  GtkFontList *self = GTK_FONT_LIST (list);
+  GSequenceIter *iter;
+
+  iter = g_sequence_get_iter_at_pos (self->faces, position);
+
+  if (g_sequence_iter_is_end (iter))
+    return NULL;
+  else
+    return g_object_ref (g_sequence_get (iter));
+}
+
+static void
+gtk_font_list_model_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gtk_font_list_get_item_type;
+  iface->get_n_items = gtk_font_list_get_n_items;
+  iface->get_item = gtk_font_list_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkFontList, gtk_font_list, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_font_list_model_init))
+
+static void
+gtk_font_list_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GtkFontList *self = GTK_FONT_LIST (object);
+
+  switch (prop_id)
+    {
+    case PROP_DISPLAY:
+      gtk_font_list_set_display (self, g_value_get_object (value));
+      break;
+
+    case PROP_FAMILIES_ONLY:
+      gtk_font_list_set_families_only (self, g_value_get_boolean(value));
+      break;
+
+    case PROP_FONT_MAP:
+      gtk_font_list_set_font_map (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void 
+gtk_font_list_get_property (GObject     *object,
+                            guint        prop_id,
+                            GValue      *value,
+                            GParamSpec  *pspec)
+{
+  GtkFontList *self = GTK_FONT_LIST (object);
+
+  switch (prop_id)
+    {
+    case PROP_DISPLAY:
+      g_value_set_object (value, self->display);
+      break;
+
+    case PROP_FAMILIES_ONLY:
+      g_value_set_boolean (value, self->families_only);
+      break;
+
+    case PROP_FONT_MAP:
+      g_value_set_object (value, self->font_map);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static int
+gtk_font_list_compare_families (PangoFontFamily *a,
+                                PangoFontFamily *b)
+{
+  const char *a_name = pango_font_family_get_name (a);
+  const char *b_name = pango_font_family_get_name (b);
+
+  return g_utf8_collate (a_name, b_name);
+}
+
+static int
+gtk_font_list_compare_families_qsort (const void *a,
+                                      const void *b)
+{
+  return gtk_font_list_compare_families (*(PangoFontFamily **) a, *(PangoFontFamily **) b);
+}
+
+static void
+gtk_font_list_rescan (GtkFontList *self)
+{
+  gint n_families, i;
+  PangoFontFamily **families;
+  PangoFontMap *font_map;
+  GSequenceIter *iter;
+  guint first, last, n_before, n_after;
+#define MARK_CHANGED(iter,insert) G_STMT_START {\
+  guint iter_pos = g_sequence_iter_get_position (iter); \
+  first = MIN (first, iter_pos); \
+  last = iter_pos + ((insert) ? 1 : 0); \
+} G_STMT_END
+
+  if (self->font_map)
+    font_map = self->font_map;
+  else
+    font_map = pango_cairo_font_map_get_default ();
+  pango_font_map_list_families (font_map, &families, &n_families);
+
+  qsort (families, n_families, sizeof (PangoFontFamily *), gtk_font_list_compare_families_qsort);
+
+  n_before = g_sequence_get_length (self->faces);
+
+  iter = g_sequence_get_begin_iter (self->faces);
+  first = G_MAXUINT;
+  last = 0;
+
+  /* Iterate over families and faces */
+  for (i = 0; i < n_families; i++)
+    {
+      PangoFontFace **faces;
+      int j, n_faces;
+
+      pango_font_family_list_faces (families[i], &faces, &n_faces);
+      while (!g_sequence_iter_is_end (iter) && 
+             gtk_font_list_compare_families (families[i], pango_font_face_get_family (g_sequence_get 
(iter))) > 0)
+        {
+          GSequenceIter *remove = iter;
+          MARK_CHANGED (iter, FALSE);
+          iter = g_sequence_iter_next (iter);
+          g_sequence_remove (remove);
+        }
+
+      if (self->families_only)
+        n_faces = MIN (1, n_faces);
+
+      for (j = 0; j < n_faces; j++)
+        {
+          if (g_sequence_iter_is_end (iter))
+            {
+              /* nothing to see */
+            }
+          else if (faces[j] == g_sequence_get (iter))
+            {
+              iter = g_sequence_iter_next (iter);
+              continue;
+            }
+          else
+            {
+              while (!g_sequence_iter_is_end (iter) && 
+                     gtk_font_list_compare_families (families[i], pango_font_face_get_family (g_sequence_get 
(iter))) >= 0)
+                {
+                  GSequenceIter *remove = iter;
+                  MARK_CHANGED (iter, FALSE);
+                  iter = g_sequence_iter_next (iter);
+                  g_sequence_remove (remove);
+                }
+            }
+          MARK_CHANGED (iter, TRUE);
+          g_sequence_insert_before (iter, g_object_ref (faces[j]));
+        }
+
+      g_free (faces);
+    }
+
+  g_free (families);
+
+  if (!g_sequence_iter_is_end (iter))
+    g_sequence_remove_range (iter, g_sequence_get_end_iter (self->faces));
+  n_after = g_sequence_get_length (self->faces);
+
+  if (first <= last)
+    {
+      last = n_after - MIN (n_after, last + 1);
+      g_list_model_items_changed (G_LIST_MODEL (self),
+                                  first,
+                                  n_before - first - last,
+                                  n_after - first - last);
+    }
+  else if (n_after > n_before)
+    {
+      g_list_model_items_changed (G_LIST_MODEL (self), n_before, 0, n_after - n_before);
+    }
+  else if (n_after < n_before)
+    {
+      g_list_model_items_changed (G_LIST_MODEL (self), n_after, n_before - n_after, 0);
+    }
+}
+
+static void
+gtk_font_list_unset_display (GtkFontList *self)
+{
+  GtkSettings *settings;
+
+  if (self->display == NULL)
+    return;
+
+  settings = gtk_settings_get_for_display (self->display);
+  g_signal_handlers_disconnect_by_func (settings, gtk_font_list_rescan, self);
+  g_clear_object (&self->display);
+}
+
+static void
+gtk_font_list_dispose (GObject *object)
+{
+  GtkFontList *self = GTK_FONT_LIST (object);
+
+  gtk_font_list_unset_display (self);
+
+  g_clear_object (&self->font_map);
+
+  G_OBJECT_CLASS (gtk_font_list_parent_class)->dispose (object);
+}
+
+static void
+gtk_font_list_class_init (GtkFontListClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->set_property = gtk_font_list_set_property;
+  gobject_class->get_property = gtk_font_list_get_property;
+  gobject_class->dispose = gtk_font_list_dispose;
+
+  /**
+   * GtkFontList:display:
+   *
+   * The display to list fonts for
+   */
+  properties[PROP_DISPLAY] =
+      g_param_spec_object ("display",
+                           P_("Display"),
+                           P_("Display to list fonts for"),
+                           GDK_TYPE_DISPLAY,
+                           GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkFontList:font-map:
+   *
+   * Font map to use
+   */
+  properties[PROP_FONT_MAP] =
+      g_param_spec_object ("font-map",
+                          P_("Font map"),
+                          P_("Font map to use"),
+                          PANGO_TYPE_FONT_MAP,
+                          GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkFontList:families-only:
+   *
+   * %TRUE if only one face per font family is loaded
+   */
+  properties[PROP_FAMILIES_ONLY] =
+      g_param_spec_boolean ("families-only",
+                            P_("Families only"),
+                            P_("Set to load only one face per family"),
+                            FALSE,
+                            GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void
+gtk_font_list_init (GtkFontList *self)
+{
+  self->faces = g_sequence_new (g_object_unref);
+}
+
+/**
+ * gtk_font_list_new:
+ * @file: (allow-none): The file to query
+ * @attributes: (allow-none): The attributes to query with
+ *
+ * Creates a new #GtkFontList querying the given @file with the given
+ * @attributes.
+ *
+ * Returns: a new #GtkFontList
+ **/
+GtkFontList *
+gtk_font_list_new (void)
+{
+  return g_object_new (GTK_TYPE_FONT_LIST,
+                       NULL);
+}
+
+/**
+ * gtk_font_list_set_display:
+ * @self: a #GtkFontList
+ * @display: (allow-none) (transfer none): the #GdkDisplay to load fonts
+ *     for or %NULL to use gdk_display_get_default()
+ *
+ * Sets the @display to enumerate fonts for.
+ *
+ * If @display is %NULL, then the defualt display will be used instead.
+ */
+void
+gtk_font_list_set_display (GtkFontList *self,
+                           GdkDisplay  *display)
+{
+  GtkSettings *settings;
+
+  g_return_if_fail (GTK_IS_FONT_LIST (self));
+  g_return_if_fail (display == NULL || GDK_IS_DISPLAY (display));
+
+  if (display == NULL)
+    display = gdk_display_get_default ();
+
+  if (self->display == display)
+    return;
+
+  gtk_font_list_unset_display (self);
+
+  self->display = g_object_ref (display);
+
+  settings = gtk_settings_get_for_display (self->display);
+  g_signal_connect_swapped (settings,
+                            "notify::gtk-fontconfig-timestamp",
+                            G_CALLBACK (gtk_font_list_rescan),
+                            self);
+
+  gtk_font_list_rescan (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DISPLAY]);
+}
+
+/**
+ * gtk_font_list_get_display:
+ * @self: a #GtkFontList
+ *
+ * Gets the display fonts are listed for.
+ *
+ * Returns: (transfer none): The display fonts are enumerated for
+ **/
+GdkDisplay *
+gtk_font_list_get_display (GtkFontList *self)
+{
+  g_return_val_if_fail (GTK_IS_FONT_LIST (self), NULL);
+
+  return self->display;
+}
+
+/**
+ * gtk_font_list_set_families_only:
+ * @self: a #GtkFontList
+ * @families_only: %TRUE to only load one face per family
+ *
+ * Set to %TRUE to only list one face per #PangoFontFamily. If set
+ * to %FALSE (the default), @self with list all faces for each family.
+ */
+void
+gtk_font_list_set_families_only (GtkFontList *self,
+                                 gboolean     families_only)
+{
+  g_return_if_fail (GTK_IS_FONT_LIST (self));
+
+  if (self->families_only == families_only)
+    return;
+
+  self->families_only = families_only;
+
+  gtk_font_list_rescan (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FAMILIES_ONLY]);
+}
+
+/**
+ * gtk_font_list_get_families_only:
+ * @self: a #GtkFontList
+ *
+ * Gets the value set via gtk_font_list_set_families_only().
+ *
+ * Returns: %TRUE if only one face is listed for each #PangoFontFamily
+ */
+gboolean
+gtk_font_list_get_families_only (GtkFontList *self)
+{
+  g_return_val_if_fail (GTK_IS_FONT_LIST (self), FALSE);
+
+  return self->families_only;
+}
+
+/**
+ * gtk_font_list_set_font_map:
+ * @self: a #GtkFontList
+ * @font_map: (nullable) (transfer none): A custom #PangoFontMap to use
+ *     for enumerating fonts or %NULL to use the default
+ *
+ * Sets the #PangoFontMap used to enumerate fonts.
+ * If set to %NULL (the default), GTK will use the display's default way
+ * to enumerate fonts.
+ *
+ * Note that even when a custom @font_map is loaded, GTK will still
+ * monitor the GtkFontMap::display for changes to installed fonts,
+ * because it assumes that the @font_map still contains those fonts.
+ */
+void
+gtk_font_list_set_font_map (GtkFontList  *self,
+                            PangoFontMap *font_map)
+{
+  g_return_if_fail (GTK_IS_FONT_LIST (self));
+  g_return_if_fail (font_map == NULL || PANGO_IS_FONT_MAP (font_map));
+
+  if (g_set_object (&self->font_map, font_map))
+    return;
+
+  gtk_font_list_rescan (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FONT_MAP]);
+}
+
+/**
+ * gtk_font_list_get_font_map:
+ * @self: a #GtkFontList
+ *
+ * Returns the #PangoFontMap set via gtk_font_list_set_font_map().
+ *
+ * Returns: (nullable) (transfer none): The custom font map used by @self
+ *     or %NULL if no custom font map is in use.
+ */
+PangoFontMap *
+gtk_font_list_get_font_map (GtkFontList *self)
+{
+  g_return_val_if_fail (GTK_IS_FONT_LIST (self), NULL);
+
+  return self->font_map;
+}
+
diff --git a/gtk/gtkfontlist.h b/gtk/gtkfontlist.h
new file mode 100644
index 0000000000..7e9a7e6cc3
--- /dev/null
+++ b/gtk/gtkfontlist.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_FONT_LIST_H__
+#define __GTK_FONT_LIST_H__
+
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_FONT_LIST (gtk_font_list_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkFontList, gtk_font_list, GTK, FONT_LIST, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkFontList *           gtk_font_list_new                       (void);
+
+GDK_AVAILABLE_IN_ALL
+GdkDisplay *            gtk_font_list_get_display               (GtkFontList            *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_font_list_set_display               (GtkFontList            *self,
+                                                                 GdkDisplay             *display);
+GDK_AVAILABLE_IN_ALL
+PangoFontMap *          gtk_font_list_get_font_map              (GtkFontList            *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_font_list_set_font_map              (GtkFontList            *self,
+                                                                 PangoFontMap           *font_map);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_font_list_get_families_only         (GtkFontList            *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_font_list_set_families_only         (GtkFontList            *self,
+                                                                 gboolean                families_only);
+
+
+G_END_DECLS
+
+#endif /* __GTK_FONT_LIST_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 02574dc6fb..e5089653b5 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -246,6 +246,7 @@ gtk_public_sources = files([
   'gtkfontchooserdialog.c',
   'gtkfontchooserutils.c',
   'gtkfontchooserwidget.c',
+  'gtkfontlist.c',
   'gtkframe.c',
   'gtkgesture.c',
   'gtkgesturedrag.c',
@@ -511,6 +512,7 @@ gtk_public_headers = files([
   'gtkfontchooser.h',
   'gtkfontchooserdialog.h',
   'gtkfontchooserwidget.h',
+  'gtkfontlist.h',
   'gtkframe.h',
   'gtkgesture.h',
   'gtkgesturedrag.h',
diff --git a/testsuite/gtk/fontlist.c b/testsuite/gtk/fontlist.c
new file mode 100644
index 0000000000..55b7d93d05
--- /dev/null
+++ b/testsuite/gtk/fontlist.c
@@ -0,0 +1,196 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <locale.h>
+
+#include <gtk/gtk.h>
+
+static GQuark changes_quark;
+
+static char *
+model_to_string (GListModel *model)
+{
+  GString *string = g_string_new (NULL);
+  guint i;
+
+  for (i = 0; i < g_list_model_get_n_items (model); i++)
+    {
+      PangoFontFace *face = g_list_model_get_item (model, i);
+      if (i > 0)
+        g_string_append (string, ", ");
+      g_string_append_printf (string, "%s-%s",
+                              pango_font_family_get_name (pango_font_face_get_family (face)),
+                              pango_font_face_get_face_name (face));
+      g_object_unref (face);
+    }
+
+  return g_string_free (string, FALSE);
+}
+
+#define assert_model(model1, model2) G_STMT_START{ \
+  char *s1 = model_to_string (G_LIST_MODEL (model1)); \
+  char *s2 = model_to_string (G_LIST_MODEL (model2)); \
+  if (!g_str_equal (s1, s2)) \
+     g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+         #model1 " == " #model2, s1, "==", s2); \
+  g_free (s1); \
+  g_free (s2); \
+}G_STMT_END
+
+#define assert_changes(model, expected) G_STMT_START{ \
+  GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
+  if (!g_str_equal (changes->str, expected)) \
+     g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+         #model " == " #expected, changes->str, "==", expected); \
+  g_string_set_size (changes, 0); \
+}G_STMT_END
+
+#define assert_no_changes(model) assert_changes(model, "")
+#define assert_some_changes(model) G_STMT_START { \
+  GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
+  if (changes->len == 0) \
+     g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+         "changes to model were expected, but none happened."); \
+  g_string_set_size (changes, 0); \
+}G_STMT_END
+
+static void
+items_changed (GListModel *model,
+               guint       position,
+               guint       removed,
+               guint       added,
+               GString    *changes)
+{
+  g_assert (removed != 0 || added != 0);
+
+  if (changes->len)
+    g_string_append (changes, ", ");
+
+  if (removed == 1 && added == 0)
+    {
+      g_string_append_printf (changes, "-%u", position);
+    }
+  else if (removed == 0 && added == 1)
+    {
+      g_string_append_printf (changes, "+%u", position);
+    }
+  else
+    {
+      g_string_append_printf (changes, "%u", position);
+      if (removed > 0)
+        g_string_append_printf (changes, "-%u", removed);
+      if (added > 0)
+        g_string_append_printf (changes, "+%u", added);
+    }
+}
+
+static void
+free_changes (gpointer data)
+{
+  GString *changes = data;
+
+  /* all changes must have been checked via assert_changes() before */
+  g_assert_cmpstr (changes->str, ==, "");
+
+  g_string_free (changes, TRUE);
+}
+
+static GtkFontList *
+new_font_list (void)
+{
+  GtkFontList *result;
+  GString *changes;
+
+  result = gtk_font_list_new ();
+  changes = g_string_new ("");
+  g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes);
+  g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes);
+
+  return result;
+}
+
+static void
+test_create (void)
+{
+  GtkFontList *list;
+  
+  list = new_font_list ();
+  assert_changes (list, "");
+  g_object_unref (list);
+}
+
+static void
+test_set_display (void)
+{
+  GtkFontList *list;
+  
+  list = new_font_list ();
+  assert_changes (list, "");
+  gtk_font_list_set_display (list, gdk_display_get_default ());
+  assert_changes (list, "");
+  gtk_font_list_set_display (list, NULL);
+  assert_changes (list, "");
+  g_object_unref (list);
+}
+
+static void
+test_set_font_map (void)
+{
+  GtkFontList *list1, *list2;
+  
+  list1 = new_font_list ();
+  assert_changes (list1, "");
+  list2 = new_font_list ();
+  gtk_font_list_set_font_map (list2, pango_cairo_font_map_get_default ());
+  assert_changes (list2, "");
+  assert_model (list1, list2);
+  g_object_unref (list2);
+  g_object_unref (list1);
+}
+
+static void
+test_set_families_only (void)
+{
+  GtkFontList *list1, *list2;
+  
+  list1 = new_font_list ();
+  assert_changes (list1, "");
+  list2 = new_font_list ();
+  gtk_font_list_set_families_only (list2, TRUE);
+  assert_some_changes (list2);
+  gtk_font_list_set_families_only (list2, FALSE);
+  assert_some_changes (list2);
+  assert_model (list1, list2);
+  g_object_unref (list2);
+  g_object_unref (list1);
+}
+
+int
+main (int argc, char *argv[])
+{
+  gtk_test_init (&argc, &argv, NULL);
+  setlocale (LC_ALL, "C");
+
+  changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?");
+
+  g_test_add_func ("/fontlistmodel/create", test_create);
+  g_test_add_func ("/fontlistmodel/set-display", test_set_display);
+  g_test_add_func ("/fontlistmodel/set-font-map", test_set_font_map);
+  g_test_add_func ("/fontlistmodel/set-families-only", test_set_families_only);
+
+  return g_test_run ();
+}
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index b02d3f51ce..73d9467946 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -31,6 +31,7 @@ tests = [
   ['firefox-stylecontext'],
   ['floating'],
   ['focus'],
+  ['fontlist'],
   ['gestures'],
   ['grid'],
   ['grid-layout'],


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