[gnome-font-viewer] Port to GtkFlowBox



commit 8b8bb9a8f7bae4c2f1e4e5e84fa636fea4c8a443
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Sun Jul 14 02:37:54 2019 +0200

    Port to GtkFlowBox
    
    Instead of GtkIconView. This allows to make code simpler, but also yields
    some nice performance improvements, since GtkIconView is very slow
    with large datasets.
    
    https://gitlab.gnome.org/GNOME/gnome-font-viewer/issues/2

 src/font-model.c | 431 +++++++++--------------------------------------
 src/font-model.h |  22 ++-
 src/font-view.c  | 499 ++++++++++++++++++++++++++++++++++++++++++-------------
 3 files changed, 481 insertions(+), 471 deletions(-)
---
diff --git a/src/font-model.c b/src/font-model.c
index 5e998fe..dc37c9f 100644
--- a/src/font-model.c
+++ b/src/font-model.c
@@ -29,6 +29,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <gio/gio.h>
 #include <gtk/gtk.h>
 #include <cairo-gobject.h>
 
@@ -36,16 +37,13 @@
 #include FT_FREETYPE_H
 #include <fontconfig/fontconfig.h>
 
-#define GNOME_DESKTOP_USE_UNSTABLE_API
-#include <libgnome-desktop/gnome-desktop-thumbnail.h>
-
 #include "font-model.h"
 #include "font-utils.h"
 #include "sushi-font-loader.h"
 
 struct _FontViewModel
 {
-    GtkListStore parent_instance;
+    GObject parent_instance;
 
     /* list of fonts in fontconfig database */
     FcFontSet *font_list;
@@ -53,297 +51,109 @@ struct _FontViewModel
 
     FT_Library library;
 
-    cairo_surface_t *fallback_icon;
+    GListStore *model;
     GCancellable *cancellable;
 
-    gint scale_factor;
     guint font_list_idle_id;
     guint fontconfig_update_id;
 };
 
-enum {
-    CONFIG_CHANGED,
-    NUM_SIGNALS
-};
-
-static guint signals[NUM_SIGNALS] = { 0, };
+G_DEFINE_TYPE (FontViewModel, font_view_model, G_TYPE_OBJECT)
 
-G_DEFINE_TYPE (FontViewModel, font_view_model, GTK_TYPE_LIST_STORE)
+struct _FontViewModelItem
+{
+    GObject parent_instance;
 
-#define ATTRIBUTES_FOR_CREATING_THUMBNAIL \
-    G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE"," \
-    G_FILE_ATTRIBUTE_TIME_MODIFIED
-#define ATTRIBUTES_FOR_EXISTING_THUMBNAIL \
-    G_FILE_ATTRIBUTE_THUMBNAIL_PATH"," \
-    G_FILE_ATTRIBUTE_THUMBNAILING_FAILED
+    gchar *collation_key;
+    gchar *font_name;
+    gchar *path;
+    int face_index;
+};
 
-typedef struct {
-    const gchar *file;
-    gchar *match_name;
-    GtkTreeIter iter;
-    gboolean found;
-} IterForFaceData;
+G_DEFINE_TYPE (FontViewModelItem, font_view_model_item, G_TYPE_OBJECT)
 
-static gboolean
-iter_for_face_foreach (GtkTreeModel *model,
-                       GtkTreePath *path,
-                       GtkTreeIter *iter,
-                       gpointer user_data)
+static void
+font_view_model_item_finalize (GObject *obj)
 {
-    IterForFaceData *data = user_data;
-    g_autofree gchar *font_name = NULL;
-    gboolean retval;
+    FontViewModelItem *self = FONT_VIEW_MODEL_ITEM (obj);
 
-    gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
-                        COLUMN_NAME, &font_name,
-                        -1);
+    g_clear_pointer (&self->collation_key, g_free);
+    g_clear_pointer (&self->font_name, g_free);
+    g_clear_pointer (&self->path, g_free);
 
-    retval = (g_strcmp0 (font_name, data->match_name) == 0);
-
-    if (retval) {
-        data->iter = *iter;
-        data->found = TRUE;
-    }
-
-    return retval;
+    G_OBJECT_CLASS (font_view_model_item_parent_class)->finalize (obj);
 }
 
-gboolean
-font_view_model_get_iter_for_face (FontViewModel *self,
-                                   FT_Face face,
-                                   GtkTreeIter *iter)
+static void
+font_view_model_item_class_init (FontViewModelItemClass *klass)
 {
-    IterForFaceData *data = g_slice_new0 (IterForFaceData);
-    gboolean found;
-
-    data->match_name = font_utils_get_font_name (face);
-    data->found = FALSE;
-
-    gtk_tree_model_foreach (GTK_TREE_MODEL (self),
-                            iter_for_face_foreach,
-                            data);
-
-    found = data->found;
-    if (found && iter)
-        *iter = data->iter;
-
-    g_free (data->match_name);
-    g_slice_free (IterForFaceData, data);
-
-    return found;
+    GObjectClass *oclass = G_OBJECT_CLASS (klass);
+    oclass->finalize = font_view_model_item_finalize;
 }
 
-typedef struct {
-    FontViewModel *self;
-    GFile *font_file;
-    gint face_index;
-    gchar *uri;
-    cairo_surface_t *surface;
-    GtkTreeIter iter;
-} ThumbInfoData;
-
 static void
-thumb_info_data_free (gpointer user_data)
+font_view_model_item_init (FontViewModelItem *self)
 {
-    ThumbInfoData *thumb_info = user_data;
-
-    g_object_unref (thumb_info->self);
-    g_object_unref (thumb_info->font_file);
-    g_clear_pointer (&thumb_info->surface, cairo_surface_destroy);
-    g_free (thumb_info->uri);
-
-    g_slice_free (ThumbInfoData, thumb_info);
 }
 
