[glib/wip/menus-rebase2] GActionMuxer



commit 0a5d35199bb5ca24d451c63498fbd085f427ab76
Author: Ryan Lortie <desrt desrt ca>
Date:   Fri Nov 25 13:53:59 2011 -0500

    GActionMuxer

 gio/Makefile.am     |    2 +
 gio/gactionmuxer.c  |  496 +++++++++++++++++++++++++++++++++++++++++++++++++++
 gio/gactionmuxer.h  |   50 +++++
 gio/gio.h           |    1 +
 gio/giotypes.h      |    1 +
 gio/tests/actions.c |  211 ++++++++++++++++++++++
 6 files changed, 761 insertions(+), 0 deletions(-)
---
diff --git a/gio/Makefile.am b/gio/Makefile.am
index 1fca66d..f89cb77 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -126,6 +126,7 @@ endif
 
 application_headers = \
 	gactiongroup.h			\
+	gactionmuxer.h			\
 	gactionobservable.h		\
 	gactionobserver.h		\
 	gsimpleactiongroup.h		\
@@ -143,6 +144,7 @@ application_headers = \
 
 application_sources = \
 	gactiongroup.c				\
+	gactionmuxer.c				\
 	gactionobservable.c			\
 	gactionobserver.c			\
 	gsimpleactiongroup.c			\
