mark PurpleImageClass as private
[pidgin-git.git] / pidgin / plugins / gestures / stroke-draw.c
blob276e171be6a9f89a611d34cb5cdb6b679c356274
1 /*
2 GNOME stroke implementation
3 Copyright (c) 2000, 2001 Dan Nicolaescu
4 See the file COPYING for distribution information.
5 */
7 #include "config.h"
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>
16 #include "gstroke.h"
17 #include "gstroke-internal.h"
19 #include <X11/Xlib.h>
20 #include <X11/Xutil.h>
22 #include "gtk3compat.h"
24 static void gstroke_invisible_window_init (GtkWidget *widget);
25 /*FIXME: Maybe these should be put in a structure, and not static...*/
26 static Display * gstroke_disp = NULL;
27 static Window gstroke_window;
28 static GC gstroke_gc;
29 static int mouse_button = 2;
30 static gboolean draw_strokes = FALSE;
32 #define GSTROKE_TIMEOUT_DURATION 10
34 #define GSTROKE_SIGNALS "gstroke_signals"
36 struct gstroke_func_and_data {
37 void (*func)(GtkWidget *, void *);
38 gpointer data;
42 /*FIXME: maybe it's better to just make 2 static variables, not a
43 structure */
44 struct mouse_position {
45 struct s_point last_point;
46 gboolean invalid;
50 static struct mouse_position last_mouse_position;
51 static guint timer_id;
53 static void gstroke_execute (GtkWidget *widget, const gchar *name);
55 static void
56 record_stroke_segment(GtkWidget *widget)
58 gint x, y;
59 struct gstroke_metrics *metrics;
60 GdkDeviceManager *devmgr;
61 GdkDevice *dev;
63 g_return_if_fail(widget != NULL);
65 devmgr = gdk_display_get_device_manager(gtk_widget_get_display(widget));
66 dev = gdk_device_manager_get_client_pointer(devmgr);
67 gdk_window_get_device_position(gtk_widget_get_window(widget),
68 dev, &x, &y, NULL);
70 if (last_mouse_position.invalid)
71 last_mouse_position.invalid = FALSE;
72 else if (gstroke_draw_strokes()) {
73 #if 1
74 XDrawLine(gstroke_disp, gstroke_window, gstroke_gc,
75 last_mouse_position.last_point.x,
76 last_mouse_position.last_point.y, x, y);
77 /* XFlush (gstroke_disp); */
78 #else
79 /* FIXME: this does not work. It will only work if we create
80 * a corresponding GDK window for stroke_window and draw on
81 * that... */
82 gdk_draw_line(gtk_widget_get_window(widget),
83 widget->style->fg_gc[GTK_STATE_NORMAL],
84 last_mouse_position.last_point.x,
85 last_mouse_position.last_point.y, x, y);
86 #endif
89 if (last_mouse_position.last_point.x != x ||
90 last_mouse_position.last_point.y != y)
92 last_mouse_position.last_point.x = x;
93 last_mouse_position.last_point.y = y;
94 metrics = g_object_get_data(G_OBJECT(widget), GSTROKE_METRICS);
95 _gstroke_record (x, y, metrics);
99 static gint
100 gstroke_timeout (gpointer data)
102 GtkWidget *widget;
104 g_return_val_if_fail(data != NULL, FALSE);
106 widget = GTK_WIDGET (data);
107 record_stroke_segment (widget);
109 return TRUE;
112 static void gstroke_cancel(GdkEvent *event)
114 last_mouse_position.invalid = TRUE;
116 if (timer_id > 0)
117 g_source_remove (timer_id);
119 timer_id = 0;
121 if( event != NULL )
122 gdk_device_ungrab(gdk_event_get_device(event), event->button.time);
124 if (gstroke_draw_strokes() && gstroke_disp != NULL) {
125 /* get rid of the invisible stroke window */
126 XUnmapWindow (gstroke_disp, gstroke_window);
127 XFlush (gstroke_disp);
132 static gint
133 process_event (GtkWidget *widget, GdkEvent *event, gpointer data G_GNUC_UNUSED)
135 static GtkWidget *original_widget = NULL;
136 static GdkCursor *cursor = NULL;
138 switch (event->type) {
139 case GDK_BUTTON_PRESS:
140 if (event->button.button != gstroke_get_mouse_button()) {
141 /* Similar to the bug below catch when any other button is
142 * clicked after the middle button is clicked (but possibly
143 * not released)
145 gstroke_cancel(event);
146 original_widget = NULL;
147 break;
150 original_widget = widget; /* remeber the widget where
151 the stroke started */
153 gstroke_invisible_window_init (widget);
155 record_stroke_segment (widget);
157 if (cursor == NULL) {
158 GdkDisplay *display = gtk_widget_get_display(widget);
159 cursor = gdk_cursor_new_for_display(display, GDK_PENCIL);
162 gdk_device_grab(gdk_event_get_device(event),
163 gtk_widget_get_window(widget), GDK_OWNERSHIP_WINDOW,
164 FALSE, GDK_BUTTON_RELEASE_MASK, cursor,
165 event->button.time);
166 timer_id = g_timeout_add (GSTROKE_TIMEOUT_DURATION,
167 gstroke_timeout, widget);
168 return TRUE;
170 case GDK_BUTTON_RELEASE:
171 if ((event->button.button != gstroke_get_mouse_button())
172 || (original_widget == NULL)) {
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;
181 last_mouse_position.invalid = TRUE;
182 original_widget = NULL;
183 g_source_remove (timer_id);
184 gdk_device_ungrab(gdk_event_get_device(event), event->button.time);
185 timer_id = 0;
188 char result[GSTROKE_MAX_SEQUENCE];
189 struct gstroke_metrics *metrics;
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);
199 _gstroke_canonical (result, metrics);
200 gstroke_execute (widget, result);
202 return FALSE;
204 default:
205 break;
208 return FALSE;
211 void
212 gstroke_set_draw_strokes(gboolean draw)
214 draw_strokes = draw;
217 gboolean
218 gstroke_draw_strokes(void)
220 return draw_strokes;
223 void
224 gstroke_set_mouse_button(gint button)
226 mouse_button = button;
229 guint
230 gstroke_get_mouse_button(void)
232 return mouse_button;
235 void
236 gstroke_enable (GtkWidget *widget)
238 struct gstroke_metrics*
239 metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget),
240 GSTROKE_METRICS);
241 if (metrics == NULL)
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;
252 g_object_set_data(G_OBJECT(widget), GSTROKE_METRICS, metrics);
254 g_signal_connect(G_OBJECT(widget), "event",
255 G_CALLBACK(process_event), NULL);
257 else
258 _gstroke_init (metrics);
260 last_mouse_position.invalid = TRUE;
263 void
264 gstroke_disable(GtkWidget *widget)
266 g_signal_handlers_disconnect_by_func(G_OBJECT(widget), G_CALLBACK(process_event), NULL);
269 guint
270 gstroke_signal_connect (GtkWidget *widget,
271 const gchar *name,
272 void (*func)(GtkWidget *widget, void *data),
273 gpointer data)
275 struct gstroke_func_and_data *func_and_data;
276 GHashTable *hash_table =
277 (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS);
279 if (!hash_table)
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);
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;
292 static void
293 gstroke_execute (GtkWidget *widget, const gchar *name)
296 GHashTable *hash_table =
297 (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS);
299 #if 0
300 purple_debug(PURPLE_DEBUG_MISC, "gestures", "gstroke %s\n", name);
301 #endif
303 if (hash_table)
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);
312 void
313 gstroke_cleanup (GtkWidget *widget)
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);
322 g_object_steal_data(G_OBJECT(widget), GSTROKE_SIGNALS);
324 metrics = (struct gstroke_metrics*)g_object_get_data(G_OBJECT(widget),
325 GSTROKE_METRICS);
326 g_free(metrics);
327 g_object_steal_data(G_OBJECT(widget), GSTROKE_METRICS);
331 /* This function should be written using GTK+ primitives*/
332 static void
333 gstroke_invisible_window_init (GtkWidget *widget)
335 XSetWindowAttributes w_attr;
336 XWindowAttributes orig_w_attr;
337 unsigned long mask, col_border, col_background;
338 unsigned int border_width;
339 XSizeHints hints;
340 Display *disp = GDK_WINDOW_XDISPLAY(gtk_widget_get_window(widget));
341 Window wind = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
342 int screen = DefaultScreen (disp);
344 if (!gstroke_draw_strokes())
345 return;
347 gstroke_disp = disp;
349 /* X server should save what's underneath */
350 XGetWindowAttributes (gstroke_disp, wind, &orig_w_attr);
351 hints.x = orig_w_attr.x;
352 hints.y = orig_w_attr.y;
353 hints.width = orig_w_attr.width;
354 hints.height = orig_w_attr.height;
355 mask = CWSaveUnder;
356 w_attr.save_under = True;
358 /* inhibit all the decorations */
359 mask |= CWOverrideRedirect;
360 w_attr.override_redirect = True;
362 /* Don't set a background, transparent window */
363 mask |= CWBackPixmap;
364 w_attr.background_pixmap = None;
366 /* Default input window look */
367 col_background = WhitePixel (gstroke_disp, screen);
369 /* no border for the window */
370 #if 0
371 border_width = 5;
372 #endif
373 border_width = 0;
375 col_border = BlackPixel (gstroke_disp, screen);
377 gstroke_window = XCreateSimpleWindow (gstroke_disp, wind,
378 0, 0,
379 hints.width - 2 * border_width,
380 hints.height - 2 * border_width,
381 border_width,
382 col_border, col_background);
384 gstroke_gc = XCreateGC (gstroke_disp, gstroke_window, 0, NULL);
386 XSetFunction (gstroke_disp, gstroke_gc, GXinvert);
388 XChangeWindowAttributes (gstroke_disp, gstroke_window, mask, &w_attr);
390 XSetLineAttributes (gstroke_disp, gstroke_gc, 2, LineSolid,
391 CapButt, JoinMiter);
392 XMapRaised (gstroke_disp, gstroke_window);
394 #if 0
395 /*FIXME: is this call really needed? If yes, does it need the real
396 argc and argv? */
397 hints.flags = PPosition | PSize;
398 XSetStandardProperties (gstroke_disp, gstroke_window, "gstroke_test", NULL,
399 (Pixmap)NULL, NULL, 0, &hints);
402 /* Receive the close window client message */
404 /* FIXME: is this really needed? If yes, something should be done
405 with wmdelete...*/
406 Atom wmdelete = XInternAtom (gstroke_disp, "WM_DELETE_WINDOW",
407 False);
408 XSetWMProtocols (gstroke_disp, gstroke_window, &wmdelete, True);
410 #endif