[gnome-builder] egg-state-machine: add EggStateMachine



commit 97ac887a6cdf5538595ee620002d4b62dd6982ad
Author: Christian Hergert <christian hergert me>
Date:   Mon May 4 16:40:09 2015 -0700

    egg-state-machine: add EggStateMachine
    
    This builds upon EggSignalGroup and EggBindingSet to allow you to lazily
    connect signals and property bindings when in different states of the
    machine.
    
    To transition to a new set of bindings and signals, simply change the
    current state with egg_state_machine_transition().
    
    Currently, only g_signal_connect_object() signals are supported. More
    will be added later (See EggSignalGroup).
    
    Additionally, EggBindingSet only supports basic bindings. Mappable
    bindings are still needed.

 contrib/egg/Makefile.am         |    2 +
 contrib/egg/egg-state-machine.c |  484 +++++++++++++++++++++++++++++++++++++++
 contrib/egg/egg-state-machine.h |   80 +++++++
 tests/Makefile.am               |    6 +
 tests/test-egg-state-machine.c  |  261 +++++++++++++++++++++
 5 files changed, 833 insertions(+), 0 deletions(-)
---
diff --git a/contrib/egg/Makefile.am b/contrib/egg/Makefile.am
index dc7d0d5..1c7d128 100644
--- a/contrib/egg/Makefile.am
+++ b/contrib/egg/Makefile.am
@@ -7,6 +7,8 @@ libegg_la_SOURCES = \
         egg-search-bar.h \
        egg-signal-group.c \
        egg-signal-group.h \
+        egg-state-machine.c \
+        egg-state-machine.h \
        $(NULL)
 
 libegg_la_CFLAGS = $(EGG_CFLAGS)
