[gnome-builder/wip/state-machine] egg: add new EggStateMachine prototype



commit ddd12164d34adfb72a10245f2e205edc8bdf10d1
Author: Christian Hergert <christian hergert me>
Date:   Tue May 19 20:36:41 2015 -0700

    egg: add new EggStateMachine prototype
    
    I'd like to be able to use this for situations like the greeter where we
    need to manage potentially a bunch of little state changes. Having all of
    that defined in UI would be very nice. It is also unlikely to be the
    only place that is useful. I imagine dialogs like the new project dialog
    and project properties could use this too. However, GtkAssistant may
    be more appropriate for the create/clone/etc workflow.
    
    This has very little project for references. So that definitely needs to
    be iterated upon.
    
    I think it shows enough promise to go forward with.

 contrib/egg/Makefile.am                   |    5 +
 contrib/egg/egg-state-machine-action.c    |  260 +++++++++++
 contrib/egg/egg-state-machine-action.h    |   35 ++
 contrib/egg/egg-state-machine-buildable.c |  665 ++++++++++++++++++++++++++++
 contrib/egg/egg-state-machine-buildable.h |   30 ++
 contrib/egg/egg-state-machine-private.h   |   59 +++
 contrib/egg/egg-state-machine.c           |  679 ++++++++++++-----------------
 contrib/egg/egg-state-machine.h           |   81 ++--
 tests/test-egg-state-machine.c            |   63 +--
 9 files changed, 1379 insertions(+), 498 deletions(-)
---
diff --git a/contrib/egg/Makefile.am b/contrib/egg/Makefile.am
index 562c65e..3f327be 100644
--- a/contrib/egg/Makefile.am
+++ b/contrib/egg/Makefile.am
@@ -15,6 +15,11 @@ libegg_la_SOURCES = \
        egg-signal-group.h \
        egg-state-machine.c \
        egg-state-machine.h \
+       egg-state-machine-action.c \
+       egg-state-machine-action.h \
+       egg-state-machine-buildable.c \
+       egg-state-machine-buildable.h \
+       egg-state-machine-private.h \
        egg-task-cache.c \
        egg-task-cache.h \
        $(NULL)