-static gboolean
-one_thumbnail_done (gpointer user_data)
+static FontViewModelItem *
+font_view_model_item_new (const gchar *font_name,
+                          const gchar *path,
+                          int          face_index)
 {
-    ThumbInfoData *thumb_info = user_data;
+    FontViewModelItem *item = g_object_new (FONT_VIEW_TYPE_MODEL_ITEM, NULL);
 
-    if (thumb_info->surface != NULL)
-        gtk_list_store_set (GTK_LIST_STORE (thumb_info->self), &(thumb_info->iter),
-                            COLUMN_ICON, thumb_info->surface,
-                            -1);
+    item->collation_key = g_utf8_collate_key (font_name, -1);
+    item->font_name = g_strdup (font_name);
+    item->path = g_strdup (path);
+    item->face_index = face_index;
 
-    thumb_info_data_free (thumb_info);
-
-    return FALSE;
+    return item;
 }
 
-static GdkPixbuf *
-create_thumbnail (ThumbInfoData *thumb_info)
+const gchar *
+font_view_model_item_get_collation_key (FontViewModelItem *self)
 {
-    g_autoptr(GdkPixbuf) pixbuf = NULL;
-    g_autoptr(GError) error = NULL;
-    g_autoptr(GFileInfo) info = NULL;
-    g_autoptr(GnomeDesktopThumbnailFactory) factory = NULL;
-    guint64 mtime;
-
-    info = g_file_query_info (thumb_info->font_file, ATTRIBUTES_FOR_CREATING_THUMBNAIL,
-                              G_FILE_QUERY_INFO_NONE,
-                              NULL, &error);
-
-    /* we don't care about reporting errors here, just fail the
-     * thumbnail.
-     */
-    if (info == NULL) {
-        g_debug ("Can't query info for file %s: %s", thumb_info->uri, error->message);
-        return NULL;
-    }
-
-    factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
-    pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail
-        (factory, 
-         thumb_info->uri, g_file_info_get_content_type (info));
-
-    mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
-    if (pixbuf != NULL) {
-        GdkPixbuf *scaled = gdk_pixbuf_scale_simple (pixbuf,
-                                                     128 * thumb_info->self->scale_factor,
-                                                     128 * thumb_info->self->scale_factor,
-                                                     GDK_INTERP_BILINEAR);
-        gnome_desktop_thumbnail_factory_save_thumbnail (factory, pixbuf,
-                                                        thumb_info->uri, (time_t) mtime);
-        g_object_unref (pixbuf);
-        pixbuf = scaled;
-    } else {
-        gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory,
-                                                                 thumb_info->uri, (time_t) mtime);
-    }
-
-    return g_steal_pointer (&pixbuf);
+    return self->collation_key;
 }
 
-static void
-ensure_thumbnails_job (GTask *task,
-                       gpointer source_object,
-                       gpointer user_data,
-                       GCancellable *cancellable)
+const gchar *
+font_view_model_item_get_font_name (FontViewModelItem *self)
 {
-    FontViewModel *self = FONT_VIEW_MODEL (source_object);
-    GList *thumb_infos = user_data, *l;
-    gint scale_factor = self->scale_factor;
-
-    for (l = thumb_infos; l != NULL; l = l->next) {
-        g_autoptr(GdkPixbuf) pixbuf = NULL;
-        g_autoptr(GError) error = NULL;
-        g_autofree gchar *thumb_path = NULL;
-        ThumbInfoData *thumb_info = l->data;
-
-        if (thumb_info->face_index == 0) {
-            g_autoptr(GFileInfo) info = NULL;
-            gboolean thumb_failed;
-
-            thumb_info->uri = g_file_get_uri (thumb_info->font_file);
-            info = g_file_query_info (thumb_info->font_file,
-                                      ATTRIBUTES_FOR_EXISTING_THUMBNAIL,
-                                      G_FILE_QUERY_INFO_NONE,
-                                      NULL, &error);
-
-            if (error != NULL) {
-                g_debug ("Can't query info for file %s: %s", thumb_info->uri, error->message);
-                goto next;
-            }
-
-            thumb_failed = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
-            if (thumb_failed)
-                goto next;
-
-            thumb_path = g_strdup (g_file_info_get_attribute_byte_string (info, 
G_FILE_ATTRIBUTE_THUMBNAIL_PATH));
-        } else {
-            g_autofree gchar *checksum = NULL, *filename = NULL, *file_uri = NULL;
-
-            file_uri = g_file_get_uri (thumb_info->font_file);
-            thumb_info->uri = g_strdup_printf ("%s#0x%08X", file_uri, thumb_info->face_index);
-
-            checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5,
-                                                    (const guchar *) thumb_info->uri,
-                                                    strlen (thumb_info->uri));
-            filename = g_strdup_printf ("%s.png", checksum);
-
-            thumb_path = g_build_filename (g_get_user_cache_dir (),
-                                           "thumbnails",
-                                           "large",
-                                           filename,
-                                           NULL);
-
-            if (!g_file_test (thumb_path, G_FILE_TEST_IS_REGULAR))
-                g_clear_pointer (&thumb_path, g_free);
-        }
-
-        if (thumb_path != NULL) {
-            g_autoptr(GFile) thumb_file = NULL;
-            g_autoptr(GFileInputStream) is = NULL;
-
-            thumb_file = g_file_new_for_path (thumb_path);
-            is = g_file_read (thumb_file, NULL, &error);
-
-            if (error != NULL) {
-                g_debug ("Can't read file %s: %s", thumb_path, error->message);
-                goto next;
-            }
-
-            pixbuf = gdk_pixbuf_new_from_stream_at_scale (G_INPUT_STREAM (is),
-                                                          128 * scale_factor, 128 * scale_factor,
-                                                          TRUE,
-                                                          NULL, &error);
-
-            if (error != NULL) {
-                g_debug ("Can't read thumbnail pixbuf %s: %s", thumb_path, error->message);
-                goto next;
-            }
-        } else {
-            pixbuf = create_thumbnail (thumb_info);
-        }
-
-        if (pixbuf != NULL)
-            thumb_info->surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, NULL);
-
-    next:
-        g_main_context_invoke (NULL, one_thumbnail_done, thumb_info);
-    }
-
-    g_list_free (thumb_infos);
+    return self->font_name;
 }
 