diff --git a/gio/gactionmuxer.c b/gio/gactionmuxer.c
new file mode 100644
index 0000000..157b866
--- /dev/null
+++ b/gio/gactionmuxer.c
@@ -0,0 +1,496 @@
+/*
+ * Copyright  2011 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "config.h"
+
+#include "gactionmuxer.h"
+
+#include "gactionobservable.h"
+#include "gactionobserver.h"
+#include "gactiongroup.h"
+
+#include <string.h>
+
+/**
+ * SECTION:gactionmuxer
+ * @short_description: Aggregate and monitor several action groups
+ *
+ * #GActionMuxer is a #GActionGroup and #GActionObservable that is
+ * capable of containing other #GActionGroup instances.
+ *
+ * The typical use is aggrgating all of the actions applicable to a
+ * particular context into a single action group, with namespacing.
+ *
+ * Consider the case of two action groups -- one containing actions
+ * applicable to an entire application (such as 'quit') and one
+ * containing actions applicable to a particular window in the
+ * application (such as 'fullscreen').
+ *
+ * In this case, each of these action groups could be added to a
+ * #GActionMuxer with the prefixes "app" and "win", respectively.  This
+ * would expose the actions as "app.quit" and "win.fullscreen" on the
+ * #GActionGroup interface presented by the #GActionMuxer.
+ *
+ * Activations and state change requests on the #GActionMuxer are wired
+ * through to the underlying action group in the expected way.
+ *
+ * This class is typically only used at the site of "consumption" of
+ * actions (eg: when displaying a menu that contains many actions on
+ * different objects).
+ *
+ * Since: 2.32
+ **/
+
+static void     g_action_muxer_group_iface_init         (GActionGroupInterface      *iface);
+static void     g_action_muxer_observable_iface_init    (GActionObservableInterface *iface);
+
+typedef GObjectClass GActionMuxerClass;
+
+struct _GActionMuxer
+{
+  GObject parent_instance;
+
+  GHashTable *actions;
+  GHashTable *groups;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_iface_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVABLE, g_action_muxer_observable_iface_init))
+
+typedef struct
+{
+  GActionMuxer *muxer;
+  GSList       *watchers;
+  gchar        *fullname;
+} Action;
+
+typedef struct
+{
+  GActionMuxer *muxer;
+  GActionGroup *group;
+  gchar        *prefix;
+  gulong        handler_ids[4];
+} Group;
+
+static gchar **
+g_action_muxer_list_actions (GActionGroup *action_group)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
+
+  return (gchar **) muxer->groups;
+}
+
+static Group *
+g_action_muxer_find_group (GActionMuxer  *muxer,
+                              const gchar     **name)
+{
+  const gchar *dot;
+  gchar *prefix;
+  Group *group;
+
+  dot = strchr (*name, '.');
+
+  if (!dot)
+    return NULL;
+
+  prefix = g_strndup (*name, dot - *name);
+  group = g_hash_table_lookup (muxer->groups, prefix);
+  g_free (prefix);
+
+  *name = dot + 1;
+
+  return group;
+}
+
+static Action *
+g_action_muxer_lookup_action (GActionMuxer *muxer,
+                              const gchar  *prefix,
+                              const gchar  *action_name)
+{
+  Action *action;
+  gchar *fullname;
+
+  fullname = g_strconcat (prefix, ".", action_name, NULL);
+  action = g_hash_table_lookup (muxer->actions, fullname);
+  g_free (fullname);
+
+  return action;
+}
+
+static void
+g_action_muxer_action_enabled_changed (GActionGroup *action_group,
+                                       const gchar  *action_name,
+                                       gboolean      enabled,
+                                       gpointer      user_data)
+{
+  Group *group = user_data;
+  Action *action;
+  GSList *node;
+
+  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_enabled_changed (node->data,
+                                              G_ACTION_OBSERVABLE (group->muxer),
+                                              action->fullname, enabled);
+}
+
+static void
+g_action_muxer_action_state_changed (GActionGroup *action_group,
+                                     const gchar  *action_name,
+                                     GVariant     *state,
+                                     gpointer      user_data)
+{
+  Group *group = user_data;
+  Action *action;
+  GSList *node;
+
+  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_state_changed (node->data,
+                                            G_ACTION_OBSERVABLE (group->muxer),
+                                            action->fullname, state);
+}
+
+static void
+g_action_muxer_action_added (GActionGroup *action_group,
+                             const gchar  *action_name,
+                             gpointer      user_data)
+{
+  Group *group = user_data;
+  const GVariantType *parameter_type;
+  gboolean enabled;
+  GVariant *state;
+  Action *action;
+
+  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name);
+  if (g_action_group_query_action (group->group, action_name, &enabled, &parameter_type, NULL, NULL, &state))
+    {
+      GSList *node;
+
+      for (node = action ? action->watchers : NULL; node; node = node->next)
+        g_action_observer_action_added (node->data,
+                                        G_ACTION_OBSERVABLE (group->muxer),
+                                        action->fullname, parameter_type, enabled, state);
+
+      if (state)
+        g_variant_unref (state);
+    }
+}
+
+static void
+g_action_muxer_action_removed (GActionGroup *action_group,
+                               const gchar  *action_name,
+                               gpointer      user_data)
+{
+  Group *group = user_data;
+  Action *action;
+  GSList *node;
+
+  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_removed (node->data,
+                                      G_ACTION_OBSERVABLE (group->muxer),
+                                      action->fullname);
+}
+
+static gboolean
+g_action_muxer_query_action (GActionGroup        *action_group,
+                             const gchar         *action_name,
+                             gboolean            *enabled,
+                             const GVariantType **parameter_type,
+                             const GVariantType **state_type,
+                             GVariant           **state_hint,
+                             GVariant           **state)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
+  Group *group;
+
+  group = g_action_muxer_find_group (muxer, &action_name);
+
+  if (!group)
+    return FALSE;
+
+  return g_action_group_query_action (group->group, action_name, enabled,
+                                      parameter_type, state_type, state_hint, state);
+}
+
+static void
+g_action_muxer_activate_action (GActionGroup *action_group,
+                                const gchar  *action_name,
+                                GVariant     *parameter)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
+  Group *group;
+
+  group = g_action_muxer_find_group (muxer, &action_name);
+
+  if (group)
+    g_action_group_activate_action (group->group, action_name, parameter);
+}
+
+static void
+g_action_muxer_change_action_state (GActionGroup *action_group,
+                                    const gchar  *action_name,
+                                    GVariant     *state)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
+  Group *group;
+
+  group = g_action_muxer_find_group (muxer, &action_name);
+
+  if (group)
+    g_action_group_change_action_state (group->group, action_name, state);
+}
+
+static void
+g_action_muxer_unregister_internal (Action   *action,
+                                    gpointer  observer)
+{
+  GActionMuxer *muxer = action->muxer;
+  GSList **ptr;
+
+  for (ptr = &action->watchers; *ptr; ptr = &(*ptr)->next)
+    if ((*ptr)->data == observer)
+      {
+        *ptr = g_slist_remove (*ptr, observer);
+
+        if (action->watchers == NULL)
+          {
+            g_hash_table_remove (muxer->actions, action->fullname);
+            g_free (action->fullname);
+
+            g_slice_free (Action, action);
+
+            g_object_unref (muxer);
+          }
+
+        break;
+      }
+}
+
+static void
+g_action_muxer_weak_notify (gpointer  data,
+                            GObject  *where_the_object_was)
+{
+  Action *action = data;
+
+  g_action_muxer_unregister_internal (action, where_the_object_was);
+}
+
+static void
+g_action_muxer_register_observer (GActionObservable *observable,
+                                  const gchar       *name,
+                                  GActionObserver   *observer)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (observable);
+  Action *action;
+
+  action = g_hash_table_lookup (muxer->actions, name);
+
+  if (action == NULL)
+    {
+      action = g_slice_new (Action);
+      action->muxer = g_object_ref (muxer);
+      action->fullname = g_strdup (name);
+      action->watchers = NULL;
+
+      g_hash_table_insert (muxer->actions, action->fullname, action);
+    }
+
+  action->watchers = g_slist_prepend (action->watchers, observer);
+  g_object_weak_ref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
+}
+
+static void
+g_action_muxer_unregister_observer (GActionObservable *observable,
+                                    const gchar       *name,
+                                    GActionObserver   *observer)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (observable);
+  Action *action;
+
+  action = g_hash_table_lookup (muxer->actions, name);
+  g_object_weak_unref (G_OBJECT (observer), g_action_muxer_weak_notify, action);
+  g_action_muxer_unregister_internal (action, observer);
+}
+
+static void
+g_action_muxer_free_group (gpointer data)
+{
+  Group *group = data;
+
+  g_object_unref (group->group);
+  g_free (group->prefix);
+
+  g_slice_free (Group, group);
+}
+
+static void
+g_action_muxer_finalize (GObject *object)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (object);
+
+  g_assert_cmpint (g_hash_table_size (muxer->actions), ==, 0);
+  g_hash_table_unref (muxer->actions);
+  g_hash_table_unref (muxer->groups);
+
+  G_OBJECT_CLASS (g_action_muxer_parent_class)
+    ->finalize (object);
+}
+
+static void
+g_action_muxer_init (GActionMuxer *muxer)
+{
+  muxer->actions = g_hash_table_new (g_str_hash, g_str_equal);
+  muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_group);
+}
+
+static void
+g_action_muxer_observable_iface_init (GActionObservableInterface *iface)
+{
+  iface->register_observer = g_action_muxer_register_observer;
+  iface->unregister_observer = g_action_muxer_unregister_observer;
+}
+
+static void
+g_action_muxer_group_iface_init (GActionGroupInterface *iface)
+{
+  iface->list_actions = g_action_muxer_list_actions;
+  iface->query_action = g_action_muxer_query_action;
+  iface->activate_action = g_action_muxer_activate_action;
+  iface->change_action_state = g_action_muxer_change_action_state;
+}
+
+static void
+g_action_muxer_class_init (GObjectClass *class)
+{
+  class->finalize = g_action_muxer_finalize;
+}
+
+/**
+ * g_action_muxer_insert:
+ * @muxer: a #GActionMuxer
+ * @prefix: the prefix string for the action group
+ * @action_group: a #GActionGroup
+ *
+ * Adds the actions in @action_group to the list of actions provided by
+ * @muxer.  @prefix is prefixed to each action name, such that for each
+ * action <varname>x</varname> in @action_group, there is an equivalent
+ * action @prefix<literal>.</literal><varname>x</varname> in @muxer.
+ *
+ * For example, if @prefix is "<literal>app</literal>" and @action_group
+ * contains an action called "<literal>quit</literal>", then @muxer will
+ * now contain an action called "<literal>app.quit</literal>".
+ *
+ * If any #GActionObservers are registered for actions in the group,
+ * "action_added" notifications will be emitted, as appropriate.
+ *
+ * @prefix must not contain a dot ('.').
+ *
+ * Since: 2.32
+ **/
+void
+g_action_muxer_insert (GActionMuxer *muxer,
+                       const gchar  *prefix,
+                       GActionGroup *action_group)
+{
+  gchar **actions;
+  Group *group;
+  gint i;
+
+  /* TODO: diff instead of ripout and replace */
+  g_action_muxer_remove (muxer, prefix);
+
+  group = g_slice_new (Group);
+  group->muxer = muxer;
+  group->group = g_object_ref (action_group);
+  group->prefix = g_strdup (prefix);
+
+  g_hash_table_insert (muxer->groups, group->prefix, group);
+
+  actions = g_action_group_list_actions (group->group);
+  for (i = 0; actions[i]; i++)
+    g_action_muxer_action_added (group->group, actions[i], group);
+  g_strfreev (actions);
+
+  group->handler_ids[0] = g_signal_connect (group->group, "action-added",
+                                            G_CALLBACK (g_action_muxer_action_added), group);
+  group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
+                                            G_CALLBACK (g_action_muxer_action_removed), group);
+  group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
+                                            G_CALLBACK (g_action_muxer_action_enabled_changed), group);
+  group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
+                                            G_CALLBACK (g_action_muxer_action_state_changed), group);
+}
+
+/**
+ * g_action_muxer_remove:
+ * @muxer: a #GActionMuxer
+ * @prefix: the prefix of the action group to remove
+ *
+ * Removes a #GActionGroup from the #GActionMuxer.
+ *
+ * If any #GActionObservers are registered for actions in the group,
+ * "action_removed" notifications will be emitted, as appropriate.
+ *
+ * Since: 2.32
+ **/
+void
+g_action_muxer_remove (GActionMuxer *muxer,
+                       const gchar  *prefix)
+{
+  Group *group;
+
+  group = g_hash_table_lookup (muxer->groups, prefix);
+
+  if (group != NULL)
+    {
+      gchar **actions;
+      gint i;
+
+      g_hash_table_steal (muxer->groups, prefix);
+
+      actions = g_action_group_list_actions (group->group);
+      for (i = 0; actions[i]; i++)
+        g_action_muxer_action_removed (group->group, actions[i], group);
+      g_strfreev (actions);
+
+      /* 'for loop' or 'four loop'? */
+      for (i = 0; i < 4; i++)
+        g_signal_handler_disconnect (group->group, group->handler_ids[i]);
+
+      g_action_muxer_free_group (group);
+    }
+}
+
+/**
+ * g_action_muxer_new:
+ *
+ * Creates a new #GActionMuxer.
+ *
+ * Since: 2.32
+ **/
+GActionMuxer *
+g_action_muxer_new (void)
+{
+  return g_object_new (G_TYPE_ACTION_MUXER, NULL);
+}
diff --git a/gio/gactionmuxer.h b/gio/gactionmuxer.h
new file mode 100644
index 0000000..0049e82
--- /dev/null
+++ b/gio/gactionmuxer.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright  2011 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
+#error "Only <gio/gio.h> can be included directly."
+#endif
+
+#ifndef __G_ACTION_MUXER_H__
+#define __G_ACTION_MUXER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_ACTION_MUXER                                 (g_action_muxer_get_type ())
+#define G_ACTION_MUXER(inst)                                (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
+                                                             G_TYPE_ACTION_MUXER, GActionMuxer))
+#define G_IS_ACTION_MUXER(inst)                             (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                             G_TYPE_ACTION_MUXER))
+
+GActionMuxer *          g_action_muxer_new                           (void);
+
+void                    g_action_muxer_insert                        (GActionMuxer *muxer,
+                                                                      const gchar  *prefix,
+                                                                      GActionGroup *group);
+
+void                    g_action_muxer_remove                        (GActionMuxer *muxer,
+                                                                      const gchar  *prefix);
+
+G_END_DECLS
+
+#endif /* __G_ACTION_MUXER_H__ */
diff --git a/gio/gio.h b/gio/gio.h
index 8b6c145..30548f2 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -153,6 +153,7 @@
 #include <gio/gmenuproxy.h>
 #include <gio/gactionobservable.h>
 #include <gio/gactionobserver.h>
