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