aboutsummaryrefslogtreecommitdiff
path: root/src/gactionmuxer.c
diff options
context:
space:
mode:
authorLars Uebernickel <lars.uebernickel@canonical.com>2012-06-03 08:33:31 +0200
committerLars Uebernickel <lars.uebernickel@canonical.com>2012-06-03 08:33:31 +0200
commit6872c840672049695904d61c1a3baba22c6d9627 (patch)
treed2393b4131adc1508f14c3977ef0d854ca36a25f /src/gactionmuxer.c
parenta6d889c175d136cfb1d63a9c04bb2156f210a09e (diff)
downloadayatana-indicator-messages-6872c840672049695904d61c1a3baba22c6d9627.tar.gz
ayatana-indicator-messages-6872c840672049695904d61c1a3baba22c6d9627.tar.bz2
ayatana-indicator-messages-6872c840672049695904d61c1a3baba22c6d9627.zip
Add GActionMuxer
This is a new implementation of an action muxer with the same interface as the one in the gtk tree. Its implementation is considerably simpler, mostly because it doesn't need to implement GActionObservable. In addition to muxing different action groups with the <prefix>.<action_name> scheme, it has the notion of global, prefix-less actions. Indicator-messages needs those for the status and clear actions.
Diffstat (limited to 'src/gactionmuxer.c')
-rw-r--r--src/gactionmuxer.c456
1 files changed, 456 insertions, 0 deletions
diff --git a/src/gactionmuxer.c b/src/gactionmuxer.c
new file mode 100644
index 0000000..cccfeda
--- /dev/null
+++ b/src/gactionmuxer.c
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ * Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "gactionmuxer.h"
+
+#include <string.h>
+
+/*
+ * SECTION:gactionmuxer
+ * @short_description: Aggregate several action groups
+ *
+ * #GActionMuxer is a #GActionGroup 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.
+ */
+
+typedef GObjectClass GActionMuxerClass;
+
+struct _GActionMuxer
+{
+ GObject parent;
+ GActionGroup *global_actions;
+ GHashTable *groups; /* prefix -> subgroup */
+ GHashTable *reverse; /* subgroup -> prefix */
+};
+
+
+static void g_action_muxer_group_init (GActionGroupInterface *iface);
+static void g_action_muxer_dispose (GObject *object);
+static void g_action_muxer_finalize (GObject *object);
+static gchar ** g_action_muxer_list_actions (GActionGroup *group);
+static void g_action_muxer_activate_action (GActionGroup *group,
+ const gchar *action_name,
+ GVariant *parameter);
+static void g_action_muxer_change_action_state (GActionGroup *group,
+ const gchar *action_name,
+ GVariant *value);
+static gboolean g_action_muxer_query_action (GActionGroup *group,
+ const gchar *action_name,
+ gboolean *enabled,
+ const GVariantType **parameter_type,
+ const GVariantType **state_type,
+ GVariant **state_hint,
+ GVariant **state);
+static void g_action_muxer_action_added (GActionGroup *group,
+ gchar *action_name,
+ gpointer user_data);
+static void g_action_muxer_action_removed (GActionGroup *group,
+ gchar *action_name,
+ gpointer user_data);
+static void g_action_muxer_action_state_changed (GActionGroup *group,
+ gchar *action_name,
+ GVariant *value,
+ gpointer user_data);
+static void g_action_muxer_action_enabled_changed (GActionGroup *group,
+ gchar *action_name,
+ gboolean enabled,
+ gpointer user_data);
+
+G_DEFINE_TYPE_WITH_CODE (GActionMuxer, g_action_muxer, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_muxer_group_init));
+
+
+static void
+g_action_muxer_class_init (GObjectClass *klass)
+{
+ klass->dispose = g_action_muxer_dispose;
+ klass->finalize = g_action_muxer_finalize;
+}
+
+static void
+g_action_muxer_init (GActionMuxer *muxer)
+{
+ muxer->global_actions = NULL;
+ muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ muxer->reverse = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+g_action_muxer_group_init (GActionGroupInterface *iface)
+{
+ iface->list_actions = g_action_muxer_list_actions;
+ iface->activate_action = g_action_muxer_activate_action;
+ iface->change_action_state = g_action_muxer_change_action_state;
+ iface->query_action = g_action_muxer_query_action;
+}
+
+static void
+g_action_muxer_dispose (GObject *object)
+{
+ GActionMuxer *muxer = G_ACTION_MUXER (object);
+ GHashTableIter it;
+ gchar *prefix;
+
+ g_clear_object (&muxer->global_actions);
+
+ g_hash_table_iter_init (&it, muxer->groups);
+ while (g_hash_table_iter_next (&it, (gpointer *) &prefix, NULL))
+ g_action_muxer_remove (muxer, prefix);
+}
+
+static void
+g_action_muxer_finalize (GObject *object)
+{
+ GActionMuxer *muxer = G_ACTION_MUXER (object);
+
+ g_hash_table_unref (muxer->groups);
+ g_hash_table_unref (muxer->reverse);
+
+ G_OBJECT_CLASS (g_action_muxer_parent_class)->finalize (object);
+}
+
+static GActionGroup *
+g_action_muxer_lookup_group (GActionMuxer *muxer,
+ const gchar *full_name,
+ const gchar **action_name)
+{
+ const gchar *sep;
+ GActionGroup *group;
+
+ sep = strchr (full_name, '.');
+
+ if (sep)
+ {
+ gchar *prefix;
+ prefix = g_strndup (full_name, sep - full_name);
+ group = g_hash_table_lookup (muxer->groups, prefix);
+ g_free (prefix);
+ if (action_name)
+ *action_name = sep + 1;
+ }
+ else
+ {
+ group = muxer->global_actions;
+ if (action_name)
+ *action_name = full_name;
+ }
+
+ return group;
+}
+
+static gchar *
+g_action_muxer_lookup_full_name (GActionMuxer *muxer,
+ GActionGroup *subgroup,
+ const gchar *action_name)
+{
+ gpointer prefix;
+
+ if (subgroup == muxer->global_actions)
+ return g_strdup (action_name);
+
+ if (g_hash_table_lookup_extended (muxer->reverse, subgroup, NULL, &prefix))
+ return g_strdup_printf ("%s.%s", (gchar *) prefix, action_name);
+
+ return NULL;
+}
+
+static gchar **
+g_action_muxer_list_actions (GActionGroup *group)
+{
+ GActionMuxer *muxer = G_ACTION_MUXER (group);
+ GHashTableIter it;
+ GArray *all_actions;
+ gchar *prefix;
+ GActionGroup *subgroup;
+ gchar **actions;
+ gchar **a;
+
+ all_actions = g_array_sized_new (TRUE, FALSE, sizeof (gchar *), 8);
+
+ actions = g_action_group_list_actions (muxer->global_actions);
+ for (a = actions; *a; a++)
+ {
+ gchar *name = g_strdup (*a);
+ g_array_append_val (all_actions, name);
+ }
+ g_strfreev (actions);
+
+ g_hash_table_iter_init (&it, muxer->groups);
+ while (g_hash_table_iter_next (&it, (gpointer *) &prefix, (gpointer *) &subgroup))
+ {
+ actions = g_action_group_list_actions (subgroup);
+ for (a = actions; *a; a++)
+ {
+ gchar *full_name = g_strdup_printf ("%s.%s", prefix, *a);
+ g_array_append_val (all_actions, full_name);
+ }
+ g_strfreev (actions);
+ }
+
+ return (gchar **) g_array_free (all_actions, FALSE);
+}
+
+static void
+g_action_muxer_activate_action (GActionGroup *group,
+ const gchar *action_name,
+ GVariant *parameter)
+{
+ GActionMuxer *muxer = G_ACTION_MUXER (group);
+ GActionGroup *subgroup;
+ const gchar *action;
+
+ subgroup = g_action_muxer_lookup_group (muxer, action_name, &action);
+
+ if (subgroup)
+ g_action_group_activate_action (subgroup, action, parameter);
+}
+
+static void
+g_action_muxer_change_action_state (GActionGroup *group,
+ const gchar *action_name,
+ GVariant *value)
+{
+ GActionMuxer *muxer = G_ACTION_MUXER (group);
+ GActionGroup *subgroup;
+ const gchar *action;
+
+ subgroup = g_action_muxer_lookup_group (muxer, action_name, &action);
+
+ if (subgroup)
+ g_action_group_change_action_state (subgroup, action, value);
+}
+
+static gboolean
+g_action_muxer_query_action (GActionGroup *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 (group);
+ GActionGroup *subgroup;
+ const gchar *action;
+
+ subgroup = g_action_muxer_lookup_group (muxer, action_name, &action);
+
+ if (!subgroup)
+ return FALSE;
+
+ return g_action_group_query_action (subgroup, action, enabled, parameter_type,
+ state_type, state_hint, state);
+}
+
+static void
+g_action_muxer_action_added (GActionGroup *group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ GActionMuxer *muxer = user_data;
+ gchar *full_name;
+
+ full_name = g_action_muxer_lookup_full_name (muxer, group, action_name);
+
+ if (full_name)
+ {
+ g_action_group_action_added (G_ACTION_GROUP (muxer), full_name);
+ g_free (full_name);
+ }
+}
+
+static void
+g_action_muxer_action_removed (GActionGroup *group,
+ gchar *action_name,
+ gpointer user_data)
+{
+ GActionMuxer *muxer = user_data;
+ gchar *full_name;
+
+ full_name = g_action_muxer_lookup_full_name (muxer, group, action_name);
+
+ if (full_name)
+ {
+ g_action_group_action_removed (G_ACTION_GROUP (muxer), full_name);
+ g_free (full_name);
+ }
+}
+
+static void
+g_action_muxer_action_state_changed (GActionGroup *group,
+ gchar *action_name,
+ GVariant *value,
+ gpointer user_data)
+{
+ GActionMuxer *muxer = user_data;
+ gchar *full_name;
+
+ full_name = g_action_muxer_lookup_full_name (muxer, group, action_name);
+
+ if (full_name)
+ {
+ g_action_group_action_state_changed (G_ACTION_GROUP (muxer), full_name, value);
+ g_free (full_name);
+ }
+}
+
+static void
+g_action_muxer_action_enabled_changed (GActionGroup *group,
+ gchar *action_name,
+ gboolean enabled,
+ gpointer user_data)
+{
+ GActionMuxer *muxer = user_data;
+ gchar *full_name;
+
+ full_name = g_action_muxer_lookup_full_name (muxer, group, action_name);
+
+ if (full_name)
+ {
+ g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), full_name, enabled);
+ g_free (full_name);
+ }
+}
+
+/*
+ * g_action_muxer_new:
+ *
+ * Creates a new #GActionMuxer.
+ */
+GActionMuxer *
+g_action_muxer_new (void)
+{
+ return g_object_new (G_TYPE_ACTION_MUXER, NULL);
+}
+
+/*
+ * g_action_muxer_insert:
+ * @muxer: a #GActionMuxer
+ * @prefix: (allow-none): the prefix string for the action group, or NULL
+ * @group: a #GActionGroup
+ *
+ * Adds the actions in @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 @group, there is an equivalent action
+ * @prefix<literal>.</literal><varname>x</varname> in @muxer.
+ *
+ * For example, if @prefix is "<literal>app</literal>" and @group contains an
+ * action called "<literal>quit</literal>", then @muxer will now contain an
+ * action called "<literal>app.quit</literal>".
+ *
+ * If @prefix is <literal>NULL</literal>, the actions in @group will be added
+ * to @muxer without prefix.
+ *
+ * There may only be one group per prefix (including the
+ * <literal>NULL</literal>-prefix). If a group has been added with @prefix in
+ * a previous call to this function, it will be removed.
+ *
+ * @prefix must not contain a dot ('.').
+ */
+void
+g_action_muxer_insert (GActionMuxer *muxer,
+ const gchar *prefix,
+ GActionGroup *group)
+{
+ gchar *prefix_copy;
+ gchar **actions;
+ gchar **action;
+
+ g_return_if_fail (G_IS_ACTION_MUXER (muxer));
+ g_return_if_fail (G_IS_ACTION_GROUP (group));
+
+ g_action_muxer_remove (muxer, prefix);
+
+ if (prefix)
+ {
+ prefix_copy = g_strdup (prefix);
+ g_hash_table_insert (muxer->groups, prefix_copy, g_object_ref (group));
+ g_hash_table_insert (muxer->reverse, group, prefix_copy);
+ }
+ else
+ muxer->global_actions = g_object_ref (group);
+
+ actions = g_action_group_list_actions (group);
+ for (action = actions; *action; action++)
+ g_action_muxer_action_added (group, *action, muxer);
+ g_strfreev (actions);
+
+ g_signal_connect (group, "action-added", G_CALLBACK (g_action_muxer_action_added), muxer);
+ g_signal_connect (group, "action-removed", G_CALLBACK (g_action_muxer_action_removed), muxer);
+ g_signal_connect (group, "action-enabled-changed", G_CALLBACK (g_action_muxer_action_enabled_changed), muxer);
+ g_signal_connect (group, "action-state-changed", G_CALLBACK (g_action_muxer_action_state_changed), muxer);
+}
+
+/*
+ * g_action_muxer_remove:
+ * @muxer: a #GActionMuxer
+ * @prefix: (allow-none): the prefix of the action group to remove, or NULL
+ *
+ * Removes a #GActionGroup from the #GActionMuxer.
+ */
+void
+g_action_muxer_remove (GActionMuxer *muxer,
+ const gchar *prefix)
+{
+ GActionGroup *subgroup;
+ gchar **actions;
+ gchar **action;
+
+ g_return_if_fail (G_IS_ACTION_MUXER (muxer));
+
+ subgroup = prefix ? g_hash_table_lookup (muxer->groups, prefix) : muxer->global_actions;
+ if (!subgroup)
+ return;
+
+ actions = g_action_group_list_actions (subgroup);
+ for (action = actions; *action; action++)
+ g_action_muxer_action_removed (subgroup, *action, muxer);
+ g_strfreev (actions);
+
+ g_signal_handlers_disconnect_by_func (subgroup, g_action_muxer_action_added, muxer);
+ g_signal_handlers_disconnect_by_func (subgroup, g_action_muxer_action_removed, muxer);
+ g_signal_handlers_disconnect_by_func (subgroup, g_action_muxer_action_enabled_changed, muxer);
+ g_signal_handlers_disconnect_by_func (subgroup, g_action_muxer_action_state_changed, muxer);
+
+ if (prefix)
+ {
+ g_hash_table_remove (muxer->groups, prefix);
+ g_hash_table_remove (muxer->reverse, subgroup);
+ }
+ else
+ g_clear_object (&muxer->global_actions);
+}
+