+#include <gio/gactionmuxer.h>
 
 #undef __GIO_GIO_H_INSIDE__
 
diff --git a/gio/giotypes.h b/gio/giotypes.h
index 328af3c..2423e49 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -52,6 +52,7 @@ typedef struct _GDBusActionGroup              GDBusActionGroup;
 typedef struct _GActionGroup                  GActionGroup;
 typedef struct _GActionObserver               GActionObserver;
 typedef struct _GActionObservable             GActionObservable;
+typedef struct _GActionMuxer                  GActionMuxer;
 typedef struct _GSimpleAction                 GSimpleAction;
 typedef struct _GAction                       GAction;
 typedef struct _GApplication                  GApplication;
diff --git a/gio/tests/actions.c b/gio/tests/actions.c
index 44da440..b42a8d7 100644
--- a/gio/tests/actions.c
+++ b/gio/tests/actions.c
@@ -362,6 +362,216 @@ test_entries (void)
   g_object_unref (actions);
 }
 
+static GString *logstr;
+
+static void
+expect_log (const gchar *expected)
+{
+  g_assert_cmpstr (logstr->str, ==, expected);
+  g_string_set_size (logstr, 0);
+}
+
+static void
+log_event (const gchar *format,
+           ...)
+{
+  va_list ap;
+
+  va_start (ap, format);
+  g_string_append_vprintf (logstr, format, ap);
+  va_end (ap);
+
+  /*
+  va_start (ap, format);
+  g_vprintf (format, ap);
+  g_printf ("\n");
+  va_end (ap);
+  */
+
+  g_string_append_c (logstr, '\n');
+}
+
+typedef GObjectClass MuxerTesterClass;
+typedef GObject MuxerTester;
+static GType muxer_tester_get_type (void);
+
+static void
+muxer_tester_added (GActionObserver    *observer,
+                    GActionObservable  *observable,
+                    const gchar        *action_name,
+                    const GVariantType *parameter_type,
+                    gboolean            enabled,
+                    GVariant           *state)
+{
+  gchar *printed;
+
+  printed = state ? g_variant_print (state, TRUE) : NULL;
+  log_event ("add %d %s %s", enabled, printed ? printed : "(null)", action_name);
+  g_free (printed);
+}
+
+static void
+muxer_tester_removed (GActionObserver    *observer,
+                      GActionObservable  *observable,
+                      const gchar        *action_name)
+{
+  log_event ("rm %s", action_name);
+}
+
+static void
+muxer_tester_enabled_changed (GActionObserver   *observer,
+                              GActionObservable *observable,
+                              const gchar       *action_name,
+                              gboolean           enabled)
+{
+  log_event ("en %d %s", enabled, action_name);
+}
+
+static void
+muxer_tester_state_changed (GActionObserver   *observer,
+                            GActionObservable *observable,
+                            const gchar       *action_name,
+                            GVariant          *state)
+{
+  gchar *printed;
+
+  printed = g_variant_print (state, TRUE);
+  log_event ("st %s %s", printed, action_name);
+  g_free (printed);
+}
+
+static void
+muxer_tester_init (MuxerTester *tester)
+{
+}
+
+static void
+muxer_tester_observer_iface_init (GActionObserverInterface *iface)
+{
+  iface->action_added = muxer_tester_added;
+  iface->action_removed = muxer_tester_removed;
+  iface->action_enabled_changed = muxer_tester_enabled_changed;
+  iface->action_state_changed = muxer_tester_state_changed;
+}
+
+static void
+muxer_tester_class_init (GObjectClass *class)
+{
+}
+
+G_DEFINE_TYPE_WITH_CODE (MuxerTester, muxer_tester, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, muxer_tester_observer_iface_init))
+
+MuxerTester *
+muxer_tester_new (void)
+{
+  return g_object_new (muxer_tester_get_type (), NULL);
+}
+
+static void
+activate_action (GSimpleAction *simple,
+                 GVariant      *parameter,
+                 gpointer       user_data)
+{
+  g_assert (parameter == NULL);
+
+  log_event ("activate %s (null) %s", g_action_get_name (G_ACTION (simple)), (char *) user_data);
+}
+
+static void
+activate_radio_action (GSimpleAction *simple,
+                       GVariant      *parameter,
+                       gpointer       user_data)
+{
+  gchar *printed;
+
+  printed = g_variant_print (parameter, TRUE);
+  log_event ("activate %s %s %s", g_action_get_name (G_ACTION (simple)), printed, (char *) user_data);
+  g_free (printed);
+
+  g_simple_action_set_state (simple, parameter);
+}
+
+static void
+activate_toggle_action (GSimpleAction *simple,
+                        GVariant      *parameter,
+                        gpointer       user_data)
+{
+  GVariant *old_state;
+
+  g_assert (parameter == NULL);
+
+  log_event ("activate %s (null) %s", g_action_get_name (G_ACTION (simple)), (char *) user_data);
+
+  old_state = g_action_get_state (G_ACTION (simple));
+  g_simple_action_set_state (simple, g_variant_new_boolean (!g_variant_get_boolean (old_state)));
+  g_variant_unref (old_state);
+}
+
+static void
+test_muxer (void)
+{
+  const GActionEntry app_entries[] = {
+    { "quit", activate_action },
+    { "new",  activate_action }
+  };
+  const GActionEntry win_entries[] = {
+    { "save", activate_action },
+    { "visible", activate_radio_action, "s", "'never'" },
+    { "fullscreen", activate_toggle_action, NULL, "false" }
+  };
+  GSimpleActionGroup *app_actions;
+  GSimpleActionGroup *win_actions;
+  MuxerTester *tester;
+  GActionMuxer *muxer;
+
+  logstr = g_string_new (NULL);
+  tester = muxer_tester_new ();
+
+  app_actions = g_simple_action_group_new ();
+  win_actions = g_simple_action_group_new ();
+
+  g_simple_action_group_add_entries (app_actions, app_entries, G_N_ELEMENTS (app_entries), "APP");
+
+  muxer = g_action_muxer_new ();
+  expect_log ("");
+
+  g_action_observable_register_observer (G_ACTION_OBSERVABLE (muxer), "app.new", G_ACTION_OBSERVER (tester));
+  g_object_unref (muxer); /* observers should keep muxer alive... */
+  expect_log ("");
+
+  g_action_observable_register_observer (G_ACTION_OBSERVABLE (muxer), "win.visible", G_ACTION_OBSERVER (tester));
+  expect_log ("");
+
+  g_action_muxer_insert (muxer, "app", G_ACTION_GROUP (app_actions));
+  expect_log ("add 1 (null) app.new\n");
+
+  g_action_muxer_insert (muxer, "win", G_ACTION_GROUP (win_actions));
+  expect_log ("");
+
+  g_simple_action_group_add_entries (win_actions, win_entries, G_N_ELEMENTS (win_entries), "WIN");
+  expect_log ("add 1 'never' win.visible\n");
+
+  g_action_observable_register_observer (G_ACTION_OBSERVABLE (muxer), "win.fullscreen", G_ACTION_OBSERVER (tester));
+  expect_log ("");
+
+  g_action_group_activate_action (G_ACTION_GROUP (muxer), "win.fullscreen", NULL);
+  expect_log ("activate fullscreen (null) WIN\n"
+              "st true win.fullscreen\n");
+
+  g_action_group_activate_action (G_ACTION_GROUP (muxer), "win.visible", g_variant_new_string ("always"));
+  expect_log ("activate visible 'always' WIN\n"
+              "st 'always' win.visible\n");
+
+  g_simple_action_set_enabled (G_SIMPLE_ACTION (g_simple_action_group_lookup (app_actions, "new")), FALSE);
+  expect_log ("en 0 app.new\n");
+
+  g_simple_action_group_remove (app_actions, "new");
+  expect_log ("rm app.new\n");
+
+  g_object_unref (tester);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -372,6 +582,7 @@ main (int argc, char **argv)
   g_test_add_func ("/actions/simplegroup", test_simple_group);
   g_test_add_func ("/actions/stateful", test_stateful);
   g_test_add_func ("/actions/entries", test_entries);
+  g_test_add_func ("/actions/muxer", test_muxer);
 
   return g_test_run ();
 }



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