From 8a86b6b2b4c0a3de4526dd01031f94279febf617 Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Fri, 2 Jun 2006 07:48:13 +0000 Subject: [PATCH] Added rudimentary gesture engine and test in gtk frontend svn path=/trunk/netsurf/; revision=2611 --- desktop/browser.c | 7 +- desktop/browser.h | 6 +- desktop/gesture_core.c | 342 +++++++++++++++++++++++++++++++++++++++++ desktop/gesture_core.h | 38 +++++ gtk/gtk_window.c | 28 +++- makefile | 6 +- 6 files changed, 420 insertions(+), 7 deletions(-) create mode 100644 desktop/gesture_core.c create mode 100644 desktop/gesture_core.h diff --git a/desktop/browser.c b/desktop/browser.c index 53717b998..f48d31702 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -34,6 +34,7 @@ #include "netsurf/desktop/options.h" #include "netsurf/desktop/selection.h" #include "netsurf/desktop/textinput.h" +#include "netsurf/desktop/gesture_core.h" #include "netsurf/render/box.h" #include "netsurf/render/form.h" #include "netsurf/render/font.h" @@ -114,7 +115,11 @@ void browser_window_create(const char *url, struct browser_window *clone, bw->history = history_create(); else bw->history = history_clone(clone->history); - bw->sel = selection_create(bw); + if (!clone) + bw->gesturer = NULL; + else + bw->gesturer = gesturer_clone(clone->gesturer); + bw->sel = selection_create(bw); bw->throbbing = false; bw->caret_callback = NULL; bw->paste_callback = NULL; diff --git a/desktop/browser.h b/desktop/browser.h index c046290b1..516ba7394 100644 --- a/desktop/browser.h +++ b/desktop/browser.h @@ -28,6 +28,7 @@ struct selection; struct browser_window; struct url_data; struct bitmap; +struct _gesturer_state; typedef void (*browser_caret_callback)(struct browser_window *bw, @@ -44,7 +45,10 @@ struct browser_window { /** Window history structure. */ struct history *history; - + + /** Gesturer for this browser window */ + struct _gesturer_state *gesturer; + /** Selection state */ struct selection *sel; diff --git a/desktop/gesture_core.c b/desktop/gesture_core.c new file mode 100644 index 000000000..39e124ab7 --- /dev/null +++ b/desktop/gesture_core.c @@ -0,0 +1,342 @@ +/* + * This file is part of NetSurf, http://netsurf.sourceforge.net/ + * Licensed under the GNU General Public License, + * http://www.opensource.org/licenses/gpl-license + * Copyright 2006 Daniel Silverstone + */ + +/** \file + * Mouse gesture core (implementation) + */ + +#include "netsurf/utils/log.h" +#include "netsurf/desktop/gesture_core.h" +#include +#include +#include + +/** A gesture as used by the recognition machinery */ +struct _internal_gesture { + struct _internal_gesture *next; /**< The next gesture in the list */ + int gesture_tag; /**< The tag to return for this gesture */ + int gesture_len; /**< The length of this gesture string */ + char *gesture; /**< The gesture string reversed for matching */ +}; + +typedef struct _internal_gesture* InternalGesture; + +/** A recogniser state. Commonly one in the application. Could have + * multiple (E.g. one for browser windows, one for the history window. + */ +struct _gesture_recogniser { + InternalGesture gestures; /**< The gestures registered */ + Gesturer gesture_users; /**< The users of the gesture engine */ + int max_len; /**< The maximum length the gestures in this recogniser */ + int min_distance; /**< The minimum distance the mouse should move */ + int max_nonmove; /**< The maximum number of data points before abort */ +}; + +/** A gesturer state. Commonly one per browser window */ +struct _gesturer_state { + GestureRecogniser recogniser; /**< The recogniser for this state */ + Gesturer next; /* Next gesture state */ + int last_x; /**< Last X coordinate fed to the gesture engine */ + int last_y; /**< Last Y coordinate fed to the gesture engine */ + int bored_count; /**< Num of boring recent add_point calls */ + int elements; /**< Number of elements in the current gesture */ + int nelements; /**< The max number of elements in this gesturer */ + char *gesture; /**< The in-progress gesture string */ +}; + +static void gesturer_notify_recognition_change(Gesturer gesturer); + +/** Create a gesture recogniser. + * + * \return A new recogniser. + */ +GestureRecogniser gesture_recogniser_create(void) +{ + GestureRecogniser ret = malloc(sizeof(struct _gesture_recogniser)); + ret->gestures = NULL; + ret->gesture_users = NULL; + ret->max_len = 0; + ret->min_distance = 1000000; /* Extremely unlikely */ + ret->max_nonmove = 1; + return ret; +} + +/** Add a gesture to the recogniser. + * + * \param recog The recogniser + * \param gesture_str The gesture string to add + * \param gesture_tag The tag to return for this gesture + */ +void gesture_recogniser_add(GestureRecogniser recog, + const char* gesture_str, int gesture_tag) +{ + InternalGesture g = malloc(sizeof(struct _internal_gesture)); + InternalGesture g2,g3; + int i; + Gesturer gest = recog->gesture_users; + + g->gesture_tag = gesture_tag; + g->gesture_len = strlen(gesture_str); + g->gesture = malloc(g->gesture_len); + + for(i = 0; i < g->gesture_len; ++i) + g->gesture[i] = gesture_str[g->gesture_len - i - 1]; + + g2 = recog->gestures; g3 = NULL; + while( g2 && g->gesture_len < g2->gesture_len ) g3=g3, g2 = g2->next; + if( g3 == NULL ) { + /* prev == NULL, this means we're inserting at the head */ + recog->gestures = g; g->next = g2; + } else { + /* prev == something; we're inserting somewhere */ + g3->next = g; g->next = g2; + } + + if( recog->max_len < g->gesture_len ) recog->max_len = g->gesture_len; + + while( gest != NULL ) { + gesturer_notify_recognition_change(gest); + gest = gest->next; + } +} + +/** Destroy a gesture recogniser. + * + * Only call this after destroying all the gesturers for it. + * + * \param recog The recogniser to destroy. + */ +void gesture_recogniser_destroy(GestureRecogniser recog) +{ + if( recog->gesture_users ) { + LOG(("Attempt to destroy a gesture recogniser with gesture users still registered.")); + return; + } + while( recog->gestures ) { + InternalGesture g = recog->gestures; + recog->gestures = g->next; + free(g->gesture); + free(g); + } + free(recog); + return; +} + +/** Set the min distance the mouse has to move in order to be + * classed as having partaken of a gesture. + * + * \param recog The recogniser. + * \param min_distance The minimum distance in pixels + */ +void gesture_recogniser_set_distance_threshold(GestureRecogniser recog, + int min_distance) +{ + recog->min_distance = min_distance * min_distance; +} + +/** Set the number of non-movement adds of points before the gesturer is + * internally reset instead of continuing to accumulate a gesture. + * + * \param recog The recogniser. + * \param max_nonmove The maximum number of non-movement adds. + */ +void gesture_recogniser_set_count_threshold(GestureRecogniser recog, + int max_nonmove) +{ + recog->max_nonmove = max_nonmove; +} + +/** Create a gesturer. + * + * \param recog The gesture recogniser for this gesturer. + * + * \return The new gesturer object + */ +Gesturer gesturer_create(GestureRecogniser recog) +{ + Gesturer ret = malloc(sizeof(struct _gesturer_state)); + ret->recogniser = recog; + ret->next = recog->gesture_users; + recog->gesture_users = ret; + ret->last_x = 0; + ret->last_y = 0; + ret->bored_count = 0; + ret->elements = 0; + ret->nelements = recog->max_len; + ret->gesture = calloc(recog->max_len+1, 1); + return ret; +} + +/** Clone a gesturer. + * + * \param gesturer The gesturer to clone + * + * \return A gesturer cloned from the parameter + */ +Gesturer gesturer_clone(Gesturer gesturer) +{ + return gesturer_create(gesturer->recogniser); +} + +/** Remove this gesturer from its recogniser and destroy it. + * + * \param gesturer The gesturer to destroy. + */ +void gesturer_destroy(Gesturer gesturer) +{ + Gesturer g = gesturer->recogniser->gesture_users, g2 = NULL; + while( g && g != gesturer ) g2 = g, g = g->next; + if( g2 == NULL ) { + /* This gesturer is first in the list */ + gesturer->recogniser->gesture_users = gesturer->next; + } else { + g2->next = gesturer->next; + } + free(gesturer->gesture); + free(gesturer); +} + +/** Notify a gesturer that its recogniser has changed in some way */ +static void gesturer_notify_recognition_change(Gesturer gesturer) +{ + char *new_gesture = calloc(gesturer->recogniser->max_len+1, 1); + int i; + for(i = 0; i < gesturer->elements; ++i) + new_gesture[i] = gesturer->gesture[i]; + free(gesturer->gesture); + gesturer->gesture = new_gesture; + gesturer->nelements = gesturer->recogniser->max_len; +} + +/** Clear the points associated with this gesturer. + * + * You might call this if the gesturer should be cleared because a mouse + * button was released or similar. + * + * \param gesturer The gesturer to clear. + */ +void gesturer_clear_points(Gesturer gesturer) +{ + memset(gesturer->gesture, 0, gesturer->elements); + gesturer->elements = 0; + gesturer->bored_count = 0; +} + +#define M_PI_8 (M_PI_4 / 2) +#define M_3_PI_8 (3 * M_PI_8) + +static struct { + float lower, upper; + bool x_neg, y_neg; + char direction; +} directions[12] = { + /* MIN MAX X_NEG Y_NEG DIRECTION */ + { 0.0, M_PI_8, false, false, '1' }, /* Right */ + { M_PI_8, M_3_PI_8, false, false, '2' }, /* Up/Right */ + { M_3_PI_8, INFINITY, false, false, '3' }, /* Up */ + { M_3_PI_8, INFINITY, true, false, '3' }, /* Up */ + { M_PI_8, M_3_PI_8, true, false, '4' }, /* Up/Left */ + { 0.0, M_PI_8, true, false, '5' }, /* Left */ + { 0.0, M_PI_8, true, true, '5' }, /* Left */ + { M_PI_8, M_3_PI_8, true, true, '6' }, /* Down/Left */ + { M_3_PI_8, INFINITY, true, true, '7' }, /* Down */ + { M_3_PI_8, INFINITY, false, true, '7' }, /* Down */ + { M_PI_8, M_3_PI_8, false, true, '8' }, /* Up/Right */ + { 0.0, M_PI_8, false, true, '1' } /* Right */ +}; + +#undef M_PI_8 +#undef M_3_PI_8 + +static char gesturer_find_direction(Gesturer gesturer, int x, int y) +{ + float dx = 0.0000000000001 + (x - gesturer->last_x); + float dy = -(0.0000000000001 + (y - gesturer->last_y)); + float arc; + bool x_neg = dx < 0; + bool y_neg = dy < 0; + int i; + + if( x_neg ) dx = -dx; + if( y_neg ) dy = -dy; + arc = atanf(dy/dx); + for( i = 0; i < 12; ++i ) { + if( directions[i].lower > arc || directions[i].upper <= arc ) + continue; /* Not within this entry */ + if( directions[i].x_neg != x_neg || + directions[i].y_neg != y_neg ) + continue; /* Signs not matching */ + return directions[i].direction; + } + LOG(("Erm, fell off the end of the direction calculator")); + return 0; /* No direction */ +} + +/** Indicate to a gesturer that a new mouse sample is available. + * + * Call this to provide a new position sample to the gesturer. + * If this is interesting, the gesturer will return a gesture tag + * as per the gesture recogniser it was constructed with. Otherwise + * it will return GESTURE_NONE which has the value -1. + * + * \param gesturer The gesturer to add the point to. + * \param x The X coordinate of the mouse pointer. + * \param y The Y coordinate of the mouse pointer. + * + * \return The gesture tag activated (or GESTURE_NONE if none) + */ +int gesturer_add_point(Gesturer gesturer, int x, int y) +{ + int distance = ((gesturer->last_x - x) * (gesturer->last_x - x)) + + ((gesturer->last_y - y) * (gesturer->last_y - y)); + char last_direction = (gesturer->elements == 0) ? 0 : + (gesturer->gesture[0]); + char this_direction = gesturer_find_direction(gesturer, x, y); + InternalGesture ig = gesturer->recogniser->gestures; + int ret = GESTURE_NONE; + + if( distance < gesturer->recogniser->min_distance ) { + gesturer->bored_count++; + if( gesturer->elements && + (gesturer->bored_count >= + gesturer->recogniser->max_nonmove) ) { + LOG(("Bored now.")); + gesturer_clear_points(gesturer); + } + if( gesturer->elements && + gesturer->bored_count == + (gesturer->recogniser->max_nonmove >> 1)) { + LOG(("Decided to look")); + while( ig && ig->gesture_len <= gesturer->elements ) { + if( strcmp(gesturer->gesture, + ig->gesture) == 0 ) + ret = ig->gesture_tag; + ig = ig->next; + } + } + return ret; /* GESTURE_NONE or else a gesture found above */ + } + /* We moved far enough that we care about the movement */ + gesturer->last_x = x; + gesturer->last_y = y; + gesturer->bored_count = 0; + if( this_direction == last_direction ) { + return GESTURE_NONE; /* Nothing */ + } + + /* Shunt the gesture one up */ + if( gesturer->elements ) { + if( gesturer->elements == gesturer->nelements ) + gesturer->elements--; + memmove(gesturer->gesture+1, gesturer->gesture, + gesturer->elements); + } + gesturer->elements++; + gesturer->gesture[0] = this_direction; + LOG(("Gesture is currently: '%s'", gesturer->gesture)); + return GESTURE_NONE; +} diff --git a/desktop/gesture_core.h b/desktop/gesture_core.h new file mode 100644 index 000000000..ccba38988 --- /dev/null +++ b/desktop/gesture_core.h @@ -0,0 +1,38 @@ +/* + * This file is part of NetSurf, http://netsurf.sourceforge.net/ + * Licensed under the GNU General Public License, + * http://www.opensource.org/licenses/gpl-license + * Copyright 2006 Daniel Silverstone + */ + +/** \file + * Mouse gesture core (interface) + */ + +#ifndef _NETSURF_DESKTOP_GESTURE_CORE_H +#define _NETSURF_DESKTOP_GESTURE_CORE_H_ + +#include + +typedef struct _gesture_recogniser* GestureRecogniser; +typedef struct _gesturer_state* Gesturer; + +GestureRecogniser gesture_recogniser_create(void); +void gesture_recogniser_add(GestureRecogniser recog, + const char* gesture_str, int gesture_tag); +void gesture_recogniser_destroy(GestureRecogniser recog); +void gesture_recogniser_set_distance_threshold(GestureRecogniser recog, + int min_distance); +void gesture_recogniser_set_count_threshold(GestureRecogniser recog, + int max_nonmove); + + +Gesturer gesturer_create(GestureRecogniser recog); +Gesturer gesturer_clone(Gesturer gesturer); +void gesturer_destroy(Gesturer gesturer); +int gesturer_add_point(Gesturer gesturer, int x, int y); +void gesturer_clear_points(Gesturer gesturer); + +#define GESTURE_NONE -1 + +#endif diff --git a/gtk/gtk_window.c b/gtk/gtk_window.c index bda8b8512..bb3e0a39d 100644 --- a/gtk/gtk_window.c +++ b/gtk/gtk_window.c @@ -19,6 +19,7 @@ #include "netsurf/desktop/plotters.h" #include "netsurf/desktop/options.h" #include "netsurf/desktop/textinput.h" +#include "netsurf/desktop/gesture_core.h" #include "netsurf/gtk/gtk_gui.h" #include "netsurf/gtk/gtk_plotters.h" #include "netsurf/gtk/gtk_window.h" @@ -49,6 +50,7 @@ struct gui_window { struct gtk_history_window *history_window; GtkWidget *history_window_widget; int caretx, carety, careth; + int last_x, last_y; }; struct gtk_history_window { @@ -101,6 +103,8 @@ static void gtk_perform_deferred_resize(void *p); static wchar_t gdkkey_to_nskey(GdkEventKey *key); +static void gtk_pass_mouse_position(void *p); + struct gui_window *gui_create_browser_window(struct browser_window *bw, struct browser_window *clone) { @@ -318,10 +322,29 @@ struct gui_window *gui_create_browser_window(struct browser_window *bw, gtk_widget_hide_on_delete, NULL); #undef NS_SIGNAL_CONNECT - + + if( !bw->gesturer ) { + /* Prepare a gesturer */ + GestureRecogniser gr = gesture_recogniser_create(); + bw->gesturer = gesturer_create(gr); + gesture_recogniser_add(gr, "732187", 100); + gesture_recogniser_set_distance_threshold(gr, 50); + gesture_recogniser_set_count_threshold(gr, 20); + schedule(5, gtk_pass_mouse_position, g); + } + return g; } +void gtk_pass_mouse_position(void *p) +{ + struct gui_window *g = (struct gui_window*)p; + if( g->bw->gesturer ) + if( gesturer_add_point(g->bw->gesturer, g->last_x, g->last_y) == 100 ) + exit(0); + schedule(5, gtk_pass_mouse_position, p); +} + void gui_window_zoomin_button_event(GtkWidget *widget, gpointer data) { struct gui_window *g = data; @@ -578,7 +601,8 @@ gboolean gui_window_motion_notify_event(GtkWidget *widget, struct gui_window *g = data; browser_window_mouse_track(g->bw, 0, event->x, event->y); - + g->last_x = event->x; + g->last_y = event->y; return TRUE; } diff --git a/makefile b/makefile index 74bea0230..99dbbf556 100644 --- a/makefile +++ b/makefile @@ -31,7 +31,7 @@ OBJECTS_IMAGE = bmp.o bmpread.o gif.o gifread.o ico.o jpeg.o \ OBJECTS_RISCOS = $(OBJECTS_COMMON) $(OBJECTS_IMAGE) OBJECTS_RISCOS += browser.o history_core.o netsurf.o selection.o \ - textinput.o version.o # desktop/ + textinput.o version.o gesture_core.o # desktop/ OBJECTS_RISCOS += 401login.o artworks.o assert.o awrender.o bitmap.o \ buffer.o configure.o debugwin.o \ dialog.o download.o draw.o filetype.o font.o \ @@ -64,7 +64,7 @@ OBJECTS_DEBUGRO += artworks.o awrender.o bitmap.o draw.o \ OBJECTS_GTK = $(OBJECTS_COMMON) $(OBJECTS_IMAGE) OBJECTS_GTK += filetyped.o # debug/ OBJECTS_GTK += browser.o history_core.o netsurf.o selection.o textinput.o \ - version.o # desktop/ + version.o gesture_core.o # desktop/ OBJECTS_GTK += font_pango.o gtk_bitmap.o gtk_gui.o \ gtk_schedule.o gtk_thumbnail.o \ gtk_plotters.o gtk_treeview.o gtk_window.o # gtk/ @@ -120,7 +120,7 @@ CFLAGS_NCOS = $(CFLAGS_RISCOS) -Dncos CFLAGS_DEBUG = -std=c9x -D_BSD_SOURCE -DDEBUG_BUILD $(WARNFLAGS) -I.. \ $(PLATFORM_CFLAGS_DEBUG) -g CFLAGS_GTK = -std=c9x -D_BSD_SOURCE -D_POSIX_C_SOURCE -Dgtk \ - $(WARNFLAGS) -I.. -g -O \ + $(WARNFLAGS) -I.. -g -O0 -Wformat=2 \ `pkg-config --cflags gtk+-2.0` `xml2-config --cflags` # Stop GCC under Cygwin throwing a fit