diff --git a/contrib/egg/egg-state-machine-action.c b/contrib/egg/egg-state-machine-action.c
new file mode 100644
index 0000000..879af83
--- /dev/null
+++ b/contrib/egg/egg-state-machine-action.c
@@ -0,0 +1,260 @@
+/* egg-state-machine-action.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-state-machine.h"
+#include "egg-state-machine-action.h"
+
+struct _EggStateMachineAction
+{
+  GObject          parent_instance;
+
+  gchar           *name;
+  EggStateMachine *state_machine;
+};
+
+static void action_iface_init (GActionInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EggStateMachineAction, egg_state_machine_action, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION, action_iface_init))
+
+enum {
+  PROP_0,
+  PROP_NAME,
+  PROP_STATE_MACHINE,
+  LAST_PROP,
+
+  PROP_ENABLED,
+  PROP_PARAMETER_TYPE,
+  PROP_STATE,
+  PROP_STATE_TYPE,
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static gboolean
+egg_state_machine_action_get_enabled (GAction *action)
+{
+  return TRUE;
+}
+
+static const gchar *
+egg_state_machine_action_get_name (GAction *action)
+{
+  EggStateMachineAction *self = (EggStateMachineAction *)action;
+
+  g_return_val_if_fail (EGG_IS_STATE_MACHINE_ACTION (self), NULL);
+
+  return self->name;
+}
+
+static const GVariantType *
+egg_state_machine_action_get_parameter_type (GAction *action)
+{
+  return G_VARIANT_TYPE_STRING;
+}
+
+static const GVariantType *
+egg_state_machine_action_get_state_type (GAction *action)
+{
+  return G_VARIANT_TYPE_STRING;
+}
+
+static GVariant *
+egg_state_machine_action_get_state (GAction *action)
+{
+  EggStateMachineAction *self = (EggStateMachineAction *)action;
+  const gchar *state;
+
+  g_return_val_if_fail (EGG_IS_STATE_MACHINE_ACTION (self), NULL);
+
+  state = egg_state_machine_get_state (self->state_machine);
+
+  if (state != NULL)
+    return g_variant_ref_sink (g_variant_new_string (state));
+
+  return NULL;
+}
+
+static void
+egg_state_machine_action_state_set_cb (EggStateMachineAction *self,
+                                       GParamSpec            *pspec,
+                                       EggStateMachine       *state_machine)
+{
+  g_return_if_fail (EGG_IS_STATE_MACHINE_ACTION (self));
+
+  g_object_notify (G_OBJECT (self), "state");
+}
+
+static void
+egg_state_machine_action_set_state_machine (EggStateMachineAction *self,
+                                            EggStateMachine       *state_machine)
+{
+  g_return_if_fail (EGG_IS_STATE_MACHINE_ACTION (self));
+  g_return_if_fail (EGG_IS_STATE_MACHINE (state_machine));
+  g_return_if_fail (self->state_machine == NULL);
+
+  if (g_set_object (&self->state_machine, state_machine))
+    {
+      g_signal_connect_object (state_machine,
+                               "notify::state",
+                               G_CALLBACK (egg_state_machine_action_state_set_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+    }
+}
+
+static void
+egg_state_machine_action_activate (GAction  *action,
+                                   GVariant *param)
+{
+  EggStateMachineAction *self = (EggStateMachineAction *)action;
+
+  g_assert (EGG_IS_STATE_MACHINE_ACTION (self));
+  g_assert (EGG_IS_STATE_MACHINE (self->state_machine));
+
+  if ((param != NULL) && g_variant_is_of_type (param, G_VARIANT_TYPE_STRING))
+    {
+      const gchar *state;
+
+      if ((state = g_variant_get_string (param, NULL)))
+        egg_state_machine_set_state (self->state_machine, state);
+    }
+}
+
+static void
+egg_state_machine_action_finalize (GObject *object)
+{
+  EggStateMachineAction *self = (EggStateMachineAction *)object;
+
+  g_clear_pointer (&self->name, g_free);
+  g_clear_object (&self->state_machine);
+
+  G_OBJECT_CLASS (egg_state_machine_action_parent_class)->finalize (object);
+}
+
+static void
+egg_state_machine_action_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  EggStateMachineAction *self = EGG_STATE_MACHINE_ACTION (object);
+  GAction *action = (GAction *)object;
+
+  switch (prop_id)
+    {
+    case PROP_ENABLED:
+      g_value_set_boolean (value, egg_state_machine_action_get_enabled (action));
+      break;
+
+    case PROP_NAME:
+      g_value_set_string (value, egg_state_machine_action_get_name (action));
+      break;
+
+    case PROP_PARAMETER_TYPE:
+      g_value_set_boxed (value, egg_state_machine_action_get_parameter_type (action));
+      break;
+
+    case PROP_STATE:
+      g_value_set_boxed (value, egg_state_machine_action_get_state (action));
+      break;
+
+    case PROP_STATE_MACHINE:
+      g_value_set_object (value, self->state_machine);
+      break;
+
+    case PROP_STATE_TYPE:
+      g_value_set_boxed (value, egg_state_machine_action_get_state_type (action));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+egg_state_machine_action_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  EggStateMachineAction *self = EGG_STATE_MACHINE_ACTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_STATE_MACHINE:
+      egg_state_machine_action_set_state_machine (self, g_value_get_object (value));
+      break;
+
+    case PROP_NAME:
+      self->name = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+egg_state_machine_action_class_init (EggStateMachineActionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = egg_state_machine_action_finalize;
+  object_class->get_property = egg_state_machine_action_get_property;
+  object_class->set_property = egg_state_machine_action_set_property;
+
+  gParamSpecs [PROP_NAME] =
+    g_param_spec_string ("name",
+                         _("Name"),
+                         _("The name of the action"),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  gParamSpecs [PROP_STATE_MACHINE] =
+    g_param_spec_object ("state-machine",
+                         _("State Machine"),
+                         _("State Machine"),
+                         EGG_TYPE_STATE_MACHINE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+
+  g_object_class_override_property (object_class, PROP_PARAMETER_TYPE, "parameter-type");
+  g_object_class_override_property (object_class, PROP_ENABLED, "enabled");
+  g_object_class_override_property (object_class, PROP_STATE_TYPE, "state-type");
+  g_object_class_override_property (object_class, PROP_STATE, "state");
+}
+
+static void
+egg_state_machine_action_init (EggStateMachineAction *self)
+{
+}
+
+static void
+action_iface_init (GActionInterface *iface)
+{
+  iface->get_enabled = egg_state_machine_action_get_enabled;
+  iface->get_name = egg_state_machine_action_get_name;
+  iface->get_parameter_type = egg_state_machine_action_get_parameter_type;
+  iface->get_state_type = egg_state_machine_action_get_state_type;
+  iface->get_state = egg_state_machine_action_get_state;
+  iface->activate = egg_state_machine_action_activate;
+}
diff --git a/contrib/egg/egg-state-machine-action.h b/contrib/egg/egg-state-machine-action.h
new file mode 100644
index 0000000..a806448
--- /dev/null
+++ b/contrib/egg/egg-state-machine-action.h
@@ -0,0 +1,35 @@
+/* egg-state-machine-action.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_ACTION_H
+#define EGG_STATE_MACHINE_ACTION_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_STATE_MACHINE_ACTION (egg_state_machine_action_get_type())
+
+G_DECLARE_FINAL_TYPE (EggStateMachineAction, egg_state_machine_action, EGG, STATE_MACHINE_ACTION, GObject)
+
+GAction *egg_state_machine_action_new (EggStateMachine *state_machine,
+                                       const gchar     *name);
+
+G_END_DECLS
+
+#endif /* EGG_STATE_MACHINE_ACTION_H */
diff --git a/contrib/egg/egg-state-machine-buildable.c b/contrib/egg/egg-state-machine-buildable.c
new file mode 100644
index 0000000..f69c2f9
--- /dev/null
+++ b/contrib/egg/egg-state-machine-buildable.c
@@ -0,0 +1,665 @@
+/* egg-state-machine-buildable.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/>.
+ */
+
+#define G_LOG_DOMAIN "egg-state-machine"
+
+#include <errno.h>
+#include <glib/gi18n.h>
+
+#include "egg-state-machine.h"
+#include "egg-state-machine-buildable.h"
+#include "egg-state-machine-private.h"
+
+typedef struct
+{
+  EggStateMachine *self;
+  GtkBuilder      *builder;
+  GQueue          *stack;
+} StatesParserData;
+
+typedef enum
+{
+  STACK_ITEM_OBJECT,
+  STACK_ITEM_STATE,
+  STACK_ITEM_PROPERTY,
+} StackItemType;
+
+typedef struct
+{
+  StackItemType type;
+  union {
+    struct {
+      gchar  *id;
+      GSList *classes;
+      GSList *properties;
+    } object;
+    struct {
+      gchar  *name;
+      GSList *objects;
+    } state;
+    struct {
+      gchar *name;
+      gchar *bind_source;
+      gchar *bind_property;
+      gchar *text;
+      GBindingFlags bind_flags;
+    } property;
+  } u;
+} StackItem;
+
+static GtkBuildableIface *egg_state_machine_parent_buildable;
+
+static void
+stack_item_free (StackItem *item)
+{
+  switch (item->type)
+    {
+    case STACK_ITEM_OBJECT:
+      g_free (item->u.object.id);
+      g_slist_free_full (item->u.object.classes, g_free);
+      break;
+
+    case STACK_ITEM_STATE:
+      g_free (item->u.state.name);
+      g_slist_free_full (item->u.state.objects, (GDestroyNotify)stack_item_free);
+      break;
+
+    case STACK_ITEM_PROPERTY:
+      g_free (item->u.property.name);
+      g_free (item->u.property.bind_source);
+      g_free (item->u.property.bind_property);
+      g_free (item->u.property.text);
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  g_slice_free (StackItem, item);
+}
+
+static StackItem *
+stack_item_new (StackItemType type)
+{
+  StackItem *item;
+
+  item = g_slice_new0 (StackItem);
+  item->type = type;
+
+  return item;
+}
+
+static void
+add_state (StatesParserData  *parser_data,
+           StackItem         *item,
+           GError           **error)
+{
+  GSList *iter;
+
+  g_assert (parser_data != NULL);
+  g_assert (item != NULL);
+  g_assert (item->type == STACK_ITEM_STATE);
+
+  for (iter = item->u.state.objects; iter; iter = iter->next)
+    {
+      StackItem *stack_obj = iter->data;
+      GObject *object;
+      GSList *prop_iter;
+      GSList *style_iter;
+
+      g_assert (stack_obj->type == STACK_ITEM_OBJECT);
+      g_assert (stack_obj->u.object.id != NULL);
+
+      object = gtk_builder_get_object (parser_data->builder, stack_obj->u.object.id);
+
+      if (object == NULL)
+        {
+          g_critical ("Failed to locate object %s for binding.", stack_obj->u.object.id);
+          continue;
+        }
+
+      if (GTK_IS_WIDGET (object))
+        for (style_iter = stack_obj->u.object.classes; style_iter; style_iter = style_iter->next)
+          egg_state_machine_add_style (parser_data->self,
+                                       item->u.state.name,
+                                       GTK_WIDGET (object),
+                                       style_iter->data);
+
+      for (prop_iter = stack_obj->u.object.properties; prop_iter; prop_iter = prop_iter->next)
+        {
+          StackItem *stack_prop = prop_iter->data;
+          GObject *bind_source;
+
+          g_assert (stack_prop->type == STACK_ITEM_PROPERTY);
+
+          if ((stack_prop->u.property.bind_source != NULL) &&
+              (stack_prop->u.property.bind_property != NULL) &&
+              (bind_source = gtk_builder_get_object (parser_data->builder, 
stack_prop->u.property.bind_source)))
+            {
+              egg_state_machine_add_binding (parser_data->self,
+                                             item->u.state.name,
+                                             bind_source,
+                                             stack_prop->u.property.bind_property,
+                                             object,
+                                             stack_prop->u.property.name,
+                                             stack_prop->u.property.bind_flags);
+            }
+          else if (stack_prop->u.property.text != NULL)
+            {
+              GParamSpec *pspec;
+              GValue value = { 0 };
+
+              pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), 
stack_prop->u.property.name);
+
+              if (pspec == NULL)
+                {
+                  g_set_error (error,
+                               GTK_BUILDER_ERROR,
+                               GTK_BUILDER_ERROR_INVALID_PROPERTY,
+                               "No such property: %s",
+                               stack_prop->u.property.name);
+                  return;
+                }
+
+              if (g_type_is_a (pspec->value_type, G_TYPE_OBJECT))
+                {
+                  GObject *relative;
+
+                  relative = gtk_builder_get_object (parser_data->builder, stack_prop->u.property.text);
+                  g_value_init (&value, pspec->value_type);
+                  g_value_set_object (&value, relative);
+                }
+              else if (!gtk_builder_value_from_string (parser_data->builder,
+                                                       pspec,
+                                                       stack_prop->u.property.text,
+                                                       &value,
+                                                       error))
+                {
+                  return;
+                }
+
+              egg_state_machine_add_property (parser_data->self,
+                                              item->u.state.name,
+                                              object,
+                                              stack_prop->u.property.name,
+                                              &value);
+
+              g_value_unset (&value);
+            }
+        }
+    }
+}
+
+static void
+add_object (StatesParserData *parser_data,
+            StackItem        *parent,
+            StackItem        *item)
+{
+  g_assert (parser_data != NULL);
+  g_assert (parent != NULL);
+  g_assert (parent->type == STACK_ITEM_STATE);
+  g_assert (item != NULL);
+  g_assert (item->type == STACK_ITEM_OBJECT);
+
+  parent->u.state.objects = g_slist_prepend (parent->u.state.objects, item);
+}
+
+static void
+add_property (StatesParserData *parser_data,
+              StackItem        *parent,
+              StackItem        *item)
+{
+  g_assert (parser_data != NULL);
+  g_assert (parent != NULL);
+  g_assert (parent->type == STACK_ITEM_OBJECT);
+  g_assert (item != NULL);
+  g_assert (item->type == STACK_ITEM_PROPERTY);
+
+  parent->u.object.properties = g_slist_prepend (parent->u.object.properties, item);
+}
+
+static gboolean
+check_parent (GMarkupParseContext  *context,
+              const gchar          *element_name,
+              GError              **error)
+{
+  const GSList *stack;
+  const gchar *parent_name;
+  const gchar *our_name;
+
+  stack = g_markup_parse_context_get_element_stack (context);
+  our_name = stack->data;
+  parent_name = stack->next ? stack->next->data : "";
+
+  if (g_strcmp0 (parent_name, element_name) != 0)
+    {
+      gint line;
+      gint col;
+
+      g_markup_parse_context_get_position (context, &line, &col);
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_INVALID_TAG,
+                   "%d:%d: Element <%s> found in <%s>, expected <%s>.",
+                   line, col, our_name, parent_name, element_name);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+/*
+ * flags_from_string:
+ *
+ * gtkbuilder.c
+ *
+ * Copyright (C) 1998-2002 James Henstridge <james daa com au>
+ * Copyright (C) 2006-2007 Async Open Source,
+ *                         Johan Dahlin <jdahlin async com br>,
+ *                         Henrique Romano <henrique async com br>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+gboolean
+flags_from_string (GType         type,
+                   const gchar  *string,
+                   guint        *flags_value,
+                   GError      **error)
+{
+  GFlagsClass *fclass;
+  gchar *endptr, *prevptr;
+  guint i, j, value;
+  gchar *flagstr;
+  GFlagsValue *fv;
+  const gchar *flag;
+  gunichar ch;
+  gboolean eos, ret;
+
+  g_return_val_if_fail (G_TYPE_IS_FLAGS (type), FALSE);
+  g_return_val_if_fail (string != 0, FALSE);
+
+  ret = TRUE;
+
+  endptr = NULL;
+  errno = 0;
+  value = g_ascii_strtoull (string, &endptr, 0);
+  if (errno == 0 && endptr != string) /* parsed a number */
+    *flags_value = value;
+  else
+    {
+      fclass = g_type_class_ref (type);
+
+      flagstr = g_strdup (string);
+      for (value = i = j = 0; ; i++)
+        {
+
+          eos = flagstr[i] == '\0';
+
+          if (!eos && flagstr[i] != '|')
+            continue;
+
+          flag = &flagstr[j];
+          endptr = &flagstr[i];
+
+          if (!eos)
+            {
+              flagstr[i++] = '\0';
+              j = i;
+            }
+
+          /* trim spaces */
+          for (;;)
+            {
+              ch = g_utf8_get_char (flag);
+              if (!g_unichar_isspace (ch))
+                break;
+              flag = g_utf8_next_char (flag);
+            }
+
+          while (endptr > flag)
+            {
+              prevptr = g_utf8_prev_char (endptr);
+              ch = g_utf8_get_char (prevptr);
+              if (!g_unichar_isspace (ch))
+                break;
+              endptr = prevptr;
+            }
+
+          if (endptr > flag)
+            {
+              *endptr = '\0';
+              fv = g_flags_get_value_by_name (fclass, flag);
+
+              if (!fv)
+                fv = g_flags_get_value_by_nick (fclass, flag);
+
+              if (fv)
+                value |= fv->value;
+              else
+                {
+                  g_set_error (error,
+                               GTK_BUILDER_ERROR,
+                               GTK_BUILDER_ERROR_INVALID_VALUE,
+                               "Unknown flag: `%s'",
+                               flag);
+                  ret = FALSE;
+                  break;
+                }
+            }
+
+          if (eos)
+            {
+              *flags_value = value;
+              break;
+            }
+        }
+
+      g_free (flagstr);
+
+      g_type_class_unref (fclass);
+    }
+
+  return ret;
+}
+
+static void
+states_parser_start_element (GMarkupParseContext  *context,
+                             const gchar          *element_name,
+                             const gchar         **attribute_names,
+                             const gchar         **attribute_values,
+                             gpointer              user_data,
+                             GError              **error)
+{
+  StatesParserData *parser_data = user_data;
+  StackItem *item;
+
+  g_assert (context != NULL);
+  g_assert (element_name != NULL);
+  g_assert (parser_data != NULL);
+
+  if (g_strcmp0 (element_name, "state") == 0)
+    {
+      const gchar *name;
+
+      if (!check_parent (context, "states", error))
+        return;
+
+      if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_INVALID))
+        return;
+
+      item = stack_item_new (STACK_ITEM_STATE);
+      item->u.state.name = g_strdup (name);
+      g_queue_push_head (parser_data->stack, item);
+    }
+  else if (g_strcmp0 (element_name, "states") == 0)
+    {
+      if (!check_parent (context, "object", error))
+        return;
+    }
+  else if (g_strcmp0 (element_name, "object") == 0)
+    {
+      const gchar *id;
+
+      if (!check_parent (context, "state", error))
+        return;
+
+      if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
+                                        G_MARKUP_COLLECT_STRING, "id", &id,
+                                        G_MARKUP_COLLECT_INVALID))
+        return;
+
+      item = stack_item_new (STACK_ITEM_OBJECT);
+      item->u.object.id = g_strdup (id);
+      g_queue_push_head (parser_data->stack, item);
+    }
+  else if (g_strcmp0 (element_name, "property") == 0)
+    {
+      const gchar *name = NULL;
+      const gchar *bind_source = NULL;
+      const gchar *bind_property = NULL;
+      const gchar *bind_flags_str = NULL;
+      GBindingFlags bind_flags = 0;
+
+      if (!check_parent (context, "object", error))
+        return;
+
+      if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-source", 
&bind_source,
+                                        G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-property", 
&bind_property,
+                                        G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-flags", 
&bind_flags_str,
+                                        G_MARKUP_COLLECT_INVALID))
+        return;
+
+      if ((bind_flags_str != NULL) && !flags_from_string (G_TYPE_BINDING_FLAGS, bind_flags_str, &bind_flags, 
error))
+        return;
+
+      item = stack_item_new (STACK_ITEM_PROPERTY);
+      item->u.property.name = g_strdup (name);
+      item->u.property.bind_source = g_strdup (bind_source);
+      item->u.property.bind_property = g_strdup (bind_property);
+      item->u.property.bind_flags = bind_flags;
+      g_queue_push_head (parser_data->stack, item);
+    }
+  else if (g_strcmp0 (element_name, "style") == 0)
+    {
+      if (!check_parent (context, "object", error))
+        return;
+    }
+  else if (g_strcmp0 (element_name, "class") == 0)
+    {
+      const gchar *name = NULL;
+
+      if (!check_parent (context, "style", error))
+        return;
+
+      if (!g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_INVALID))
+        return;
+
+      item = g_queue_peek_head (parser_data->stack);
+      g_assert (item->type == STACK_ITEM_OBJECT);
+
+      item->u.object.classes = g_slist_prepend (item->u.object.classes, g_strdup (name));
+    }
+  else
+    {
+      const GSList *stack;
+      const gchar *parent_name;
+      const gchar *our_name;
+      gint line;
+      gint col;
+
+      stack = g_markup_parse_context_get_element_stack (context);
+      our_name = stack->data;
+      parent_name = stack->next ? stack->next->data : "";
+
+      g_markup_parse_context_get_position (context, &line, &col);
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_INVALID_TAG,
+                   "%d:%d: Unknown element <%s> found in <%s>.",
+                   line, col, our_name, parent_name);
+    }
+
+  return;
+}
+
+static void
+states_parser_end_element (GMarkupParseContext  *context,
+                           const gchar          *element_name,
+                           gpointer              user_data,
+                           GError              **error)
+{
+  StatesParserData *parser_data = user_data;
+  StackItem *item;
+
+  g_assert (context != NULL);
+  g_assert (element_name != NULL);
+  g_assert (parser_data != NULL);
+
+  if (g_strcmp0 (element_name, "state") == 0)
+    {
+      item = g_queue_pop_head (parser_data->stack);
+      g_assert (item->type == STACK_ITEM_STATE);
+      add_state (parser_data, item, error);
+      stack_item_free (item);
+    }
+  else if (g_strcmp0 (element_name, "object") == 0)
+    {
+      StackItem *parent;
+
+      item = g_queue_pop_head (parser_data->stack);
+      g_assert (item->type == STACK_ITEM_OBJECT);
+
+      parent = g_queue_peek_head (parser_data->stack);
+      g_assert (parent->type == STACK_ITEM_STATE);
+
+      add_object (parser_data, parent, item);
+    }
+  else if (g_strcmp0 (element_name, "property") == 0)
+    {
+      StackItem *parent;
+
+      item = g_queue_pop_head (parser_data->stack);
+      g_assert (item->type == STACK_ITEM_PROPERTY);
+
+      parent = g_queue_peek_head (parser_data->stack);
+      g_assert (parent->type == STACK_ITEM_OBJECT);
+
+      add_property (parser_data, parent, item);
+    }
+}
+
+static void
+states_parser_text (GMarkupParseContext  *context,
+                    const gchar          *text,
+                    gsize                 text_len,
+                    gpointer              user_data,
+                    GError              **error)
+{
+  StatesParserData *parser_data = user_data;
+  StackItem *item;
+
+  g_assert (parser_data != NULL);
+
+  item = g_queue_peek_head (parser_data->stack);
+  if ((item != NULL) && (item->type == STACK_ITEM_PROPERTY))
+    item->u.property.text = g_strndup (text, text_len);
+}
+
+static GMarkupParser StatesParser = {
+  states_parser_start_element,
+  states_parser_end_element,
+  states_parser_text,
+};
+
+static gboolean
+egg_state_machine_buildable_custom_tag_start (GtkBuildable  *buildable,
+                                              GtkBuilder    *builder,
+                                              GObject       *child,
+                                              const gchar   *tagname,
+                                              GMarkupParser *parser,
+                                              gpointer      *data)
+{
+  EggStateMachine *self = (EggStateMachine *)buildable;
+
+  g_assert (EGG_IS_STATE_MACHINE (self));
+  g_assert (GTK_IS_BUILDER (builder));
+  g_assert (tagname != NULL);
+  g_assert (parser != NULL);
+  g_assert (data != NULL);
+
+  if (g_strcmp0 (tagname, "states") == 0)
+    {
+      StatesParserData *parser_data;
+
+      parser_data = g_slice_new0 (StatesParserData);
+      parser_data->self = g_object_ref (buildable);
+      parser_data->builder = g_object_ref (builder);
+      parser_data->stack = g_queue_new ();
+
+      egg_state_machine_freeze (self);
+
+      *parser = StatesParser;
+      *data = parser_data;
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+egg_state_machine_buildable_custom_finished (GtkBuildable *buildable,
+                                             GtkBuilder   *builder,
+                                             GObject      *child,
+                                             const gchar  *tagname,
+                                             gpointer      user_data)
+{
+  EggStateMachine *self = (EggStateMachine *)buildable;
+
+  g_assert (EGG_IS_STATE_MACHINE (self));
+  g_assert (GTK_IS_BUILDER (builder));
+  g_assert (tagname != NULL);
+
+  if (g_strcmp0 (tagname, "states") == 0)
+    {
+      StatesParserData *parser_data = user_data;
+      gchar *state;
+
+      g_object_unref (parser_data->self);
+      g_object_unref (parser_data->builder);
+      g_queue_free_full (parser_data->stack, (GDestroyNotify)stack_item_free);
+      g_slice_free (StatesParserData, parser_data);
+
+      egg_state_machine_thaw (self);
+
+      /* XXX: reapply current state */
+      state = g_strdup (egg_state_machine_get_state (self));
+      egg_state_machine_set_state (self, NULL);
+      egg_state_machine_set_state (self, state);
+      g_free (state);
+    }
+}
+
+void
+egg_state_machine_buildable_iface_init (GtkBuildableIface *iface)
+{
+  g_assert (iface != NULL);
+
+  egg_state_machine_parent_buildable = g_type_interface_peek_parent (iface);
+
+  iface->custom_tag_start = egg_state_machine_buildable_custom_tag_start;
+  iface->custom_finished = egg_state_machine_buildable_custom_finished;
+}
diff --git a/contrib/egg/egg-state-machine-buildable.h b/contrib/egg/egg-state-machine-buildable.h
new file mode 100644
index 0000000..636969e
--- /dev/null
+++ b/contrib/egg/egg-state-machine-buildable.h
@@ -0,0 +1,30 @@
+/* egg-state-machine-buildable.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_BUILDABLE_H
+#define EGG_STATE_MACHINE_BUILDABLE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void egg_state_machine_buildable_iface_init (GtkBuildableIface *iface);
+
+G_END_DECLS
+
+#endif /* EGG_STATE_MACHINE_BUILDABLE_H */
diff --git a/contrib/egg/egg-state-machine-private.h b/contrib/egg/egg-state-machine-private.h
new file mode 100644
index 0000000..dec731b
--- /dev/null
+++ b/contrib/egg/egg-state-machine-private.h
@@ -0,0 +1,59 @@
+/* egg-state-machine-private.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_PRIVATE_H
+#define EGG_STATE_MACHINE_PRIVATE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+  gchar      *state;
+  GPtrArray  *actions;
+  GHashTable *states;
+  gchar      *freeze_state;
+  gint        freeze_count;
+} EggStateMachinePrivate;
+
+typedef struct
+{
+  gchar      *name;
+  GHashTable *signals;
+  GHashTable *bindings;
+  GPtrArray  *properties;
+  GPtrArray  *styles;
+} EggState;
+
+typedef struct
+{
+  gpointer  object;
+  gchar    *property;
+  GValue    value;
+} EggStateProperty;
+
+typedef struct
+{
+  GtkWidget *widget;
+  gchar     *name;
+} EggStateStyle;
+
+G_END_DECLS
+
+#endif /* EGG_STATE_MACHINE_PRIVATE_H */
diff --git a/contrib/egg/egg-state-machine.c b/contrib/egg/egg-state-machine.c
index 819fc38..1b4996e 100644
--- a/contrib/egg/egg-state-machine.c
+++ b/contrib/egg/egg-state-machine.c
@@ -16,43 +16,24 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define G_LOG_DOMAIN "egg-state-machine"
+
 #include <glib/gi18n.h>
 
 #include "egg-binding-set.h"
 #include "egg-signal-group.h"
+
 #include "egg-state-machine.h"
+#include "egg-state-machine-action.h"
+#include "egg-state-machine-buildable.h"
+#include "egg-state-machine-private.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;
-
-  /*
-   * Container for actions which should have sensitivity mutated during state transitions.
-   *
-   * GHashTable of GPtrArray of ActionState.
-   */
-  GHashTable *actions_by_state;
-
-  gsize       sequence;
-} EggStateMachinePrivate;
-
-typedef struct
-{
-  GSimpleAction *action;
-  guint          invert_enabled : 1;
-} ActionState;
+G_DEFINE_QUARK (egg_state_machine_error, egg_state_machine_error)
 
-G_DEFINE_TYPE_WITH_PRIVATE (EggStateMachine, egg_state_machine, G_TYPE_OBJECT)
-G_DEFINE_QUARK (EggStateMachineError, egg_state_machine_error)
+G_DEFINE_TYPE_WITH_CODE (EggStateMachine, egg_state_machine, G_TYPE_OBJECT,
+                         G_ADD_PRIVATE (EggStateMachine)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+                                                egg_state_machine_buildable_iface_init))
 
 enum {
   PROP_0,
@@ -60,208 +41,155 @@ enum {
   LAST_PROP
 };
 
-enum {
-  TRANSITION,
-  LAST_SIGNAL
-};
-
 static GParamSpec *gParamSpecs [LAST_PROP];
-static guint gSignals [LAST_SIGNAL];
 
 static void
-action_state_free (gpointer data)
+egg_state_free (gpointer data)
 {
-  ActionState *state = data;
-
-  g_clear_object (&state->action);
-  g_slice_free (ActionState, state);
+  EggState *state = data;
+
+  g_free (state->name);
+  g_hash_table_unref (state->signals);
+  g_hash_table_unref (state->bindings);
+  g_ptr_array_unref (state->properties);
+  g_ptr_array_unref (state->styles);
+  g_slice_free (EggState, state);
 }
 
-static gboolean
-egg_state_transition_accumulator (GSignalInvocationHint *hint,
-                                  GValue                *return_value,
-                                  const GValue          *handler_return,
-                                  gpointer               data)
+static void
+egg_state_property_free (gpointer data)
 {
-  EggStateTransition ret;
-
-  ret = g_value_get_enum (handler_return);
+  EggStateProperty *prop = data;
 
-  if (ret == EGG_STATE_TRANSITION_INVALID)
-    {
-      g_value_set_enum (return_value, ret);
-      return FALSE;
-    }
-
-  return TRUE;
+  g_free (prop->property);
+  g_value_unset (&prop->value);
+  g_slice_free (EggStateProperty, prop);
 }
 
-const gchar *
-egg_state_machine_get_state (EggStateMachine *self)
+static void
+egg_state_style_free (gpointer data)
 {
-  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+  EggStateStyle *style = data;
 
-  g_return_val_if_fail (EGG_IS_STATE_MACHINE (self), NULL);
-
-  return priv->state;
+  g_free (style->name);
+  g_slice_free (EggStateStyle, style);
 }
 
 static void
-egg_state_machine_do_transition (EggStateMachine *self,
-                                 const gchar     *new_state)
+egg_state_apply (EggStateMachine *self,
+                 EggState        *state)
 {
-  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
   GHashTableIter iter;
-  const gchar *key;
-  GPtrArray *action_states;
-  GHashTable *value;
+  gpointer key;
+  gpointer value;
   gsize i;
 
   g_assert (EGG_IS_STATE_MACHINE (self));
-  g_assert (new_state != NULL);
+  g_assert (state != NULL);
 
-  priv->sequence++;
+  g_hash_table_iter_init (&iter, state->bindings);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    egg_binding_set_set_source (value, key);
 
-  g_free (priv->state);
-  priv->state = g_strdup (new_state);
+  g_hash_table_iter_init (&iter, state->signals);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    egg_signal_group_set_target (value, key);
 
-  g_hash_table_iter_init (&iter, priv->signal_groups_by_state);
-  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+  for (i = 0; i < state->properties->len; i++)
     {
-      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);
+      EggStateProperty *prop;
 
-      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);
-        }
+      prop = g_ptr_array_index (state->properties, i);
+      g_object_set_property (prop->object, prop->property, &prop->value);
     }
 
