summaryrefslogtreecommitdiff
path: root/font-viewer/font-model.c
diff options
context:
space:
mode:
Diffstat (limited to 'font-viewer/font-model.c')
-rw-r--r--font-viewer/font-model.c458
1 files changed, 458 insertions, 0 deletions
diff --git a/font-viewer/font-model.c b/font-viewer/font-model.c
new file mode 100644
index 00000000..64db8660
--- /dev/null
+++ b/font-viewer/font-model.c
@@ -0,0 +1,458 @@
+/* -*- mode: C; c-basic-offset: 4 -*-
+ * mate-font-view:
+ *
+ * Copyright (C) 2012 Cosimo Cecchi <[email protected]>
+ *
+ * based on code from
+ *
+ * fontilus - a collection of font utilities for MATE
+ * Copyright (C) 2002-2003 James Henstridge <[email protected]>
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <gtk/gtk.h>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include <fontconfig/fontconfig.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include <libmate-desktop/mate-desktop-thumbnail.h>
+
+#include "font-model.h"
+#include "sushi-font-loader.h"
+
+struct _FontViewModelPrivate {
+ /* list of fonts in fontconfig database */
+ FcFontSet *font_list;
+
+ FT_Library library;
+};
+
+static void ensure_thumbnail (FontViewModel *self, const gchar *path);
+
+G_DEFINE_TYPE (FontViewModel, font_view_model, GTK_TYPE_LIST_STORE);
+
+#define ATTRIBUTES_FOR_THUMBNAIL \
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE"," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED
+
+static gboolean
+create_thumbnail (GIOSchedulerJob *job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result = user_data;
+ GFile *file = G_FILE (g_async_result_get_source_object (G_ASYNC_RESULT (result)));
+ MateDesktopThumbnailFactory *factory;
+ GFileInfo *info;
+ gchar *uri;
+ GdkPixbuf *pixbuf;
+ guint64 mtime;
+
+ uri = g_file_get_uri (file);
+ info = g_file_query_info (file, ATTRIBUTES_FOR_THUMBNAIL,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ /* we don't care about reporting errors here, just fail the
+ * thumbnail.
+ */
+ if (info == NULL)
+ {
+ g_simple_async_result_set_op_res_gboolean (result, FALSE);
+ goto out;
+ }
+
+ mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ factory = mate_desktop_thumbnail_factory_new (MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+ pixbuf = mate_desktop_thumbnail_factory_generate_thumbnail
+ (factory,
+ uri, g_file_info_get_content_type (info));
+
+ if (pixbuf != NULL)
+ {
+ mate_desktop_thumbnail_factory_save_thumbnail (factory, pixbuf,
+ uri, (time_t) mtime);
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gboolean (result, FALSE);
+ }
+
+ g_object_unref (info);
+ g_object_unref (file);
+ g_object_unref (factory);
+ g_clear_object (&pixbuf);
+
+ out:
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+
+ return FALSE;
+}
+
+static void
+gd_queue_thumbnail_job_for_file_async (GFile *file,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (file),
+ callback, user_data,
+ gd_queue_thumbnail_job_for_file_async);
+
+ g_io_scheduler_push_job (create_thumbnail,
+ result, NULL,
+ G_PRIORITY_DEFAULT, NULL);
+}
+
+static gboolean
+gd_queue_thumbnail_job_for_file_finish (GAsyncResult *res)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+
+ return g_simple_async_result_get_op_res_gboolean (simple);
+}
+
+typedef struct {
+ const gchar *file;
+ GtkTreeIter iter;
+ gboolean found;
+} IterForFileData;
+
+static gboolean
+iter_for_file_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ IterForFileData *data = user_data;
+ gchar *font_path;
+ gboolean retval;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
+ COLUMN_PATH, &font_path,
+ -1);
+
+ retval = (g_strcmp0 (font_path, data->file) == 0);
+ g_free (font_path);
+
+ if (retval) {
+ data->iter = *iter;
+ data->found = TRUE;
+ }
+
+ return retval;
+}
+
+gboolean
+font_view_model_get_iter_for_file (FontViewModel *self,
+ const gchar *file,
+ GtkTreeIter *iter)
+{
+ IterForFileData *data = g_slice_new0 (IterForFileData);
+ gboolean found;
+
+ data->file = file;
+ data->found = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (self),
+ iter_for_file_foreach,
+ data);
+
+ found = data->found;
+ if (found && iter)
+ *iter = data->iter;
+
+ g_slice_free (IterForFileData, data);
+
+ return found;
+}
+
+typedef struct {
+ FontViewModel *self;
+ gchar *font_path;
+} LoadThumbnailData;
+
+static void
+thumbnail_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ FontViewModel *self = user_data;
+ gboolean result;
+ gchar *path;
+ GFile *file;
+
+ result = gd_queue_thumbnail_job_for_file_finish (G_ASYNC_RESULT (res));
+
+ if (result) {
+ file = G_FILE (source);
+ path = g_file_get_path (file);
+ ensure_thumbnail (self, path);
+
+ g_free (path);
+ }
+}
+
+static void
+pixbuf_async_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ LoadThumbnailData *data = user_data;
+ GdkPixbuf *pix;
+ GtkTreeIter iter;
+
+ pix = gdk_pixbuf_new_from_stream_finish (res, NULL);
+
+ if (pix != NULL) {
+ if (font_view_model_get_iter_for_file (data->self, data->font_path, &iter))
+ gtk_list_store_set (GTK_LIST_STORE (data->self), &iter,
+ COLUMN_ICON, pix,
+ -1);
+
+ g_object_unref (pix);
+ }
+
+ g_object_unref (data->self);
+ g_free (data->font_path);
+ g_slice_free (LoadThumbnailData, data);
+}
+
+static void
+thumb_file_read_async_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ LoadThumbnailData *data = user_data;
+ GFileInputStream *is;
+
+ is = g_file_read_finish (G_FILE (source),
+ res, NULL);
+
+ if (is != NULL) {
+ gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (is),
+ 128, 128, TRUE,
+ NULL, pixbuf_async_ready_cb, data);
+ g_object_unref (is);
+ }
+}
+
+static void
+ensure_thumbnail (FontViewModel *self,
+ const gchar *path)
+{
+ GFile *file, *thumb_file = NULL;
+ GFileInfo *info;
+ const gchar *thumb_path;
+ LoadThumbnailData *data;
+
+ file = g_file_new_for_path (path);
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_THUMBNAIL_PATH ","
+ G_FILE_ATTRIBUTE_THUMBNAILING_FAILED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ if (!info)
+ goto out;
+ if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED))
+ goto out;
+
+ thumb_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
+ if (thumb_path)
+ thumb_file = g_file_new_for_path (thumb_path);
+ else
+ gd_queue_thumbnail_job_for_file_async (file, thumbnail_ready_cb, self);
+
+ if (thumb_file != NULL) {
+ data = g_slice_new0 (LoadThumbnailData);
+ data->self = g_object_ref (self);
+ data->font_path = g_file_get_path (file);
+
+ g_file_read_async (thumb_file, G_PRIORITY_DEFAULT, NULL,
+ thumb_file_read_async_ready_cb, data);
+
+ g_object_unref (thumb_file);
+ }
+
+ out:
+ g_clear_object (&file);
+ g_clear_object (&info);
+}
+
+static gchar *
+get_font_name (FontViewModel *self,
+ const gchar *path)
+{
+ GFile *file;
+ gchar *uri, *contents = NULL, *name = NULL;
+ GError *error = NULL;
+ FT_Face face;
+
+ file = g_file_new_for_path (path);
+ uri = g_file_get_uri (file);
+
+ face = sushi_new_ft_face_from_uri (self->priv->library, uri, &contents, &error);
+ if (face != NULL) {
+ if (g_strcmp0 (face->style_name, "Regular") == 0)
+ name = g_strdup (face->family_name);
+ else
+ name = g_strconcat (face->family_name, ", ", face->style_name, NULL);
+ FT_Done_Face (face);
+ } else if (error != NULL) {
+ g_warning ("Can't get font name: %s\n", error->message);
+ g_error_free (error);
+ }
+
+ g_free (uri);
+ g_object_unref (file);
+ g_free (contents);
+
+ return name;
+}
+
+/* make sure the font list is valid */
+static void
+ensure_font_list (FontViewModel *self)
+{
+ FcPattern *pat;
+ FcObjectSet *os;
+ gint i;
+ GtkTreeIter iter;
+ FcChar8 *file;
+ gchar *font_name;
+
+ pat = FcPatternCreate ();
+ os = FcObjectSetBuild (FC_FILE, FC_FAMILY, FC_WEIGHT, FC_SLANT, NULL);
+
+ self->priv->font_list = FcFontList (NULL, pat, os);
+
+ FcPatternDestroy (pat);
+ FcObjectSetDestroy (os);
+
+ if (!self->priv->font_list)
+ return;
+
+ for (i = 0; i < self->priv->font_list->nfont; i++) {
+ FcPatternGetString (self->priv->font_list->fonts[i], FC_FILE, 0, &file);
+ font_name = get_font_name (self, (const gchar *) file);
+
+ gtk_list_store_append (GTK_LIST_STORE (self), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (self), &iter,
+ COLUMN_NAME, font_name,
+ COLUMN_POINTER, self->priv->font_list->fonts[i],
+ COLUMN_PATH, file,
+ -1);
+
+ ensure_thumbnail (self, (const gchar *) file);
+ g_free (font_name);
+ }
+}
+
+static int
+font_view_model_sort_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ gchar *name_a = NULL, *name_b = NULL;
+ int retval;
+
+ gtk_tree_model_get (model, a,
+ COLUMN_NAME, &name_a,
+ -1);
+ gtk_tree_model_get (model, b,
+ COLUMN_NAME, &name_b,
+ -1);
+
+ retval = g_strcmp0 (name_a, name_b);
+
+ g_free (name_a);
+ g_free (name_b);
+
+ return retval;
+}
+
+static void
+font_view_model_init (FontViewModel *self)
+{
+ GType types[NUM_COLUMNS] =
+ { G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, GDK_TYPE_PIXBUF };
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FONT_VIEW_TYPE_MODEL, FontViewModelPrivate);
+
+ if (!FcInit())
+ g_critical ("Can't initialize fontconfig library");
+ if (FT_Init_FreeType (&self->priv->library) != FT_Err_Ok)
+ g_critical ("Can't initialize FreeType library");
+
+ 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);
+ ensure_font_list (self);
+}
+
+static void
+font_view_model_finalize (GObject *obj)
+{
+ FontViewModel *self = FONT_VIEW_MODEL (obj);
+
+ if (self->priv->font_list) {
+ FcFontSetDestroy (self->priv->font_list);
+ self->priv->font_list = NULL;
+ }
+
+ if (self->priv->library != NULL) {
+ FT_Done_FreeType (self->priv->library);
+ self->priv->library = NULL;
+ }
+
+ G_OBJECT_CLASS (font_view_model_parent_class)->finalize (obj);
+}
+
+static void
+font_view_model_class_init (FontViewModelClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ oclass->finalize = font_view_model_finalize;
+
+ g_type_class_add_private (klass, sizeof (FontViewModelPrivate));
+}
+
+GtkTreeModel *
+font_view_model_new (void)
+{
+ return g_object_new (FONT_VIEW_TYPE_MODEL, NULL);
+}
+