[gnome-builder] egg-state-machine: add EggStateMachine
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] egg-state-machine: add EggStateMachine
- Date: Mon, 4 May 2015 23:40:29 +0000 (UTC)
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]