[gtk+/wip/action-helper] Second pass at action rework



commit 6265d4a11fcfe67e034f749e53e1049323f88ea3
Author: Lars Uebernickel <lars uebernickel canonical com>
Date:   Fri Aug 10 11:52:14 2012 +0200

    Second pass at action rework
    
    Each widget now has an associated action group which contains all
    actions associated with itself and all of its parent containers.
    Parents are typically GtkApplicationWindow and GtkApplication, but
    additional action groups can be installed at any point in the hierarchy.
    
    This approach has two major benefits compared to the previous:
    
      (1) Instead of extracting the prefix from an action name and asking a
      widget for the action group with that prefix, callers can now query
      and activate actions directly on the widget's action group.
    
      (2) Additional action groups can be inserted without subclassing
      GtkWidget.
    
    gtk_widget_get_action_group_by_name() and the "actions-changed"
    signal are not needed anymore and removed.
    
    The action group associated with widgets is implemented with a
    GActionMuxer, which gained support for inheriting actions from a parent
    muxer.  Widgets use that to expose actions from their parent, attached
    widget (menus), or application (windows).
    
    Currently, every muxer listens to action change signals from its parent,
    which means that every widget is notified about all changes to all
    actions. A more efficient implementation will follow shortly.
    
    OSX support is probably broken again and accels are still not working.

 gtk/Makefile.am            |    6 +
 gtk/gactionmuxer.c         |  777 ++++++++++++++++++++++++++++++++++++++++++++
 gtk/gactionmuxer.h         |   58 ++++
 gtk/gactionobservable.c    |   78 +++++
 gtk/gactionobservable.h    |   62 ++++
 gtk/gactionobserver.c      |  159 +++++++++
 gtk/gactionobserver.h      |   88 +++++
 gtk/gtk.symbols            |    2 +
 gtk/gtkactionhelper.c      |  427 ++++++------------------
 gtk/gtkapplicationwindow.c |   19 +-
 gtk/gtkmenu.c              |   52 +---
 gtk/gtkmenuprivate.h       |    1 -
 gtk/gtkwidget.c            |  184 ++++-------
 gtk/gtkwidget.h            |   16 +-
 gtk/gtkwidgetprivate.h     |    3 +
 gtk/gtkwindow.c            |   18 +-
 16 files changed, 1423 insertions(+), 527 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 82cf10c..d8f79b7 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -535,6 +535,9 @@ gtk_private_h_sources =		\
 	gtkwin32themeprivate.h	\
 	gtkwindowprivate.h	\
 	gtktreemenu.h		\
+	gactionmuxer.h		\
+	gactionobserver.h	\
+	gactionobservable.h	\
 	$(gtk_private_type_h_sources) \
 	$(gtk_clipboard_dnd_h_sources) \
 	$(gtk_appchooser_impl_h_sources)
@@ -852,6 +855,9 @@ gtk_base_c_sources = 		\
 	gtkwidgetpath.c		\
 	gtkwindow.c		\
 	gtkwin32theme.c		\
+	gactionmuxer.c		\
+	gactionobserver.c	\
+	gactionobservable.c	\
 	$(gtk_clipboard_dnd_c_sources) \
 	$(gtk_appchooser_impl_c_sources)
 