-typedef struct {
-    gchar *font_path;
-    gint face_index;
-    gchar *font_name;
-} FontInfoData;
-
-static void
-font_info_data_free (gpointer user_data)
+const gchar *
+font_view_model_item_get_font_path (FontViewModelItem *self)
 {
-    FontInfoData *font_info = user_data;
+    return self->path;
+}
 
-    g_free (font_info->font_path);
-    g_free (font_info->font_name);
-    g_slice_free (FontInfoData, font_info);
+gint
+font_view_model_item_get_face_index (FontViewModelItem *self)
+{
+    return self->face_index;
 }
 
-static void
-ensure_fallback_icon (FontViewModel *self)
+gboolean
+font_view_model_has_face (FontViewModel *self,
+                          FT_Face face)
 {
-    g_autoptr(GIcon) icon = NULL;
-    g_autoptr(GtkIconInfo) icon_info = NULL;
-    GtkIconTheme *icon_theme;
-    const char *mimetype = "font/ttf";
+    guint n_items;
+    gint idx;
+    g_autofree gchar *match_name = NULL;
 
-    if (self->fallback_icon != NULL)
-        return;
+    n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+    match_name = font_utils_get_font_name (face);
 
-    icon_theme = gtk_icon_theme_get_default ();
-    icon = g_content_type_get_icon (mimetype);
-    icon_info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme, icon,
-                                                          128, self->scale_factor,
-                                                          GTK_ICON_LOOKUP_FORCE_SIZE);
-    if (!icon_info) {
-        g_warning ("Fallback icon for %s not found", mimetype);
-        return;
+    for (idx = 0; idx < n_items; idx++) {
+        FontViewModelItem *item = g_list_model_get_item (G_LIST_MODEL (self->model), idx);
+
+        if (g_strcmp0 (item->font_name, match_name) == 0)
+            return TRUE;
     }
 
-    self->fallback_icon = gtk_icon_info_load_surface (icon_info, NULL, NULL);
+    return FALSE;
 }
 
 static void
@@ -352,43 +162,9 @@ font_infos_loaded (GObject *source_object,
                    gpointer user_data)
 {
     FontViewModel *self = FONT_VIEW_MODEL (source_object);
-    g_autoptr(GTask) task = NULL;
-    GList *l, *thumb_infos = NULL;
-    GList *font_infos = g_task_propagate_pointer (G_TASK (result), NULL);
-
-    ensure_fallback_icon (self);
-
-    for (l = font_infos; l != NULL; l = l->next) {
-        FontInfoData *font_info = l->data;
-        g_autofree gchar *collation_key = NULL;
-        GtkTreeIter iter;
-        ThumbInfoData *thumb_info;
-
-        collation_key = g_utf8_collate_key (font_info->font_name, -1);
-        gtk_list_store_insert_with_values (GTK_LIST_STORE (self), &iter, -1,
-                                           COLUMN_NAME, font_info->font_name,
-                                           COLUMN_PATH, font_info->font_path,
-                                           COLUMN_FACE_INDEX, font_info->face_index,
-                                           COLUMN_ICON, self->fallback_icon,
-                                           COLUMN_COLLATION_KEY, collation_key,
-                                           -1);
-
-        thumb_info = g_slice_new0 (ThumbInfoData);
-        thumb_info->font_file = g_file_new_for_path (font_info->font_path);
-        thumb_info->face_index = font_info->face_index;
-        thumb_info->iter = iter;
-        thumb_info->self = g_object_ref (self);
-
-        font_info_data_free (font_info);
-        thumb_infos = g_list_prepend (thumb_infos, thumb_info);
-    }
-
-    g_signal_emit (self, signals[CONFIG_CHANGED], 0);
-    g_list_free (font_infos);
+    g_autoptr(GPtrArray) items = g_task_propagate_pointer (G_TASK (result), NULL);
 
-    task = g_task_new (self, NULL, NULL, NULL);
-    g_task_set_task_data (task, thumb_infos, NULL);
-    g_task_run_in_thread (task, ensure_thumbnails_job);
+    g_list_store_splice (self->model, 0, 0, items->pdata, items->len);
 }
 
 static void
@@ -398,19 +174,20 @@ load_font_infos (GTask *task,
                  GCancellable *cancellable)
 {
     FontViewModel *self = FONT_VIEW_MODEL (source_object);
+    g_autoptr(GPtrArray) items = NULL;
     gint i, n_fonts;
-    GList *font_infos = NULL;
 
     n_fonts = self->font_list->nfont;
+    items = g_ptr_array_new_full (n_fonts, g_object_unref);
 
     for (i = 0; i < n_fonts; i++) {
-        FontInfoData *font_info;
+        FontViewModelItem *item;
         FcChar8 *file;
         int index;
-        gchar *font_name;
+        g_autofree gchar *font_name = NULL;
 
-        if (g_cancellable_is_cancelled (cancellable))
-            break;
+        if (g_task_return_error_if_cancelled (task))
+            return;
 
         g_mutex_lock (&self->font_list_mutex);
         FcPatternGetString (self->font_list->fonts[i], FC_FILE, 0, &file);
@@ -424,15 +201,11 @@ load_font_infos (GTask *task,
         if (!font_name)
             continue;
 
-        font_info = g_slice_new0 (FontInfoData);
-        font_info->font_name = font_name;
-        font_info->font_path = g_strdup ((const gchar *) file);
-        font_info->face_index = index;
-
-        font_infos = g_list_prepend (font_infos, font_info);
+        item = font_view_model_item_new (font_name, (const gchar *) file, index);
+        g_ptr_array_add (items, item);
     }
 
-    g_task_return_pointer (task, font_infos, NULL);
+    g_task_return_pointer (task, g_steal_pointer (&items), NULL);
 }
 
 /* make sure the font list is valid */
@@ -450,7 +223,7 @@ ensure_font_list (FontViewModel *self)
     g_cancellable_cancel (self->cancellable);
     g_clear_object (&self->cancellable);
 
-    gtk_list_store_clear (GTK_LIST_STORE (self));
+    g_list_store_remove_all (self->model);
 
     pat = FcPatternCreate ();
     os = FcObjectSetBuild (FC_FILE, FC_INDEX, FC_FAMILY, FC_WEIGHT, FC_SLANT, NULL);
@@ -498,24 +271,6 @@ schedule_update_font_list (FontViewModel *self)
         g_idle_add (ensure_font_list_idle, self);
 }
 
