[gtk/wip/fontchooser-language-filtering: 184/186] fontchooser: Add user filtering




commit 09604eb3eb3928429cceeeb235eb4ff3a0bd5169
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Sep 8 19:51:12 2020 -0400

    fontchooser: Add user filtering
    
    Add a popover that has filtering options. As a start,
    allow filtering by monospace and by language coverage.

 gtk/gtkfontchooserwidget.c     | 279 ++++++++++++++++++++++++++++++++++++++++-
 gtk/ui/gtkfontchooserwidget.ui |  77 ++++++++++--
 2 files changed, 348 insertions(+), 8 deletions(-)
---
diff --git a/gtk/gtkfontchooserwidget.c b/gtk/gtkfontchooserwidget.c
index 9be3d33cd2..fb23f78dd7 100644
--- a/gtk/gtkfontchooserwidget.c
+++ b/gtk/gtkfontchooserwidget.c
@@ -56,6 +56,12 @@
 #include "gtkflattenlistmodel.h"
 #include "gtkslicelistmodel.h"
 #include "gtkmaplistmodel.h"
+#include "gtklistitem.h"
+#include "gtksignallistitemfactory.h"
+#include "gtkstringlist.h"
+#include "gtklistview.h"
+#include "gtksortlistmodel.h"
+#include "gtkstringsorter.h"
 
 #include <hb-ot.h>
 #if defined(HAVE_PANGOFT) && defined(HAVE_HARFBUZZ)
@@ -104,6 +110,7 @@ struct _GtkFontChooserWidget
   GtkWidget    *list_stack;
   GtkSingleSelection *selection;
   GtkCustomFilter      *custom_filter;
+  GtkCustomFilter      *user_filter;
   GtkFilterListModel   *filter_model;
 
   GtkWidget       *preview;
@@ -121,6 +128,14 @@ struct _GtkFontChooserWidget
   GtkWidget       *axis_grid;
   GtkWidget       *feature_box;
 
+  GtkWidget         *language_list;
+  GtkStringList     *languages;
+  GHashTable        *language_table;
+
+  PangoLanguage     *filter_language;
+  gboolean           filter_by_language;
+  gboolean           filter_by_monospace;
+
   PangoFontMap         *font_map;
 
   PangoFontDescription *font_desc;
@@ -325,6 +340,94 @@ output_cb (GtkSpinButton *spin,
   return TRUE;
 }
 
+static gboolean
+user_filter_cb (gpointer item,
+                gpointer data)
+{
+  GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (data);
+  PangoFontFamily *family;
+  PangoFontFace *face;
+
+  if (PANGO_IS_FONT_FAMILY (item))
+    {
+      family = item;
+      face = pango_font_family_get_face (family, NULL);
+    }
+  else
+    {
+      face = PANGO_FONT_FACE (item);
+      family = pango_font_face_get_family (face);
+    }
+
+  if (self->filter_by_monospace &&
+      !pango_font_family_is_monospace (family))
+    return FALSE;
+
+  if (self->filter_by_language &&
+      self->filter_language)
+    {
+      PangoFontDescription *desc;
+      PangoContext *context;
+      PangoFont *font;
+      gboolean ret;
+
+      desc = pango_font_face_describe (face);
+      pango_font_description_set_size (desc, 20);
+
+      context = gtk_widget_get_pango_context (GTK_WIDGET (self));
+      font = pango_context_load_font (context, desc);
+
+      ret = TRUE;
+
+      if (PANGO_IS_FC_FONT (font))
+        {
+          PangoLanguage **langs;
+          int i;
+
+          ret = FALSE;
+
+          langs = pango_fc_font_get_languages (PANGO_FC_FONT (font));
+          for (i = 0; langs[i]; i++)
+            {
+              if (langs[i] == self->filter_language)
+                {
+                  ret = TRUE;
+                  break;
+                }
+            }
+        }
+
+      g_object_unref (font);
+      pango_font_description_free (desc);
+
+      return ret;
+    }
+
+  return TRUE;
+}
+
+static void
+monospace_check_changed (GtkCheckButton       *check,
+                         GParamSpec           *pspec,
+                         GtkFontChooserWidget *self)
+{
+  self->filter_by_monospace = gtk_check_button_get_active (check);
+  gtk_filter_changed (GTK_FILTER (self->user_filter),
+                      self->filter_by_monospace ? GTK_FILTER_CHANGE_MORE_STRICT
+                                                : GTK_FILTER_CHANGE_LESS_STRICT);
+}
+
+static void
+language_check_changed (GtkCheckButton       *check,
+                        GParamSpec           *pspec,
+                        GtkFontChooserWidget *self)
+{
+  self->filter_by_language = gtk_check_button_get_active (check);
+  gtk_filter_changed (GTK_FILTER (self->user_filter),
+                      self->filter_by_language ? GTK_FILTER_CHANGE_MORE_STRICT
+                                               : GTK_FILTER_CHANGE_LESS_STRICT);
+}
+
 static void
 gtk_font_chooser_widget_update_marks (GtkFontChooserWidget *self)
 {
@@ -749,6 +852,7 @@ gtk_font_chooser_widget_dispose (GObject *object)
   g_clear_pointer (&self->filter_data, self->filter_data_destroy);
 
   g_clear_pointer (&self->stack, gtk_widget_unparent);
+  g_clear_pointer (&self->language_table, g_hash_table_unref);
 
   G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->dispose (object);
 }