-  g_hash_table_iter_init (&iter, priv->binding_sets_by_state);
-  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+  for (i = 0; i < state->styles->len; i++)
     {
-      GHashTable *binding_sets = value;
-      GHashTableIter groups_iter;
-      EggBindingSet *binding_set;
-      gpointer instance;
-      gboolean enabled = (g_strcmp0 (key, new_state) == 0);
+      EggStateStyle *style;
+      GtkStyleContext *style_context;
 
-      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);
-        }
+      style = g_ptr_array_index (state->styles, i);
+      style_context = gtk_widget_get_style_context (GTK_WIDGET (style->widget));
+      gtk_style_context_add_class (style_context, style->name);
     }
+}
 
-  /* apply GSimpleAction:enabled to non-matching states */
-  g_hash_table_iter_init (&iter, priv->actions_by_state);
-  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&action_states))
-    {
-      if (g_strcmp0 (key, priv->state) == 0)
-        continue;
+static void
+egg_state_unapply (EggStateMachine *self,
+                   EggState        *state)
+{
+  GHashTableIter iter;
+  gpointer key;
+  gpointer value;
+  gsize i;
 
-      for (i = 0; i < action_states->len; i++)
-        {
-          ActionState *action_state;
+  g_assert (EGG_IS_STATE_MACHINE (self));
+  g_assert (state != NULL);
 
-          action_state = g_ptr_array_index (action_states, i);
-          g_simple_action_set_enabled (action_state->action, action_state->invert_enabled);
-        }
-    }
+  g_hash_table_iter_init (&iter, state->bindings);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    egg_binding_set_set_source (value, NULL);
+
+  g_hash_table_iter_init (&iter, state->signals);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    egg_signal_group_set_target (value, NULL);
 
-  /* apply GSimpleAction:enabled to matching state */
-  action_states = g_hash_table_lookup (priv->actions_by_state, priv->state);
-  if (action_states != NULL)
+  for (i = 0; i < state->styles->len; i++)
     {
-      for (i = 0; i < action_states->len; i++)
-        {
-          ActionState *action_state;
+      EggStateStyle *style;
+      GtkStyleContext *style_context;
 
-          action_state = g_ptr_array_index (action_states, i);
-          g_simple_action_set_enabled (action_state->action, !action_state->invert_enabled);
-        }
+      style = g_ptr_array_index (state->styles, i);
+      style_context = gtk_widget_get_style_context (GTK_WIDGET (style->widget));
+      gtk_style_context_remove_class (style_context, style->name);
     }
 }
 
