/* 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 < http : //www.gnu.org/licenses/>.
*
* Author ( s ) : Carlos Garnacho < carlosg @ gnome . org >
*/
/**
* GtkPadController :
*
* Handles input from the pads found in drawing tablets .
*
* Pads are 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 . ` GtkPadController ` is provided to map those to
* [ iface @ Gio . Action ] objects , thus letting the application give them 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 [ method @ Gdk . DevicePad . get_n_groups ] and
* [ method @ Gdk . DevicePad . get_group_n_modes ] .
*
* Each of the actions that a given button / strip / ring performs for a given mode
* is defined by a [ struct @ Gtk . PadActionEntry ] . It contains an action name that
* will be looked up in the given [ iface @ Gio . ActionGroup ] 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 :
*
* ` ` ` c
* 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 ( action_group , NULL ) ;
* ` ` `
*
* The actions belonging to rings / strips / dials will be activated with a parameter
* of type % G_VARIANT_TYPE_DOUBLE bearing the value of the given axis , it
* is required that those are made stateful and accepting this ` GVariantType ` .
* For rings the value is the angle of the ring position in degrees with 0
* facing up . For strips the value is the absolute position on the strip , normalized
* to the [ 0 . 0 , 1 . 0 ] range .
* For dials the value is the relative movement of the dial , normalized so that the
* value 120 represents one logical scroll wheel detent in the positive direction .
* Devices that support high - resolution scrolling may send events with fractions of
* 120 to signify a smaller motion .
*/
#include "config.h"
#include "gtkeventcontrollerprivate.h"
#include "gtkpadcontroller.h"
#include "gtkwindow.h"
#include "gtkprivate.h"
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/wayland/gdkwayland.h>
#include "gdk/wayland/gdkdevice-wayland-private.h"
#endif
struct _GtkPadController {
GtkEventController parent_instance;
GActionGroup *action_group;
GdkDevice *pad;
GArray *action_entries;
};
struct _GtkPadControllerClass {
GtkEventControllerClass parent_class;
};
enum {
PROP_0,
PROP_ACTION_GROUP,
PROP_PAD,
N_PROPS
};
typedef struct
{
GtkPadActionType type;
int index;
int mode;
char *label;
char *action_name;
} ActionEntryData;
static GParamSpec *pspecs[N_PROPS] = { NULL };
G_DEFINE_TYPE (GtkPadController, gtk_pad_controller, GTK_TYPE_EVENT_CONTROLLER)
static const ActionEntryData *
gtk_pad_action_find_match (GtkPadController *controller,
GtkPadActionType type,
int index,
int mode)
{
guint i;
for (i = 0 ; i < controller->action_entries->len; i++)
{
const ActionEntryData *entry = &g_array_index (controller->action_entries,
ActionEntryData, i);
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 ActionEntryData *entry)
{
g_action_group_activate_action (controller->action_group,
entry->action_name,
NULL);
}
static void
gtk_pad_controller_activate_action_with_axis (GtkPadController *controller,
const ActionEntryData *entry,
double value)
{
g_action_group_activate_action (controller->action_group,
entry->action_name,
g_variant_new_double (value));
}
static void
gtk_pad_controller_handle_mode_switch (GtkPadController *controller,
GdkDevice *pad,
guint group,
guint mode)
{
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gdk_device_get_display (pad)))
{
const ActionEntryData *entry;
int elem, idx, n_features;
for (elem = GTK_PAD_ACTION_BUTTON; elem <= GTK_PAD_ACTION_STRIP; elem++)
{
n_features = gdk_device_pad_get_n_features (GDK_DEVICE_PAD (pad),
elem);
for (idx = 0 ; idx < n_features; idx++)
{
if (gdk_device_pad_get_feature_group (GDK_DEVICE_PAD (pad),
elem, idx) != group)
continue ;
entry = gtk_pad_action_find_match (controller, elem, idx, mode);
if (!entry)
continue ;
if (!g_action_group_has_action (controller->action_group,
entry->action_name))
continue ;
gdk_wayland_device_pad_set_feedback (pad, elem, idx,
g_dgettext (NULL, entry->label));
}
}
}
#endif
}
static gboolean
gtk_pad_controller_filter_event (GtkEventController *controller,
GdkEvent *event)
{
GtkPadController *pad_controller = GTK_PAD_CONTROLLER (controller);
GdkEventType event_type = gdk_event_get_event_type (event);
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_DIAL &&
event_type != GDK_PAD_GROUP_MODE)
return TRUE ;
if (pad_controller->pad &&
gdk_event_get_device (event) != pad_controller->pad)
return TRUE ;
return FALSE ;
}
static gboolean
gtk_pad_controller_handle_event (GtkEventController *controller,
GdkEvent *event,
double x,
double y)
{
GtkPadController *pad_controller = GTK_PAD_CONTROLLER (controller);
GdkEventType event_type = gdk_event_get_event_type (event);
const ActionEntryData *entry;
GtkPadActionType type;
guint index, mode, group;
double value = 0 ;
gdk_pad_event_get_group_mode (event, &group, &mode);
if (event_type == GDK_PAD_GROUP_MODE)
{
gtk_pad_controller_handle_mode_switch (pad_controller,
gdk_event_get_device (event),
group,
mode);
return GDK_EVENT_PROPAGATE;
}
switch ((guint) event_type)
{
case GDK_PAD_BUTTON_PRESS:
type = GTK_PAD_ACTION_BUTTON;
index = gdk_pad_event_get_button (event);
break ;
case GDK_PAD_RING:
type = GTK_PAD_ACTION_RING;
gdk_pad_event_get_axis_value (event, &index, &value);
break ;
case GDK_PAD_STRIP:
type = GTK_PAD_ACTION_STRIP;
gdk_pad_event_get_axis_value (event, &index, &value);
break ;
case GDK_PAD_DIAL:
type = GTK_PAD_ACTION_DIAL;
gdk_pad_event_get_axis_value (event, &index, &value);
break ;
default :
return GDK_EVENT_PROPAGATE;
}
entry = gtk_pad_action_find_match (pad_controller,
type, index, mode);
if (!entry)
return GDK_EVENT_PROPAGATE;
if (event_type == GDK_PAD_RING || event_type == GDK_PAD_STRIP || event_type == GDK_PAD_DIAL)
gtk_pad_controller_activate_action_with_axis (pad_controller, entry, value);
else
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);
guint i;
for (i = 0 ; i < controller->action_entries->len; i++)
{
const ActionEntryData *entry = &g_array_index (controller->action_entries,
ActionEntryData, i);
g_free (entry->label);
g_free (entry->action_name);
}
g_array_free (controller->action_entries, TRUE );
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;
/**
* GtkPadController : action - group :
*
* The action group of the controller .
*/
pspecs[PROP_ACTION_GROUP] =
g_param_spec_object ("action-group" , NULL, NULL,
G_TYPE_ACTION_GROUP,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_CONSTRUCT_ONLY);
/**
* GtkPadController : pad :
*
* The pad of the controller .
*/
pspecs[PROP_PAD] =
g_param_spec_object ("pad" , NULL, NULL,
GDK_TYPE_DEVICE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_properties (object_class, N_PROPS, pspecs);
}
static void
gtk_pad_controller_init (GtkPadController *controller)
{
controller->action_entries = g_array_new (FALSE , TRUE , sizeof (ActionEntryData));
}
/**
* gtk_pad_controller_new :
* @ 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 toplevel window , as execution
* order is not guaranteed .
*
* The ` GtkPadController ` is created with no mapped actions . In order to
* map pad events to actions , use [ method @ Gtk . PadController . set_action_entries ]
* or [ method @ Gtk . PadController . set_action ] .
*
* Be aware that pad events will only be delivered to ` GtkWindow ` s , so adding
* a pad controller to any other type of widget will not have an effect .
*
* Returns : A newly created ` GtkPadController `
*/
GtkPadController *
gtk_pad_controller_new (GActionGroup *group,
GdkDevice *pad)
{
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,
"action-group" , group,
"pad" , pad,
NULL);
}
static int
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)
{
guint i;
const ActionEntryData new_entry = {
.type = entry->type,
.index = entry->index,
.mode = entry->mode,
.label= g_strdup (entry->label),
.action_name = g_strdup (entry->action_name)
};
for (i = 0 ; i < controller->action_entries->len; i++)
{
if (entry_compare_func (&new_entry,
&g_array_index (controller->action_entries, ActionEntryData, i)) == 0 )
break ;
}
g_array_insert_val (controller->action_entries, i, new_entry);
}
/**
* 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
*
* A convenience function to add a group of action entries on
* @ controller .
*
* See [ struct @ Gtk . PadActionEntry ] and [ method @ Gtk . PadController . set_action ] .
*/
void
gtk_pad_controller_set_action_entries (GtkPadController *controller,
const GtkPadActionEntry *entries,
int n_entries)
{
int 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 .
*/
void
gtk_pad_controller_set_action (GtkPadController *controller,
GtkPadActionType type,
int index,
int mode,
const char *label,
const char *action_name)
{
const GtkPadActionEntry entry = { type, index, mode, label, action_name };
g_return_if_fail (GTK_IS_PAD_CONTROLLER (controller));
g_return_if_fail (type <= GTK_PAD_ACTION_STRIP);
gtk_pad_controller_add_entry (controller, &entry);
}
Messung V0.5 in Prozent C=98 H=95 G=96
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland