[gtk+/wip/css: 1/15] cssprovider: Add parsing support for @keyframes



commit d2a728f879c0bb06e61358ab6e8bf82fc069fe24
Author: Benjamin Otte <otte redhat com>
Date:   Thu Apr 19 03:41:41 2012 +0200

    cssprovider: Add parsing support for @keyframes

 gtk/Makefile.am              |    2 +
 gtk/gtkcsskeyframes.c        |  521 ++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkcsskeyframesprivate.h |   52 +++++
 gtk/gtkcssprovider.c         |  106 +++++++++-
 gtk/gtkcsssection.h          |    6 +-
 5 files changed, 683 insertions(+), 4 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index afd36c7..1524b4b 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -445,6 +445,7 @@ gtk_private_h_sources =		\
 	gtkcssimagewin32private.h	\
 	gtkcssinheritvalueprivate.h	\
 	gtkcssinitialvalueprivate.h	\
+	gtkcsskeyframesprivate.h	\
 	gtkcsslookupprivate.h	\
 	gtkcssmatcherprivate.h	\
 	gtkcssnumbervalueprivate.h	\
@@ -665,6 +666,7 @@ gtk_base_c_sources = 		\
 	gtkcssimagewin32.c	\
 	gtkcssinheritvalue.c	\
 	gtkcssinitialvalue.c	\
+	gtkcsskeyframes.c	\
 	gtkcsslookup.c		\
 	gtkcssmatcher.c		\
 	gtkcssnumbervalue.c	\
