forked from AuroraMiddleware/gtk
536 lines
14 KiB
C
536 lines
14 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;
|
|
GdkDeviceTool *last_tool;
|
|
gdouble *axes;
|
|
GdkRGBA color;
|
|
gdouble x;
|
|
gdouble y;
|
|
} AxesInfo;
|
|
|
|
typedef struct {
|
|
GHashTable *pointer_info; /* GdkDevice -> AxesInfo */
|
|
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]);
|
|
|
|
cur_color = (cur_color + 1) % G_N_ELEMENTS (colors);
|
|
|
|
return info;
|
|
}
|
|
|
|
static EventData *
|
|
event_data_new (void)
|
|
{
|
|
EventData *data;
|
|
|
|
data = g_new0 (EventData, 1);
|
|
data->pointer_info = g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) g_free);
|
|
data->touch_info = g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) g_free);
|
|
|
|
return data;
|
|
}
|
|
|
|
static void
|
|
event_data_free (EventData *data)
|
|
{
|
|
g_hash_table_destroy (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;
|
|
GdkDeviceTool *tool;
|
|
gdouble x, y;
|
|
AxesInfo *info;
|
|
|
|
device = gdk_event_get_device (event);
|
|
source_device = gdk_event_get_source_device (event);
|
|
sequence = gdk_event_get_event_sequence (event);
|
|
tool = gdk_event_get_device_tool (event);
|
|
|
|
if (event->type == GDK_TOUCH_END ||
|
|
event->type == GDK_TOUCH_CANCEL)
|
|
{
|
|
g_hash_table_remove (data->touch_info, sequence);
|
|
return;
|
|
}
|
|
else if (event->type == GDK_LEAVE_NOTIFY)
|
|
{
|
|
g_hash_table_remove (data->pointer_info, device);
|
|
return;
|
|
}
|
|
|
|
if (!sequence)
|
|
{
|
|
info = g_hash_table_lookup (data->pointer_info, device);
|
|
|
|
if (!info)
|
|
{
|
|
info = axes_info_new ();
|
|
g_hash_table_insert (data->pointer_info, device, 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)
|
|
info->last_source = source_device;
|
|
|
|
if (info->last_tool != tool)
|
|
info->last_tool = tool;
|
|
|
|
g_clear_pointer (&info->axes, g_free);
|
|
|
|
if (event->type == GDK_TOUCH_BEGIN ||
|
|
event->type == GDK_TOUCH_UPDATE)
|
|
{
|
|
if (sequence && event->touch.emulating_pointer)
|
|
g_hash_table_remove (data->pointer_info, device);
|
|
}
|
|
if (event->type == GDK_MOTION_NOTIFY)
|
|
{
|
|
info->axes =
|
|
g_memdup (event->motion.axes,
|
|
sizeof (gdouble) * gdk_device_get_n_axes (source_device));
|
|
}
|
|
else if (event->type == GDK_BUTTON_PRESS ||
|
|
event->type == GDK_BUTTON_RELEASE)
|
|
{
|
|
info->axes =
|
|
g_memdup (event->button.axes,
|
|
sizeof (gdouble) * gdk_device_get_n_axes (source_device));
|
|
}
|
|
|
|
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, distance, wheel, rotation, slider;
|
|
GdkAxisFlags axes = gdk_device_get_axes (info->last_source);
|
|
|
|
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 (!info->axes)
|
|
{
|
|
cairo_restore (cr);
|
|
return;
|
|
}
|
|
|
|
if (axes & GDK_AXIS_FLAG_PRESSURE)
|
|
{
|
|
cairo_pattern_t *pattern;
|
|
|
|
gdk_device_get_axis (info->last_source, info->axes, GDK_AXIS_PRESSURE,
|
|
&pressure);
|
|
|
|
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 & GDK_AXIS_FLAG_XTILT &&
|
|
axes & GDK_AXIS_FLAG_YTILT)
|
|
{
|
|
gdk_device_get_axis (info->last_source, info->axes, GDK_AXIS_XTILT,
|
|
&tilt_x);
|
|
gdk_device_get_axis (info->last_source, info->axes, GDK_AXIS_YTILT,
|
|
&tilt_y);
|
|
|
|
render_arrow (cr, tilt_x * 100, tilt_y * 100, "Tilt");
|
|
}
|
|
|
|
if (axes & GDK_AXIS_FLAG_DISTANCE)
|
|
{
|
|
double dashes[] = { 5.0, 5.0 };
|
|
cairo_text_extents_t extents;
|
|
|
|
gdk_device_get_axis (info->last_source, info->axes, GDK_AXIS_DISTANCE,
|
|
&distance);
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_move_to (cr, distance * 100, 0);
|
|
|
|
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
|
|
cairo_set_dash (cr, dashes, 2, 0.0);
|
|
cairo_arc (cr, 0, 0, distance * 100, 0, 2 * G_PI);
|
|
cairo_stroke (cr);
|
|
|
|
cairo_move_to (cr, 0, -distance * 100);
|
|
cairo_text_extents (cr, "Distance", &extents);
|
|
cairo_rel_move_to (cr, -extents.width / 2, 0);
|
|
cairo_show_text (cr, "Distance");
|
|
|
|
cairo_move_to (cr, 0, 0);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
if (axes & GDK_AXIS_FLAG_WHEEL)
|
|
{
|
|
gdk_device_get_axis (info->last_source, info->axes, GDK_AXIS_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);
|
|
}
|
|
|
|
if (axes & GDK_AXIS_FLAG_ROTATION)
|
|
{
|
|
gdk_device_get_axis (info->last_source, info->axes, GDK_AXIS_ROTATION,
|
|
&rotation);
|
|
rotation *= 2 * G_PI;
|
|
|
|
cairo_save (cr);
|
|
cairo_rotate (cr, - G_PI / 2);
|
|
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
|
|
cairo_set_line_width (cr, 5);
|
|
|
|
cairo_new_sub_path (cr);
|
|
cairo_arc (cr, 0, 0, 100, 0, rotation);
|
|
cairo_stroke (cr);
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
if (axes & GDK_AXIS_FLAG_SLIDER)
|
|
{
|
|
cairo_pattern_t *pattern, *mask;
|
|
|
|
gdk_device_get_axis (info->last_source, info->axes, GDK_AXIS_SLIDER,
|
|
&slider);
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_move_to (cr, 0, -10);
|
|
cairo_rel_line_to (cr, 0, -50);
|
|
cairo_rel_line_to (cr, 10, 0);
|
|
cairo_rel_line_to (cr, -5, 50);
|
|
cairo_close_path (cr);
|
|
|
|
cairo_clip_preserve (cr);
|
|
|
|
pattern = cairo_pattern_create_linear (0, -10, 0, -60);
|
|
cairo_pattern_add_color_stop_rgb (pattern, 0, 0, 1, 0);
|
|
cairo_pattern_add_color_stop_rgb (pattern, 1, 1, 0, 0);
|
|
cairo_set_source (cr, pattern);
|
|
cairo_pattern_destroy (pattern);
|
|
|
|
mask = cairo_pattern_create_linear (0, -10, 0, -60);
|
|
cairo_pattern_add_color_stop_rgba (mask, 0, 0, 0, 0, 1);
|
|
cairo_pattern_add_color_stop_rgba (mask, slider, 0, 0, 0, 1);
|
|
cairo_pattern_add_color_stop_rgba (mask, slider, 0, 0, 0, 0);
|
|
cairo_pattern_add_color_stop_rgba (mask, 1, 0, 0, 0, 0);
|
|
cairo_mask (cr, mask);
|
|
cairo_pattern_destroy (mask);
|
|
|
|
cairo_set_source_rgb (cr, 0, 0, 0);
|
|
cairo_stroke (cr);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
static const gchar *
|
|
tool_type_to_string (GdkDeviceToolType tool_type)
|
|
{
|
|
switch (tool_type)
|
|
{
|
|
case GDK_DEVICE_TOOL_TYPE_PEN:
|
|
return "Pen";
|
|
case GDK_DEVICE_TOOL_TYPE_ERASER:
|
|
return "Eraser";
|
|
case GDK_DEVICE_TOOL_TYPE_BRUSH:
|
|
return "Brush";
|
|
case GDK_DEVICE_TOOL_TYPE_PENCIL:
|
|
return "Pencil";
|
|
case GDK_DEVICE_TOOL_TYPE_AIRBRUSH:
|
|
return "Airbrush";
|
|
case GDK_DEVICE_TOOL_TYPE_MOUSE:
|
|
return "Mouse";
|
|
case GDK_DEVICE_TOOL_TYPE_LENS:
|
|
return "Lens cursor";
|
|
case GDK_DEVICE_TOOL_TYPE_UNKNOWN:
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
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));
|
|
|
|
if (info->last_tool)
|
|
{
|
|
const gchar *tool_type;
|
|
guint64 serial;
|
|
|
|
tool_type = tool_type_to_string (gdk_device_tool_get_tool_type (info->last_tool));
|
|
serial = gdk_device_tool_get_serial (info->last_tool);
|
|
g_string_append_printf (string, "\nTool: %s", tool_type);
|
|
|
|
if (serial != 0)
|
|
g_string_append_printf (string, ", Serial: %lx", serial);
|
|
}
|
|
|
|
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 *info;
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
gint y = 0;
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
/* Draw Abs info */
|
|
g_hash_table_iter_init (&iter, data->pointer_info);
|
|
|
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
|
{
|
|
info = value;
|
|
draw_axes_info (cr, info, &allocation);
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, data->touch_info);
|
|
|
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
|
{
|
|
info = value;
|
|
draw_axes_info (cr, info, &allocation);
|
|
}
|
|
|
|
/* Draw name, color legend and misc data */
|
|
g_hash_table_iter_init (&iter, data->pointer_info);
|
|
|
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
|
{
|
|
info = value;
|
|
draw_device_info (widget, cr, NULL, &y, info);
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, data->touch_info);
|
|
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
info = value;
|
|
draw_device_info (widget, cr, key, &y, 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_title (GTK_WINDOW (window), "Event Axes");
|
|
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_set_support_multidevice (box, TRUE);
|
|
gtk_widget_add_events (box,
|
|
GDK_POINTER_MOTION_MASK |
|
|
GDK_BUTTON_PRESS_MASK |
|
|
GDK_BUTTON_RELEASE_MASK |
|
|
GDK_SMOOTH_SCROLL_MASK |
|
|
GDK_ENTER_NOTIFY_MASK |
|
|
GDK_LEAVE_NOTIFY_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);
|
|
|
|
return window;
|
|
}
|