[clutter] actor: Add ClutterActorIter



commit 58ffcfb10ed8b7fc0e299035c8b049967f8fc0ef
Author: Emmanuele Bassi <ebassi linux intel com>
Date:   Wed Jan 25 15:27:57 2012 +0000

    actor: Add ClutterActorIter
    
    Iterating over children and ancestors of an actor is a relatively common
    operation. Currently, you only have one option: start a for() loop, get
    the first child of the actor, and advance to the next sibling for the
    list of children; or start a for() loop and advance to the parent of the
    actor.
    
    These operations can be easily done through the ClutterActor API, but
    they all require going through the public API, and performing multiple
    type checks on the arguments.
    
    Along with the DOM API, it would be nice to have an ancillary, utility
    API that uses an iterator structure to hold the state, and can be
    advanced in a loop.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=668669

 clutter/clutter-actor.c                    |  186 ++++++++++++++++++++++++++++
 clutter/clutter-actor.h                    |   29 +++++
 clutter/clutter-types.h                    |   13 ++
 clutter/clutter.symbols                    |    4 +
 doc/reference/clutter/clutter-sections.txt |    7 +
 tests/conform/Makefile.am                  |    1 +
 tests/conform/test-actor-iter.c            |  129 +++++++++++++++++++
 tests/conform/test-conform-main.c          |   31 +++--
 8 files changed, 386 insertions(+), 14 deletions(-)
---
diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c
index b11e562..8999e4e 100644
--- a/clutter/clutter-actor.c
+++ b/clutter/clutter-actor.c
@@ -395,6 +395,14 @@ struct _ClutterActorPrivate
 
   gint n_children;
 
+  /* tracks whenever the children of an actor are changed; the
+   * age is incremented by 1 whenever an actor is added or
+   * removed. the age is not incremented when the first or the
+   * last child pointers are changed, or when grandchildren of
+   * an actor are changed.
+   */
+  gint age;
+
   gchar *name; /* a non-unique name, used for debugging */
   guint32 id; /* unique id, used for backward compatibility */
 
@@ -3500,6 +3508,8 @@ clutter_actor_remove_child_internal (ClutterActor                 *self,
 
   self->priv->n_children -= 1;
 
+  self->priv->age += 1;
+
   /* clutter_actor_reparent() will emit ::parent-set for us */
   if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child))
     g_signal_emit (child, actor_signals[PARENT_SET], 0, self);
@@ -10040,6 +10050,8 @@ clutter_actor_add_child_internal (ClutterActor              *self,
 
   self->priv->n_children += 1;
 
+  self->priv->age += 1;
+
   /* if push_internal() has been called then we automatically set
    * the flag on the actor
    */
@@ -15770,3 +15782,177 @@ clutter_actor_get_last_child (ClutterActor *self)
 
   return self->priv->last_child;
 }