@@ -801,6 +905,7 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass)
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, filter_model);
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, selection);
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, custom_filter);
+  gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, user_filter);
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, preview);
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, preview2);
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, size_label);
@@ -812,6 +917,7 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass)
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, font_name_label);
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, feature_box);
   gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, axis_grid);
+  gtk_widget_class_bind_template_child (widget_class, GtkFontChooserWidget, language_list);
 
   gtk_widget_class_bind_template_callback (widget_class, get_font_name);
   gtk_widget_class_bind_template_callback (widget_class, get_font_attributes);
@@ -822,6 +928,8 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass)
   gtk_widget_class_bind_template_callback (widget_class, output_cb);
   gtk_widget_class_bind_template_callback (widget_class, selection_changed_cb);
   gtk_widget_class_bind_template_callback (widget_class, resize_by_scroll_cb);
+  gtk_widget_class_bind_template_callback (widget_class, monospace_check_changed);
+  gtk_widget_class_bind_template_callback (widget_class, language_check_changed);
 
   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
   gtk_widget_class_set_css_name (widget_class, I_("fontchooser"));
@@ -896,6 +1004,76 @@ axis_free (gpointer v)
   g_free (a);
 }
 
+static void
+select_added (GListModel *model,
+              guint       position,
+              guint       removed,
+              guint       added,
+              gpointer    data)
+{
+  GtkSingleSelection *selection = GTK_SINGLE_SELECTION (model);
+
+  g_assert (removed == 0);
+  g_assert (added == 1);
+
+  gtk_single_selection_set_selected (selection, position);
+}
+
+static void
+add_languages_from_font (GtkFontChooserWidget *self,
+                         gpointer              item)
+{
+  PangoFontFace *face;
+  PangoFontDescription *desc;
+  PangoFont *font;
+  PangoContext *context;
+
+  if (PANGO_IS_FONT_FAMILY (item))
+    face = pango_font_family_get_face (PANGO_FONT_FAMILY (item), NULL);
+  else
+    face = PANGO_FONT_FACE (item);
+
+  desc = pango_font_face_describe (face);
+  pango_font_description_set_size (desc, 20);
+
+  context = gtk_widget_get_pango_context (GTK_WIDGET (self));
+  font = pango_context_load_font (context, desc);
+
+  if (PANGO_IS_FC_FONT (font))
+    {
+      GtkSelectionModel *model = gtk_list_view_get_model (GTK_LIST_VIEW (self->language_list));
+      PangoLanguage *default_lang = pango_language_get_default ();
+      PangoLanguage **langs;
+      int i;
+
+      langs = pango_fc_font_get_languages (PANGO_FC_FONT (font));
+      for (i = 0; langs[i]; i++)
+        {
+          if (!g_hash_table_contains (self->language_table, langs[i]))
+            {
+              g_hash_table_add (self->language_table, langs[i]);
+              if (get_language_name (langs[i]))
+                {
+                  const char *l = pango_language_to_string (langs[i]);
+                  gulong id = 0;
+
+                  /* Pre-select the default language */
+                  if (pango_language_matches (default_lang, l))
+                    id = g_signal_connect (model, "items-changed", G_CALLBACK (select_added), NULL);
+
+                  gtk_string_list_append (self->languages, l);
+
+                  if (id)
+                    g_signal_handler_disconnect (model, id);
+                }
+            }
+        }
+    }
+
+  g_object_unref (font);
+  pango_font_description_free (desc);
+}
+
 /* We incrementally populate our fontlist to prevent blocking
  * the font chooser for a long time with expensive FcFontSort
  * calls in pango for every row in the list).
@@ -908,7 +1086,7 @@ add_to_fontlist (GtkWidget     *widget,
   GtkFontChooserWidget *self = GTK_FONT_CHOOSER_WIDGET (widget);
   GtkSliceListModel *model = user_data;
   GListModel *child_model;
-  guint n;
+  guint i, n;
 
   if (gtk_filter_list_model_get_model (self->filter_model) != G_LIST_MODEL (model))
     return G_SOURCE_REMOVE;
@@ -917,6 +1095,15 @@ add_to_fontlist (GtkWidget     *widget,
 
   n = gtk_slice_list_model_get_size (model);
 
+  for (i = n; i < n + 10; i++)
+    {
+      gpointer item = g_list_model_get_item (child_model, i);
+      if (!item)
+        break;
+      add_languages_from_font (self, item);
+      g_object_unref (item);
+    }
+
   n += 10;
 
   if (n >= g_list_model_get_n_items (child_model))
@@ -952,6 +1139,93 @@ update_fontlist (GtkFontChooserWidget *self)
   g_object_unref (model);
 }
 
+static void
+setup_lang_item (GtkSignalListItemFactory *factory,
+                 gpointer                  item,
+                 gpointer                  data)
+{
+  GtkWidget *label;
+
+  label = gtk_label_new (NULL);
+  gtk_label_set_xalign (GTK_LABEL (label), 0);
+  gtk_list_item_set_child (GTK_LIST_ITEM (item), label);
+}
+
+static void
+bind_lang_item (GtkSignalListItemFactory *factory,
+                gpointer                  item,
+                gpointer                  data)
+{
+  GtkWidget *label;
+  gpointer obj;
+  const char *str;
+  PangoLanguage *language;
+  const char *name;
+
+  obj = gtk_list_item_get_item (GTK_LIST_ITEM (item));
+  str = gtk_string_object_get_string (GTK_STRING_OBJECT (obj));
+  language = pango_language_from_string (str);
+  name = get_language_name (language);
+
+  label = gtk_list_item_get_child (GTK_LIST_ITEM (item));
+  gtk_label_set_label (GTK_LABEL (label), name);
+}
+
+static char *
+get_lang_name (gpointer    this,
+               const char *lang)
+{
+  return g_strdup (get_language_name (pango_language_from_string (lang)));
+}
+
+static void
+language_selection_changed (GtkSelectionModel    *model,
+                            guint                 position,
+                            guint                 n_items,
+                            GtkFontChooserWidget *self)
+{
+  gpointer obj;
+
+  obj = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (model));
+
+  if (obj)
+    self->filter_language = pango_language_from_string (gtk_string_object_get_string (obj));
+  else
+    self->filter_language = NULL;
+
+  if (self->filter_by_language)
+    gtk_filter_changed (GTK_FILTER (self->user_filter), GTK_FILTER_CHANGE_DIFFERENT);
+}
+
+static void
+setup_language_list (GtkFontChooserWidget *self)
+{
+  GtkListItemFactory *factory;
+  GtkExpression *expression;
+  GListModel *model;
+  GtkSelectionModel *selection;
+
+  self->languages = gtk_string_list_new (NULL);
+  self->language_table = g_hash_table_new (NULL, NULL);
+
+  expression = gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string");
+  expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 1, &expression, (GCallback)get_lang_name, 
NULL, NULL);
+
+  model = G_LIST_MODEL (gtk_sort_list_model_new (G_LIST_MODEL (self->languages),
+                        GTK_SORTER (gtk_string_sorter_new (expression))));
+
+  selection = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
+  g_signal_connect (selection, "selection-changed", G_CALLBACK (language_selection_changed), self);
+  gtk_list_view_set_model (GTK_LIST_VIEW (self->language_list), selection);
+  g_object_unref (selection);
+
+  factory = gtk_signal_list_item_factory_new ();
+  g_signal_connect (factory, "setup", G_CALLBACK (setup_lang_item), self);
+  g_signal_connect (factory, "bind", G_CALLBACK (bind_lang_item), self);
+  gtk_list_view_set_factory (GTK_LIST_VIEW (self->language_list), factory);
+  g_object_unref (factory);
+}
+
 static void
 gtk_font_chooser_widget_init (GtkFontChooserWidget *self)
 {
@@ -988,6 +1262,9 @@ gtk_font_chooser_widget_init (GtkFontChooserWidget *self)
   gtk_font_chooser_widget_populate_features (self);
 
   gtk_font_chooser_widget_take_font_desc (self, NULL);
+
+  gtk_custom_filter_set_filter_func (self->user_filter, user_filter_cb, self, NULL);
+  setup_language_list (self);
 }
 
 /**
diff --git a/gtk/ui/gtkfontchooserwidget.ui b/gtk/ui/gtkfontchooserwidget.ui
index acfbf57570..c3933c84b2 100644
--- a/gtk/ui/gtkfontchooserwidget.ui
+++ b/gtk/ui/gtkfontchooserwidget.ui
@@ -5,6 +5,7 @@
     <signal name="items-changed" handler="rows_changed_cb" object="GtkFontChooserWidget" swapped="1" />
     <property name="model">
       <object class="GtkFilterListModel" id="filter_model">
+        <property name="incremental">1</property>
         <property name="filter">
           <object class="GtkEveryFilter">
             <child>
@@ -18,8 +19,10 @@
               </object>
             </child>
             <child>
-              <object class="GtkCustomFilter" id="custom_filter">
-              </object>
+              <object class="GtkCustomFilter" id="custom_filter"/>
+            </child>
+            <child>
+              <object class="GtkCustomFilter" id="user_filter"/>
             </child>
           </object>
         </property>
@@ -49,11 +52,71 @@
                 <property name="row-spacing">6</property>
                 <property name="column-spacing">6</property>
                 <child>
-                  <object class="GtkSearchEntry" id="search_entry">
-                    <property name="hexpand">1</property>
-                    <property name="activates-default">1</property>
-                    <property name="placeholder-text" translatable="yes">Search font name</property>
-                    <signal name="stop-search" handler="stop_search_cb" swapped="no"/>
+                  <object class="GtkBox">
+                    <style>
+                      <class name="linked"/>
+                    </style>
+                    <child>
+                      <object class="GtkSearchEntry" id="search_entry">
+                        <property name="hexpand">1</property>
+                        <property name="activates-default">1</property>
+                        <property name="placeholder-text" translatable="yes">Search font name</property>
+                        <signal name="stop-search" handler="stop_search_cb" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuButton">
+                        <property name="icon-name">pan-down-symbolic</property>
+                        <property name="popover">
+                          <object class="GtkPopover">
+                            <child>
+                              <object class="GtkBox">
+                                <property name="orientation">vertical</property>
+                                <property name="spacing">6</property>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="label" translatable="yes">Filter by</property>
+                                    <property name="width-chars">20</property>
+                                    <property name="margin-bottom">10</property>
+                                    <style>
+                                      <class name="title-4"/>
+                                    </style>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="monospace_button">
+                                    <property name="label" translatable="yes">Monospace</property>
+                                    <signal name="notify::active" handler="monospace_check_changed"/>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="language_button">
+                                    <property name="label" translatable="yes">Language</property>
+                                    <signal name="notify::active" handler="language_check_changed"/>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkFrame">
+                                    <property name="margin-start">12</property>
+                                    <child>
+                                      <object class="GtkScrolledWindow">
+                                        <property name="min-content-height">200</property>
+                                        <property name="hscrollbar-policy">never</property>
+                                        <property name="vscrollbar-policy">automatic</property>
+                                        <child>
+                                          <object class="GtkListView" id="language_list">
+                                          </object>
+                                        </child>
+                                      </object>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
                     <layout>
                       <property name="column">0</property>
                       <property name="row">0</property>


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