-/**
- * 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)
+static EggState *
+egg_state_machine_get_state_obj (EggStateMachine *self,
+                                 const gchar     *state)
 {
   EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
-  g_autofree gchar *old_state = 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), EGG_STATE_TRANSITION_INVALID);
-  g_return_val_if_fail (new_state != NULL, EGG_STATE_TRANSITION_INVALID);
-  g_return_val_if_fail (error == NULL || *error == NULL, EGG_STATE_TRANSITION_INVALID);
-
-  if (g_strcmp0 (new_state, priv->state) == 0)
-    return EGG_STATE_TRANSITION_SUCCESS;
+  EggState *state_obj;
 
-  /* Be careful with reentrancy. */
-
-  old_state = g_strdup (priv->state);
-  sequence = priv->sequence;
-
-  g_signal_emit (self, gSignals [TRANSITION], 0, old_state, new_state, &local_error, &ret);
+  g_assert (EGG_IS_STATE_MACHINE (self));
 
-  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;
-    }
+  state_obj = g_hash_table_lookup (priv->states, state);
 
-  if (sequence == priv->sequence)
+  if (state_obj == NULL)
     {
-      egg_state_machine_do_transition (self, new_state);
-      g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_STATE]);
+      state_obj = g_slice_new0 (EggState);
+      state_obj->name = g_strdup (state);
+      state_obj->signals = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
+      state_obj->bindings = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
+      state_obj->properties = g_ptr_array_new_with_free_func (egg_state_property_free);
+      state_obj->styles = g_ptr_array_new_with_free_func (egg_state_style_free);
+      g_hash_table_insert (priv->states, g_strdup (state), state_obj);
     }
 