+
+/* easy way to have properly named fields instead of the dummy ones
+ * we use in the public structure
+ */
+typedef struct _RealActorIter
+{
+  ClutterActor *root;           /* dummy1 */
+  ClutterActor *current;        /* dummy2 */
+  gpointer padding_1;           /* dummy3 */
+  gint age;                     /* dummy4 */
+  gpointer padding_2;           /* dummy5 */
+} RealActorIter;
+
+/**
+ * clutter_actor_iter_init:
+ * @iter: a #ClutterActorIter
+ * @root: a #ClutterActor
+ *
+ * Initializes a #ClutterActorIter, which can then be used to iterate
+ * efficiently over a section of the scene graph, and associates it
+ * with @root.
+ *
+ * Modifying the scene graph section that contains @root will invalidate
+ * the iterator.
+ *
+ * |[
+ *   ClutterActorIter iter;
+ *   ClutterActor *child;
+ *
+ *   clutter_actor_iter_init (&iter, container);
+ *   while (clutter_actor_iter_next (&iter, &child))
+ *     {
+ *       /&ast; do something with child &ast;/
+ *     }
+ * ]|
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_iter_init (ClutterActorIter *iter,
+                         ClutterActor     *root)
+{
+  RealActorIter *ri = (RealActorIter *) iter;
+
+  g_return_if_fail (iter != NULL);
+  g_return_if_fail (CLUTTER_IS_ACTOR (root));
+
+  ri->root = root;
+  ri->current = NULL;
+  ri->age = root->priv->age;
+}
+
+/**
+ * clutter_actor_iter_next:
+ * @iter: a #ClutterActorIter
+ * @child: (out): return location for a #ClutterActor
+ *
+ * Advances the @iter and retrieves the next child of the root #ClutterActor
+ * that was used to initialize the #ClutterActorIterator.
+ *
+ * If the iterator can advance, this function returns %TRUE and sets the
+ * @child argument.
+ *
+ * If the iterator cannot advance, this function returns %FALSE, and
+ * the contents of @child are undefined.
+ *
+ * Return value: %TRUE if the iterator could advance, and %FALSE otherwise.
+ *
+ * Since: 1.10
+ */
+gboolean
+clutter_actor_iter_next (ClutterActorIter  *iter,
+                         ClutterActor     **child)
+{
+  RealActorIter *ri = (RealActorIter *) iter;
+
+  g_return_val_if_fail (iter != NULL, FALSE);
+  g_return_val_if_fail (ri->root != NULL, FALSE);
+#ifndef G_DISABLE_ASSERT
+  g_return_val_if_fail (ri->age == ri->root->priv->age, FALSE);
+#endif
+
+  if (ri->current == NULL)
+    ri->current = ri->root->priv->first_child;
+  else
+    ri->current = ri->current->priv->next_sibling;
+
+  if (child != NULL)
+    *child = ri->current;
+
+  return ri->current != NULL;
+}
+
+/**
+ * clutter_actor_iter_next:
+ * @iter: a #ClutterActorIter
+ * @child: (out): return location for a #ClutterActor
+ *
+ * Advances the @iter and retrieves the previous child of the root
+ * #ClutterActor that was used to initialize the #ClutterActorIterator.
+ *
+ * If the iterator can advance, this function returns %TRUE and sets the
+ * @child argument.
+ *
+ * If the iterator cannot advance, this function returns %FALSE, and
+ * the contents of @child are undefined.
+ *
+ * Return value: %TRUE if the iterator could advance, and %FALSE otherwise.
+ *
+ * Since: 1.10
+ */
+gboolean
+clutter_actor_iter_prev (ClutterActorIter  *iter,
+                         ClutterActor     **child)
+{
+  RealActorIter *ri = (RealActorIter *) iter;
+
+  g_return_val_if_fail (iter != NULL, FALSE);
+  g_return_val_if_fail (ri->root != NULL, FALSE);
+#ifndef G_DISABLE_ASSERT
+  g_return_val_if_fail (ri->age == ri->root->priv->age, FALSE);
+#endif
+
+  if (ri->current == NULL)
+    ri->current = ri->root->priv->last_child;
+  else
+    ri->current = ri->current->priv->prev_sibling;
+
+  if (child != NULL)
+    *child = ri->current;
+
+  return ri->current != NULL;
+}
+
+/**
+ * clutter_actor_iter_remove:
+ * @iter: a #ClutterActorIter
+ *
+ * Safely removes the #ClutterActor currently pointer to by the iterator
+ * from its parent.
+ *
+ * This function can only be called after clutter_actor_iter_next() or
+ * clutter_actor_iter_prev() returned %TRUE, and cannot be called more
+ * than once for the same actor.
+ *
+ * This function will call clutter_actor_remove_child() internally.
+ *
+ * Since: 1.10
+ */
+void
+clutter_actor_iter_remove (ClutterActorIter *iter)
+{
+  RealActorIter *ri = (RealActorIter *) iter;
+  ClutterActor *cur;
+
+  g_return_if_fail (iter != NULL);
+  g_return_if_fail (ri->root != NULL);
+#ifndef G_DISABLE_ASSERT
+  g_return_if_fail (ri->age == ri->root->priv->age);
+#endif
+  g_return_if_fail (ri->current != NULL);
+
+  cur = ri->current;
+
+  if (cur != NULL)
+    {
+      ri->current = cur->priv->prev_sibling;
+
+      clutter_actor_remove_child_internal (ri->root, cur,
+                                           REMOVE_CHILD_DEFAULT_FLAGS);
+
+      ri->age += 1;
+    }
+}
diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h
index c7e021e..279259a 100644
--- a/clutter/clutter-actor.h
+++ b/clutter/clutter-actor.h
@@ -263,6 +263,27 @@ struct _ClutterActorClass
   gpointer _padding_dummy[28];
 };
 