diff --git a/gtk/gtkcsskeyframes.c b/gtk/gtkcsskeyframes.c
new file mode 100644
index 0000000..fddcb88
--- /dev/null
+++ b/gtk/gtkcsskeyframes.c
@@ -0,0 +1,521 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * 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 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkcsskeyframesprivate.h"
+
+#include "gtkcssarrayvalueprivate.h"
+#include "gtkcssshorthandpropertyprivate.h"
+#include "gtkcssstylepropertyprivate.h"
+#include "gtkstylepropertyprivate.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct _GtkCssKeyframes {
+  int ref_count;                /* ref count */
+  int n_keyframes;              /* number of keyframes (at least 2 for 0% and 100% */
+  double *keyframe_progress;    /* ordered array of n_keyframes of [0..1] */
+  int n_properties;             /* number of properties used by keyframes */
+  guint *property_ids;          /* ordered array of n_properties property ids */
+  GtkCssValue **values;         /* 2D array: n_keyframes * n_properties of (value or NULL) for all the keyframes */
+};
+
+GtkCssKeyframes *
+_gtk_css_keyframes_ref (GtkCssKeyframes *keyframes)
+{
+  g_return_val_if_fail (keyframes != NULL, NULL);
+
+  keyframes->ref_count++;
+
+  return keyframes;
+}
+
+void
+_gtk_css_keyframes_unref (GtkCssKeyframes *keyframes)
+{
+  g_return_if_fail (keyframes != NULL);
+
+  keyframes->ref_count--;
+  if (keyframes->ref_count > 0)
+    return;
+
+  g_free (keyframes->keyframe_progress);
+  g_free (keyframes->property_ids);
+  g_free (keyframes->values);
+
+  g_slice_free (GtkCssKeyframes, keyframes);
+}
+
+#define KEYFRAMES_VALUE(keyframes, k, p) ((keyframes)->values[(k) * (keyframes)->n_properties + (p)])
+
+static guint
+gtk_css_keyframes_add_keyframe (GtkCssKeyframes *keyframes,
+                                double           progress)
+{
+  guint k, p;
+
+  for (k = 0; k < keyframes->n_keyframes; k++)
+    {
+      if (keyframes->keyframe_progress[k] == progress)
+        {
+          for (p = 0; p < keyframes->n_properties; p++)
+            {
+              if (KEYFRAMES_VALUE (keyframes, k, p) == NULL)
+                continue;
+
+              _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
+              KEYFRAMES_VALUE (keyframes, k, p) = NULL;
+
+              /* XXX: GC properties that are now unset
+               * in all keyframes? */
+            }
+          return k;
+        }
+      else if (keyframes->keyframe_progress[k] > progress)
+        break;
+    }
+
+  keyframes->n_keyframes++;
+  keyframes->keyframe_progress = g_realloc (keyframes->keyframe_progress, sizeof (double) * keyframes->n_keyframes);
+  memmove (keyframes->keyframe_progress + k + 1, keyframes->keyframe_progress + k, sizeof (double) * (keyframes->n_keyframes - k - 1));
+  keyframes->keyframe_progress[k] = progress;
+
+  if (keyframes->n_properties)
+    {
+      gsize size = sizeof (GtkCssValue *) * keyframes->n_properties;
+      
+      keyframes->values = g_realloc (keyframes->values, sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties);
+      memmove (&KEYFRAMES_VALUE (keyframes, k + 1, 0), &KEYFRAMES_VALUE (keyframes, k, 0), size * (keyframes->n_keyframes - k - 1));
+      memset (&KEYFRAMES_VALUE (keyframes, k, 0), 0, size);
+    }
+
+  return k;
+}
+
+static guint
+gtk_css_keyframes_lookup_property (GtkCssKeyframes *keyframes,
+                                   guint            property_id)
+{
+  guint p;
+
+  for (p = 0; p < keyframes->n_properties; p++)
+    {
+      if (keyframes->property_ids[p] == property_id)
+        return p;
+      else if (keyframes->property_ids[p] > property_id)
+        break;
+    }
+
+  keyframes->n_properties++;
+  keyframes->property_ids = g_realloc (keyframes->property_ids, sizeof (guint) * keyframes->n_properties);
+  memmove (keyframes->property_ids + p + 1, keyframes->property_ids + p, sizeof (guint) * (keyframes->n_properties - p - 1));
+  keyframes->property_ids[p] = property_id;
+
+  if (keyframes->n_properties > 1)
+    {
+      guint old_n_properties = keyframes->n_properties - 1;
+      int k;
+      
+      keyframes->values = g_realloc (keyframes->values, sizeof (GtkCssValue *) * keyframes->n_keyframes * keyframes->n_properties);
+
+      if (p + 1 < keyframes->n_properties)
+        {
+          memmove (&KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p + 1),
+                   &keyframes->values[(keyframes->n_keyframes - 1) * old_n_properties + p],
+                   sizeof (GtkCssValue *) * (keyframes->n_properties - p - 1));
+        }
+      KEYFRAMES_VALUE (keyframes, keyframes->n_keyframes - 1, p) = NULL;
+
+      for (k = keyframes->n_keyframes - 2; k >= 0; k--)
+        {
+          memmove (&KEYFRAMES_VALUE (keyframes, k, p + 1),
+                   &keyframes->values[k * old_n_properties + p],
+                   sizeof (GtkCssValue *) * old_n_properties);
+          KEYFRAMES_VALUE (keyframes, k, p) = NULL;
+        }
+    }
+  else
+    {
+      keyframes->values = g_new0 (GtkCssValue *, keyframes->n_keyframes);
+    }
+
+  return p;
+}
+
+static GtkCssKeyframes *
+gtk_css_keyframes_new (void)
+{
+  GtkCssKeyframes *keyframes;
+
+  keyframes = g_slice_new0 (GtkCssKeyframes);
+  keyframes->ref_count = 1;
+
+  gtk_css_keyframes_add_keyframe (keyframes, 0);
+  gtk_css_keyframes_add_keyframe (keyframes, 1);
+
+  return keyframes;
+}
+
+static gboolean
+keyframes_set_value (GtkCssKeyframes     *keyframes,
+                     guint                k,
+                     GtkCssStyleProperty *property,
+                     GtkCssValue         *value)
+{
+  guint p;
+
+  if (!_gtk_css_style_property_is_animated (property))
+    return FALSE;
+
+  p = gtk_css_keyframes_lookup_property (keyframes, _gtk_css_style_property_get_id (property));
+  
+  if (KEYFRAMES_VALUE (keyframes, k, p))
+    _gtk_css_value_unref (KEYFRAMES_VALUE (keyframes, k, p));
+
+  KEYFRAMES_VALUE (keyframes, k, p) = _gtk_css_value_ref (value);
+
+  return TRUE;
+}
+
+static gboolean
+parse_declaration (GtkCssKeyframes *keyframes,
+                   guint            k,
+                   GtkCssParser    *parser)
+{
+  GtkStyleProperty *property;
+  GtkCssValue *value;
+  char *name;
+
+  while (_gtk_css_parser_try (parser, ";", TRUE))
+    {
+      /* SKIP ALL THE THINGS! */
+    }
+
+  name = _gtk_css_parser_try_ident (parser, TRUE);
+  if (name == NULL)
+    {
+      _gtk_css_parser_error (parser, "No property name given");
+      return FALSE;
+    }
+
+  property = _gtk_style_property_lookup (name);
+  if (property == NULL)
+    {
+      /* should be GTK_CSS_PROVIDER_ERROR_NAME */
+      _gtk_css_parser_error (parser, "No property named '%s'", name);
+      g_free (name);
+      return FALSE;
+    }
+
+  g_free (name);
+
+  if (!_gtk_css_parser_try (parser, ":", TRUE))
+    {
+      _gtk_css_parser_error (parser, "Expected a ':'");
+      return FALSE;
+    }
+
+  value = _gtk_style_property_parse_value (property, parser);
+  if (value == NULL)
+    return FALSE;
+
+  if (!_gtk_css_parser_try (parser, ";", TRUE) &&
+      !_gtk_css_parser_begins_with (parser, '}'))
+    {
+      _gtk_css_parser_error (parser, "Junk at end of value");
+      _gtk_css_value_unref (value);
+      return FALSE;
+    }
+
+  if (GTK_IS_CSS_SHORTHAND_PROPERTY (property))
+    {
+      GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property);
+      gboolean animatable = FALSE;
+      guint i;
+
+      for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++)
+        {
+          GtkCssStyleProperty *child = _gtk_css_shorthand_property_get_subproperty (shorthand, i);
+          GtkCssValue *sub = _gtk_css_array_value_get_nth (value, i);
+          
+          animatable |= keyframes_set_value (keyframes, k, child, sub);
+        }
+
+      if (!animatable)
+        _gtk_css_parser_error (parser, "shorthand '%s' cannot be animated", _gtk_style_property_get_name (property));
+    }
+  else if (GTK_IS_CSS_STYLE_PROPERTY (property))
+    {
+      if (!keyframes_set_value (keyframes, k, GTK_CSS_STYLE_PROPERTY (property), value))
+        _gtk_css_parser_error (parser, "Cannot animate property '%s'", _gtk_style_property_get_name (property));
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+      
+  _gtk_css_value_unref (value);
+
+  return TRUE;
+}
+
+static gboolean
+parse_block (GtkCssKeyframes *keyframes,
+             guint            k,
+             GtkCssParser    *parser)
+{
+  if (!_gtk_css_parser_try (parser, "{", TRUE))
+    {
+      _gtk_css_parser_error (parser, "Expected closing bracket after keyframes block");
+      return FALSE;
+    }
+
+  while (!_gtk_css_parser_try (parser, "}", TRUE))
+    {
+      if (!parse_declaration (keyframes, k, parser))
+        _gtk_css_parser_resync (parser, TRUE, '}');
+
+      if (_gtk_css_parser_is_eof (parser))
+        {
+          _gtk_css_parser_error (parser, "Expected closing '}'after keyframes block");
+          return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+GtkCssKeyframes *
+_gtk_css_keyframes_parse (GtkCssParser *parser)
+{
+  GtkCssKeyframes *keyframes;
+  double progress;
+  guint k;
+
+  g_return_val_if_fail (parser != NULL, NULL);
+
+  keyframes = gtk_css_keyframes_new ();
+
+  while (!_gtk_css_parser_begins_with (parser, '}'))
+    {
+      if (_gtk_css_parser_try (parser, "from", TRUE))
+        progress = 0;
+      else if (_gtk_css_parser_try (parser, "to", TRUE))
+        progress = 1;
+      else if (_gtk_css_parser_try_double (parser, &progress) &&
+               _gtk_css_parser_try (parser, "%", TRUE))
+        {
+          if (progress < 0 || progress > 100)
+            {
+              /* XXX: should we skip over the block here? */
+              _gtk_css_parser_error (parser, "percentages must be between 0%% and 100%%");
+              _gtk_css_keyframes_unref (keyframes);
+              return NULL;
+            }
+          progress /= 100;
+        }
+      else
+        {
+          _gtk_css_parser_error (parser, "expected a percentage");
+          _gtk_css_keyframes_unref (keyframes);
+          return NULL;
+        }
+
+      k = gtk_css_keyframes_add_keyframe (keyframes, progress);
+
+      if (!parse_block (keyframes, k, parser))
+        {
+          _gtk_css_keyframes_unref (keyframes);
+          return NULL;
+        }
+    }
+
+  return keyframes;
+}
+
+static int
+compare_property_by_name (gconstpointer a,
+                          gconstpointer b,
+                          gpointer      data)
+{
+  GtkCssKeyframes *keyframes = data;
+
+  return strcmp (_gtk_style_property_get_name (GTK_STYLE_PROPERTY (
+                    _gtk_css_style_property_lookup_by_id (keyframes->property_ids[*(const guint *) a]))),
+                 _gtk_style_property_get_name (GTK_STYLE_PROPERTY (
+                    _gtk_css_style_property_lookup_by_id (keyframes->property_ids[*(const guint *) b]))));
+}
+
+void
+_gtk_css_keyframes_print (GtkCssKeyframes *keyframes,
+                          GString         *string)
+{
+  guint k, p;
+  guint *sorted;
+
+  g_return_if_fail (keyframes != NULL);
+  g_return_if_fail (string != NULL);
+
+  sorted = g_new (guint, keyframes->n_properties);
+  for (p = 0; p < keyframes->n_properties; p++)
+    sorted[p] = p;
+  g_qsort_with_data (sorted, keyframes->n_properties, sizeof (guint), compare_property_by_name, keyframes);
+
+  for (k = 0; k < keyframes->n_keyframes; k++)
+    {
+      /* useful for 0% and 100% which might be empty */
+      gboolean opened = FALSE;
+
+      for (p = 0; p < keyframes->n_properties; p++)
+        {
+          if (KEYFRAMES_VALUE (keyframes, k, sorted[p]) == NULL)
+            continue;
+
+          if (!opened)
+            {
+              if (keyframes->keyframe_progress[k] == 0.0)
+                g_string_append (string, "  from {\n");
+              else if (keyframes->keyframe_progress[k] == 1.0)
+                g_string_append (string, "  to {\n");
+              else
+                g_string_append_printf (string, "  %g%% {\n", keyframes->keyframe_progress[k] * 100);
+              opened = TRUE;
+            }
+          
+          g_string_append_printf (string, "    %s: ", _gtk_style_property_get_name (
+                                                        GTK_STYLE_PROPERTY (
+                                                          _gtk_css_style_property_lookup_by_id (
+                                                            keyframes->property_ids[sorted[p]]))));
+          _gtk_css_value_print (KEYFRAMES_VALUE (keyframes, k, sorted[p]), string);
+          g_string_append (string, ";\n");
+        }
+
+      if (opened)
+        g_string_append (string, "  }\n");
+    }
+
+  g_free (sorted);
+}
+
+GtkCssKeyframes *
+_gtk_css_keyframes_compute (GtkCssKeyframes *keyframes,
+                            GtkStyleContext *context)
+{
+  GtkCssKeyframes *resolved;
+  guint k, p;
+
+  g_return_val_if_fail (keyframes != NULL, NULL);
+  g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), NULL);
+
+  resolved = gtk_css_keyframes_new ();
+  resolved->n_keyframes = keyframes->n_keyframes;
+  resolved->keyframe_progress = g_memdup (keyframes->keyframe_progress, keyframes->n_keyframes * sizeof (double));
+  resolved->n_properties = keyframes->n_properties;
+  resolved->property_ids = g_memdup (keyframes->property_ids, keyframes->n_properties * sizeof (guint));
+  resolved->values = g_new0 (GtkCssValue *, resolved->n_keyframes * resolved->n_properties);
+
+  for (p = 0; p < resolved->n_properties; p++)
+    {
+      for (k = 0; k < resolved->n_keyframes; k++)
+        {
+          if (KEYFRAMES_VALUE (keyframes, k, p) == NULL)
+            continue;
+
+          KEYFRAMES_VALUE (resolved, k, p) =  _gtk_css_value_compute (KEYFRAMES_VALUE (keyframes, k, p),
+                                                                      resolved->property_ids[p],
+                                                                      context,
+                                                                      NULL);
+        }
+    }
+
+  return resolved;
+}
+
+guint
+_gtk_css_keyframes_get_n_properties (GtkCssKeyframes *keyframes)
+{
+  g_return_val_if_fail (keyframes != NULL, 0);
+
+  return keyframes->n_properties;
+}
+
+guint
+_gtk_css_keyframes_get_property_id (GtkCssKeyframes *keyframes,
+                                    guint            id)
+{
+  g_return_val_if_fail (keyframes != NULL, 0);
+  g_return_val_if_fail (id < keyframes->n_properties, 0);
+
+  return keyframes->property_ids[id];
+}
+
+GtkCssValue *
+_gtk_css_keyframes_get_value (GtkCssKeyframes *keyframes,
+                              guint            id,
+                              double           progress,
+                              GtkCssValue     *default_value)
+{
+  GtkCssValue *start_value, *end_value, *result;
+  double start_progress, end_progress;
+  guint k;
+
+  g_return_val_if_fail (keyframes != NULL, 0);
+  g_return_val_if_fail (id < keyframes->n_properties, 0);
+
+  start_value = default_value;
+  start_progress = 0.0;
+  end_value = default_value;
+  end_progress = 1.0;
+
+  for (k = 0; k < keyframes->n_keyframes; k++)
+    {
+      if (KEYFRAMES_VALUE (keyframes, k, id) == NULL)
+        continue;
+
+      if (keyframes->keyframe_progress[k] == progress)
+        {
+          return _gtk_css_value_ref (KEYFRAMES_VALUE (keyframes, k, id));
+        }
+      else if (keyframes->keyframe_progress[k] < progress)
+        {
+          start_value = KEYFRAMES_VALUE (keyframes, k, id);
+          start_progress = keyframes->keyframe_progress[k];
+        }
+      else
+        {
+          end_value = KEYFRAMES_VALUE (keyframes, k, id);
+          end_progress = keyframes->keyframe_progress[k];
+          break;
+        }
+    }
+
+  progress = (progress - start_progress) / (end_progress - start_progress);
+
+  result = _gtk_css_value_transition (start_value,
+                                      end_value,
+                                      keyframes->property_ids[id],
+                                      progress);
+
+  /* XXX: Dear spec, what's the correct thing to do here? */
+  if (result == NULL)
+    return _gtk_css_value_ref (start_value);
+
+  return result;
+}
+
diff --git a/gtk/gtkcsskeyframesprivate.h b/gtk/gtkcsskeyframesprivate.h
new file mode 100644
index 0000000..b08ce09
--- /dev/null
+++ b/gtk/gtkcsskeyframesprivate.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright  2012 Red Hat Inc.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Alexander Larsson <alexl gnome org>
+ */
+
+#ifndef __GTK_CSS_KEYFRAMES_PRIVATE_H__
+#define __GTK_CSS_KEYFRAMES_PRIVATE_H__
+
+#include "gtkcssparserprivate.h"
+#include "gtkcssvalueprivate.h"
+#include "gtktypes.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCssKeyframes GtkCssKeyframes;
+
+GtkCssKeyframes *   _gtk_css_keyframes_parse                  (GtkCssParser           *parser);
+
+GtkCssKeyframes *   _gtk_css_keyframes_ref                    (GtkCssKeyframes        *keyframes);
+void                _gtk_css_keyframes_unref                  (GtkCssKeyframes        *keyframes);
+
+void                _gtk_css_keyframes_print                  (GtkCssKeyframes        *keyframes,
+                                                               GString                *string);
+
+GtkCssKeyframes *   _gtk_css_keyframes_compute                (GtkCssKeyframes        *keyframes,
+                                                               GtkStyleContext        *context);
+
+guint               _gtk_css_keyframes_get_n_properties       (GtkCssKeyframes        *keyframes);
+guint               _gtk_css_keyframes_get_property_id        (GtkCssKeyframes        *keyframes,
+                                                               guint                   id);
+GtkCssValue *       _gtk_css_keyframes_get_value              (GtkCssKeyframes        *keyframes,
+                                                               guint                   id,
+                                                               double                  progress,
+                                                               GtkCssValue            *default_value);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_KEYFRAMES_PRIVATE_H__ */
diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c
index d4a3196..950df33 100644
--- a/gtk/gtkcssprovider.c
+++ b/gtk/gtkcssprovider.c
@@ -27,11 +27,12 @@
 
 #include "gtkbitmaskprivate.h"
 #include "gtkcssarrayvalueprivate.h"
-#include "gtkcssstylefuncsprivate.h"
+#include "gtkcsskeyframesprivate.h"
 #include "gtkcssparserprivate.h"
 #include "gtkcsssectionprivate.h"
 #include "gtkcssselectorprivate.h"
 #include "gtkcssshorthandpropertyprivate.h"
+#include "gtkcssstylefuncsprivate.h"
 #include "gtksymboliccolor.h"
 #include "gtkstyleprovider.h"
 #include "gtkstylecontextprivate.h"
@@ -1007,6 +1008,7 @@ struct _GtkCssProviderPrivate
   GScanner *scanner;
 
   GHashTable *symbolic_colors;
+  GHashTable *keyframes;
 
   GArray *rulesets;
   GResource *resource;
@@ -1423,6 +1425,9 @@ gtk_css_provider_init (GtkCssProvider *css_provider)
   priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                  (GDestroyNotify) g_free,
                                                  (GDestroyNotify) gtk_symbolic_color_unref);