-static int
-font_view_model_sort_func (GtkTreeModel *model,
-                           GtkTreeIter *a,
-                           GtkTreeIter *b,
-                           gpointer user_data)
-{
-    g_autofree gchar *key_a = NULL, *key_b = NULL;
-
-    gtk_tree_model_get (model, a,
-                        COLUMN_COLLATION_KEY, &key_a,
-                        -1);
-    gtk_tree_model_get (model, b,
-                        COLUMN_COLLATION_KEY, &key_b,
-                        -1);
-
-    return g_strcmp0 (key_a, key_b);
-}
-
 static void
 connect_to_fontconfig_updates (FontViewModel *self)
 {
@@ -530,24 +285,11 @@ connect_to_fontconfig_updates (FontViewModel *self)
 static void
 font_view_model_init (FontViewModel *self)
 {
-    GType types[NUM_COLUMNS] =
-        { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, CAIRO_GOBJECT_TYPE_SURFACE, G_TYPE_STRING };
-
     if (FT_Init_FreeType (&self->library) != FT_Err_Ok)
         g_critical ("Can't initialize FreeType library");
 
     g_mutex_init (&self->font_list_mutex);
-
-    gtk_list_store_set_column_types (GTK_LIST_STORE (self),
-                                     NUM_COLUMNS, types);
-
-    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
-                                          COLUMN_NAME,
-                                          GTK_SORT_ASCENDING);
-    gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
-                                     COLUMN_NAME,
-                                     font_view_model_sort_func,
-                                     NULL, NULL);
+    self->model = g_list_store_new (FONT_VIEW_TYPE_MODEL_ITEM);
 
     schedule_update_font_list (self);
     connect_to_fontconfig_updates (self);
@@ -562,6 +304,7 @@ font_view_model_finalize (GObject *obj)
     g_cancellable_cancel (self->cancellable);
     g_clear_object (&self->cancellable);
 
+    g_clear_object (&self->model);
     g_clear_pointer (&self->font_list, FcFontSetDestroy);
     g_clear_pointer (&self->library, FT_Done_FreeType);
 
@@ -574,7 +317,6 @@ font_view_model_finalize (GObject *obj)
     }
 
     g_mutex_clear (&self->font_list_mutex);
-    g_clear_pointer (&self->fallback_icon, cairo_surface_destroy);
 
     G_OBJECT_CLASS (font_view_model_parent_class)->finalize (obj);
 }
@@ -584,27 +326,16 @@ font_view_model_class_init (FontViewModelClass *klass)
 {
     GObjectClass *oclass = G_OBJECT_CLASS (klass);
     oclass->finalize = font_view_model_finalize;
-
-    signals[CONFIG_CHANGED] = 
-        g_signal_new ("config-changed",
-                      FONT_VIEW_TYPE_MODEL,
-                      G_SIGNAL_RUN_FIRST,
-                      0, NULL, NULL, NULL,
-                      G_TYPE_NONE, 0);
 }
 
-GtkTreeModel *
+FontViewModel *
 font_view_model_new (void)
 {
     return g_object_new (FONT_VIEW_TYPE_MODEL, NULL);
 }
 
-void
-font_view_model_set_scale_factor (FontViewModel *self,
-                                  gint           scale_factor)
+GListModel *
+font_view_model_get_list_model (FontViewModel *self)
 {
-    self->scale_factor = scale_factor;
-
-    g_clear_pointer (&self->fallback_icon, cairo_surface_destroy);
-    schedule_update_font_list (self);
+    return G_LIST_MODEL (self->model);
 }
diff --git a/src/font-model.h b/src/font-model.h
index 9f3d0d7..0b33a0b 100644
--- a/src/font-model.h
+++ b/src/font-model.h
@@ -40,19 +40,25 @@ typedef enum {
 } FontViewModelColumns;
 
 #define FONT_VIEW_TYPE_MODEL (font_view_model_get_type ())
-
 G_DECLARE_FINAL_TYPE (FontViewModel, font_view_model,
                       FONT_VIEW, MODEL,
-                      GtkListStore)
+                      GObject)
+
+FontViewModel * font_view_model_new (void);
 
-GtkTreeModel * font_view_model_new (void);
+gboolean font_view_model_has_face (FontViewModel *self,
+                                   FT_Face face);
+GListModel *font_view_model_get_list_model (FontViewModel *self);
 
-gboolean font_view_model_get_iter_for_face (FontViewModel *self,
-                                            FT_Face        face,
-                                            GtkTreeIter   *iter);
+#define FONT_VIEW_TYPE_MODEL_ITEM (font_view_model_item_get_type ())
+G_DECLARE_FINAL_TYPE (FontViewModelItem, font_view_model_item,
+                      FONT_VIEW, MODEL_ITEM,
+                      GObject)
 
-void     font_view_model_set_scale_factor  (FontViewModel *self,
-                                            gint           scale_factor);
+gint font_view_model_item_get_face_index (FontViewModelItem *self);
+const gchar *font_view_model_item_get_collation_key (FontViewModelItem *self);
+const gchar *font_view_model_item_get_font_name (FontViewModelItem *self);
+const gchar *font_view_model_item_get_font_path (FontViewModelItem *self);
 
 G_END_DECLS
 