+/**
+ * ClutterActorIter:
+ *
+ * An iterator structure that allows to efficiently iterate over a
+ * section of the scene graph.
+ *
+ * The contents of the <structname>ClutterActorIter</structname> structure
+ * are private and should only be accessed using the provided API.
+ *
+ * Since: 1.10
+ */
+struct _ClutterActorIter
+{
+  /*< private >*/
+  gpointer CLUTTER_PRIVATE_FIELD (dummy1);
+  gpointer CLUTTER_PRIVATE_FIELD (dummy2);
+  gpointer CLUTTER_PRIVATE_FIELD (dummy3);
+  gint     CLUTTER_PRIVATE_FIELD (dummy4);
+  gpointer CLUTTER_PRIVATE_FIELD (dummy5);
+};
+
 GType clutter_actor_get_type (void) G_GNUC_CONST;
 
 ClutterActor *        clutter_actor_new                       (void);
@@ -486,6 +507,14 @@ void                  clutter_actor_set_child_at_index        (ClutterActor
                                                                ClutterActor          *child,
                                                                gint                   index_);
 
+void                  clutter_actor_iter_init                 (ClutterActorIter      *iter,
+                                                               ClutterActor          *root);
+gboolean              clutter_actor_iter_next                 (ClutterActorIter      *iter,
+                                                               ClutterActor         **child);
+gboolean              clutter_actor_iter_prev                 (ClutterActorIter      *iter,
+                                                               ClutterActor         **child);
+void                  clutter_actor_iter_remove               (ClutterActorIter      *iter);
+
 /* Transformations */
 gboolean              clutter_actor_is_rotated                (ClutterActor          *self);
 gboolean              clutter_actor_is_scaled                 (ClutterActor          *self);
diff --git a/clutter/clutter-types.h b/clutter/clutter-types.h
index f9c109e..2136874 100644
--- a/clutter/clutter-types.h
+++ b/clutter/clutter-types.h
@@ -48,6 +48,18 @@ G_BEGIN_DECLS
 #define CLUTTER_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f)
 #endif
 
+/* some structures are meant to be opaque and still be allocated on the stack;
+ * in order to avoid people poking at their internals, we use this macro to
+ * ensure that users don't accidentally access a struct private members.
+ *
+ * we use the CLUTTER_COMPILATION define to allow us easier access, though.
+ */
+#ifdef CLUTTER_COMPILATION
+#define CLUTTER_PRIVATE_FIELD(x)        x
+#else
+#define CLUTTER_PRIVATE_FIELD(x)        clutter_private_ ## x
+#endif
+
 #define CLUTTER_TYPE_ACTOR_BOX          (clutter_actor_box_get_type ())
 #define CLUTTER_TYPE_FOG                (clutter_fog_get_type ())
 #define CLUTTER_TYPE_GEOMETRY           (clutter_geometry_get_type ())
@@ -65,6 +77,7 @@ typedef struct _ClutterChildMeta        ClutterChildMeta;
 typedef struct _ClutterLayoutMeta       ClutterLayoutMeta;
 typedef struct _ClutterActorMeta        ClutterActorMeta;
 typedef struct _ClutterLayoutManager    ClutterLayoutManager;
+typedef struct _ClutterActorIter        ClutterActorIter;
 
 typedef struct _ClutterAlpha            ClutterAlpha;
 typedef struct _ClutterAnimatable       ClutterAnimatable; /* dummy */
diff --git a/clutter/clutter.symbols b/clutter/clutter.symbols
index 294200f..98510fd 100644
--- a/clutter/clutter.symbols
+++ b/clutter/clutter.symbols
@@ -157,6 +157,10 @@ clutter_actor_insert_child_below
 clutter_actor_is_in_clone_paint
 clutter_actor_is_rotated
 clutter_actor_is_scaled
