[gnome-software] Add support for CSS IDs



commit 759b403fac37039e1e433a8ed0fca2f6e264b4cc
Author: Richard Hughes <richard hughsie com>
Date:   Tue May 16 13:41:00 2017 +0100

    Add support for CSS IDs
    
    At the moment only the feature tile is supported. The only supported IDs are:
    
        #tile{background: blue;}
        #name{color: green;}
        #summary{color: red;}

 src/gs-common.c         |    6 +-
 src/gs-css.c            |  315 +++++++++++++++++++++++++++++++++++++++++++++++
 src/gs-css.h            |   54 ++++++++
 src/gs-editor.c         |   66 ++--------
 src/gs-feature-tile.c   |   13 ++-
 src/gs-popular-tile.c   |    5 +-
 src/gs-self-test.c      |   71 +++++++++++
 src/gs-summary-tile.c   |    5 +-
 src/gs-upgrade-banner.c |    5 +-
 src/meson.build         |   33 +++++
 10 files changed, 509 insertions(+), 64 deletions(-)
---
diff --git a/src/gs-common.c b/src/gs-common.c
index 1ec2afd..e2d6a81 100644
--- a/src/gs-common.c
+++ b/src/gs-common.c
@@ -386,12 +386,9 @@ gs_utils_widget_set_css_simple (GtkWidget *widget, const gchar *css)
 }
 
 void
