/* dzl-widget-action-group.c
*
* Copyright (C) 2015 Christian Hergert <chergert@redhat.com>
*
* This file 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.1 of the License, or (at your option) any later version.
*
* This file 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "dzl-widget-action-group"
#include "config.h"
#include <string.h>
#include "dzl-widget-action-group.h"
struct _DzlWidgetActionGroup
{
GObject parent_instance;
GtkWidget *widget;
GHashTable *enabled;
};
static void action_group_iface_init (GActionGroupInterface *iface);
G_DEFINE_TYPE_EXTENDED (DzlWidgetActionGroup, dzl_widget_action_group, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, action_group_iface_init))
enum {
PROP_0,
PROP_WIDGET,
N_PROPS
};
static GHashTable *cached_types;
static GParamSpec *properties [N_PROPS];
static gboolean
supports_types (const GType *types,
guint n_types)
{
guint i;
g_assert (types != NULL || n_types == 0);
for (i = 0; i < n_types; i++)
{
switch (types [i])
{
case G_TYPE_STRING:
case G_TYPE_INT:
case G_TYPE_UINT:
case G_TYPE_INT64:
case G_TYPE_UINT64:
case G_TYPE_BOOLEAN:
case G_TYPE_DOUBLE:
case G_TYPE_FLOAT:
case G_TYPE_CHAR:
case G_TYPE_UCHAR:
case G_TYPE_ENUM:
case G_TYPE_FLAGS:
case G_TYPE_VARIANT:
case G_TYPE_NONE:
break;
default:
if (G_TYPE_IS_FLAGS (types [i]) || G_TYPE_IS_ENUM (types [i]))
break;
return FALSE;
}
}
return TRUE;
}
static const GVariantType *
create_variant_type (const GType *types,
guint n_types)
{
const GVariantType *ret = NULL;
GString *str;
g_assert (types != NULL || n_types == 0);
str = g_string_new ("(");
for (guint i = 0; i < n_types; i++)
{
switch (types [i])
{
case G_TYPE_STRING:
g_string_append_c (str, 's');
break;
case G_TYPE_INT:
g_string_append_c (str, 'i');
break;
case G_TYPE_UINT:
g_string_append_c (str, 'u');
break;
case G_TYPE_INT64:
g_string_append_c (str, 'x');
break;
case G_TYPE_UINT64:
g_string_append_c (str, 't');
break;
case G_TYPE_BOOLEAN:
g_string_append_c (str, 'b');
break;
case G_TYPE_DOUBLE:
case G_TYPE_FLOAT:
g_string_append_c (str, 'd');
break;
case G_TYPE_CHAR:
case G_TYPE_UCHAR:
g_string_append_c (str, 'y');
break;
case G_TYPE_VARIANT:
g_string_append_c (str, 'v');
break;
case G_TYPE_NONE:
break;
default:
if (G_TYPE_IS_ENUM (types [i]) || G_TYPE_IS_FLAGS (types [i]))
{
g_string_append_c (str, 'u');
break;
}
g_string_free (str, TRUE);
return NULL;
}
}
g_string_append_c (str, ')');
if (g_str_equal (str->str, "()"))
{
g_string_free (str, TRUE);
return NULL;
}
if (cached_types == NULL)
cached_types = g_hash_table_new (g_str_hash, g_str_equal);
ret = g_hash_table_lookup (cached_types, str->str);
if (ret == NULL)
{
gchar *type_str = g_string_free (str, FALSE);
g_hash_table_insert (cached_types, type_str, type_str);
ret = (const GVariantType *)type_str;
}
else
g_string_free (str, TRUE);
return ret;
}
static void
do_activate (DzlWidgetActionGroup *self,
GtkWidget *widget,
GSignalQuery *query,
GVariant *params)
{
g_auto(GValue) return_value = G_VALUE_INIT;
g_auto(GValue) instance = G_VALUE_INIT;
GArray *ar;
GVariantIter iter;
gsize n_children;
g_assert (query != NULL);
g_assert (GTK_IS_WIDGET (widget));
if (params != NULL)
g_debug ("Activating %s with %s\n", query->signal_name, g_variant_print (params, TRUE));
if (params == NULL && query->n_params != 0)
{
g_critical ("%s::%s() requires %d parameters",
G_OBJECT_TYPE_NAME (widget), query->signal_name, query->n_params);
return;
}
if (query->return_type != G_TYPE_NONE)
g_value_init (&return_value, query->return_type);
g_value_init (&instance, query->itype);
g_value_set_object (&instance, widget);
if (params == NULL)
{
g_signal_emitv (&instance, query->signal_id, 0, &return_value);
return;
}
g_assert (g_variant_is_container (params));
g_assert (params != NULL);
n_children = g_variant_iter_init (&iter, params);
if (n_children != query->n_params)
{
g_critical ("%s::%s() requires %d params, got %d",
G_OBJECT_TYPE_NAME (widget), query->signal_name,
(gint)n_children, query->n_params);
return;
}
ar = g_array_new (FALSE, FALSE, sizeof (GValue));
g_array_append_val (ar, instance);
g_variant_iter_init (&iter, params);
for (guint i = 0; i < query->n_params; i++)
{
g_autoptr(GVariant) param = NULL;
GValue value = G_VALUE_INIT;
param = g_variant_iter_next_value (&iter);
#define CONVERT_PARAM(TYPE, VARIANT_TYPE, setter, getter, ...) \
case G_TYPE_##TYPE: \
{ \
if (!g_variant_is_of_type (param, G_VARIANT_TYPE_##VARIANT_TYPE)) \
{ \
g_critical ("parameter type mismatch for signal %s", \
query->signal_name); \
goto skip_emit; \
} \
g_value_init (&value, G_TYPE_##TYPE); \
g_value_set_##setter (&value, g_variant_get_##getter (param, ##__VA_ARGS__)); \
g_array_append_val (ar, value); \
} \
break
switch (query->param_types [i])
{
CONVERT_PARAM(STRING, STRING, string, string, NULL);
CONVERT_PARAM(INT, INT32, int, int32);
CONVERT_PARAM(UINT, UINT32, uint, uint32);
CONVERT_PARAM(INT64, INT64, int64, int64);
CONVERT_PARAM(UINT64, UINT64, uint64, uint64);
CONVERT_PARAM(BOOLEAN, BOOLEAN, boolean, boolean);
CONVERT_PARAM(DOUBLE, DOUBLE, double, double);
CONVERT_PARAM(FLOAT, DOUBLE, float, double);
CONVERT_PARAM(CHAR, BYTE, schar, byte);
CONVERT_PARAM(UCHAR, BYTE, uchar, byte);
CONVERT_PARAM(VARIANT, VARIANT, variant, variant);
default:
if (G_TYPE_IS_ENUM(query->param_types [i]))
{
if (!g_variant_is_of_type (param, G_VARIANT_TYPE_UINT32))
goto skip_emit;
g_value_init (&value, query->param_types [i]);
g_value_set_enum (&value, g_variant_get_uint32 (param));
g_array_append_val (ar, value);
break;
}
else if (G_TYPE_IS_FLAGS (query->param_types [i]))
{
if (!g_variant_is_of_type (param, G_VARIANT_TYPE_UINT32))
goto skip_emit;
g_value_init (&value, query->param_types [i]);
g_value_set_flags (&value, g_variant_get_uint32 (param));
g_array_append_val (ar, value);
break;
}
g_critical ("Unknown param type: %s", g_type_name (query->param_types [i]));
goto skip_emit;
}
#undef CONVERT_PARAM
}
g_signal_emitv ((GValue *)(gpointer)ar->data, query->signal_id, 0, &return_value);
skip_emit:
/* ignore instance */
for (guint i = 1; i < ar->len; i++)
g_value_unset (&g_array_index (ar, GValue, i));
g_array_unref (ar);
}
static void
dzl_widget_action_group_set_widget (DzlWidgetActionGroup *self,
GtkWidget *widget)
{
g_assert (DZL_IS_WIDGET_ACTION_GROUP (self));
g_assert (!widget || GTK_IS_WIDGET (widget));
if (widget != self->widget)
{
if (self->widget != NULL)
{
g_signal_handlers_disconnect_by_func (self->widget,
G_CALLBACK (gtk_widget_destroyed),
&self->widget);
self->widget = NULL;
}
if (widget != NULL)
{
self->widget = widget;
g_signal_connect (self->widget,
"destroy",
G_CALLBACK (gtk_widget_destroyed),
&self->widget);
}
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_WIDGET]);
}
}
static void
dzl_widget_action_group_finalize (GObject *object)
{
DzlWidgetActionGroup *self = (DzlWidgetActionGroup *)object;
g_clear_pointer (&self->enabled, g_hash_table_unref);
G_OBJECT_CLASS (dzl_widget_action_group_parent_class)->finalize (object);
}
static void
dzl_widget_action_group_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
DzlWidgetActionGroup *self = DZL_WIDGET_ACTION_GROUP (object);
switch (prop_id)
{
case PROP_WIDGET:
g_value_set_object (value, self->widget);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
dzl_widget_action_group_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
DzlWidgetActionGroup *self = DZL_WIDGET_ACTION_GROUP (object);
switch (prop_id)
{
case PROP_WIDGET:
dzl_widget_action_group_set_widget (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
dzl_widget_action_group_class_init (DzlWidgetActionGroupClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = dzl_widget_action_group_finalize;
object_class->get_property = dzl_widget_action_group_get_property;
object_class->set_property = dzl_widget_action_group_set_property;
properties [PROP_WIDGET] =
g_param_spec_object ("widget",
"Widget",
"Widget",
GTK_TYPE_WIDGET,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
dzl_widget_action_group_init (DzlWidgetActionGroup *self)
{
}
static gboolean
dzl_widget_action_group_has_action (GActionGroup *group,
const gchar *action_name)
{
DzlWidgetActionGroup *self = (DzlWidgetActionGroup *)group;
g_assert (DZL_IS_WIDGET_ACTION_GROUP (self));
g_assert (action_name != NULL);
if (GTK_IS_WIDGET (self->widget))
return (0 != g_signal_lookup (action_name, G_OBJECT_TYPE (self->widget)));
return FALSE;
}
static gchar **
dzl_widget_action_group_list_actions (GActionGroup *group)
{
DzlWidgetActionGroup *self = (DzlWidgetActionGroup *)group;
GPtrArray *ar;
g_assert (DZL_IS_WIDGET_ACTION_GROUP (self));
ar = g_ptr_array_new ();
if (self->widget != NULL)
{
for (GType type = G_OBJECT_TYPE (self->widget);
type != G_TYPE_INVALID;
type = g_type_parent (type))
{
g_autofree guint *signal_ids = NULL;
guint n_ids = 0;
guint i;
signal_ids = g_signal_list_ids (type, &n_ids);
for (i = 0; i < n_ids; i++)
{
GSignalQuery query;
g_signal_query (signal_ids[i], &query);
if ((query.signal_flags & G_SIGNAL_ACTION) != 0)
g_ptr_array_add (ar, g_strdup (query.signal_name));
}
}
}
g_ptr_array_add (ar, NULL);
return (gchar **)g_ptr_array_free (ar, FALSE);
}
static gboolean
dzl_widget_action_group_get_action_enabled (GActionGroup *group,
const gchar *action_name)
{
DzlWidgetActionGroup *self = (DzlWidgetActionGroup *)group;
g_assert (DZL_IS_WIDGET_ACTION_GROUP (group));
g_assert (action_name != NULL);
if (self->enabled && g_hash_table_contains (self->enabled, action_name))
return GPOINTER_TO_INT (g_hash_table_lookup (self->enabled, action_name));
return TRUE;
}
const GVariantType *
dzl_widget_action_group_get_action_parameter_type (GActionGroup *group,
const gchar *action_name)
{
DzlWidgetActionGroup *self = (DzlWidgetActionGroup *)group;
GSignalQuery query;
guint signal_id;
g_assert (DZL_IS_WIDGET_ACTION_GROUP (self));
g_assert (action_name != NULL);
if (!GTK_IS_WIDGET (self->widget))
return NULL;
signal_id = g_signal_lookup (action_name, G_OBJECT_TYPE (self->widget));
if (signal_id == 0)
return NULL;
g_signal_query (signal_id, &query);
if (!supports_types (query.param_types, query.n_params))
return NULL;
return create_variant_type (query.param_types, query.n_params);
}
const GVariantType *
dzl_widget_action_group_get_action_state_type (GActionGroup *group,
const gchar *action_name)
{
g_assert (DZL_IS_WIDGET_ACTION_GROUP (group));
g_assert (action_name != NULL);
return NULL;
}
static void
dzl_widget_action_group_activate_action (GActionGroup *group,
const gchar *action_name,
GVariant *params)
{
DzlWidgetActionGroup *self = (DzlWidgetActionGroup *)group;
g_assert (DZL_IS_WIDGET_ACTION_GROUP (group));
g_assert (action_name != NULL);
if (GTK_IS_WIDGET (self->widget))
{
guint signal_id;
signal_id = g_signal_lookup (action_name, G_OBJECT_TYPE (self->widget));
if (signal_id != 0)
{
GSignalQuery query;
g_signal_query (signal_id, &query);
if (query.signal_flags & G_SIGNAL_ACTION)
{
do_activate (self, self->widget, &query, params);
return;
}
}
}
g_warning ("Failed to activate action %s due to missing widget or action",
action_name);
}
static gboolean
dzl_widget_action_group_query_action (GActionGroup *group,
const gchar *action_name,
gboolean *enabled,
const GVariantType **parameter_type,
const GVariantType **state_type,
GVariant **state_hint,
GVariant **state)
{
DzlWidgetActionGroup *self = (DzlWidgetActionGroup *)group;
g_assert (DZL_IS_WIDGET_ACTION_GROUP (group));
if (!GTK_IS_WIDGET (self->widget))
return FALSE;
if (!g_signal_lookup (action_name, G_OBJECT_TYPE (self->widget)))
return FALSE;
if (state_hint)
*state_hint = NULL;
if (state_type)
*state_type = NULL;
if (state)
*state = NULL;
if (parameter_type)
*parameter_type = dzl_widget_action_group_get_action_parameter_type (group, action_name);
if (enabled)
*enabled = dzl_widget_action_group_get_action_enabled (group, action_name);
return TRUE;
}
static void
action_group_iface_init (GActionGroupInterface *iface)
{
iface->has_action = dzl_widget_action_group_has_action;
iface->list_actions = dzl_widget_action_group_list_actions;
iface->get_action_enabled = dzl_widget_action_group_get_action_enabled;
iface->get_action_parameter_type = dzl_widget_action_group_get_action_parameter_type;
iface->get_action_state_type = dzl_widget_action_group_get_action_state_type;
iface->activate_action = dzl_widget_action_group_activate_action;
iface->query_action = dzl_widget_action_group_query_action;
}
/**
* dzl_widget_action_group_new:
*
* Returns: (transfer full): An #DzlWidgetActionGroup.
*/
GActionGroup *
dzl_widget_action_group_new (GtkWidget *widget)
{
return g_object_new (DZL_TYPE_WIDGET_ACTION_GROUP,
"widget", widget,
NULL);
}
/**
* dzl_widget_action_group_attach:
* @widget: (type Gtk.Widget): A #GtkWidget
* @group_name: the group name to use for the action group
*
* Helper function to create an #DzlWidgetActionGroup and attach
* it to @widget using the group name @group_name.
*/
void
dzl_widget_action_group_attach (gpointer widget,
const gchar *group_name)
{
GActionGroup *group;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (group_name != NULL);
group = dzl_widget_action_group_new (widget);
gtk_widget_insert_action_group (widget, group_name, group);
g_object_unref (group);
}
void
dzl_widget_action_group_set_action_enabled (DzlWidgetActionGroup *self,
const gchar *action_name,
gboolean enabled)
{
g_return_if_fail (DZL_IS_WIDGET_ACTION_GROUP (self));
g_return_if_fail (action_name != NULL);
g_return_if_fail (dzl_widget_action_group_has_action (G_ACTION_GROUP (self), action_name));
enabled = !!enabled;
if (self->enabled == NULL)
self->enabled = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_hash_table_insert (self->enabled, g_strdup (action_name), GINT_TO_POINTER (enabled));
g_action_group_action_enabled_changed (G_ACTION_GROUP (self), action_name, enabled);
g_debug ("Action %s %s", action_name, enabled ? "enabled" : "disabled");
}