diff --git a/gtk/gactionmuxer.c b/gtk/gactionmuxer.c
new file mode 100644
index 0000000..3c996c4
--- /dev/null
+++ b/gtk/gactionmuxer.c
@@ -0,0 +1,777 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "config.h"
+
+#include "gactionmuxer.h"
+
+#include "gactionobservable.h"
+#include "gactionobserver.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 aggregating 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).
+ */
+
+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 *observed_actions;
+  GHashTable *groups;
+  GActionMuxer *parent;
+};
+
+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))
+
+enum
+{
+  PROP_0,
+  PROP_PARENT,
+  NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES];
+
+typedef struct
+{
+  GActionMuxer *muxer;
+  GSList       *watchers;
+  gchar        *fullname;
+} Action;
+
+typedef struct
+{
+  GActionMuxer *muxer;
+  GActionGroup *group;
+  gchar        *prefix;
+  gulong        handler_ids[4];
+} Group;
+
+static void
+g_action_muxer_append_group_actions (gpointer key,
+                                     gpointer value,
+                                     gpointer user_data)
+{
+  const gchar *prefix = key;
+  Group *group = value;
+  GArray *actions = user_data;
+  gchar **group_actions;
+  gchar **action;
+
+  group_actions = g_action_group_list_actions (group->group);
+  for (action = group_actions; *action; action++)
+    {
+      gchar *fullname;
+
+      fullname = g_strconcat (prefix, ".", *action, NULL);
+      g_array_append_val (actions, fullname);
+    }
+
+  g_strfreev (group_actions);
+}
+
+static gchar **
+g_action_muxer_list_actions (GActionGroup *action_group)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (action_group);
+  GArray *actions;
+
+  actions = g_array_new (TRUE, FALSE, sizeof (gchar *));
+
+  for ( ; muxer != NULL; muxer = muxer->parent)
+    {
+      g_hash_table_foreach (muxer->groups,
+                            g_action_muxer_append_group_actions,
+                            actions);
+    }
+
+  return (gchar **) g_array_free (actions, FALSE);
+}
+
+static Group *
+g_action_muxer_find_group (GActionMuxer  *muxer,
+                           const gchar   *full_name,
+                           const gchar  **action_name)
+{
+  const gchar *dot;
+  gchar *prefix;
+  Group *group;
+
+  dot = strchr (full_name, '.');
+
+  if (!dot)
+    return NULL;
+
+  prefix = g_strndup (full_name, dot - full_name);
+  group = g_hash_table_lookup (muxer->groups, prefix);
+  g_free (prefix);
+
+  if (action_name)
+    *action_name = dot + 1;
+
+  return group;
+}
+
+static void
+g_action_muxer_action_enabled_changed (GActionMuxer *muxer,
+                                       const gchar  *action_name,
+                                       gboolean      enabled)
+{
+  Action *action;
+  GSList *node;
+
+  action = g_hash_table_lookup (muxer->observed_actions, action_name);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (muxer), action_name, enabled);
+  g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), action_name, enabled);
+}
+
+static void
+g_action_muxer_group_action_enabled_changed (GActionGroup *action_group,
+                                             const gchar  *action_name,
+                                             gboolean      enabled,
+                                             gpointer      user_data)
+{
+  Group *group = user_data;
+  gchar *fullname;
+
+  fullname = g_strconcat (group->prefix, ".", action_name, NULL);
+  g_action_muxer_action_enabled_changed (group->muxer, fullname, enabled);
+
+  g_free (fullname);
+}
+
+static void
+g_action_muxer_parent_action_enabled_changed (GActionGroup *action_group,
+                                              const gchar  *action_name,
+                                              gboolean      enabled,
+                                              gpointer      user_data)
+{
+  GActionMuxer *muxer = user_data;
+
+  g_action_muxer_action_enabled_changed (muxer, action_name, enabled);
+}
+
+static void
+g_action_muxer_action_state_changed (GActionMuxer *muxer,
+                                     const gchar  *action_name,
+                                     GVariant     *state)
+{
+  Action *action;
+  GSList *node;
+
+  action = g_hash_table_lookup (muxer->observed_actions, action_name);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (muxer), action_name, state);
+  g_action_group_action_state_changed (G_ACTION_GROUP (muxer), action_name, state);
+}
+
+static void
+g_action_muxer_group_action_state_changed (GActionGroup *action_group,
+                                           const gchar  *action_name,
+                                           GVariant     *state,
+                                           gpointer      user_data)
+{
+  Group *group = user_data;
+  gchar *fullname;
+
+  fullname = g_strconcat (group->prefix, ".", action_name, NULL);
+  g_action_muxer_action_state_changed (group->muxer, fullname, state);
+
+  g_free (fullname);
+}
+
+static void
+g_action_muxer_parent_action_state_changed (GActionGroup *action_group,
+                                            const gchar  *action_name,
+                                            GVariant     *state,
+                                            gpointer      user_data)
+{
+  GActionMuxer *muxer = user_data;
+
+  g_action_muxer_action_state_changed (muxer, action_name, state);
+}
+
+static void
+g_action_muxer_action_added (GActionMuxer *muxer,
+                             const gchar  *action_name,
+                             GActionGroup *original_group,
+                             const gchar  *orignal_action_name)
+{
+  const GVariantType *parameter_type;
+  gboolean enabled;
+  GVariant *state;
+
+  if (g_action_group_query_action (original_group, orignal_action_name,
+                                   &enabled, &parameter_type, NULL, NULL, &state))
+    {
+      Action *action;
+      GSList *node;
+
+      action = g_hash_table_lookup (muxer->observed_actions, action_name);
+
+      for (node = action ? action->watchers : NULL; node; node = node->next)
+        g_action_observer_action_added (node->data,
+                                        G_ACTION_OBSERVABLE (muxer),
+                                        action_name, parameter_type, enabled, state);
+
+      g_action_group_action_added (G_ACTION_GROUP (muxer), action_name);
+
+      if (state)
+        g_variant_unref (state);
+    }
+}
+
+static void
+g_action_muxer_action_added_to_group (GActionGroup *action_group,
+                                      const gchar  *action_name,
+                                      gpointer      user_data)
+{
+  Group *group = user_data;
+  gchar *fullname;
+
+  fullname = g_strconcat (group->prefix, ".", action_name, NULL);
+  g_action_muxer_action_added (group->muxer, fullname, action_group, action_name);
+
+  g_free (fullname);
+}
+
+static void
+g_action_muxer_action_added_to_parent (GActionGroup *action_group,
+                                       const gchar  *action_name,
+                                       gpointer      user_data)
+{
+  GActionMuxer *muxer = user_data;
+
+  g_action_muxer_action_added (muxer, action_name, action_group, action_name);
+}
+
+static void
+g_action_muxer_action_removed (GActionMuxer *muxer,
+                               const gchar  *action_name)
+{
+  Action *action;
+  GSList *node;
+
+  action = g_hash_table_lookup (muxer->observed_actions, action_name);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (muxer), action_name);
+  g_action_group_action_removed (G_ACTION_GROUP (muxer), action_name);
+}
+
+static void
+g_action_muxer_action_removed_from_group (GActionGroup *action_group,
+                                          const gchar  *action_name,
+                                          gpointer      user_data)
+{
+  Group *group = user_data;
+  gchar *fullname;
+
+  fullname = g_strconcat (group->prefix, ".", action_name, NULL);
+  g_action_muxer_action_removed (group->muxer, fullname);
+
+  g_free (fullname);
+}
+
+static void
+g_action_muxer_action_removed_from_parent (GActionGroup *action_group,
+                                           const gchar  *action_name,
+                                           gpointer      user_data)
+{
+  GActionMuxer *muxer = user_data;
+
+  g_action_muxer_action_removed (muxer, action_name);
+}
+
+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;
+  const gchar *unprefixed_name;
+
+  group = g_action_muxer_find_group (muxer, action_name, &unprefixed_name);
+
+  if (group)
+    return g_action_group_query_action (group->group, unprefixed_name, enabled,
+                                        parameter_type, state_type, state_hint, state);
+
+  if (muxer->parent)
+    return g_action_group_query_action (G_ACTION_GROUP (muxer->parent), action_name,
+                                        enabled, parameter_type,
+                                        state_type, state_hint, state);
+
+  return FALSE;
+}
+
+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;
+  const gchar *unprefixed_name;
+
+  group = g_action_muxer_find_group (muxer, action_name, &unprefixed_name);
+
+  if (group)
+    g_action_group_activate_action (group->group, unprefixed_name, parameter);
+  else if (muxer->parent)
+    g_action_group_activate_action (G_ACTION_GROUP (muxer->parent), 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;
+  const gchar *unprefixed_name;
+
+  group = g_action_muxer_find_group (muxer, action_name, &unprefixed_name);
+
+  if (group)
+    g_action_group_change_action_state (group->group, unprefixed_name, state);
+  else if (muxer->parent)
+    g_action_group_change_action_state (G_ACTION_GROUP (muxer->parent), 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->observed_actions, action->fullname);
+
+        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->observed_actions, name);
+
+  if (action == NULL)
+    {
+      action = g_slice_new (Action);
+      action->muxer = muxer;
+      action->fullname = g_strdup (name);
+      action->watchers = NULL;
+
+      g_hash_table_insert (muxer->observed_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->observed_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;
+  gint i;
+
+  /* 'for loop' or 'four loop'? */
+  for (i = 0; i < 4; i++)
+    g_signal_handler_disconnect (group->group, group->handler_ids[i]);
+
+  g_object_unref (group->group);
+  g_free (group->prefix);
+
+  g_slice_free (Group, group);
+}
+
+static void
+g_action_muxer_free_action (gpointer data)
+{
+  Action *action = data;
+  GSList *it;
+
+  for (it = action->watchers; it; it = it->next)
+    g_object_weak_unref (G_OBJECT (it->data), g_action_muxer_weak_notify, action);
+
+  g_slist_free (action->watchers);
+  g_free (action->fullname);
+
+  g_slice_free (Action, action);
+}
+
+static void
+g_action_muxer_finalize (GObject *object)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (object);
+
+  g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0);
+  g_hash_table_unref (muxer->observed_actions);
+  g_hash_table_unref (muxer->groups);
+
+  G_OBJECT_CLASS (g_action_muxer_parent_class)
+    ->finalize (object);
+}
+
+static void
+g_action_muxer_dispose (GObject *object)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (object);
+
+  if (muxer->parent)
+  {
+    g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_action_added_to_parent, muxer);
+    g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_action_removed_from_parent, muxer);
+    g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_parent_action_enabled_changed, muxer);
+    g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_parent_action_state_changed, muxer);
+
+    g_clear_object (&muxer->parent);
+  }
+
+  g_hash_table_remove_all (muxer->observed_actions);
+
+  G_OBJECT_CLASS (g_action_muxer_parent_class)
+    ->dispose (object);
+}
+
+static void
+g_action_muxer_get_property (GObject    *object,
+                             guint       property_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (object);
+
+  switch (property_id)
+    {
+    case PROP_PARENT:
+      g_value_set_object (value, g_action_muxer_get_parent (muxer));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+g_action_muxer_set_property (GObject      *object,
+                             guint         property_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GActionMuxer *muxer = G_ACTION_MUXER (object);
+
+  switch (property_id)
+    {
+    case PROP_PARENT:
+      g_action_muxer_set_parent (muxer, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+g_action_muxer_init (GActionMuxer *muxer)
+{
+  muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_action_muxer_free_action);
+  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->get_property = g_action_muxer_get_property;
+  class->set_property = g_action_muxer_set_property;
+  class->finalize = g_action_muxer_finalize;
+  class->dispose = g_action_muxer_dispose;
+
+  properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent",
+                                                 "The parent muxer",
+                                                 G_TYPE_ACTION_MUXER,
+                                                 G_PARAM_READWRITE |
+                                                 G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (class, NUM_PROPERTIES, properties);
+}
+
+/*
+ * 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 ('.').
+ */
+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_to_group (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_to_group), group);
+  group->handler_ids[1] = g_signal_connect (group->group, "action-removed",
+                                            G_CALLBACK (g_action_muxer_action_removed_from_group), group);
+  group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed",
+                                            G_CALLBACK (g_action_muxer_group_action_enabled_changed), group);
+  group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed",
+                                            G_CALLBACK (g_action_muxer_group_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.
+ */
+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_from_group (group->group, actions[i], group);
+      g_strfreev (actions);
+
+      g_action_muxer_free_group (group);
+    }
+}
+
+/*
+ * g_action_muxer_new:
+ *
+ * Creates a new #GActionMuxer.
+ */
+GActionMuxer *
+g_action_muxer_new (GActionMuxer *parent)
+{
+  return g_object_new (G_TYPE_ACTION_MUXER,
+                       "parent", parent,
+                       NULL);
+}
+
+/* g_action_muxer_get_parent:
+ * @muxer: a #GActionMuxer
+ *
+ * Returns: (transfer-none): the parent of @muxer, or NULL.
+ */
+GActionMuxer *
+g_action_muxer_get_parent (GActionMuxer *muxer)
+{
+  g_return_val_if_fail (G_IS_ACTION_MUXER (muxer), NULL);
+
+  return muxer->parent;
+}
+
+/* g_action_muxer_set_parent:
+ * @muxer: a #GActionMuxer
+ * @parent: (allow-none): the new parent #GActionMuxer
+ *
+ * Sets the parent of @muxer to @parent.
+ */
+void
+g_action_muxer_set_parent (GActionMuxer *muxer,
+                           GActionMuxer *parent)
+{
+  g_return_if_fail (G_IS_ACTION_MUXER (muxer));
+  g_return_if_fail (parent == NULL || G_IS_ACTION_MUXER (parent));
+
+  if (muxer->parent == parent)
+    return;
+
+  if (muxer->parent != NULL)
+    {
+      gchar **actions;
+      gchar **it;
+
+      actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
+      for (it = actions; *it; it++)
+        g_action_group_action_removed (G_ACTION_GROUP (muxer), *it);
+      g_strfreev (actions);
+
+      g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_action_added_to_parent, muxer);
+      g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_action_removed_from_parent, muxer);
+      g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_parent_action_enabled_changed, muxer);
+      g_signal_handlers_disconnect_by_func (muxer->parent, g_action_muxer_parent_action_state_changed, muxer);
+
+      g_object_unref (muxer->parent);
+    }
+
+  muxer->parent = parent;
+
+  if (muxer->parent != NULL)
+    {
+      gchar **actions;
+      gchar **it;
+
+      g_object_ref (muxer->parent);
+
+      actions = g_action_group_list_actions (G_ACTION_GROUP (muxer->parent));
+      for (it = actions; *it; it++)
+        g_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it);
+      g_strfreev (actions);
+
+      g_signal_connect (muxer->parent, "action-added",
+                        G_CALLBACK (g_action_muxer_action_added_to_parent), muxer);
+      g_signal_connect (muxer->parent, "action-removed",
+                        G_CALLBACK (g_action_muxer_action_removed_from_parent), muxer);
+      g_signal_connect (muxer->parent, "action-enabled-changed",
+                        G_CALLBACK (g_action_muxer_parent_action_enabled_changed), muxer);
+      g_signal_connect (muxer->parent, "action-state-changed",
+                        G_CALLBACK (g_action_muxer_parent_action_state_changed), muxer);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]);
+}
diff --git a/gtk/gactionmuxer.h b/gtk/gactionmuxer.h
new file mode 100644
index 0000000..95abab0
--- /dev/null
+++ b/gtk/gactionmuxer.h
@@ -0,0 +1,58 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#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))
+
+typedef struct _GActionMuxer                                GActionMuxer;
+
+G_GNUC_INTERNAL
+GType                   g_action_muxer_get_type                         (void);
+G_GNUC_INTERNAL
+GActionMuxer *          g_action_muxer_new                              (GActionMuxer *parent);
+
+G_GNUC_INTERNAL
+void                    g_action_muxer_insert                           (GActionMuxer *muxer,
+                                                                         const gchar  *prefix,
+                                                                         GActionGroup *group);
+
+G_GNUC_INTERNAL
+void                    g_action_muxer_remove                           (GActionMuxer *muxer,
+                                                                         const gchar  *prefix);
+
+G_GNUC_INTERNAL
+GActionMuxer *          g_action_muxer_get_parent                       (GActionMuxer *muxer);
+
+G_GNUC_INTERNAL
+void                    g_action_muxer_set_parent                       (GActionMuxer *muxer,
+                                                                         GActionMuxer *parent);
+
+G_END_DECLS
+
+#endif /* __G_ACTION_MUXER_H__ */
diff --git a/gtk/gactionobservable.c b/gtk/gactionobservable.c
new file mode 100644
index 0000000..17049b7
--- /dev/null
+++ b/gtk/gactionobservable.c
@@ -0,0 +1,78 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "config.h"
+
+#include "gactionobservable.h"
+
+G_DEFINE_INTERFACE (GActionObservable, g_action_observable, G_TYPE_OBJECT)
+
+/*
+ * SECTION:gactionobserable
+ * @short_description: an interface implemented by objects that report
+ *                     changes to actions
+ */
+
+void
+g_action_observable_default_init (GActionObservableInterface *iface)
+{
+}
+
+/*
+ * g_action_observable_register_observer:
+ * @observable: a #GActionObservable
+ * @action_name: the name of the action
+ * @observer: the #GActionObserver to which the events will be reported
+ *
+ * Registers @observer as being interested in changes to @action_name on
+ * @observable.
+ */
+void
+g_action_observable_register_observer (GActionObservable *observable,
+                                       const gchar       *action_name,
+                                       GActionObserver   *observer)
+{
+  g_return_if_fail (G_IS_ACTION_OBSERVABLE (observable));
+
+  G_ACTION_OBSERVABLE_GET_IFACE (observable)
+    ->register_observer (observable, action_name, observer);
+}
+
+/*
+ * g_action_observable_unregister_observer:
+ * @observable: a #GActionObservable
+ * @action_name: the name of the action
+ * @observer: the #GActionObserver to which the events will be reported
+ *
+ * Removes the registration of @observer as being interested in changes
+ * to @action_name on @observable.
+ *
+ * If the observer was registered multiple times, it must be
+ * unregistered an equal number of times.
+ */
+void
+g_action_observable_unregister_observer (GActionObservable *observable,
+                                         const gchar       *action_name,
+                                         GActionObserver   *observer)
+{
+  g_return_if_fail (G_IS_ACTION_OBSERVABLE (observable));
+
+  G_ACTION_OBSERVABLE_GET_IFACE (observable)
+    ->unregister_observer (observable, action_name, observer);
+}
diff --git a/gtk/gactionobservable.h b/gtk/gactionobservable.h
new file mode 100644
index 0000000..e5906aa
--- /dev/null
+++ b/gtk/gactionobservable.h
@@ -0,0 +1,62 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __G_ACTION_OBSERVABLE_H__
+#define __G_ACTION_OBSERVABLE_H__
+
+#include <gtk/gactionobserver.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_ACTION_OBSERVABLE                            (g_action_observable_get_type ())
+#define G_ACTION_OBSERVABLE(inst)                           (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
+                                                             G_TYPE_ACTION_OBSERVABLE, GActionObservable))
+#define G_IS_ACTION_OBSERVABLE(inst)                        (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                             G_TYPE_ACTION_OBSERVABLE))
+#define G_ACTION_OBSERVABLE_GET_IFACE(inst)                 (G_TYPE_INSTANCE_GET_INTERFACE ((inst),                  \
+                                                             G_TYPE_ACTION_OBSERVABLE, GActionObservableInterface))
+
+typedef struct _GActionObservableInterface                  GActionObservableInterface;
+
+struct _GActionObservableInterface
+{
+  GTypeInterface g_iface;
+
+  void (* register_observer)   (GActionObservable *observable,
+                                const gchar       *action_name,
+                                GActionObserver   *observer);
+  void (* unregister_observer) (GActionObservable *observable,
+                                const gchar       *action_name,
+                                GActionObserver   *observer);
+};
+
+G_GNUC_INTERNAL
+GType                   g_action_observable_get_type                    (void);
+G_GNUC_INTERNAL
+void                    g_action_observable_register_observer           (GActionObservable  *observable,
+                                                                         const gchar        *action_name,
+                                                                         GActionObserver    *observer);
+G_GNUC_INTERNAL
+void                    g_action_observable_unregister_observer         (GActionObservable  *observable,
+                                                                         const gchar        *action_name,
+                                                                         GActionObserver    *observer);
+
+G_END_DECLS
+
+#endif /* __G_ACTION_OBSERVABLE_H__ */
diff --git a/gtk/gactionobserver.c b/gtk/gactionobserver.c
new file mode 100644
index 0000000..babd38a
--- /dev/null
+++ b/gtk/gactionobserver.c
@@ -0,0 +1,159 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "config.h"
+
+#include "gactionobserver.h"
+
+G_DEFINE_INTERFACE (GActionObserver, g_action_observer, G_TYPE_OBJECT)
+
+/**
+ * SECTION:gactionobserver
+ * @short_description: an interface implemented by objects that are
+ *                     interested in monitoring actions for changes
+ *
+ * GActionObserver is a simple interface allowing objects that wish to
+ * be notified of changes to actions to be notified of those changes.
+ *
+ * It is also possible to monitor changes to action groups using
+ * #GObject signals, but there are a number of reasons that this
+ * approach could become problematic:
+ *
+ *  - there are four separate signals that must be manually connected
+ *    and disconnected
+ *
+ *  - when a large number of different observers wish to monitor a
+ *    (usually disjoint) set of actions within the same action group,
+ *    there is only one way to avoid having all notifications delivered
+ *    to all observers: signal detail.  In order to use signal detail,
+ *    each action name must be quarked, which is not always practical.
+ *
+ *  - even if quarking is acceptable, #GObject signal details are
+ *    implemented by scanning a linked list, so there is no real
+ *    decrease in complexity
+ */
+
+void
+g_action_observer_default_init (GActionObserverInterface *class)
+{
+}
+
+/*
+ * g_action_observer_action_added:
+ * @observer: a #GActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ * @enabled: %TRUE if the action is now enabled
+ * @parameter_type: the parameter type for action invocations, or %NULL
+ *                  if no parameter is required
+ * @state: the current state of the action, or %NULL if the action is
+ *         stateless
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for is added.
+ *
+ * This function should only be called by objects with which the
+ * observer has explicitly registered itself to receive events.
+ */
+void
+g_action_observer_action_added (GActionObserver    *observer,
+                                GActionObservable  *observable,
+                                const gchar        *action_name,
+                                const GVariantType *parameter_type,
+                                gboolean            enabled,
+                                GVariant           *state)
+{
+  g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
+
+  G_ACTION_OBSERVER_GET_IFACE (observer)
+    ->action_added (observer, observable, action_name, parameter_type, enabled, state);
+}
+
+/*
+ * g_action_observer_action_enabled_changed:
+ * @observer: a #GActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ * @enabled: %TRUE if the action is now enabled
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for becomes enabled or disabled.
+ *
+ * This function should only be called by objects with which the
+ * observer has explicitly registered itself to receive events.
+ */
+void
+g_action_observer_action_enabled_changed (GActionObserver   *observer,
+                                          GActionObservable *observable,
+                                          const gchar       *action_name,
+                                          gboolean           enabled)
+{
+  g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
+
+  G_ACTION_OBSERVER_GET_IFACE (observer)
+    ->action_enabled_changed (observer, observable, action_name, enabled);
+}
+
+/*
+ * g_action_observer_action_state_changed:
+ * @observer: a #GActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ * @state: the new state of the action
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for changes its state.
+ *
+ * This function should only be called by objects with which the
+ * observer has explicitly registered itself to receive events.
+ */
+void
+g_action_observer_action_state_changed (GActionObserver   *observer,
+                                        GActionObservable *observable,
+                                        const gchar       *action_name,
+                                        GVariant          *state)
+{
+  g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
+
+  G_ACTION_OBSERVER_GET_IFACE (observer)
+    ->action_state_changed (observer, observable, action_name, state);
+}
+
+/*
+ * g_action_observer_action_removed:
+ * @observer: a #GActionObserver
+ * @observable: the source of the event
+ * @action_name: the name of the action
+ *
+ * This function is called when an action that the observer is
+ * registered to receive events for is removed.
+ *
+ * This function should only be called by objects with which the
+ * observer has explicitly registered itself to receive events.
+ */
+void
+g_action_observer_action_removed (GActionObserver   *observer,
+                                  GActionObservable *observable,
+                                  const gchar       *action_name)
+{
+  g_return_if_fail (G_IS_ACTION_OBSERVER (observer));
+
+  G_ACTION_OBSERVER_GET_IFACE (observer)
+    ->action_removed (observer, observable, action_name);
+}
diff --git a/gtk/gactionobserver.h b/gtk/gactionobserver.h
new file mode 100644
index 0000000..e6d2833
--- /dev/null
+++ b/gtk/gactionobserver.h
@@ -0,0 +1,88 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __G_ACTION_OBSERVER_H__
+#define __G_ACTION_OBSERVER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_ACTION_OBSERVER                              (g_action_observer_get_type ())
+#define G_ACTION_OBSERVER(inst)                             (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
+                                                             G_TYPE_ACTION_OBSERVER, GActionObserver))
+#define G_IS_ACTION_OBSERVER(inst)                          (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                             G_TYPE_ACTION_OBSERVER))
+#define G_ACTION_OBSERVER_GET_IFACE(inst)                   (G_TYPE_INSTANCE_GET_INTERFACE ((inst),                  \
+                                                             G_TYPE_ACTION_OBSERVER, GActionObserverInterface))
+
+typedef struct _GActionObserverInterface                    GActionObserverInterface;
+typedef struct _GActionObservable                           GActionObservable;
+typedef struct _GActionObserver                             GActionObserver;
+
+struct _GActionObserverInterface
+{
+  GTypeInterface g_iface;
+
+  void (* action_added)           (GActionObserver    *observer,
+                                   GActionObservable  *observable,
+                                   const gchar        *action_name,
+                                   const GVariantType *parameter_type,
+                                   gboolean            enabled,
+                                   GVariant           *state);
+  void (* action_enabled_changed) (GActionObserver    *observer,
+                                   GActionObservable  *observable,
+                                   const gchar        *action_name,
+                                   gboolean            enabled);
+  void (* action_state_changed)   (GActionObserver    *observer,
+                                   GActionObservable  *observable,
+                                   const gchar        *action_name,
+                                   GVariant           *state);
+  void (* action_removed)         (GActionObserver    *observer,
+                                   GActionObservable  *observable,
+                                   const gchar        *action_name);
+};
+
+G_GNUC_INTERNAL
+GType                   g_action_observer_get_type                      (void);
+G_GNUC_INTERNAL
+void                    g_action_observer_action_added                  (GActionObserver    *observer,
+                                                                         GActionObservable  *observable,
+                                                                         const gchar        *action_name,
+                                                                         const GVariantType *parameter_type,
+                                                                         gboolean            enabled,
+                                                                         GVariant           *state);
+G_GNUC_INTERNAL
+void                    g_action_observer_action_enabled_changed        (GActionObserver    *observer,
+                                                                         GActionObservable  *observable,
+                                                                         const gchar        *action_name,
+                                                                         gboolean            enabled);
+G_GNUC_INTERNAL
+void                    g_action_observer_action_state_changed          (GActionObserver    *observer,
+                                                                         GActionObservable  *observable,
+                                                                         const gchar        *action_name,
+                                                                         GVariant           *state);
+G_GNUC_INTERNAL
+void                    g_action_observer_action_removed                (GActionObserver    *observer,
+                                                                         GActionObservable  *observable,
+                                                                         const gchar        *action_name);
+
+G_END_DECLS
+
+#endif /* __G_ACTION_OBSERVER_H__ */
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index 0247de8..5c655f1 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -3811,6 +3811,8 @@ gtk_widget_unmap
 gtk_widget_unparent
 gtk_widget_unrealize
 gtk_widget_unset_state_flags