-  return EGG_STATE_TRANSITION_SUCCESS;
+  return state_obj;
 }
 
-static EggStateTransition
-egg_state_machine_real_transition (EggStateMachine  *self,
-                                   const gchar      *old_state,
-                                   const gchar      *new_state,
-                                   GError          **error)
+static void
+egg_state_machine_transition (EggStateMachine *self,
+                              const gchar     *old_state,
+                              const gchar     *new_state)
 {
-  return EGG_STATE_TRANSITION_IGNORED;
+  EggState *state_obj;
+
+  g_assert (EGG_IS_STATE_MACHINE (self));
+
+  g_object_freeze_notify (G_OBJECT (self));
+
+  if (old_state && (state_obj = egg_state_machine_get_state_obj (self, old_state)))
+    egg_state_unapply (self, state_obj);
+
+  if (new_state && (state_obj = egg_state_machine_get_state_obj (self, new_state)))
+    egg_state_apply (self, state_obj);
+
+  g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_STATE]);
+
+  g_object_thaw_notify (G_OBJECT (self));
 }
 
 static void
@@ -270,10 +198,8 @@ egg_state_machine_finalize (GObject *object)
   EggStateMachine *self = (EggStateMachine *)object;
   EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
 
+  g_clear_pointer (&priv->states, g_hash_table_unref);
   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_clear_pointer (&priv->actions_by_state, g_hash_table_unref);
 
   G_OBJECT_CLASS (egg_state_machine_parent_class)->finalize (object);
 }
