[gnome-shell/wip/menus-rebase2] Another update for GLib API changes



commit ab4a7c52371c8e8d87e76f0c68a56569d2ee6e6e
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Dec 15 00:29:31 2011 -0500

    Another update for GLib API changes
    
    GDBusActionGroup api has changed again, adapt to that.
    Also, use a GActionMuxer to add the 'app.' prefix to actions,
    instead of manually stripping it out of the action names.
    In the future, the muxer will also contain per-window actions
    with a 'win.' prefix.

 js/ui/popupMenu.js      |    1 -
 src/Makefile.am         |    9 +-
 src/gactionmuxer.c      |  495 +++++++++++++++++++++++++++++++++++++++++++++++
 src/gactionmuxer.h      |   53 +++++
 src/gactionobservable.c |   80 ++++++++
 src/gactionobservable.h |   64 ++++++
 src/gactionobserver.c   |  161 +++++++++++++++
 src/gactionobserver.h   |   90 +++++++++
 src/shell-app.c         |   84 +++------
 9 files changed, 974 insertions(+), 63 deletions(-)
---
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index 4e9d157..c6dc3df 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -1752,7 +1752,6 @@ const RemoteMenu = new Lang.Class({
         }
 
         let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack();
-        action_id = action_id.replace('app.', '');
         if (!this.actionGroup.has_action(action_id)) {
             // the action may not be there yet, wait for action-added
             return [null, false, 'action-added'];
diff --git a/src/Makefile.am b/src/Makefile.am
index 375ff91..3000a09 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -157,7 +157,14 @@ libgnome_shell_la_SOURCES =		\
 	shell-util.c			\
 	shell-window-tracker.c		\
 	shell-wm.c			\
-	shell-xfixes-cursor.c
+	shell-xfixes-cursor.c		\
+	gactionmuxer.h			\
+	gactionmuxer.c			\
+	gactionobservable.h		\
+	gactionobservable.c		\
+	gactionobserver.h		\
+	gactionobserver.c
+
 
 libgnome_shell_la_gir_sources = \
 	$(filter-out %-private.h $(shell_recorder_non_gir_sources), $(shell_public_headers_h) $(libgnome_shell_la_SOURCES))
diff --git a/src/gactionmuxer.c b/src/gactionmuxer.c
new file mode 100644
index 0000000..bdc34b7
--- /dev/null
+++ b/src/gactionmuxer.c
@@ -0,0 +1,495 @@
+/*
+ * 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 <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 *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,
+                              gchar        **fullname)
+{
+  Action *action;
+
+  *fullname = g_strconcat (prefix, ".", action_name, NULL);
+  action = g_hash_table_lookup (muxer->actions, *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;
+  gchar *fullname;
+  Action *action;
+  GSList *node;
+
+  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_enabled_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, enabled);
+  g_action_group_action_enabled_changed (G_ACTION_GROUP (group->muxer), fullname, enabled);
+  g_free (fullname);
+}
+
+static void
+g_action_muxer_action_state_changed (GActionGroup *action_group,
+                                     const gchar  *action_name,
+                                     GVariant     *state,
+                                     gpointer      user_data)
+{
+  Group *group = user_data;
+  gchar *fullname;
+  Action *action;
+  GSList *node;
+
+  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_state_changed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname, state);
+  g_action_group_action_state_changed (G_ACTION_GROUP (group->muxer), fullname, state);
+  g_free (fullname);
+}
+
+static void
+g_action_muxer_action_added (GActionGroup *action_group,
+                             const gchar  *action_name,
+                             gpointer      user_data)
+{
+  const GVariantType *parameter_type;
+  Group *group = user_data;
+  gboolean enabled;
+  GVariant *state;
+
+  if (g_action_group_query_action (group->group, action_name, &enabled, &parameter_type, NULL, NULL, &state))
+    {
+      gchar *fullname;
+      Action *action;
+      GSList *node;
+
+      action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
+
+      for (node = action ? action->watchers : NULL; node; node = node->next)
+        g_action_observer_action_added (node->data,
+                                        G_ACTION_OBSERVABLE (group->muxer),
+                                        fullname, parameter_type, enabled, state);
+
+      g_action_group_action_added (G_ACTION_GROUP (group->muxer), fullname);
+
+      if (state)
+        g_variant_unref (state);
+
+      g_free (fullname);
+    }
+}
+
+static void
+g_action_muxer_action_removed (GActionGroup *action_group,
+                               const gchar  *action_name,
+                               gpointer      user_data)
+{
+  Group *group = user_data;
+  gchar *fullname;
+  Action *action;
+  GSList *node;
+
+  action = g_action_muxer_lookup_action (group->muxer, group->prefix, action_name, &fullname);
+  for (node = action ? action->watchers : NULL; node; node = node->next)
+    g_action_observer_action_removed (node->data, G_ACTION_OBSERVABLE (group->muxer), fullname);
+  g_action_group_action_removed (G_ACTION_GROUP (group->muxer), fullname);
+  g_free (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 ('.').
+ */
+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.
+ */
+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.
+ */
+GActionMuxer *
+g_action_muxer_new (void)
+{
+  return g_object_new (G_TYPE_ACTION_MUXER, NULL);
+}
diff --git a/src/gactionmuxer.h b/src/gactionmuxer.h
new file mode 100644
index 0000000..adafd03
--- /dev/null
+++ b/src/gactionmuxer.h
@@ -0,0 +1,53 @@
+/*
+ * 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>
+ */
+
+#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                              (void);
+
+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_END_DECLS
+
+#endif /* __G_ACTION_MUXER_H__ */
diff --git a/src/gactionobservable.c b/src/gactionobservable.c
new file mode 100644
index 0000000..5d1c652
--- /dev/null
+++ b/src/gactionobservable.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright  2011 Canonical Limited
+ *
+ * This program 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.
+ *
+ * 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/src/gactionobservable.h b/src/gactionobservable.h
new file mode 100644
index 0000000..1d5d1aa
--- /dev/null
+++ b/src/gactionobservable.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright  2011 Canonical Limited
+ *
+ * This program 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.
+ *
+ * Authors: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __G_ACTION_OBSERVABLE_H__
+#define __G_ACTION_OBSERVABLE_H__
+
+#include "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/src/gactionobserver.c b/src/gactionobserver.c
new file mode 100644
index 0000000..0586f07
--- /dev/null
+++ b/src/gactionobserver.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright  2011 Canonical Limited
+ *
+ * This program 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.
+ *
+ * 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/src/gactionobserver.h b/src/gactionobserver.h
new file mode 100644
index 0000000..eb15c3a
--- /dev/null
+++ b/src/gactionobserver.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright  2011 Canonical Limited
+ *
+ * This program 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.
+ *
+ * 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/src/shell-app.c b/src/shell-app.c
index 42d1165..7d64e2a 100644
--- a/src/shell-app.c
+++ b/src/shell-app.c
@@ -15,6 +15,7 @@
 #include "shell-app-system-private.h"
 #include "shell-window-tracker-private.h"
 #include "st.h"
+#include "gactionmuxer.h"
 
 typedef enum {
   MATCH_NONE,
@@ -42,8 +43,9 @@ typedef struct {
   gint              name_watcher_id;
   gchar            *dbus_name;
   GDBusProxy       *app_proxy;
-  GDBusActionGroup *remote_actions;
+  GActionGroup     *remote_actions;
   GMenuModel       *remote_menu;
+  GActionMuxer     *muxer;
   GCancellable     *dbus_cancellable;
 } ShellAppRunningState;
 
@@ -129,7 +131,7 @@ shell_app_get_property (GObject    *gobject,
       break;
     case PROP_ACTION_GROUP:
       if (app->running_state)
-        g_value_set_object (value, app->running_state->remote_actions);
+        g_value_set_object (value, app->running_state->muxer);
       break;
     case PROP_MENU:
       if (app->running_state)
@@ -1023,29 +1025,28 @@ _shell_app_remove_window (ShellApp   *app,
 }
 
 static void
-on_action_group_acquired (GObject      *object,
-                          GAsyncResult *result,
-                          gpointer      user_data)
+on_dbus_proxy_gotten (GObject      *initable,
+                      GAsyncResult *result,
+                      gpointer      user_data)
 {
   ShellApp *self = SHELL_APP (user_data);
   ShellAppRunningState *state = self->running_state;
   GError *error = NULL;
   GVariant *menu_property;
 
-  state->remote_actions = g_dbus_action_group_new_finish (result,
-                                                          &error);
+  state->app_proxy = g_dbus_proxy_new_finish (result,
+                                              &error);
 
   if (error)
     {
       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
           !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
         {
-          g_warning ("Unexpected error while reading application actions: %s", error->message);
+          g_warning ("Unexpected error while creating application proxy: %s", error->message);
         }
 
       g_clear_error (&error);
       g_clear_object (&state->dbus_cancellable);
-      g_clear_object (&state->app_proxy);
 
       if (state->name_watcher_id)
         {
@@ -1060,9 +1061,17 @@ on_action_group_acquired (GObject      *object,
       return;
     }
 
-  g_object_notify (G_OBJECT (self), "action-group");
+  /* on to the second step, the primary action group */
+
+  state->remote_actions = (GActionGroup*)g_dbus_action_group_get (
+                           g_dbus_proxy_get_connection (state->app_proxy),
+                           g_dbus_proxy_get_name (state->app_proxy),
+                           g_dbus_proxy_get_object_path (state->app_proxy));
+  state->muxer = g_action_muxer_new ();
+  g_action_muxer_insert (state->muxer, "app", state->remote_actions);
+  g_strfreev (g_action_group_list_actions (state->remote_actions));
 
-  /* third step: the application menu */
+  g_object_notify (G_OBJECT (self), "action-group");
 
   menu_property = g_dbus_proxy_get_cached_property (state->app_proxy, "AppMenu");
 
@@ -1078,55 +1087,6 @@ on_action_group_acquired (GObject      *object,
 
       g_object_notify (G_OBJECT (self), "menu");
     }
-
-  g_object_unref (self);
-}
-
-static void
-on_dbus_proxy_gotten (GObject      *initable,
-                      GAsyncResult *result,
-                      gpointer      user_data)
-{
-  ShellApp *self = SHELL_APP (user_data);
-  ShellAppRunningState *state = self->running_state;
-  GError *error = NULL;
-
-  state->app_proxy = g_dbus_proxy_new_finish (result,
-                                              &error);
-
-  if (error)
-    {
-      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
-          !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
-        {
-          g_warning ("Unexpected error while creating application proxy: %s", error->message);
-        }
-
-      g_clear_error (&error);
-      g_clear_object (&state->dbus_cancellable);
-
-      if (state->name_watcher_id)
-        {
-          g_bus_unwatch_name (state->name_watcher_id);
-          state->name_watcher_id = 0;
-        }
-
-      g_free (state->dbus_name);
-      state->dbus_name = NULL;
-
-      g_object_unref (self);
-      return;
-    }
-
-  /* on to the second step, the primary action group */
-
-  g_dbus_action_group_new (g_dbus_proxy_get_connection (state->app_proxy),
-                           g_dbus_proxy_get_name (state->app_proxy),
-                           g_dbus_proxy_get_object_path (state->app_proxy),
-                           G_DBUS_ACTION_GROUP_FLAGS_NONE,
-                           state->dbus_cancellable,
-                           on_action_group_acquired,
-                           self);
 }
 
 static void
@@ -1183,6 +1143,7 @@ on_dbus_name_disappeared (GDBusConnection *bus,
   g_clear_object (&state->app_proxy);
   g_clear_object (&state->remote_actions);
   g_clear_object (&state->remote_menu);
+  g_clear_object (&state->muxer);
 
   g_free (state->dbus_name);
   state->dbus_name = NULL;
@@ -1428,6 +1389,7 @@ unref_running_state (ShellAppRunningState *state)
   g_clear_object (&state->app_proxy);
   g_clear_object (&state->remote_actions);
   g_clear_object (&state->remote_menu);
+  g_clear_object (&state->muxer);
   g_free (state->dbus_name);
 
   if (state->name_watcher_id)
@@ -1701,7 +1663,7 @@ shell_app_class_init(ShellAppClass *klass)
                                    g_param_spec_object ("action-group",
                                                         "Application Action Group",
                                                         "The action group exported by the remote application",
-                                                        G_TYPE_DBUS_ACTION_GROUP,
+                                                        G_TYPE_ACTION_GROUP,
                                                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
   /**
    * ShellApp:menu:



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