/*
* 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 "gtkactionmuxerprivate.h"
#include "gtkactionobservableprivate.h"
#include "gtkactionobserverprivate.h"
#include "gtkbitmaskprivate.h"
#include "gtkmarshalers.h"
#include "gtkwidgetprivate.h"
#include "gsettings-mapping.h"
#include "gtkdebug.h"
#include "gtkprivate.h"
#include <string.h>
typedef struct
{
char *action_and_target;
char *accel;
} GtkAccel;
static void
gtk_accel_clear (GtkAccel *accel)
{
g_free (accel->action_and_target);
g_free (accel->accel);
}
#define GDK_ARRAY_NAME gtk_accels
#define GDK_ARRAY_TYPE_NAME GtkAccels
#define GDK_ARRAY_ELEMENT_TYPE GtkAccel
#define GDK_ARRAY_FREE_FUNC gtk_accel_clear
#define GDK_ARRAY_BY_VALUE 1
#define GDK_ARRAY_PREALLOC 2
#include "gdk/gdkarrayimpl.c"
static guint
gtk_accels_find (GtkAccels *accels,
const char *action_and_target)
{
guint i;
for (i = 0 ; i < gtk_accels_get_size (accels); i++)
{
GtkAccel *accel = gtk_accels_index (accels, i);
if (strcmp (accel->action_and_target, action_and_target) == 0 )
return i;
}
return G_MAXUINT;
}
static void
gtk_accels_replace (GtkAccels *accels,
const char *action_and_target,
const char *primary_accel)
{
guint position;
position = gtk_accels_find (accels, action_and_target);
if (position < gtk_accels_get_size (accels))
{
GtkAccel *accel = gtk_accels_index (accels, position);
g_free (accel->accel);
accel->accel = g_strdup (primary_accel);
}
else
{
GtkAccel accel;
accel.action_and_target = g_strdup (action_and_target);
accel.accel = g_strdup (primary_accel);
gtk_accels_append (accels, &accel);
}
}
static void
gtk_accels_remove (GtkAccels *accels,
const char *action_and_target)
{
guint position;
position = gtk_accels_find (accels, action_and_target);
if (position < gtk_accels_get_size (accels))
gtk_accels_splice (accels, position, 1 , FALSE , NULL, 0 );
}
/*< private >
* GtkActionMuxer :
*
* GtkActionMuxer aggregates and monitors actions from multiple sources .
*
* ` GtkActionMuxer ` is ` GtkActionObservable ` and ` GtkActionObserver ` that
* offers a ` GActionGroup ` - like api and is capable of containing other
* ` GActionGroup ` instances . ` GtkActionMuxer ` does not implement the
* ` GActionGroup ` interface because it requires excessive signal emissions
* and has poor scalability . We use the ` GtkActionObserver ` machinery
* instead to propagate changes between action muxer instances and
* to other users .
*
* Beyond action groups , ` GtkActionMuxer ` can incorporate actions that
* are associated with widget classes ( * class actions * ) and actions
* that are associated with the parent widget , allowing for recursive
* lookup .
*
* In addition to the action attributes provided by ` GActionGroup ` ,
* ` GtkActionMuxer ` maintains a * primary accelerator * string for
* actions that can be shown in menuitems .
*
* 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 ` - like interface presented by the ` GtkActionMuxer ` .
*
* Activations and state change requests on the ` GtkActionMuxer ` are
* wired through to the underlying actions 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_observable_iface_init (GtkActionObservableInterface *iface);
static void gtk_action_muxer_observer_iface_init (GtkActionObserverInterface *iface);
typedef GObjectClass GtkActionMuxerClass;
struct _GtkActionMuxer
{
GObject parent_instance;
GtkActionMuxer *parent;
GtkWidget *widget;
GHashTable *observed_actions;
GHashTable *groups;
GtkAccels primary_accels;
GtkBitmask *widget_actions_disabled;
};
G_DEFINE_TYPE_WITH_CODE (GtkActionMuxer, gtk_action_muxer, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVER, gtk_action_muxer_observer_iface_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVABLE, gtk_action_muxer_observable_iface_init))
enum
{
PROP_0,
PROP_PARENT,
PROP_WIDGET,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES];
typedef struct
{
GtkActionMuxer *muxer;
GSList *watchers;
char *fullname;
} Action;
typedef struct
{
GtkActionMuxer *muxer;
GActionGroup *group;
char *prefix;
gulong handler_ids[4 ];
} Group;
static inline guint
get_action_position (GtkWidgetAction *action)
{
guint slot;
/* We use the length of @action to the end of the chain as the slot so that
* we have stable positions for any class or it ' s subclasses . Doing so helps
* us avoid having mutable arrays in the class data as we will not have
* access to the ClassPrivate data during instance _ init ( ) functions .
*/
for (slot = 0 ; action->next != NULL; slot++, action = action->next) {}
return slot;
}
static void
gtk_action_muxer_append_group_actions (const char *prefix,
Group *group,
GHashTable *actions)
{
char **group_actions;
char **action;
group_actions = g_action_group_list_actions (group->group);
for (action = group_actions; *action; action++)
{
char *name = g_strconcat (prefix, "." , *action, NULL);
g_hash_table_add (actions, name);
}
g_strfreev (group_actions);
}
char **
gtk_action_muxer_list_actions (GtkActionMuxer *muxer,
gboolean local_only)
{
GHashTable *actions;
char **keys;
g_return_val_if_fail (GTK_IS_ACTION_MUXER (muxer), NULL);
actions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
for ( ; muxer != NULL; muxer = muxer->parent)
{
GHashTableIter iter;
const char *prefix;
Group *group;
if (muxer->widget)
{
GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
GtkWidgetClassPrivate *priv = klass->priv;
GtkWidgetAction *action;
for (action = priv->actions; action; action = action->next)
g_hash_table_add (actions, g_strdup (action->name));
}
if (muxer->groups)
{
g_hash_table_iter_init (&iter, muxer->groups);
while (g_hash_table_iter_next (&iter, (gpointer *)&prefix, (gpointer *)&group))
gtk_action_muxer_append_group_actions (prefix, group, actions);
}
if (local_only)
break ;
}
keys = (char **)g_hash_table_get_keys_as_array (actions, NULL);
g_hash_table_steal_all (actions);
g_hash_table_unref (actions);
return (char **)keys;
}
static Group *
gtk_action_muxer_find_group (GtkActionMuxer *muxer,
const char *full_name,
const char **action_name)
{
const char *dot;
char *prefix;
const char *name;
Group *group;
if (!muxer->groups)
return NULL;
dot = strchr (full_name, '.' );
if (!dot)
return NULL;
name = dot + 1 ;
prefix = g_strndup (full_name, dot - full_name);
group = g_hash_table_lookup (muxer->groups, prefix);
g_free (prefix);
if (action_name)
*action_name = name;
if (group &&
g_action_group_has_action (group->group, name))
return group;
return NULL;
}
GActionGroup *
gtk_action_muxer_find (GtkActionMuxer *muxer,
const char *action_name,
const char **unprefixed_name)
{
Group *group;
group = gtk_action_muxer_find_group (muxer, action_name, unprefixed_name);
if (group)
return group->group;
return NULL;
}
GActionGroup *
gtk_action_muxer_get_group (GtkActionMuxer *muxer,
const char *group_name)
{
Group *group;
if (!muxer->groups)
return NULL;
group = g_hash_table_lookup (muxer->groups, group_name);
if (group)
return group->group;
return NULL;
}
static inline Action *
find_observers (GtkActionMuxer *muxer,
const char *action_name)
{
if (muxer->observed_actions)
return g_hash_table_lookup (muxer->observed_actions, action_name);
return NULL;
}
void
gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
const char *action_name,
gboolean enabled)
{
GtkWidgetAction *iter;
Action *action;
GSList *node;
if (muxer->widget)
{
GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
GtkWidgetClassPrivate *priv = klass->priv;
for (iter = priv->actions; iter; iter = iter->next)
{
if (strcmp (action_name, iter->name) == 0 )
{
guint position = get_action_position (iter);
muxer->widget_actions_disabled =
_gtk_bitmask_set (muxer->widget_actions_disabled, position, !enabled);
break ;
}
}
}
action = find_observers (muxer, 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);
}
static void
gtk_action_muxer_group_action_enabled_changed (GActionGroup *action_group,
const char *action_name,
gboolean enabled,
gpointer user_data)
{
Group *group = user_data;
char *fullname;
fullname = g_strconcat (group->prefix, "." , action_name, NULL);
gtk_action_muxer_action_enabled_changed (group->muxer, fullname, enabled);
g_free (fullname);
}
void
gtk_action_muxer_action_state_changed (GtkActionMuxer *muxer,
const char *action_name,
GVariant *state)
{
Action *action;
GSList *node;
action = find_observers (muxer, 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);
}
static void
gtk_action_muxer_group_action_state_changed (GActionGroup *action_group,
const char *action_name,
GVariant *state,
gpointer user_data)
{
Group *group = user_data;
char *fullname;
fullname = g_strconcat (group->prefix, "." , action_name, NULL);
gtk_action_muxer_action_state_changed (group->muxer, fullname, state);
g_free (fullname);
}
static gboolean action_muxer_query_action (GtkActionMuxer *muxer,
const char *action_name,
gboolean *enabled,
const GVariantType **parameter_type,
const GVariantType **state_type,
GVariant **state_hint,
GVariant **state,
gboolean recurse);
static void
notify_observers_added (GtkActionMuxer *muxer,
GtkActionMuxer *parent)
{
GHashTableIter iter;
const char *action_name;
Action *action;
if (!muxer->observed_actions)
return ;
g_hash_table_iter_init (&iter, muxer->observed_actions);
while (g_hash_table_iter_next (&iter, (gpointer *)&action_name, (gpointer *)&action))
{
const GVariantType *parameter_type;
gboolean enabled;
GVariant *state;
GSList *node;
GSList *action_watchers;
action_watchers = action ? action->watchers : NULL;
if (!action_watchers)
continue ;
for (node = action_watchers; node; node = node->next)
gtk_action_observer_primary_accel_changed (node->data, GTK_ACTION_OBSERVABLE (muxer),
action_name, NULL);
gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (parent), action_name, GTK_ACTION_OBSERVER (muxer));
if (!action_muxer_query_action (muxer, action_name,
&enabled, ¶meter_type,
NULL, NULL, &state,
TRUE ))
continue ;
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);
}
}
static void
notify_observers_removed (GtkActionMuxer *muxer,
GtkActionMuxer *parent)
{
GHashTableIter iter;
const char *action_name;
Action *action;
if (!muxer->observed_actions)
return ;
g_hash_table_iter_init (&iter, muxer->observed_actions);
while (g_hash_table_iter_next (&iter, (gpointer *)&action_name, (gpointer *)&action))
{
GSList *node;
gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (parent), action_name, GTK_ACTION_OBSERVER (muxer));
for (node = action->watchers; node; node = node->next)
{
gtk_action_observer_action_removed (node->data,
GTK_ACTION_OBSERVABLE (muxer),
action_name);
}
}
}
static void
gtk_action_muxer_action_added (GtkActionMuxer *muxer,
const char *action_name,
const GVariantType *parameter_type,
gboolean enabled,
GVariant *state)
{
Action *action;
GSList *node;
action = find_observers (muxer, action_name);
for (node = action ? action->watchers : NULL; node; node = node->next)
gtk_action_observer_action_added (node->data,
GTK_ACTION_OBSERVABLE (muxer),
action_name, parameter_type, enabled, state);
}
static void
gtk_action_muxer_action_added_to_group (GActionGroup *action_group,
const char *action_name,
gpointer user_data)
{
Group *group = user_data;
GtkActionMuxer *muxer = group->muxer;
Action *action;
const GVariantType *parameter_type;
gboolean enabled;
GVariant *state;
char *fullname;
fullname = g_strconcat (group->prefix, "." , action_name, NULL);
if (muxer->parent)
gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (muxer->parent),
fullname,
GTK_ACTION_OBSERVER (muxer));
action = find_observers (muxer, fullname);
if (action && action->watchers &&
g_action_group_query_action (action_group, action_name,
&enabled, ¶meter_type, NULL, NULL, &state))
{
gtk_action_muxer_action_added (muxer, fullname, parameter_type, enabled, state);
if (state)
g_variant_unref (state);
}
g_free (fullname);
}
static void
gtk_action_muxer_action_removed (GtkActionMuxer *muxer,
const char *action_name)
{
Action *action;
GSList *node;
action = find_observers (muxer, 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);
}
static void
gtk_action_muxer_action_removed_from_group (GActionGroup *action_group,
const char *action_name,
gpointer user_data)
{
Group *group = user_data;
GtkActionMuxer *muxer = group->muxer;
char *fullname;
Action *action;
fullname = g_strconcat (group->prefix, "." , action_name, NULL);
gtk_action_muxer_action_removed (muxer, fullname);
g_free (fullname);
action = find_observers (muxer, action_name);
if (action && action->watchers &&
!action_muxer_query_action (muxer, action_name,
NULL, NULL, NULL, NULL, NULL, FALSE ))
{
if (muxer->parent)
gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (muxer->parent),
action_name,
GTK_ACTION_OBSERVER (muxer));
}
}
static void
gtk_action_muxer_primary_accel_changed (GtkActionMuxer *muxer,
const char *action_name,
const char *action_and_target)
{
Action *action;
GSList *node;
if (!action_name)
action_name = strrchr (action_and_target, '|' ) + 1 ;
action = find_observers (muxer, 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);
}
static GVariant *
prop_action_get_state (GtkWidget *widget,
GtkWidgetAction *action)
{
GValue value = G_VALUE_INIT;
GVariant *result;
g_value_init (&value, action->pspec->value_type);
g_object_get_property (G_OBJECT (widget), action->pspec->name, &value);
result = g_settings_set_mapping (&value, action->state_type, NULL);
g_value_unset (&value);
return g_variant_ref_sink (result);
}
static GVariant *
prop_action_get_state_hint (GtkWidget *widget,
GtkWidgetAction *action)
{
if (action->pspec->value_type == G_TYPE_INT)
{
GParamSpecInt *pspec = (GParamSpecInt *)action->pspec;
return g_variant_new ("(ii)" , pspec->minimum, pspec->maximum);
}
else if (action->pspec->value_type == G_TYPE_UINT)
{
GParamSpecUInt *pspec = (GParamSpecUInt *)action->pspec;
return g_variant_new ("(uu)" , pspec->minimum, pspec->maximum);
}
else if (action->pspec->value_type == G_TYPE_FLOAT)
{
GParamSpecFloat *pspec = (GParamSpecFloat *)action->pspec;
return g_variant_new ("(dd)" , (double )pspec->minimum, (double )pspec->maximum);
}
else if (action->pspec->value_type == G_TYPE_DOUBLE)
{
GParamSpecDouble *pspec = (GParamSpecDouble *)action->pspec;
return g_variant_new ("(dd)" , pspec->minimum, pspec->maximum);
}
return NULL;
}
static void
prop_action_set_state (GtkWidget *widget,
GtkWidgetAction *action,
GVariant *state)
{
GValue value = G_VALUE_INIT;
g_value_init (&value, action->pspec->value_type);
g_settings_get_mapping (&value, state, NULL);
g_object_set_property (G_OBJECT (widget), action->pspec->name, &value);
g_value_unset (&value);
}
static void
prop_action_activate (GtkWidget *widget,
GtkWidgetAction *action,
GVariant *parameter)
{
if (action->pspec->value_type == G_TYPE_BOOLEAN)
{
gboolean value;
g_return_if_fail (parameter == NULL);
g_object_get (G_OBJECT (widget), action->pspec->name, &value, NULL);
value = !value;
g_object_set (G_OBJECT (widget), action->pspec->name, value, NULL);
}
else
{
g_return_if_fail (parameter != NULL && g_variant_is_of_type (parameter, action->state_type));
prop_action_set_state (widget, action, parameter);
}
}
static void
prop_action_notify (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GtkWidget *widget = GTK_WIDGET (object);
GtkWidgetAction *action = user_data;
GtkActionMuxer *muxer = _gtk_widget_get_action_muxer (widget, TRUE );
GVariant *state;
g_assert (muxer->widget == widget);
g_assert (action->pspec == pspec);
state = prop_action_get_state (widget, action);
gtk_action_muxer_action_state_changed (muxer, action->name, state);
g_variant_unref (state);
}
static void
prop_actions_connect (GtkActionMuxer *muxer)
{
GtkWidgetClassPrivate *priv;
GtkWidgetAction *action;
GtkWidgetClass *klass;
guint signal_id;
if (!muxer->widget)
return ;
klass = GTK_WIDGET_GET_CLASS (muxer->widget);
priv = klass->priv;
if (!priv->actions)
return ;
signal_id = g_signal_lookup ("notify" , G_TYPE_OBJECT);
for (action = priv->actions; action; action = action->next)
{
if (!action->pspec)
continue ;
g_signal_connect_closure_by_id (muxer->widget,
signal_id,
g_param_spec_get_name_quark (action->pspec),
g_cclosure_new (G_CALLBACK (prop_action_notify),
action,
NULL),
FALSE );
}
}
static gboolean
action_muxer_query_action (GtkActionMuxer *muxer,
const char *action_name,
gboolean *enabled,
const GVariantType **parameter_type,
const GVariantType **state_type,
GVariant **state_hint,
GVariant **state,
gboolean recurse)
{
GtkWidgetAction *action;
Group *group;
const char *unprefixed_name;
if (muxer->widget)
{
GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
GtkWidgetClassPrivate *priv = klass->priv;
for (action = priv->actions; action; action = action->next)
{
guint position;
if (strcmp (action->name, action_name) != 0 )
continue ;
position = get_action_position (action);
if (enabled)
*enabled = !_gtk_bitmask_get (muxer->widget_actions_disabled, position);
if (parameter_type)
*parameter_type = action->parameter_type;
if (state_type)
*state_type = action->state_type;
if (state_hint)
*state_hint = NULL;
if (state)
*state = NULL;
if (action->pspec)
{
if (state)
*state = prop_action_get_state (muxer->widget, action);
if (state_hint)
*state_hint = prop_action_get_state_hint (muxer->widget, action);
}
return TRUE ;
}
}
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 && recurse)
return gtk_action_muxer_query_action (muxer->parent, action_name,
enabled, parameter_type,
state_type, state_hint, state);
return FALSE ;
}
gboolean
gtk_action_muxer_query_action (GtkActionMuxer *muxer,
const char *action_name,
gboolean *enabled,
const GVariantType **parameter_type,
const GVariantType **state_type,
GVariant **state_hint,
GVariant **state)
{
return action_muxer_query_action (muxer, action_name,
enabled, parameter_type,
state_type, state_hint, state,
TRUE );
}
gboolean
gtk_action_muxer_has_action (GtkActionMuxer *muxer,
const char *action_name)
{
return action_muxer_query_action (muxer, action_name,
NULL, NULL, NULL, NULL, NULL,
TRUE );
}
void
gtk_action_muxer_activate_action (GtkActionMuxer *muxer,
const char *action_name,
GVariant *parameter)
{
const char *unprefixed_name;
Group *group;
if (muxer->widget)
{
GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
GtkWidgetClassPrivate *priv = klass->priv;
GtkWidgetAction *action;
for (action = priv->actions; action; action = action->next)
{
if (strcmp (action->name, action_name) == 0 )
{
guint position = get_action_position (action);
if (!_gtk_bitmask_get (muxer->widget_actions_disabled, position))
{
if (action->activate)
{
GTK_DEBUG (ACTIONS, "%s: activate action" , action->name);
action->activate (muxer->widget, action->name, parameter);
}
else if (action->pspec)
{
GTK_DEBUG (ACTIONS, "%s: activate prop action" , action->pspec->name);
prop_action_activate (muxer->widget, action, parameter);
}
}
return ;
}
}
}
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)
gtk_action_muxer_activate_action (muxer->parent, action_name, parameter);
}
void
gtk_action_muxer_change_action_state (GtkActionMuxer *muxer,
const char *action_name,
GVariant *state)
{
GtkWidgetAction *action;
const char *unprefixed_name;
Group *group;
if (muxer->widget)
{
GtkWidgetClass *klass = GTK_WIDGET_GET_CLASS (muxer->widget);
GtkWidgetClassPrivate *priv = klass->priv;
for (action = priv->actions; action; action = action->next)
{
if (strcmp (action->name, action_name) == 0 )
{
if (action->pspec)
prop_action_set_state (muxer->widget, action, state);
return ;
}
}
}
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)
gtk_action_muxer_change_action_state (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)
{
if (muxer->parent)
gtk_action_observable_unregister_observer (GTK_ACTION_OBSERVABLE (muxer->parent),
action->fullname,
GTK_ACTION_OBSERVER (muxer));
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_free_action (gpointer data);
static void
gtk_action_muxer_register_observer (GtkActionObservable *observable,
const char *name,
GtkActionObserver *observer)
{
GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
Action *action;
gboolean enabled;
const GVariantType *parameter_type;
GVariant *state;
gboolean is_duplicate;
if (!muxer->observed_actions)
muxer->observed_actions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_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);
}
is_duplicate = g_slist_find (action->watchers, observer) != NULL;
action->watchers = g_slist_prepend (action->watchers, observer);
g_object_weak_ref (G_OBJECT (observer), gtk_action_muxer_weak_notify, action);
if (!is_duplicate)
{
if (action_muxer_query_action (muxer, name,
&enabled, ¶meter_type,
NULL, NULL, &state, TRUE ))
{
gtk_action_muxer_action_added (muxer, name, parameter_type, enabled, state);
g_clear_pointer (&state, g_variant_unref);
}
if (muxer->parent)
{
gtk_action_observable_register_observer (GTK_ACTION_OBSERVABLE (muxer->parent),
name,
GTK_ACTION_OBSERVER (muxer));
}
}
}
static void
gtk_action_muxer_unregister_observer (GtkActionObservable *observable,
const char *name,
GtkActionObserver *observer)
{
GtkActionMuxer *muxer = GTK_ACTION_MUXER (observable);
Action *action = find_observers (muxer, name);
if (action)
{
if (g_slist_find (action->watchers, observer) != NULL)
{
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;
int 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_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);
if (muxer->observed_actions)
{
g_assert_cmpint (g_hash_table_size (muxer->observed_actions), ==, 0 );
g_hash_table_unref (muxer->observed_actions);
}
if (muxer->groups)
g_hash_table_unref (muxer->groups);
gtk_accels_clear (&muxer->primary_accels);
_gtk_bitmask_free (muxer->widget_actions_disabled);
G_OBJECT_CLASS (gtk_action_muxer_parent_class)->finalize (object);
}
static void
gtk_action_muxer_dispose (GObject *object)
{
GtkActionMuxer *muxer = GTK_ACTION_MUXER (object);
g_clear_object (&muxer->parent);
if (muxer->observed_actions)
g_hash_table_remove_all (muxer->observed_actions);
muxer->widget = NULL;
G_OBJECT_CLASS (gtk_action_muxer_parent_class)->dispose (object);
}
void
gtk_action_muxer_connect_class_actions (GtkActionMuxer *muxer)
{
prop_actions_connect (muxer);
}
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 ;
case PROP_WIDGET:
g_value_set_object (value, muxer->widget);
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 ;
case PROP_WIDGET:
muxer->widget = 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->widget_actions_disabled = _gtk_bitmask_new ();
}
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_observer_action_added (GtkActionObserver *observer,
GtkActionObservable *observable,
const char *action_name,
const GVariantType *parameter_type,
gboolean enabled,
GVariant *state)
{
if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name,
NULL, NULL, NULL, NULL, NULL, FALSE ))
return ;
gtk_action_muxer_action_added (GTK_ACTION_MUXER (observer),
action_name,
parameter_type,
enabled,
state);
}
static void
gtk_action_muxer_observer_action_removed (GtkActionObserver *observer,
GtkActionObservable *observable,
const char *action_name)
{
if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name,
NULL, NULL, NULL, NULL, NULL, FALSE ))
return ;
gtk_action_muxer_action_removed (GTK_ACTION_MUXER (observer), action_name);
}
static void
gtk_action_muxer_observer_action_enabled_changed (GtkActionObserver *observer,
GtkActionObservable *observable,
const char *action_name,
gboolean enabled)
{
if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name,
NULL, NULL, NULL, NULL, NULL, FALSE ))
return ;
gtk_action_muxer_action_enabled_changed (GTK_ACTION_MUXER (observer), action_name, enabled);
}
static void
gtk_action_muxer_observer_action_state_changed (GtkActionObserver *observer,
GtkActionObservable *observable,
const char *action_name,
GVariant *state)
{
if (action_muxer_query_action (GTK_ACTION_MUXER (observer), action_name,
NULL, NULL, NULL, NULL, NULL, FALSE ))
return ;
gtk_action_muxer_action_state_changed (GTK_ACTION_MUXER (observer), action_name, state);
}
static void
gtk_action_muxer_observer_primary_accel_changed (GtkActionObserver *observer,
GtkActionObservable *observable,
const char *action_name,
const char *action_and_target)
{
gtk_action_muxer_primary_accel_changed (GTK_ACTION_MUXER (observer),
action_name,
action_and_target);
}
static void
gtk_action_muxer_observer_iface_init (GtkActionObserverInterface *iface)
{
iface->action_added = gtk_action_muxer_observer_action_added;
iface->action_removed = gtk_action_muxer_observer_action_removed;
iface->action_enabled_changed = gtk_action_muxer_observer_action_enabled_changed;
iface->action_state_changed = gtk_action_muxer_observer_action_state_changed;
iface->primary_accel_changed = gtk_action_muxer_observer_primary_accel_changed;
}
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;
properties[PROP_PARENT] = g_param_spec_object ("parent" , NULL, NULL,
GTK_TYPE_ACTION_MUXER,
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME);
properties[PROP_WIDGET] = g_param_spec_object ("widget" , NULL, NULL,
GTK_TYPE_WIDGET,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NAME);
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 ` GtkActionObserver ` s 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 char *prefix,
GActionGroup *action_group)
{
char **actions;
Group *group;
int i;
/* TODO: diff instead of ripout and replace */
gtk_action_muxer_remove (muxer, prefix);
if (!muxer->groups)
muxer->groups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gtk_action_muxer_free_group);
group = g_new0 (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);
}
/*< 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 ` GtkActionObserver ` s are registered for actions in the group ,
* “ action_removed ” notifications will be emitted , as appropriate .
*/
void
gtk_action_muxer_remove (GtkActionMuxer *muxer,
const char *prefix)
{
Group *group;
if (!muxer->groups)
return ;
group = g_hash_table_lookup (muxer->groups, prefix);
if (group != NULL)
{
char **actions;
int 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);
}
}
/*< private >
* gtk_action_muxer_new :
* @ widget : the widget to which the muxer belongs
*
* Creates a new ` GtkActionMuxer ` .
*/
GtkActionMuxer *
gtk_action_muxer_new (GtkWidget *widget)
{
return g_object_new (GTK_TYPE_ACTION_MUXER,
"widget" , widget,
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;
}
/*< private >
* 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)
{
notify_observers_removed (muxer, muxer->parent);
g_object_unref (muxer->parent);
}
muxer->parent = parent;
if (muxer->parent != NULL)
{
g_object_ref (muxer->parent);
notify_observers_added (muxer, muxer->parent);
}
g_object_notify_by_pspec (G_OBJECT (muxer), properties[PROP_PARENT]);
}
void
gtk_action_muxer_set_primary_accel (GtkActionMuxer *muxer,
const char *action_and_target,
const char *primary_accel)
{
if (primary_accel)
gtk_accels_replace (&muxer->primary_accels, action_and_target, primary_accel);
else
gtk_accels_remove (&muxer->primary_accels, action_and_target);
gtk_action_muxer_primary_accel_changed (muxer, NULL, action_and_target);
}
const char *
gtk_action_muxer_get_primary_accel (GtkActionMuxer *muxer,
const char *action_and_target)
{
guint position;
position = gtk_accels_find (&muxer->primary_accels, action_and_target);
if (position < G_MAXUINT)
return gtk_accels_index (&muxer->primary_accels, position)->accel;
if (!muxer->parent)
return NULL;
return gtk_action_muxer_get_primary_accel (muxer->parent, action_and_target);
}
char *
gtk_print_action_and_target (const char *action_namespace,
const char *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 );
}
char *
gtk_normalise_detailed_action_name (const char *detailed_action_name)
{
GError *error = NULL;
char *action_and_target;
char *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;
}
Messung V0.5 in Prozent C=98 H=94 G=95
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland