diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 6ba53c0c56..2ad5b92fcc 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -5247,6 +5247,8 @@ gtk_binding_set_find gtk_bindings_activate gtk_bindings_activate_event gtk_binding_set_activate +gtk_binding_entry_add_action +gtk_binding_entry_add_action_variant gtk_binding_entry_add_signal gtk_binding_entry_add_signal_from_string gtk_binding_entry_skip diff --git a/gtk/gtkbindings.c b/gtk/gtkbindings.c index db9d6bd2a7..59c1815772 100644 --- a/gtk/gtkbindings.c +++ b/gtk/gtkbindings.c @@ -138,6 +138,12 @@ struct _GtkBindingArg } d; }; +typedef enum +{ + GTK_BINDING_SIGNAL, + GTK_BINDING_ACTION +} GtkBindingActionType; + /** * GtkBindingSignal: * @next: implementation detail @@ -151,10 +157,16 @@ struct _GtkBindingArg */ struct _GtkBindingSignal { - GtkBindingSignal *next; - gchar *signal_name; - guint n_args; - GtkBindingArg *args; + GtkBindingSignal *next; + gchar *signal_name; + GtkBindingActionType action_type; + union { + struct { + guint n_args; + GtkBindingArg *args; + }; + GVariant *variant; + }; }; /* --- variables --- */ @@ -168,13 +180,14 @@ static GQuark key_id_class_binding_set = 0; /* --- functions --- */ static GtkBindingSignal* -binding_signal_new (const gchar *signal_name, - guint n_args) +binding_signal_new_signal (const gchar *signal_name, + guint n_args) { GtkBindingSignal *signal; signal = (GtkBindingSignal *) g_slice_alloc0 (sizeof (GtkBindingSignal) + n_args * sizeof (GtkBindingArg)); signal->next = NULL; + signal->action_type = GTK_BINDING_SIGNAL; signal->signal_name = (gchar *)g_intern_string (signal_name); signal->n_args = n_args; signal->args = (GtkBindingArg *)(signal + 1); @@ -182,17 +195,48 @@ binding_signal_new (const gchar *signal_name, return signal; } +static GtkBindingSignal* +binding_signal_new_action (const gchar *signal_name, + GVariant *variant) +{ + GtkBindingSignal *signal; + + signal = g_slice_new0 (GtkBindingSignal); + signal->next = NULL; + signal->action_type = GTK_BINDING_ACTION; + signal->signal_name = (gchar *)g_intern_string (signal_name); + signal->variant = variant; + if (variant) + g_variant_ref_sink (variant); + + return signal; +} + static void binding_signal_free (GtkBindingSignal *sig) { guint i; - for (i = 0; i < sig->n_args; i++) + switch (sig->action_type) { - if (G_TYPE_FUNDAMENTAL (sig->args[i].arg_type) == G_TYPE_STRING) - g_free (sig->args[i].d.string_data); + case GTK_BINDING_SIGNAL: + for (i = 0; i < sig->n_args; i++) + { + if (G_TYPE_FUNDAMENTAL (sig->args[i].arg_type) == G_TYPE_STRING) + g_free (sig->args[i].d.string_data); + } + g_slice_free1 (sizeof (GtkBindingSignal) + sig->n_args * sizeof (GtkBindingArg), sig); + break; + + case GTK_BINDING_ACTION: + g_clear_pointer (&sig->variant, g_variant_unref); + g_slice_free (GtkBindingSignal, sig); + break; + + default: + g_assert_not_reached (); + break; } - g_slice_free1 (sizeof (GtkBindingSignal) + sig->n_args * sizeof (GtkBindingArg), sig); } static guint @@ -559,6 +603,97 @@ binding_compose_params (GObject *object, return valid; } +static gboolean +binding_signal_activate_signal (GtkBindingSignal *sig, + GObject *object) +{ + GSignalQuery query; + guint signal_id; + GValue *params = NULL; + GValue return_val = G_VALUE_INIT; + gboolean handled = FALSE; + + signal_id = g_signal_lookup (sig->signal_name, G_OBJECT_TYPE (object)); + if (!signal_id) + { + g_warning ("gtk_binding_entry_activate(): " + "could not find signal \"%s\" in the '%s' class ancestry", + sig->signal_name, + g_type_name (G_OBJECT_TYPE (object))); + return FALSE; + } + + g_signal_query (signal_id, &query); + if (query.n_params != sig->n_args || + (query.return_type != G_TYPE_NONE && query.return_type != G_TYPE_BOOLEAN) || + !binding_compose_params (object, sig->args, &query, ¶ms)) + { + g_warning ("gtk_binding_entry_activate(): " + "signature mismatch for signal \"%s\" in the '%s' class ancestry", + sig->signal_name, + g_type_name (G_OBJECT_TYPE (object))); + return FALSE; + } + else if (!(query.signal_flags & G_SIGNAL_ACTION)) + { + g_warning ("gtk_binding_entry_activate(): " + "signal \"%s\" in the '%s' class ancestry cannot be used for action emissions", + sig->signal_name, + g_type_name (G_OBJECT_TYPE (object))); + return FALSE; + } + + if (query.return_type == G_TYPE_BOOLEAN) + g_value_init (&return_val, G_TYPE_BOOLEAN); + + g_signal_emitv (params, signal_id, 0, &return_val); + + if (query.return_type == G_TYPE_BOOLEAN) + { + if (g_value_get_boolean (&return_val)) + handled = TRUE; + g_value_unset (&return_val); + } + else + handled = TRUE; + + if (params != NULL) + { + guint i; + + for (i = 0; i < query.n_params + 1; i++) + g_value_unset (¶ms[i]); + + g_free (params); + } + + return handled; +} + +static gboolean +binding_signal_activate_action (GtkBindingSignal *sig, + GObject *object) +{ + if (!GTK_IS_WIDGET (object)) + { + g_warning ("gtk_binding_entry_activate(): " + "actions must be emitted on GtkWidget subtypes, %s is not supported", + G_OBJECT_TYPE_NAME (object)); + return FALSE; + } + + if (!gtk_widget_activate_action_variant (GTK_WIDGET (object), sig->signal_name, sig->variant)) + { + g_warning ("gtk_binding_entry_activate(): " + "action \"%s\" does not exist on class \"%s\"", + sig->signal_name, + G_OBJECT_TYPE_NAME (object)); + return FALSE; + } + + return TRUE; +} + static gboolean gtk_binding_entry_activate (GtkBindingEntry *entry, GObject *object) @@ -566,7 +701,6 @@ gtk_binding_entry_activate (GtkBindingEntry *entry, GtkBindingSignal *sig; gboolean old_emission; gboolean handled = FALSE; - gint i; old_emission = entry->in_emission; entry->in_emission = TRUE; @@ -575,73 +709,19 @@ gtk_binding_entry_activate (GtkBindingEntry *entry, for (sig = entry->signals; sig; sig = sig->next) { - GSignalQuery query; - guint signal_id; - GValue *params = NULL; - GValue return_val = G_VALUE_INIT; - gchar *accelerator = NULL; - - signal_id = g_signal_lookup (sig->signal_name, G_OBJECT_TYPE (object)); - if (!signal_id) + switch (sig->action_type) { - accelerator = gtk_accelerator_name (entry->keyval, entry->modifiers); - g_warning ("gtk_binding_entry_activate(): binding \"%s::%s\": " - "could not find signal \"%s\" in the '%s' class ancestry", - entry->binding_set->set_name, - accelerator, - sig->signal_name, - g_type_name (G_OBJECT_TYPE (object))); - g_free (accelerator); - continue; - } + case GTK_BINDING_SIGNAL: + handled = binding_signal_activate_signal (sig, object); + break; - g_signal_query (signal_id, &query); - if (query.n_params != sig->n_args || - (query.return_type != G_TYPE_NONE && query.return_type != G_TYPE_BOOLEAN) || - !binding_compose_params (object, sig->args, &query, ¶ms)) - { - accelerator = gtk_accelerator_name (entry->keyval, entry->modifiers); - g_warning ("gtk_binding_entry_activate(): binding \"%s::%s\": " - "signature mismatch for signal \"%s\" in the '%s' class ancestry", - entry->binding_set->set_name, - accelerator, - sig->signal_name, - g_type_name (G_OBJECT_TYPE (object))); - } - else if (!(query.signal_flags & G_SIGNAL_ACTION)) - { - accelerator = gtk_accelerator_name (entry->keyval, entry->modifiers); - g_warning ("gtk_binding_entry_activate(): binding \"%s::%s\": " - "signal \"%s\" in the '%s' class ancestry cannot be used for action emissions", - entry->binding_set->set_name, - accelerator, - sig->signal_name, - g_type_name (G_OBJECT_TYPE (object))); - } - g_free (accelerator); - if (accelerator) - continue; + case GTK_BINDING_ACTION: + handled = binding_signal_activate_action (sig, object); + break; - if (query.return_type == G_TYPE_BOOLEAN) - g_value_init (&return_val, G_TYPE_BOOLEAN); - - g_signal_emitv (params, signal_id, 0, &return_val); - - if (query.return_type == G_TYPE_BOOLEAN) - { - if (g_value_get_boolean (&return_val)) - handled = TRUE; - g_value_unset (&return_val); - } - else - handled = TRUE; - - if (params != NULL) - { - for (i = 0; i < query.n_params + 1; i++) - g_value_unset (¶ms[i]); - - g_free (params); + default: + g_assert_not_reached (); + break; } if (entry->destroyed) @@ -889,7 +969,7 @@ gtk_binding_entry_add_signall (GtkBindingSet *binding_set, keyval = gdk_keyval_to_lower (keyval); modifiers = modifiers & BINDING_MOD_MASK (); - signal = binding_signal_new (signal_name, g_slist_length (binding_args)); + signal = binding_signal_new_signal (signal_name, g_slist_length (binding_args)); arg = signal->args; for (slist = binding_args; slist; slist = slist->next) @@ -1060,6 +1140,92 @@ gtk_binding_entry_add_signal (GtkBindingSet *binding_set, g_slist_free (free_slist); } +/** + * gtk_binding_entry_add_action_variant: + * @binding_set: a #GtkBindingSet to install an entry for + * @keyval: key value of binding to install + * @modifiers: key modifier of binding to install + * @action_name: signal to execute upon activation + * @args: #GVariant of the arguments or %NULL if none + * + * Override or install a new key binding for @keyval with @modifiers on + * @binding_set. When the binding is activated, @action_name will be + * activated on the target widget, with @args used as arguments. + */ +void +gtk_binding_entry_add_action_variant (GtkBindingSet *binding_set, + guint keyval, + GdkModifierType modifiers, + const gchar *action_name, + GVariant *args) +{ + GtkBindingEntry *entry; + GtkBindingSignal *signal, **signal_p; + + g_return_if_fail (binding_set != NULL); + g_return_if_fail (action_name != NULL); + + keyval = gdk_keyval_to_lower (keyval); + modifiers = modifiers & BINDING_MOD_MASK (); + + signal = binding_signal_new_action (action_name, args); + + entry = binding_ht_lookup_entry (binding_set, keyval, modifiers); + if (!entry) + { + gtk_binding_entry_clear_internal (binding_set, keyval, modifiers); + entry = binding_ht_lookup_entry (binding_set, keyval, modifiers); + } + signal_p = &entry->signals; + while (*signal_p) + signal_p = &(*signal_p)->next; + *signal_p = signal; +} + +/** + * gtk_binding_entry_add_action: + * @binding_set: a #GtkBindingSet to install an entry for + * @keyval: key value of binding to install + * @modifiers: key modifier of binding to install + * @action_name: signal to execute upon activation + * @format_string: GVariant format string for arguments or %NULL + * for no arguments + * @...: arguments, as given by format string + * + * Override or install a new key binding for @keyval with @modifiers on + * @binding_set. When the binding is activated, @action_name will be + * activated on the target widget, with arguments read according to + * @format_string. + */ +void +gtk_binding_entry_add_action (GtkBindingSet *binding_set, + guint keyval, + GdkModifierType modifiers, + const char *action_name, + const char *format_string, + ...) +{ + GVariant *parameters = NULL; + + g_return_if_fail (binding_set != NULL); + g_return_if_fail (action_name != NULL); + + if (format_string != NULL) + { + va_list args; + + va_start (args, format_string); + parameters = g_variant_new_va (format_string, NULL, &args); + va_end (args); + + g_variant_ref_sink (parameters); + } + + gtk_binding_entry_add_action_variant (binding_set, keyval, modifiers, action_name, parameters); + + g_clear_pointer (¶meters, g_variant_unref); +} + static guint gtk_binding_parse_signal (GScanner *scanner, GtkBindingSet *binding_set, diff --git a/gtk/gtkbindings.h b/gtk/gtkbindings.h index 887869f981..5f2b019006 100644 --- a/gtk/gtkbindings.h +++ b/gtk/gtkbindings.h @@ -76,6 +76,20 @@ GDK_AVAILABLE_IN_ALL GTokenType gtk_binding_entry_add_signal_from_string (GtkBindingSet *binding_set, const gchar *signal_desc); +GDK_AVAILABLE_IN_ALL +void gtk_binding_entry_add_action_variant + (GtkBindingSet *binding_set, + guint keyval, + GdkModifierType modifiers, + const char *action_name, + GVariant *args); +GDK_AVAILABLE_IN_ALL +void gtk_binding_entry_add_action (GtkBindingSet *binding_set, + guint keyval, + GdkModifierType modifiers, + const char *action_name, + const char *format_string, + ...); GDK_AVAILABLE_IN_ALL void gtk_binding_entry_remove (GtkBindingSet *binding_set,