+clutter_actor_iter_init
+clutter_actor_iter_next
+clutter_actor_iter_prev
+clutter_actor_iter_remove
 clutter_actor_lower
 clutter_actor_lower_bottom
 clutter_actor_map
diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt
index 789f945..55d7a9a 100644
--- a/doc/reference/clutter/clutter-sections.txt
+++ b/doc/reference/clutter/clutter-sections.txt
@@ -403,6 +403,13 @@ clutter_actor_set_child_at_index
 clutter_actor_set_child_below_sibling
 clutter_actor_contains
 clutter_actor_get_stage
+ClutterActorIter
+clutter_actor_iter_init
+clutter_actor_iter_next
+clutter_actor_iter_prev
+clutter_actor_iter_remove
+
+<SUBSECTION>
 clutter_actor_push_internal
 clutter_actor_pop_internal
 clutter_actor_set_parent
diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am
index ccc07ac..59d5e3e 100644
--- a/tests/conform/Makefile.am
+++ b/tests/conform/Makefile.am
@@ -62,6 +62,7 @@ units_sources += \
 	test-actor-graph.c		\
 	test-actor-destroy.c		\
 	test-actor-invariants.c 	\
+	test-actor-iter.c		\
 	test-actor-layout.c		\
 	test-actor-size.c		\
 	test-anchors.c                  \
diff --git a/tests/conform/test-actor-iter.c b/tests/conform/test-actor-iter.c
new file mode 100644
index 0000000..fdfb0d5
--- /dev/null
+++ b/tests/conform/test-actor-iter.c
@@ -0,0 +1,129 @@
+#include <glib.h>
+#include <clutter/clutter.h>
+#include "test-conform-common.h"
+
+void
+actor_iter_traverse_children (TestConformSimpleFixture *fixture G_GNUC_UNUSED,
+                              gconstpointer dummy G_GNUC_UNUSED)
+{
+  ClutterActorIter iter;
+  ClutterActor *actor;
+  ClutterActor *child;
+  int i, n_actors;
+
+  actor = clutter_actor_new ();
+  clutter_actor_set_name (actor, "root");
+  g_object_ref_sink (actor);
+
+  n_actors = g_random_int_range (10, 50);
+  for (i = 0; i < n_actors; i++)
+    {
+      char *name;
+
+      name = g_strdup_printf ("actor%d", i);
+      child = clutter_actor_new ();
+      clutter_actor_set_name (child, name);
+
+      clutter_actor_add_child (actor, child);
+
+      g_free (name);
+    }
+
+  g_assert_cmpint (clutter_actor_get_n_children (actor), ==, n_actors);
+
+  i = 0;
+  clutter_actor_iter_init (&iter, actor);
+  while (clutter_actor_iter_next (&iter, &child))
+    {
+      g_assert (CLUTTER_IS_ACTOR (child));
+      g_assert (clutter_actor_get_parent (child) == actor);
+
+      if (g_test_verbose ())
+        g_print ("actor %d = '%s'\n", i, clutter_actor_get_name (child));
+
+      if (i == 0)
+        g_assert (child == clutter_actor_get_first_child (actor));
+
+      if (i == (n_actors - 1))
+        g_assert (child == clutter_actor_get_last_child (actor));
+
+      i += 1;
+    }
+
+  g_assert_cmpint (i, ==, n_actors);
+
+  i = 0;
+  clutter_actor_iter_init (&iter, actor);
+  while (clutter_actor_iter_prev (&iter, &child))
+    {
+      g_assert (CLUTTER_IS_ACTOR (child));
+      g_assert (clutter_actor_get_parent (child) == actor);
+
+      if (g_test_verbose ())
+        g_print ("actor %d = '%s'\n", i, clutter_actor_get_name (child));
+
+      if (i == 0)
+        g_assert (child == clutter_actor_get_last_child (actor));
+
+      if (i == (n_actors - 1))
+        g_assert (child == clutter_actor_get_first_child (actor));
+
+      i += 1;
+    }
+
+  g_object_unref (actor);
+}
+
+void
+actor_iter_traverse_remove (TestConformSimpleFixture *fixture G_GNUC_UNUSED,
+                            gconstpointer dummy G_GNUC_UNUSED)
+{
+  ClutterActorIter iter;
+  ClutterActor *actor;
+  ClutterActor *child;
+  int i, n_actors;
+
+  actor = clutter_actor_new ();
+  clutter_actor_set_name (actor, "root");
+  g_object_ref_sink (actor);
+
+  n_actors = g_random_int_range (10, 50);
+  for (i = 0; i < n_actors; i++)
+    {
+      char *name;
+
+      name = g_strdup_printf ("actor%d", i);
+      child = clutter_actor_new ();
+      clutter_actor_set_name (child, name);
+
+      clutter_actor_add_child (actor, child);
+
+      g_free (name);
+    }
+
+  g_assert_cmpint (clutter_actor_get_n_children (actor), ==, n_actors);
+
+  i = 0;
+  clutter_actor_iter_init (&iter, actor);
+  while (clutter_actor_iter_next (&iter, &child))
+    {
+      g_assert (CLUTTER_IS_ACTOR (child));
+      g_assert (clutter_actor_get_parent (child) == actor);
+
+      if (g_test_verbose ())
+        g_print ("actor %d = '%s'\n", i, clutter_actor_get_name (child));
+
+      if (i == 0)
+        g_assert (child == clutter_actor_get_first_child (actor));
+
+      if (i == (n_actors - 1))
+        g_assert (child == clutter_actor_get_last_child (actor));
+
+      clutter_actor_iter_remove (&iter);
+
+      i += 1;
+    }
+
+  g_assert_cmpint (i, ==, n_actors);
+  g_assert_cmpint (0, ==, clutter_actor_get_n_children (actor));
+}
diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c
index a643133..1a6f608 100644
--- a/tests/conform/test-conform-main.c
+++ b/tests/conform/test-conform-main.c
@@ -145,20 +145,23 @@ main (int argc, char **argv)
   TEST_CONFORM_SIMPLE ("/actor", test_offscreen_redirect);
   TEST_CONFORM_SIMPLE ("/actor", test_shader_effect);
 
