From a0b9586465bc86c06105129208ab6eb4de8a357a Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Thu, 4 Aug 2016 19:22:34 +0200 Subject: [PATCH] gtk: Add GtkPadController This GdkEventController is a helper object to handle pad events, it allows setting a mapping to action names, to be triggered in the given action group. In order to help on places where advanced mapping/configurability of pad features is not desirable, this controller also allows passing a NULL pad device, meaning it will listen on all pads, and/or passing -1 on mode/index, so an action applies to all modes/features (eg. strips/rings). https://bugzilla.gnome.org/show_bug.cgi?id=770026 --- docs/reference/gtk/gtk-docs.sgml | 3 +- docs/reference/gtk/gtk3-sections.txt | 22 ++ docs/reference/gtk/gtk3.types.in | 1 + gtk/Makefile.am | 2 + gtk/gtk.h | 1 + gtk/gtkpadcontroller.c | 467 +++++++++++++++++++++++++++ gtk/gtkpadcontroller.h | 99 ++++++ 7 files changed, 594 insertions(+), 1 deletion(-) create mode 100644 gtk/gtkpadcontroller.c create mode 100644 gtk/gtkpadcontroller.h diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml index d7d96c3056..8c962aeb07 100644 --- a/docs/reference/gtk/gtk-docs.sgml +++ b/docs/reference/gtk/gtk-docs.sgml @@ -299,7 +299,7 @@ - Gestures + Gestures and event handling @@ -310,6 +310,7 @@ + diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index b1a81c71c1..98641e2551 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -8450,6 +8450,28 @@ GTK_GESTURE_ZOOM_GET_CLASS gtk_gesture_zoom_get_type +
+gtkpadcontroller +GtkPadController +GtkPadController +gtk_pad_controller_new +gtk_pad_controller_set_action_entries +gtk_pad_controller_set_action +GtkPadActionType +GtkPadActionEntry + + +GTK_TYPE_PAD_CONTROLLER +GTK_PAD_CONTROLLER +GTK_PAD_CONTROLLER_CLASS +GTK_IS_PAD_CONTROLLER +GTK_IS_PAD_CONTROLLER_CLASS +GTK_PAD_CONTROLLER_GET_CLASS + + +gtk_pad_controller_get_type +
+
gtkstacksidebar GtkStackSidebar diff --git a/docs/reference/gtk/gtk3.types.in b/docs/reference/gtk/gtk3.types.in index e6874c5c81..9f7d51234e 100644 --- a/docs/reference/gtk/gtk3.types.in +++ b/docs/reference/gtk/gtk3.types.in @@ -133,6 +133,7 @@ gtk_numerable_icon_get_type gtk_offscreen_window_get_type gtk_orientable_get_type gtk_overlay_get_type +gtk_pad_controller_get_type gtk_page_setup_get_type @DISABLE_ON_W32@gtk_page_setup_unix_dialog_get_type gtk_paned_get_type diff --git a/gtk/Makefile.am b/gtk/Makefile.am index ed6c224370..3b76b82db6 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -250,6 +250,7 @@ gtk_public_h_sources = \ gtkoffscreenwindow.h \ gtkorientable.h \ gtkoverlay.h \ + gtkpadcontroller.h \ gtkpagesetup.h \ gtkpaned.h \ gtkpapersize.h \ @@ -829,6 +830,7 @@ gtk_base_c_sources = \ gtkoffscreenwindow.c \ gtkorientable.c \ gtkoverlay.c \ + gtkpadcontroller.c \ gtkpagesetup.c \ gtkpaned.c \ gtkpango.c \ diff --git a/gtk/gtk.h b/gtk/gtk.h index c818f32e97..7b901e1ece 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -152,6 +152,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkpadcontroller.c b/gtk/gtkpadcontroller.c new file mode 100644 index 0000000000..dd91835f86 --- /dev/null +++ b/gtk/gtkpadcontroller.c @@ -0,0 +1,467 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2016, Red Hat, Inc. + * + * 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 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 . + * + * Author(s): Carlos Garnacho + */ + +/** + * SECTION:gtkpadcontroller + * @Short_description: Controller for drawing tablet pads + * @Title: GtkPadController + * @See_also: #GtkEventController, #GdkDevicePad + * + * #GtkPadController is an event controller for the pads found in drawing + * tablets (The collection of buttons and tactile sensors often found around + * the stylus-sensitive area). + * + * These buttons and sensors have no implicit meaning, and by default they + * perform no action, this event controller is provided to map those to + * #GAction objects, thus letting the application give those a more semantic + * meaning. + * + * Buttons and sensors are not constrained to triggering a single action, some + * %GDK_SOURCE_TABLET_PAD devices feature multiple "modes", all these input + * elements have one current mode, which may determine the final action + * being triggered. Pad devices often divide buttons and sensors into groups, + * all elements in a group share the same current mode, but different groups + * may have different modes. See gdk_device_pad_get_n_groups() and + * gdk_device_pad_get_group_n_modes(). + * + * Each of the actions that a given button/strip/ring performs for a given + * mode is defined by #GtkPadActionEntry, it contains an action name that + * will be looked up in the given #GActionGroup and activated whenever the + * specified input element and mode are triggered. + * + * A simple example of #GtkPadController usage, assigning button 1 in all + * modes and pad devices to an "invert-selection" action: + * |[ + * GtkPadActionEntry *pad_actions[] = { + * { GTK_PAD_ACTION_BUTTON, 1, -1, "Invert selection", "pad-actions.invert-selection" }, + * … + * }; + * + * … + * action_group = g_simple_action_group_new (); + * action = g_simple_action_new ("pad-actions.invert-selection", NULL); + * g_signal_connect (action, "activate", on_invert_selection_activated, NULL); + * g_action_map_add_action (G_ACTION_MAP (action_group), action); + * … + * pad_controller = gtk_pad_controller_new (window, action_group, NULL); + * ]| + */ + +#include "config.h" + +#include "gtkeventcontrollerprivate.h" +#include "gtkpadcontroller.h" +#include "gtkwindow.h" +#include "gtkprivate.h" +#include "gtkintl.h" + +#ifdef GDK_WINDOWING_WAYLAND +#include +#endif + +struct _GtkPadController { + GtkEventController parent_instance; + GActionGroup *action_group; + GdkDevice *pad; + + GList *entries; +}; + +struct _GtkPadControllerClass { + GtkEventControllerClass parent_class; +}; + +enum { + PROP_0, + PROP_ACTION_GROUP, + PROP_PAD, + N_PROPS +}; + +static GParamSpec *pspecs[N_PROPS] = { NULL }; + +G_DEFINE_TYPE (GtkPadController, gtk_pad_controller, GTK_TYPE_EVENT_CONTROLLER) + +static GtkPadActionEntry * +gtk_pad_action_entry_copy (const GtkPadActionEntry *entry) +{ + GtkPadActionEntry *copy; + + copy = g_slice_new0 (GtkPadActionEntry); + *copy = *entry; + copy->label = g_strdup (entry->label); + copy->action_name = g_strdup (entry->action_name); + + return copy; +} + +static void +gtk_pad_action_entry_free (GtkPadActionEntry *entry) +{ + g_free (entry->label); + g_free (entry->action_name); + g_slice_free (GtkPadActionEntry, entry); +} + +static const GtkPadActionEntry * +gtk_pad_action_find_match (GtkPadController *controller, + GtkPadActionType type, + gint index, + gint mode) +{ + GList *l; + + for (l = controller->entries; l; l = l->next) + { + GtkPadActionEntry *entry = l->data; + gboolean match_index = FALSE, match_mode = FALSE; + + if (entry->type != type) + continue; + + match_index = entry->index < 0 || entry->index == index; + match_mode = entry->mode < 0 || entry->mode == mode; + + if (match_index && match_mode) + return entry; + } + + return NULL; +} + +static void +gtk_pad_controller_activate_action (GtkPadController *controller, + const GtkPadActionEntry *entry) +{ + g_action_group_activate_action (controller->action_group, + entry->action_name, + NULL); +} + +static void +gtk_pad_controller_handle_mode_switch (GtkPadController *controller, + GdkDevice *pad, + guint group, + guint mode) +{ +} + +static gboolean +gtk_pad_controller_filter_event (GtkEventController *controller, + const GdkEvent *event) +{ + GtkPadController *pad_controller = GTK_PAD_CONTROLLER (controller); + + if (event->type != GDK_PAD_BUTTON_PRESS && + event->type != GDK_PAD_BUTTON_RELEASE && + event->type != GDK_PAD_RING && + event->type != GDK_PAD_STRIP && + event->type != GDK_PAD_GROUP_MODE) + return TRUE; + + if (pad_controller->pad && + gdk_event_get_source_device (event) != pad_controller->pad) + return TRUE; + + return FALSE; +} + +static gboolean +gtk_pad_controller_handle_event (GtkEventController *controller, + const GdkEvent *event) +{ + GtkPadController *pad_controller = GTK_PAD_CONTROLLER (controller); + const GtkPadActionEntry *entry; + GtkPadActionType type; + gint index, mode; + + if (event->type == GDK_PAD_GROUP_MODE) + { + gtk_pad_controller_handle_mode_switch (pad_controller, + gdk_event_get_source_device (event), + event->pad_group_mode.group, + event->pad_group_mode.mode); + return GDK_EVENT_PROPAGATE; + } + + switch (event->type) + { + case GDK_PAD_BUTTON_PRESS: + type = GTK_PAD_ACTION_BUTTON; + index = event->pad_button.button; + mode = event->pad_button.mode; + break; + case GDK_PAD_RING: + case GDK_PAD_STRIP: + type = event->type == GDK_PAD_RING ? + GTK_PAD_ACTION_RING : GTK_PAD_ACTION_STRIP; + index = event->pad_axis.index; + mode = event->pad_axis.mode; + break; + default: + return GDK_EVENT_PROPAGATE; + } + + entry = gtk_pad_action_find_match (pad_controller, + type, index, mode); + if (!entry) + return GDK_EVENT_PROPAGATE; + + gtk_pad_controller_activate_action (pad_controller, entry); + + return GDK_EVENT_STOP; +} + +static void +gtk_pad_controller_set_pad (GtkPadController *controller, + GdkDevice *pad) +{ + g_return_if_fail (!pad || GDK_IS_DEVICE (pad)); + g_return_if_fail (!pad || gdk_device_get_source (pad) == GDK_SOURCE_TABLET_PAD); + + g_set_object (&controller->pad, pad); +} + +static void +gtk_pad_controller_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkPadController *controller = GTK_PAD_CONTROLLER (object); + + switch (prop_id) + { + case PROP_ACTION_GROUP: + controller->action_group = g_value_dup_object (value); + break; + case PROP_PAD: + gtk_pad_controller_set_pad (controller, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_pad_controller_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkPadController *controller = GTK_PAD_CONTROLLER (object); + + switch (prop_id) + { + case PROP_ACTION_GROUP: + g_value_set_object (value, controller->action_group); + break; + case PROP_PAD: + g_value_set_object (value, controller->pad); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_pad_controller_dispose (GObject *object) +{ + GtkPadController *controller = GTK_PAD_CONTROLLER (object); + + g_clear_object (&controller->action_group); + g_clear_object (&controller->pad); + + G_OBJECT_CLASS (gtk_pad_controller_parent_class)->dispose (object); +} + +static void +gtk_pad_controller_finalize (GObject *object) +{ + GtkPadController *controller = GTK_PAD_CONTROLLER (object); + + g_list_free_full (controller->entries, (GDestroyNotify) gtk_pad_action_entry_free); + + G_OBJECT_CLASS (gtk_pad_controller_parent_class)->finalize (object); +} + +static void +gtk_pad_controller_class_init (GtkPadControllerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); + + controller_class->filter_event = gtk_pad_controller_filter_event; + controller_class->handle_event = gtk_pad_controller_handle_event; + + object_class->set_property = gtk_pad_controller_set_property; + object_class->get_property = gtk_pad_controller_get_property; + object_class->dispose = gtk_pad_controller_dispose; + object_class->finalize = gtk_pad_controller_finalize; + + pspecs[PROP_ACTION_GROUP] = + g_param_spec_object ("action-group", + P_("Action group"), + P_("Action group to launch actions from"), + G_TYPE_ACTION_GROUP, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + pspecs[PROP_PAD] = + g_param_spec_object ("pad", + P_("Pad device"), + P_("Pad device to control"), + GDK_TYPE_DEVICE, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, pspecs); +} + +static void +gtk_pad_controller_init (GtkPadController *controller) +{ +} + +/** + * gtk_pad_controller_new: + * @window: a #GtkWindow + * @group: #GActionGroup to trigger actions from + * @pad: (nullable): A %GDK_SOURCE_TABLET_PAD device, or %NULL to handle all pads + * + * Creates a new #GtkPadController that will associate events from @pad to + * actions. A %NULL pad may be provided so the controller manages all pad devices + * generically, it is discouraged to mix #GtkPadController objects with %NULL + * and non-%NULL @pad argument on the same @window, as execution order is not + * guaranteed. + * + * The #GtkPadController is created with no mapped actions. In order to map pad + * events to actions, use gtk_pad_controller_set_action_entries() or + * gtk_pad_controller_set_action(). + * + * Returns: A newly created #GtkPadController + * + * Since: 3.22 + **/ +GtkPadController * +gtk_pad_controller_new (GtkWindow *window, + GActionGroup *group, + GdkDevice *pad) +{ + g_return_val_if_fail (GTK_IS_WINDOW (window), NULL); + g_return_val_if_fail (G_IS_ACTION_GROUP (group), NULL); + g_return_val_if_fail (!pad || GDK_IS_DEVICE (pad), NULL); + g_return_val_if_fail (!pad || gdk_device_get_source (pad) == GDK_SOURCE_TABLET_PAD, NULL); + + return g_object_new (GTK_TYPE_PAD_CONTROLLER, + "propagation-phase", GTK_PHASE_CAPTURE, + "widget", window, + "action-group", group, + "pad", pad, + NULL); +} + +static gint +entry_compare_func (gconstpointer a, + gconstpointer b) +{ + const GtkPadActionEntry *entry1 = a, *entry2 = b; + + if (entry1->mode > entry2->mode) + return -1; + else if (entry1->mode < entry2->mode) + return 1; + else if (entry1->index > entry2->index) + return -1; + else if (entry1->index < entry2->index) + return 1; + + return 0; +} + +static void +gtk_pad_controller_add_entry (GtkPadController *controller, + const GtkPadActionEntry *entry) +{ + GtkPadActionEntry *copy; + + copy = gtk_pad_action_entry_copy (entry); + controller->entries = g_list_insert_sorted (controller->entries, copy, + (GCompareFunc) entry_compare_func); +} + +/** + * gtk_pad_controller_set_action_entries: + * @controller: a #GtkPadController + * @entries: (array length=n_entries): the action entries to set on @controller + * @n_entries: the number of elements in @entries + * + * This is a convenience function to add a group of action entries on + * @controller. See #GtkPadActionEntry and gtk_pad_controller_set_action(). + * + * Since: 3.22 + **/ +void +gtk_pad_controller_set_action_entries (GtkPadController *controller, + const GtkPadActionEntry *entries, + gint n_entries) +{ + gint i; + + g_return_if_fail (GTK_IS_PAD_CONTROLLER (controller)); + g_return_if_fail (entries != NULL); + + for (i = 0; i < n_entries; i++) + gtk_pad_controller_add_entry (controller, &entries[i]); +} + +/** + * gtk_pad_controller_set_action: + * @controller: a #GtkPadController + * @type: the type of pad feature that will trigger this action + * @index: the 0-indexed button/ring/strip number that will trigger this action + * @mode: the mode that will trigger this action, or -1 for all modes. + * @label: Human readable description of this action, this string should + * be deemed user-visible. + * @action_name: action name that will be activated in the #GActionGroup + * + * Adds an individual action to @controller. This action will only be activated + * if the given button/ring/strip number in @index is interacted while + * the current mode is @mode. -1 may be used for simple cases, so the action + * is triggered on all modes. + * + * The given @label should be considered user-visible, so internationalization + * rules apply. Some windowing systems may be able to use those for user + * feedback. + * + * Since: 3.22 + **/ +void +gtk_pad_controller_set_action (GtkPadController *controller, + GtkPadActionType type, + gint index, + gint mode, + const gchar *label, + const gchar *action_name) +{ + GtkPadActionEntry entry = { type, index, mode, + (gchar *) label, (gchar *) action_name }; + + g_return_if_fail (GTK_IS_PAD_CONTROLLER (controller)); + g_return_if_fail (type <= GTK_PAD_ACTION_RING); + + gtk_pad_controller_add_entry (controller, &entry); +} diff --git a/gtk/gtkpadcontroller.h b/gtk/gtkpadcontroller.h new file mode 100644 index 0000000000..f6a5f40b93 --- /dev/null +++ b/gtk/gtkpadcontroller.h @@ -0,0 +1,99 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2016, Red Hat, Inc. + * + * 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 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 . + * + * Author(s): Carlos Garnacho + */ + +#ifndef __GTK_PAD_CONTROLLER_H__ +#define __GTK_PAD_CONTROLLER_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_PAD_CONTROLLER (gtk_pad_controller_get_type ()) +#define GTK_PAD_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_PAD_CONTROLLER, GtkPadController)) +#define GTK_PAD_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_PAD_CONTROLLER, GtkPadControllerClass)) +#define GTK_IS_PAD_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_PAD_CONTROLLER)) +#define GTK_IS_PAD_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_PAD_CONTROLLER)) +#define GTK_PAD_CONTROLLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_PAD_CONTROLLER, GtkPadControllerClass)) + +typedef struct _GtkPadController GtkPadController; +typedef struct _GtkPadControllerClass GtkPadControllerClass; +typedef struct _GtkPadActionEntry GtkPadActionEntry; + +/** + * GtkPadActionType: + * @GTK_PAD_ACTION_BUTTON: Action is triggered by a pad button + * @GTK_PAD_ACTION_RING: Action is triggered by a pad ring + * @GTK_PAD_ACTION_STRIP: Action is triggered by a pad strip + * + * The type of a pad action. + */ +typedef enum { + GTK_PAD_ACTION_BUTTON, + GTK_PAD_ACTION_RING, + GTK_PAD_ACTION_STRIP +} GtkPadActionType; + +/** + * GtkPadActionEntry: + * @type: the type of pad feature that will trigger this action entry. + * @index: the 0-indexed button/ring/strip number that will trigger this action + * entry. + * @mode: the mode that will trigger this action entry, or -1 for all modes. + * @label: Human readable description of this action entry, this string should + * be deemed user-visible. + * @action_name: action name that will be activated in the #GActionGroup. + * + * Struct defining a pad action entry. + */ +struct _GtkPadActionEntry { + GtkPadActionType type; + gint index; + gint mode; + gchar *label; + gchar *action_name; +}; + +GDK_AVAILABLE_IN_3_22 +GType gtk_pad_controller_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_3_22 +GtkPadController *gtk_pad_controller_new (GtkWindow *window, + GActionGroup *group, + GdkDevice *pad); + +GDK_AVAILABLE_IN_3_22 +void gtk_pad_controller_set_action_entries (GtkPadController *controller, + const GtkPadActionEntry *entries, + gint n_entries); +GDK_AVAILABLE_IN_3_22 +void gtk_pad_controller_set_action (GtkPadController *controller, + GtkPadActionType type, + gint index, + gint mode, + const gchar *label, + const gchar *action_name); + +G_END_DECLS + +#endif /* __GTK_PAD_CONTROLLER_H__ */