@@ -304,12 +230,11 @@ egg_state_machine_set_property (GObject      *object,
                                 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);
+      egg_state_machine_set_state (self, g_value_get_string (value));
       break;
 
     default:
@@ -326,42 +251,14 @@ egg_state_machine_class_init (EggStateMachineClass *klass)
   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_PARAM_READWRITE | 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
@@ -369,268 +266,232 @@ 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);
-
-  priv->actions_by_state =
-    g_hash_table_new_full (g_str_hash,
-                           g_str_equal,
-                           g_free,
-                           (GDestroyNotify)g_ptr_array_unref);
+  priv->states = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, egg_state_free);
 }
 
-static void
-egg_state_machine__connect_object_weak_notify (gpointer  data,
-                                               GObject  *where_object_was)
+EggStateMachine *
+egg_state_machine_new (void)
 {
-  EggStateMachine *self = data;
-  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
-  GHashTableIter iter;
-  const gchar *key;
-  GHashTable *value;
+  return g_object_new (EGG_TYPE_STATE_MACHINE, NULL);
+}
 
-  g_assert (EGG_IS_STATE_MACHINE (self));
-  g_assert (where_object_was != NULL);
+/**
+ * egg_state_machine_get_state:
+ * @self: the #EggStateMachine.
+ *
+ * Gets the #EggStateMachine:state property. This is the name of the
+ * current state of the machine.
+ *
+ * Returns: The current state of the machine.
+ */
+const gchar *
+egg_state_machine_get_state (EggStateMachine *self)
+{
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
 
-  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;
+  g_return_if_fail (EGG_IS_STATE_MACHINE (self));
 
-      g_hash_table_remove (signal_groups, where_object_was);
-    }
+  return priv->state;
 }
 
+/**
+ * egg_state_machine_set_state:
+ * @self: the #EggStateMachine @self: the #
+ *
+ * Sets the #EggStateMachine:state property.
+ *
+ * Registered state transformations will be applied during the state
+ * transformation.
+ *
+ * If the transition results in a cyclic operation, the state will stop at
+ * the last state before the cycle was detected.
+ */
 void
-egg_state_machine_connect_object (EggStateMachine *self,
-                                  const gchar     *state,
-                                  gpointer         instance,
-                                  const gchar     *detailed_signal,
-                                  GCallback        callback,
-                                  gpointer         user_data,
-                                  GConnectFlags    flags)
+egg_state_machine_set_state (EggStateMachine *self,
+                             const gchar     *state)
 {
   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 (g_signal_parse_name (detailed_signal,
-                                         G_TYPE_FROM_INSTANCE (instance),
-                                         NULL, NULL, FALSE) != 0);
-  g_return_if_fail (callback != NULL);
-
-  signal_groups = g_hash_table_lookup (priv->signal_groups_by_state, state);
 
-  if (signal_groups == NULL)
+  if (g_strcmp0 (priv->state, state) != 0)
     {
-      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);
-    }
+      gchar *old_state = priv->state;
+      gchar *new_state = g_strdup (state);
 
-  g_assert (signal_groups != NULL);
+      /*
+       * Steal ownership of old state and create a copy for new state
+       * to ensure that we own the references. State machines tend to
+       * get used in re-entrant fashion.
+       */
 
-  signal_group = g_hash_table_lookup (signal_groups, instance);
+      priv->state = g_strdup (state);
 
-  if (signal_group == NULL)
-    {
-      created = TRUE;
-      signal_group = egg_signal_group_new (G_TYPE_FROM_INSTANCE (instance));
-      g_hash_table_insert (signal_groups, instance, signal_group);
-      g_object_weak_ref (instance,
-                         (GWeakNotify)egg_state_machine__connect_object_weak_notify,
-                         self);
+      if (priv->freeze_count == 0)
+        egg_state_machine_transition (self, old_state, state);
+
+      g_free (new_state);
+      g_free (old_state);
     }
+}
 
-  egg_signal_group_connect_object (signal_group, detailed_signal, callback, user_data, flags);
+GAction *
+egg_state_machine_create_action (EggStateMachine *self,
+                                 const gchar     *name)
+{
+  g_return_val_if_fail (EGG_IS_STATE_MACHINE (self), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
 
-  if ((created == TRUE) && (g_strcmp0 (state, priv->state) == 0))
-    egg_signal_group_set_target (signal_group, instance);
+  return g_object_new (EGG_TYPE_STATE_MACHINE_ACTION,
+                       "state-machine", self,
+                       "name", name,
+                       NULL);
 }
 
-static void
-egg_state_machine__bind_source_weak_notify (gpointer  data,
-                                            GObject  *where_object_was)
+void
+egg_state_machine_add_property (EggStateMachine *self,
+                                const gchar     *state,
+                                gpointer         object,
+                                const gchar     *property,
+                                const GValue    *value)
 {
-  EggStateMachine *self = data;
-  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
-  GHashTableIter iter;
-  const gchar *key;
-  GHashTable *value;
+  EggState *state_obj;
+  EggStateProperty *state_prop;
 
-  g_assert (EGG_IS_STATE_MACHINE (self));
-  g_assert (where_object_was != NULL);
+  g_return_if_fail (EGG_IS_STATE_MACHINE (self));
+  g_return_if_fail (state != NULL);
+  g_return_if_fail (object != NULL);
+  g_return_if_fail (property != NULL);
+  g_return_if_fail (G_IS_VALUE (value));
 
-  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;
+  state_obj = egg_state_machine_get_state_obj (self, state);
 
-      g_hash_table_remove (binding_sets, where_object_was);
-    }
+  state_prop = g_slice_new0 (EggStateProperty);
+  state_prop->object = object;
+  state_prop->property = g_strdup (property);
+  g_value_init (&state_prop->value, G_VALUE_TYPE (value));
+  g_value_copy (value, &state_prop->value);
+
+  g_ptr_array_add (state_obj->properties, state_prop);
 }
 
 void