diff --git a/src/font-view.c b/src/font-view.c
index 18c2ffc..ed51ddf 100644
--- a/src/font-view.c
+++ b/src/font-view.c
@@ -38,6 +38,9 @@
 #include <hb-ot.h>
 #include <hb-ft.h>
 
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+
 #include "font-model.h"
 #include "sushi-font-widget.h"
 
@@ -68,20 +71,306 @@ struct _FontViewApplication {
     GtkWidget *swin_view;
     GtkWidget *swin_preview;
     GtkWidget *swin_info;
-    GtkWidget *icon_view;
+    GtkWidget *flow_box;
     GtkWidget *search_bar;
     GtkWidget *search_entry;
     GtkWidget *search_toggle;
     GtkWidget *menu_button;
 
-    GtkTreeModel *model;
-    GtkTreeModel *filter_model;
+    FontViewModel *model;
 
     GFile *font_file;
 
     GCancellable *cancellable;
 };
 
+G_DEFINE_TYPE (FontViewApplication, font_view_application,
+               GTK_TYPE_APPLICATION);
+
+G_DECLARE_FINAL_TYPE (FontViewItem, font_view_item,
+                      FONT_VIEW, ITEM,
+                      GtkFlowBoxChild);
+
+struct _FontViewItem {
+    GtkFlowBoxChild parent;
+
+    GtkWidget *image;
+    GtkWidget *label;
+    FontViewModelItem *item;
+
+    GCancellable *thumbnail_cancellable;
+};
+
+#define FONT_VIEW_TYPE_ITEM (font_view_item_get_type ())
+G_DEFINE_TYPE (FontViewItem, font_view_item, GTK_TYPE_FLOW_BOX_CHILD)
+
+static cairo_surface_t *
+load_fallback_icon (gint scale_factor)
+{
+    static cairo_surface_t *fallback_icon = NULL;
+    g_autoptr(GIcon) icon = NULL;
+    g_autoptr(GtkIconInfo) icon_info = NULL;
+    GtkIconTheme *icon_theme;
+    const char *mimetype = "font/ttf";
+
+    if (fallback_icon != NULL)
+        return fallback_icon;
+
+    icon_theme = gtk_icon_theme_get_default ();
+    icon = g_content_type_get_icon (mimetype);
+    icon_info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme, icon,
+                                                          128, scale_factor,
+                                                          GTK_ICON_LOOKUP_FORCE_SIZE);
+    if (!icon_info) {
+        g_warning ("Fallback icon for %s not found", mimetype);
+        return NULL;
+    }
+
+    fallback_icon = gtk_icon_info_load_surface (icon_info, NULL, NULL);
+    return fallback_icon;
+}
+
+static void
+font_view_item_dispose (GObject *obj)
+{
+    FontViewItem *self = FONT_VIEW_ITEM (obj);
+
+    g_clear_object (&self->item);
+    g_cancellable_cancel (self->thumbnail_cancellable);
+    g_clear_object (&self->thumbnail_cancellable);
+
+    G_OBJECT_CLASS (font_view_item_parent_class)->dispose (obj);
+}
+
+static void
+font_view_item_class_init (FontViewItemClass *klass)
+{
+    GObjectClass *oclass = G_OBJECT_CLASS (klass);
+    oclass->dispose = font_view_item_dispose;
+}
+
+static void
+font_view_item_init (FontViewItem *self)
+{
+    GtkWidget *box;
+    gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+
+    box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+    gtk_container_add (GTK_CONTAINER (self), box);
+
+    self->image = gtk_image_new ();
+    gtk_widget_set_margin_start (self->image, 6);
+    gtk_widget_set_margin_end (self->image, 6);
+    gtk_widget_set_halign (self->image, GTK_ALIGN_CENTER);
+    gtk_image_set_from_surface (GTK_IMAGE (self->image), load_fallback_icon (scale_factor));
+    gtk_container_add (GTK_CONTAINER (box), self->image);
+
+    self->label = gtk_label_new (NULL);
+    gtk_widget_set_halign (self->label, GTK_ALIGN_CENTER);
+    gtk_label_set_line_wrap (GTK_LABEL (self->label), TRUE);
+    gtk_label_set_line_wrap_mode (GTK_LABEL (self->label), PANGO_WRAP_WORD_CHAR);
+    gtk_label_set_max_width_chars (GTK_LABEL (self->label), 18);
+    gtk_label_set_justify (GTK_LABEL (self->label), GTK_JUSTIFY_CENTER);
+    gtk_container_add (GTK_CONTAINER (box), self->label);
+}
+
+#define ATTRIBUTES_FOR_CREATING_THUMBNAIL \
+    G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE"," \
+    G_FILE_ATTRIBUTE_TIME_MODIFIED
+#define ATTRIBUTES_FOR_EXISTING_THUMBNAIL \
+    G_FILE_ATTRIBUTE_THUMBNAIL_PATH"," \
+    G_FILE_ATTRIBUTE_THUMBNAILING_FAILED
+
+static GdkPixbuf *
+create_thumbnail (GFile       *font_file,
+                  const gchar *thumb_uri,
+                  gint         scale_factor)
+{
+    g_autoptr(GdkPixbuf) pixbuf = NULL;
+    g_autoptr(GError) error = NULL;
+    g_autoptr(GFileInfo) info = NULL;
+    g_autoptr(GnomeDesktopThumbnailFactory) factory = NULL;
+    guint64 mtime;
+
+    info = g_file_query_info (font_file, ATTRIBUTES_FOR_CREATING_THUMBNAIL,
+                              G_FILE_QUERY_INFO_NONE,
+                              NULL, &error);
+
+    /* we don't care about reporting errors here, just fail the
+     * thumbnail.
+     */
+    if (info == NULL) {
+        g_autofree gchar *file_uri = g_file_get_uri (font_file);
+        g_debug ("Can't query info for file %s: %s", file_uri, error->message);
+        return NULL;
+    }
+
+    factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE);
+    pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail
+        (factory, thumb_uri, g_file_info_get_content_type (info));
+
+    mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+    if (pixbuf != NULL) {
+        GdkPixbuf *scaled = gdk_pixbuf_scale_simple (pixbuf,
+                                                     128 * scale_factor,
+                                                     128 * scale_factor,
+                                                     GDK_INTERP_BILINEAR);
+        gnome_desktop_thumbnail_factory_save_thumbnail (factory, pixbuf,
+                                                        thumb_uri, (time_t) mtime);
+        g_object_unref (pixbuf);
+        pixbuf = scaled;
+    } else {
+        gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory,
+                                                                 thumb_uri, (time_t) mtime);
+    }
+
+    return g_steal_pointer (&pixbuf);
+}
+
+static void
+font_view_item_load_thumbnail_job (GTask *task,
+                                   gpointer source_object,
+                                   gpointer task_data,
+                                   GCancellable *cancellable)
+{
+    FontViewItem *self = source_object;
+    FontViewModelItem *item = self->item;
+    gint scale_factor = GPOINTER_TO_INT (task_data);
+    g_autoptr(GdkPixbuf) pixbuf = NULL;
+    g_autoptr(GError) error = NULL;
+    g_autoptr(GFile) file = NULL;
+    g_autofree gchar *thumb_path = NULL, *thumb_uri = NULL, *file_uri = NULL;
+    gint face_index;
+    const gchar *font_path;
+
+    if (g_task_return_error_if_cancelled (task))
+        return;
+
+    face_index = font_view_model_item_get_face_index (item);
+    font_path = font_view_model_item_get_font_path (item);
+    file = g_file_new_for_path (font_path);
+    file_uri = g_file_get_uri (file);
+
+    if (face_index == 0) {
+        g_autoptr(GFileInfo) info = NULL;
+        gboolean thumb_failed;
+
+        thumb_uri = g_strdup (file_uri);
+        info = g_file_query_info (file,
+                                  ATTRIBUTES_FOR_EXISTING_THUMBNAIL,
+                                  G_FILE_QUERY_INFO_NONE,
+                                  NULL, &error);
+
+        if (error != NULL) {
+            g_debug ("Can't query info for file %s: %s", file_uri, error->message);
+            return;
+        }
+
+        thumb_failed = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
+        if (thumb_failed)
+            return;
+
+        thumb_path = g_strdup (g_file_info_get_attribute_byte_string (info, 
G_FILE_ATTRIBUTE_THUMBNAIL_PATH));
+    } else {
+        g_autofree gchar *checksum = NULL, *filename = NULL;
+
+        thumb_uri = g_strdup_printf ("%s#0x%08X", file_uri, face_index);
+        checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5,
+                                                (const guchar *) thumb_uri,
+                                                strlen (thumb_uri));
+        filename = g_strdup_printf ("%s.png", checksum);
+
+        thumb_path = g_build_filename (g_get_user_cache_dir (),
+                                       "thumbnails",
+                                       "large",
+                                       filename,
+                                       NULL);
+
+        if (!g_file_test (thumb_path, G_FILE_TEST_IS_REGULAR))
+            g_clear_pointer (&thumb_path, g_free);
+    }
+
+    if (thumb_path != NULL) {
+        g_autoptr(GFile) thumb_file = NULL;
+        g_autoptr(GFileInputStream) is = NULL;
+
+        thumb_file = g_file_new_for_path (thumb_path);
+        is = g_file_read (thumb_file, NULL, &error);
+
+        if (error != NULL) {
+            g_debug ("Can't read file %s: %s", thumb_path, error->message);
+            return;
+        }
+
+        pixbuf = gdk_pixbuf_new_from_stream_at_scale (G_INPUT_STREAM (is),
+                                                      128 * scale_factor, 128 * scale_factor,
+                                                      TRUE,
+                                                      NULL, &error);
+
+        if (error != NULL) {
+            g_debug ("Can't read thumbnail pixbuf %s: %s", thumb_path, error->message);
+            return;
+        }
+    } else {
+        pixbuf = create_thumbnail (file, thumb_uri, scale_factor);
+    }
+
+    if (pixbuf != NULL)
+        g_task_return_pointer (task, gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, NULL),
+                               (GDestroyNotify) cairo_surface_destroy);
+    else
+        g_task_return_pointer (task, NULL, NULL);
+}
+
+static void
+font_view_item_thumbnail_loaded (GObject *source_object,
+                                 GAsyncResult *result,
+                                 gpointer user_data)
+{
+    FontViewItem *self = user_data;
+    cairo_surface_t *surface = g_task_propagate_pointer (G_TASK (result), NULL);
+
+    g_clear_object (&self->thumbnail_cancellable);
+
+    if (surface != NULL) {
+        gtk_image_set_from_surface (GTK_IMAGE (self->image), surface);
+        cairo_surface_destroy (surface);
+    }
+}
+
+static void
+font_view_item_load_thumbnail (FontViewItem *self)
+{
+    g_autoptr(GTask) task = NULL;
+    gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+
+    self->thumbnail_cancellable = g_cancellable_new ();
+    task = g_task_new (self, self->thumbnail_cancellable,
+                       font_view_item_thumbnail_loaded, self);
+    g_task_set_task_data (task, GINT_TO_POINTER (scale_factor), NULL);
+    g_task_run_in_thread (task, font_view_item_load_thumbnail_job);
+}
+
+static GtkWidget *
+font_view_item_new (FontViewModelItem *item)
+{
+    FontViewItem *view_item = g_object_new (FONT_VIEW_TYPE_ITEM, NULL);
+
+    view_item->item = g_object_ref (item);
+    gtk_label_set_text (GTK_LABEL (view_item->label),
+                        font_view_model_item_get_font_name (item));
+    font_view_item_load_thumbnail (view_item);
+    gtk_widget_show_all (GTK_WIDGET (view_item));
+
+    return GTK_WIDGET (view_item);
+}
+
+static void font_view_application_do_overview (FontViewApplication *self);
+static void ensure_window (FontViewApplication *self);
+
+#define VIEW_COLUMN_SPACING 18
+#define VIEW_MARGIN 16
+
 static gboolean
 _print_version_and_exit (const gchar *option_name,
                          const gchar *value,
@@ -100,17 +389,6 @@ static const GOptionEntry goption_options[] =
     { NULL }
 };
 