-gs_utils_widget_set_css_app (GsApp *app,
-                            GtkWidget *widget,
-                            const gchar *metadata_css)
+gs_utils_widget_set_css_app (GsApp *app, GtkWidget *widget, const gchar *css)
 {
        GPtrArray *key_colors;
-       const gchar *css;
        guint i;
        g_autofree gchar *class_name = NULL;
        g_autoptr(GString) css_str = NULL;
@@ -400,7 +397,6 @@ gs_utils_widget_set_css_app (GsApp *app,
        g_return_if_fail (GS_IS_APP (app));
 
        /* invalid */
-       css = gs_app_get_metadata_item (app, metadata_css);
        if (css == NULL) {
                gs_utils_widget_set_css_simple (widget, css);
                return;
diff --git a/src/gs-css.c b/src/gs-css.c
new file mode 100644
index 0000000..b4d2777
--- /dev/null
+++ b/src/gs-css.c
@@ -0,0 +1,315 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * SECTION:gs-css
+ * @title: GsCss
+ * @stability: Unstable
+ * @short_description: Parse, validate and rewrite CSS resources
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#include "gs-css.h"
+
+struct _GsCss
+{
+       GObject                  parent_instance;
+       GHashTable              *ids;
+       GsCssRewriteFunc         rewrite_func;
+       gpointer                 rewrite_func_data;
+};
+
+G_DEFINE_TYPE (GsCss, gs_css, G_TYPE_OBJECT)
+
+static void
+_cleanup_string (GString *str)
+{
+       /* remove leading newlines */
+       while (g_str_has_prefix (str->str, "\n") || g_str_has_prefix (str->str, " "))
+               g_string_erase (str, 0, 1);
+
+       /* remove trailing newlines */
+       while (g_str_has_suffix (str->str, "\n") || g_str_has_suffix (str->str, " "))
+               g_string_truncate (str, str->len - 1);
+}
+
+/**
+ * gs_css_parse:
+ * @self: a #GsCss
+ * @markup: come CSS, or %NULL
+ * @error: a #GError or %NULL
+ *
+ * Parses the CSS markup and does some basic validation checks on the input.
+ *
+ * Returns: %TRUE for success
+ */
+gboolean
+gs_css_parse (GsCss *self, const gchar *markup, GError **error)
+{
+       g_autoptr(GHashTable) results = NULL;
+       g_auto(GStrv) parts = NULL;
+
+       /* no data */
+       if (markup == NULL || markup[0] == '\0')
+               return TRUE;
+
+       /* old style, no IDs */
+       if (!g_str_has_prefix (markup, "#")) {
+               g_hash_table_insert (self->ids, g_strdup ("tile"), g_strdup (markup));
+               return TRUE;
+       }
+
+       /* split up CSS into ID chunks, e.g.
+        *
+        *    #tile {border-radius: 0;}
+        *    #name {color: white;}
+        */
+       parts = g_strsplit (markup + 1, "\n#", -1);
+       for (guint i = 0; parts[i] != NULL; i++) {
+               g_autoptr(GString) current_css = NULL;
+               g_autoptr(GString) current_key = NULL;
+               for (guint j = 1; parts[i][j] != '\0'; j++) {
+                       const gchar ch = parts[i][j];
+                       if (ch == '{') {
+                               if (current_key != NULL || current_css != NULL) {
+                                       g_set_error_literal (error,
+                                                            G_IO_ERROR,
+                                                            G_IO_ERROR_INVALID_DATA,
+                                                            "invalid '{'");
+                                       return FALSE;
+                               }
+                               current_key = g_string_new_len (parts[i], j);
+                               current_css = g_string_new (NULL);
+                               _cleanup_string (current_key);
+
+                               /* already added */
+                               if (g_hash_table_lookup (self->ids, current_key->str) != NULL) {
+                                       g_set_error (error,
+                                                    G_IO_ERROR,
+                                                    G_IO_ERROR_INVALID_DATA,
+                                                    "duplicate ID '%s'",
+                                                    current_key->str);
+                                       return FALSE;
+                               }
+                               continue;
+                       }
+                       if (ch == '}') {
+                               if (current_key == NULL || current_css == NULL) {
+                                       g_set_error_literal (error,
+                                                            G_IO_ERROR,
+                                                            G_IO_ERROR_INVALID_DATA,
+                                                            "invalid '}'");
+                                       return FALSE;
+                               }
+                               _cleanup_string (current_css);
+                               g_hash_table_insert (self->ids,
+                                                    g_string_free (current_key, FALSE),
+                                                    g_string_free (current_css, FALSE));
+                               current_key = NULL;
+                               current_css = NULL;
+                               continue;
+                       }
+                       if (current_css != NULL)
+                               g_string_append_c (current_css, ch);
+               }
+               if (current_key != NULL || current_css != NULL) {
+                       g_set_error_literal (error,
+                                            G_IO_ERROR,
+                                            G_IO_ERROR_INVALID_DATA,
+                                            "missing '}'");
+                       return FALSE;
+               }
+       }
+
+       /* success */
+       return TRUE;
+}
+
+/**
+ * gs_css_get_markup_for_id:
+ * @self: a #GsCss
+ * @id: an ID, or %NULL for the default
+ *
+ * Gets the CSS markup for a specific ID.
+ *
+ * Returns: %TRUE for success
+ */
+const gchar *
+gs_css_get_markup_for_id (GsCss *self, const gchar *id)
+{
+       if (id == NULL)
+               id = "tile";
+       return g_hash_table_lookup (self->ids, id);
+}
+
+static void
+_css_parsing_error_cb (GtkCssProvider *provider,
+                      GtkCssSection *section,
+                      GError *error,
+                      gpointer user_data)
+{
+       GError **error_parse = (GError **) user_data;
+       if (*error_parse != NULL) {
+               g_warning ("ignoring parse error %u:%u: %s",
+                          gtk_css_section_get_start_line (section),
+                          gtk_css_section_get_start_position (section),
+                          error->message);
+               return;
+       }
+       *error_parse = g_error_copy (error);
+}
+
+static gboolean
+gs_css_validate_part (GsCss *self, const gchar *markup, GError **error)
+{
+       g_autofree gchar *markup_new = NULL;
+       g_autoptr(GError) error_parse = NULL;
+       g_autoptr(GString) str = NULL;
+       g_autoptr(GtkCssProvider) provider = NULL;
+
+       /* nothing set */
+       if (markup == NULL)
+               return TRUE;
+
+       /* remove custom class if NULL */
+       str = g_string_new (NULL);
+       g_string_append (str, ".themed-widget {");
+       if (self->rewrite_func != NULL) {
+               markup_new = self->rewrite_func (self->rewrite_func_data,
+                                                markup,
+                                                error);
+               if (markup_new == NULL)
+                       return FALSE;
+       } else {
+               markup_new = g_strdup (markup);
+       }
+       g_string_append (str, markup_new);
+       g_string_append (str, "}");
+
+       /* set up custom provider */
+       provider = gtk_css_provider_new ();
+       g_signal_connect (provider, "parsing-error",
+                         G_CALLBACK (_css_parsing_error_cb), &error_parse);
+       gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+                                                  GTK_STYLE_PROVIDER (provider),
+                                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_css_provider_load_from_data (provider, str->str, -1, NULL);
+       if (error_parse != NULL) {
+               if (error != NULL)
+                       *error = g_error_copy (error_parse);
+               return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * gs_css_validate:
+ * @self: a #GsCss
+ * @error: a #GError or %NULL
+ *
+ * Validates each part of the CSS markup.
+ *
+ * Returns: %TRUE for success
+ */
+gboolean
+gs_css_validate (GsCss *self, GError **error)
+{
+       g_autoptr(GList) keys = NULL;
+
+       /* check each CSS ID */
+       keys = g_hash_table_get_keys (self->ids);
+       for (GList *l = keys; l != NULL; l = l->next) {
+               const gchar *tmp;
+               const gchar *id = l->data;
+               if (g_strcmp0 (id, "tile") != 0 &&
+                   g_strcmp0 (id, "name") != 0 &&
+                   g_strcmp0 (id, "summary") != 0) {
+                       g_set_error (error,
+                                    G_IO_ERROR,
+                                    G_IO_ERROR_INVALID_DATA,
+                                    "Invalid CSS ID '%s'",
+                                    id);
+                       return FALSE;
+               }
+               tmp = g_hash_table_lookup (self->ids, id);
+               if (!gs_css_validate_part (self, tmp, error))
+                       return FALSE;
+       }
+
+       /* success */
+       return TRUE;
+}
+
+/**
+ * gs_css_set_rewrite_func:
+ * @self: a #GsCss
+ * @func: a #GsCssRewriteFunc or %NULL
+ * @user_data: userr data to pass to @func
+ *
+ * Sets a function to be used when rewriting CSS before it is parsed.
+ *
+ * Returns: %TRUE for success
+ */
+void
+gs_css_set_rewrite_func (GsCss *self, GsCssRewriteFunc func, gpointer user_data)
+{
+       self->rewrite_func = func;
+       self->rewrite_func_data = user_data;
+}
+
+static void
+gs_css_finalize (GObject *object)
+{
+       GsCss *self = GS_CSS (object);
+       g_hash_table_unref (self->ids);
+       G_OBJECT_CLASS (gs_css_parent_class)->finalize (object);
+}
+
+static void
+gs_css_class_init (GsCssClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = gs_css_finalize;
+}
+
+static void
+gs_css_init (GsCss *self)
+{
+       self->ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+}
+
+/**
+ * gs_css_new:
+ *
+ * Return value: a new #GsCss object.
+ **/
+GsCss *
+gs_css_new (void)
+{
+       GsCss *self;
+       self = g_object_new (GS_TYPE_CSS, NULL);
+       return GS_CSS (self);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-css.h b/src/gs-css.h
new file mode 100644
index 0000000..4091697
--- /dev/null
+++ b/src/gs-css.h
@@ -0,0 +1,54 @@
+ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GS_CSS_H
+#define __GS_CSS_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_CSS (gs_css_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsCss, gs_css, GS, CSS, GObject)
+
+typedef gchar  *(*GsCssRewriteFunc)            (gpointer        user_data,
+                                                const gchar    *markup,
+                                                GError         **error);
+
+GsCss          *gs_css_new                     (void);
+const gchar    *gs_css_get_markup_for_id       (GsCss          *self,
+                                                const gchar    *id);
+gboolean        gs_css_parse                   (GsCss          *self,
+                                                const gchar    *markup,
+                                                GError         **error);
+gboolean        gs_css_validate                (GsCss          *self,
+                                                GError         **error);
+void            gs_css_set_rewrite_func        (GsCss          *self,
+                                                GsCssRewriteFunc func,
+                                                gpointer        user_data);
+
+G_END_DECLS
+
+#endif /* __GS_CSS_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-editor.c b/src/gs-editor.c
index bb5e25b..e70a888 100644
--- a/src/gs-editor.c
+++ b/src/gs-editor.c
@@ -26,6 +26,7 @@
 #include <locale.h>
 
 #include "gs-common.h"
+#include "gs-css.h"
 #include "gs-feature-tile.h"
 #include "gs-summary-tile.h"
 #include "gs-upgrade-banner.h"
@@ -61,60 +62,21 @@ gs_editor_css_download_resources (GsEditor *self, const gchar *css, GError **err
        return gs_plugin_download_rewrite_resource (plugin, css, NULL, error);
 }
 
-typedef struct {
-       GsEditor        *self;
-       GError          **error;
-} GsDesignErrorHelper;
-
-static void
-gs_design_css_parsing_error_cb (GtkCssProvider *provider,
-                               GtkCssSection *section,
-                               GError *error,
-                               gpointer user_data)
+static gchar *
+_css_rewrite_cb (gpointer user_data, const gchar *markup, GError **error)
 {
-       GsDesignErrorHelper *helper = (GsDesignErrorHelper *) user_data;
-       if (*(helper->error) != NULL) {
-               g_warning ("ignoring parse error %u:%u: %s",
-                          gtk_css_section_get_start_line (section),
-                          gtk_css_section_get_start_position (section),
-                          error->message);
-               return;
-       }
-       *(helper->error) = g_error_copy (error);
+       GsEditor *self = (GsEditor *) user_data;
+       return gs_editor_css_download_resources (self, markup, error);
 }
 
 static gboolean
-gs_design_validate_css (GsEditor *self, const gchar *css, GError **error)
+gs_design_validate_css (GsEditor *self, const gchar *markup, GError **error)
 {
-       GsDesignErrorHelper helper;
-       g_autofree gchar *css_new = NULL;
-       g_autoptr(GString) str = NULL;
-       g_autoptr(GtkCssProvider) provider = NULL;
-
-       /* nothing set */
-       if (css == NULL)
-               return TRUE;
-
-       /* remove custom class if NULL */
-       str = g_string_new (NULL);
-       g_string_append (str, ".themed-widget {");
-       css_new = gs_editor_css_download_resources (self, css, error);
-       if (css_new == NULL)
+       g_autoptr(GsCss) css = gs_css_new ();
+       gs_css_set_rewrite_func (css, _css_rewrite_cb, self);
+       if (!gs_css_parse (css, markup, error))
                return FALSE;
-       g_string_append (str, css_new);
-       g_string_append (str, "}");
-
-       /* set up custom provider */
-       helper.self = self;
-       helper.error = error;
-       provider = gtk_css_provider_new ();
-       g_signal_connect (provider, "parsing-error",
-                         G_CALLBACK (gs_design_css_parsing_error_cb), &helper);
-       gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
-                                                  GTK_STYLE_PROVIDER (provider),
-                                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
-       gtk_css_provider_load_from_data (provider, str->str, -1, NULL);
-       return *(helper.error) == NULL;
+       return gs_css_validate (css, error);
 }
 
 static void
@@ -220,13 +182,13 @@ gs_editor_convert_app (GsEditor *self, AsApp *item)
        /* copy metadata */
        for (guint i = 0; keys[i] != NULL; i++) {
                g_autoptr(GError) error = NULL;
-               const gchar *css = as_app_get_metadata_item (item, keys[i]);
-               if (css != NULL) {
+               const gchar *markup = as_app_get_metadata_item (item, keys[i]);
+               if (markup != NULL) {
                        g_autofree gchar *css_new = NULL;
-                       css_new = gs_editor_css_download_resources (self, css, &error);
+                       css_new = gs_editor_css_download_resources (self, markup, &error);
                        if (css_new == NULL) {
                                g_warning ("%s", error->message);
-                               gs_app_set_metadata (app, keys[i], css);
+                               gs_app_set_metadata (app, keys[i], markup);
                        } else {
                                gs_app_set_metadata (app, keys[i], css_new);
                        }
diff --git a/src/gs-feature-tile.c b/src/gs-feature-tile.c
index dfda13e..323e0de 100644
--- a/src/gs-feature-tile.c
+++ b/src/gs-feature-tile.c
@@ -26,6 +26,7 @@
 
 #include "gs-feature-tile.h"
 #include "gs-common.h"
+#include "gs-css.h"
 
 struct _GsFeatureTile
 {
@@ -50,7 +51,9 @@ app_state_changed_idle (gpointer user_data)
 {
        GsFeatureTile *tile = GS_FEATURE_TILE (user_data);
        AtkObject *accessible;
+       const gchar *markup;
        g_autofree gchar *name = NULL;
+       g_autoptr(GsCss) css = NULL;
 
        /* nothing set yet */
        if (tile->app == NULL)
@@ -61,8 +64,16 @@ app_state_changed_idle (gpointer user_data)
        gtk_label_set_label (GTK_LABEL (tile->subtitle), gs_app_get_summary (tile->app));
 
        /* perhaps set custom css */
+       markup = gs_app_get_metadata_item (tile->app, "GnomeSoftware::FeatureTile-css");
+       css = gs_css_new ();
+       if (markup != NULL)
+               gs_css_parse (css, markup, NULL);
        gs_utils_widget_set_css_app (tile->app, GTK_WIDGET (tile),
-                                    "GnomeSoftware::FeatureTile-css");
+                                    gs_css_get_markup_for_id (css, "tile"));
+       gs_utils_widget_set_css_simple (tile->title,
+                                       gs_css_get_markup_for_id (css, "name"));
+       gs_utils_widget_set_css_simple (tile->subtitle,
+                                       gs_css_get_markup_for_id (css, "summary"));
 
        accessible = gtk_widget_get_accessible (GTK_WIDGET (tile));
 
diff --git a/src/gs-popular-tile.c b/src/gs-popular-tile.c
index 272c538..1047e6d 100644
--- a/src/gs-popular-tile.c
+++ b/src/gs-popular-tile.c
@@ -97,6 +97,7 @@ static void
 gs_popular_tile_set_app (GsAppTile *app_tile, GsApp *app)
 {
        GsPopularTile *tile = GS_POPULAR_TILE (app_tile);
+       const gchar *css;
 
        g_return_if_fail (GS_IS_APP (app) || app == NULL);
 
@@ -125,8 +126,8 @@ gs_popular_tile_set_app (GsAppTile *app_tile, GsApp *app)
        app_state_changed (tile->app, NULL, tile);
 
        /* perhaps set custom css */
-       gs_utils_widget_set_css_app (app, GTK_WIDGET (tile),
-                                    "GnomeSoftware::PopularTile-css");
+       css = gs_app_get_metadata_item (app, "GnomeSoftware::PopularTile-css");
+       gs_utils_widget_set_css_app (app, GTK_WIDGET (tile), css);
 
        if (gs_app_get_pixbuf (tile->app) != NULL) {
                gs_image_set_from_pixbuf (GTK_IMAGE (tile->image), gs_app_get_pixbuf (tile->app));
diff --git a/src/gs-self-test.c b/src/gs-self-test.c
new file mode 100644
index 0000000..9218f16
--- /dev/null
+++ b/src/gs-self-test.c
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include "gnome-software-private.h"
+
+#include "gs-css.h"
+#include "gs-test.h"
+
+static void
+gs_css_func (void)
+{
+       const gchar *tmp;
+       gboolean ret;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsCss) css = gs_css_new ();
+
+       /* no IDs */
+       ret = gs_css_parse (css, "border: 0;", &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       tmp = gs_css_get_markup_for_id (css, "tile");
+       g_assert_cmpstr (tmp, ==, "border: 0;");
+
+       /* with IDs */
+       ret = gs_css_parse (css, "#tile2{\nborder: 0;}\n#name {color: white;\n}", &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       tmp = gs_css_get_markup_for_id (css, "NotGoingToExist");
+       g_assert_cmpstr (tmp, ==, NULL);
+       tmp = gs_css_get_markup_for_id (css, "tile2");
+       g_assert_cmpstr (tmp, ==, "border: 0;");
+       tmp = gs_css_get_markup_for_id (css, "name");
+       g_assert_cmpstr (tmp, ==, "color: white;");
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+       g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+       /* only critical and error are fatal */
+       g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
+
+       /* tests go here */
+       g_test_add_func ("/gnome-software/src/css", gs_css_func);
+
+       return g_test_run ();
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-summary-tile.c b/src/gs-summary-tile.c
index 2fd6f90..4162953 100644
--- a/src/gs-summary-tile.c
+++ b/src/gs-summary-tile.c
@@ -115,6 +115,7 @@ gs_summary_tile_set_app (GsAppTile *app_tile, GsApp *app)
 {
        const GdkPixbuf *pixbuf;
        GsSummaryTile *tile = GS_SUMMARY_TILE (app_tile);
+       const gchar *css;
        g_autofree gchar *text = NULL;
 
        g_return_if_fail (GS_IS_APP (app) || app == NULL);
@@ -150,8 +151,8 @@ gs_summary_tile_set_app (GsAppTile *app_tile, GsApp *app)
        gtk_label_set_label (GTK_LABEL (tile->name), gs_app_get_name (app));
 
        /* perhaps set custom css */
-       gs_utils_widget_set_css_app (app, GTK_WIDGET (tile),
-                                    "GnomeSoftware::AppTile-css");
+       css = gs_app_get_metadata_item (app, "GnomeSoftware::AppTile-css");
+       gs_utils_widget_set_css_app (app, GTK_WIDGET (tile), css);
 
        /* some kinds have boring summaries */
        switch (gs_app_get_kind (app)) {
diff --git a/src/gs-upgrade-banner.c b/src/gs-upgrade-banner.c
index c343013..4080f4a 100644
--- a/src/gs-upgrade-banner.c
+++ b/src/gs-upgrade-banner.c
@@ -207,6 +207,7 @@ void
 gs_upgrade_banner_set_app (GsUpgradeBanner *self, GsApp *app)
 {
        GsUpgradeBannerPrivate *priv = gs_upgrade_banner_get_instance_private (self);
+       const gchar *css;
 
        g_return_if_fail (GS_IS_UPGRADE_BANNER (self));
        g_return_if_fail (GS_IS_APP (app) || app == NULL);
@@ -226,8 +227,8 @@ gs_upgrade_banner_set_app (GsUpgradeBanner *self, GsApp *app)
                          G_CALLBACK (app_progress_changed), self);
 
        /* perhaps set custom css */
-       gs_utils_widget_set_css_app (app, priv->box_upgrades,
-                                    "GnomeSoftware::UpgradeBanner-css");
+       css = gs_app_get_metadata_item (app, "GnomeSoftware::UpgradeBanner-css");
+       gs_utils_widget_set_css_app (app, priv->box_upgrades, css);
 
        gs_upgrade_banner_refresh (self);
 }
diff --git a/src/meson.build b/src/meson.build
index a7c29d0..6159937 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -25,6 +25,7 @@ gnome_software_sources = [
   'gs-category-page.c',
   'gs-category-tile.c',
   'gs-common.c',
+  'gs-css.c',
   'gs-content-rating.c',
   'gs-details-page.c',
   'gs-extras-page.c',
@@ -140,6 +141,7 @@ executable(
   sources : [
     'gs-app-tile.c',
     'gs-common.c',
+    'gs-css.c',
     'gs-editor.c',
     'gs-summary-tile.c',
     'gs-star-widget.c',
@@ -263,3 +265,34 @@ if get_option('enable-packagekit')
     configuration : conf
   )
 endif
+
+if get_option('enable-tests')
+  cargs += ['-DTESTDATADIR="' + join_paths(meson.current_source_dir(), '..', 'data') + '"']
+  e = executable(
+    'gs-self-test-src',
+    sources : [
+      'gs-css.c',
+      'gs-common.c',
+      'gs-self-test.c',
+    ],
+    include_directories : [
+      include_directories('..'),
+      include_directories('../lib'),
+    ],
+    dependencies : [
+      appstream_glib,
+      gio_unix,
+      gmodule,
+      gtk,
+      json_glib,
+      libm,
+      libsecret,
+      libsoup,
+    ],
+    link_with : [
+      libgnomesoftware
+    ],
+    c_args : cargs
+  )
+  test('gs-self-test-src', e)
+endif


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