-egg_state_machine_bind (EggStateMachine *self,
-                        const gchar     *state,
-                        gpointer         source,
-                        const gchar     *source_property,
-                        gpointer         target,
-                        const gchar     *target_property,
-                        GBindingFlags    flags)
+egg_state_machine_add_binding (EggStateMachine *self,
+                               const gchar     *state,
+                               gpointer         source_object,
+                               const gchar     *source_property,
+                               gpointer         target_object,
+                               const gchar     *target_property,
+                               GBindingFlags    flags)
 {
-  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
-  GHashTable *binding_sets;
-  EggBindingSet *binding_set;
-  gboolean created = FALSE;
+  EggBindingSet *bindings;
+  EggState *state_obj;
 
   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_object != NULL);
   g_return_if_fail (source_property != NULL);
-  g_return_if_fail (g_object_class_find_property (G_OBJECT_GET_CLASS (source),
-                                                  source_property) != NULL);
-  g_return_if_fail (G_IS_OBJECT (target));
+  g_return_if_fail (target_object != NULL);
   g_return_if_fail (target_property != NULL);
-  g_return_if_fail (g_object_class_find_property (G_OBJECT_GET_CLASS (target),
-                                                  target_property) != NULL);
 
-  /* Use G_BINDING_SYNC_CREATE as we lazily connect them. */
-  flags |= G_BINDING_SYNC_CREATE;
+  state_obj = egg_state_machine_get_state_obj (self, state);
 
-  binding_sets = g_hash_table_lookup (priv->binding_sets_by_state, state);
+  bindings = g_hash_table_lookup (state_obj->bindings, source_object);
 
-  if (binding_sets == NULL)
+  if (bindings == 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);
+      bindings = egg_binding_set_new ();
+      g_hash_table_insert (state_obj->bindings, source_object, bindings);
     }
 
-  g_assert (binding_sets != NULL);
+  egg_binding_set_bind (bindings, source_property, target_object, target_property, flags);
+}
 
-  binding_set = g_hash_table_lookup (binding_sets, source);
+void
+egg_state_machine_add_style (EggStateMachine *self,
+                             const gchar     *state,
+                             GtkWidget       *widget,
+                             const gchar     *style)
+{
+  EggState *state_obj;
+  EggStateStyle *style_obj;
 
-  if (binding_set == NULL)
-    {
-      created = TRUE;
-      binding_set = egg_binding_set_new ();
-      g_hash_table_insert (binding_sets, source, binding_set);
-      g_object_weak_ref (source,
-                         (GWeakNotify)egg_state_machine__bind_source_weak_notify,
-                         self);
-    }
+  g_return_if_fail (EGG_IS_STATE_MACHINE (self));
+  g_return_if_fail (state != NULL);
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (style);
 
-  egg_binding_set_bind (binding_set,
-                        source_property,
-                        target,
-                        target_property,
-                        flags);
+  state_obj = egg_state_machine_get_state_obj (self, state);
 
-  if ((created == TRUE) && (g_strcmp0 (state, priv->state) == 0))
-    egg_binding_set_set_source (binding_set, source);
+  style_obj = g_slice_new0 (EggStateStyle);
+  style_obj->name = g_strdup (style);
+  style_obj->widget = widget;
+
+  g_ptr_array_add (state_obj->styles, style_obj);
 }
 
 void
-egg_state_machine_add_action (EggStateMachine *self,
-                              const gchar     *state,
-                              GSimpleAction   *action,
-                              gboolean         invert_enabled)
+egg_state_machine_connect_object (EggStateMachine *self,
+                                  const gchar     *state,
+                                  gpointer         source,
+                                  const gchar     *detailed_signal,
+                                  GCallback        callback,
+                                  gpointer         user_data,
+                                  GConnectFlags    flags)
 {
-  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
-  ActionState *action_state;
-  GPtrArray *actions;
-  gboolean enabled;
+  EggState *state_obj;
+  EggSignalGroup *signals;
 
   g_return_if_fail (EGG_IS_STATE_MACHINE (self));
   g_return_if_fail (state != NULL);
-  g_return_if_fail (G_IS_SIMPLE_ACTION (action));
-
-  action_state = g_slice_new0 (ActionState);
-  action_state->action = g_object_ref (action);
-  action_state->invert_enabled = invert_enabled;
+  g_return_if_fail (G_IS_OBJECT (source));
+  g_return_if_fail (detailed_signal != NULL);
+  g_return_if_fail (callback != NULL);
 
-  actions = g_hash_table_lookup (priv->actions_by_state, state);
+  state_obj = egg_state_machine_get_state_obj (self, state);
 
-  if (actions == NULL)
+  if (!(signals = g_hash_table_lookup (state_obj->signals, source)))
     {
-      actions = g_ptr_array_new_with_free_func (action_state_free);
-      g_hash_table_insert (priv->actions_by_state, g_strdup (state), actions);
+      signals = egg_signal_group_new (G_OBJECT_TYPE (source));
+      g_hash_table_insert (state_obj->signals, source, signals);
     }
 
-  g_ptr_array_add (actions, action_state);
-
-  enabled = (g_strcmp0 (state, priv->state) == 0);
-  if (invert_enabled)
-    enabled = !enabled;
-
-  g_simple_action_set_enabled (action, enabled);
+  egg_signal_group_connect_object (signals, detailed_signal, callback, user_data, flags);
 }
 
-EggStateMachine *
-egg_state_machine_new (void)
+void
+egg_state_machine_freeze (EggStateMachine *self)
 {
-  return g_object_new (EGG_TYPE_STATE_MACHINE, NULL);
-}
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
 
-GType
-egg_state_machine_error_get_type (void)
-{
-  static gsize type_id;
+  g_return_if_fail (EGG_IS_STATE_MACHINE (self));
+  g_return_if_fail (priv->freeze_count >= 0);
 
-  if (g_once_init_enter (&type_id))
+  if (++priv->freeze_count == 1)
     {
-      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);
+      g_assert (priv->freeze_state == NULL);
+      priv->freeze_state = g_strdup (priv->state);
     }
-
-  return type_id;
 }
 
-GType
-egg_state_transition_get_type (void)
+void
+egg_state_machine_thaw (EggStateMachine *self)
 {
-  static gsize type_id;
+  EggStateMachinePrivate *priv = egg_state_machine_get_instance_private (self);
+
+  g_return_if_fail (EGG_IS_STATE_MACHINE (self));
+  g_return_if_fail (priv->freeze_count > 0);
 
-  if (g_once_init_enter (&type_id))
+  if (--priv->freeze_count == 0)
     {
-      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);
-    }
+      if (g_strcmp0 (priv->freeze_state, priv->state) != 0)
+        {
+          gchar *state;
 
-  return type_id;
+          state = priv->freeze_state;
+          priv->freeze_state = NULL;
+          egg_state_machine_set_state (self, state);
+          g_free (state);
+        }
+    }
 }
