PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/pidgin/plugins/gestures/stroke-draw.c

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