diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index b66170816b..bea5b37ad2 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -339,6 +339,11 @@ + + Keyboard shortcuts + + + Data exchange, clipboards and Drag-and-Drop diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index f0fa4d3cc6..9355db617c 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -6063,6 +6063,30 @@ GTK_EVENT_CONTROLLER_MOTION_GET_CLASS gtk_event_controller_motion_get_type +
+gtkshortcut +GtkShortcut +GtkShortcut +gtk_shortcut_new +gtk_shortcut_set_keyval +gtk_shortcut_activate +gtk_shortcut_get_arguments +gtk_shortcut_set_arguments +gtk_shortcut_get_signal +gtk_shortcut_set_signal + + +GTK_TYPE_SHORTCUT +GTK_SHORTCUT +GTK_SHORTCUT_CLASS +GTK_IS_SHORTCUT +GTK_IS_SHORTCUT_CLASS +GTK_SHORTCUT_GET_CLASS + + +gtk_shortcut_get_type +
+
gtkshortcutcontroller GtkShortcutController diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index d3d45fb013..19995bc504 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -165,6 +165,7 @@ gtk_search_entry_get_type gtk_selection_model_get_type gtk_separator_get_type gtk_settings_get_type +gtk_shortcut_get_type gtk_shortcut_controller_get_type gtk_shortcut_label_get_type gtk_shortcuts_window_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 4304d6888a..09906c5337 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -198,6 +198,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkshortcut.c b/gtk/gtkshortcut.c new file mode 100644 index 0000000000..48c021561a --- /dev/null +++ b/gtk/gtkshortcut.c @@ -0,0 +1,317 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * 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.1 of the License, 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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gtkshortcut.h" + +#include "gtkbindingsprivate.h" +#include "gtkintl.h" +#include "gtkwidget.h" + +/** + * SECTION:gtkshortcut + * @title: GtkShortcut + * @short_description: An object describing a keyboard shortcut + * @see_also: #GtkShortcutController, #GtkShortcutTrigger + * + * GtkShortcut is the low level object used for managing keyboard + * shortcuts. + * + * It contains a description of how to trigger the shortcut via a + * #GtkShortcutTrigger and a way to activate the shortcut on a widget + * with gtk_shortcut_activate(). + * + * The actual work is usually done via #GtkShortcutController, which + * decides if and when to activate a shortcut. Using that controller + * directly however is rarely necessary as Various higher level + * convenience APIs exist on #GtkWidgets that make it easier to use + * shortcuts in GTK. + * + * #GtkShortcut does provide functionality to make it easy for users + * to work with shortcuts, either by providing informational strings + * for display purposes or by allowing shortcuts to be configured. + */ + +struct _GtkShortcut +{ + GObject parent_instance; + + GdkModifierType mods; + guint keyval; + + char *signal; + GVariant *args; +}; + +enum +{ + PROP_0, + PROP_ARGUMENTS, + PROP_SIGNAL, + + N_PROPS +}; + +G_DEFINE_TYPE (GtkShortcut, gtk_shortcut, G_TYPE_OBJECT) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gtk_shortcut_dispose (GObject *object) +{ + GtkShortcut *self = GTK_SHORTCUT (object); + + g_clear_pointer (&self->signal, g_free); + g_clear_pointer (&self->args, g_variant_unref); + + G_OBJECT_CLASS (gtk_shortcut_parent_class)->dispose (object); +} + +static void +gtk_shortcut_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkShortcut *self = GTK_SHORTCUT (object); + + switch (property_id) + { + case PROP_ARGUMENTS: + g_value_set_variant (value, self->args); + break; + + case PROP_SIGNAL: + g_value_set_string (value, self->signal); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_shortcut_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkShortcut *self = GTK_SHORTCUT (object); + + switch (property_id) + { + case PROP_ARGUMENTS: + gtk_shortcut_set_arguments (self, g_value_get_variant (value)); + break; + + case PROP_SIGNAL: + gtk_shortcut_set_signal (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_shortcut_class_init (GtkShortcutClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = gtk_shortcut_dispose; + gobject_class->get_property = gtk_shortcut_get_property; + gobject_class->set_property = gtk_shortcut_set_property; + + /** + * GtkShortcut:arguments: + * + * Arguments passed to activation. + */ + properties[PROP_ARGUMENTS] = + g_param_spec_variant ("arguments", + P_("Arguments"), + P_("Arguments passed to activation"), + G_VARIANT_TYPE_ANY, + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkShortcut:signal: + * + * The action signal to emit on the widget upon activation. + */ + properties[PROP_SIGNAL] = + g_param_spec_string ("signal", + P_("Signal"), + P_("The action signal to emit"), + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +gtk_shortcut_init (GtkShortcut *self) +{ +} + +/** + * gtk_shortcut_new: + * + * Creates a new empty #GtkShortcut that never triggers and activates nothing. + * + * Returns: a new #GtkShortcut + **/ +GtkShortcut * +gtk_shortcut_new (void) +{ + return g_object_new (GTK_TYPE_SHORTCUT, NULL); +} + +void +gtk_shortcut_set_keyval (GtkShortcut *self, + guint keyval, + GdkModifierType mods) +{ + g_return_if_fail (GTK_IS_SHORTCUT (self)); + + /* To deal with , we need to uppercase + * or lowercase depending on situation. + */ + if (mods & GDK_SHIFT_MASK) + { + if (keyval == GDK_KEY_Tab) + keyval = GDK_KEY_ISO_Left_Tab; + else + keyval = gdk_keyval_to_upper (keyval); + } + else + { + if (keyval == GDK_KEY_ISO_Left_Tab) + keyval = GDK_KEY_Tab; + else + keyval = gdk_keyval_to_lower (keyval); + } + + self->keyval = keyval; + self->mods = mods; +} + +gboolean +gtk_shortcut_trigger (GtkShortcut *self, + GdkEvent *event) +{ + GdkModifierType mods; + guint keyval; + + if (gdk_event_get_event_type (event) != GDK_KEY_PRESS) + return FALSE; + + /* XXX: This needs to deal with groups */ + mods = gdk_event_get_modifier_state (event); + keyval = gdk_key_event_get_keyval (event); + + return keyval == self->keyval && mods == self->mods; +} + +gboolean +gtk_shortcut_activate (GtkShortcut *self, + GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_SHORTCUT (self), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + if (self->signal) + { + GError *error = NULL; + gboolean handled; + + if (!gtk_binding_emit_signal (G_OBJECT (widget), + self->signal, + self->args, + &handled, + &error)) + { + char *accelerator = gtk_accelerator_name (self->keyval, self->mods); + g_warning ("gtk_shortcut_activate(): \":%s\": %s", + accelerator, + error->message); + g_clear_error (&error); + return FALSE; + } + + return handled; + } + else + { + /* shortcut is a dud */ + return FALSE; + } +} + +GVariant * +gtk_shortcut_get_arguments (GtkShortcut *self) +{ + g_return_val_if_fail (GTK_IS_SHORTCUT (self), NULL); + + return self->args; +} + +void +gtk_shortcut_set_arguments (GtkShortcut *self, + GVariant *args) +{ + g_return_if_fail (GTK_IS_SHORTCUT (self)); + + if (self->args == args) + return; + + g_clear_pointer (&self->args, g_variant_unref); + if (args) + self->args = g_variant_ref_sink (args); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ARGUMENTS]); +} + +const char * +gtk_shortcut_get_signal (GtkShortcut *self) +{ + g_return_val_if_fail (GTK_IS_SHORTCUT (self), NULL); + + return self->signal; +} + +void +gtk_shortcut_set_signal (GtkShortcut *self, + const gchar *signal) +{ + g_return_if_fail (GTK_IS_SHORTCUT (self)); + + if (g_strcmp0 (self->signal, signal) == 0) + return; + + g_clear_pointer (&self->signal, g_free); + self->signal = g_strdup (signal); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIGNAL]); +} + diff --git a/gtk/gtkshortcut.h b/gtk/gtkshortcut.h new file mode 100644 index 0000000000..074695b932 --- /dev/null +++ b/gtk/gtkshortcut.h @@ -0,0 +1,60 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * 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.1 of the License, 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 . + * + * Authors: Benjamin Otte + */ + +#ifndef __GTK_SHORTCUT_H__ +#define __GTK_SHORTCUT_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_SHORTCUT (gtk_shortcut_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkShortcut, gtk_shortcut, GTK, SHORTCUT, GObject) + +GDK_AVAILABLE_IN_ALL +GtkShortcut * gtk_shortcut_new (void); + +GDK_AVAILABLE_IN_ALL +void gtk_shortcut_set_keyval (GtkShortcut *self, + guint keyval, + GdkModifierType mods); + +GDK_AVAILABLE_IN_ALL +gboolean gtk_shortcut_trigger (GtkShortcut *self, + GdkEvent *event); +GDK_AVAILABLE_IN_ALL +gboolean gtk_shortcut_activate (GtkShortcut *self, + GtkWidget *widget); + +GDK_AVAILABLE_IN_ALL +GVariant * gtk_shortcut_get_arguments (GtkShortcut *self); +GDK_AVAILABLE_IN_ALL +void gtk_shortcut_set_arguments (GtkShortcut *self, + GVariant *args); +GDK_AVAILABLE_IN_ALL +const char * gtk_shortcut_get_signal (GtkShortcut *self); +GDK_AVAILABLE_IN_ALL +void gtk_shortcut_set_signal (GtkShortcut *self, + const gchar *signal); + +G_END_DECLS + +#endif /* __GTK_SHORTCUT_H__ */ diff --git a/gtk/gtktypes.h b/gtk/gtktypes.h index a5ad190720..0b5869bb02 100644 --- a/gtk/gtktypes.h +++ b/gtk/gtktypes.h @@ -45,6 +45,7 @@ typedef struct _GtkNative GtkNative; typedef struct _GtkRequisition GtkRequisition; typedef struct _GtkRoot GtkRoot; typedef struct _GtkSettings GtkSettings; +typedef struct _GtkShortcut GtkShortcut; typedef GdkSnapshot GtkSnapshot; typedef struct _GtkStyleContext GtkStyleContext; typedef struct _GtkTooltip GtkTooltip; diff --git a/gtk/meson.build b/gtk/meson.build index b58180f032..f155fa75ce 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -331,6 +331,7 @@ gtk_public_sources = files([ 'gtkselectionmodel.c', 'gtkseparator.c', 'gtksettings.c', + 'gtkshortcut.c', 'gtkshortcutcontroller.c', 'gtkshortcutlabel.c', 'gtkshortcutsgroup.c', @@ -569,6 +570,7 @@ gtk_public_headers = files([ 'gtkselectionmodel.h', 'gtkseparator.h', 'gtksettings.h', + 'gtkshortcut.h', 'gtkshortcutcontroller.h', 'gtkshortcutlabel.h', 'gtkshortcutsgroup.h',