diff --git a/contrib/egg/egg-state-machine.c b/contrib/egg/egg-state-machine.c
new file mode 100644
index 0000000..43b9b7d
--- /dev/null
+++ b/contrib/egg/egg-state-machine.c
@@ -0,0 +1,484 @@
+/* egg-state-machine.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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 General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "egg-binding-set.h"
+#include "egg-signal-group.h"
+#include "egg-state-machine.h"
+
+typedef struct
+{
+  gchar *state;
+
+  /*
+   * Containers for lazily bound signals and bindings.
+   *
+   * Each is a GHashTable indexed by state name, containing another
+   * GHashTable indexed by source object.
+   */
+  GHashTable *binding_sets_by_state;
+  GHashTable *signal_groups_by_state;
+
+  gsize       sequence;
+} EggStateMachinePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (EggStateMachine, egg_state_machine, G_TYPE_OBJECT)
+G_DEFINE_QUARK (EggStateMachineError, egg_state_machine_error)
+
+enum {
+  PROP_0,
+  PROP_STATE,
+  LAST_PROP
+};
+
+enum {
+  TRANSITION,
+  LAST_SIGNAL
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+static guint gSignals [LAST_SIGNAL];
+
+static gboolean
+egg_state_transition_accumulator (GSignalInvocationHint *hint,
+                                  GValue                *return_value,
+                                  const GValue          *handler_return,
+                                  gpointer               data)
+{
+  EggStateTransition ret;
+
+  ret = g_value_get_enum (handler_return);
+
+  if (ret == EGG_STATE_TRANSITION_INVALID)
+    {
+      g_value_set_enum (return_value, ret);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+const gchar *
+egg_state_machine_get_state (EggStateMachine *self)
+{
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+
+  g_return_val_if_fail (EGG_IS_STATE_MACHINE (self), NULL);
+
+  return priv->state;
+}
+
+static void
+egg_state_machine_do_transition (EggStateMachine *self,
+                                 const gchar     *new_state)
+{
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+  GHashTableIter iter;
+  const gchar *key;
+  GHashTable *value;
+
+  g_assert (EGG_IS_STATE_MACHINE (self));
+  g_assert (new_state != NULL);
+
+  priv->sequence++;
+
+  g_free (priv->state);
+  priv->state = g_strdup (new_state);
+
+  g_hash_table_iter_init (&iter, priv->signal_groups_by_state);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+    {
+      GHashTable *signal_groups = value;
+      GHashTableIter groups_iter;
+      EggSignalGroup *signal_group;
+      gpointer instance;
+      gboolean enabled = (g_strcmp0 (key, new_state) == 0);
+
+      g_hash_table_iter_init (&groups_iter, signal_groups);
+
+      while (g_hash_table_iter_next (&groups_iter, &instance, (gpointer *)&signal_group))
+        {
+          g_assert (G_IS_OBJECT (instance));
+          g_assert (EGG_IS_SIGNAL_GROUP (signal_group));
+
+          egg_signal_group_set_target (signal_group, enabled ? instance : NULL);
+        }
+    }
+
+  g_hash_table_iter_init (&iter, priv->binding_sets_by_state);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+    {
+      GHashTable *binding_sets = value;
+      GHashTableIter groups_iter;
+      EggBindingSet *binding_set;
+      gpointer instance;
+      gboolean enabled = (g_strcmp0 (key, new_state) == 0);
+
+      g_hash_table_iter_init (&groups_iter, binding_sets);
+
+      while (g_hash_table_iter_next (&groups_iter, &instance, (gpointer *)&binding_set))
+        {
+          g_assert (G_IS_OBJECT (instance));
+          g_assert (EGG_IS_BINDING_SET (binding_set));
+
+          egg_binding_set_set_source (binding_set, enabled ? instance : NULL);
+        }
+    }
+}
+
+/**
+ * egg_state_machine_transition:
+ * @self: A #EggStateMachine.
+ * @new_state: The name of the new state.
+ * @error: A location for a #GError, or %NULL.
+ *
+ * Attempts to change the state of the state machine to @new_state.
+ *
+ * This operation can fail, in which %EGG_STATE_TRANSITION_INVALID will be
+ * returned and @error will be set.
+ *
+ * Upon success, %EGG_STATE_TRANSITION_SUCCESS is returned.
+ *
+ * Returns: An #EggStateTransition.
+ */
+EggStateTransition
+egg_state_machine_transition (EggStateMachine  *self,
+                              const gchar      *new_state,
+                              GError          **error)
+{
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+  g_autofree gchar *old_state = NULL;
+  g_autofree gchar *new_state_copy = NULL;
+  EggStateTransition ret = EGG_STATE_TRANSITION_IGNORED;
+  g_autoptr(GError) local_error = NULL;
+  gsize sequence;
+
+  g_return_val_if_fail (EGG_IS_STATE_MACHINE (self), FALSE);
+  g_return_val_if_fail (new_state != NULL, FALSE);
+
+  if (g_strcmp0 (new_state, priv->state) == 0)
+    return TRUE;
+
+  /* Be careful with reentrancy. */
+
+  old_state = g_strdup (priv->state);
+  new_state_copy = g_strdup (new_state);
+  sequence = priv->sequence;
+
+  g_signal_emit (self, gSignals [TRANSITION], 0, old_state, new_state, &local_error, &ret);
+
+  if (ret == EGG_STATE_TRANSITION_INVALID)
+    {
+      if (local_error == NULL)
+        local_error = g_error_new_literal (EGG_STATE_MACHINE_ERROR,
+                                           EGG_STATE_MACHINE_ERROR_INVALID_TRANSITION,
+                                           "Unknown error during state transition.");
+      g_propagate_error (error, local_error);
+      local_error = NULL;
+      return ret;
+    }
+
+  if (sequence == priv->sequence)
+    {
+      egg_state_machine_do_transition (self, new_state);
+      g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_STATE]);
+    }
+
+  return EGG_STATE_TRANSITION_SUCCESS;
+}
+
+static EggStateTransition
+egg_state_machine_real_transition (EggStateMachine  *self,
+                                   const gchar      *old_state,
+                                   const gchar      *new_state,
+                                   GError          **error)
+{
+  return EGG_STATE_TRANSITION_IGNORED;
+}
+
+static void
+egg_state_machine_finalize (GObject *object)
+{
+  EggStateMachine *self = (EggStateMachine *)object;
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+
+  g_clear_pointer (&priv->state, g_free);
+  g_clear_pointer (&priv->binding_sets_by_state, g_hash_table_unref);
+  g_clear_pointer (&priv->signal_groups_by_state, g_hash_table_unref);
+
+  G_OBJECT_CLASS (egg_state_machine_parent_class)->finalize (object);
+}
+
+static void
+egg_state_machine_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  EggStateMachine *self = EGG_STATE_MACHINE (object);
+
+  switch (prop_id)
+    {
+    case PROP_STATE:
+      g_value_set_string (value, egg_state_machine_get_state (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+egg_state_machine_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  EggStateMachine *self = EGG_STATE_MACHINE (object);
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_STATE:
+      priv->state = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+egg_state_machine_class_init (EggStateMachineClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = egg_state_machine_finalize;
+  object_class->get_property = egg_state_machine_get_property;
+  object_class->set_property = egg_state_machine_set_property;
+
+  klass->transition = egg_state_machine_real_transition;
+
+  gParamSpecs [PROP_STATE] =
+    g_param_spec_string ("state",
+                         _("State"),
+                         _("The current state of the machine."),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+
+  /**
+   * EggStateMachine::transition:
+   * @self: An #EggStateMachine.
+   * @old_state: The current state.
+   * @new_state: The new state.
+   * @error: (ctype GError**): A location for a #GError, or %NULL.
+   *
+   * Determines if the transition is allowed.
+   *
+   * If the state transition is invalid, @error should be set to a new #GError.
+   *
+   * Returns: %TRUE if the state transition is acceptable.
+   */
+  gSignals [TRANSITION] =
+    g_signal_new ("transition",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (EggStateMachineClass, transition),
+                  egg_state_transition_accumulator, NULL,
+                  NULL,
+                  EGG_TYPE_STATE_TRANSITION,
+                  3,
+                  G_TYPE_STRING,
+                  G_TYPE_STRING,
+                  G_TYPE_POINTER);
+}
+
+static void
+egg_state_machine_init (EggStateMachine *self)
+{
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+
+  priv->binding_sets_by_state =
+    g_hash_table_new_full (g_str_hash,
+                           g_str_equal,
+                           g_free,
+                           (GDestroyNotify)g_hash_table_destroy);
+
+  priv->signal_groups_by_state =
+    g_hash_table_new_full (g_str_hash,
+                           g_str_equal,
+                           g_free,
+                           (GDestroyNotify)g_hash_table_destroy);
+}
+
+void
+egg_state_machine_connect_object (EggStateMachine *self,
+                                  const gchar     *state,
+                                  gpointer         instance,
+                                  const gchar     *detailed_signal,
+                                  GCallback        callback,
+                                  gpointer         user_data,
+                                  GConnectFlags    flags)
+{
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+  GHashTable *signal_groups;
+  EggSignalGroup *signal_group;
+  gboolean created = FALSE;
+
+  g_return_if_fail (EGG_IS_STATE_MACHINE (self));
+  g_return_if_fail (state != NULL);
+  g_return_if_fail (G_IS_OBJECT (instance));
+  g_return_if_fail (detailed_signal != NULL);
+  g_return_if_fail (callback != NULL);
+
+  signal_groups = g_hash_table_lookup (priv->signal_groups_by_state, state);
+
+  if (signal_groups == NULL)
+    {
+      signal_groups = g_hash_table_new_full (g_direct_hash,
+                                             g_direct_equal,
+                                             NULL,
+                                             g_object_unref);
+      g_hash_table_insert (priv->signal_groups_by_state, g_strdup (state), signal_groups);
+    }
+
+  g_assert (signal_groups != NULL);
+
+  signal_group = g_hash_table_lookup (signal_groups, instance);
+
+  if (signal_group == NULL)
+    {
+      signal_group = egg_signal_group_new (G_TYPE_FROM_INSTANCE (instance));
+      g_hash_table_insert (signal_groups, instance, signal_group);
+      created = TRUE;
+    }
+
+  egg_signal_group_connect_object (signal_group, detailed_signal, callback, user_data, flags);
+
+  if ((created == TRUE) && (g_strcmp0 (state, priv->state) == 0))
+    egg_signal_group_set_target (signal_group, instance);
+}
+
+void
+egg_state_machine_bind (EggStateMachine *self,
+                        const gchar     *state,
+                        gpointer         source,
+                        const gchar     *source_property,
+                        gpointer         target,
+                        const gchar     *target_property,
+                        GBindingFlags    flags)
+{
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+  GHashTable *binding_sets;
+  EggBindingSet *binding_set;
+  gboolean created = FALSE;
+
+  g_return_if_fail (EGG_IS_STATE_MACHINE (self));
+  g_return_if_fail (state != NULL);
+  g_return_if_fail (G_IS_OBJECT (source));
+  g_return_if_fail (source_property != NULL);
+  g_return_if_fail (G_IS_OBJECT (target));
+  g_return_if_fail (target_property != NULL);
+
+  /* Use G_BINDING_SYNC_CREATE as we lazily connect them. */
+  flags |= G_BINDING_SYNC_CREATE;
+
+  binding_sets = g_hash_table_lookup (priv->binding_sets_by_state, state);
+
+  if (binding_sets == NULL)
+    {
+      binding_sets = g_hash_table_new_full (g_direct_hash,
+                                            g_direct_equal,
+                                            NULL,
+                                            g_object_unref);
+      g_hash_table_insert (priv->binding_sets_by_state, g_strdup (state), binding_sets);
+    }
+
+  g_assert (binding_sets != NULL);
+
+  binding_set = g_hash_table_lookup (binding_sets, source);
+
+  if (binding_set == NULL)
+    {
+      binding_set = egg_binding_set_new ();
+      g_hash_table_insert (binding_sets, source, binding_set);
+      created = TRUE;
+    }
+
+  egg_binding_set_bind (binding_set,
+                        source_property,
+                        target,
+                        target_property,
+                        flags);
+
+  if ((created == TRUE) && (g_strcmp0 (state, priv->state) == 0))
+    egg_binding_set_set_source (binding_set, source);
+}
+
+EggStateMachine *
+egg_state_machine_new (void)
+{
+  return g_object_new (EGG_TYPE_STATE_MACHINE, NULL);
+}
+
+GType
+egg_state_machine_error_get_type (void)
+{
+  static gsize type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      static const GEnumValue values[] = {
+        { EGG_STATE_MACHINE_ERROR_INVALID_TRANSITION,
+          "EGG_STATE_MACHINE_ERROR_INVALID_TRANSITION",
+          "invalid-transition" },
+        { 0 }
+      };
+      gsize _type_id;
+
+      _type_id = g_enum_register_static ("EggStateMachineError", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
+
+GType
+egg_state_transition_get_type (void)
+{
+  static gsize type_id;
+
+  if (g_once_init_enter (&type_id))
+    {
+      static const GEnumValue values[] = {
+        { EGG_STATE_TRANSITION_IGNORED, "EGG_STATE_TRANSITION_IGNORED", "ignored" },
+        { EGG_STATE_TRANSITION_INVALID, "EGG_STATE_TRANSITION_INVALID", "invalid" },
+        { EGG_STATE_TRANSITION_SUCCESS, "EGG_STATE_TRANSITION_SUCCESS", "success" },
+        { 0 }
+      };
+      gsize _type_id;
+
+      _type_id = g_enum_register_static ("EggStateTransition", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
diff --git a/contrib/egg/egg-state-machine.h b/contrib/egg/egg-state-machine.h
new file mode 100644
index 0000000..5b38700
--- /dev/null
+++ b/contrib/egg/egg-state-machine.h
@@ -0,0 +1,80 @@
+/* egg-state-machine.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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 General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EGG_STATE_MACHINE_H
+#define EGG_STATE_MACHINE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_STATE_MACHINE       (egg_state_machine_get_type())
+#define EGG_TYPE_STATE_MACHINE_ERROR (egg_state_machine_get_type())
+#define EGG_STATE_MACHINE_ERROR      (egg_state_machine_error_quark())
+#define EGG_TYPE_STATE_TRANSITION    (egg_state_transition_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (EggStateMachine, egg_state_machine, EGG, STATE_MACHINE, GObject)
+
+typedef enum
+{
+  EGG_STATE_TRANSITION_IGNORED = 0,
+  EGG_STATE_TRANSITION_SUCCESS = 1,
+  EGG_STATE_TRANSITION_INVALID = 2,
+} EggStateTransition;
+
+typedef enum
+{
+  EGG_STATE_MACHINE_ERROR_INVALID_TRANSITION = 1,
+} EggStateMachineError;
+
+struct _EggStateMachineClass
+{
+  GObjectClass parent;
+
+  EggStateTransition (*transition) (EggStateMachine  *self,
+                                    const gchar      *old_state,
+                                    const gchar      *new_state,
+                                    GError          **error);
+};
+
+GType               egg_state_transition_get_type    (void);
+GType               egg_state_machine_error_get_type (void);
+GQuark              egg_state_machine_error_quark    (void);
+EggStateMachine    *egg_state_machine_new            (void);
+EggStateTransition  egg_state_machine_transition     (EggStateMachine  *self,
+                                                      const gchar      *new_state,
+                                                      GError          **error);
+const gchar        *egg_state_machine_get_state      (EggStateMachine  *self);
+void                egg_state_machine_bind           (EggStateMachine  *self,
+                                                      const gchar      *state,
+                                                      gpointer          source,
+                                                      const gchar      *source_property,
+                                                      gpointer          target,
+                                                      const gchar      *target_property,
+                                                      GBindingFlags     flags);
+void                egg_state_machine_connect_object (EggStateMachine  *self,
+                                                      const gchar      *state,
+                                                      gpointer          instance,
+                                                      const gchar      *detailed_signal,
+                                                      GCallback         callback,
+                                                      gpointer          user_data,
+                                                      GConnectFlags     flags);
+
+G_END_DECLS
+
+#endif /* EGG_STATE_MACHINE_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7fccf8c..4c30f42 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -96,6 +96,12 @@ test_ide_source_view_CFLAGS = $(tests_cflags)
 test_ide_source_view_LDADD = $(tests_libs)
 
 
+noinst_PROGRAMS += test-egg-state-machine
+test_egg_state_machine_SOURCES = test-egg-state-machine.c
+test_egg_state_machine_CFLAGS = $(tests_cflags) -I$(top_srcdir)/contrib/egg
+test_egg_state_machine_LDADD = $(tests_libs) $(top_builddir)/contrib/egg/libegg.la
+
+
 EXTRA_DIST += \
        data/project1/configure.ac \
        data/project1/.editorconfig \
diff --git a/tests/test-egg-state-machine.c b/tests/test-egg-state-machine.c
new file mode 100644
index 0000000..6a8db03
--- /dev/null
+++ b/tests/test-egg-state-machine.c
@@ -0,0 +1,261 @@
+/* test-egg-state-machine.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "egg-state-machine.h"
+
+struct _TestObject
+{
+  GObject parent_instance;
+
+  gint obj1_count;
+  gint obj2_count;
+
+  gchar *str;
+};
+
+#define TEST_TYPE_OBJECT (test_object_get_type())
+G_DECLARE_FINAL_TYPE (TestObject, test_object, TEST, OBJECT, GObject)
+G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_STRING,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void
+get_property (GObject    *object,
+              guint       prop_id,
+              GValue     *value,
+              GParamSpec *pspec)
+{
+  TestObject *obj = (TestObject *)object;
+
+  switch (prop_id) {
+  case PROP_STRING:
+    g_value_set_string (value, obj->str);
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+  }
+}
+
+static void
+set_property (GObject      *object,
+              guint         prop_id,
+              const GValue *value,
+              GParamSpec   *pspec)
+{
+  TestObject *obj = (TestObject *)object;
+
+  switch (prop_id) {
+  case PROP_STRING:
+    g_free (obj->str);
+    obj->str = g_value_dup_string (value);
+    g_object_notify_by_pspec (object, pspec);
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+  }
+}
+
+static void
+finalize (GObject *object)
+{
+  TestObject *self = (TestObject *)object;
+
+  g_free (self->str);
+
+  G_OBJECT_CLASS (test_object_parent_class)->finalize (object);
+}
+
+static void
+test_object_class_init (TestObjectClass *klass)
+{
+  GObjectClass *obj_class = G_OBJECT_CLASS (klass);
+
+  g_signal_new ("frobnicate", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, 
G_TYPE_NONE, 0);
+
+  obj_class->finalize = finalize;
+  obj_class->get_property = get_property;
+  obj_class->set_property = set_property;
+
+  gParamSpecs [PROP_STRING] =
+    g_param_spec_string ("string",
+                         "string",
+                         "string",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (obj_class, LAST_PROP, gParamSpecs);
+}
+
+static void
+test_object_init (TestObject *self)
+{
+}
+
+static void
+obj1_frobnicate (TestObject *dummy,
+                 TestObject *source)
+{
+  g_assert (TEST_IS_OBJECT (dummy));
+  g_assert (TEST_IS_OBJECT (source));
+
+  dummy->obj1_count++;
+}
+
+static void
+obj2_frobnicate (TestObject *dummy,
+                 TestObject *source)
+{
+  g_assert (TEST_IS_OBJECT (dummy));
+  g_assert (TEST_IS_OBJECT (source));
+
+  dummy->obj2_count++;
+}
+
+static void
+assert_prop_equal (gpointer     obja,
+                   gpointer     objb,
+                   const gchar *propname)
+{
+  GParamSpec *pspec;
+  GValue va = { 0 };
+  GValue vb = { 0 };
+
+  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (obja), propname);
+  g_assert (pspec != NULL);
+
+  g_value_init (&va, pspec->value_type);
+  g_value_init (&vb, pspec->value_type);
+
+  g_object_get_property (obja, propname, &va);
+  g_object_get_property (objb, propname, &vb);
+
+#define ADD_NUMBER_CHECK(NAME, name) \
+  case G_TYPE_##NAME: \
+    g_assert_cmpint(g_value_get_##name(&va), ==, g_value_get_##name(&vb)); \
+    break
+
+  switch (pspec->value_type)
+    {
+    case G_TYPE_STRING:
+      g_assert_cmpstr (g_value_get_string (&va), ==, g_value_get_string (&vb));
+      break;
+
+    ADD_NUMBER_CHECK (INT, int);
+    ADD_NUMBER_CHECK (UINT, uint);
+    ADD_NUMBER_CHECK (FLOAT, float);
+    ADD_NUMBER_CHECK (DOUBLE, double);
+    ADD_NUMBER_CHECK (BOOLEAN, boolean);
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  g_value_unset (&va);
+  g_value_unset (&vb);
+}
+
+static void
+test_state_machine_basic (void)
+{
+  EggStateMachine *machine;
+  EggStateTransition ret;
+  TestObject *dummy;
+  TestObject *obj1;
+  TestObject *obj2;
+  GError *error = NULL;
+
+  machine = egg_state_machine_new ();
+  g_object_add_weak_pointer (G_OBJECT (machine), (gpointer *)&machine);
+
+  dummy = g_object_new (TEST_TYPE_OBJECT, NULL);
+  obj1 = g_object_new (TEST_TYPE_OBJECT, NULL);
+  obj2 = g_object_new (TEST_TYPE_OBJECT, NULL);
+
+#if 0
+  g_print ("obj1=%p  obj2=%p  dummy=%p\n", obj1, obj2, dummy);
+#endif
+
+  egg_state_machine_connect_object (machine, "state1", obj1, "frobnicate", G_CALLBACK (obj1_frobnicate), 
dummy, G_CONNECT_SWAPPED);
+  egg_state_machine_connect_object (machine, "state2", obj2, "frobnicate", G_CALLBACK (obj2_frobnicate), 
dummy, G_CONNECT_SWAPPED);
+
+  egg_state_machine_bind (machine, "state1", obj1, "string", dummy, "string", 0);
+  egg_state_machine_bind (machine, "state2", obj2, "string", dummy, "string", 0);
+
+  ret = egg_state_machine_transition (machine, "state1", &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (ret, ==, EGG_STATE_TRANSITION_SUCCESS);
+  g_assert_cmpint (dummy->obj1_count, ==, 0);
+  g_assert_cmpint (dummy->obj2_count, ==, 0);
+
+  g_signal_emit_by_name (obj1, "frobnicate");
+  g_assert_cmpint (dummy->obj1_count, ==, 1);
+  g_assert_cmpint (dummy->obj2_count, ==, 0);
+
+  g_signal_emit_by_name (obj2, "frobnicate");
+  g_assert_cmpint (dummy->obj1_count, ==, 1);
+  g_assert_cmpint (dummy->obj2_count, ==, 0);
+
+  ret = egg_state_machine_transition (machine, "state2", &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (ret, ==, EGG_STATE_TRANSITION_SUCCESS);
+
+  g_signal_emit_by_name (obj1, "frobnicate");
+  g_assert_cmpint (dummy->obj1_count, ==, 1);
+  g_assert_cmpint (dummy->obj2_count, ==, 0);
+
+  g_signal_emit_by_name (obj2, "frobnicate");
+  g_assert_cmpint (dummy->obj1_count, ==, 1);
+  g_assert_cmpint (dummy->obj2_count, ==, 1);
+
+  g_object_set (obj2, "string", "obj2", NULL);
+  g_object_set (obj1, "string", "obj1", NULL);
+  assert_prop_equal (obj2, dummy, "string");
+
+  ret = egg_state_machine_transition (machine, "state1", &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (ret, ==, EGG_STATE_TRANSITION_SUCCESS);
+
+  assert_prop_equal (obj1, dummy, "string");
+  g_object_set (obj1, "string", "obj1-1", NULL);
+  assert_prop_equal (obj1, dummy, "string");
+  g_object_set (obj2, "string", "obj2-1", NULL);
+  assert_prop_equal (obj1, dummy, "string");
+
+  g_object_unref (machine);
+  g_assert (machine == NULL);
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/Egg/StateMachine/basic", test_state_machine_basic);
+  return g_test_run ();
+}


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