+  priv->keyframes = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                           (GDestroyNotify) g_free,
+                                           (GDestroyNotify) _gtk_css_value_unref);
 }
 
 static void
@@ -1666,8 +1671,8 @@ gtk_css_provider_finalize (GObject *object)
 
   g_array_free (priv->rulesets, TRUE);
 
-  if (priv->symbolic_colors)
-    g_hash_table_destroy (priv->symbolic_colors);
+  g_hash_table_destroy (priv->symbolic_colors);
+  g_hash_table_destroy (priv->keyframes);
 
   if (priv->resource)
     {
@@ -1797,6 +1802,7 @@ gtk_css_provider_reset (GtkCssProvider *css_provider)
     }
 
   g_hash_table_remove_all (priv->symbolic_colors);
+  g_hash_table_remove_all (priv->keyframes);
 
   for (i = 0; i < priv->rulesets->len; i++)
     gtk_css_ruleset_clear (&g_array_index (priv->rulesets, GtkCssRuleset, i));
@@ -2083,6 +2089,70 @@ skip_semicolon:
   return TRUE;
 }
 
+static gboolean
+parse_keyframes (GtkCssScanner *scanner)
+{
+  GtkCssKeyframes *keyframes;
+  char *name;
+
+  gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
+
+  if (!_gtk_css_parser_try (scanner->parser, "@keyframes", TRUE))
+    {
+      gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
+      return FALSE;
+    }
+
+  name = _gtk_css_parser_try_ident (scanner->parser, TRUE);
+  if (name == NULL)
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Expected name for keyframes");
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      goto exit;
+    }
+
+  if (!_gtk_css_parser_try (scanner->parser, "{", TRUE))
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "Expected '{' for keyframes");
+      _gtk_css_parser_resync (scanner->parser, TRUE, 0);
+      goto exit;
+    }
+
+  keyframes = _gtk_css_keyframes_parse (scanner->parser);
+  if (keyframes == NULL)
+    {
+      _gtk_css_parser_resync (scanner->parser, TRUE, '}');
+      g_free (name);
+      goto exit;
+    }
+
+  g_hash_table_insert (scanner->provider->priv->keyframes, name, keyframes);
+
+  if (!_gtk_css_parser_try (scanner->parser, "}", TRUE))
+    {
+      gtk_css_provider_error_literal (scanner->provider,
+                                      scanner,
+                                      GTK_CSS_PROVIDER_ERROR,
+                                      GTK_CSS_PROVIDER_ERROR_SYNTAX,
+                                      "expected '}' after declarations");
+      if (!_gtk_css_parser_is_eof (scanner->parser))
+        _gtk_css_parser_resync (scanner->parser, FALSE, 0);
+    }
+
+exit:
+  gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
+
+  return TRUE;
+}
+
 static void
 parse_at_keyword (GtkCssScanner *scanner)
 {
@@ -2092,6 +2162,8 @@ parse_at_keyword (GtkCssScanner *scanner)
     return;
   if (parse_binding_set (scanner))
     return;
+  if (parse_keyframes (scanner))
+    return;
 
   else
     {
@@ -2900,6 +2972,33 @@ gtk_css_provider_print_colors (GHashTable *colors,
   g_list_free (keys);
 }
 
+static void
+gtk_css_provider_print_keyframes (GHashTable *keyframes,
+                                  GString    *str)
+{
+  GList *keys, *walk;
+
+  keys = g_hash_table_get_keys (keyframes);
+  /* so the output is identical for identical styles */
+  keys = g_list_sort (keys, (GCompareFunc) strcmp);
+
+  for (walk = keys; walk; walk = walk->next)
+    {
+      const char *name = walk->data;
+      GtkCssKeyframes *keyframe = g_hash_table_lookup (keyframes, (gpointer) name);
+
+      if (str->len > 0)
+        g_string_append (str, "\n");
+      g_string_append (str, "@keyframes ");
+      g_string_append (str, name);
+      g_string_append (str, " {\n");
+      _gtk_css_keyframes_print (keyframe, str);
+      g_string_append (str, "}\n");
+    }
+
+  g_list_free (keys);
+}
+
 /**
  * gtk_css_provider_to_string:
  * @provider: the provider to write to a string
@@ -2930,6 +3029,7 @@ gtk_css_provider_to_string (GtkCssProvider *provider)
   str = g_string_new ("");
 
   gtk_css_provider_print_colors (priv->symbolic_colors, str);
+  gtk_css_provider_print_keyframes (priv->keyframes, str);
 
   for (i = 0; i < priv->rulesets->len; i++)
     {
diff --git a/gtk/gtkcsssection.h b/gtk/gtkcsssection.h
index 7901a43..5321cdf 100644
--- a/gtk/gtkcsssection.h
+++ b/gtk/gtkcsssection.h
@@ -40,6 +40,9 @@ G_BEGIN_DECLS
  * @GTK_CSS_SECTION_DECLARATION: The section defines the declaration of
  *   a CSS variable.
  * @GTK_CSS_SECTION_VALUE: The section defines the value of a CSS declaration.
+ * @GTK_CSS_SECTION_KEYFRAMES: The section defines keyframes. See <ulink
+ *   url="http://dev.w3.org/csswg/css3-animations/#keyframes";>CSS
+ *   animations</ulink> for details. Since 3.4
  *
  * The different types of sections indicate parts of a CSS document as
  * parsed by GTK's CSS parser. They are oriented towards the CSS grammar
@@ -60,7 +63,8 @@ typedef enum
   GTK_CSS_SECTION_RULESET,
   GTK_CSS_SECTION_SELECTOR,
   GTK_CSS_SECTION_DECLARATION,
-  GTK_CSS_SECTION_VALUE
+  GTK_CSS_SECTION_VALUE,
+  GTK_CSS_SECTION_KEYFRAMES
 } GtkCssSectionType;
 
 typedef struct _GtkCssSection GtkCssSection;



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