1 /*
2 * Gromit-MPX -- a program for painting on the screen
3 *
4 * Gromit Copyright (C) 2000 Simon Budig <Simon.Budig@unix-ag.org>
5 *
6 * Gromit-MPX Copyright (C) 2009,2010 Christian Beier <dontmind@freeshell.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 *
22 */
23
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdint.h>
27 #include "gromit-mpx.h"
28 #include "input.h"
29 #include "callbacks.h"
30 #include "config.h"
31 #include "build-config.h"
32
33
on_expose(GtkWidget * widget,cairo_t * cr,gpointer user_data)34 gboolean on_expose (GtkWidget *widget,
35 cairo_t* cr,
36 gpointer user_data)
37 {
38 GromitData *data = (GromitData *) user_data;
39
40 if(data->debug)
41 g_printerr("DEBUG: got draw event\n");
42
43 cairo_save (cr);
44 cairo_set_source_surface (cr, data->backbuffer, 0, 0);
45 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
46 cairo_paint (cr);
47 cairo_restore (cr);
48
49 return TRUE;
50 }
51
52
53
54
on_configure(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)55 gboolean on_configure (GtkWidget *widget,
56 GdkEventExpose *event,
57 gpointer user_data)
58 {
59 GromitData *data = (GromitData *) user_data;
60
61 if(data->debug)
62 g_printerr("DEBUG: got configure event\n");
63
64 return TRUE;
65 }
66
67
68
on_screen_changed(GtkWidget * widget,GdkScreen * previous_screen,gpointer user_data)69 void on_screen_changed(GtkWidget *widget,
70 GdkScreen *previous_screen,
71 gpointer user_data)
72 {
73 GromitData *data = (GromitData *) user_data;
74
75 if(data->debug)
76 g_printerr("DEBUG: got screen-changed event\n");
77
78 GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET (widget));
79 GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
80 if (visual == NULL)
81 visual = gdk_screen_get_system_visual (screen);
82
83 gtk_widget_set_visual (widget, visual);
84 }
85
86
87
on_monitors_changed(GdkScreen * screen,gpointer user_data)88 void on_monitors_changed ( GdkScreen *screen,
89 gpointer user_data)
90 {
91 GromitData *data = (GromitData *) user_data;
92
93 // get new sizes
94 data->width = gdk_screen_get_width (data->screen);
95 data->height = gdk_screen_get_height (data->screen);
96
97 if(data->debug)
98 g_printerr("DEBUG: screen size changed to %d x %d!\n", data->width, data->height);
99
100 // change size
101 gtk_widget_set_size_request(GTK_WIDGET(data->win), data->width, data->height);
102 // try to set transparent for input
103 cairo_region_t* r = cairo_region_create();
104 gtk_widget_input_shape_combine_region(data->win, r);
105 cairo_region_destroy(r);
106
107 /* recreate the shape surface */
108 cairo_surface_t *new_shape = cairo_image_surface_create(CAIRO_FORMAT_ARGB32 ,data->width, data->height);
109 cairo_t *cr = cairo_create (new_shape);
110 cairo_set_source_surface (cr, data->backbuffer, 0, 0);
111 cairo_paint (cr);
112 cairo_destroy (cr);
113 cairo_surface_destroy(data->backbuffer);
114 data->backbuffer = new_shape;
115
116 /*
117 these depend on the shape surface
118 */
119 GHashTableIter it;
120 gpointer value;
121 g_hash_table_iter_init (&it, data->tool_config);
122 while (g_hash_table_iter_next (&it, NULL, &value))
123 paint_context_free(value);
124 g_hash_table_remove_all(data->tool_config);
125
126
127 parse_config(data); // also calls paint_context_new() :-(
128
129
130 data->default_pen = paint_context_new (data, GROMIT_PEN,
131 data->red, 7, 0, 1);
132 data->default_eraser = paint_context_new (data, GROMIT_ERASER,
133 data->red, 75, 0, 1);
134
135 if(!data->composited) // set shape
136 {
137 cairo_region_t* r = gdk_cairo_region_create_from_surface(data->backbuffer);
138 gtk_widget_shape_combine_region(data->win, r);
139 cairo_region_destroy(r);
140 }
141
142 setup_input_devices(data);
143
144
145 gtk_widget_show_all (data->win);
146 }
147
148
149
on_composited_changed(GdkScreen * screen,gpointer user_data)150 void on_composited_changed ( GdkScreen *screen,
151 gpointer user_data)
152 {
153 GromitData *data = (GromitData *) user_data;
154
155 if(data->debug)
156 g_printerr("DEBUG: got composited-changed event\n");
157
158 data->composited = gdk_screen_is_composited (data->screen);
159
160 if(data->composited)
161 {
162 // undo shape
163 gtk_widget_shape_combine_region(data->win, NULL);
164 // re-apply transparency
165 gtk_widget_set_opacity(data->win, 0.75);
166 }
167
168 // set anti-aliasing
169 GHashTableIter it;
170 gpointer value;
171 g_hash_table_iter_init (&it, data->tool_config);
172 while (g_hash_table_iter_next (&it, NULL, &value))
173 {
174 GromitPaintContext *context = value;
175 cairo_set_antialias(context->paint_ctx, data->composited ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
176 }
177
178
179 GdkRectangle rect = {0, 0, data->width, data->height};
180 gdk_window_invalidate_rect(gtk_widget_get_window(data->win), &rect, 0);
181 }
182
183
184
on_clientapp_selection_get(GtkWidget * widget,GtkSelectionData * selection_data,guint info,guint time,gpointer user_data)185 void on_clientapp_selection_get (GtkWidget *widget,
186 GtkSelectionData *selection_data,
187 guint info,
188 guint time,
189 gpointer user_data)
190 {
191 GromitData *data = (GromitData *) user_data;
192
193 gchar *ans = "";
194
195 if(data->debug)
196 g_printerr("DEBUG: clientapp received request.\n");
197
198 if (gtk_selection_data_get_target(selection_data) == GA_TOGGLEDATA)
199 {
200 ans = data->clientdata;
201 }
202
203 gtk_selection_data_set (selection_data,
204 gtk_selection_data_get_target(selection_data),
205 8, (guchar*)ans, strlen (ans));
206 }
207
208
on_clientapp_selection_received(GtkWidget * widget,GtkSelectionData * selection_data,guint time,gpointer user_data)209 void on_clientapp_selection_received (GtkWidget *widget,
210 GtkSelectionData *selection_data,
211 guint time,
212 gpointer user_data)
213 {
214 GromitData *data = (GromitData *) user_data;
215
216 /* If someone has a selection for us, Gromit is already running. */
217
218 if(gtk_selection_data_get_data_type(selection_data) == GDK_NONE)
219 data->client = 0;
220 else
221 data->client = 1;
222
223 gtk_main_quit ();
224 }
225
226
227
228 static float line_thickener = 0;
229
230
on_buttonpress(GtkWidget * win,GdkEventButton * ev,gpointer user_data)231 gboolean on_buttonpress (GtkWidget *win,
232 GdkEventButton *ev,
233 gpointer user_data)
234 {
235 GromitData *data = (GromitData *) user_data;
236 gdouble pressure = 1;
237
238 /* get the data for this device */
239 GromitDeviceData *devdata = g_hash_table_lookup(data->devdatatable, ev->device);
240
241 if(data->debug)
242 g_printerr("DEBUG: Device '%s': Button %i Down State %d at (x,y)=(%.2f : %.2f)\n",
243 gdk_device_get_name(ev->device), ev->button, ev->state, ev->x, ev->y);
244
245 if (!devdata->is_grabbed)
246 return FALSE;
247
248 if (gdk_device_get_source(gdk_event_get_source_device((GdkEvent *)ev)) == GDK_SOURCE_PEN) {
249 /* Do not drop unprocessed motion events. Smoother drawing for pens of tablets. */
250 gdk_window_set_event_compression(gtk_widget_get_window(data->win), FALSE);
251 } else {
252 /* For all other source types, set back to default. Otherwise, lines were only
253 fully drawn to the end on button release. */
254 gdk_window_set_event_compression(gtk_widget_get_window(data->win), TRUE);
255 }
256
257 /* See GdkModifierType. Am I fixing a Gtk misbehaviour??? */
258 ev->state |= 1 << (ev->button + 7);
259
260
261 if (ev->state != devdata->state ||
262 devdata->lastslave != gdk_event_get_source_device ((GdkEvent *) ev))
263 select_tool (data, ev->device, gdk_event_get_source_device ((GdkEvent *) ev), ev->state);
264
265 devdata->lastx = ev->x;
266 devdata->lasty = ev->y;
267 devdata->motion_time = ev->time;
268
269 snap_undo_state (data);
270
271 gdk_event_get_axis ((GdkEvent *) ev, GDK_AXIS_PRESSURE, &pressure);
272 data->maxwidth = (CLAMP (pressure + line_thickener, 0, 1) *
273 (double) (devdata->cur_context->width -
274 devdata->cur_context->minwidth) +
275 devdata->cur_context->minwidth);
276
277 if (ev->button <= 5)
278 draw_line (data, ev->device, ev->x, ev->y, ev->x, ev->y);
279
280 coord_list_prepend (data, ev->device, ev->x, ev->y, data->maxwidth);
281
282 return TRUE;
283 }
284
285
on_motion(GtkWidget * win,GdkEventMotion * ev,gpointer user_data)286 gboolean on_motion (GtkWidget *win,
287 GdkEventMotion *ev,
288 gpointer user_data)
289 {
290 GromitData *data = (GromitData *) user_data;
291 GdkTimeCoord **coords = NULL;
292 gint nevents;
293 int i;
294 gdouble pressure = 1;
295 /* get the data for this device */
296 GromitDeviceData *devdata = g_hash_table_lookup(data->devdatatable, ev->device);
297
298 if (!devdata->is_grabbed)
299 return FALSE;
300
301 if(data->debug)
302 g_printerr("DEBUG: Device '%s': motion to (x,y)=(%.2f : %.2f)\n", gdk_device_get_name(ev->device), ev->x, ev->y);
303
304 if (ev->state != devdata->state ||
305 devdata->lastslave != gdk_event_get_source_device ((GdkEvent *) ev))
306 select_tool (data, ev->device, gdk_event_get_source_device ((GdkEvent *) ev), ev->state);
307
308 gdk_device_get_history (ev->device, ev->window,
309 devdata->motion_time, ev->time,
310 &coords, &nevents);
311
312 if(!data->xinerama && nevents > 0)
313 {
314 for (i=0; i < nevents; i++)
315 {
316 gdouble x, y;
317
318 gdk_device_get_axis (ev->device, coords[i]->axes,
319 GDK_AXIS_PRESSURE, &pressure);
320 if (pressure > 0)
321 {
322 data->maxwidth = (CLAMP (pressure + line_thickener, 0, 1) *
323 (double) (devdata->cur_context->width -
324 devdata->cur_context->minwidth) +
325 devdata->cur_context->minwidth);
326
327 gdk_device_get_axis(ev->device, coords[i]->axes,
328 GDK_AXIS_X, &x);
329 gdk_device_get_axis(ev->device, coords[i]->axes,
330 GDK_AXIS_Y, &y);
331
332 draw_line (data, ev->device, devdata->lastx, devdata->lasty, x, y);
333
334 coord_list_prepend (data, ev->device, x, y, data->maxwidth);
335 devdata->lastx = x;
336 devdata->lasty = y;
337 }
338 }
339
340 devdata->motion_time = coords[nevents-1]->time;
341 g_free (coords);
342 }
343
344 /* always paint to the current event coordinate. */
345 gdk_event_get_axis ((GdkEvent *) ev, GDK_AXIS_PRESSURE, &pressure);
346
347 if (pressure > 0)
348 {
349 data->maxwidth = (CLAMP (pressure + line_thickener, 0, 1) *
350 (double) (devdata->cur_context->width -
351 devdata->cur_context->minwidth) +
352 devdata->cur_context->minwidth);
353
354 if(devdata->motion_time > 0)
355 {
356 draw_line (data, ev->device, devdata->lastx, devdata->lasty, ev->x, ev->y);
357 coord_list_prepend (data, ev->device, ev->x, ev->y, data->maxwidth);
358 }
359 }
360
361 devdata->lastx = ev->x;
362 devdata->lasty = ev->y;
363 devdata->motion_time = ev->time;
364
365 return TRUE;
366 }
367
368
on_buttonrelease(GtkWidget * win,GdkEventButton * ev,gpointer user_data)369 gboolean on_buttonrelease (GtkWidget *win,
370 GdkEventButton *ev,
371 gpointer user_data)
372 {
373 GromitData *data = (GromitData *) user_data;
374 /* get the device data for this event */
375 GromitDeviceData *devdata = g_hash_table_lookup(data->devdatatable, ev->device);
376
377 gfloat direction = 0;
378 gint width = 0;
379 if(devdata->cur_context)
380 width = devdata->cur_context->arrowsize * devdata->cur_context->width / 2;
381
382
383 if ((ev->x != devdata->lastx) ||
384 (ev->y != devdata->lasty))
385 on_motion(win, (GdkEventMotion *) ev, user_data);
386
387 if (!devdata->is_grabbed)
388 return FALSE;
389
390 if (devdata->cur_context->arrowsize != 0 &&
391 coord_list_get_arrow_param (data, ev->device, width * 3,
392 &width, &direction))
393 draw_arrow (data, ev->device, ev->x, ev->y, width, direction);
394
395 coord_list_free (data, ev->device);
396
397 return TRUE;
398 }
399
400 /* Remote control */
on_mainapp_selection_get(GtkWidget * widget,GtkSelectionData * selection_data,guint info,guint time,gpointer user_data)401 void on_mainapp_selection_get (GtkWidget *widget,
402 GtkSelectionData *selection_data,
403 guint info,
404 guint time,
405 gpointer user_data)
406 {
407 GromitData *data = (GromitData *) user_data;
408
409 gchar *uri = "OK";
410 GdkAtom action = gtk_selection_data_get_target(selection_data);
411
412 if(action == GA_TOGGLE)
413 {
414 /* ask back client for device id */
415 gtk_selection_convert (data->win, GA_DATA,
416 GA_TOGGLEDATA, time);
417 gtk_main(); /* Wait for the response */
418 }
419 else if (action == GA_VISIBILITY)
420 toggle_visibility (data);
421 else if (action == GA_CLEAR)
422 clear_screen (data);
423 else if (action == GA_RELOAD)
424 setup_input_devices(data);
425 else if (action == GA_QUIT)
426 gtk_main_quit ();
427 else if (action == GA_UNDO)
428 undo_drawing (data);
429 else if (action == GA_REDO)
430 redo_drawing (data);
431 else
432 uri = "NOK";
433
434
435 gtk_selection_data_set (selection_data,
436 gtk_selection_data_get_target(selection_data),
437 8, (guchar*)uri, strlen (uri));
438 }
439
440
on_mainapp_selection_received(GtkWidget * widget,GtkSelectionData * selection_data,guint time,gpointer user_data)441 void on_mainapp_selection_received (GtkWidget *widget,
442 GtkSelectionData *selection_data,
443 guint time,
444 gpointer user_data)
445 {
446 GromitData *data = (GromitData *) user_data;
447
448 if(gtk_selection_data_get_length(selection_data) < 0)
449 {
450 if(data->debug)
451 g_printerr("DEBUG: mainapp got no answer back from client.\n");
452 }
453 else
454 {
455 if(gtk_selection_data_get_target(selection_data) == GA_TOGGLEDATA )
456 {
457 intptr_t dev_nr = strtoull((gchar*)gtk_selection_data_get_data(selection_data), NULL, 10);
458
459 if(data->debug)
460 g_printerr("DEBUG: mainapp got toggle id '%ld' back from client.\n", (long)dev_nr);
461
462 if(dev_nr < 0)
463 toggle_grab(data, NULL); /* toggle all */
464 else
465 {
466 /* find dev numbered dev_nr */
467 GHashTableIter it;
468 gpointer value;
469 GromitDeviceData* devdata = NULL;
470 g_hash_table_iter_init (&it, data->devdatatable);
471 while (g_hash_table_iter_next (&it, NULL, &value))
472 {
473 devdata = value;
474 if(devdata->index == dev_nr)
475 break;
476 else
477 devdata = NULL;
478 }
479
480 if(devdata)
481 toggle_grab(data, devdata->device);
482 else
483 g_printerr("ERROR: No device at index %ld.\n", (long)dev_nr);
484 }
485 }
486 }
487
488 gtk_main_quit ();
489 }
490
491
on_device_removed(GdkDeviceManager * device_manager,GdkDevice * device,gpointer user_data)492 void on_device_removed (GdkDeviceManager *device_manager,
493 GdkDevice *device,
494 gpointer user_data)
495 {
496 GromitData *data = (GromitData *) user_data;
497
498 if(!gdk_device_get_device_type(device) == GDK_DEVICE_TYPE_MASTER
499 || gdk_device_get_n_axes(device) < 2)
500 return;
501
502 if(data->debug)
503 g_printerr("DEBUG: device '%s' removed\n", gdk_device_get_name(device));
504
505 setup_input_devices(data);
506 }
507
on_device_added(GdkDeviceManager * device_manager,GdkDevice * device,gpointer user_data)508 void on_device_added (GdkDeviceManager *device_manager,
509 GdkDevice *device,
510 gpointer user_data)
511 {
512 GromitData *data = (GromitData *) user_data;
513
514 if(!gdk_device_get_device_type(device) == GDK_DEVICE_TYPE_MASTER
515 || gdk_device_get_n_axes(device) < 2)
516 return;
517
518 if(data->debug)
519 g_printerr("DEBUG: device '%s' added\n", gdk_device_get_name(device));
520
521 setup_input_devices(data);
522 }
523
524
525
on_toggle_paint(GtkWidget * widget,GdkEventButton * ev,gpointer user_data)526 gboolean on_toggle_paint(GtkWidget *widget,
527 GdkEventButton *ev,
528 gpointer user_data)
529 {
530 GromitData *data = (GromitData *) user_data;
531
532 if(data->debug)
533 g_printerr("DEBUG: Device '%s': Button %i on_toggle_paint at (x,y)=(%.2f : %.2f)\n",
534 gdk_device_get_name(ev->device), ev->button, ev->x, ev->y);
535
536 toggle_grab(data, ev->device);
537
538 return TRUE;
539 }
540
on_toggle_paint_all(GtkMenuItem * menuitem,gpointer user_data)541 void on_toggle_paint_all (GtkMenuItem *menuitem,
542 gpointer user_data)
543 {
544 GromitData *data = (GromitData *) user_data;
545
546 /*
547 on_toggle_paint_all() is called when toggle_paint_item in the menu
548 is clicked on KDE-like platforms. Under X11, at least
549 https://github.com/ubuntu/gnome-shell-extension-appindicator seems to
550 grab the pointer, preventing grabbing by Gromit-MPX.
551 Simply work around this by waiting :-/
552 */
553 char *xdg_session_type = getenv("XDG_SESSION_TYPE");
554 if (xdg_session_type && strcmp(xdg_session_type, "x11") == 0)
555 g_usleep(333*1000);
556
557 toggle_grab(data, NULL);
558 }
559
560
on_clear(GtkMenuItem * menuitem,gpointer user_data)561 void on_clear (GtkMenuItem *menuitem,
562 gpointer user_data)
563 {
564 GromitData *data = (GromitData *) user_data;
565 clear_screen(data);
566 }
567
568
on_toggle_vis(GtkMenuItem * menuitem,gpointer user_data)569 void on_toggle_vis(GtkMenuItem *menuitem,
570 gpointer user_data)
571 {
572 GromitData *data = (GromitData *) user_data;
573 toggle_visibility(data);
574 }
575
576
on_thicker_lines(GtkMenuItem * menuitem,gpointer user_data)577 void on_thicker_lines(GtkMenuItem *menuitem,
578 gpointer user_data)
579 {
580 line_thickener += 0.1;
581 }
582
on_thinner_lines(GtkMenuItem * menuitem,gpointer user_data)583 void on_thinner_lines(GtkMenuItem *menuitem,
584 gpointer user_data)
585 {
586 line_thickener -= 0.1;
587 if (line_thickener < -1)
588 line_thickener = -1;
589 }
590
591
on_opacity_bigger(GtkMenuItem * menuitem,gpointer user_data)592 void on_opacity_bigger(GtkMenuItem *menuitem,
593 gpointer user_data)
594 {
595 GromitData *data = (GromitData *) user_data;
596 data->opacity += 0.1;
597 if(data->opacity>1.0)
598 data->opacity = 1.0;
599 gtk_widget_set_opacity(data->win, data->opacity);
600 }
601
on_opacity_lesser(GtkMenuItem * menuitem,gpointer user_data)602 void on_opacity_lesser(GtkMenuItem *menuitem,
603 gpointer user_data)
604 {
605 GromitData *data = (GromitData *) user_data;
606 data->opacity -= 0.1;
607 if(data->opacity<0.0)
608 data->opacity = 0.0;
609 gtk_widget_set_opacity(data->win, data->opacity);
610 }
611
612
on_undo(GtkMenuItem * menuitem,gpointer user_data)613 void on_undo(GtkMenuItem *menuitem,
614 gpointer user_data)
615 {
616 GromitData *data = (GromitData *) user_data;
617 undo_drawing (data);
618 }
619
on_redo(GtkMenuItem * menuitem,gpointer user_data)620 void on_redo(GtkMenuItem *menuitem,
621 gpointer user_data)
622 {
623 GromitData *data = (GromitData *) user_data;
624 redo_drawing (data);
625 }
626
627
on_about(GtkMenuItem * menuitem,gpointer user_data)628 void on_about(GtkMenuItem *menuitem,
629 gpointer user_data)
630 {
631 const gchar *authors [] = { "Christian Beier <dontmind@freeshell.org>",
632 "Simon Budig <Simon.Budig@unix-ag.org>",
633 "Barak A. Pearlmutter <barak+git@pearlmutter.net>",
634 "Nathan Whitehead <nwhitehe@gmail.com>",
635 "Lukáš Hermann <tuxilero@gmail.com>",
636 "Katie Holly <git@meo.ws>",
637 "Monty Montgomery <xiphmont@gmail.com>",
638 "AlisterH <alister.hood@gmail.com>",
639 "Mehmet Atif Ergun <mehmetaergun@users.noreply.github.com>",
640 "Russel Winder <russel@winder.org.uk>",
641 "Tao Klerks <tao@klerks.biz>",
642 "Tobias Schönberg <tobias47n9e@gmail.com>",
643 "Yuri D'Elia <yuri.delia@eurac.edu>",
644 "Julián Unrrein <junrrein@gmail.com>",
645 "Eshant Gupta <guptaeshant@gmail.com>",
646 NULL };
647 gtk_show_about_dialog (NULL,
648 "program-name", "Gromit-MPX",
649 "logo-icon-name", "net.christianbeier.Gromit-MPX",
650 "title", "About Gromit-MPX",
651 "comments", "Gromit-MPX (GRaphics Over MIscellaneous Things - Multi-Pointer-EXtension) is an on-screen annotation tool that works with any Unix desktop environment under X11 as well as Wayland.",
652 "version", VERSION,
653 "website", "https://github.com/bk138/gromit-mpx",
654 "authors", authors,
655 "copyright", "2009-2022 Christian Beier, Copyright 2000 Simon Budig",
656 "license-type", GTK_LICENSE_GPL_2_0,
657 NULL);
658 }
659
660
on_intro_show_again_button_toggled(GtkCheckButton * toggle,GromitData * data)661 static void on_intro_show_again_button_toggled(GtkCheckButton *toggle, GromitData *data)
662 {
663 data->show_intro_on_startup = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle));
664 }
665
on_intro(GtkMenuItem * menuitem,gpointer user_data)666 void on_intro(GtkMenuItem *menuitem,
667 gpointer user_data)
668 {
669 GromitData *data = (GromitData *) user_data;
670
671 // Create a new assistant widget with no pages.
672 GtkWidget *assistant = gtk_assistant_new ();
673 gtk_window_set_position (GTK_WINDOW(assistant), GTK_WIN_POS_CENTER);
674
675 // set page one
676 GtkWidget *widgetOne = gtk_label_new ("Gromit-MPX (GRaphics Over MIscellaneous Things) is a small tool to make\n"
677 "annotations on the screen.\n\n"
678 "Its main use is for making presentations of some application. Normally,\n"
679 "you would have to move the mouse pointer around the point of interest\n"
680 "until hopefully everybody noticed it. With Gromit-MPX, you can draw\n"
681 "everywhere onto the screen, highlighting some button or area.\n\n"
682 "If you happen to enjoy using Gromit-MPX, please consider supporting\n"
683 "its development by using one of the donation options on the project's\n"
684 "website or directly via the support options available from the tray menu.\n");
685 gtk_assistant_append_page (GTK_ASSISTANT (assistant), widgetOne);
686 gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), widgetOne, "Gromit-MPX - What is it?");
687 gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), widgetOne, GTK_ASSISTANT_PAGE_INTRO);
688 gtk_assistant_set_page_complete (GTK_ASSISTANT (assistant), widgetOne, TRUE);
689
690 // set page two
691 GtkWidget *widgetTwo = gtk_label_new (NULL);
692 char widgetTwoBuf[4096];
693 snprintf(widgetTwoBuf, sizeof(widgetTwoBuf),
694 "You can operate Gromit-MPX using its tray icon (if your desktop environment\n"
695 "provides a sys tray), but since you typically want to use the program you are\n"
696 "demonstrating and highlighting something is a short interruption of your\n"
697 "workflow, Gromit-MPX can be toggled on and off on the fly via a hotkey:\n\n"
698 "It grabs the `%s` and `%s` keys, so that no other application can use them\n"
699 "and they are available to Gromit-MPX only. The available commands are:\n\n<tt><b>"
700 " toggle painting: %s\n"
701 " clear screen: SHIFT-%s\n"
702 " toggle visibility: CTRL-%s\n"
703 " quit: ALT-%s\n"
704 " undo last stroke: %s\n"
705 " redo last undone stroke: SHIFT-%s</b></tt>",
706 data->hot_keyval, data->undo_keyval,
707 data->hot_keyval, data->hot_keyval, data->hot_keyval, data->hot_keyval, data->undo_keyval, data->undo_keyval);
708 gtk_label_set_markup (GTK_LABEL (widgetTwo), widgetTwoBuf);
709 gtk_assistant_append_page (GTK_ASSISTANT (assistant), widgetTwo);
710 gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), widgetTwo, "Gromit-MPX - How to use it");
711 gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), widgetTwo, GTK_ASSISTANT_PAGE_CONTENT);
712 gtk_assistant_set_page_complete (GTK_ASSISTANT (assistant), widgetTwo, TRUE);
713
714 // set page three
715 GtkWidget *widgetThree = gtk_grid_new ();
716 GtkWidget *widgetThreeText = gtk_label_new ("Do you want to show this introduction again on the next start of Gromit-MPX?\n"
717 "You can always access it again via the sys tray menu.\n");
718 GtkWidget *widgetThreeButton = gtk_check_button_new_with_label ("Show again on startup");
719 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgetThreeButton), data->show_intro_on_startup);
720 gtk_grid_attach (GTK_GRID (widgetThree), widgetThreeText, 0, 0, 1, 1);
721 gtk_grid_attach_next_to (GTK_GRID (widgetThree), widgetThreeButton, widgetThreeText, GTK_POS_BOTTOM, 1, 1);
722 g_signal_connect (G_OBJECT (widgetThreeButton), "toggled",
723 G_CALLBACK (on_intro_show_again_button_toggled), data);
724 gtk_assistant_append_page (GTK_ASSISTANT (assistant), widgetThree);
725 gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), widgetThree, GTK_ASSISTANT_PAGE_CONFIRM);
726 gtk_assistant_set_page_complete (GTK_ASSISTANT (assistant), widgetThree, TRUE);
727
728 // connect the close buttons
729 g_signal_connect (G_OBJECT (assistant), "cancel",
730 G_CALLBACK (gtk_widget_destroy), NULL);
731 g_signal_connect (G_OBJECT (assistant), "close",
732 G_CALLBACK (gtk_widget_destroy), NULL);
733
734 // show
735 gtk_widget_show_all (assistant);
736 }
737
on_support_liberapay(GtkMenuItem * menuitem,gpointer user_data)738 void on_support_liberapay(GtkMenuItem *menuitem, gpointer user_data)
739 {
740 gtk_show_uri_on_window (NULL,
741 "https://liberapay.com/bk138",
742 GDK_CURRENT_TIME,
743 NULL);
744
745 }
746
on_support_patreon(GtkMenuItem * menuitem,gpointer user_data)747 void on_support_patreon(GtkMenuItem *menuitem, gpointer user_data)
748 {
749 gtk_show_uri_on_window (NULL,
750 "https://patreon.com/bk138",
751 GDK_CURRENT_TIME,
752 NULL);
753
754 }
755
on_support_paypal(GtkMenuItem * menuitem,gpointer user_data)756 void on_support_paypal(GtkMenuItem *menuitem, gpointer user_data)
757 {
758 gtk_show_uri_on_window (NULL,
759 "https://www.paypal.com/donate?hosted_button_id=N7GSSPRPUSTPU",
760 GDK_CURRENT_TIME,
761 NULL);
762
763 }
764