-G_DEFINE_TYPE (FontViewApplication, font_view_application,
-               GTK_TYPE_APPLICATION)
-
-static void font_view_application_do_overview (FontViewApplication *self);
-static void ensure_window (FontViewApplication *self);
-
-#define VIEW_ITEM_WIDTH 140
-#define VIEW_ITEM_WRAP_WIDTH 128
-#define VIEW_COLUMN_SPACING 36
-#define VIEW_MARGIN 16
-
 #define WHITESPACE_CHARS "\f \t"
 
 static void
@@ -512,7 +790,7 @@ install_button_refresh_appearance (FontViewApplication *self,
     } else {
         face = sushi_font_widget_get_ft_face (SUSHI_FONT_WIDGET (self->font_widget));
 
-        if (font_view_model_get_iter_for_face (FONT_VIEW_MODEL (self->model), face, NULL)) {
+        if (font_view_model_has_face (FONT_VIEW_MODEL (self->model), face)) {
             gtk_container_add (GTK_CONTAINER (self->install_button), self->installed_label);
             gtk_widget_set_sensitive (self->install_button, FALSE);
             gtk_style_context_remove_class (context, "suggested-action");
@@ -528,13 +806,44 @@ install_button_refresh_appearance (FontViewApplication *self,
 }
 
 static void
-font_model_config_changed_cb (FontViewModel *model,
-                              gpointer user_data)
+font_view_populate_from_model (FontViewApplication *self,
+                               guint position,
+                               guint removed,
+                               guint added)
+{
+    GtkFlowBox *flow_box = GTK_FLOW_BOX (self->flow_box);
+    GListModel *list_model = font_view_model_get_list_model (self->model);
+    gint i;
+
+    while (removed--) {
+        GtkFlowBoxChild *child;
+
+        child = gtk_flow_box_get_child_at_index (flow_box, position);
+        gtk_widget_destroy (GTK_WIDGET (child));
+    }
+
+    for (i = 0; i < added; i++) {
+        g_autoptr(FontViewModelItem) item = g_list_model_get_item (list_model, position + i);
+        GtkWidget *widget = font_view_item_new (item);
+
+        gtk_flow_box_insert (flow_box, widget, position + i);
+    }
+}
+
+static void
+font_model_items_changed_cb (GListModel *model,
+                             guint position,
+                             guint removed,
+                             guint added,
+                             gpointer user_data)
 {
     FontViewApplication *self = user_data;
 
     if (self->font_file != NULL)
         install_button_refresh_appearance (self, NULL);
+
+    if (self->flow_box != NULL)
+        font_view_populate_from_model (self, position, removed, added);
 }
 
 static void
@@ -767,38 +1076,40 @@ info_button_clicked_cb (GtkButton *button,
     gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "info");
 }
 
-static void
-font_view_update_scale_factor (FontViewApplication *self)
-{
-    gint scale_factor = gtk_widget_get_scale_factor (self->main_window);
-    font_view_model_set_scale_factor (FONT_VIEW_MODEL (self->model),
-                                      scale_factor);
-}
-
 static gboolean
-font_visible_func (GtkTreeModel *model,
-                   GtkTreeIter  *iter,
-                   gpointer      data)
+font_view_filter_func (GtkFlowBoxChild *child,
+                       gpointer user_data)
 {
-    FontViewApplication *self = data;
-    g_autofree gchar *name = NULL, *cf_name = NULL, *cf_search = NULL;
-    const char *search;
+    FontViewApplication *self = user_data;
+    FontViewItem *view_item = FONT_VIEW_ITEM (child);
+    FontViewModelItem *item = view_item->item;
+    g_autofree gchar *cf_name = NULL, *cf_search = NULL;
+    const char *font_name, *search;
 
     if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->search_toggle)))
         return TRUE;
 
     search = gtk_entry_get_text (GTK_ENTRY (self->search_entry));