+gtk_widget_get_action_context
+gtk_widget_insert_action_group
 #ifdef GDK_WINDOWING_WIN32
 gtk_win32_embed_widget_get_type
 #endif
diff --git a/gtk/gtkactionhelper.c b/gtk/gtkactionhelper.c
index f4da0e5..4648aff 100644
--- a/gtk/gtkactionhelper.c
+++ b/gtk/gtkactionhelper.c
@@ -18,8 +18,10 @@
  */
 
 #include "gtkactionhelper.h"
+#include "gactionobservable.h"
 
 #include "gtkwidget.h"
+#include "gtkwidgetprivate.h"
 
 #include <string.h>
 
@@ -30,9 +32,6 @@ typedef struct
   GHashTable *watchers;
 } GtkActionHelperGroup;
 
-/* These functions are now GtkActionHelperGroup reports changes to the
- * individual GtkActionHelper instances.
- */
 static void             gtk_action_helper_action_added                  (GtkActionHelper    *helper,
                                                                          gboolean            enabled,
                                                                          const GVariantType *parameter_type,
@@ -47,187 +46,6 @@ static void             gtk_action_helper_action_enabled_changed        (GtkActi
 static void             gtk_action_helper_action_state_changed          (GtkActionHelper    *helper,
                                                                          GVariant           *new_state);
 
-/*< private >
- * GtkActionHelperGroup:
- *
- * This helper struct is created one per GActionGroup in order to
- * monitor the action group for changes and notify interested
- * GtkActionHelper instances in an efficient way.
- *
- * This exists because GSignal is insufficient in these two ways:
- *
- *  1) Signal detail involves quarking the detail string and we don't
- *     want to quark arbitrary action names (which may be coming from
- *     out of process).
- *
- *  2) Even if that was okay, detailed signal emission is linear in the
- *     total number of connected handlers (ie: each connected handler is
- *     manually checked to see if the detail matches).
- *
- * If we fix GSignal to have a non-quark-based hash table for detailed
- * emissions then we can lose this struct.
- *
- * The reference counting situation here is a little bit tricky.  The
- * #GActionGroup "owns" this struct (via the #GDestroyNotify on the
- * qdata).  At the same time, we want to keep the #GActionGroup alive
- * for as long as we have any watchers.  We do this by taking a
- * reference on the #GActionGroup for each watch.
- */
-
-static void
-gtk_action_helper_group_action_added (GActionGroup *action_group,
-                                      const gchar  *action_name,
-                                      gpointer      user_data)
-{
-  GtkActionHelperGroup *group_helper = user_data;
-  GSList *watchers;
-
-  g_assert (action_group == group_helper->group);
-
-  watchers = g_hash_table_lookup (group_helper->watchers, action_name);
-
-  if (watchers)
-    {
-      const GVariantType *parameter_type;
-      gboolean enabled;
-      GVariant *state;
-      GSList *node;
-
-      if (!g_action_group_query_action (action_group, action_name, &enabled, &parameter_type, NULL, NULL, &state))
-        g_assert_not_reached ();
-
-      for (node = watchers; node; node = node->next)
-        gtk_action_helper_action_added (node->data, enabled, parameter_type, state, TRUE);
-
-      if (state)
-        g_variant_unref (state);
-    }
-}
-
-static void
-gtk_action_helper_group_action_removed (GActionGroup *action,
-                                        const gchar  *action_name,
-                                        gpointer      user_data)
-{
-  GtkActionHelperGroup *group_helper = user_data;
-  GSList *node;
-
-  for (node = g_hash_table_lookup (group_helper->watchers, action_name); node; node = node->next)
-    gtk_action_helper_action_removed (node->data);
-}
-
-static void
-gtk_action_helper_group_action_enabled_changed (GActionGroup *group,
-                                                const gchar  *action_name,
-                                                gboolean      enabled,
-                                                gpointer      user_data)
-{
-  GtkActionHelperGroup *group_helper = user_data;
-  GSList *node;
-
-  for (node = g_hash_table_lookup (group_helper->watchers, action_name); node; node = node->next)
-    gtk_action_helper_action_enabled_changed (node->data, enabled);
-}
-
-static void
-gtk_action_helper_group_action_state_changed (GActionGroup *group,
-                                              const gchar  *action_name,
-                                              GVariant     *new_state,
-                                              gpointer      user_data)
-{
-  GtkActionHelperGroup *group_helper = user_data;
-  GSList *node;
-
-  for (node = g_hash_table_lookup (group_helper->watchers, action_name); node; node = node->next)
-    gtk_action_helper_action_state_changed (node->data, new_state);
-}
-
-static void
-gtk_action_helper_group_free (gpointer data)
-{
-  GtkActionHelperGroup *group_helper = data;
-
-  g_signal_handlers_disconnect_by_data (group_helper->group, group_helper);
-  g_assert_cmpint (g_hash_table_size (group_helper->watchers), ==, 0);
-  g_hash_table_unref (group_helper->watchers);
-
-  g_slice_free (GtkActionHelperGroup, group_helper);
-}
-
-/*< really quite private >
- * gtk_action_helper_group_get:
- * @group: a #GActionGroup
- *
- * Gets the #GtkActionHelperGroup for @group, creating it if it does not
- * exist.
- *
- * The helper will be freed when @group is destroyed.
- */
-static GtkActionHelperGroup *
-gtk_action_helper_group_get (GActionGroup *group)
-{
-  GtkActionHelperGroup *group_helper;
-  static GQuark group_helper_quark;
-
-  if (!group_helper_quark)
-    group_helper_quark = g_quark_from_static_string ("GtkActionHelper group data quark");
-
-  group_helper = g_object_get_qdata (G_OBJECT (group), group_helper_quark);
-
-  if (group_helper == NULL)
-    {
-      group_helper = g_slice_new (GtkActionHelperGroup);
-      group_helper->group = group;
-      group_helper->watchers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-
-      g_signal_connect (group, "action-added",
-                        G_CALLBACK (gtk_action_helper_group_action_added), group_helper);
-      g_signal_connect (group, "action-removed",
-                        G_CALLBACK (gtk_action_helper_group_action_removed), group_helper);
-      g_signal_connect (group, "action-enabled-changed",
-                        G_CALLBACK (gtk_action_helper_group_action_enabled_changed), group_helper);
-      g_signal_connect (group, "action-state-changed",
-                        G_CALLBACK (gtk_action_helper_group_action_state_changed), group_helper);
-
-      g_object_set_qdata_full (G_OBJECT (group), group_helper_quark, group_helper, gtk_action_helper_group_free);
-    }
-
-  return group_helper;
-}
-
-static void
-gtk_action_helper_group_watch (GtkActionHelperGroup *group_helper,
-                               const gchar          *action_name,
-                               GtkActionHelper      *helper)
-{
-  GSList *helpers;
-
-  helpers = g_hash_table_lookup (group_helper->watchers, action_name);
-  helpers = g_slist_prepend (helpers, helper);
-  g_hash_table_insert (group_helper->watchers, g_strdup (action_name), helpers);
-  g_object_ref (group_helper->group);
-}
-
-static void
-gtk_action_helper_group_unwatch (GtkActionHelperGroup *group_helper,
-                                 const gchar          *action_name,
-                                 GtkActionHelper      *helper)
-{
-  GSList *helpers;
-
-  helpers = g_hash_table_lookup (group_helper->watchers, action_name);
-  g_warn_if_fail (helpers != NULL);
-  helpers = g_slist_remove (helpers, helper);
-
-  if (helpers)
-    g_hash_table_insert (group_helper->watchers, g_strdup (action_name), helpers);
-  else
-    g_hash_table_remove (group_helper->watchers, action_name);
-
-  /* This may be our own death... */
-  g_object_unref (group_helper->group);
-}
-
 typedef GObjectClass GtkActionHelperClass;
 
 struct _GtkActionHelper
@@ -239,9 +57,8 @@ struct _GtkActionHelper
 
   GtkActionHelperGroup *group;
 
-  gchar *full_action_name;    /* "app.quit" */
-  gchar *action_group_name;   /* "app" */
-  gchar *action_name;         /* "quit" */
+  GActionMuxer *action_context;
+  gchar *action_name;
 
   GVariant *target;
 
@@ -264,7 +81,10 @@ enum
 
 static GParamSpec *gtk_action_helper_pspecs[N_PROPS];
 
-G_DEFINE_TYPE (GtkActionHelper, gtk_action_helper, G_TYPE_OBJECT)
+static void gtk_action_helper_observer_iface_init (GActionObserverInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkActionHelper, gtk_action_helper, G_TYPE_OBJECT,
+  G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_OBSERVER, gtk_action_helper_observer_iface_init))
 
 static void
 gtk_action_helper_report_change (GtkActionHelper *helper,
@@ -419,103 +239,6 @@ gtk_action_helper_action_state_changed (GtkActionHelper *helper,
 }
 
 static void
-gtk_action_helper_actions_changed (GtkWidget   *widget,
-                                   const gchar *action_group_name,
-                                   gpointer     user_data)
-{
-  GtkActionHelper *helper = user_data;
-  gboolean was_enabled, was_active;
-  GtkActionHelperRole old_role;
-  GActionGroup *group;
-
-  /* We are interested in two cases:
-   *
-   *   - the group name that we were watching has changed
-   *
-   *   - NULL means potentially all groups have changed
-   *
-   * Note: helper->action_group_name could be NULL.
-   */
-  if (action_group_name && g_strcmp0 (action_group_name, helper->action_group_name) != 0)
-    return;
-
-  /* If either the widget is NULL or the group name is NULL then we
-   * should not be tracking any action group.  This function is called
-   * in those two cases in order to clean up any existing group, so we
-   * take group = NULL for those and continue normally.
-   */
-  if (helper->widget && helper->action_group_name)
-    group = gtk_widget_get_action_group_by_name (GTK_WIDGET (helper->widget), helper->action_group_name);
-  else
-    group = NULL;
-
-  /* We didn't have the group before and we still don't have it now. */
-  if (group == NULL && helper->group == NULL)
-    return;
-
-  /* We already had the group and we still have the same one. */
-  if (helper->group && helper->group->group == group)
-    return;
-
-  /* At this point we know that a change of some kind will happen.
-   *
-   * Start by recording the current state of our properties so we know
-   * what notify signals we will need to send.
-   */
-  was_enabled = helper->enabled;
-  was_active = helper->active;
-  old_role = helper->role;
-
-  /* Tear down the existing group, if we have one. */
-  if (helper->group)
-    {
-      gtk_action_helper_group_unwatch (helper->group, helper->action_name, helper);
-      helper->role = GTK_ACTION_HELPER_ROLE_NORMAL;
-      helper->can_activate = FALSE;
-      helper->enabled = FALSE;
-      helper->active = FALSE;
-      helper->group = NULL;
-    }
-
-  /* Setup the new group, if there is one. */
-  if (group)
-    {
-      const GVariantType *parameter_type;
-      gboolean enabled;
-      GVariant *state;
-
-      helper->group = gtk_action_helper_group_get (group);
-
-      gtk_action_helper_group_watch (helper->group, helper->action_name, helper);
-
-      if (g_action_group_query_action (group, helper->action_name, &enabled, &parameter_type, NULL, NULL, &state))
-        {
-          gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
-
-          if (state)
-            g_variant_unref (state);
-        }
-    }
-
-  /* Send the notifies for the properties that changed.
-   *
-   * When called during construction, widget is NULL.  We don't need to
-   * report in that case.
-   */
-  if (widget != NULL)
-    {
-      if (helper->enabled != was_enabled)
-        gtk_action_helper_report_change (helper, PROP_ENABLED);
-
-      if (helper->active != was_active)
-        gtk_action_helper_report_change (helper, PROP_ACTIVE);
-
-      if (helper->role != old_role)
-        gtk_action_helper_report_change (helper, PROP_ROLE);
-    }
-}
-
-static void
 gtk_action_helper_get_property (GObject *object, guint prop_id,
                                 GValue *value, GParamSpec *pspec)
 {
@@ -545,21 +268,15 @@ gtk_action_helper_finalize (GObject *object)
 {
   GtkActionHelper *helper = GTK_ACTION_HELPER (object);
 
-  g_signal_handlers_disconnect_by_func (helper->widget, gtk_action_helper_actions_changed, helper);
-
   if (helper->application)
     {
       g_signal_handlers_disconnect_by_data (helper->application, helper);
       g_object_unref (helper->application);
 
       if (helper->widget)
-        {
-          g_signal_handlers_disconnect_by_data (helper->widget, helper);
           g_object_unref (helper->widget);
-        }
     }
 
-  g_free (helper->action_group_name);
   g_free (helper->action_name);
 
   if (helper->target)
@@ -570,6 +287,43 @@ gtk_action_helper_finalize (GObject *object)
 }
 
 static void
+gtk_action_helper_observer_action_added (GActionObserver    *observer,
+                                         GActionObservable  *observable,
+                                         const gchar        *action_name,
+                                         const GVariantType *parameter_type,
+                                         gboolean            enabled,
+                                         GVariant           *state)
+{
+  gtk_action_helper_action_added (GTK_ACTION_HELPER (observer), enabled, parameter_type, state, TRUE);
+}
+
+static void
+gtk_action_helper_observer_action_enabled_changed (GActionObserver    *observer,
+                                                   GActionObservable  *observable,
+                                                   const gchar        *action_name,
+                                                   gboolean            enabled)
+{
+  gtk_action_helper_action_enabled_changed (GTK_ACTION_HELPER (observer), enabled);
+}
+
+static void
+gtk_action_helper_observer_action_state_changed (GActionObserver    *observer,
+                                                 GActionObservable  *observable,
+                                                 const gchar        *action_name,
+                                                 GVariant           *state)
+{
+  gtk_action_helper_action_state_changed (GTK_ACTION_HELPER (observer), state);
+}
+
+static void
+gtk_action_helper_observer_action_removed (GActionObserver    *observer,
+                                           GActionObservable  *observable,
+                                           const gchar        *action_name)
+{
+  gtk_action_helper_action_removed (GTK_ACTION_HELPER (observer));
+}
+
+static void
 gtk_action_helper_init (GtkActionHelper *helper)
 {
 }
@@ -589,6 +343,15 @@ gtk_action_helper_class_init (GtkActionHelperClass *class)
   g_object_class_install_properties (class, N_PROPS, gtk_action_helper_pspecs);
 }
 
+static void
+gtk_action_helper_observer_iface_init (GActionObserverInterface *iface)
+{
+  iface->action_added = gtk_action_helper_observer_action_added;
+  iface->action_enabled_changed = gtk_action_helper_observer_action_enabled_changed;
+  iface->action_state_changed = gtk_action_helper_observer_action_state_changed;
+  iface->action_removed = gtk_action_helper_observer_action_removed;
+}
+
 /*< private >
  * gtk_action_helper_new:
  * @widget: a #GtkWidget
@@ -619,7 +382,7 @@ gtk_action_helper_new (GtkActionable *widget)
 
   helper->widget = GTK_WIDGET (widget);
 
-  g_signal_connect (widget, "actions-changed", G_CALLBACK (gtk_action_helper_actions_changed), helper);
+  helper->action_context = _gtk_widget_get_action_muxer (GTK_WIDGET (widget));
 
   return helper;
 }
@@ -632,20 +395,13 @@ gtk_action_helper_active_window_changed (GObject    *object,
   GtkActionHelper *helper = user_data;
 
   if (helper->widget)
-    {
-      g_signal_handlers_disconnect_by_func (helper->widget, gtk_action_helper_actions_changed, helper);
       g_object_unref (helper->widget);
-    }
 
   helper->widget = GTK_WIDGET (gtk_application_get_active_window (helper->application));
+  helper->action_context = _gtk_widget_get_action_muxer (GTK_WIDGET (helper->widget));
 
   if (helper->widget)
-    {
-      g_signal_connect (helper->widget, "actions-changed", G_CALLBACK (gtk_action_helper_actions_changed), helper);
       g_object_ref (helper->widget);
-    }
-
-  gtk_action_helper_actions_changed (helper->widget, NULL, helper);
 }
 
 GtkActionHelper *
@@ -668,35 +424,58 @@ void
 gtk_action_helper_set_action_name (GtkActionHelper *helper,
                                    const gchar     *action_name)
 {
-  const gchar *name_dot = NULL;
+  gboolean was_enabled, was_active;
+  GtkActionHelperRole old_role;
+  const GVariantType *parameter_type;
+  gboolean enabled;
+  GVariant *state;
 
   if (g_strcmp0 (action_name, helper->action_name) == 0)
     return;
 
-  if (action_name)
-    name_dot = strchr (action_name, '.');
-
-  /* If the name was given it must have a dot in it */
-  g_return_if_fail (action_name == NULL || name_dot != NULL);
-
-  if (helper->full_action_name)
+  if (helper->action_name)
     {
-      g_free (helper->full_action_name);
-      g_free (helper->action_group_name);
+      g_action_observable_unregister_observer (G_ACTION_OBSERVABLE (helper->action_context),
+                                               helper->action_name,
+                                               G_ACTION_OBSERVER (helper));
       g_free (helper->action_name);
-      helper->full_action_name = NULL;
-      helper->action_group_name = NULL;
-      helper->action_name = NULL;
     }
 
-  if (action_name)
+  helper->action_name = g_strdup (action_name);
+
+  g_action_observable_register_observer (G_ACTION_OBSERVABLE (helper->action_context),
+                                         helper->action_name,
+                                         G_ACTION_OBSERVER (helper));
+
+  /* Start by recording the current state of our properties so we know
+   * what notify signals we will need to send.
+   */
+  was_enabled = helper->enabled;
+  was_active = helper->active;
+  old_role = helper->role;
+
+  if (g_action_group_query_action (G_ACTION_GROUP (helper->action_context), helper->action_name,
+                                   &enabled, &parameter_type, NULL, NULL, &state))
     {
-      helper->full_action_name = g_strdup (action_name);
-      helper->action_group_name = g_strndup (action_name, name_dot - action_name);
-      helper->action_name = g_strdup (name_dot + 1);
+      gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
+
+      if (state)
+        g_variant_unref (state);
     }
 
-  gtk_action_helper_actions_changed (NULL, NULL, helper);
+  /* Send the notifies for the properties that changed.
+   *
+   * When called during construction, widget is NULL.  We don't need to
+   * report in that case.
+   */
+  if (helper->enabled != was_enabled)
+    gtk_action_helper_report_change (helper, PROP_ENABLED);
+
+  if (helper->active != was_active)
+    gtk_action_helper_report_change (helper, PROP_ACTIVE);
+
+  if (helper->role != old_role)
+    gtk_action_helper_report_change (helper, PROP_ROLE);
 
   if (!helper->application)
     g_object_notify (G_OBJECT (helper->widget), "action-name");
@@ -738,14 +517,15 @@ gtk_action_helper_set_action_target_value (GtkActionHelper *helper,
   helper->enabled = FALSE;
   helper->active = FALSE;
 
-  if (helper->group)
+  if (helper->action_context)
     {
       const GVariantType *parameter_type;
       gboolean enabled;
       GVariant *state;
 
-      if (g_action_group_query_action (helper->group->group, helper->action_name,
-                                       &enabled, &parameter_type, NULL, NULL, &state))
+      if (g_action_group_query_action (G_ACTION_GROUP (helper->action_context),
+                                       helper->action_name, &enabled, &parameter_type,
+                                       NULL, NULL, &state))
         {
           gtk_action_helper_action_added (helper, enabled, parameter_type, state, FALSE);
 
@@ -770,7 +550,7 @@ gtk_action_helper_get_action_name (GtkActionHelper *helper)
   if (helper == NULL)
     return NULL;
 
-  return helper->full_action_name;
+  return helper->action_name;
 }
 
 GVariant *
@@ -815,5 +595,6 @@ gtk_action_helper_activate (GtkActionHelper *helper)
   if (!helper->can_activate || helper->reporting)
     return;
 
-  g_action_group_activate_action (helper->group->group, helper->action_name, helper->target);
+  g_action_group_activate_action (G_ACTION_GROUP (helper->action_context),
+                                  helper->action_name, helper->target);
 }
diff --git a/gtk/gtkapplicationwindow.c b/gtk/gtkapplicationwindow.c
index 798d4ab..01ceac6 100644
--- a/gtk/gtkapplicationwindow.c
+++ b/gtk/gtkapplicationwindow.c
@@ -511,7 +511,10 @@ gtk_application_window_list_actions (GActionGroup *group)
 {
   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
 
-  return g_action_group_list_actions (G_ACTION_GROUP (window->priv->actions));
+  if (window->priv->actions)
+    return g_action_group_list_actions (G_ACTION_GROUP (window->priv->actions));
+  else
+    return g_new0 (gchar *, 1);
 }
 
 static gboolean
@@ -868,17 +871,6 @@ gtk_application_window_real_forall_internal (GtkContainer *container,
     ->forall (container, include_internal, callback, user_data);
 }
 
-static GActionGroup *
-gtk_application_window_get_action_group_by_name (GtkWidget   *widget,
-                                                 const gchar *name)
-{
-  if (g_str_equal (name, "win"))
-    return G_ACTION_GROUP (widget);
-
-  return GTK_WIDGET_CLASS (gtk_application_window_parent_class)
-    ->get_action_group_by_name (widget, name);
-}
-
 static void
 gtk_application_window_get_property (GObject    *object,
                                      guint       prop_id,
@@ -950,6 +942,8 @@ gtk_application_window_init (GtkApplicationWindow *window)
   window->priv->accels = gtk_accel_group_new ();
   gtk_window_add_accel_group (GTK_WINDOW (window), window->priv->accels);
 
+  gtk_widget_insert_action_group (GTK_WIDGET (window), "win", G_ACTION_GROUP (window));
+
   /* window->priv->actions is the one and only ref on the group, so when
    * we dispose, the action group will die, disconnecting all signals.
    */
@@ -979,7 +973,6 @@ gtk_application_window_class_init (GtkApplicationWindowClass *class)
   widget_class->realize = gtk_application_window_real_realize;
   widget_class->unrealize = gtk_application_window_real_unrealize;
   widget_class->map = gtk_application_window_real_map;
-  widget_class->get_action_group_by_name = gtk_application_window_get_action_group_by_name;
   object_class->get_property = gtk_application_window_get_property;
   object_class->set_property = gtk_application_window_set_property;
   object_class->dispose = gtk_application_window_dispose;
diff --git a/gtk/gtkmenu.c b/gtk/gtkmenu.c
index 9e21e36..f39cbc2 100644
--- a/gtk/gtkmenu.c
+++ b/gtk/gtkmenu.c
@@ -302,9 +302,6 @@ static void gtk_menu_get_preferred_height_for_width (GtkWidget           *widget
                                                      gint                *minimum_size,
                                                      gint                *natural_size);
 
-static GActionGroup * gtk_menu_get_action_group_by_name (GtkWidget   *widget,
-                                                         const gchar *name);
-
 static const gchar attach_data_key[] = "gtk-menu-attach-data";
 
 static guint menu_signals[LAST_SIGNAL] = { 0 };
@@ -515,7 +512,6 @@ gtk_menu_class_init (GtkMenuClass *class)
   widget_class->get_preferred_width = gtk_menu_get_preferred_width;
   widget_class->get_preferred_height = gtk_menu_get_preferred_height;
   widget_class->get_preferred_height_for_width = gtk_menu_get_preferred_height_for_width;
-  widget_class->get_action_group_by_name = gtk_menu_get_action_group_by_name;
 
   container_class->remove = gtk_menu_remove;
   container_class->get_child_property = gtk_menu_get_child_property;
@@ -1168,14 +1164,6 @@ attach_widget_screen_changed (GtkWidget *attach_widget,
     menu_change_screen (menu, gtk_widget_get_screen (attach_widget));
 }
 
-static void
-attach_widget_actions_changed (GtkWidget   *attach_widget,
-                               const gchar *action_group_name,
-                               GtkMenu     *menu)
-{
-  gtk_widget_propagate_actions_changed (GTK_WIDGET (menu), action_group_name);
-}
-
 /**
  * gtk_menu_attach_to_widget:
  * @menu: a #GtkMenu
@@ -1194,6 +1182,8 @@ gtk_menu_attach_to_widget (GtkMenu           *menu,
 {
   GtkMenuAttachData *data;
   GList *list;
+  GActionMuxer *actions;
+  GActionMuxer *attach_actions;
 
   g_return_if_fail (GTK_IS_MENU (menu));
   g_return_if_fail (GTK_IS_WIDGET (attach_widget));
@@ -1216,11 +1206,9 @@ gtk_menu_attach_to_widget (GtkMenu           *menu,
                     G_CALLBACK (attach_widget_screen_changed), menu);
   attach_widget_screen_changed (attach_widget, NULL, menu);
 
-  if (menu->priv->action_requested)
-    {
-      g_signal_connect (attach_widget, "actions-changed", G_CALLBACK (attach_widget_actions_changed), menu);
-      attach_widget_actions_changed (NULL, NULL, menu);
-    }
+  actions = _gtk_widget_get_action_muxer (GTK_WIDGET (menu));
+  attach_actions = _gtk_widget_get_action_muxer (attach_widget);
+  g_action_muxer_set_parent (actions, attach_actions);
 
   data->detacher = detacher;
   g_object_set_data (G_OBJECT (menu), I_(attach_data_key), data);
@@ -1277,6 +1265,7 @@ gtk_menu_detach (GtkMenu *menu)
 {
   GtkMenuAttachData *data;
   GList *list;
+  GActionMuxer *actions;
 
   g_return_if_fail (GTK_IS_MENU (menu));
 
@@ -1289,15 +1278,15 @@ gtk_menu_detach (GtkMenu *menu)
     }
   g_object_set_data (G_OBJECT (menu), I_(attach_data_key), NULL);
 
+  actions = _gtk_widget_get_action_muxer (GTK_WIDGET (menu));
+  g_action_muxer_set_parent (actions, NULL);
+
   /* Detach the toplevel window. */
   gtk_window_set_attached_to (GTK_WINDOW (menu->priv->toplevel), NULL);
 
   g_signal_handlers_disconnect_by_func (data->attach_widget,
                                         (gpointer) attach_widget_screen_changed,
                                         menu);
-  g_signal_handlers_disconnect_by_func (data->attach_widget,
-                                        (gpointer) attach_widget_actions_changed,
-                                        menu);
 
   if (data->detacher)
     data->detacher (data->attach_widget, menu);
@@ -1338,29 +1327,6 @@ gtk_menu_remove (GtkContainer *container,
   menu_queue_resize (menu);
 }
 
-static GActionGroup *
-gtk_menu_get_action_group_by_name (GtkWidget   *widget,
-                                   const gchar *name)
-{
-  GtkMenu *menu = GTK_MENU (widget);
-  GtkWidget *attach_widget;
-
-  attach_widget = gtk_menu_get_attach_widget (GTK_MENU (widget));
-
-  if (!menu->priv->action_requested)
-    {
-      if (attach_widget != NULL)
-        g_signal_connect (attach_widget, "actions-changed", G_CALLBACK (attach_widget_actions_changed), menu);
-
-      menu->priv->action_requested = TRUE;
-    }
-
-  if (attach_widget == NULL)
-    return NULL;
-
-  return gtk_widget_get_action_group_by_name (attach_widget, name);
-}
-
 /**
  * gtk_menu_new:
  *
diff --git a/gtk/gtkmenuprivate.h b/gtk/gtkmenuprivate.h
index 6dd76e6..f458358 100644
--- a/gtk/gtkmenuprivate.h
+++ b/gtk/gtkmenuprivate.h
@@ -101,7 +101,6 @@ struct _GtkMenuPrivate
   guint no_toggle_size        : 1;
   guint drag_already_pressed  : 1;
   guint drag_scroll_started   : 1;
-  guint action_requested      : 1;
 
   /* info used for the table */
   guint *heights;
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 916d1ea..2355c44 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -360,9 +360,6 @@ struct _GtkWidgetPrivate
   guint sizegroup_bumping     : 1;
   guint have_size_groups      : 1;
 
-  /* If any action groups have been requested */
-  guint action_requested      : 1;
-
   /* The widget's name. If the widget does not have a name
    * (the name is NULL), then its name (as returned by
    * "gtk_widget_get_name") is its class's name.
@@ -393,6 +390,9 @@ struct _GtkWidgetPrivate
   /* The widget's requested sizes */
   SizeRequestCache requests;
 
+  /* actions attached to this or any parent widget */
+  GActionMuxer *muxer;
+
   /* The widget's window or its parent window if it does
    * not have a window. (Which will be indicated by the
    * GTK_NO_WINDOW flag being set).
@@ -484,7 +484,6 @@ enum {
   DRAG_FAILED,
   STYLE_UPDATED,
   TOUCH_EVENT,
-  ACTIONS_CHANGED,
   LAST_SIGNAL
 };
 
@@ -633,6 +632,7 @@ static void             gtk_widget_real_get_width_for_height    (GtkWidget
                                                                  gint             *natural_width);
 static void             gtk_widget_real_state_flags_changed     (GtkWidget        *widget,
                                                                  GtkStateFlags     old_state);
+static void             gtk_widget_update_parent_muxer          (GtkWidget        *widget);
 static const GtkWidgetAuxInfo* _gtk_widget_get_aux_info_or_defaults (GtkWidget *widget);
 static GtkWidgetAuxInfo* gtk_widget_get_aux_info                (GtkWidget        *widget,
                                                                  gboolean          create);
@@ -861,20 +861,6 @@ gtk_widget_draw_marshallerv (GClosure     *closure,
   cairo_restore (cr);
 }
 
-static GActionGroup *
-gtk_widget_real_get_action_group_by_name (GtkWidget   *widget,
-                                          const gchar *name)
-{
-  GtkWidget *parent;
-
-  parent = gtk_widget_get_parent (widget);
-
-  if (parent == NULL)
-    return NULL;
-
-  return gtk_widget_get_action_group_by_name (parent, name);
-}
-
 static void
 gtk_widget_class_init (GtkWidgetClass *klass)
 {
@@ -995,8 +981,6 @@ gtk_widget_class_init (GtkWidgetClass *klass)
   klass->adjust_size_request = gtk_widget_real_adjust_size_request;
   klass->adjust_size_allocation = gtk_widget_real_adjust_size_allocation;
 
-  klass->get_action_group_by_name = gtk_widget_real_get_action_group_by_name;
-
   g_object_class_install_property (gobject_class,
 				   PROP_NAME,
 				   g_param_spec_string ("name",
@@ -3123,27 +3107,6 @@ gtk_widget_class_init (GtkWidgetClass *klass)
 		  _gtk_marshal_BOOLEAN__UINT,
                   G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
 
-  /**
-   * GtkWidget::actions-changed:
-   * @widget: the object which received the signal
-   * @action_group_name: (allow none): the name of the affected action
-   *     group, or %NULL in case all groups may have been replaced
-   *
-   * Signals that the action group with the name of @action_group_name
-   * has been potentially added, removed or replaced from the widget
-   * (ie: the value returned by gtk_widget_get_action_group_by_name()
-   * may have changed).  If @action_group_name is %NULL then potentially
-   * all groups have been replaced.
-   *
-   * Since: 3.6
-   **/
-  widget_signals[ACTIONS_CHANGED] =
-    g_signal_new (I_("actions-changed"),
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-                  g_cclosure_marshal_VOID__STRING,
-                  G_TYPE_NONE, 1, G_TYPE_STRING);
-
   binding_set = gtk_binding_set_by_class (klass);
   gtk_binding_entry_add_signal (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK,
                                 "popup-menu", 0);
@@ -3932,6 +3895,8 @@ gtk_widget_unparent (GtkWidget *widget)
   if (priv->context)
     gtk_style_context_set_parent (priv->context, NULL);
 
+  gtk_widget_update_parent_muxer (widget);
+
   g_signal_emit (widget, widget_signals[PARENT_SET], 0, old_parent);
   if (toplevel)
     {
@@ -6731,6 +6696,21 @@ gtk_widget_real_state_flags_changed (GtkWidget     *widget,
 }
 
 static void
+gtk_widget_update_parent_muxer (GtkWidget *widget)
+{
+  GActionMuxer *muxer;
+  GtkWidget *parent;
+  GActionMuxer *parent_muxer;
+
+  muxer = _gtk_widget_get_action_muxer (widget);
+
+  parent = gtk_widget_get_parent (widget);
+  parent_muxer = parent ? _gtk_widget_get_action_muxer (parent) : NULL;
+
+  g_action_muxer_set_parent (muxer, parent_muxer);
+}
+
+static void
 gtk_widget_real_style_updated (GtkWidget *widget)
 {
   GtkWidgetPrivate *priv = widget->priv;
@@ -8000,6 +7980,8 @@ gtk_widget_set_parent (GtkWidget *widget,
     gtk_style_context_set_parent (priv->context,
                                   gtk_widget_get_style_context (parent));
 
+  gtk_widget_update_parent_muxer (widget);
+
   g_signal_emit (widget, widget_signals[PARENT_SET], 0, NULL);
   if (priv->parent->priv->anchored)
     _gtk_widget_propagate_hierarchy_changed (widget, NULL);
@@ -8320,7 +8302,8 @@ gtk_widget_propagate_hierarchy_changed_recurse (GtkWidget *widget,
 
       g_signal_emit (widget, widget_signals[HIERARCHY_CHANGED], 0, info->previous_toplevel);
       do_screen_change (widget, info->previous_screen, info->new_screen);
-      g_signal_emit (widget, widget_signals[ACTIONS_CHANGED], 0, NULL);
+
+
 
       if (GTK_IS_CONTAINER (widget))
 	gtk_container_forall (GTK_CONTAINER (widget),
@@ -8463,47 +8446,6 @@ _gtk_widget_propagate_screen_changed (GtkWidget    *widget,
 }
 
 static void
-propagate_actions_changed (GtkWidget *widget,
-                           gpointer   user_data)
-{
-  const gchar *action_group_name = user_data;
-
-  if (!widget->priv->action_requested)
-    return;
-
-  if (GTK_IS_CONTAINER (widget))
-    gtk_container_forall (GTK_CONTAINER (widget), propagate_actions_changed, user_data);
-
-  g_signal_emit (widget, widget_signals[ACTIONS_CHANGED], 0, action_group_name);
-}
-
-/**
- * gtk_widget_propagate_actions_changed:
- * @widget: a #GtkWidget
- * @action_group_name: the name of an action group
- *
- * Emits a #GtkWidget::actions-changed signal on @widget and all of its
- * children, recursively.
- *
- * This should be done to signal that the action group with the name of
- * @action_group_name has been potentially added, removed or replaced
- * from the widget (ie: the value returned by
- * gtk_widget_get_action_group_by_name() may have changed).
- *
- * Only widget implementations (and specifically, those that provide
- * their own implementation of gtk_widget_get_action_group_by_name())
- * should ever call this function.
- *
- * Since: 3.6
- **/
-void
-gtk_widget_propagate_actions_changed (GtkWidget   *widget,
-                                      const gchar *action_group_name)
-{
-  propagate_actions_changed (widget, (gpointer) action_group_name);
-}
-
-static void
 reset_style_recurse (GtkWidget *widget, gpointer data)
 {
   _gtk_widget_invalidate_style_context (widget, GTK_CSS_CHANGE_ANY);
@@ -10342,6 +10284,8 @@ gtk_widget_dispose (GObject *object)
       priv->in_destruction = FALSE;
     }
 
+  g_clear_object (&priv->muxer);
+
   G_OBJECT_CLASS (gtk_widget_parent_class)->dispose (object);
 }
 
@@ -14147,46 +14091,46 @@ _gtk_widget_set_style (GtkWidget *widget,
   widget->priv->style = style;
 }
 
-/**
- * gtk_widget_get_action_group_by_name:
- * @widget: a #GtkWidget
- * @name: the name of an action group to lookup
- *
- * Gets the action group with @name as is applicable to the context of
- * @widget.
- *
- * The default implementation is to ask the same question of the parent
- * widget, returning %NULL if there is no parent.  In most cases this
- * means that you are really querying the toplevel associated with
- * @widget.
- *
- * #GtkWindow will return the associated #GtkApplication for @name equal
- * to "app".  #GtkApplicationWindow will return itself for @name equal
- * to "win".  #GtkMenu will query its attach-widget.
- *
- * Other widgets (including containers between the queried widget and
- * its toplevel) may return other action groups for other strings (and
- * may even override the strings "win" and "app").  #GtkMenuButton
- * provides a method for associating an arbitrary #GActionGroup; see
- * gtk_menu_button_add_action_group().
- *
- * gtk_menubar_new_from_model() and gtk_menu_new_from_model() will call
- * this function on themselves in order to find the actions specified in
- * the #GMenuModel.
- *
- * Returns: (transfer none): the action group, or %NULL if none exists
- *
- * Since: 3.6
- **/
+GActionMuxer *
+_gtk_widget_get_action_muxer (GtkWidget *widget)
+{
+  if (G_UNLIKELY (widget->priv->muxer == NULL))
+    {
+      GtkWidget *parent;
+      GActionMuxer *parent_actions;
+
+      parent = gtk_widget_get_parent (widget);
+      parent_actions = parent ? _gtk_widget_get_action_muxer (parent) : NULL;
+
+      widget->priv->muxer = g_action_muxer_new (parent_actions);
+    }
+
+  return widget->priv->muxer;
+}
+
+void
+gtk_widget_insert_action_group (GtkWidget    *widget,
+                                const gchar  *name,
+                                GActionGroup *group)
+{
+  GActionMuxer *muxer;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (name != NULL);
+
+  muxer = _gtk_widget_get_action_muxer (widget);
+
+  g_action_muxer_insert (muxer, name, group);
+}
+
 GActionGroup *
-gtk_widget_get_action_group_by_name (GtkWidget   *widget,
-                                     const gchar *name)
+gtk_widget_get_action_context (GtkWidget *widget)
 {
+  GActionMuxer *muxer;
+
   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
-  g_return_val_if_fail (name != NULL, NULL);
 
-  widget->priv->action_requested = TRUE;
+  muxer = _gtk_widget_get_action_muxer (widget);
 
-  return GTK_WIDGET_GET_CLASS (widget)
-    ->get_action_group_by_name (widget, name);
+  return G_ACTION_GROUP (muxer);
 }
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index b717347..f185098 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -420,19 +420,13 @@ struct _GtkWidgetClass
 
   GtkWidgetClassPrivate *priv;
 
-  /*< public >*/
-
-  GActionGroup * (* get_action_group_by_name) (GtkWidget   *widget,
-                                               const gchar *name);
-
-  /*< private >*/
-
   /* Padding for future expansion */
   void (*_gtk_reserved1) (void);
   void (*_gtk_reserved2) (void);
   void (*_gtk_reserved3) (void);
   void (*_gtk_reserved4) (void);
   void (*_gtk_reserved5) (void);
+  void (*_gtk_reserved6) (void);
 };
 
 struct _GtkWidgetAuxInfo
@@ -893,11 +887,11 @@ GdkModifierType   gtk_widget_get_modifier_mask (GtkWidget         *widget,
                                                 GdkModifierIntent  intent);
 
 GDK_AVAILABLE_IN_3_6
-GActionGroup *          gtk_widget_get_action_group_by_name             (GtkWidget   *widget,
-                                                                         const gchar *name);
+void                    gtk_widget_insert_action_group                  (GtkWidget    *widget,
+                                                                         const gchar  *name,
+                                                                         GActionGroup *group);
 GDK_AVAILABLE_IN_3_6
-void                    gtk_widget_propagate_actions_changed            (GtkWidget   *widget,
-                                                                         const gchar *action_group_name);
+GActionGroup *          gtk_widget_get_action_context                   (GtkWidget *widget);
 
 G_END_DECLS
 
diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h
index c59ed39..5bccdc7 100644
--- a/gtk/gtkwidgetprivate.h
+++ b/gtk/gtkwidgetprivate.h
@@ -27,6 +27,7 @@
 
 #include "gtkcsstypesprivate.h"
 #include "gtkwidget.h"
+#include "gactionmuxer.h"
 
 G_BEGIN_DECLS
 
@@ -178,6 +179,8 @@ void              _gtk_widget_invalidate_style_context     (GtkWidget    *widget
                                                             GtkCssChange  change);
 void              _gtk_widget_style_context_invalidated    (GtkWidget    *widget);
 
+GActionMuxer *    _gtk_widget_get_action_muxer             (GtkWidget    *widget);
+
 G_END_DECLS
 
 #endif /* __GTK_WIDGET_PRIVATE_H__ */
diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c
index c179cf2..baca42a 100644
--- a/gtk/gtkwindow.c
+++ b/gtk/gtkwindow.c
@@ -549,21 +549,6 @@ startup_id_is_fake (const gchar* startup_id)
   return strncmp (startup_id, "_TIME", 5) == 0;
 }
 
-static GActionGroup *
-gtk_window_get_action_group_by_name (GtkWidget   *widget,
-                                     const gchar *name)
-{
-  if (g_str_equal (name, "app"))
-    {
-      GtkWindow *window = GTK_WINDOW (widget);
-
-      if (window->priv->application)
-        return G_ACTION_GROUP (window->priv->application);
-    }
-
-  return NULL;
-}
-
 static void
 gtk_window_class_init (GtkWindowClass *klass)
 {
@@ -612,7 +597,6 @@ gtk_window_class_init (GtkWindowClass *klass)
   widget_class->direction_changed = gtk_window_direction_changed;
   widget_class->state_changed = gtk_window_state_changed;
   widget_class->style_updated = gtk_window_style_updated;
-  widget_class->get_action_group_by_name = gtk_window_get_action_group_by_name;
 
   container_class->check_resize = gtk_window_check_resize;
 
@@ -2836,6 +2820,8 @@ gtk_window_set_application (GtkWindow      *window,
           gtk_application_add_window (priv->application, window);
         }
 
+      gtk_widget_insert_action_group (GTK_WIDGET (window), "app", G_ACTION_GROUP (application));
+
       g_object_notify (G_OBJECT (window), "application");
     }
 }



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