forked from AuroraMiddleware/gtk
c4a47e218f
No need to construct a detailed signal name for every action when we can just look up the signal ID once and use the quark that the GParamSpec already has. Also, we don't need to loop over the actions every time we get a notification.
1262 lines
39 KiB
C
1262 lines
39 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 "gtkactionmuxerprivate.h"
|
|
|
|
#include "gtkactionobservableprivate.h"
|
|
#include "gtkactionobserverprivate.h"
|
|
#include "gtkbitmaskprivate.h"
|
|
#include "gtkintl.h"
|
|
#include "gtkmarshalers.h"
|
|
#include "gtkwidgetprivate.h"
|
|
#include "gsettings-mapping.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;
|
|
|
|
GtkWidget *widget;
|
|
|
|
GtkBitmask *widget_actions_disabled;
|
|
};
|
|
|
|
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,
|
|
PROP_WIDGET,
|
|
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 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)
|
|
{
|
|
gchar **group_actions;
|
|
gchar **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);
|
|
}
|
|
|
|
static gchar **
|
|
gtk_action_muxer_list_actions (GActionGroup *action_group)
|
|
{
|
|
GtkActionMuxer *muxer = GTK_ACTION_MUXER (action_group);
|
|
GHashTable *actions;
|
|
char **keys;
|
|
|
|
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));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 gchar *full_name,
|
|
const gchar **action_name)
|
|
{
|
|
const gchar *dot;
|
|
gchar *prefix;
|
|
const char *name;
|
|
Group *group;
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
gtk_action_muxer_action_enabled_changed (GtkActionMuxer *muxer,
|
|
const gchar *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 = 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);
|
|
}
|
|
|
|
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 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
|
|
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);
|
|
GtkWidgetAction *action;
|
|
Group *group;
|
|
const gchar *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)
|
|
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);
|
|
const gchar *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)
|
|
action->activate (muxer->widget, action->name, parameter);
|
|
else if (action->pspec)
|
|
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)
|
|
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);
|
|
GtkWidgetAction *action;
|
|
const gchar *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)
|
|
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);
|
|
|
|
_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);
|
|
|
|
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);
|
|
|
|
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->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);
|
|
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_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);
|
|
|
|
properties[PROP_WIDGET] = g_param_spec_object ("widget", "Widget",
|
|
"The widget that owns the muxer",
|
|
GTK_TYPE_WIDGET,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*< 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|