diff --git a/contrib/egg/egg-state-machine.h b/contrib/egg/egg-state-machine.h
index beb52a7..2a10520 100644
--- a/contrib/egg/egg-state-machine.h
+++ b/contrib/egg/egg-state-machine.h
@@ -19,65 +19,50 @@
 #ifndef EGG_STATE_MACHINE_H
 #define EGG_STATE_MACHINE_H
 
-#include <gio/gio.h>
+#include <gtk/gtk.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())
+#define EGG_TYPE_STATE_MACHINE  (egg_state_machine_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);
-void                egg_state_machine_add_action     (EggStateMachine  *self,
-                                                      const gchar      *state,
-                                                      GSimpleAction    *action,
-                                                      gboolean          invert_enabled);
+EggStateMachine *egg_state_machine_new               (void);
+const gchar     *egg_state_machine_get_state         (EggStateMachine *self);
+void             egg_state_machine_set_state         (EggStateMachine *self,
+                                                      const gchar     *state);
+GAction         *egg_state_machine_create_action     (EggStateMachine *self,
+                                                      const gchar     *name);
+void             egg_state_machine_add_property      (EggStateMachine *self,
+                                                      const gchar     *state,
+                                                      gpointer         object,
+                                                      const gchar     *property,
+                                                      const GValue    *value);
+void             egg_state_machine_add_binding       (EggStateMachine *self,
+                                                      const gchar     *state,
+                                                      gpointer         source_object,
+                                                      const gchar     *source_property,
+                                                      gpointer         target_object,
+                                                      const gchar     *target_property,
+                                                      GBindingFlags    flags);
+void             egg_state_machine_add_style         (EggStateMachine *self,
+                                                      const gchar     *state,
+                                                      GtkWidget       *widget,
+                                                      const gchar     *style);
+void             egg_state_machine_freeze            (EggStateMachine *self);
+void             egg_state_machine_thaw              (EggStateMachine *self);
+void             egg_state_machine_connect_object    (EggStateMachine *self,
+                                                      const gchar     *state,
+                                                      gpointer         source,
+                                                      const gchar     *detailed_signal,
+                                                      GCallback        callback,
+                                                      gpointer         user_data,
+                                                      GConnectFlags    flags);
 
 G_END_DECLS
 
diff --git a/tests/test-egg-state-machine.c b/tests/test-egg-state-machine.c
index d221503..849a43b 100644
--- a/tests/test-egg-state-machine.c
+++ b/tests/test-egg-state-machine.c
@@ -188,30 +188,22 @@ assert_prop_equal (gpointer     obja,
   g_value_unset (&vb);
 }
 
-static EggStateTransition
-transition_cb (EggStateMachine *machine,
-               const gchar     *old_state,
-               const gchar     *new_state,
-               gpointer         user_data)
-{
-  /* allow any state to state3, except state2 */
-
-  if ((g_strcmp0 (old_state, "state2") == 0) && (g_strcmp0 (new_state, "state3") == 0))
-    return EGG_STATE_TRANSITION_INVALID;
-
-  return EGG_STATE_TRANSITION_IGNORED;
-}
-
 static void
 test_state_machine_basic (void)
 {
   EggStateMachine *machine;
-  EggStateTransition ret;
   GSimpleAction *action;
   TestObject *dummy;
   TestObject *obj1;
   TestObject *obj2;
-  GError *error = NULL;
+  GValue vtrue = { 0 };
+  GValue vfalse = { 0 };
+
+  g_value_init (&vtrue, G_TYPE_BOOLEAN);
+  g_value_set_boolean (&vtrue, TRUE);
+
+  g_value_init (&vfalse, G_TYPE_BOOLEAN);
+  g_value_set_boolean (&vfalse, FALSE);
 
   machine = egg_state_machine_new ();
   g_object_add_weak_pointer (G_OBJECT (machine), (gpointer *)&machine);
@@ -221,6 +213,8 @@ test_state_machine_basic (void)
   obj1 = g_object_new (TEST_TYPE_OBJECT, NULL);
   obj2 = g_object_new (TEST_TYPE_OBJECT, NULL);
 
+  g_simple_action_set_enabled (action, FALSE);
+
 #if 0
   g_print ("obj1=%p  obj2=%p  dummy=%p\n", obj1, obj2, dummy);
 #endif
@@ -230,18 +224,17 @@ test_state_machine_basic (void)
   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);
+  egg_state_machine_add_binding (machine, "state1", obj1, "string", dummy, "string", 0);
+  egg_state_machine_add_binding (machine, "state2", obj2, "string", dummy, "string", 0);
 
-  egg_state_machine_add_action (machine, "state1", action, FALSE);
-
-  g_signal_connect (machine, "transition", G_CALLBACK (transition_cb), NULL);
+  egg_state_machine_add_property (machine, "state1", action, "enabled", &vtrue);
+  egg_state_machine_add_property (machine, "state2", action, "enabled", &vfalse);
+  egg_state_machine_add_property (machine, "state3", action, "enabled", &vfalse);
 
   g_assert_cmpint (g_action_get_enabled (G_ACTION (action)), ==, FALSE);
 
-  ret = egg_state_machine_transition (machine, "state1", &error);
-  g_assert_no_error (error);
-  g_assert_cmpint (ret, ==, EGG_STATE_TRANSITION_SUCCESS);
+  egg_state_machine_set_state (machine, "state1");
+  g_assert_cmpstr (egg_state_machine_get_state (machine), ==, "state1");
   g_assert_cmpint (dummy->obj1_count, ==, 0);
   g_assert_cmpint (dummy->obj2_count, ==, 0);
 
@@ -255,9 +248,8 @@ test_state_machine_basic (void)
   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);
+  egg_state_machine_set_state (machine, "state2");
+  g_assert_cmpstr (egg_state_machine_get_state (machine), ==, "state2");
 
   g_assert_cmpint (g_action_get_enabled (G_ACTION (action)), ==, FALSE);
 
@@ -273,16 +265,8 @@ test_state_machine_basic (void)
   g_object_set (obj1, "string", "obj1", NULL);
   assert_prop_equal (obj2, dummy, "string");
 
-  /* state2 -> state3 should fail */
-  ret = egg_state_machine_transition (machine, "state3", &error);
-  g_assert_error (error, EGG_STATE_MACHINE_ERROR,
-                  EGG_STATE_MACHINE_ERROR_INVALID_TRANSITION);
-  g_assert_cmpint (ret, ==, EGG_STATE_TRANSITION_INVALID);
-  g_clear_error (&error);
-
-  ret = egg_state_machine_transition (machine, "state1", &error);
-  g_assert_no_error (error);
-  g_assert_cmpint (ret, ==, EGG_STATE_TRANSITION_SUCCESS);
+  egg_state_machine_set_state (machine, "state3");
+  egg_state_machine_set_state (machine, "state1");
 
   assert_prop_equal (obj1, dummy, "string");
   g_object_set (obj1, "string", "obj1-1", NULL);
@@ -290,10 +274,7 @@ test_state_machine_basic (void)
   g_object_set (obj2, "string", "obj2-1", NULL);
   assert_prop_equal (obj1, dummy, "string");
 
-  /* state1 -> state3 should succeed */
-  ret = egg_state_machine_transition (machine, "state3", &error);
-  g_assert_no_error (error);
-  g_assert_cmpint (ret, ==, EGG_STATE_TRANSITION_SUCCESS);
+  egg_state_machine_set_state (machine, "state3");
 
   g_object_unref (machine);
   g_assert (machine == NULL);


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