/* GNOME stroke implementation Copyright (c) 2000, 2001 Dan Nicolaescu See the file COPYING for distribution information. */ #include "config.h" #include #include #include #include #include #include #include "gstroke.h" #include "gstroke-internal.h" #include #include 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; #define GSTROKE_TIMEOUT_DURATION 10 #define GSTROKE_SIGNALS "gstroke_signals" struct gstroke_func_and_data { GtkSignalFunc func; 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 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*)gtk_object_get_data (GTK_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_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_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 = (struct gstroke_metrics*) gtk_object_get_data (GTK_OBJECT (widget), GSTROKE_METRICS); /* 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_enable (GtkWidget *widget) { struct gstroke_metrics* metrics = (struct gstroke_metrics*)gtk_object_get_data (GTK_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; gtk_object_set_data (GTK_OBJECT (widget), GSTROKE_METRICS, metrics); gtk_signal_connect (GTK_OBJECT (widget), "event", (GtkSignalFunc) process_event, NULL); } else _gstroke_init (metrics); last_mouse_position.invalid = TRUE; } guint gstroke_signal_connect (GtkWidget *widget, const gchar *name, GtkSignalFunc func, gpointer data) { struct gstroke_func_and_data *func_and_data; GHashTable *hash_table = (GHashTable*)gtk_object_get_data (GTK_OBJECT (widget), GSTROKE_SIGNALS); if (!hash_table) { hash_table = g_hash_table_new (g_str_hash, g_str_equal); gtk_object_set_data (GTK_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*)gtk_object_get_data (GTK_OBJECT (widget), GSTROKE_SIGNALS); #ifdef DEBUG printf ("gstroke %s\n", name); fflush (stdout); #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*)gtk_object_get_data (GTK_OBJECT (widget), GSTROKE_SIGNALS); if (hash_table) /* FIXME: does this delete the elements too? */ g_hash_table_destroy (hash_table); gtk_object_remove_data (GTK_OBJECT (widget), GSTROKE_SIGNALS); metrics = (struct gstroke_metrics*)gtk_object_get_data (GTK_OBJECT (widget), GSTROKE_METRICS); if (metrics) g_free (metrics); gtk_object_remove_data (GTK_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); 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 }