Mercurial > pidgin
changeset 4390:16540914c963
[gaim-migrate @ 4656]
Added a mouse gestures plugin. Yes, you heard me.
committer: Tailor Script <tailor@pidgin.im>
author | Christian Hammond <chipx86@chipx86.com> |
---|---|
date | Wed, 22 Jan 2003 13:57:27 +0000 |
parents | e23a59b62700 |
children | 9673b50b55fa |
files | configure.ac configure.in plugins/Makefile.am plugins/gestures/.cvsignore plugins/gestures/Makefile.am plugins/gestures/gestures.c plugins/gestures/gstroke-internal.h plugins/gestures/gstroke.h plugins/gestures/stroke-draw.c plugins/gestures/stroke.c |
diffstat | 10 files changed, 1047 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.ac Wed Jan 22 13:57:22 2003 +0000 +++ b/configure.ac Wed Jan 22 13:57:27 2003 +0000 @@ -407,6 +407,7 @@ pixmaps/smileys/default/Makefile plugins/Makefile plugins/docklet/Makefile + plugins/gestures/Makefile plugins/ticker/Makefile po/Makefile.in sounds/Makefile
--- a/configure.in Wed Jan 22 13:57:22 2003 +0000 +++ b/configure.in Wed Jan 22 13:57:27 2003 +0000 @@ -390,6 +390,7 @@ pixmaps/smileys/default/Makefile plugins/Makefile plugins/docklet/Makefile + plugins/gestures/Makefile plugins/ticker/Makefile po/Makefile.in sounds/Makefile
--- a/plugins/Makefile.am Wed Jan 22 13:57:22 2003 +0000 +++ b/plugins/Makefile.am Wed Jan 22 13:57:27 2003 +0000 @@ -1,4 +1,4 @@ -SUBDIRS = docklet ticker +SUBDIRS = docklet gestures ticker plugindir = $(libdir)/gaim
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gestures/.cvsignore Wed Jan 22 13:57:27 2003 +0000 @@ -0,0 +1,7 @@ +Makefile +Makefile.in +.deps +.libs +gestures.dll +*.la +*.lo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gestures/Makefile.am Wed Jan 22 13:57:27 2003 +0000 @@ -0,0 +1,22 @@ +plugindir = $(libdir)/gaim + +gestures_la_LDFLAGS = -module -avoid-version + +if PLUGINS + +plugin_LTLIBRARIES = gestures.la + +gestures_la_SOURCES = \ + gestures.c \ + gstroke.h \ + gstroke-internal.h \ + stroke.c \ + stroke-draw.c + +endif + +INCLUDES = \ + -I$(top_srcdir)/src \ + -DVERSION=\"$(VERSION)\" \ + -DDATADIR=\"$(datadir)\" \ + $(DEBUG_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gestures/gestures.c Wed Jan 22 13:57:27 2003 +0000 @@ -0,0 +1,282 @@ +/* + * Mouse gestures plugin for Gaim + * + * Copyright (C) 2003 Christian Hammond. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ +#include "config.h" + +#ifndef GAIM_PLUGINS +#define GAIM_PLUGINS +#endif + +#include "gaim.h" +#include "gstroke.h" + +static GModule *handle = NULL; +struct gaim_plugin_description desc; + +static void +stroke_close(GtkWidget *widget, void *data) +{ + struct gaim_conversation *conv; + struct gaim_gtk_conversation *gtkconv; + + conv = (struct gaim_conversation *)data; + + /* Double-check */ + if (!GAIM_IS_GTK_CONVERSATION(conv)) + return; + + gtkconv = GAIM_GTK_CONVERSATION(conv); + + gstroke_cleanup(gtkconv->imhtml); + gaim_conversation_destroy(conv); +} + +static void +stroke_prev_tab(GtkWidget *widget, void *data) +{ + struct gaim_conversation *conv; + struct gaim_window *win; + unsigned int index; + + conv = (struct gaim_conversation *)data; + win = gaim_conversation_get_window(conv); + index = gaim_conversation_get_index(conv); + + if (index == 0) + index = gaim_window_get_conversation_count(win) - 1; + else + index--; + + gaim_window_switch_conversation(win, index); +} + +static void +stroke_next_tab(GtkWidget *widget, void *data) +{ + struct gaim_conversation *conv; + struct gaim_window *win; + unsigned int index; + + conv = (struct gaim_conversation *)data; + win = gaim_conversation_get_window(conv); + index = gaim_conversation_get_index(conv); + + if (index == gaim_window_get_conversation_count(win) - 1) + index = 0; + else + index++; + + gaim_window_switch_conversation(win, index); +} + +void +stroke_new_win(GtkWidget *widget, void *data) +{ + struct gaim_window *new_win, *old_win; + struct gaim_conversation *conv; + + conv = (struct gaim_conversation *)data; + old_win = gaim_conversation_get_window(conv); + + if (gaim_window_get_conversation_count(old_win) <= 1) + return; + + new_win = gaim_window_new(); + + gaim_window_remove_conversation(old_win, gaim_conversation_get_index(conv)); + gaim_window_add_conversation(new_win, conv); + + gaim_window_show(new_win); +} + +static void +attach_signals(struct gaim_conversation *conv) +{ + struct gaim_gtk_conversation *gtkconv; + + gtkconv = GAIM_GTK_CONVERSATION(conv); + + gstroke_enable(gtkconv->imhtml); + gstroke_signal_connect(gtkconv->imhtml, "14789", stroke_close, conv); + gstroke_signal_connect(gtkconv->imhtml, "1456", stroke_close, conv); + gstroke_signal_connect(gtkconv->imhtml, "74123", stroke_next_tab, conv); + gstroke_signal_connect(gtkconv->imhtml, "7456", stroke_next_tab, conv); + gstroke_signal_connect(gtkconv->imhtml, "96321", stroke_prev_tab, conv); + gstroke_signal_connect(gtkconv->imhtml, "9654", stroke_prev_tab, conv); + gstroke_signal_connect(gtkconv->imhtml, "25852", stroke_new_win, conv); +} + +static void +new_conv_cb(char *who) +{ + struct gaim_conversation *conv; + + conv = gaim_find_conversation(who); + + if (conv == NULL || !GAIM_IS_GTK_CONVERSATION(conv)) + return; + + attach_signals(conv); +} + +#if 0 +static void +mouse_button_menu_cb(GtkMenuItem *item, gpointer data) +{ + int button = (int)data; + + gstroke_set_mouse_button(button + 2); +} +#endif + +static void +toggle_draw_cb(GtkToggleButton *toggle, gpointer data) +{ + gstroke_set_draw_strokes(!gstroke_draw_strokes()); +} + +char * +gaim_plugin_init(GModule *h) +{ + struct gaim_conversation *conv; + GList *l; + + handle = h; + + for (l = gaim_get_conversations(); l != NULL; l = l->next) { + conv = (struct gaim_conversation *)l->data; + + if (!GAIM_IS_GTK_CONVERSATION(conv)) + continue; + + attach_signals(conv); + } + + gaim_signal_connect(handle, event_new_conversation, new_conv_cb, NULL); + + return NULL; +} + +void +gaim_plugin_remove(void) +{ + struct gaim_conversation *conv; + struct gaim_gtk_conversation *gtkconv; + GList *l; + + for (l = gaim_get_conversations(); l != NULL; l = l->next) { + conv = (struct gaim_conversation *)l->data; + + if (!GAIM_IS_GTK_CONVERSATION(conv)) + continue; + + gtkconv = GAIM_GTK_CONVERSATION(conv); + + gstroke_cleanup(gtkconv->imhtml); + } +} + +GtkWidget * +gaim_plugin_config_gtk(void) +{ + GtkWidget *ret; + GtkWidget *vbox; + GtkWidget *toggle; +#if 0 + GtkWidget *opt; + GtkWidget *menu, *item; +#endif + + /* Outside container */ + ret = gtk_vbox_new(FALSE, 18); + gtk_container_set_border_width(GTK_CONTAINER(ret), 12); + + /* Configuration frame */ + vbox = make_frame(ret, _("Mouse Gestures Configuration")); + +#if 0 + /* Mouse button drop-down menu */ + menu = gtk_menu_new(); + opt = gtk_option_menu_new(); + + item = gtk_menu_item_new_with_label(_("Middle mouse button")); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(mouse_button_menu_cb), opt); + gtk_menu_append(menu, item); + + item = gtk_menu_item_new_with_label(_("Right mouse button")); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(mouse_button_menu_cb), opt); + gtk_menu_append(menu, item); + + gtk_box_pack_start(GTK_BOX(vbox), opt, FALSE, FALSE, 0); + gtk_option_menu_set_menu(GTK_OPTION_MENU(opt), menu); + gtk_option_menu_set_history(GTK_OPTION_MENU(opt), + gstroke_get_mouse_button() - 2); +#endif + + /* "Visual gesture display" checkbox */ + toggle = gtk_check_button_new_with_mnemonic(_("_Visual gesture display")); + gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), + gstroke_draw_strokes()); + g_signal_connect(G_OBJECT(toggle), "toggled", + G_CALLBACK(toggle_draw_cb), NULL); + + gtk_widget_show_all(ret); + + return ret; +} + +struct gaim_plugin_description * +gaim_plugin_desc() +{ + desc.api_version = PLUGIN_API_VERSION; + desc.name = g_strdup(_("Mouse Gestures")); + desc.version = g_strdup(VERSION); + desc.description = g_strdup( + _("Allows support for mouse gestures in conversation windows.\n\n" + "Drag down and then to the right to close a conversation.\n" + "Drag up and then to the left to switch to the previous " + "conversation.\n" + "Drag up and then to the right to switch to the next " + "conversation.")); + desc.authors = g_strdup("Christian Hammond <chipx86@gnupdate.org>"); + desc.url = g_strdup(WEBSITE); + + return &desc; +} + +char * +name(void) +{ + return _("Mouse Gestures"); +} + +char * +description(void) +{ + return _("Allows support for mouse gestures in conversation windows.\n\n" + "Drag down and then to the right to close a conversation.\n" + "Drag up and then to the left to switch to the previous " + "conversation.\n" + "Drag up and then to the right to switch to the next " + "conversation."); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gestures/gstroke-internal.h Wed Jan 22 13:57:27 2003 +0000 @@ -0,0 +1,41 @@ +/* This file is to be used internally by the libgstroke implementation. + It should not be installed or used elsewhere. + + See the file COPYING for distribution information. +*/ + +#ifndef _GSTROKE_INTERNAL_H_ +#define _GSTROKE_INTERNAL_H_ + +/* metrics for stroke, they are used while processing a stroke, this + structure should be stored in local widget storage */ +struct gstroke_metrics { + GSList *pointList; /* point list */ + gint min_x; + gint min_y; + gint max_x; + gint max_y; + gint point_count; +}; + +#define GSTROKE_METRICS "gstroke_metrics" + +/* translate stroke to sequence */ +gint _gstroke_trans (gchar *sequence, struct gstroke_metrics *metrics); +gint _gstroke_canonical (gchar* sequence, struct gstroke_metrics *metrics); + +/* record point in stroke */ +void _gstroke_record (gint x, gint y, struct gstroke_metrics *metrics); + +/* initialize stroke functions */ +void _gstroke_init (struct gstroke_metrics*); + +/* structure for holding point data */ +struct s_point { + gint x; + gint y; +}; + +typedef struct s_point *p_point; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gestures/gstroke.h Wed Jan 22 13:57:27 2003 +0000 @@ -0,0 +1,45 @@ +/* + libgstroke - a GNOME stroke interface library + Copyright (c) 1996,1997,1998,1999,2000,2001 Mark F. Willey, ETLA Technical + + See the file COPYING for distribution information. +*/ + +/* largest number of points allowed to be sampled */ +#ifndef _GSTROKE_H_ +#define _GSTROKE_H_ + +#define GSTROKE_MAX_POINTS 10000 + +/* number of sample points required to have a valid stroke */ +#define GSTROKE_MIN_POINTS 50 + +/* maximum number of numbers in stroke */ +#define GSTROKE_MAX_SEQUENCE 32 + +/* threshold of size of smaller axis needed for it to define its own + bin size */ +#define GSTROKE_SCALE_RATIO 4 + +/* minimum percentage of points in bin needed to add to sequence */ +#define GSTROKE_BIN_COUNT_PERCENT 0.07 + +void gstroke_set_draw_strokes(gboolean draw); +gboolean gstroke_draw_strokes(void); + +void gstroke_set_mouse_button(gint button); +int gstroke_get_mouse_button(void); + +/* enable strokes for the widget */ +void gstroke_enable (GtkWidget *widget); + +guint gstroke_signal_connect (GtkWidget *widget, + const gchar *name, + void (*func)(GtkWidget *widget, void *data), + gpointer data); + +/* frees all the memory allocated for stroke, should be called when + the widget is destroyed*/ +void gstroke_cleanup (GtkWidget *widget); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gestures/stroke-draw.c Wed Jan 22 13:57:27 2003 +0000 @@ -0,0 +1,358 @@ +/* + GNOME stroke implementation + Copyright (c) 2000, 2001 Dan Nicolaescu + See the file COPYING for distribution information. +*/ + +#include "config.h" + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <glib.h> +#include <gtk/gtk.h> + +#include <gdk/gdkx.h> +#include "gstroke.h" +#include "gstroke-internal.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + + +static void gstroke_invisible_window_init (GtkWidget *widget); +/*FIXME: Maybe these should be put in a structure, and not static...*/ +static Display * gstroke_disp; +static Window gstroke_window; +static GC gstroke_gc; +static int mouse_button = 2; +static gboolean draw_strokes = FALSE; + +#define GSTROKE_TIMEOUT_DURATION 10 + +#define GSTROKE_SIGNALS "gstroke_signals" + +struct gstroke_func_and_data { + void (*func)(GtkWidget *, void *); + gpointer data; +}; + + +/*FIXME: maybe it's better to just make 2 static variables, not a + structure */ +struct mouse_position { + struct s_point last_point; + gboolean invalid; +}; + + +static struct mouse_position last_mouse_position; +static guint timer_id; + +static void gstroke_execute (GtkWidget *widget, const gchar *name); + +static void +record_stroke_segment (GtkWidget *widget) +{ + gint x, y; + struct gstroke_metrics *metrics; + + gtk_widget_get_pointer (widget, &x, &y); + + if (last_mouse_position.invalid) + last_mouse_position.invalid = FALSE; + else if (gstroke_draw_strokes()) + { +#if 1 + XDrawLine (gstroke_disp, gstroke_window, gstroke_gc, + last_mouse_position.last_point.x, + last_mouse_position.last_point.y, + x, y); + /* XFlush (gstroke_disp); */ +#else + /* FIXME: this does not work. It will only work if we create a + corresponding GDK window for stroke_window and draw on + that... */ + gdk_draw_line (widget->window, widget->style->fg_gc[GTK_STATE_NORMAL], + last_mouse_position.last_point.x, + last_mouse_position.last_point.y, + x, + y); +#endif + } + + if (last_mouse_position.last_point.x != x + || last_mouse_position.last_point.y != y) + { + last_mouse_position.last_point.x = x; + last_mouse_position.last_point.y = y; + metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget), + GSTROKE_METRICS); + _gstroke_record (x, y, metrics); + } +} + +static gint +gstroke_timeout (gpointer data) +{ + GtkWidget *widget = GTK_WIDGET (data); + record_stroke_segment (widget); + + return TRUE; +} + +static gint +process_event (GtkWidget *widget, GdkEvent *event, gpointer data G_GNUC_UNUSED) +{ + static GtkWidget *original_widget = NULL; + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button != gstroke_get_mouse_button()) + break; + + original_widget = widget; /* remeber the widget where + the stroke started */ + + gstroke_invisible_window_init (widget); + + record_stroke_segment (widget); + + gdk_pointer_grab (widget->window, FALSE, + GDK_BUTTON_RELEASE_MASK, NULL, NULL, + event->button.time); + timer_id = gtk_timeout_add (GSTROKE_TIMEOUT_DURATION, + gstroke_timeout, widget); + return TRUE; + + case GDK_BUTTON_RELEASE: + if ((event->button.button != gstroke_get_mouse_button()) + || (original_widget == NULL)) + /* the stroke probably did not start here... */ + break; + + last_mouse_position.invalid = TRUE; + original_widget = NULL; + gtk_timeout_remove (timer_id); + gdk_pointer_ungrab (event->button.time); + timer_id = 0; + + { + char result[GSTROKE_MAX_SEQUENCE]; + struct gstroke_metrics *metrics; + + metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT (widget), + GSTROKE_METRICS); + if (gstroke_draw_strokes()) { + /* get rid of the invisible stroke window */ + XUnmapWindow (gstroke_disp, gstroke_window); + XFlush (gstroke_disp); + } + + _gstroke_canonical (result, metrics); + gstroke_execute (widget, result); + return FALSE; + } + return TRUE; + default: + break; + } + + return FALSE; +} + +void +gstroke_set_draw_strokes(gboolean draw) +{ + draw_strokes = draw; +} + +gboolean +gstroke_draw_strokes(void) +{ + return draw_strokes; +} + +void +gstroke_set_mouse_button(gint button) +{ + mouse_button = button; +} + +int +gstroke_get_mouse_button(void) +{ + return mouse_button; +} + +void +gstroke_enable (GtkWidget *widget) +{ + struct gstroke_metrics* + metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget), + GSTROKE_METRICS); + if (metrics == NULL) + { + metrics = (struct gstroke_metrics *)g_malloc (sizeof + (struct gstroke_metrics)); + metrics->pointList = NULL; + metrics->min_x = 10000; + metrics->min_y = 10000; + metrics->max_x = 0; + metrics->max_y = 0; + metrics->point_count = 0; + + g_object_set_data(G_OBJECT(widget), GSTROKE_METRICS, metrics); + + g_signal_connect(G_OBJECT(widget), "event", + G_CALLBACK(process_event), NULL); + } + else + _gstroke_init (metrics); + + last_mouse_position.invalid = TRUE; +} + +guint +gstroke_signal_connect (GtkWidget *widget, + const gchar *name, + void (*func)(GtkWidget *widget, void *data), + gpointer data) +{ + struct gstroke_func_and_data *func_and_data; + GHashTable *hash_table = + (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS); + + if (!hash_table) + { + hash_table = g_hash_table_new (g_str_hash, g_str_equal); + g_object_set_data(G_OBJECT(widget), GSTROKE_SIGNALS, + (gpointer)hash_table); + } + func_and_data = g_new (struct gstroke_func_and_data, 1); + func_and_data->func = func; + func_and_data->data = data; + g_hash_table_insert (hash_table, (gpointer)name, (gpointer)func_and_data); + return TRUE; +} + +static void +gstroke_execute (GtkWidget *widget, const gchar *name) +{ + + GHashTable *hash_table = + (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS); + +#if 0 + debug_printf("gstroke %s\n", name); +#endif + + if (hash_table) + { + struct gstroke_func_and_data *fd = + (struct gstroke_func_and_data*)g_hash_table_lookup (hash_table, name); + if (fd) + (*fd->func)(widget, fd->data); + } +} + +void +gstroke_cleanup (GtkWidget *widget) +{ + struct gstroke_metrics *metrics; + GHashTable *hash_table = + (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS); + if (hash_table) + /* FIXME: does this delete the elements too? */ + g_hash_table_destroy (hash_table); + + g_object_steal_data(G_OBJECT(widget), GSTROKE_SIGNALS); + + metrics = (struct gstroke_metrics*)g_object_get_data(G_OBJECT(widget), + GSTROKE_METRICS); + if (metrics) + g_free (metrics); + g_object_steal_data(G_OBJECT(widget), GSTROKE_METRICS); +} + + +/* This function should be written using Gtk+ primitives*/ +static void +gstroke_invisible_window_init (GtkWidget *widget) +{ + XSetWindowAttributes w_attr; + XWindowAttributes orig_w_attr; + unsigned long mask, col_border, col_background; + unsigned int border_width; + XSizeHints hints; + Display *disp = GDK_WINDOW_XDISPLAY(widget->window); + Window wind = GDK_WINDOW_XWINDOW (widget->window); + int screen = DefaultScreen (disp); + + if (!gstroke_draw_strokes()) + return; + + gstroke_disp = disp; + + /* X server should save what's underneath */ + XGetWindowAttributes (gstroke_disp, wind, &orig_w_attr); + hints.x = orig_w_attr.x; + hints.y = orig_w_attr.y; + hints.width = orig_w_attr.width; + hints.height = orig_w_attr.height; + mask = CWSaveUnder; + w_attr.save_under = True; + + /* inhibit all the decorations */ + mask |= CWOverrideRedirect; + w_attr.override_redirect = True; + + /* Don't set a background, transparent window */ + mask |= CWBackPixmap; + w_attr.background_pixmap = None; + + /* Default input window look */ + col_background = WhitePixel (gstroke_disp, screen); + + /* no border for the window */ +#ifdef DEBUG + border_width = 5; +#else + border_width = 0; +#endif + col_border = BlackPixel (gstroke_disp, screen); + + gstroke_window = XCreateSimpleWindow (gstroke_disp, wind, + 0, 0, + hints.width - 2 * border_width, + hints.height - 2 * border_width, + border_width, + col_border, col_background); + + gstroke_gc = XCreateGC (gstroke_disp, gstroke_window, 0, NULL); + + XSetFunction (gstroke_disp, gstroke_gc, GXinvert); + + XChangeWindowAttributes (gstroke_disp, gstroke_window, mask, &w_attr); + + XSetLineAttributes (gstroke_disp, gstroke_gc, 2, LineSolid, + CapButt, JoinMiter); + XMapRaised (gstroke_disp, gstroke_window); + +#if 0 + /*FIXME: is this call really needed? If yes, does it need the real + argc and argv? */ + hints.flags = PPosition | PSize; + XSetStandardProperties (gstroke_disp, gstroke_window, "gstroke_test", NULL, + (Pixmap)NULL, NULL, 0, &hints); + + + /* Receive the close window client message */ + { + /* FIXME: is this really needed? If yes, something should be done + with wmdelete...*/ + Atom wmdelete = XInternAtom (gstroke_disp, "WM_DELETE_WINDOW", + False); + XSetWMProtocols (gstroke_disp, gstroke_window, &wmdelete, True); + } +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/gestures/stroke.c Wed Jan 22 13:57:27 2003 +0000 @@ -0,0 +1,289 @@ +/* + libgstroke - a GNOME stroke interface library + Copyright (c) 1996,1997,1998,1999,2000,2001 Mark F. Willey, ETLA Technical + + See the file COPYING for distribution information. + + This file contains the stroke recognition algorithm. +*/ + +#include "config.h" + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <math.h> +#include <glib.h> +#include <gtk/gtk.h> +#include "gstroke.h" +#include "gstroke-internal.h" + + +void +_gstroke_init (struct gstroke_metrics *metrics) +{ + if (metrics->pointList != NULL) { + /* FIXME: does this free the data too?*/ + g_slist_free (metrics->pointList); + metrics->pointList = NULL; + metrics->point_count = 0; + } +} + +/* figure out which bin the point falls in */ +static gint +_gstroke_bin (p_point point_p, gint bound_x_1, gint bound_x_2, + gint bound_y_1, gint bound_y_2) +{ + + gint bin_num = 1; + + if (point_p->x > bound_x_1) bin_num += 1; + if (point_p->x > bound_x_2) bin_num += 1; + if (point_p->y > bound_y_1) bin_num += 3; + if (point_p->y > bound_y_2) bin_num += 3; + + return bin_num; +} + +gint +_gstroke_trans (gchar *sequence, struct gstroke_metrics *metrics) +{ + GSList *crt_elem; + /* number of bins recorded in the stroke */ + guint sequence_count = 0; + + /* points-->sequence translation scratch variables */ + gint prev_bin = 0; + gint current_bin = 0; + gint bin_count = 0; + + /* flag indicating the start of a stroke - always count it in the sequence */ + gint first_bin = TRUE; + + /* bin boundary and size variables */ + gint delta_x, delta_y; + gint bound_x_1, bound_x_2; + gint bound_y_1, bound_y_2; + + + /* determine size of grid */ + delta_x = metrics->max_x - metrics->min_x; + delta_y = metrics->max_y - metrics->min_y; + + /* calculate bin boundary positions */ + bound_x_1 = metrics->min_x + (delta_x / 3); + bound_x_2 = metrics->min_x + 2 * (delta_x / 3); + + bound_y_1 = metrics->min_y + (delta_y / 3); + bound_y_2 = metrics->min_y + 2 * (delta_y / 3); + + if (delta_x > GSTROKE_SCALE_RATIO * delta_y) { + bound_y_1 = (metrics->max_y + metrics->min_y - delta_x) / 2 + (delta_x / 3); + bound_y_2 = (metrics->max_y + metrics->min_y - delta_x) / 2 + 2 * (delta_x / 3); + } else if (delta_y > GSTROKE_SCALE_RATIO * delta_x) { + bound_x_1 = (metrics->max_x + metrics->min_x - delta_y) / 2 + (delta_y / 3); + bound_x_2 = (metrics->max_x + metrics->min_x - delta_y) / 2 + 2 * (delta_y / 3); + } + +#if 0 + printf ("DEBUG:: point count: %d\n", metrics->point_count); + printf ("DEBUG:: metrics->min_x: %d\n", metrics->min_x); + printf ("DEBUG:: metrics->max_x: %d\n", metrics->max_x); + printf ("DEBUG:: metrics->min_y: %d\n", metrics->min_y); + printf ("DEBUG:: metrics->max_y: %d\n", metrics->max_y); + printf ("DEBUG:: delta_x: %d\n", delta_x); + printf ("DEBUG:: delta_y: %d\n", delta_y); + printf ("DEBUG:: bound_x_1: %d\n", bound_x_1); + printf ("DEBUG:: bound_x_2: %d\n", bound_x_2); + printf ("DEBUG:: bound_y_1: %d\n", bound_y_1); + printf ("DEBUG:: bound_y_2: %d\n", bound_y_2); +#endif + + /* + build string by placing points in bins, collapsing bins and + discarding those with too few points... */ + + crt_elem = metrics->pointList; + while (crt_elem != NULL) + { + /* figure out which bin the point falls in */ + + /*printf ("X = %d Y = %d\n", ((p_point)crt_elem->data)->x, + ((p_point)crt_elem->data)->y); */ + + + current_bin = _gstroke_bin ((p_point)crt_elem->data, bound_x_1, + bound_x_2, bound_y_1, bound_y_2); + + /* if this is the first point, consider it the previous bin, too. */ + if (prev_bin == 0) + prev_bin = current_bin; + + /*printf ("DEBUG:: current bin: %d x=%d y = %d\n", current_bin, + ((p_point)crt_elem->data)->x, + ((p_point)crt_elem->data)->y); */ + + if (prev_bin == current_bin) + bin_count++; + else { + /* we are moving to a new bin -- consider adding to the sequence */ + if ((bin_count > (metrics->point_count * GSTROKE_BIN_COUNT_PERCENT)) + || (first_bin == TRUE)) { + + //gchar val = '0' + prev_bin; + //printf ("%c", val);fflush (stdout); + //g_string_append (&sequence, &val); + + first_bin = FALSE; + sequence[sequence_count++] = '0' + prev_bin; + /* printf ("DEBUG:: adding sequence: %d\n", prev_bin); */ + + } + + /* restart counting points in the new bin */ + bin_count=0; + prev_bin = current_bin; + } + + /* move to next point, freeing current point from list */ + + free (crt_elem->data); + crt_elem = g_slist_next (crt_elem); + } + /* add the last run of points to the sequence */ + sequence[sequence_count++] = '0' + current_bin; + /* printf ("DEBUG:: adding final sequence: %d\n", current_bin); */ + + _gstroke_init (metrics); + + { + // FIXME: get rid of this block + //gchar val = '0' + current_bin; + //printf ("%c\n", val);fflush (stdout); + //g_string_append (&sequence, '\0'); + sequence[sequence_count] = '\0'; + } + + return TRUE; +} + +/* my plan is to make a stroke training program where you can enter all of +the variations of slop that map to a canonical set of strokes. When the +application calls gstroke_canonical, it gets one of the recognized strokes, +or "", if it's not a recognized variation. I will probably use a hash +table. Right now, it just passes the values through to gstroke_trans */ +gint +_gstroke_canonical (gchar *sequence, struct gstroke_metrics *metrics) +{ + return _gstroke_trans (sequence, metrics); +} + + +void +_gstroke_record (gint x, gint y, struct gstroke_metrics *metrics) +{ + p_point new_point_p; + gint delx, dely; + float ix, iy; + +#if 0 + printf ("%d:%d ", x, y); fflush (stdout); +#endif + + if (metrics->point_count < GSTROKE_MAX_POINTS) { + new_point_p = (p_point) g_malloc (sizeof (struct s_point)); + + if (metrics->pointList == NULL) { + + /* first point in list - initialize metrics */ + metrics->min_x = 10000; + metrics->min_y = 10000; + metrics->max_x = -1; + metrics->max_y = -1; + + metrics->pointList = (GSList*) g_malloc (sizeof (GSList)); + + metrics->pointList->data = new_point_p; + metrics->pointList->next = NULL; + metrics->point_count = 0; + + } else { + +#define LAST_POINT ((p_point)(g_slist_last (metrics->pointList)->data)) + + /* interpolate between last and current point */ + delx = x - LAST_POINT->x; + dely = y - LAST_POINT->y; + + if (abs(delx) > abs(dely)) { /* step by the greatest delta direction */ + iy = LAST_POINT->y; + + /* go from the last point to the current, whatever direction it may be */ + for (ix = LAST_POINT->x; (delx > 0) ? (ix < x) : (ix > x); ix += (delx > 0) ? 1 : -1) { + + /* step the other axis by the correct increment */ + iy += fabs(((float) dely / (float) delx)) * (float) ((dely < 0) ? -1.0 : 1.0); + + /* add the interpolated point */ + new_point_p->x = ix; + new_point_p->y = iy; + g_slist_append (metrics->pointList, new_point_p); + + /* update metrics */ + if (((gint) ix) < metrics->min_x) metrics->min_x = (gint) ix; + if (((gint) ix) > metrics->max_x) metrics->max_x = (gint) ix; + if (((gint) iy) < metrics->min_y) metrics->min_y = (gint) iy; + if (((gint) iy) > metrics->max_y) metrics->max_y = (gint) iy; + metrics->point_count++; + + new_point_p = (p_point) malloc (sizeof(struct s_point)); + } + } else { /* same thing, but for dely larger than delx case... */ + ix = LAST_POINT->x; + + /* go from the last point to the current, whatever direction it may be + */ + for (iy = LAST_POINT->y; (dely > 0) ? (iy < y) : (iy > y); iy += (dely > 0) ? 1 : -1) { + + /* step the other axis by the correct increment */ + ix += fabs(((float) delx / (float) dely)) * (float) ((delx < 0) ? -1.0 : 1.0); + + /* add the interpolated point */ + new_point_p->y = iy; + new_point_p->x = ix; + g_slist_append(metrics->pointList, new_point_p); + + /* update metrics */ + if (((gint) ix) < metrics->min_x) metrics->min_x = (gint) ix; + if (((gint) ix) > metrics->max_x) metrics->max_x = (gint) ix; + if (((gint) iy) < metrics->min_y) metrics->min_y = (gint) iy; + if (((gint) iy) > metrics->max_y) metrics->max_y = (gint) iy; + metrics->point_count++; + + new_point_p = (p_point) malloc (sizeof(struct s_point)); + } + } + + /* add the sampled point */ + g_slist_append(metrics->pointList, new_point_p); + } + + /* record the sampled point values */ + new_point_p->x = x; + new_point_p->y = y; + +#if 0 + { + GSList *crt = metrics->pointList; + printf ("Record "); + while (crt != NULL) + { + printf ("(%d,%d)", ((p_point)crt->data)->x, ((p_point)crt->data)->y); + crt = g_slist_next (crt); + } + printf ("\n"); + } +#endif + } +}