diff --git a/demos/gtk-demo/Makefile.am b/demos/gtk-demo/Makefile.am index b8fb5b6b86..49a9a70319 100644 --- a/demos/gtk-demo/Makefile.am +++ b/demos/gtk-demo/Makefile.am @@ -22,6 +22,7 @@ demos = \ editable_cells.c \ entry_buffer.c \ entry_completion.c \ + event_axes.c \ expander.c \ hypertext.c \ iconview.c \ diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index a7b1336c10..19c582bb81 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -91,6 +91,7 @@ editable_cells.c entry_buffer.c entry_completion.c + event_axes.c expander.c flowbox.c hypertext.c diff --git a/demos/gtk-demo/event_axes.c b/demos/gtk-demo/event_axes.c new file mode 100644 index 0000000000..3a761f14a9 --- /dev/null +++ b/demos/gtk-demo/event_axes.c @@ -0,0 +1,419 @@ +/* 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 . 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 + +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; +}