1 /*	mygtk.h
2 	Copyright (C) 2004-2021 Mark Tyler and Dmitry Groshev
3 
4 	This file is part of mtPaint.
5 
6 	mtPaint is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 3 of the License, or
9 	(at your option) any later version.
10 
11 	mtPaint is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with mtPaint in the file COPYING.
18 */
19 
20 #include <gtk/gtk.h>
21 #include <gdk/gdkkeysyms.h>
22 #include <math.h>
23 #include <stdarg.h>
24 #include <stddef.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <limits.h>
29 #include <dirent.h>
30 #include <sys/stat.h>
31 
32 ///	GTK+2 version to use
33 
34 #if GTK_MAJOR_VERSION == 2
35 #ifndef GTK2VERSION
36 #define GTK2VERSION GTK_MINOR_VERSION
37 #endif
38 #endif
39 
40 ///	GTK+3 version to use
41 
42 #if GTK_MAJOR_VERSION == 3
43 #ifndef GTK3VERSION
44 #define GTK3VERSION GTK_MINOR_VERSION
45 #endif
46 #endif
47 
48 ///	List widgets to use
49 
50 #if GTK_MAJOR_VERSION == 1
51 #undef U_LISTS_GTK1
52 #define U_LISTS_GTK1
53 
54 #elif GTK_MAJOR_VERSION == 2
55 #if GTK2VERSION < 18
56 #undef U_LISTS_GTK1
57 #define U_LISTS_GTK1
58 #endif
59 
60 #else /* GTK_MAJOR_VERSION == 3 */
61 #undef U_LISTS_GTK1
62 #endif
63 
64 ///	Meaningless differences to hide
65 
66 #if GTK_MAJOR_VERSION == 3
67 #define g_thread_init(A) /* Not needed anymore */
68 #define GtkSignalFunc GCallback
69 #define GTK_SIGNAL_FUNC(A) G_CALLBACK(A)
70 #define GTK_OBJECT(A) G_OBJECT(A)
71 #define gtk_signal_connect(A,B,C,D) g_signal_connect(A,B,C,D)
72 #define gtk_signal_connect_object(A,B,C,D) g_signal_connect_swapped(A,B,C,D)
73 #define gtk_signal_disconnect_by_data(A,B) g_signal_handlers_disconnect_by_data(A,B)
74 #define gtk_signal_emit_by_name g_signal_emit_by_name
75 
76 #define KEY(A) GDK_KEY_##A
77 
78 /* Only one target backend */
79 #ifdef GDK_WINDOWING_X11
80 #undef GDK_WINDOWING_QUARTZ
81 #endif
82 
83 #else
84 #define gtk_widget_get_parent(A) ((A)->parent)
85 #define gtk_widget_get_window(A) ((A)->window)
86 #define gdk_device_get_name(A) ((A)->name)
87 #define gdk_device_get_n_axes(A) ((A)->num_axes)
88 #define gdk_device_get_mode(A) ((A)->mode)
89 #define KEY(A) GDK_##A
90 #endif
91 
92 #ifndef G_GNUC_BEGIN_IGNORE_DEPRECATIONS
93 #define G_GNUC_BEGIN_IGNORE_DEPRECATIONS
94 #define G_GNUC_END_IGNORE_DEPRECATIONS
95 #endif
96 
97 ///	Icon descriptor type
98 
99 typedef void *xpm_icon_desc[2];
100 
101 #if GTK_MAJOR_VERSION == 1
102 #define XPM_TYPE char**
103 
104 #else /* if GTK_MAJOR_VERSION >= 2 */
105 #define XPM_TYPE void**
106 #endif
107 
108 ///	Generic RGB buffer
109 
110 typedef struct {
111 	int xy[4];
112 	unsigned char *rgb;
113 } rgbcontext;
114 
115 ///	Main toplevel, for anchoring dialogs and rendering pixmaps
116 
117 GtkWidget *main_window;
118 
119 ///	Generic Widget Primitives
120 
121 GtkWidget *add_a_window(GtkWindowType type, char *title, GtkWindowPosition pos);
122 GtkWidget *add_a_spin( int value, int min, int max );
123 
124 int user_break;
125 
126 void progress_init(char *text, int canc);		// Initialise progress window
127 int progress_update(float val);				// Update progress window
128 void progress_end();					// Close progress window
129 
130 int alert_box(char *title, char *message, char *text1, ...);
131 
132 //	Tablet handling
133 
134 #if GTK_MAJOR_VERSION == 1
135 GdkDeviceInfo *tablet_device;
136 #else /* #if GTK_MAJOR_VERSION >= 2 */
137 GdkDevice *tablet_device;
138 #endif
139 void init_tablet();
140 void conf_tablet(void **slot);
141 void conf_done(void *cause);
142 
143 // GTK+3 specific support code
144 
145 #if GTK_MAJOR_VERSION == 3
146 void cairo_surface_fdestroy(cairo_surface_t *s);
147 cairo_surface_t *cairo_upload_rgb(cairo_surface_t *ref, GdkWindow *win,
148 	unsigned char *src, int w, int h, int len);
149 void cairo_set_rgb(cairo_t *cr, int c);
150 /* Prevent color bleed on HiDPI */
151 void cairo_unfilter(cairo_t *cr);
152 void css_restyle(GtkWidget *w, char *css, char *class, char *name);
153 void add_css_class(GtkWidget *w, char *class);
154 /* Add CSS, builtin and user-provided, to default screen */
155 void init_css(char *cssfile);
156 /* Find button widget of a GtkComboBox with entry */
157 GtkWidget *combobox_button(GtkWidget *cbox);
158 void get_padding_and_border(GtkStyleContext *ctx, GtkBorder *pad, GtkBorder *bor,
159 	GtkBorder *both);
160 /* Find out which set of bugs & breakages is active */
161 int gtk3version;
162 #else
163 #define add_css_class(A,B)
164 #endif
165 
166 // Slider-spin combo (a decorated spinbutton)
167 
168 GtkWidget *mt_spinslide_new(int swidth, int sheight);
169 #define ADJ2INT(a) ((int)rint((a)->value))
170 
171 // Self-contained package of radio buttons
172 
173 GtkWidget *wj_radio_pack(char **names, int cnt, int vnum, int idx, void **r,
174 	GtkSignalFunc handler);
175 int wj_radio_pack_get_active(GtkWidget *widget);
176 
177 // Easier way with spinbuttons
178 
179 int read_spin(GtkWidget *spin);
180 double read_float_spin(GtkWidget *spin);
181 GtkWidget *add_float_spin(double value, double min, double max);
182 void spin_connect(GtkWidget *spin, GtkSignalFunc handler, gpointer user_data);
183 #if GTK_MAJOR_VERSION == 1
184 void spin_set_range(GtkWidget *spin, int min, int max);
185 #else
186 #define spin_set_range(spin, min, max) \
187 	gtk_spin_button_set_range(GTK_SPIN_BUTTON(spin), (min), (max))
188 #endif
189 
190 // Box unpacking macros
191 #if GTK_MAJOR_VERSION <= 2
192 #define BOX_CHILD_0(box) \
193 	(((GtkBoxChild*)GTK_BOX(box)->children->data)->widget)
194 #define BOX_CHILD_1(box) \
195 	(((GtkBoxChild*)GTK_BOX(box)->children->next->data)->widget)
196 #define BOX_CHILD_2(box) \
197 	(((GtkBoxChild*)GTK_BOX(box)->children->next->next->data)->widget)
198 #define BOX_CHILD(box, n) \
199 	(((GtkBoxChild *)g_list_nth_data(GTK_BOX(box)->children, (n)))->widget)
200 #endif
201 
202 // Wrapper for utf8->C and C->utf8 translation
203 
204 char *gtkxncpy(char *dest, const char *src, int cnt, int u);
205 #define gtkncpy(dest, src, cnt) gtkxncpy(dest, src, cnt, FALSE)
206 #define gtkuncpy(dest, src, cnt) gtkxncpy(dest, src, cnt, TRUE)
207 
208 // Generic wrapper for strncpy(), ensuring NUL termination
209 
210 #define strncpy0(A,B,C) (strncpy((A), (B), (C))[(C) - 1] = 0)
211 
212 // A more sane replacement for strncat()
213 
214 char *strnncat(char *dest, const char *src, int max);
215 
216 // Add C strings to a string with explicit length
217 
218 char *wjstrcat(char *dest, int max, const char *s0, int l, ...);
219 
220 // Add directory to filename
221 
222 char *file_in_dir(char *dest, const char *dir, const char *file, int cnt);
223 char *file_in_homedir(char *dest, const char *file, int cnt);
224 
225 // Set minimum size for a widget
226 
227 void widget_set_minsize(GtkWidget *widget, int width, int height);
228 GtkWidget *widget_align_minsize(GtkWidget *widget, int width, int height);
229 
230 // Make widget request no less size than before (in one direction)
231 
232 void widget_set_keepsize(GtkWidget *widget, int keep_height);
233 
234 // Workaround for GtkCList reordering bug in GTK2
235 
236 void clist_enable_drag(GtkWidget *clist);
237 
238 // Most common use of boxes
239 
240 GtkWidget *pack(GtkWidget *box, GtkWidget *widget);
241 GtkWidget *xpack(GtkWidget *box, GtkWidget *widget);
242 GtkWidget *pack_end(GtkWidget *box, GtkWidget *widget);
243 
244 // Put vbox into container
245 
246 GtkWidget *add_vbox(GtkWidget *cont);
247 
248 // Fix for paned widgets losing focus in GTK+1
249 
250 #if GTK_MAJOR_VERSION == 1
251 void paned_mouse_fix(GtkWidget *widget);
252 #else
253 #define paned_mouse_fix(X)
254 #endif
255 
256 // Init-time bugfixes
257 
258 void gtk_init_bugfixes();
259 
260 // Moving mouse cursor
261 
262 int move_mouse_relative(int dx, int dy);
263 
264 // Mapping keyval to key
265 
266 guint real_key(GdkEventKey *event);
267 guint low_key(GdkEventKey *event);
268 guint keyval_key(guint keyval);
269 
270 // Interpreting arrow keys
271 
272 int arrow_key_(unsigned key, unsigned state, int *dx, int *dy, int mult);
273 #define arrow_key(E,X,Y,M) arrow_key_((E)->keyval, (E)->state, (X), (Y), (M))
274 
275 // Create pixmap cursor
276 
277 GdkCursor *make_cursor(char *icon, char *mask, int w, int h, int tip_x, int tip_y);
278 
279 // Menu-like combo box
280 
281 GtkWidget *wj_combo_box(char **names, int cnt, int u, int idx, void **r,
282 	GtkSignalFunc handler);
283 int wj_combo_box_get_history(GtkWidget *combobox);
284 
285 #if GTK_MAJOR_VERSION == 3
286 // Bin widget with customizable size handling
287 
288 GtkWidget *wjsizebin_new(GCallback get_size, GCallback size_alloc, gpointer user_data);
289 #else
290 // Box widget with customizable size handling
291 
292 GtkWidget *wj_size_box();
293 #endif
294 
295 // Disable visual updates while tweaking container's contents
296 
297 gpointer toggle_updates(GtkWidget *widget, gpointer unlock);
298 
299 // Maximized & iconified states
300 
301 #if GTK_MAJOR_VERSION == 1
302 int is_maximized(GtkWidget *window);
303 void set_maximized(GtkWidget *window);
304 #elif GTK_MAJOR_VERSION == 2
305 #define is_maximized(W) \
306 	(!!(gdk_window_get_state((W)->window) & GDK_WINDOW_STATE_MAXIMIZED))
307 #define set_maximized(W) gtk_window_maximize(W)
308 #else /* if GTK_MAJOR_VERSION == 3 */
309 #define is_maximized(W) gtk_window_is_maximized(GTK_WINDOW(W))
310 #define set_maximized(W) gtk_window_maximize(W)
311 #endif
312 void set_iconify(GtkWidget *window, int state);
313 
314 // Drawable to RGB
315 
316 #if GTK_MAJOR_VERSION == 3
317 unsigned char *wj_get_rgb_image(GdkWindow *window, cairo_surface_t *s,
318 	unsigned char *buf, int x, int y, int width, int height);
319 #else /* if GTK_MAJOR_VERSION <= 2 */
320 unsigned char *wj_get_rgb_image(GdkWindow *window, GdkPixmap *pixmap,
321 	unsigned char *buf, int x, int y, int width, int height);
322 #endif
323 
324 // Clipboard
325 
326 int internal_clipboard(int which);
327 
328 // Clipboard pixmaps
329 
330 typedef unsigned long XID_type;
331 
332 typedef struct {
333 	int w, h, depth;
334 	XID_type xid;
335 #if GTK_MAJOR_VERSION == 3
336 	cairo_surface_t *pm;
337 #else /* if GTK_MAJOR_VERSION <= 2 */
338 	GdkPixmap *pm;
339 #endif
340 } pixmap_info;
341 
342 #if (GTK_MAJOR_VERSION == 1) || defined GDK_WINDOWING_X11
343 #define HAVE_PIXMAPS
344 int export_pixmap(pixmap_info *p, int w, int h);
345 void pixmap_put_rows(pixmap_info *p, unsigned char *src, int y, int cnt);
346 #endif
347 int import_pixmap(pixmap_info *p, XID_type *xid); // xid = NULL for a screenshot
348 void drop_pixmap(pixmap_info *p);
349 int pixmap_get_rows(pixmap_info *p, unsigned char *dest, int y, int cnt);
350 
351 // Image widget
352 
353 GtkWidget *xpm_image(XPM_TYPE xpm);
354 
355 // Render stock icons to pixmaps
356 
357 /* !!! Mask needs be zeroed before the call - especially with GTK+1 :-) */
358 #if GTK_MAJOR_VERSION == 1
359 #define render_stock_pixmap(X,Y,Z) NULL
360 #elif GTK_MAJOR_VERSION == 2
361 GdkPixmap *render_stock_pixmap(GtkWidget *widget, const gchar *stock_id,
362 	GdkBitmap **mask);
363 #endif
364 
365 // Release outstanding pointer grabs
366 
367 int release_grab();
368 
369 // Frame widget with passthrough scrolling
370 
371 GtkWidget *wjframe_new();
372 
373 // Scrollable canvas widget
374 
375 GtkWidget *wjcanvas_new();
376 void wjcanvas_set_expose(GtkWidget *widget, GtkSignalFunc handler, gpointer user_data);
377 void wjcanvas_size(GtkWidget *widget, int width, int height);
378 void wjcanvas_get_vport(GtkWidget *widget, int *vport);
379 int wjcanvas_scroll_in(GtkWidget *widget, int x, int y);
380 int wjcanvas_bind_mouse(GtkWidget *widget, GdkEventMotion *event, int x, int y);
381 #if GTK_MAJOR_VERSION == 3
382 void wjcanvas_uncache(GtkWidget *widget, int *rxy);
383 void wjcanvas_draw_rgb(GtkWidget *widget, int x, int y, int w, int h,
384 	unsigned char *rgb, int step, int fill, int repaint);
385 #else /* if GTK_MAJOR_VERSION <= 2 */
386 #define wjcanvas_uncache(A,B)
387 #endif
388 
389 // Focusable pixmap widget
390 
391 GtkWidget *wjpixmap_new(int width, int height);
392 #if GTK_MAJOR_VERSION == 3
393 cairo_surface_t *wjpixmap_pixmap(GtkWidget *widget);
394 #else /* if GTK_MAJOR_VERSION <= 2 */
395 GdkPixmap *wjpixmap_pixmap(GtkWidget *widget);
396 #endif
397 void wjpixmap_draw_rgb(GtkWidget *widget, int x, int y, int w, int h,
398 	unsigned char *rgb, int step);
399 void wjpixmap_move_cursor(GtkWidget *widget, int x, int y);
400 void wjpixmap_set_cursor(GtkWidget *widget, char *image, char *mask,
401 	int width, int height, int hot_x, int hot_y, int focused);
402 int wjpixmap_rxy(GtkWidget *widget, int x, int y, int *xr, int *yr);
403 
404 // Type of pathname
405 
406 #define PT_ABS 0	/* Absolute */
407 #define PT_REL 1	/* Relative */
408 #define PT_DRIVE_ABS 2	/* On Windows: absolute w/o drive (\DIR\FILE) */
409 #define PT_DRIVE_REL 3	/* On Windows: relative with drive (C:FILE) */
410 
411 int path_type(char *path);
412 
413 // Convert pathname to absolute
414 
415 char *resolve_path(char *buf, int buflen, char *path);
416 
417 // A (better) substitute for fnmatch(), in case one is needed
418 
419 #if defined(WIN32) || ((GTK_MAJOR_VERSION == 2) && (GTK2VERSION < 4))
420 int wjfnmatch(const char *mask, const char *str, int utf);
421 #endif
422 
423 // Replace '/' path separators
424 
425 #ifdef WIN32
426 void reseparate(char *str);
427 #endif
428 
429 // Process event queue
430 
431 void handle_events();
432 
433 // Make GtkEntry accept Ctrl+Enter as a character
434 
435 void accept_ctrl_enter(GtkWidget *entry);
436 
437 // Grab/ungrab input
438 
439 #define GRAB_FULL    0 /* Redirect everything */
440 #define GRAB_WIDGET  1 /* Redirect everything outside widget */
441 #define GRAB_PROGRAM 2 /* Redirect everything outside program's windows */
442 
443 int do_grab(int mode, GtkWidget *widget, GdkCursor *cursor);
444 void undo_grab(GtkWidget *widget);
445 
446 // Workaround for crazy GTK+1 resize handling
447 
448 #if GTK_MAJOR_VERSION == 1
449 void force_resize(GtkWidget *widget);
450 #endif
451 
452 // Workaround for broken GTK_SHADOW_NONE viewports in GTK+1
453 
454 #if GTK_MAJOR_VERSION == 1
455 void vport_noshadow_fix(GtkWidget *widget);
456 #else
457 #define vport_noshadow_fix(X)
458 #endif
459 
460 // Helper for accessing scrollbars
461 
462 void get_scroll_adjustments(GtkWidget *win, GtkAdjustment **h, GtkAdjustment **v);
463 
464 // Helper for widget show/hide
465 
466 void widget_showhide(GtkWidget *widget, int what);
467 
468 // Color name to value
469 
470 int parse_color(char *what);
471 
472 //	DPI value
473 
474 double window_dpi(GtkWidget *win);
475 
476 //	Interface scale
477 
478 #if GTK_MAJOR_VERSION == 3
479 #define window_scale(W) gtk_widget_get_scale_factor(W)
480 #else /* if GTK_MAJOR_VERSION <= 2 */
481 #define window_scale(W) 1
482 #endif
483 
484 //	Memory size (Mb)
485 
486 unsigned sys_mem_size();
487 
488 // Filtering bogus xine-ui "keypresses" (Linux only)
489 #ifdef WIN32
490 #define XINE_FAKERY(key) 0
491 #else
492 #define XINE_FAKERY(key) (((key) == KEY(Shift_L)) || ((key) == KEY(Control_L)) \
493 	|| ((key) == KEY(Scroll_Lock)) || ((key) == KEY(Num_Lock)))
494 #endif
495 
496 // Workaround for stupid GTK1 typecasts
497 #if GTK_MAJOR_VERSION == 1
498 #define GTK_RADIO_BUTTON_0(X) (GtkRadioButton *)(X)
499 #else
500 #define GTK_RADIO_BUTTON_0(X) GTK_RADIO_BUTTON(X)
501 #endif
502 
503 // Path separator char
504 #ifdef WIN32
505 #define DIR_SEP '\\'
506 #define DIR_SEP_STR "\\"
507 #else
508 #define DIR_SEP '/'
509 #define DIR_SEP_STR "/"
510 #endif
511 
512 // Path string sizes
513 /* If path is longer than this, it is user's own problem */
514 #define SANE_PATH_LEN 2048
515 
516 #ifdef WIN32
517 #define PATHBUF 260 /* MinGW defines PATH_MAX to not include terminating NUL */
518 #elif defined MAXPATHLEN
519 #define PATHBUF MAXPATHLEN /* MAXPATHLEN includes the NUL */
520 #elif defined PATH_MAX
521 #define PATHBUF PATH_MAX /* POSIXly correct PATH_MAX does too */
522 #else
523 #define PATHBUF SANE_PATH_LEN /* Arbitrary limit for GNU Hurd and the like */
524 #endif
525 #if PATHBUF > SANE_PATH_LEN /* Force a sane limit */
526 #undef PATHBUF
527 #define PATHBUF SANE_PATH_LEN
528 #endif
529 
530 #if GTK_MAJOR_VERSION == 1 /* Same encoding in GTK+1 */
531 #define PATHTXT PATHBUF
532 #else /* Allow for expansion when converting from codepage to UTF8 */
533 #define PATHTXT (PATHBUF * 2)
534 #endif
535 
536 // Filename string size
537 
538 #ifdef WIN32
539 #define NAMEBUF 256 /* MinGW doesn't define MAXNAMLEN nor NAME_MAX */
540 #elif defined MAXNAMLEN
541 #define NAMEBUF (MAXNAMLEN + 1)
542 #elif defined NAME_MAX
543 #define NAMEBUF (NAME_MAX + 1)
544 #else
545 #define NAMEBUF 256 /* Most filesystems limit filenames to 255 bytes or less */
546 #endif
547 
548 // Threading helpers
549 
550 #if 0 /* Not needed for now - GTK+/Win32 still isn't thread-safe anyway */
551 //#ifdef U_THREADS
552 guint threads_idle_add_priority(gint priority, GtkFunction function, gpointer data);
553 guint threads_timeout_add(guint32 interval, GSourceFunc function, gpointer data);
554 #define THREADS_ENTER() gdk_threads_enter()
555 #define THREADS_LEAVE() gdk_threads_leave()
556 #else
557 #if GTK_MAJOR_VERSION == 3
558 #define GTK_PRIORITY_REDRAW GDK_PRIORITY_REDRAW
559 #define GtkFunction GSourceFunc
560 #define threads_idle_add_priority(X,Y,Z) g_idle_add_full(X,Y,Z,NULL)
561 #define gtk_idle_remove(A) g_source_remove(A)
562 #else
563 #define threads_idle_add_priority(X,Y,Z) gtk_idle_add_priority(X,Y,Z)
564 #endif
565 #define threads_timeout_add(X,Y,Z) g_timeout_add(X,Y,Z)
566 #define THREADS_ENTER()
567 #define THREADS_LEAVE()
568 #endif
569