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