summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCosimo Cecchi <cosimoc@gnome.org>2019-07-14 02:37:54 +0200
committerCosimo Cecchi <cosimoc@gnome.org>2019-07-15 23:55:24 +0200
commit8b8bb9a8f7bae4c2f1e4e5e84fa636fea4c8a443 (patch)
treed3e87d2d6d902da58a09fe326dd4c9165987cbb5 /src
parent0309c6f9706aaa478cd829d8b157e2a799150449 (diff)
downloadgnome-font-viewer-8b8bb9a8f7bae4c2f1e4e5e84fa636fea4c8a443.tar.gz
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
Diffstat (limited to 'src')
-rw-r--r--src/font-model.c431
-rw-r--r--src/font-model.h22
-rw-r--r--src/font-view.c499
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);