[gnome-software/wip/hughsie/banner-editor-ids: 4/4] Support a few defined CSS IDs when setting up the feature tile



commit 3bfc60cd5f5e11d632b9583e48d33e789c5de65b
Author: Richard Hughes <richard hughsie com>
Date:   Mon May 15 19:57:56 2017 +0100

    Support a few defined CSS IDs when setting up the feature tile

 src/gs-common.c         |  111 ++++++++++++++++++++++++++++++++++++++++++++--
 src/gs-common.h         |    2 +
 src/gs-editor.c         |   37 +++++++++++++++-
 src/gs-feature-tile.c   |   14 +++++-
 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         |   30 +++++++++++++
 9 files changed, 266 insertions(+), 14 deletions(-)
---
diff --git a/src/gs-common.c b/src/gs-common.c
index 1ec2afd..26cad26 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;
@@ -434,6 +430,111 @@ gs_utils_widget_set_css_app (GsApp *app,
 }
 
 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_utils_parse_css_ids:
+ * @css: CSS markup
+ * @error: A #GError, or %NULL
+ *
+ * Splits up some CSS into the different IDs. If the CSS is ID-less then
+ * it is handled fine and added to an empty key.
+ *
+ * Returns: a #GHashTable or %NULL for error
+ */
+GHashTable *
+gs_utils_parse_css_ids (const gchar *css, GError **error)
+{
+       g_autoptr(GHashTable) results = NULL;
+       g_auto(GStrv) parts = NULL;
+
+       /* no data */
+       results = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+       if (css == NULL || css[0] == '\0')
+               return g_steal_pointer (&results);
+
+       /* old style, no IDs */
+       if (!g_str_has_prefix (css, "#")) {
+               g_hash_table_insert (results, g_strdup (""), g_strdup (css));
+               return g_steal_pointer (&results);
+       }
+
+       /* split up CSS into ID chunks, e.g.
+        *
+        *    #tile {border-radius: 0;}
+        *    #name {color: white;}
+        */
+       parts = g_strsplit (css + 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 NULL;
+                               }
+                               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 (results, current_key->str) != NULL) {
+                                       g_set_error (error,
+                                                    G_IO_ERROR,
+                                                    G_IO_ERROR_INVALID_DATA,
+                                                    "duplicate ID '%s'",
+                                                    current_key->str);
+                                       return NULL;
+                               }
+                               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 NULL;
+                               }
+                               _cleanup_string (current_css);
+                               g_hash_table_insert (results,
+                                                    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 NULL;
+               }
+       }
+
+       /* success */
+       return g_steal_pointer (&results);
+}
+
+static void
 do_not_expand (GtkWidget *child, gpointer data)
 {
        gtk_container_child_set (GTK_CONTAINER (gtk_widget_get_parent (child)),
diff --git a/src/gs-common.h b/src/gs-common.h
index b33b8f3..0d02e53 100644
--- a/src/gs-common.h
+++ b/src/gs-common.h
@@ -60,6 +60,8 @@ gchar         *gs_utils_build_unique_id_kind  (AsAppKind       kind,
                                                 const gchar    *id);
 gboolean        gs_utils_list_has_app_fuzzy    (GsAppList      *list,
                                                 GsApp          *app);
+GHashTable     *gs_utils_parse_css_ids         (const gchar    *css,
+                                                GError         **error);
 
 G_END_DECLS
 
diff --git a/src/gs-editor.c b/src/gs-editor.c
index d8f7f45..6c896af 100644
--- a/src/gs-editor.c
+++ b/src/gs-editor.c
@@ -89,7 +89,7 @@ gs_design_css_parsing_error_cb (GtkCssProvider *provider,
 }
 
 static gboolean
-gs_design_validate_css (GsEditor *self, const gchar *css, GError **error)
+gs_design_validate_css_part (GsEditor *self, const gchar *css, GError **error)
 {
        GsDesignErrorHelper helper;
        g_autofree gchar *css_new = NULL;
@@ -122,6 +122,41 @@ gs_design_validate_css (GsEditor *self, const gchar *css, GError **error)
        return *(helper.error) == NULL;
 }
 
+static gboolean
+gs_design_validate_css (GsEditor *self, const gchar *css, GError **error)
+{
+       const gchar *tmp;
+       g_autoptr(GHashTable) ids = NULL;
+       g_autoptr(GList) keys = NULL;
+
+       /* check each CSS ID */
+       ids = gs_utils_parse_css_ids (css, error);
+       if (ids == NULL)
+               return FALSE;
+       keys = g_hash_table_get_keys (ids);
+       for (GList *l = keys; l != NULL; l = l->next) {
+               const gchar *id = l->data;
+               g_warning ("%s", id);
+               if (g_strcmp0 (id, "") != 0 &&
+                   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 (ids, id);
+               if (!gs_design_validate_css_part (self, tmp, error))
+                       return FALSE;
+       }
+
+       /* success */
+       return TRUE;
+}
+
 static void
 gs_editor_refine_app_pixbuf (GsApp *app)
 {
diff --git a/src/gs-feature-tile.c b/src/gs-feature-tile.c
index dfda13e..4876048 100644
--- a/src/gs-feature-tile.c
+++ b/src/gs-feature-tile.c
@@ -50,7 +50,9 @@ app_state_changed_idle (gpointer user_data)
 {
        GsFeatureTile *tile = GS_FEATURE_TILE (user_data);
        AtkObject *accessible;
+       const gchar *css;
        g_autofree gchar *name = NULL;
+       g_autoptr(GHashTable) ids = NULL;
 
        /* nothing set yet */
        if (tile->app == NULL)
@@ -61,8 +63,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 */
-       gs_utils_widget_set_css_app (tile->app, GTK_WIDGET (tile),
-                                    "GnomeSoftware::FeatureTile-css");
+       css = gs_app_get_metadata_item (tile->app, "GnomeSoftware::FeatureTile-css");
+       ids = gs_utils_parse_css_ids (css, NULL);
+       css = g_hash_table_lookup (ids, "tile");
+       if (css == NULL)
+               css = g_hash_table_lookup (ids, "");
+       gs_utils_widget_set_css_app (tile->app, GTK_WIDGET (tile), css);
+       gs_utils_widget_set_css_simple (tile->title,
+                                       g_hash_table_lookup (ids, "name"));
+       gs_utils_widget_set_css_simple (tile->subtitle,
+                                       g_hash_table_lookup (ids, "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..52bc4d7
--- /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-common.h"
+#include "gs-test.h"
+
+static void
+gs_common_func (void)
+{
+       const gchar *css;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GHashTable) hash1 = NULL;
+       g_autoptr(GHashTable) hash2 = NULL;
+
+       /* hash, no IDs */
+       hash1 = gs_utils_parse_css_ids ("border: 0;", &error);
+       g_assert_no_error (error);
+       g_assert (hash1 != NULL);
+       css = g_hash_table_lookup (hash1, "");
+       g_assert_cmpstr (css, ==, "border: 0;");
+
+       /* hash IDs */
+       hash2 = gs_utils_parse_css_ids ("#tile{\nborder: 0;}\n#name {color: white;\n}", &error);
+       g_assert_no_error (error);
+       g_assert (hash2 != NULL);
+       css = g_hash_table_lookup (hash2, "");
+       g_assert_cmpstr (css, ==, NULL);
+       css = g_hash_table_lookup (hash2, "tile");
+       g_assert_cmpstr (css, ==, "border: 0;");
+       css = g_hash_table_lookup (hash2, "name");
+       g_assert_cmpstr (css, ==, "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/common", gs_common_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..153e126 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -263,3 +263,33 @@ 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-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]