[gtk+/gestures] Implement a gestures interpreter
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/gestures] Implement a gestures interpreter
- Date: Mon, 17 Jan 2011 03:53:58 +0000 (UTC)
commit a82167eb2166977934e61bd8ee5d86526a9b725c
Author: Carlos Garnacho <carlosg gnome org>
Date: Fri Jan 14 10:26:29 2011 +0100
Implement a gestures interpreter
GtkGesturesInterpreter is a private object, the GtkWidget methods
gtk_widget_enable_gesture() and disable_gesture() are the only
entry points, the interpreter itself is hooked into the event
delivery mechanism.
The algorithm used to determine strokes similarity is a variation
of weighted Levenshtein, where variable weights are applied
depending on the torsion effort needed to transform one stroke
into another.
gtk/Makefile.am | 2 +
gtk/gtkenums.h | 9 +
gtk/gtkgesturesinterpreter.c | 599 ++++++++++++++++++++++++++++++++++++++++++
gtk/gtkgesturesinterpreter.h | 64 +++++
gtk/gtkmain.c | 12 +
gtk/gtkwidget.c | 94 +++++++
gtk/gtkwidget.h | 9 +
gtk/gtkwidgetprivate.h | 5 +
tests/Makefile.am | 6 +
tests/testgestures.c | 233 ++++++++++++++++
10 files changed, 1033 insertions(+), 0 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index a81ea5c..b7fe884 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -391,6 +391,7 @@ gtk_private_h_sources = \
gtkfilechooserutils.h \
gtkfilesystem.h \
gtkfilesystemmodel.h \
+ gtkgesturesinterpreter.h \
gtkiconcache.h \
gtkimcontextsimpleseqs.h \
gtkintl.h \
@@ -525,6 +526,7 @@ gtk_base_c_sources = \
gtkfontbutton.c \
gtkfontsel.c \
gtkframe.c \
+ gtkgesturesinterpreter.c \
gtkgradient.c \
gtkgrid.c \
gtkhandlebox.c \
diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h
index 1eb3b9f..827656a 100644
--- a/gtk/gtkenums.h
+++ b/gtk/gtkenums.h
@@ -654,6 +654,15 @@ typedef enum {
GTK_BORDER_STYLE_OUTSET
} GtkBorderStyle;
+typedef enum {
+ GTK_GESTURE_SWIPE_LEFT,
+ GTK_GESTURE_SWIPE_RIGHT,
+ GTK_GESTURE_SWIPE_UP,
+ GTK_GESTURE_SWIPE_DOWN,
+ GTK_GESTURE_CIRCULAR_CLOCKWISE,
+ GTK_GESTURE_CIRCULAR_COUNTERCLOCKWISE
+} GtkGestureType;
+
G_END_DECLS
diff --git a/gtk/gtkgesturesinterpreter.c b/gtk/gtkgesturesinterpreter.c
new file mode 100644
index 0000000..266984b
--- /dev/null
+++ b/gtk/gtkgesturesinterpreter.c
@@ -0,0 +1,599 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Carlos Garnacho <carlos lanedo com>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "gtkgesturesinterpreter.h"
+#include <gdk/gdk.h>
+#include <math.h>
+
+#define GRID_SIZE 100
+#define POINT_DISTANCE 5
+#define N_CIRCULAR_SIDES 6
+
+#undef DEBUG_WEIGHTS
+
+G_DEFINE_TYPE (GtkGesturesInterpreter, gtk_gestures_interpreter, G_TYPE_OBJECT)
+
+typedef struct _GtkGesturesInterpreterPrivate GtkGesturesInterpreterPrivate;
+typedef struct _GestureNode GestureNode;
+typedef struct _StockGesture StockGesture;
+typedef struct _RecordedGesture RecordedGesture;
+
+struct _GtkGesturesInterpreterPrivate
+{
+ GHashTable *events;
+};
+
+struct _GestureNode
+{
+ gint angle;
+ gint distance;
+};
+
+struct _StockGesture
+{
+ GArray *gesture_data;
+ GtkGestureType gesture;
+};
+
+struct _RecordedGesture
+{
+ GArray *coordinates;
+ gint min_x;
+ gint max_x;
+ gint min_y;
+ gint max_y;
+};
+
+static GList *gestures_stock = NULL;
+
+static void
+_gtk_gestures_interpreter_register_gesture (GArray *gesture,
+ GtkGestureType gesture_type)
+{
+ StockGesture *stock_gesture;
+
+ stock_gesture = g_slice_new0 (StockGesture);
+ stock_gesture->gesture_data = gesture;
+ stock_gesture->gesture = gesture_type;
+
+ gestures_stock = g_list_prepend (gestures_stock, stock_gesture);
+}
+
+static void
+register_basic_gestures (void)
+{
+ GestureNode node;
+ GArray *gesture;
+ gint n, angle;
+
+ /* Swipe right */
+ gesture = g_array_new (FALSE, FALSE, sizeof (GestureNode));
+ node.distance = 100;
+ node.angle = 90;
+ g_array_append_val (gesture, node);
+ _gtk_gestures_interpreter_register_gesture (gesture, GTK_GESTURE_SWIPE_RIGHT);
+
+ /* Swipe left */
+ gesture = g_array_new (FALSE, FALSE, sizeof (GestureNode));
+ node.distance = 100;
+ node.angle = 270;
+ g_array_append_val (gesture, node);
+ _gtk_gestures_interpreter_register_gesture (gesture, GTK_GESTURE_SWIPE_LEFT);
+
+ /* Swipe up */
+ gesture = g_array_new (FALSE, FALSE, sizeof (GestureNode));
+ node.distance = 100;
+ node.angle = 0;
+ g_array_append_val (gesture, node);
+ _gtk_gestures_interpreter_register_gesture (gesture, GTK_GESTURE_SWIPE_UP);
+
+ /* Swipe down */
+ gesture = g_array_new (FALSE, FALSE, sizeof (GestureNode));
+ node.distance = 100;
+ node.angle = 180;
+ g_array_append_val (gesture, node);
+ _gtk_gestures_interpreter_register_gesture (gesture, GTK_GESTURE_SWIPE_DOWN);
+
+ /* Circular clockwise */
+ gesture = g_array_new (FALSE, FALSE, sizeof (GestureNode));
+ angle = 90;
+
+ for (n = 0; n < N_CIRCULAR_SIDES; n++)
+ {
+ node.distance = 50;
+ node.angle = angle % 360;
+
+ g_array_append_val (gesture, node);
+ angle += 360 / N_CIRCULAR_SIDES;
+ }
+
+ _gtk_gestures_interpreter_register_gesture (gesture, GTK_GESTURE_CIRCULAR_CLOCKWISE);
+
+ /* Circular counterclockwise */
+ gesture = g_array_new (FALSE, FALSE, sizeof (GestureNode));
+ angle = 270;
+
+ for (n = 0; n < N_CIRCULAR_SIDES; n++)
+ {
+ node.distance = 50;
+ node.angle = angle % 360;
+
+ g_array_append_val (gesture, node);
+ angle -= 360 / N_CIRCULAR_SIDES;
+
+ if (angle < 0)
+ angle += 360;
+ }
+
+ _gtk_gestures_interpreter_register_gesture (gesture, GTK_GESTURE_CIRCULAR_COUNTERCLOCKWISE);
+}
+
+static void
+gtk_gestures_interpreter_class_init (GtkGesturesInterpreterClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ register_basic_gestures ();
+
+ g_type_class_add_private (object_class, sizeof (GtkGesturesInterpreterPrivate));
+}
+
+static RecordedGesture *
+recorded_gesture_new (void)
+{
+ RecordedGesture *recorded;
+
+ recorded = g_slice_new0 (RecordedGesture);
+ recorded->coordinates = g_array_new (FALSE, FALSE, sizeof (GdkPoint));
+ recorded->min_x = G_MAXINT;
+ recorded->max_x = G_MININT;
+ recorded->min_y = G_MAXINT;
+ recorded->max_y = G_MININT;
+
+ return recorded;
+}
+
+static void
+recorded_gesture_free (RecordedGesture *recorded)
+{
+ g_array_free (recorded->coordinates, TRUE);
+ g_slice_free (RecordedGesture, recorded);
+}
+
+static void
+recorded_gesture_append_coordinate (RecordedGesture *recorded,
+ gint x,
+ gint y)
+{
+ GdkPoint point;
+
+ point.x = x;
+ point.y = y;
+
+ g_array_append_val (recorded->coordinates, point);
+
+ recorded->min_x = MIN (recorded->min_x, x);
+ recorded->max_x = MAX (recorded->max_x, x);
+ recorded->min_y = MIN (recorded->min_y, y);
+ recorded->max_y = MAX (recorded->max_y, y);
+}
+
+static void
+find_angle_and_distance (GdkPoint *point0,
+ GdkPoint *point1,
+ gint *angle,
+ gint *distance)
+{
+ gdouble ang;
+ gint x, y;
+
+ x = point1->x - point0->x;
+ y = point1->y - point0->y;
+
+ ang = (atan2 (x, y) + G_PI) * (360 / (2 * G_PI));
+ *angle = ((gint) ang) % 360;
+ *angle = 360 - *angle;
+
+ *distance = (gint) sqrt ((x * x) + (y * y));
+}
+
+static GArray *
+recorded_gesture_vectorize (RecordedGesture *recorded)
+{
+ GdkPoint *point, *prev = NULL;
+ GArray *vector_data;
+ gint i, diff;
+
+ if ((recorded->max_x - recorded->min_x) == 0)
+ return NULL;
+
+ if ((recorded->max_y - recorded->min_y) == 0)
+ return NULL;
+
+ diff = ((recorded->max_x - recorded->min_x) - (recorded->max_y - recorded->min_y));
+
+ if (diff > 0)
+ {
+ /* Sample is more wide than high */
+ recorded->min_y -= diff / 2;
+ recorded->max_y += diff / 2;
+ }
+ else
+ {
+ /* Sample is more high than wide */
+ recorded->min_x -= ABS (diff) / 2;
+ recorded->max_x += ABS (diff) / 2;
+ }
+
+ vector_data = g_array_new (FALSE, FALSE, sizeof (GestureNode));
+
+ for (i = 0; i < recorded->coordinates->len; i++)
+ {
+ point = &g_array_index (recorded->coordinates, GdkPoint, i);
+
+ /* Resample coordinates so they fit in a GRID_SIZExGRID_SIZE grid */
+ point->x = ((point->x - recorded->min_x) * GRID_SIZE) / (recorded->max_x - recorded->min_x);
+ point->y = ((point->y - recorded->min_y) * GRID_SIZE) / (recorded->max_y - recorded->min_y);
+
+ /* Transform the coordinates into a vector array */
+ if (prev)
+ {
+ gint angle, distance;
+
+ find_angle_and_distance (prev, point, &angle, &distance);
+
+ if (distance > 0)
+ {
+ GestureNode *node, new;
+
+ if (vector_data->len > 0)
+ node = &g_array_index (vector_data, GestureNode, vector_data->len - 1);
+ else
+ node = NULL;
+
+ if (node && node->angle == angle)
+ node->distance += distance;
+ else
+ {
+ new.angle = angle;
+ new.distance = distance;
+ g_array_append_val (vector_data, new);
+ }
+ }
+ }
+
+ prev = point;
+ }
+
+ return vector_data;
+}
+
+static void
+gtk_gestures_interpreter_init (GtkGesturesInterpreter *interpreter)
+{
+ GtkGesturesInterpreterPrivate *priv;
+
+ priv = interpreter->priv = G_TYPE_INSTANCE_GET_PRIVATE (interpreter,
+ GTK_TYPE_GESTURES_INTERPRETER,
+ GtkGesturesInterpreterPrivate);
+ priv->events = g_hash_table_new_full (NULL, NULL,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) recorded_gesture_free);
+}
+
+GtkGesturesInterpreter *
+_gtk_gestures_interpreter_new (void)
+{
+ return g_object_new (GTK_TYPE_GESTURES_INTERPRETER, NULL);
+}
+
+gboolean
+_gtk_gestures_interpreter_feed_event (GtkGesturesInterpreter *interpreter,
+ GdkEvent *event)
+{
+ GtkGesturesInterpreterPrivate *priv;
+ RecordedGesture *recorded;
+ GdkDevice *device;
+ gdouble x, y;
+
+ g_return_val_if_fail (GTK_IS_GESTURES_INTERPRETER (interpreter), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ priv = interpreter->priv;
+ device = gdk_event_get_source_device (event);
+
+ if (!device)
+ return FALSE;
+
+ if (!gdk_event_get_coords (event, &x, &y))
+ return FALSE;
+
+ recorded = g_hash_table_lookup (priv->events, device);
+
+ if (!recorded)
+ {
+ recorded = recorded_gesture_new ();
+ g_hash_table_insert (priv->events,
+ g_object_ref (device),
+ recorded);
+ }
+
+ recorded_gesture_append_coordinate (recorded, x, y);
+
+ return TRUE;
+}
+
+static GArray *
+get_gesture_data (GArray *gesture)
+{
+ GArray *data;
+ gint i, j;
+
+ data = g_array_new (FALSE, FALSE, sizeof (guint));
+
+ for (i = 0; i < gesture->len; i++)
+ {
+ GestureNode *node;
+ gint n_points;
+
+ node = &g_array_index (gesture, GestureNode, i);
+ n_points = MAX (1, node->distance / POINT_DISTANCE);
+
+ for (j = 0; j < n_points; j++)
+ g_array_append_val (data, node->angle);
+ }
+
+ return data;
+}
+
+static guint
+get_angle_diff (gint angle1,
+ gint angle2)
+{
+ gint diff;
+
+ diff = ABS (angle1 - angle2);
+
+ if (diff > 180)
+ diff = 360 - diff;
+
+ return diff;
+}
+
+/* This is a Levenshtein algorithm modification to have variable
+ * weights for modifications, gesture data is splitted and
+ * transformed into a list of angles, defining points at a
+ * more or less regular distance.
+ */
+static guint
+similarity_weight (GArray *gesture,
+ GArray *stock)
+{
+ GArray *gesture_data, *stock_data;
+ guint *cur, *prev;
+ gint cost, i, j;
+#ifdef DEBUG_WEIGHTS
+ gchar chr;
+#endif
+
+ gesture_data = get_gesture_data (gesture);
+ stock_data = get_gesture_data (stock);
+
+ cur = g_new0 (guint, stock_data->len);
+ prev = g_new0 (guint, stock_data->len);
+
+#ifdef DEBUG_WEIGHTS
+ for (j = 0; j < stock_data->len; j++)
+ {
+ if (j == 0)
+ g_print (" %4d ", 0);
+ else
+ g_print (" %4d ", g_array_index (stock_data, guint, j - 1));
+ }
+
+ g_print ("\n");
+#endif
+
+ for (i = 0; i < gesture_data->len; i++)
+ {
+ guint *tmp;
+
+#ifdef DEBUG_WEIGHTS
+ if (i == 0)
+ g_print ("%4d ", 0);
+ else
+ g_print ("%4d ", g_array_index (gesture_data, guint, i - 1));
+#endif
+
+ for (j = 0; j < stock_data->len; j++)
+ {
+ gint diff;
+
+#ifdef DEBUG_WEIGHTS
+ chr = ' ';
+#endif
+
+ if (i == 0)
+ {
+ /* First iteration, fill in initial costs */
+ if (j == 0)
+ {
+ diff = get_angle_diff (g_array_index (stock_data, guint, 1),
+ g_array_index (gesture_data, guint, 1));
+ cur[j] = diff;
+ }
+ else
+ {
+ diff = get_angle_diff (g_array_index (stock_data, guint, j),
+ g_array_index (stock_data, guint, j - 1));
+
+ cur[j] = cur[j - 1] + diff;
+ }
+ }
+ else
+ {
+ if (j == 0)
+ {
+ diff = get_angle_diff (g_array_index (gesture_data, guint, i),
+ g_array_index (gesture_data, guint, i - 1));
+
+ cur[j] = prev[j] + diff;
+ }
+ else
+ {
+ guint add_cost, del_cost, change_cost;
+
+ /* Calculate add cost */
+ if (i + 1 < gesture_data->len)
+ diff =
+ get_angle_diff (g_array_index (stock_data, guint, j),
+ g_array_index (gesture_data, guint, i)) +
+ get_angle_diff (g_array_index (stock_data, guint, j),
+ g_array_index (gesture_data, guint, i + 1));
+ else
+ diff = 360;
+
+ add_cost = cur[j - 1] + diff;
+
+ /* Calculate deletion cost */
+ if (j + 1 < stock_data->len)
+ diff =
+ get_angle_diff (g_array_index (stock_data, guint, j),
+ g_array_index (gesture_data, guint, i)) +
+ get_angle_diff (g_array_index (stock_data, guint, j + 1),
+ g_array_index (gesture_data, guint, i));
+ else
+ diff = 0;
+
+ del_cost = prev[j] + (360 - diff);
+
+ /* Calculate change cost */
+ diff = get_angle_diff (g_array_index (stock_data, guint, j),
+ g_array_index (gesture_data, guint, i));
+ change_cost = prev[j - 1] + diff;
+
+ cur[j] = MIN (add_cost, MIN (del_cost, change_cost));
+
+#ifdef DEBUG_WEIGHTS
+ if (cur[j] == add_cost)
+ chr = '-';
+ else if (cur[j] == del_cost)
+ chr = '|';
+ else
+ chr = '\\';
+#endif
+ }
+ }
+
+#ifdef DEBUG_WEIGHTS
+ g_print ("%c%4d ", chr, cur[j]);
+#endif
+ }
+
+#ifdef DEBUG_WEIGHTS
+ g_print ("\n");
+#endif
+
+ /* swap arrays for the next iteration, this also means it's
+ * the prev array which will contain the final score
+ */
+ tmp = prev;
+ prev = cur;
+ cur = tmp;
+ }
+
+ cost = prev[stock_data->len - 1] * MAX (1, ABS ((gint) stock_data->len - (gint) gesture_data->len));
+
+ g_array_free (gesture_data, TRUE);
+ g_array_free (stock_data, TRUE);
+ g_free (cur);
+ g_free (prev);
+
+ return cost;
+}
+
+gboolean
+_gtk_gestures_interpreter_finish (GtkGesturesInterpreter *interpreter,
+ GdkEvent *event,
+ GSList *recognized_gestures,
+ GtkGestureType *gesture)
+{
+ GtkGesturesInterpreterPrivate *priv;
+ RecordedGesture *recorded;
+ GtkGestureType gesture_type = 0;
+ GdkDevice *device;
+ GArray *vector_data;
+ guint weight, min_weight = G_MAXUINT;
+ gboolean found = FALSE;
+ GList *l;
+
+ g_return_val_if_fail (GTK_IS_GESTURES_INTERPRETER (interpreter), FALSE);
+
+ priv = interpreter->priv;
+ device = gdk_event_get_source_device (event);
+
+ if (!device)
+ return FALSE;
+
+ recorded = g_hash_table_lookup (priv->events, device);
+
+ if (!recorded)
+ return FALSE;
+
+ /* Any other considerations to
+ * discard a gesture event goes here
+ */
+
+ vector_data = recorded_gesture_vectorize (recorded);
+
+ if (!vector_data)
+ return FALSE;
+
+ /* Find out gesture data similarity with each gesture,
+ * the lower the number, the more similar is the just
+ * finished gesture with the one from the stock.
+ */
+ for (l = gestures_stock; l; l = l->next)
+ {
+ StockGesture *stock_gesture;
+
+ stock_gesture = l->data;
+ weight = similarity_weight (vector_data, stock_gesture->gesture_data);
+
+ if (weight < min_weight)
+ {
+ min_weight = weight;
+ gesture_type = stock_gesture->gesture;
+ found = TRUE;
+ }
+ }
+
+ g_hash_table_remove (priv->events, device);
+ g_array_free (vector_data, TRUE);
+
+ if (found &&
+ g_slist_find (recognized_gestures,
+ GUINT_TO_POINTER (gesture_type)))
+ {
+ *gesture = gesture_type;
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/gtk/gtkgesturesinterpreter.h b/gtk/gtkgesturesinterpreter.h
new file mode 100644
index 0000000..122866a
--- /dev/null
+++ b/gtk/gtkgesturesinterpreter.h
@@ -0,0 +1,64 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2011 Carlos Garnacho <carlosg gnome org>
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_GESTURES_INTERPRETER_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_GESTURES_INTERPRETER (gtk_gestures_interpreter_get_type ())
+#define GTK_GESTURES_INTERPRETER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_GESTURES_INTERPRETER, GtkGesturesInterpreter))
+#define GTK_GESTURES_INTERPRETER_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), GTK_TYPE_GESTURES_INTERPRETER, GtkGesturesInterpreterClass))
+#define GTK_IS_GESTURES_INTERPRETER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_GESTURES_INTERPRETER))
+#define GTK_IS_GESTURES_INTERPRETER_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), GTK_TYPE_GESTURES_INTERPRETER))
+#define GTK_GESTURES_INTERPRETER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_GESTURES_INTERPRETER, GtkGesturesInterpreterClass))
+
+typedef struct _GtkGesturesInterpreter GtkGesturesInterpreter;
+typedef struct _GtkGesturesInterpreterClass GtkGesturesInterpreterClass;
+
+struct _GtkGesturesInterpreter
+{
+ GObject parent_object;
+ gpointer priv;
+};
+
+struct _GtkGesturesInterpreterClass
+{
+ GObjectClass parent_class;
+};
+
+GType gtk_gestures_interpreter_get_type (void) G_GNUC_CONST;
+
+GtkGesturesInterpreter * _gtk_gestures_interpreter_new (void);
+
+gboolean _gtk_gestures_interpreter_feed_event (GtkGesturesInterpreter *interpreter,
+ GdkEvent *event);
+gboolean _gtk_gestures_interpreter_finish (GtkGesturesInterpreter *interpreter,
+ GdkEvent *event,
+ GSList *recognized_gestures,
+ GtkGestureType *gesture);
+
+G_END_DECLS
+
+#endif /* __GTK_GESTURES_INTERPRETER_H__ */
diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c
index 63376bf..af9b27f 100644
--- a/gtk/gtkmain.c
+++ b/gtk/gtkmain.c
@@ -1888,6 +1888,18 @@ gtk_main_do_event (GdkEvent *event)
_gtk_tooltip_handle_event (event);
}
+ /* Handle gestures */
+ if (event->type == GDK_MOTION_NOTIFY)
+ {
+ GdkModifierType state;
+
+ if (gdk_event_get_state (event, &state) &&
+ state & GDK_BUTTON1_MASK)
+ _gtk_widget_gesture_stroke (grab_widget, event);
+ }
+ else if (event->type == GDK_BUTTON_RELEASE)
+ _gtk_widget_gesture_finish (grab_widget, event);
+
tmp_list = current_events;
current_events = g_list_remove_link (current_events, tmp_list);
g_list_free_1 (tmp_list);
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 24cc272..2320635 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -61,6 +61,7 @@
#include "gtkcssprovider.h"
#include "gtkanimationdescription.h"
#include "gtkmodifierstyle.h"
+#include "gtkgesturesinterpreter.h"
#include "gtkversion.h"
#include "gtkdebug.h"
#include "gtktypebuiltins.h"
@@ -377,6 +378,8 @@ struct _GtkWidgetPrivate
/* Widget's path for styling */
GtkWidgetPath *path;
+
+ GSList *gestures;
};
enum {
@@ -450,6 +453,7 @@ enum {
QUERY_TOOLTIP,
DRAG_FAILED,
STYLE_UPDATED,
+ GESTURE,
LAST_SIGNAL
};
@@ -691,6 +695,7 @@ static GQuark quark_has_tooltip = 0;
static GQuark quark_tooltip_window = 0;
static GQuark quark_visual = 0;
static GQuark quark_modifier_style = 0;
+static GQuark quark_gestures_interpreter = 0;
GParamSpecPool *_gtk_widget_child_property_pool = NULL;
GObjectNotifyContext *_gtk_widget_child_property_notify_context = NULL;
@@ -804,6 +809,7 @@ gtk_widget_class_init (GtkWidgetClass *klass)
quark_tooltip_window = g_quark_from_static_string ("gtk-tooltip-window");
quark_visual = g_quark_from_static_string ("gtk-widget-visual");
quark_modifier_style = g_quark_from_static_string ("gtk-widget-modifier-style");
+ quark_gestures_interpreter = g_quark_from_static_string ("gtk-widget-gestures-interpreter");
style_property_spec_pool = g_param_spec_pool_new (FALSE);
_gtk_widget_child_property_pool = g_param_spec_pool_new (TRUE);
@@ -2966,6 +2972,15 @@ gtk_widget_class_init (GtkWidgetClass *klass)
_gtk_marshal_BOOLEAN__UINT,
G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
+ widget_signals[GESTURE] =
+ g_signal_new (I_("gesture"),
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtkWidgetClass, gesture),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1, GTK_TYPE_GESTURE_TYPE);
+
binding_set = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK,
"popup-menu", 0);
@@ -14025,3 +14040,82 @@ gtk_widget_get_style_context (GtkWidget *widget)
return widget->priv->context;
}
+
+void
+_gtk_widget_gesture_stroke (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GtkGesturesInterpreter *interpreter;
+ GtkWidgetPrivate *priv;
+
+ priv = widget->priv;
+
+ if (!priv->gestures)
+ return;
+
+ interpreter = g_object_get_qdata (G_OBJECT (widget), quark_gestures_interpreter);
+
+ if (!interpreter)
+ {
+ interpreter = _gtk_gestures_interpreter_new ();
+ g_object_set_qdata_full (G_OBJECT (widget), quark_gestures_interpreter,
+ interpreter, (GDestroyNotify) g_object_unref);
+ }
+
+ _gtk_gestures_interpreter_feed_event (interpreter, event);
+}
+
+void
+_gtk_widget_gesture_finish (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GtkGesturesInterpreter *interpreter;
+ GtkWidgetPrivate *priv;
+ GtkGestureType gesture;
+
+ priv = widget->priv;
+
+ if (!priv->gestures)
+ return;
+
+ interpreter = g_object_get_qdata (G_OBJECT (widget), quark_gestures_interpreter);
+
+ if (!interpreter)
+ return;
+
+ if (_gtk_gestures_interpreter_finish (interpreter, event, priv->gestures, &gesture))
+ g_signal_emit (widget, widget_signals[GESTURE], 0, gesture);
+}
+
+void
+gtk_widget_enable_gesture (GtkWidget *widget,
+ GtkGestureType gesture)
+{
+ GtkWidgetPrivate *priv;
+
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ priv = widget->priv;
+
+ if (!g_slist_find (priv->gestures, GUINT_TO_POINTER (gesture)))
+ priv->gestures = g_slist_prepend (priv->gestures,
+ GUINT_TO_POINTER (gesture));
+}
+
+void
+gtk_widget_disable_gesture (GtkWidget *widget,
+ GtkGestureType gesture)
+{
+ GtkWidgetPrivate *priv;
+
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ priv = widget->priv;
+ priv->gestures = g_slist_remove (priv->gestures,
+ GUINT_TO_POINTER (gesture));
+
+ if (!priv->gestures)
+ {
+ /* Cancel current interpreter operation */
+ }
+}
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index a2d4d5b..fc0a4cd 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -425,6 +425,9 @@ struct _GtkWidgetClass
void (* style_updated) (GtkWidget *widget);
+ void (* gesture) (GtkWidget *widget,
+ GtkGestureType gesture);
+
/*< private >*/
/* Padding for future expansion */
@@ -977,6 +980,12 @@ GtkStyleContext * gtk_widget_get_style_context (GtkWidget *widget);
GtkWidgetPath * gtk_widget_get_path (GtkWidget *widget);
+/* Gestures */
+void gtk_widget_enable_gesture (GtkWidget *widget,
+ GtkGestureType gesture);
+void gtk_widget_disable_gesture (GtkWidget *widget,
+ GtkGestureType gesture);
+
G_END_DECLS
diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h
index c566646..be5044a 100644
--- a/gtk/gtkwidgetprivate.h
+++ b/gtk/gtkwidgetprivate.h
@@ -93,6 +93,11 @@ gboolean _gtk_widget_get_translation_to_window (GtkWidget *widget,
int *x,
int *y);
+void _gtk_widget_gesture_stroke (GtkWidget *widget,
+ GdkEvent *event);
+void _gtk_widget_gesture_finish (GtkWidget *widget,
+ GdkEvent *event);
+
G_END_DECLS
#endif /* __GTK_WIDGET_PRIVATE_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 5364298..2157907 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -49,6 +49,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testfilechooserbutton \
testframe \
testgeometry \
+ testgestures \
testgiconpixbuf \
testgrid \
testgtk \
@@ -149,6 +150,7 @@ testfilechooser_DEPENDENCIES = $(TEST_DEPS)
testfilechooserbutton_DEPENDENCIES = $(TEST_DEPS)
testframe_DEPENDENCIES = $(TEST_DEPS)
testgeometry_DEPENDENCIES = $(TEST_DEPS)
+testgestures_DEPENDENCIES = $(TEST_DEPS)
testgiconpixbuf = $(TEST_DEPS)
testgrid_DEPENDENCIES = $(TEST_DEPS)
testgtk_DEPENDENCIES = $(TEST_DEPS)
@@ -225,6 +227,7 @@ testfilechooser_LDADD = $(LDADDS)
testfilechooserbutton_LDADD = $(LDADDS)
testframe_LDADD = $(LDADDS)
testgeometry_LDADD = $(LDADDS)
+testgestures_LDADD = $(LDADDS)
testgiconpixbuf_LDADD = $(LDADDS)
testgrid_LDADD = $(LDADDS)
testgtk_LDADD = $(LDADDS)
@@ -359,6 +362,9 @@ testframe_SOURCES = \
testgeometry_SOURCES = \
testgeometry.c
+testgestures_SOURCES = \
+ testgestures.c
+
testgiconpixbuf_SOURCES = \
testgiconpixbuf.c
diff --git a/tests/testgestures.c b/tests/testgestures.c
new file mode 100644
index 0000000..dab0cec
--- /dev/null
+++ b/tests/testgestures.c
@@ -0,0 +1,233 @@
+#include <gtk/gtk.h>
+
+static GArray *points;
+static GtkGestureType shown_gesture = -1;
+static guint timeout_id = 0;
+
+static gboolean
+hide_gesture_cb (gpointer user_data)
+{
+ shown_gesture = -1;
+ gtk_widget_queue_draw (GTK_WIDGET (user_data));
+
+ timeout_id = 0;
+ return FALSE;
+}
+
+static void
+gesture_cb (GtkWidget *widget,
+ GtkGestureType gesture,
+ gpointer user_data)
+{
+ shown_gesture = gesture;
+ gtk_widget_queue_draw (widget);
+
+ if (timeout_id != 0)
+ g_source_remove (timeout_id);
+
+ timeout_id = g_timeout_add (500, hide_gesture_cb, widget);
+}
+
+static gboolean
+draw_cb (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer user_data)
+{
+ GdkPoint *point;
+ gint i;
+
+ if (shown_gesture != -1)
+ {
+ gint width, height, size;
+
+ cairo_save (cr);
+ width = gdk_window_get_width (gtk_widget_get_window (widget));
+ height = gdk_window_get_height (gtk_widget_get_window (widget));
+
+ size = MIN (width, height);
+ size -= size / 4;
+
+ cairo_translate (cr, width / 2, height / 2);
+
+ cairo_set_line_width (cr, 10);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_source_rgb (cr, 0.8, 0.8, 0.8);
+
+ switch (shown_gesture)
+ {
+ case GTK_GESTURE_SWIPE_LEFT:
+ case GTK_GESTURE_SWIPE_RIGHT:
+ case GTK_GESTURE_SWIPE_UP:
+ case GTK_GESTURE_SWIPE_DOWN:
+ if (shown_gesture == GTK_GESTURE_SWIPE_RIGHT)
+ cairo_rotate (cr, (G_PI * 3) / 2);
+ else if (shown_gesture == GTK_GESTURE_SWIPE_LEFT)
+ cairo_rotate (cr, G_PI / 2);
+ else if (shown_gesture == GTK_GESTURE_SWIPE_UP)
+ cairo_rotate (cr, G_PI);
+
+ cairo_move_to (cr, 0, - size / 2);
+ cairo_line_to (cr, 0, size / 2);
+
+ cairo_move_to (cr, 0, size / 2);
+ cairo_rel_line_to (cr, -size / 4, -size / 4);
+
+ cairo_move_to (cr, 0, size / 2);
+ cairo_rel_line_to (cr, size / 4, -size / 4);
+
+ cairo_stroke (cr);
+ break;
+ case GTK_GESTURE_CIRCULAR_COUNTERCLOCKWISE:
+ cairo_move_to (cr, 0, - size / 2);
+ cairo_rel_line_to (cr, size / 8, - size / 8);
+
+ cairo_move_to (cr, 0, - size / 2);
+ cairo_rel_line_to (cr, size / 8, size / 8);
+
+ cairo_new_sub_path (cr);
+ cairo_arc (cr, 0, 0, size / 2, (3 * G_PI) / 2, (5 * G_PI) / 4);
+
+ cairo_stroke (cr);
+ break;
+ case GTK_GESTURE_CIRCULAR_CLOCKWISE:
+ cairo_move_to (cr, 0, - size / 2);
+ cairo_rel_line_to (cr, - size / 8, - size / 8);
+
+ cairo_move_to (cr, 0, - size / 2);
+ cairo_rel_line_to (cr, - size / 8, size / 8);
+
+ cairo_new_sub_path (cr);
+ cairo_arc_negative (cr, 0, 0, size / 2, (3 * G_PI) / 2, (7* G_PI) / 4);
+
+ cairo_stroke (cr);
+ break;
+ default:
+ break;
+ }
+
+ cairo_restore (cr);
+ }
+ else if (points->len > 0)
+ {
+ cairo_set_line_width (cr, 2);
+
+ point = &g_array_index (points, GdkPoint, 0);
+ cairo_move_to (cr, point->x, point->y);
+
+ for (i = 0; i < points->len; i++)
+ {
+ point = &g_array_index (points, GdkPoint, i);
+ cairo_line_to (cr, point->x, point->y);
+ }
+
+ cairo_stroke (cr);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+motion_notify_cb (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GdkModifierType state;
+ gdouble x, y;
+ GdkPoint point;
+
+ if (!gdk_event_get_state (event, &state))
+ return FALSE;
+
+ if ((state & GDK_BUTTON1_MASK) == 0)
+ return FALSE;
+
+ gdk_event_get_coords (event, &x, &y);
+
+ point.x = x;
+ point.y = y;
+ g_array_append_val (points, point);
+
+ gtk_widget_queue_draw (widget);
+
+ return FALSE;
+}
+
+static gboolean
+button_release_cb (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ if (points->len > 0)
+ {
+ g_array_remove_range (points, 0, points->len);
+ gtk_widget_queue_draw (widget);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+button_press_cb (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ if (timeout_id != 0)
+ {
+ g_source_remove (timeout_id);
+ timeout_id = 0;
+ }
+
+ shown_gesture = -1;
+ gtk_widget_queue_draw (widget);
+
+ return FALSE;
+}
+
+static void
+create_window (void)
+{
+ GtkWidget *window;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_app_paintable (window, TRUE);
+ gtk_widget_set_size_request (window, 400, 400);
+ gtk_window_set_title (GTK_WINDOW (window), "Gestures demo");
+
+ gtk_widget_add_events (window,
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK);
+
+ gtk_widget_enable_gesture (window, GTK_GESTURE_SWIPE_LEFT);
+ gtk_widget_enable_gesture (window, GTK_GESTURE_SWIPE_RIGHT);
+ gtk_widget_enable_gesture (window, GTK_GESTURE_SWIPE_UP);
+ gtk_widget_enable_gesture (window, GTK_GESTURE_SWIPE_DOWN);
+ gtk_widget_enable_gesture (window, GTK_GESTURE_CIRCULAR_CLOCKWISE);
+ gtk_widget_enable_gesture (window, GTK_GESTURE_CIRCULAR_COUNTERCLOCKWISE);
+
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+ g_signal_connect (window, "motion-notify-event",
+ G_CALLBACK (motion_notify_cb), NULL);
+ g_signal_connect (window, "button-release-event",
+ G_CALLBACK (button_release_cb), NULL);
+ g_signal_connect (window, "button-press-event",
+ G_CALLBACK (button_press_cb), NULL);
+ g_signal_connect (window, "draw",
+ G_CALLBACK (draw_cb), NULL);
+ g_signal_connect (window, "gesture",
+ G_CALLBACK (gesture_cb), NULL);
+
+ gtk_widget_show (window);
+}
+
+int
+main (int argc, char *argv[])
+{
+ gtk_init (&argc, &argv);
+
+ points = g_array_new (FALSE, FALSE, sizeof (GdkPoint));
+ create_window ();
+
+ gtk_main ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]