1 /*
2   GNOME stroke implementation
3   Copyright (c) 2000, 2001 Dan Nicolaescu
4   See the file COPYING for distribution information.
5 */
6 
7 #include "config.h"
8 
9 #include <unistd.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <glib.h>
13 #include <gtk/gtk.h>
14 #include <gdk/gdkx.h>
15 
16 #include "gstroke.h"
17 #include "gstroke-internal.h"
18 
19 #include <X11/Xlib.h>
20 #include <X11/Xutil.h>
21 
22 #if !GTK_CHECK_VERSION(2,14,0)
23 #define gtk_widget_get_window(x) x->window
24 #endif
25 
26 static void gstroke_invisible_window_init (GtkWidget *widget);
27 /*FIXME: Maybe these should be put in a structure, and not static...*/
28 static Display * gstroke_disp = NULL;
29 static Window gstroke_window;
30 static GC gstroke_gc;
31 static int mouse_button = 2;
32 static gboolean draw_strokes = FALSE;
33 
34 #define GSTROKE_TIMEOUT_DURATION 10
35 
36 #define GSTROKE_SIGNALS "gstroke_signals"
37 
38 struct gstroke_func_and_data {
39 	void (*func)(GtkWidget *, void *);
40 	gpointer data;
41 };
42 
43 
44 /*FIXME: maybe it's better to just make 2 static variables, not a
45   structure */
46 struct mouse_position {
47 	struct s_point last_point;
48 	gboolean invalid;
49 };
50 
51 
52 static struct mouse_position last_mouse_position;
53 static guint timer_id;
54 
55 static void gstroke_execute (GtkWidget *widget, const gchar *name);
56 
57 static void
record_stroke_segment(GtkWidget * widget)58 record_stroke_segment (GtkWidget *widget)
59 {
60   gint x, y;
61   struct gstroke_metrics *metrics;
62 
63   g_return_if_fail( widget != NULL );
64 
65   gtk_widget_get_pointer (widget, &x, &y);
66 
67   if (last_mouse_position.invalid)
68     last_mouse_position.invalid = FALSE;
69   else if (gstroke_draw_strokes())
70     {
71 #if 1
72       XDrawLine (gstroke_disp, gstroke_window, gstroke_gc,
73                  last_mouse_position.last_point.x,
74                  last_mouse_position.last_point.y,
75                  x, y);
76       /* XFlush (gstroke_disp); */
77 #else
78       /* FIXME: this does not work. It will only work if we create a
79          corresponding GDK window for stroke_window and draw on
80          that... */
81       gdk_draw_line (gtk_widget_get_window(widget),
82                      widget->style->fg_gc[GTK_STATE_NORMAL],
83                      last_mouse_position.last_point.x,
84                      last_mouse_position.last_point.y,
85                      x,
86                      y);
87 #endif
88     }
89 
90   if (last_mouse_position.last_point.x != x
91       || last_mouse_position.last_point.y != y)
92     {
93       last_mouse_position.last_point.x = x;
94       last_mouse_position.last_point.y = y;
95       metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget),
96 															GSTROKE_METRICS);
97       _gstroke_record (x, y, metrics);
98     }
99 }
100 
101 static gint
gstroke_timeout(gpointer data)102 gstroke_timeout (gpointer data)
103 {
104 	GtkWidget *widget;
105 
106 	g_return_val_if_fail(data != NULL, FALSE);
107 
108 	widget = GTK_WIDGET (data);
109 	record_stroke_segment (widget);
110 
111 	return TRUE;
112 }
113 
gstroke_cancel(GdkEvent * event)114 static void gstroke_cancel(GdkEvent *event)
115 {
116 	last_mouse_position.invalid = TRUE;
117 
118 	if (timer_id > 0)
119 	    g_source_remove (timer_id);
120 
121 	timer_id = 0;
122 
123 	if( event != NULL )
124 		gdk_pointer_ungrab (event->button.time);
125 
126 
127 	if (gstroke_draw_strokes() && gstroke_disp != NULL) {
128 	    /* get rid of the invisible stroke window */
129 	    XUnmapWindow (gstroke_disp, gstroke_window);
130 	    XFlush (gstroke_disp);
131 	}
132 
133 }
134 
135 static gint
process_event(GtkWidget * widget,GdkEvent * event,gpointer data G_GNUC_UNUSED)136 process_event (GtkWidget *widget, GdkEvent *event, gpointer data G_GNUC_UNUSED)
137 {
138   static GtkWidget *original_widget = NULL;
139   static GdkCursor *cursor = NULL;
140 
141   switch (event->type) {
142     case GDK_BUTTON_PRESS:
143 		if (event->button.button != gstroke_get_mouse_button()) {
144 			/* Similar to the bug below catch when any other button is
145 			 * clicked after the middle button is clicked (but possibly
146 			 * not released)
147 			 */
148 			gstroke_cancel(event);
149 			original_widget = NULL;
150 			break;
151 		}
152 
153       original_widget = widget; /* remeber the widget where
154                                    the stroke started */
155 
156       gstroke_invisible_window_init (widget);
157 
158       record_stroke_segment (widget);
159 
160 	  if (cursor == NULL)
161 		  cursor = gdk_cursor_new(GDK_PENCIL);
162 
163       gdk_pointer_grab (gtk_widget_get_window(widget), FALSE,
164 			GDK_BUTTON_RELEASE_MASK, NULL, cursor,
165 			event->button.time);
166       timer_id = g_timeout_add (GSTROKE_TIMEOUT_DURATION,
167 				  gstroke_timeout, widget);
168       return TRUE;
169 
170     case GDK_BUTTON_RELEASE:
171       if ((event->button.button != gstroke_get_mouse_button())
172 	  || (original_widget == NULL)) {
173 
174 		/* Nice bug when you hold down one button and press another. */
175 		/* We'll just cancel the gesture instead. */
176 		gstroke_cancel(event);
177 		original_widget = NULL;
178 		break;
179 	  }
180 
181       last_mouse_position.invalid = TRUE;
182       original_widget = NULL;
183       g_source_remove (timer_id);
184       gdk_pointer_ungrab (event->button.time);
185       timer_id = 0;
186 
187       {
188 	char result[GSTROKE_MAX_SEQUENCE];
189 	struct gstroke_metrics *metrics;
190 
191 	metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT (widget),
192 														  GSTROKE_METRICS);
193 		if (gstroke_draw_strokes()) {
194 			/* get rid of the invisible stroke window */
195 			XUnmapWindow (gstroke_disp, gstroke_window);
196 			XFlush (gstroke_disp);
197 		}
198 
199 	_gstroke_canonical (result, metrics);
200 	gstroke_execute (widget, result);
201       }
202       return FALSE;
203 
204     default:
205       break;
206   }
207 
208   return FALSE;
209 }
210 
211 void
gstroke_set_draw_strokes(gboolean draw)212 gstroke_set_draw_strokes(gboolean draw)
213 {
214 	draw_strokes = draw;
215 }
216 
217 gboolean
gstroke_draw_strokes(void)218 gstroke_draw_strokes(void)
219 {
220 	return draw_strokes;
221 }
222 
223 void
gstroke_set_mouse_button(gint button)224 gstroke_set_mouse_button(gint button)
225 {
226 	mouse_button = button;
227 }
228 
229 guint
gstroke_get_mouse_button(void)230 gstroke_get_mouse_button(void)
231 {
232 	return mouse_button;
233 }
234 
235 void
gstroke_enable(GtkWidget * widget)236 gstroke_enable (GtkWidget *widget)
237 {
238   struct gstroke_metrics*
239     metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget),
240 														  GSTROKE_METRICS);
241   if (metrics == NULL)
242     {
243       metrics = (struct gstroke_metrics *)g_malloc (sizeof
244                                                     (struct gstroke_metrics));
245       metrics->pointList = NULL;
246       metrics->min_x = 10000;
247       metrics->min_y = 10000;
248       metrics->max_x = 0;
249       metrics->max_y = 0;
250       metrics->point_count = 0;
251 
252       g_object_set_data(G_OBJECT(widget), GSTROKE_METRICS, metrics);
253 
254       g_signal_connect(G_OBJECT(widget), "event",
255 					   G_CALLBACK(process_event), NULL);
256     }
257   else
258     _gstroke_init (metrics);
259 
260   last_mouse_position.invalid = TRUE;
261 }
262 
263 void
gstroke_disable(GtkWidget * widget)264 gstroke_disable(GtkWidget *widget)
265 {
266   g_signal_handlers_disconnect_by_func(G_OBJECT(widget), G_CALLBACK(process_event), NULL);
267 }
268 
269 guint
gstroke_signal_connect(GtkWidget * widget,const gchar * name,void (* func)(GtkWidget * widget,void * data),gpointer data)270 gstroke_signal_connect (GtkWidget *widget,
271                         const gchar *name,
272                         void (*func)(GtkWidget *widget, void *data),
273                         gpointer data)
274 {
275   struct gstroke_func_and_data *func_and_data;
276   GHashTable *hash_table =
277     (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS);
278 
279   if (!hash_table)
280     {
281       hash_table = g_hash_table_new (g_str_hash, g_str_equal);
282       g_object_set_data(G_OBJECT(widget), GSTROKE_SIGNALS,
283 						(gpointer)hash_table);
284     }
285   func_and_data = g_new (struct gstroke_func_and_data, 1);
286   func_and_data->func = func;
287   func_and_data->data = data;
288   g_hash_table_insert (hash_table, (gpointer)name, (gpointer)func_and_data);
289   return TRUE;
290 }
291 
292 static void
gstroke_execute(GtkWidget * widget,const gchar * name)293 gstroke_execute (GtkWidget *widget, const gchar *name)
294 {
295 
296   GHashTable *hash_table =
297     (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS);
298 
299 #if 0
300   purple_debug(PURPLE_DEBUG_MISC, "gestures", "gstroke %s\n", name);
301 #endif
302 
303   if (hash_table)
304     {
305       struct gstroke_func_and_data *fd =
306 	(struct gstroke_func_and_data*)g_hash_table_lookup (hash_table, name);
307       if (fd)
308 	(*fd->func)(widget, fd->data);
309     }
310 }
311 
312 void
gstroke_cleanup(GtkWidget * widget)313 gstroke_cleanup (GtkWidget *widget)
314 {
315   struct gstroke_metrics *metrics;
316   GHashTable *hash_table =
317     (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS);
318   if (hash_table)
319     /*  FIXME: does this delete the elements too?  */
320     g_hash_table_destroy (hash_table);
321 
322   g_object_steal_data(G_OBJECT(widget), GSTROKE_SIGNALS);
323 
324   metrics = (struct gstroke_metrics*)g_object_get_data(G_OBJECT(widget),
325 													   GSTROKE_METRICS);
326   if (metrics)
327     g_free (metrics);
328   g_object_steal_data(G_OBJECT(widget), GSTROKE_METRICS);
329 }
330 
331 
332 /* This function should be written using GTK+ primitives*/
333 static void
gstroke_invisible_window_init(GtkWidget * widget)334 gstroke_invisible_window_init (GtkWidget *widget)
335 {
336   XSetWindowAttributes w_attr;
337   XWindowAttributes orig_w_attr;
338   unsigned long mask, col_border, col_background;
339   unsigned int border_width;
340   XSizeHints hints;
341   Display *disp = GDK_WINDOW_XDISPLAY(gtk_widget_get_window(widget));
342   Window wind = GDK_WINDOW_XWINDOW (gtk_widget_get_window(widget));
343   int screen = DefaultScreen (disp);
344 
345 	if (!gstroke_draw_strokes())
346 		return;
347 
348   gstroke_disp = disp;
349 
350   /* X server should save what's underneath */
351   XGetWindowAttributes (gstroke_disp, wind, &orig_w_attr);
352   hints.x = orig_w_attr.x;
353   hints.y = orig_w_attr.y;
354   hints.width = orig_w_attr.width;
355   hints.height = orig_w_attr.height;
356   mask = CWSaveUnder;
357   w_attr.save_under = True;
358 
359   /* inhibit all the decorations */
360   mask |= CWOverrideRedirect;
361   w_attr.override_redirect = True;
362 
363   /* Don't set a background, transparent window */
364   mask |= CWBackPixmap;
365   w_attr.background_pixmap = None;
366 
367   /* Default input window look */
368   col_background = WhitePixel (gstroke_disp, screen);
369 
370   /* no border for the window */
371 #if 0
372   border_width = 5;
373 #endif
374   border_width = 0;
375 
376   col_border = BlackPixel (gstroke_disp, screen);
377 
378   gstroke_window = XCreateSimpleWindow (gstroke_disp, wind,
379                                         0, 0,
380                                         hints.width - 2 * border_width,
381                                         hints.height - 2 * border_width,
382                                         border_width,
383                                         col_border, col_background);
384 
385   gstroke_gc = XCreateGC (gstroke_disp, gstroke_window, 0, NULL);
386 
387   XSetFunction (gstroke_disp, gstroke_gc, GXinvert);
388 
389   XChangeWindowAttributes (gstroke_disp, gstroke_window, mask, &w_attr);
390 
391   XSetLineAttributes (gstroke_disp, gstroke_gc, 2, LineSolid,
392                       CapButt, JoinMiter);
393   XMapRaised (gstroke_disp, gstroke_window);
394 
395 #if 0
396   /*FIXME: is this call really needed? If yes, does it need the real
397     argc and argv? */
398   hints.flags = PPosition | PSize;
399   XSetStandardProperties (gstroke_disp, gstroke_window, "gstroke_test", NULL,
400                           (Pixmap)NULL, NULL, 0, &hints);
401 
402 
403   /* Receive the close window client message */
404   {
405     /* FIXME: is this really needed? If yes, something should be done
406        with wmdelete...*/
407     Atom wmdelete = XInternAtom (gstroke_disp, "WM_DELETE_WINDOW",
408                                  False);
409     XSetWMProtocols (gstroke_disp, gstroke_window, &wmdelete, True);
410   }
411 #endif
412 }
413