forked from AuroraMiddleware/gtk
08f216e11f
The documentation for gtk_widget_get_action_group and gtk_widget_list_prefixes states that both of these operate on all the action groups that are 'available' to the widget. Which means: they are meant to walk up the parent muxer chain. So do that. Add tests to verify the expected behavior. Closes: https://gitlab.gnome.org/GNOME/gtk/issues/1396
987 lines
31 KiB
C
987 lines
31 KiB
C
/*
|
|
* 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 "gtkintl.h"
|
|
#include "gtkmarshalers.h"
|
|
|
|
#include <string.h>
|
|
|
|
/*< private >
|
|
* 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 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)
|
|
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)
|
|
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_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), 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_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
|
|
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_slice_free (Action, 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);
|
|
if (muxer->primary_accels)
|
|
g_hash_table_unref (muxer->primary_accels);
|
|
|
|
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 (I_("primary-accel-changed"),
|
|
GTK_TYPE_ACTION_MUXER,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
_gtk_marshal_VOID__STRING_STRING,
|
|
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
|
|
g_signal_set_va_marshaller (accel_signal,
|
|
G_TYPE_FROM_CLASS (class),
|
|
_gtk_marshal_VOID__STRING_STRINGv);
|
|
|
|
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);
|
|
}
|
|
|
|
/*< private >
|
|
* 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 `x` in @action_group, there is an equivalent
|
|
* action @prefix`.x` in @muxer.
|
|
*
|
|
* For example, if @prefix is “`app`” and @action_group
|
|
* contains an action called “`quit`”, then @muxer will
|
|
* now contain an action called “`app.quit`”.
|
|
*
|
|
* 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_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++)
|
|
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);
|
|
}
|
|
|
|
/*< private >
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_action_muxer_append_prefixes (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *prefix = key;
|
|
GArray *prefixes= user_data;
|
|
|
|
g_array_append_val (prefixes, prefix);
|
|
}
|
|
|
|
const gchar **
|
|
gtk_action_muxer_list_prefixes (GtkActionMuxer *muxer)
|
|
{
|
|
GArray *prefixes;
|
|
|
|
prefixes = g_array_new (TRUE, FALSE, sizeof (gchar *));
|
|
|
|
for ( ; muxer != NULL; muxer = muxer->parent)
|
|
{
|
|
g_hash_table_foreach (muxer->groups,
|
|
gtk_action_muxer_append_prefixes,
|
|
prefixes);
|
|
}
|
|
|
|
return (const gchar **)(void *) g_array_free (prefixes, FALSE);
|
|
}
|
|
|
|
GActionGroup *
|
|
gtk_action_muxer_lookup (GtkActionMuxer *muxer,
|
|
const gchar *prefix)
|
|
{
|
|
Group *group;
|
|
|
|
for ( ; muxer != NULL; muxer = muxer->parent)
|
|
{
|
|
group = g_hash_table_lookup (muxer->groups, prefix);
|
|
|
|
if (group != NULL)
|
|
return group->group;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*< private >
|
|
* gtk_action_muxer_new:
|
|
*
|
|
* Creates a new #GtkActionMuxer.
|
|
*/
|
|
GtkActionMuxer *
|
|
gtk_action_muxer_new (void)
|
|
{
|
|
return g_object_new (GTK_TYPE_ACTION_MUXER, NULL);
|
|
}
|
|
|
|
/*< private >
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
/*< private >
|
|
* gtk_action_muxer_set_parent:
|
|
* @muxer: a #GtkActionMuxer
|
|
* @parent: (allow-none): 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);
|
|
}
|
|
|
|
gchar *
|
|
gtk_normalise_detailed_action_name (const gchar *detailed_action_name)
|
|
{
|
|
GError *error = NULL;
|
|
gchar *action_and_target;
|
|
gchar *action_name;
|
|
GVariant *target;
|
|
|
|
g_action_parse_detailed_name (detailed_action_name, &action_name, &target, &error);
|
|
g_assert_no_error (error);
|
|
|
|
action_and_target = gtk_print_action_and_target (NULL, action_name, target);
|
|
|
|
if (target)
|
|
g_variant_unref (target);
|
|
|
|
g_free (action_name);
|
|
|
|
return action_and_target;
|
|
}
|