diff options
Diffstat (limited to '')
-rw-r--r-- | src/gtkactionmuxer.c | 945 |
1 files changed, 945 insertions, 0 deletions
diff --git a/src/gtkactionmuxer.c b/src/gtkactionmuxer.c new file mode 100644 index 0000000..7e3e86e --- /dev/null +++ b/src/gtkactionmuxer.c @@ -0,0 +1,945 @@ +/* + * 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 "gtkactionmuxer.h" + +#include "gtkactionobservable.h" +#include "gtkactionobserver.h" + +#include <clutter/clutter.h> + +#include <string.h> + +/** + * SECTION:gtkactionmuxer + * @short_description: Aggregate and monitor several action groups + * + * #GtkActionMuxer is a #GActionGroup and #GtkActionObservable 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 + * #GtkActionMuxer 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 #GtkActionMuxer. + * + * Activations and state change requests on the #GtkActionMuxer 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 gtk_action_muxer_group_iface_init (GActionGroupInterface *iface); +static void gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface); + +typedef GObjectClass GtkActionMuxerClass; + +struct _GtkActionMuxer +{ + GObject parent_instance; + + GHashTable *observed_actions; + GHashTable *groups; + GHashTable *primary_accels; + GtkActionMuxer *parent; +}; + +G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, gtk_action_muxer_group_iface_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init)) + +enum +{ + PROP_0, + PROP_PARENT, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +guint accel_signal; + +typedef struct +{ + GtkActionMuxer *muxer; + GSList *watchers; + gchar *fullname; +} Action; + +typedef struct +{ + GtkActionMuxer *muxer; + GActionGroup *group; + gchar *prefix; + gulong handler_ids[4]; +} Group; + +static void +gtk_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 ** +gtk_action_muxer_list_actions (GActionGroup *action_group) +{ + GtkActionMuxer *muxer = GTK_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, + gtk_action_muxer_append_group_actions, + actions); + } + + return (gchar **)(void *) g_array_free (actions, FALSE); +} + +static Group * +gtk_action_muxer_find_group (GtkActionMuxer *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 +gtk_action_muxer_action_enabled_changed (GtkActionMuxer *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) + gtk_action_observer_action_enabled_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, enabled); + g_action_group_action_enabled_changed (G_ACTION_GROUP (muxer), action_name, enabled); +} + +static void +gtk_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); + gtk_action_muxer_action_enabled_changed (group->muxer, fullname, enabled); + + g_free (fullname); +} + +static void +gtk_action_muxer_parent_action_enabled_changed (GActionGroup *action_group, + const gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + gtk_action_muxer_action_enabled_changed (muxer, action_name, enabled); +} + +static void +gtk_action_muxer_action_state_changed (GtkActionMuxer *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) + gtk_action_observer_action_state_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name, state); + g_action_group_action_state_changed (G_ACTION_GROUP (muxer), action_name, state); +} + +static void +gtk_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); + gtk_action_muxer_action_state_changed (group->muxer, fullname, state); + + g_free (fullname); +} + +static void +gtk_action_muxer_parent_action_state_changed (GActionGroup *action_group, + const gchar *action_name, + GVariant *state, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + gtk_action_muxer_action_state_changed (muxer, action_name, state); +} + +static void +gtk_action_muxer_action_added (GtkActionMuxer *muxer, + const gchar *action_name, + GActionGroup *original_group, + const gchar *orignal_action_name) +{ + const GVariantType *parameter_type; + gboolean enabled; + GVariant *state; + Action *action; + + action = g_hash_table_lookup (muxer->observed_actions, action_name); + + if (action && action->watchers && + g_action_group_query_action (original_group, orignal_action_name, + &enabled, ¶meter_type, NULL, NULL, &state)) + { + GSList *node; + + for (node = action->watchers; node; node = node->next) + gtk_action_observer_action_added (node->data, + GTK_ACTION_OBSERVABLE (muxer), + action_name, parameter_type, enabled, state); + + if (state) + g_variant_unref (state); + } + + g_action_group_action_added (G_ACTION_GROUP (muxer), action_name); +} + +static void +gtk_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); + gtk_action_muxer_action_added (group->muxer, fullname, action_group, action_name); + + g_free (fullname); +} + +static void +gtk_action_muxer_action_added_to_parent (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + gtk_action_muxer_action_added (muxer, action_name, action_group, action_name); +} + +static void +gtk_action_muxer_action_removed (GtkActionMuxer *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) + gtk_action_observer_action_removed (node->data, GTK_ACTION_OBSERVABLE (muxer), action_name); + g_action_group_action_removed (G_ACTION_GROUP (muxer), action_name); +} + +static void +gtk_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); + gtk_action_muxer_action_removed (group->muxer, fullname); + + g_free (fullname); +} + +static void +gtk_action_muxer_action_removed_from_parent (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + gtk_action_muxer_action_removed (muxer, action_name); +} + +static void +gtk_action_muxer_primary_accel_changed (GtkActionMuxer *muxer, + const gchar *action_name, + const gchar *action_and_target) +{ + Action *action; + GSList *node; + + if (!action_name) + action_name = strrchr (action_and_target, '|') + 1; + + action = g_hash_table_lookup (muxer->observed_actions, action_name); + for (node = action ? action->watchers : NULL; node; node = node->next) + gtk_action_observer_primary_accel_changed (node->data, GTK_ACTION_OBSERVABLE (muxer), + action_name, action_and_target); + g_signal_emit (muxer, accel_signal, 0, action_name, action_and_target); +} + +static void +gtk_action_muxer_parent_primary_accel_changed (GtkActionMuxer *parent, + const gchar *action_name, + const gchar *action_and_target, + gpointer user_data) +{ + GtkActionMuxer *muxer = user_data; + + /* If it's in our table then don't let the parent one filter through */ + if (muxer->primary_accels && g_hash_table_lookup (muxer->primary_accels, action_and_target)) + return; + + gtk_action_muxer_primary_accel_changed (muxer, action_name, action_and_target); +} + +static gboolean +gtk_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) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group); + Group *group; + const gchar *unprefixed_name; + + group = gtk_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 GVariant * +get_platform_data (void) +{ + gchar time[32]; + GVariantBuilder *builder; + GVariant *result; + + g_snprintf (time, 32, "_TIME%d", clutter_get_current_event_time ()); + + builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + + g_variant_builder_add (builder, "{sv}", "desktop-startup-id", + g_variant_new_string (time)); + + result = g_variant_builder_end (builder); + g_variant_builder_unref (builder); + + return result; +} + +static void +gtk_action_muxer_activate_action (GActionGroup *action_group, + const gchar *action_name, + GVariant *parameter) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group); + Group *group; + const gchar *unprefixed_name; + + group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name); + + if (group) + { + if (G_IS_REMOTE_ACTION_GROUP (group->group)) + g_remote_action_group_activate_action_full (G_REMOTE_ACTION_GROUP (group->group), + unprefixed_name, parameter, + get_platform_data ()); + else + 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 +gtk_action_muxer_change_action_state (GActionGroup *action_group, + const gchar *action_name, + GVariant *state) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group); + Group *group; + const gchar *unprefixed_name; + + group = gtk_action_muxer_find_group (muxer, action_name, &unprefixed_name); + + if (group) + { + if (G_IS_REMOTE_ACTION_GROUP (group->group)) + g_remote_action_group_change_action_state_full (G_REMOTE_ACTION_GROUP (group->group), + unprefixed_name, + state, + get_platform_data ()); + else + 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 +gtk_action_muxer_unregister_internal (Action *action, + gpointer observer) +{ + GtkActionMuxer *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 +gtk_action_muxer_weak_notify (gpointer data, + GObject *where_the_object_was) +{ + Action *action = data; + + gtk_action_muxer_unregister_internal (action, where_the_object_was); +} + +static void +gtk_action_muxer_register_observer (GtkActionObservable *observable, + const gchar *name, + GtkActionObserver *observer) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable); + Action *action; + + action = g_hash_table_lookup (muxer->observed_actions, name); + + if (action == NULL) + { + action = g_new (Action, 1); + 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), gtk_action_muxer_weak_notify, action); +} + +static void +gtk_action_muxer_unregister_observer (GtkActionObservable *observable, + const gchar *name, + GtkActionObserver *observer) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable); + Action *action; + + action = g_hash_table_lookup (muxer->observed_actions, name); + g_object_weak_unref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action); + gtk_action_muxer_unregister_internal (action, observer); +} + +static void +gtk_action_muxer_free_group (gpointer data) +{ + Group *group = data; + gint i; + + /* 'for loop' or 'four loop'? */ + for (i = 0; i < 4; i++) + g_clear_signal_handler (&group->handler_ids[i], group->group); + + g_object_unref (group->group); + g_free (group->prefix); + + g_free (group); +} + +static void +gtk_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), gtk_action_muxer_weak_notify, action); + + g_slist_free (action->watchers); + g_free (action->fullname); + + g_free (action); +} + +static void +gtk_action_muxer_finalize (GObject *object) +{ + GtkActionMuxer *muxer = GTK_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 (gtk_action_muxer_parent_class) + ->finalize (object); +} + +static void +gtk_action_muxer_dispose (GObject *object) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); + + if (muxer->parent) + { + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_changed, muxer); + + g_clear_object (&muxer->parent); + } + + g_hash_table_remove_all (muxer->observed_actions); + + G_OBJECT_CLASS (gtk_action_muxer_parent_class) + ->dispose (object); +} + +static void +gtk_action_muxer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); + + switch (property_id) + { + case PROP_PARENT: + g_value_set_object (value, gtk_action_muxer_get_parent (muxer)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gtk_action_muxer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkActionMuxer *muxer = GTK_ACTION_MUXER (object); + + switch (property_id) + { + case PROP_PARENT: + gtk_action_muxer_set_parent (muxer, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gtk_action_muxer_init (GtkActionMuxer *muxer) +{ + muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_action); + muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_group); +} + +static void +gtk_action_muxer_observable_iface_init (GtkActionObservableInterface *iface) +{ + iface->register_observer = gtk_action_muxer_register_observer; + iface->unregister_observer = gtk_action_muxer_unregister_observer; +} + +static void +gtk_action_muxer_group_iface_init (GActionGroupInterface *iface) +{ + iface->list_actions = gtk_action_muxer_list_actions; + iface->query_action = gtk_action_muxer_query_action; + iface->activate_action = gtk_action_muxer_activate_action; + iface->change_action_state = gtk_action_muxer_change_action_state; +} + +static void +gtk_action_muxer_class_init (GObjectClass *class) +{ + class->get_property = gtk_action_muxer_get_property; + class->set_property = gtk_action_muxer_set_property; + class->finalize = gtk_action_muxer_finalize; + class->dispose = gtk_action_muxer_dispose; + + accel_signal = g_signal_new ("primary-accel-changed", GTK_TYPE_ACTION_MUXER, G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + + properties[PROP_PARENT] = g_param_spec_object ("parent", "Parent", + "The parent muxer", + GTK_TYPE_ACTION_MUXER, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (class, NUM_PROPERTIES, properties); +} + +/** + * gtk_action_muxer_insert: + * @muxer: a #GtkActionMuxer + * @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 #GtkActionObservers are registered for actions in the group, + * "action_added" notifications will be emitted, as appropriate. + * + * @prefix must not contain a dot ('.'). + */ +void +gtk_action_muxer_insert (GtkActionMuxer *muxer, + const gchar *prefix, + GActionGroup *action_group) +{ + gchar **actions; + Group *group; + gint i; + + /* TODO: diff instead of ripout and replace */ + gtk_action_muxer_remove (muxer, prefix); + + group = g_new (Group, 1); + 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++) + gtk_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 (gtk_action_muxer_action_added_to_group), group); + group->handler_ids[1] = g_signal_connect (group->group, "action-removed", + G_CALLBACK (gtk_action_muxer_action_removed_from_group), group); + group->handler_ids[2] = g_signal_connect (group->group, "action-enabled-changed", + G_CALLBACK (gtk_action_muxer_group_action_enabled_changed), group); + group->handler_ids[3] = g_signal_connect (group->group, "action-state-changed", + G_CALLBACK (gtk_action_muxer_group_action_state_changed), group); +} + +/** + * gtk_action_muxer_remove: + * @muxer: a #GtkActionMuxer + * @prefix: the prefix of the action group to remove + * + * Removes a #GActionGroup from the #GtkActionMuxer. + * + * If any #GtkActionObservers are registered for actions in the group, + * "action_removed" notifications will be emitted, as appropriate. + */ +void +gtk_action_muxer_remove (GtkActionMuxer *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++) + gtk_action_muxer_action_removed_from_group (group->group, actions[i], group); + g_strfreev (actions); + + gtk_action_muxer_free_group (group); + } +} + +/** + * gtk_action_muxer_new: + * + * Creates a new #GtkActionMuxer. + */ +GtkActionMuxer * +gtk_action_muxer_new (void) +{ + return g_object_new (GTK_TYPE_ACTION_MUXER, NULL); +} + +/** + * gtk_action_muxer_get_parent: + * @muxer: a #GtkActionMuxer + * + * Returns: (transfer none): the parent of @muxer, or NULL. + */ +GtkActionMuxer * +gtk_action_muxer_get_parent (GtkActionMuxer *muxer) +{ + g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL); + + return muxer->parent; +} + +static void +emit_changed_accels (GtkActionMuxer *muxer, + GtkActionMuxer *parent) +{ + while (parent) + { + if (parent->primary_accels) + { + GHashTableIter iter; + gpointer key; + + g_hash_table_iter_init (&iter, parent->primary_accels); + while (g_hash_table_iter_next (&iter, &key, NULL)) + gtk_action_muxer_primary_accel_changed (muxer, NULL, key); + } + + parent = parent->parent; + } +} + +/** + * gtk_action_muxer_set_parent: + * @muxer: a #GtkActionMuxer + * @parent: (nullable): the new parent #GtkActionMuxer + * + * Sets the parent of @muxer to @parent. + */ +void +gtk_action_muxer_set_parent (GtkActionMuxer *muxer, + GtkActionMuxer *parent) +{ + g_return_if_fail (GTK_IS_ACTION_MUXER (muxer)); + g_return_if_fail (parent == NULL || GTK_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++) + gtk_action_muxer_action_removed (muxer, *it); + g_strfreev (actions); + + emit_changed_accels (muxer, muxer->parent); + + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_added_to_parent, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_action_removed_from_parent, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_enabled_changed, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_action_state_changed, muxer); + g_signal_handlers_disconnect_by_func (muxer->parent, gtk_action_muxer_parent_primary_accel_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++) + gtk_action_muxer_action_added (muxer, *it, G_ACTION_GROUP (muxer->parent), *it); + g_strfreev (actions); + + emit_changed_accels (muxer, muxer->parent); + + g_signal_connect (muxer->parent, "action-added", + G_CALLBACK (gtk_action_muxer_action_added_to_parent), muxer); + g_signal_connect (muxer->parent, "action-removed", + G_CALLBACK (gtk_action_muxer_action_removed_from_parent), muxer); + g_signal_connect (muxer->parent, "action-enabled-changed", + G_CALLBACK (gtk_action_muxer_parent_action_enabled_changed), muxer); + g_signal_connect (muxer->parent, "action-state-changed", + G_CALLBACK (gtk_action_muxer_parent_action_state_changed), muxer); + g_signal_connect (muxer->parent, "primary-accel-changed", + G_CALLBACK (gtk_action_muxer_parent_primary_accel_changed), muxer); + } + + g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]); +} + +void +gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer, + const gchar *action_and_target, + const gchar *primary_accel) +{ + if (!muxer->primary_accels) + muxer->primary_accels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + if (primary_accel) + g_hash_table_insert (muxer->primary_accels, g_strdup (action_and_target), g_strdup (primary_accel)); + else + g_hash_table_remove (muxer->primary_accels, action_and_target); + + gtk_action_muxer_primary_accel_changed (muxer, NULL, action_and_target); +} + +const gchar * +gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer, + const gchar *action_and_target) +{ + if (muxer->primary_accels) + { + const gchar *primary_accel; + + primary_accel = g_hash_table_lookup (muxer->primary_accels, action_and_target); + + if (primary_accel) + return primary_accel; + } + + if (!muxer->parent) + return NULL; + + return gtk_action_muxer_get_primary_accel (muxer->parent, action_and_target); +} + +gchar * +gtk_print_action_and_target (const gchar *action_namespace, + const gchar *action_name, + GVariant *target) +{ + GString *result; + + g_return_val_if_fail (strchr (action_name, '|') == NULL, NULL); + g_return_val_if_fail (action_namespace == NULL || strchr (action_namespace, '|') == NULL, NULL); + + result = g_string_new (NULL); + + if (target) + g_variant_print_string (target, result, TRUE); + g_string_append_c (result, '|'); + + if (action_namespace) + { + g_string_append (result, action_namespace); + g_string_append_c (result, '.'); + } + + g_string_append (result, action_name); + + return g_string_free (result, FALSE); +} |