+    font_name = font_view_model_item_get_font_name (item);
 
-    gtk_tree_model_get (model, iter,
-                        COLUMN_NAME, &name,
-                        -1);
-
-    cf_name = g_utf8_casefold (name, -1);
+    cf_name = g_utf8_casefold (font_name, -1);
     cf_search = g_utf8_casefold (search, -1);
 
     return strstr (cf_name, cf_search) != NULL;
 }
 
+static gint
+font_view_sort_func (GtkFlowBoxChild *child1,
+                     GtkFlowBoxChild *child2,
+                     gpointer user_data)
+{
+    FontViewModelItem *item1 = FONT_VIEW_ITEM (child1)->item;
+    FontViewModelItem *item2 = FONT_VIEW_ITEM (child2)->item;
+
+    return g_strcmp0 (font_view_model_item_get_collation_key (item1),
+                      font_view_model_item_get_collation_key (item2));
+}
+
 static void
 font_view_ensure_model (FontViewApplication *self)
 {
@@ -806,15 +1117,8 @@ font_view_ensure_model (FontViewApplication *self)
         return;
 
     self->model = font_view_model_new ();
-    self->filter_model = gtk_tree_model_filter_new (self->model, NULL);
-    gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (self->filter_model),
-                                            font_visible_func, self, NULL);
-    g_signal_connect (self->model, "config-changed",
-                      G_CALLBACK (font_model_config_changed_cb), self);
-
-    g_signal_connect_swapped (self->main_window, "notify::scale-factor",
-                              G_CALLBACK (font_view_update_scale_factor), self);
-    font_view_update_scale_factor (self);
+    g_signal_connect (font_view_model_get_list_model (self->model), "items-changed",
+                      G_CALLBACK (font_model_items_changed_cb), self);
 }
 
 static void
