forked from AuroraMiddleware/gtk
bd2f875141
This demo condenses the essentials of advanced management of input events. Depending on the information available in input events, this demo will try to represent as much information as possible for those. https://bugzilla.gnome.org/show_bug.cgi?id=719987
420 lines
9.9 KiB
C
420 lines
9.9 KiB
C
/* Event axes
|
|
*
|
|
* Demonstrates advanced handling of event information from exotic
|
|
* input devices.
|
|
*
|
|
* On one hand, this snippet demonstrates management of input axes,
|
|
* those contain additional information for the pointer other than
|
|
* X/Y coordinates.
|
|
*
|
|
* Input axes are dependent on hardware devices, on linux/unix you
|
|
* can see the device axes through xinput list <device>. Each time
|
|
* a different hardware device is used to move the pointer, the
|
|
* master device will be updated to match the axes it provides,
|
|
* these changes can be tracked through GdkDevice::changed, or
|
|
* checking gdk_event_get_source_device().
|
|
*
|
|
* On the other hand, this demo handles basic multitouch events,
|
|
* each event coming from an specific touchpoint will contain a
|
|
* GdkEventSequence that's unique for its lifetime, so multiple
|
|
* touchpoints can be tracked.
|
|
*/
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
typedef struct {
|
|
GdkDevice *last_source;
|
|
GHashTable *axes; /* axis label atom -> value */
|
|
GdkRGBA color;
|
|
gdouble x;
|
|
gdouble y;
|
|
} AxesInfo;
|
|
|
|
typedef struct {
|
|
AxesInfo *pointer_info;
|
|
GHashTable *touch_info; /* GdkEventSequence -> AxesInfo */
|
|
} EventData;
|
|
|
|
const gchar *colors[] = {
|
|
"black",
|
|
"orchid",
|
|
"fuchsia",
|
|
"indigo",
|
|
"thistle",
|
|
"sienna",
|
|
"azure",
|
|
"plum",
|
|
"lime",
|
|
"navy",
|
|
"maroon",
|
|
"burlywood"
|
|
};
|
|
|
|
static guint cur_color = 0;
|
|
|
|
static AxesInfo *
|
|
axes_info_new (void)
|
|
{
|
|
AxesInfo *info;
|
|
|
|
info = g_new0 (AxesInfo, 1);
|
|
gdk_rgba_parse (&info->color, colors[cur_color]);
|
|
info->axes = g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) g_free);
|
|
|
|
cur_color = (cur_color + 1) % G_N_ELEMENTS (colors);
|
|
|
|
return info;
|
|
}
|
|
|
|
static void
|
|
axes_info_free (AxesInfo *info)
|
|
{
|
|
g_hash_table_destroy (info->axes);
|
|
g_free (info);
|
|
}
|
|
|
|
static gboolean
|
|
axes_info_lookup (AxesInfo *info,
|
|
const gchar *axis_label,
|
|
gdouble *value)
|
|
{
|
|
gdouble *val;
|
|
GdkAtom atom;
|
|
|
|
atom = gdk_atom_intern (axis_label, FALSE);
|
|
|
|
if (atom == GDK_NONE)
|
|
return FALSE;
|
|
|
|
val = g_hash_table_lookup (info->axes, GDK_ATOM_TO_POINTER (atom));
|
|
|
|
if (!val)
|
|
return FALSE;
|
|
|
|
*value = *val;
|
|
return TRUE;
|
|
}
|
|
|
|
static EventData *
|
|
event_data_new (void)
|
|
{
|
|
EventData *data;
|
|
|
|
data = g_new0 (EventData, 1);
|
|
data->touch_info = g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) axes_info_free);
|
|
|
|
return data;
|
|
}
|
|
|
|
static void
|
|
event_data_free (EventData *data)
|
|
{
|
|
axes_info_free (data->pointer_info);
|
|
g_hash_table_destroy (data->touch_info);
|
|
g_free (data);
|
|
}
|
|
|
|
static void
|
|
update_axes_from_event (GdkEvent *event,
|
|
EventData *data)
|
|
{
|
|
GdkDevice *device, *source_device;
|
|
GdkEventSequence *sequence;
|
|
gdouble x, y, value;
|
|
GList *l, *axes;
|
|
AxesInfo *info;
|
|
|
|
device = gdk_event_get_device (event);
|
|
source_device = gdk_event_get_source_device (event);
|
|
sequence = gdk_event_get_event_sequence (event);
|
|
|
|
if (event->type == GDK_TOUCH_END)
|
|
{
|
|
g_hash_table_remove (data->touch_info, sequence);
|
|
return;
|
|
}
|
|
else if (event->type == GDK_LEAVE_NOTIFY)
|
|
{
|
|
if (data->pointer_info)
|
|
axes_info_free (data->pointer_info);
|
|
data->pointer_info = NULL;
|
|
return;
|
|
}
|
|
|
|
if (!sequence)
|
|
{
|
|
if (!data->pointer_info)
|
|
data->pointer_info = axes_info_new ();
|
|
info = data->pointer_info;
|
|
}
|
|
else
|
|
{
|
|
info = g_hash_table_lookup (data->touch_info, sequence);
|
|
|
|
if (!info)
|
|
{
|
|
info = axes_info_new ();
|
|
g_hash_table_insert (data->touch_info, sequence, info);
|
|
}
|
|
}
|
|
|
|
if (info->last_source != source_device)
|
|
{
|
|
g_hash_table_remove_all (info->axes);
|
|
info->last_source = source_device;
|
|
}
|
|
|
|
if (event->type == GDK_TOUCH_BEGIN ||
|
|
event->type == GDK_TOUCH_UPDATE ||
|
|
event->type == GDK_MOTION_NOTIFY ||
|
|
event->type == GDK_BUTTON_PRESS ||
|
|
event->type == GDK_BUTTON_RELEASE)
|
|
{
|
|
axes = gdk_device_list_axes (device);
|
|
|
|
if (sequence && event->touch.emulating_pointer)
|
|
{
|
|
if (data->pointer_info)
|
|
axes_info_free (data->pointer_info);
|
|
data->pointer_info = NULL;
|
|
}
|
|
|
|
for (l = axes; l; l = l->next)
|
|
{
|
|
gdouble *ptr;
|
|
|
|
/* All those event types are compatible wrt axes position in the struct */
|
|
if (!gdk_device_get_axis_value (device, event->motion.axes,
|
|
l->data, &value))
|
|
continue;
|
|
|
|
ptr = g_new0 (gdouble, 1);
|
|
*ptr = value;
|
|
g_hash_table_insert (info->axes, GDK_ATOM_TO_POINTER (l->data), ptr);
|
|
}
|
|
|
|
g_list_free (axes);
|
|
}
|
|
|
|
if (gdk_event_get_coords (event, &x, &y))
|
|
{
|
|
info->x = x;
|
|
info->y = y;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
event_cb (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
update_axes_from_event (event, user_data);
|
|
gtk_widget_queue_draw (widget);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
render_arrow (cairo_t *cr,
|
|
gdouble x_diff,
|
|
gdouble y_diff,
|
|
const gchar *label)
|
|
{
|
|
cairo_save (cr);
|
|
|
|
cairo_set_source_rgb (cr, 0, 0, 0);
|
|
cairo_new_path (cr);
|
|
cairo_move_to (cr, 0, 0);
|
|
cairo_line_to (cr, x_diff, y_diff);
|
|
cairo_stroke (cr);
|
|
|
|
cairo_move_to (cr, x_diff, y_diff);
|
|
cairo_show_text (cr, label);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
draw_axes_info (cairo_t *cr,
|
|
AxesInfo *info,
|
|
GtkAllocation *allocation)
|
|
{
|
|
gdouble pressure, tilt_x, tilt_y, wheel;
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_set_line_width (cr, 1);
|
|
gdk_cairo_set_source_rgba (cr, &info->color);
|
|
|
|
cairo_move_to (cr, 0, info->y);
|
|
cairo_line_to (cr, allocation->width, info->y);
|
|
cairo_move_to (cr, info->x, 0);
|
|
cairo_line_to (cr, info->x, allocation->height);
|
|
cairo_stroke (cr);
|
|
|
|
cairo_translate (cr, info->x, info->y);
|
|
|
|
if (axes_info_lookup (info, "Abs Pressure", &pressure))
|
|
{
|
|
cairo_pattern_t *pattern;
|
|
|
|
pattern = cairo_pattern_create_radial (0, 0, 0, 0, 0, 100);
|
|
cairo_pattern_add_color_stop_rgba (pattern, pressure, 1, 0, 0, pressure);
|
|
cairo_pattern_add_color_stop_rgba (pattern, 1, 0, 0, 1, 0);
|
|
|
|
cairo_set_source (cr, pattern);
|
|
|
|
cairo_arc (cr, 0, 0, 100, 0, 2 * G_PI);
|
|
cairo_fill (cr);
|
|
|
|
cairo_pattern_destroy (pattern);
|
|
}
|
|
|
|
if (axes_info_lookup (info, "Abs Tilt X", &tilt_x) &&
|
|
axes_info_lookup (info, "Abs Tilt Y", &tilt_y))
|
|
render_arrow (cr, tilt_x * 100, tilt_y * 100, "Tilt");
|
|
|
|
if (axes_info_lookup (info, "Abs Wheel", &wheel))
|
|
{
|
|
cairo_save (cr);
|
|
cairo_set_line_width (cr, 10);
|
|
cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
|
|
|
|
cairo_new_sub_path (cr);
|
|
cairo_arc (cr, 0, 0, 100, 0, wheel * 2 * G_PI);
|
|
cairo_stroke (cr);
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static void
|
|
draw_device_info (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
GdkEventSequence *sequence,
|
|
gint *y,
|
|
AxesInfo *info)
|
|
{
|
|
PangoLayout *layout;
|
|
GString *string;
|
|
gint height;
|
|
|
|
cairo_save (cr);
|
|
|
|
string = g_string_new (NULL);
|
|
g_string_append_printf (string, "Source: %s",
|
|
gdk_device_get_name (info->last_source));
|
|
|
|
if (sequence)
|
|
g_string_append_printf (string, "\nSequence: %d",
|
|
GPOINTER_TO_UINT (sequence));
|
|
|
|
cairo_move_to (cr, 10, *y);
|
|
layout = gtk_widget_create_pango_layout (widget, string->str);
|
|
pango_cairo_show_layout (cr, layout);
|
|
cairo_stroke (cr);
|
|
|
|
pango_layout_get_pixel_size (layout, NULL, &height);
|
|
|
|
gdk_cairo_set_source_rgba (cr, &info->color);
|
|
cairo_set_line_width (cr, 10);
|
|
cairo_move_to (cr, 0, *y);
|
|
|
|
*y = *y + height;
|
|
cairo_line_to (cr, 0, *y);
|
|
cairo_stroke (cr);
|
|
|
|
cairo_restore (cr);
|
|
|
|
g_object_unref (layout);
|
|
g_string_free (string, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
draw_cb (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gpointer user_data)
|
|
{
|
|
EventData *data = user_data;
|
|
GtkAllocation allocation;
|
|
AxesInfo *touch_info;
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
gint y = 0;
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
/* Draw Abs info */
|
|
if (data->pointer_info)
|
|
draw_axes_info (cr, data->pointer_info, &allocation);
|
|
|
|
g_hash_table_iter_init (&iter, data->touch_info);
|
|
|
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
|
{
|
|
touch_info = value;
|
|
draw_axes_info (cr, touch_info, &allocation);
|
|
}
|
|
|
|
/* Draw name, color legend and misc data */
|
|
if (data->pointer_info)
|
|
draw_device_info (widget, cr, NULL, &y, data->pointer_info);
|
|
|
|
g_hash_table_iter_init (&iter, data->touch_info);
|
|
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
touch_info = value;
|
|
draw_device_info (widget, cr, key, &y, touch_info);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
GtkWidget *
|
|
do_event_axes (GtkWidget *toplevel)
|
|
{
|
|
static GtkWidget *window = NULL;
|
|
EventData *event_data;
|
|
GtkWidget *box;
|
|
|
|
if (!window)
|
|
{
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
|
|
|
|
g_signal_connect (window, "destroy",
|
|
G_CALLBACK (gtk_widget_destroyed), &window);
|
|
|
|
box = gtk_event_box_new ();
|
|
gtk_container_add (GTK_CONTAINER (window), box);
|
|
gtk_widget_add_events (box,
|
|
GDK_POINTER_MOTION_MASK |
|
|
GDK_BUTTON_PRESS_MASK |
|
|
GDK_BUTTON_RELEASE_MASK |
|
|
GDK_SMOOTH_SCROLL_MASK |
|
|
GDK_TOUCH_MASK);
|
|
|
|
event_data = event_data_new ();
|
|
g_object_set_data_full (G_OBJECT (box), "gtk-demo-event-data",
|
|
event_data, (GDestroyNotify) event_data_free);
|
|
|
|
g_signal_connect (box, "event",
|
|
G_CALLBACK (event_cb), event_data);
|
|
g_signal_connect (box, "draw",
|
|
G_CALLBACK (draw_cb), event_data);
|
|
}
|
|
|
|
if (!gtk_widget_get_visible (window))
|
|
gtk_widget_show_all (window);
|
|
else
|
|
{
|
|
gtk_widget_destroy (window);
|
|
window = NULL;
|
|
}
|
|
|
|
return window;
|
|
}
|