-  TEST_CONFORM_SIMPLE ("/invariants", test_initial_state);
-  TEST_CONFORM_SIMPLE ("/invariants", test_shown_not_parented);
-  TEST_CONFORM_SIMPLE ("/invariants", test_realized);
-  TEST_CONFORM_SIMPLE ("/invariants", test_realize_not_recursive);
-  TEST_CONFORM_SIMPLE ("/invariants", test_map_recursive);
-  TEST_CONFORM_SIMPLE ("/invariants", test_mapped);
-  TEST_CONFORM_SIMPLE ("/invariants", test_show_on_set_parent);
-  TEST_CONFORM_SIMPLE ("/invariants", test_clone_no_map);
-  TEST_CONFORM_SIMPLE ("/invariants", test_contains);
-  TEST_CONFORM_SIMPLE ("/invariants", default_stage);
-
-  TEST_CONFORM_SIMPLE ("/opacity", test_label_opacity);
-  TEST_CONFORM_SIMPLE ("/opacity", test_rectangle_opacity);
-  TEST_CONFORM_SIMPLE ("/opacity", test_paint_opacity);
+  TEST_CONFORM_SIMPLE ("/actor/iter", actor_iter_traverse_children);
+  TEST_CONFORM_SIMPLE ("/actor/iter", actor_iter_traverse_remove);
+
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_initial_state);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_shown_not_parented);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_realized);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_realize_not_recursive);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_map_recursive);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_mapped);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_show_on_set_parent);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_clone_no_map);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", test_contains);
+  TEST_CONFORM_SIMPLE ("/actor/invariants", default_stage);
+
+  TEST_CONFORM_SIMPLE ("/actor/opacity", test_label_opacity);
+  TEST_CONFORM_SIMPLE ("/actor/opacity", test_rectangle_opacity);
+  TEST_CONFORM_SIMPLE ("/actor/opacity", test_paint_opacity);
 
   TEST_CONFORM_SIMPLE ("/text", text_utf8_validation);
   TEST_CONFORM_SIMPLE ("/text", text_set_empty);



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