@@ -909,43 +1213,24 @@ font_view_application_do_open (FontViewApplication *self,
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->info_button), FALSE);
 }
 
-static gboolean
-icon_view_release_cb (GtkWidget *widget,
-                      GdkEventButton *event,
-                      gpointer user_data)
+static void
+view_child_activated_cb (GtkFlowBox *flow_box,
+                         GtkFlowBoxChild *child,
+                         gpointer user_data)
 {
     FontViewApplication *self = user_data;
-    g_autoptr(GtkTreePath) path = NULL;
-    GtkTreeIter filter_iter;
+    FontViewItem *view_item = FONT_VIEW_ITEM (child);
+    FontViewModelItem *item = view_item->item;
+    const gchar *font_path;
+    gint face_index;
 
-    /* eat double/triple click events */
-    if (event->type != GDK_BUTTON_RELEASE)
-        return TRUE;
+    font_path = font_view_model_item_get_font_path (item);
+    face_index = font_view_model_item_get_face_index (item);
 
-    path = gtk_icon_view_get_path_at_pos (GTK_ICON_VIEW (widget),
-                                          event->x, event->y);
-
-    if (path != NULL &&
-        gtk_tree_model_get_iter (self->filter_model, &filter_iter, path)) {
-        g_autofree gchar *font_path = NULL;
-        GtkTreeIter iter;
-        gint face_index;
-
-        gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (self->filter_model),
-                                                          &iter,
-                                                          &filter_iter);
-        gtk_tree_model_get (self->model, &iter,
-                            COLUMN_PATH, &font_path,
-                            COLUMN_FACE_INDEX, &face_index,
-                            -1);
-
-        if (font_path != NULL) {
-            g_autoptr(GFile) file = g_file_new_for_path (font_path);
-            font_view_application_do_open (self, file, face_index);
-        }
+    if (font_path != NULL) {
+        g_autoptr(GFile) file = g_file_new_for_path (font_path);
+        font_view_application_do_open (self, file, face_index);
     }
-
-    return FALSE;
 }
 
 static void
@@ -974,44 +1259,33 @@ font_view_application_do_overview (FontViewApplication *self)
     gtk_header_bar_set_title (GTK_HEADER_BAR (self->header), _("All Fonts"));
     gtk_header_bar_set_subtitle (GTK_HEADER_BAR (self->header), NULL);
 
-    if (self->icon_view == NULL) {
-        GtkWidget *icon_view;
-        GtkCellRenderer *cell;
-
-        self->icon_view = icon_view = gtk_icon_view_new_with_model (self->filter_model);
+    if (self->flow_box == NULL) {
+        GtkWidget *flow_box;
 
-        g_object_set (icon_view,
+        self->flow_box = flow_box = gtk_flow_box_new ();
+        g_object_set (flow_box,
                       "column-spacing", VIEW_COLUMN_SPACING,
                       "margin", VIEW_MARGIN,
+                      "selection-mode", GTK_SELECTION_NONE,
+                      "vexpand", TRUE,
                       NULL);
-
-        gtk_widget_set_vexpand (GTK_WIDGET (icon_view), TRUE);
-        gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (icon_view), GTK_SELECTION_NONE);
-        gtk_container_add (GTK_CONTAINER (self->swin_view), icon_view);
-
-        cell = gtk_cell_renderer_pixbuf_new ();
-        g_object_set (cell,
-                      "xalign", 0.5,
-                      "yalign", 0.5,
-                      NULL);
-
-        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), cell, FALSE);
-        gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (icon_view), cell,
-                                       "surface", COLUMN_ICON);
-        
-        cell = gtk_cell_renderer_text_new ();
-        g_object_set (cell,
-                      "alignment", PANGO_ALIGN_CENTER,
-                      "xalign", 0.5,
-                      "wrap-mode", PANGO_WRAP_WORD_CHAR,
-                      "wrap-width", VIEW_ITEM_WRAP_WIDTH,
-                      NULL);
-        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), cell, FALSE);
-        gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (icon_view), cell,
-                                       "text", COLUMN_NAME);
-
-        g_signal_connect (icon_view, "button-release-event",
-                          G_CALLBACK (icon_view_release_cb), self);
+        gtk_flow_box_set_filter_func (GTK_FLOW_BOX (flow_box),
+                                      font_view_filter_func,
+                                      self, NULL);
+        gtk_flow_box_set_sort_func (GTK_FLOW_BOX (flow_box),
+                                    font_view_sort_func,
+                                    self, NULL);
+        g_signal_connect (flow_box, "child-activated",
+                          G_CALLBACK (view_child_activated_cb), self);
+        gtk_container_add (GTK_CONTAINER (self->swin_view), flow_box);
+
+        /* Instead of using gtk_flow_box_bind_model(), we populate the view
+         * manually, since we want to support filtering and sorting through
+         * the flowbox, which somehow gtk_flow_box_bind_model() does not support.
+         */
+        font_view_populate_from_model
+            (self, 0, 0,
+             g_list_model_get_n_items (font_view_model_get_list_model (self->model)));
     }
 
     gtk_widget_show_all (self->main_window);
@@ -1123,7 +1397,7 @@ static void
 search_text_changed (GtkEntry *entry,
                      FontViewApplication *self)
 {
-  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (self->filter_model));
+    gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (self->flow_box));
 }
 
 static void
@@ -1248,7 +1522,6 @@ font_view_application_dispose (GObject *obj)
 
     g_clear_object (&self->cancellable);
     g_clear_object (&self->font_file);
-    g_clear_object (&self->filter_model);
     g_clear_object (&self->model);
 
     G_OBJECT_CLASS (font_view_application_parent_class)->dispose (obj);



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